[Node.js] node-cronをDockerコンテナから使ってスケジュール実行をさせてみる


2020/09/06

首題のように
node-cronを常駐させたDockerコンテナから定期実行されるか実験してみます。


はじめに

前回はホスト側のcronによってdockerコンテナ内のプログラムを定期実行させる事例を紹介しました。

ですがこれだと、ホストOSがcronの使えないWindowsなどのLinux以外のOSではこの方法が利用できません。

そんなときにはnode-cronを利用することで、コンテナ内のnode.jsで実装したプログラムをcronのように定期実行できるようになります。

この場合には、ホストOSのデーモン機能が無くてもコンテナを常時起動しているだけで簡単なスケジューラーとすることが可能です。


Dockerコンテナの構築

例のように例のごとくDockerとdocker-composeのインストール済みの環境であることが前提です。

これらのインストール方法などは割愛させていただきます。

手元の環境は以下の通りですが、ホストOSはDockerが動作すればOKです。

            
            $ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 10 (buster)
Release: 10
Codename: buster
$ docker -v
Docker version 19.03.8, build afacb8b7f0
$ docker-compose -v
docker-compose version 1.16.1, build 6d1ac21
        

プロジェクトのファイル構造の下準備

まず今回の内容を試すのに必要最低限のファイル・フォルダを以下のように与えておきましょう。

どこかの適当なフォルダに移って、以下のようにします。

            
            $ touch Dockerfile docker-compose.yml index.js package.json && mkdir tmp
$ tree
.
├── Dockerfile
├── docker-compose.yml
├── index.js
├── package.json
└── tmp #👈空のフォルダ
        
以下ではこのリソースファイルの中身を編集していきます。

Dockerfile

ベースイメージはnode-alpineですが、nodeのバージョンやalpineのリビジョンはなんでもOKです。

            
            FROM node:12-alpine
ENV NODE_ENV development
WORKDIR /usr/src/app

COPY package.json ./

RUN yarn install && yarn cache clean

CMD ["node", "index.js"]
        

docker-compose.yml

Dockerイメージのビルドにdocker-composeを使います。

docker-composeのサービス名は
cronで登録してみます。

            
            version: '3'

services:
  cron:
    image: node-cron-dckr:12-alpine
    build: .
    container_name: my-node-cron
    user: 'node:node'
    environment:
      NODE_ENV: development
    volumes:
        - ./:/usr/src/app
    tty: true
        

index.js

cronのスケジュール文法に関しては後述しています。

毎分実行する相当の
* * * * *を使い、tmpフォルダに文字を書き込んでみます。

            
            const cron = require('node-cron');
const fs = require("fs");

cron.schedule('* * * * *', () => {
    try{
        console.log('running a task every minute');
        fs.appendFileSync("tmp/file.txt", "Hello Node-Cron!\n");
    }
    catch(e){
        console.log(e.message);
    }
});
        

package.json

package.jsonを以下の内容で与えておきます。

            
            {
    "name": "node-cron-dckr",
    "version": "0.0.1",
    "devDependencies": {
        "node-cron": "^2.0.0"
    }
}
        
ここは、devDependenciesをなくして、Dockerfile側でRUN yarn add node-cron -Dとしてもコンテナにnode-cronがインストールされます。

イメージのビルド

以上のDockerイメージをビルドします。

            
            $ docker-compose build
$ docker images
REPOSITORY       TAG         IMAGE ID      SIZE
node-cron-dckr   12-alpine   76bed01fec57  104MB
        
これで今回のイメージがスタンバイできました。


Cronのスケジュール文法

cronを扱ったことのある方なら分かっていらっしゃると思います。

まずはちょこっとだけcronのスケジュール表記の復習です。

cronのタスクをスケジュールするのに、スペース切りの5つの数字かアスタリスクの羅列する箇所(
* * * * *みたいなところ)があります。

この5つの数値でcronのスケジュールが管理されます。

cat /etc/crontabと叩くとおおよそのスケジュール文法の概要がコメントで書かれいますが、抜粋すると

            
            Example of job definition:
 .---------------- minute (0 - 59)
 |  .------------- hour (0 - 23)
 |  |  .---------- day of month (1 - 31)
 |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
 |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
 |  |  |  |  |
 *  *  *  *  * user-name command to be executed
        
と説明書きがあります。

ざっと左から、分、時、日、月、曜日、となります。

anyを意味する*を使うと、毎〜として解釈されます。

例えば、よくあるパターンの
* * * * *は回りくどくいうと、毎曜日毎月毎日毎時毎分...つまり毎分実行するという意味です。

            
            * * * * 1:
    月曜日限定で毎分実行
10 * * * *:
    毎時間、時計の針が10分になったら実行
    (..., 8:10, 9:10, 10:10, ...)
20 8 * * *:
    毎日午前8時20分になったら実行
        
あと当然ですが、秒単位での実行指定はcronではできません。

なので、別のタイマーライブラリなどを利用することになります。


コンテナの起動

ではデタッチモード(バックグラウンド)で起動します。

            
            $ docker-compose run -d cron
        
しばらく経ってから、tmpフォルダにファイルが書き込まれているのを確認したら、cronが定期実行されています。

            
            $ cat tmp/file.txt
Hello Node-Cron!
Hello Node-Cron!
Hello Node-Cron!
Hello Node-Cron!
#...以降一分毎に更新
        

止め方・消し方

今回はnode-cronのテスト程度ですのでいつまでも動かしている必要がないため、終わったら止めておきます。

まずは
docker psで動いているコンテナのID値か名前を探します。

            
            $ docker ps
CONTAINER ID        IMAGE                      COMMAND                  STATUS   NAMES
0639a40822ed        node-cron-dckr:12-alpine   "docker-entrypoint.s…"  Up       nodecrondckr_cron_run_1
        
なおdocker-composeで起動したコンテナ名は、<イメージ名>_<サービス名>_run_<起動しているコンテナ数>という命名ルールで自動登録されているはずです。

            
            #👇コンテナを止める場合
$ docker stop nodecrondckr_cron_run_1
nodecrondckr_cron_run_1
#👇コンテナを消す場合
$ docker rm nodecrondckr_cron_run_1
nodecrondckr_cron_run_1
        


まとめ

node-cronを使えば、cronデーモンのdocker版のようなプログラムが簡単に作成できます。

shellプログラミングが苦手な方も簡単にスケジューラーがカスタマイズできます。

また今回紹介した方法ならばdockerが使える環境は必要ですがホストOSは問わないので、共通したプログラムで同じ結果が得られるのがメリットです。


参考サイト

Scheduling Cron Jobs in Node.js with Node-Cron(英語サイト)

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。