コンテナアプリケーションのローカル開発環境では、諸々のセットアップにdocker composeを使うことってよくありますよね。むしろローカルにk8sを起動してる方の方が少ないかもしれません。
composeで困るのが定期実行系のジョブです。k8sではCronJobというがあって、crontabのフォーマットで実行タイミングを記述しておくと、指定された時間にJobが動き出します。composeはこれができなくて(できる?)定期実行系のアプリケーションコンテナにcronをインストールして、無理やりこの仕組みを作らなければいけません。
この方法で作られたコンテナはイメージサイズは増加しますし、rootで動かす必要があります。これをそのままデプロイするわけにはいかないのでcronをインストールしないバージョンのDockerfileも必要になります。ちょっと気持ち悪いですよね。
そこで今回は定期実行の仕組みを提供するofeliaを使ってみます。
ofelia
OfeliaはDocker環境向けに設計された軽量なジョブスケジューラーです。Go言語で実装されており、Dockerコンテナ内で定期的にコマンドを実行するための機能を提供します。
主な昨日は以下の4つです。
job-exec
既存の実行中コンテナ内でコマンドを実行します。DBコンテナでバックアップコマンドを実行するのとかで使うといいかもしれないですね。
job-run
指定したイメージから新しいコンテナを起動してコマンドを実行します。完了後はコンテナを削除してくれるので、一時的なバッチ処理に使えそうです。
job-local
ofeliaが動作しているホスト上(docker compose upしたマシン)でコマンドを実行します。これの使い道がよくわかりません。
job-service-run
Docker Swarm環境で一度だけ実行されるサービスとしてジョブを起動してくれるみたいです。Swarm使ってないのでよくわかりません。
今回はjob-execとjob-runの2つを使ってみたいと思います。
お試し
ofeliaにどのように動作させるかはINIファイルで指定する方法と、コンテナラベルで指定する方法があるみたいです。
INIファイルで指定
# config.ini [job-exec "database-dump"] schedule = 0 58 16 * * * container = postgres command = sh -c 'pg_dump -U myuser mydb > /backup/dump-`date +%s`.sql'
scheduleでは「秒 分 時 日 月 曜日」の順に指定しています。通常のcronに「秒」の項目が追加された感じです。
containerでは実行されるコンテナ名を指定します。1点注意なのですが、docker composeのコンテナ名は「project-container-1」のような名前になります。compose.yamlでコンテナ名を固定するといいでしょう。「container_name: postgres」
commandにはこのコンテナで実行したいコマンドを記載します。「bg_dump > dump.sql」にはリダイレクトが含まれていてコマンドとして認識されないのでsh -cの中で実行するようにしています。
# compose.yaml
services:
postgres:
image: postgres
container_name: postgres
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: example
POSTGRES_DB: mydb
TZ: Asia/Tokyo
volumes:
- ./backup:/backup
ofelia:
image: mcuadros/ofelia:latest
depends_on:
- postgres
command: daemon --config=/etc/ofelia/config.ini
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./config.ini:/etc/ofelia/config.ini
environment:
TZ: Asia/Tokyo
services.postgresの設定で、container_nameでコンテナ名を固定している以外は普通ですね。TZでタイムゾーンをJSTにしてるくらいです。
services.ofeliaではofeliaの設定をしています。
depends_onで起動順を制御しています。ofeliaでダンプを実行した時にpostgresが起動していなかったらダンプが失敗するので、ofeliaよりも先にpostgresが起動するようにしています。
volumesの「/var/run/docker.sock:/var/run/docker.sock:ro」はofeliaの中から他のコンテナに命令を投げられるようにするためにマウントしています。
environmentのTZはタイムゾーンをJSTにしています。ここでタイムゾーンを設定しておかないとofeliaは(おそらく)UTCで動くので、scheduleで設定した時間が別のタイムゾーンで動いてしまいます。
ここまでの状態でdocker comopseを実行すると、下記のようにジョブが登録され、時間がきたら登録したジョブが実行されます。dump.sqlの作成時刻がconfig.iniで登録した時刻と一致しています。

コンテナラベルで指定
config.iniをつかわなくともコンテナラベルから設定できます。設定ファイルの方が見やすいですが、ファイルが増えるとあっちこっちファイルを探さなくていけなくなるので、これくらいの設定だったらラベルで書いてしまった方がいいかもしれません。
services:
postgres:
image: postgres
container_name: postgres
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: example
POSTGRES_DB: mydb
TZ: Asia/Tokyo
volumes:
- ./backup:/backup
labels:
ofelia.enabled: "true"
ofelia.job-exec.database-dump.schedule: "0 6 17 * * *"
ofelia.job-exec.database-dump.command: "sh -c 'pg_dump -U myuser mydb > /backup/dump-`date +%s`.sql'"
ofelia:
image: mcuadros/ofelia:latest
depends_on:
- postgres
command: daemon --docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
TZ: Asia/Tokyo
定期実行したいサービス(今回だとpostgres)のラベルにofeliaの設定を書いてあげると、起動時にofeliaがそのラベルを読み込みジョブを設定します。
ラベルのofelia.job-exec.に続く文字列がジョブ名です。一意でわかりやすいの名前を設定しましょう。
job-runの動作
今まではjob-execを指定しました。job-execは既存のコンテナで指定時間に指定のコマンドを実行します。コンテナにツールを内包している場合はこれでいいでしょう。例えば、postgresコンテナにはpg_dumpコマンドが含まれていますし、DBもこのコンテナ内にあるのでこういう場合はjob-execの方が都合がいいです。
job-runは新しいコンテナを起動し、処理が終わったら削除します。例えば一定期間経ったバックアップを削除する処理でfindコマンドを使用したい場合、postgresコンテナにfindコマンドがインストールされている必要があります(インストールされているかは確認していません)。こういう場合、使いたいツールを既存のコンテナに追加するのではなく、別途ツールがインストールされたコンテナを起動して、処理を実行する方がスマートですよね。
services:
postgres:
image: postgres
container_name: postgres
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: example
POSTGRES_DB: mydb
TZ: Asia/Tokyo
volumes:
- ./backup:/backup
labels:
ofelia.enabled: "true"
ofelia.job-exec.database-dump.schedule: "0 6 17 * * *"
ofelia.job-exec.database-dump.command: "sh -c 'pg_dump -U myuser mydb > /backup/dump-`date +%s`.sql'"
ofelia:
image: mcuadros/ofelia:latest
depends_on:
- postgres
command: daemon --docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
TZ: Asia/Tokyo
labels:
ofelia.enabled: "true"
ofelia.job-run.cleanup-backup.schedule: "0 0 18 * * *"
ofelia.job-run.cleanup-backup.image: "alpine:latest"
ofelia.job-run.cleanup-backup.volume: "$PWD/backup:/backup"
ofelia.job-run.cleanup-backup.command: "find /backup -name '*.sql' -mtime +7 -delete"
ofeliaのラベルにjob-runを追加しました。imageで指定したイメージを起動し、volumeで指定したパスをマウントし、commandで指定したコマンドを実行します。ラベルのvolumeは相対パスで指定できないので$PWD/backupにしています。
実行結果は↓

試すために実行時間をそれぞれ変更していますが、pg_dump -> find -deleteが成功しているのがログからわかります。
まとめ
わざわざcronコンテナを用意しなくてofeliaで定期実行が実現できました。job-execでは既存コンテナにコマンドを実行するため、既存コンテナで動いているアプリケーションで実行したい処理を書く、job-runでは任意の新規コンテナでコマンドを実行できるため、既存コンテナにインストールしたくないコマンドをこっちで実行するのがよさそうです。
