とことんDevOps | 日本仮想化技術のDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けする「とことんDevOps」では、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果、TipsなどDevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、コンテナ開発、IaCなど
読者登録と各種SNSのフォローもよろしくお願いいたします。

ofeliaでコンテナを定期実行する

コンテナアプリケーションのローカル開発環境では、諸々のセットアップにdocker composeを使うことってよくありますよね。むしろローカルにk8sを起動してる方の方が少ないかもしれません。

composeで困るのが定期実行系のジョブです。k8sではCronJobというがあって、crontabのフォーマットで実行タイミングを記述しておくと、指定された時間にJobが動き出します。composeはこれができなくて(できる?)定期実行系のアプリケーションコンテナにcronをインストールして、無理やりこの仕組みを作らなければいけません。

この方法で作られたコンテナはイメージサイズは増加しますし、rootで動かす必要があります。これをそのままデプロイするわけにはいかないのでcronをインストールしないバージョンのDockerfileも必要になります。ちょっと気持ち悪いですよね。

そこで今回は定期実行の仕組みを提供するofeliaを使ってみます。

ofelia

github.com

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では任意の新規コンテナでコマンドを実行できるため、既存コンテナにインストールしたくないコマンドをこっちで実行するのがよさそうです。