まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

タグ:container

以前にとある人から
 (例えば仮想マシン環境と比較して)コンテナ環境のデメリットはなんですか?
と質問され、一瞬返答に詰まってしまったことがありました。メリットを考えたことはあったけど、積極的にデメリットを考えたことがなく、即答できませんでした。ちゃんと正しく理解していないとなかなか難しい質問だと思っています。

改めて時間のある時に考えてみるといくつか思いつきます。まあ「デメリット」といえるかどうかはともかく、「VMでできてコンテナでできないこと」はいくつかあります。

その一つが "cron" ジョブだと思っています。特定時刻とか、何分おきにとか、実行タイミングのスケジュールを決めた上で実行する機能です。例えば kubernetes であれば CronJob を使って実現するなど、厳密には「コンテナで実現できない」わけではないのですが、そのコンテナ環境に合わせた対応が必要になるのはそれはそれでデメリットになりえますよね。

一方、アプリケーション開発レベルでは、これらのスケジュールジョブを併用することでアプリケーションとしての必要な機能を実現することは珍しくありません。1分毎にどこかからデータを取得して更新するとか、毎日○時に自動的にバックアップを取得するとかといった場合です。それらの機能が必要なアプリケーションをコンテナ環境で動かす可能性がある中で実装する場合、どういった方法を検討する必要があるでしょうか?


その答えの1つが「アプリケーションレベルで(アプリケーションの機能の一部として) cron ジョブを実装する」方法です。Node.js アプリケーションの場合は Node-Schedule ライブラリを使うと簡単に実現できそうだったので、その内容を以下で紹介します:
2020071200



Node-Schedule は Node.js で使えるライブラリで、cron ライクなスケジュールジョブを比較的簡単に(setTimeout とかを意識することなく)実現できます。またスケジュールの定義フォーマットは cron のものと互換性があるので、crontab に1行追加する感覚で、アプリケーション内にスケジュールジョブを追加・更新・削除できるものです。アプリケーションの中でスケジュールジョブを定義できるので、アプリケーションの実行環境(実機とか、VM とか、コンテナとか、・・)を意識する必要もありません。

例を1つ記述しておきます。以下のコードで1分おきにコンソールにメッセージを表示するウェブアプリケーションが作成できます(Node-Schedule に関係している部分のみ赤字):
// app.js
var schedule = require( 'node-schedule' );
var express = require( 'express' );
var app = express();

//. 毎分実行
schedule.scheduleJob( '* * * * *', function(){
  console.log( 'running a task every minute' );
});


app.get( '/', function( req, res ){
  res.write( JSON.stringify( { status: true }, null, 2 ) );
  res.end();
});


var port = process.env.PORT || 8080;
app.listen( port );
console.log( 'server started on ' + port );

上記コードから赤字部分を抜くと、ごくシンプルな Node.js + Express のウェブアプリケーションになるのがわかると思います。つまり Node-Schedule に関係しているのは赤字部分の実質4行だけです。

で、その赤字部分で何をしているのかというと、まず先頭行で require() して Node-Schedule ライブラリのモジュールを呼び出します:
// app.js
var schedule = require( 'node-schedule' );

そして scheduleJob() メソッドを使ってジョブを(イメージとしては cron に)登録します:
//. 毎分実行
schedule.scheduleJob( '* * * * *', function(){
  console.log( 'running a task every minute' );
});

この第一パラメータは cron に登録する時に指定する時刻フォーマットと互換性のある文字列表現を使います。'* * * * *' は「毎分実行」を意味しています。

そして第二パラメータには該当時刻になったら実行するコールバック関数を指定します。上の例では 'running a task every minute' とコンソールに表示するだけの内容にしていますが、実際にはここに crontab の最後に指定するコマンドを登録することになります。これで1分ごとに 'running a task every minute' という文字列がコンソールに表示され続けるジョブが登録できたことになります。


なお、一度登録したジョブをキャンセルするには登録時の scheduleJob() メソッドの実行結果オブジェクトを受け取り、そのオブジェクトの cancel() メソッドを実行します:
//. 毎分実行
var job = schedule.scheduleJob( '* * * * *', function(){
  console.log( 'running a task every minute' );
});

  :

job.cancel(); //. 登録したジョブをキャンセル

