Dockerコンテナ内でcronを実行する方法

Dockerコンテナ内でcronを実行する方法

はじめに

今回の記事では、Docker コンテナ内で cron を実行する方法について解説しています。

実行環境は PHP で、アプリケーション(FPM)だけ動作している環境で検証しています(Web サーバのコンテナは立てていない)

使用しているエディタは VS Code で、Remote Developmentを使用して Ubuntu サーバに SSH 接続してコンテナを起動しています。

開発環境

  • OS: Ubuntu 20.04.6 LTS
  • PHP: 8.2.8
  • Docker: 24.0.2, build cb74dfc

コンテナ内で起動できるプロセス

通常、Docker コンテナ内で動作するフォアグラウンドプロセスは 1 つだけに設計されています。

また、そのコンテナ内のフォアグラウンドプロセスが終了するとコンテナ自体も停止してしまいます。

つまり、今回のようにcronFPMのプロセスの 2 つをフォアグラウンドで起動することは出来ません。しかし、コンテナ起動時に以下のようなシェルスクリプトを実行することでcronはバックグランド、FPMはフォアグラウンドで起動する事が出来ます。

#!/bin/sh

# バックグラウンドでcronを起動
cron & # &はバックグランドで実行するという意味

# フォアグラウンドでPHP-FPMを起動
php-fpm

ただし、バックグランドで実行されているプロセスのログの出力やクラッシュした際の再起動など、ライフサイクルの扱いが難しくなり推奨されていません。

そこで今回は、プロセスの管理をすることが出来るツールであるSupervisorを使用したいと思います。

Dockerfile の内容

今回使用する Dockerfile の内容です。

基本的なツールのインストールの他にcronsupervisorを記述します。

supervisorというツールは、UNIX 系 OS 上でプロセスを監視・制御するためのツールです。複数のプロセスを管理したり、管理下のプロセスのクラッシュ時に自動再起動を実施して永続化したりすることが出きます。

".devcontainer/php/Dockerfile"
FROM php:8.2.8-fpm

RUN apt-get update && \
    # cron と supervisor を追加
    apt-get -y install git cron supervisor libicu-dev libonig-dev libzip-dev unzip locales && \
    apt-get clean && \
    docker-php-ext-install intl pdo_mysql mbstring zip bcmath && \
    pecl install xdebug && \
    # Xdebug を有効化する
    docker-php-ext-enable xdebug

COPY ./php.ini /usr/local/etc/php/php.ini
# cronの設定内容が書かれたファイルをコンテナ内にコピーする
COPY ./cron/root /etc/cron.d/cron
# supervisordの設定ファイルをコピーする
COPY ./supervisord/supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# 権限の変更
RUN chmod 0644 /etc/cron.d/* && \
    touch /var/log/cron.log && \
    # 作業用ユーザーの追加
    useradd -m -s /bin/bash user01 && \
    # Xebugで使用するログファイルの作成とファイルへのアクセス権限を付与する
    touch /var/log/xdebug.log && \
    chown user01:user01 /var/log/xdebug.log && \
    chmod 644 /var/log/xdebug.log

WORKDIR /workspaces

# supervisordを起動
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

次は各種設定ファイルを作成します。

cron の設定ファイルを作成する

スケジュールされた実行ジョブを記述した設定ファイルを作成します。

設定ファイルの内容に関しては、cron の設定ガイドを参考にして下さい。

".devcontainer/php/cron/root"
# 設定ファイルの末尾に改行必須!
* * * * * root /usr/local/bin/php -q -f /workspace/public/index.php >> /var/log/cron.log 2>&1
# (ここに改行)

注意点として、cron の設定ファイルは行が改行で終端している必要があります。改行がない場合は指定したジョブは実行されません。

例として、以下の設定ファイルの場合index2.phpの後に改行が無いためindex2.phpのジョブは実行されません。

* * * * * root /usr/local/bin/php -q -f /index.php
* * * * * root /usr/local/bin/php -q -f /index2.php

index.php ファイルの作成

実行されるスクリプトファイルであるindex.phpも作成しておきます。

"/workspace/public/index.php"
<?php

echo "Hello PHP Cron!!" . "\n";

supervisord の設定ファイルを作成する

supervisord の設定ファイルを作成します。詳細はコメントに記載されている通りです。

".devcontainer/php/supervisord/supervisord.conf"
[supervisord]
nodaemon=true ; デーモンで起動させずにフォアグラウンドで起動させる
logfile=/tmp/supervisord.log ; (メインログファイルの場所を指定. デフォルトは $CWD/supervisord.log)
logfile_maxbytes=50MB        ; (ログローテーションの起点となるサイズ. デフォルトは 50MB)
logfile_backups=10           ; (ログファイルのローテーション時に保持するバックアップの数を指定. デフォルトは 10)
loglevel=info                ; (supervisordのログレベルを指定. デフォルトは info レベル; その他: debug, warn, trace)

[program:php-fpm]
command=php-fpm -F # フォアグラウンドで実行するコマンドを指定
stdout_logfile=/tmp/php-fpm.log # このプログラムでの標準出力のログの保存場所を指定
redirect_stderr=true # 標準エラー出力を行うか否か

[program:cron]
command=cron -f # フォアグラウンドで実行するコマンドを指定
stdout_logfile=/tmp/cron.log # このプログラムでの標準出力のログの保存場所を指定
redirect_stderr=true # 標準エラー出力を行うか否か

ここまで設定できたら一度コンテナを起動します。

cron が実行されているか確認する

コンテナ起動後、プロセスが正常に起動されているか確認します。

そのためにpsコマンドを含むパッケージをインストールします。このコマンドは cron やその他のプロセスが起動しているか確認したいときのみインストールして下さい。

rootユーザーである必要があるので、devcontainer でコンテナにアタッチした際のユーザーではなくdocker execコマンドでコンテナにアクセスします。

# コンテナIDを表示する
docker ps

# コンテナに接続
docker exec -it [コンテナID] bash

apt-get update && apt-get install -y procps

インストール後ps auxコマンドを使用してプロセスのリストを取得します。

docker-process.png

どうやら正常に起動しているようです。次に、cron ファイルに設定したプログラムが正常に実行されているか確認します。

実行結果のログは/var/log/cron.logに出力するように設定していました。

cron-execute.png

1 分おきに php スクリプトが実行されていることが分かります。

今回作成した環境は以下のリポジトリから利用することが出来ます。