ジョブの実行条件や実行内容を更新する場合は一度キャンセルしてから再登録することで実現できます。


最近のクラウド環境は PaaS 化が進み、どういうコンテナ環境を使っているのかよくわからないことがあるかもしれません。アプリケーションを Node.js で記述するという条件はありますが、この方法で実装していればコンテナ環境に依存しないスケジュールジョブが実現できそうです。


Ubuntu で LXC を使ってみました。

LXC は "LinuX Containers" の略で、Linux の仮想化技術の1つです。1つのセンターホストの上で複数の Linux システム(コンテナ)を走らせる、というものです。コンテナなので、いわゆる VM(Virtual Machines) とは異なり、個別のプロセスとネットワーク空間を作り出す仮想環境です。なお Ubuntu 14.04 の 64bit 版を前提として以下を記載します(いつもの CentOS6 環境だとなぜか上手くいきませんでした):

導入は簡単。apt-get コマンド一発で LXC を導入します:
$ sudo apt-get install lxc

導入できたら中身を一度確認してみます。まず用意されているテンプレートの一覧が /usr/share/lxc/templates/ にあるのでその一覧を確認します(青字が出力結果、これだけのテンプレートが用意されています):
$ ls /usr/share/lxc/templates/
lxc-alpine     lxc-centos    lxc-fedora        lxc-oracle  lxc-ubuntu-cloud
lxc-altlinux   lxc-cirros    lxc-gentoo        lxc-plamo
lxc-archlinux  lxc-debian    lxc-openmandriva  lxc-sshd
lxc-busybox    lxc-download  lxc-opensuse      lxc-ubuntu

では今回は Plamo テンプレート(lxc-plamo)を使って、p01 という名称で Plamo Linux コンテナを作成してみます:
$ sudo lxc-create -t plamo -n p01
  :
  :
  :
morse-2.1-x86_64-P1 のインストール中
PACKAGE DESCRIPTION:

qrq-0.1.4-x86_64-P1 のインストール中
PACKAGE DESCRIPTION:

Copy /var/cache/lxc/rootfs-plamo-5.x-x86_64 to /var/lib/lxc/p01/rootfs...
Copying /var/cache/lxc/rootfs-plamo-5.x-x86_64 to /var/lib/lxc/p01/rootfs...
patching file /var/lib/lxc/p01/rootfs/etc/inittab
Setting root password to 'root'...
Please change root password!

コマンドが完了するまでしばらくかかりますが、無事に完了したようです(root ユーザーのパスワードが root で、すぐに変更しろ、というメッセージが表示されています)。 作成されたコンテナのルートディレクトリは /var/lib/lxc/p01/rootfs/ 以下に作成されています(p01 はコンテナ名):
$ ls /var/lib/lxc/p01/rootfs/
bin   cdrom  etc           home  lib64  mnt   root  sbin  tmp  var
boot  dev    etc-incoming  lib   media  proc  run   sys   usr

ではこの Plamo Linux のコンテナを実際に起動してみましょう。名前を指定して、デーモンモードで起動して、コンソールに接続します:
$ sudo lxc-start -n p01 -d
$ sudo lxc-console -n p01

Connected to tty 1
Type  to exit the console,  to enter Ctrl+a itself


Welcome to Linux 3.13.0-85-generic.

p01 login:
 

接続できました!ここで root ユーザーで(パスワード root で)ログインし、とりあえずはパスワードを変更しておきます:
p01 login: root (ユーザー名は root)
Password: (パスワードは root)
root@p01:~# passwd
Enter new UNIX password: (新パスワードを入力)
Retype new UNIX password: (同じ新パスワードを入力)
passwd: password updated successfully

で、ここからはコンテナ上に展開された Plamo Linux を1サーバーのように使うことができるようになります:
root@p01:~# df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/dm-1       913G   16G  851G   2% /
none            100K     0  100K   0% /dev
/media          5.8G     0  5.8G   0% /media
/tmp            5.8G     0  5.8G   0% /tmp

元の(Ubuntu の)コンソールに戻るには Ctrl+A を押し、続けて Q を押します:
root@p01:~# exit (Plamo Linux から普通にログアウト)
logout


Welcome to Linux 3.13.0-85-generic.

p01 login: (ここで Ctrl+A,Q)
$ (Ubuntu のコマンドプロンプトへ戻った)





このページのトップヘ