【docker】コンテナ内で環境変数を読み込んでcronを実行する

# はじめに
Docker環境でバッチ処理を実装するために、コンテナ内でcronを実行する方法について書きます。

コンテナ内でcronを実行する際は、ホスト側から設定した環境変数を読み込んでいないため、それを読み込むための工夫が必要になります。
今回はスクリプトファイルを作成・実行し、cron実行時前に環境変数を読み込む方法で実装してみました。
また、cronを専用の別コンテナに立てて実行する方法もありますが、今回は別プロセス(php)のコンテナ内にcronをインストールして実行する方法で実装しています。

この方法以外にもcronを実行できる方法はいくつかあると思いますが、１つの例として参考にしていただけると幸いです。
また、不備がありましたら是非ご指摘いただけると幸いです。

# 環境
* Docker Compose version v2.22.0  
* Docker Image: php version 8.2

# docker環境でcronを実行する方法
主に以下2つの方法が挙げられます。

* ホスト側から「docker exec」コマンドでcronを実行する方法
 手軽に実装できるが、ホスト環境に依存してしまう。
 
* コンテナ内からcronを実行する方法
 コンテナ内ではcron実行時にホスト側から設定した環境変数を読み込まないので実装に工夫が必要だが、ホスト環境に依存しない。

今回は両者を比較した結果、バージョン管理や再利用性などを考慮してコンテナ内からcronを実行する方法を選びました。

# 全体の流れ
コンテナ内でcronを実行できるようにするまでの大まかな流れです。

1. 必要なスクリプトファイルを作成する
    * 「entrypoint.sh」
コンテナ起動直後に実行するスクリプトファイル
cronを実行するために必要な環境変数設定スクリプトやcronの起動等を行う
    * 「cron.sh」
cronで実行するスクリプトファイル
環境変数を読み込んでからメインの処理を実行する
    <br>
2. Dockerfileを編集
1.で作成したスクリプトファイルを作成・実行するように追記します。
    <br>

3. docker-composeでコンテナ起動
cronが正しく起動できているかログを見て確認します。

## 1. スクリプトファイルを作成
必要なスクリプトファイルを作成します。
スクリプトファイルに実行権限を与えておかないとコンテナ起動やcron実行時に上手くいかない可能性があるので注意です。
```
touch entrypoint.sh
sudo chmod +x entrypoint.sh
touch cron.sh
sudo chmod +x cron.sh
```

まずは、コンテナ起動時に実行する「entrypoint.sh」を書いていきます。
```shell:entrypoint.sh
#!/bin/sh

# 環境変数設定スクリプト作成
printenv | awk '{print "export " $1}' > /root/env.sh

# cron 起動
service cron start

# php-fpm 起動
docker-php-entrypoint php-fpm
```
スクリプトの中を順番に解説します。
まず初めに環境変数設定スクリプトファイル（env.sh）を作成します。
env.shはprintenvで出力される各環境変数が、exportコマンドの引数となる形で出力されます。
exportコマンドは「環境変数を設定する」ための使うコマンドなので、env.shはホスト側から設定した環境変数を設定するためのスクリプトファイルということになります。
```shell:/root/env.sh
export HOSTNAME=*****
export PHP_VERSION=8.2.13
export PHP_INI_DIR=/usr/local/etc/php
export TZ=Asia/Tokyo
export HOME=/root
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
　・・・
　　(省略)
```

次に、「service cron start」でcronを起動します。

最後の「docker-php-entrypoint」コマンドは、php-fpmを起動させるためのコマンドです。
今回は、phpコンテナの中にcronをインストールして実行することを想定しているため、最後にphp-fpmを起動するためのコマンドを書いています。
その理由は、DockerfileでENTRYPOINT命令を用いてentrypoint.shを実行すると、元々のイメージファイルで実行する予定だったENTRYCOMMANDやCMDが上書きされてしまうからです。今回の場合だと、php-fpmが起動しなくなってしまいます。そのため、docker-php-entrypointコマンドを最後に実行することで、元々phpコンテナで実行予定だったエントリーポイントの上書きを回避しています。

ちなみに、cronを起動するコンテナがphpコンテナから独立している（＝cron専用のコンテナとして起動する）場合は、「cron -f」を実行すれば、cronがフォアグラウンドで実行するため問題なくコンテナ起動できます。
今回のように、コンテナ内でcronとは別でフォアグランドで実行させるプロセスがある場合は、エントリーポイントの上書きに注意してDockerfileを変更する必要があります。

さらに詳しく知りたい場合は、こちらの記事が参考になると思います。
[PHP-FPMのDockerfileのENTRYPOINTやCMDを上書きしたらエラーが起きた]()


次に、cronで実行するスクリプトファイル「cron.sh」を作成します。

```shell:cron.sh
#!/bin/sh

# 環境変数を設定
. /root/env.sh　

# 定期実行したい処理
date 1>/proc/1/fd/1
```

ポイントは、定期実行したい処理の前に、entrypoint.shで作成したenv.shを実行することです。
これによって、ホスト側から設定した環境変数をcron実行時にも設定できるので、その後の処理は環境変数が読み込まれた状態で実行されることになります。
今回はテストで、dateコマンドの出力をログに表示するようにします。

## 2. Dockerfileを編集
作成したスクリプトファイルをDockerfileに追記します。
```
FROM php:8.2-fpm

# スクリプトファイルをコピー
COPY ./docker/app/entrypoint.sh /tmp/
COPY ./docker/app/cron.sh /root/script/

　(省略)

# cronをインストール
RUN apt-get install -y cron

# crontabに登録
RUN echo '* * * * * sh /root/script/cron.sh' > /var/spool/cron/crontabs/root
RUN crontab /var/spool/cron/crontabs/root

# エントリーポイントの設定
ENTRYPOINT ["/tmp/entrypoint.sh"]
```
ポイントは、crontabコマンドを実行しておくことです。
crontabを登録する際は、cronファイルを「/var/spool/cron/crontabs/[ユーザ名]」に置くだけではcronデーモンに認識されないのでコンテナを起動しても実行されません。
そのため、最後にcrontabコマンドでファイルを指定して実行しておくことで、コンテナ起動後に自動でcronが実行されるようになります。

## 3. docker-composeでコンテナ起動
Dockerfileを変更したら、コンテナを起動してみます。

```
docker compose up -d --build
```
「docker ps -a」でコンテナが無事起動していることを確認したら、実際にログを見てcronが実行できているか確認します。
```
docker compose logs -f
```
実行結果：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3527297/ce94793d-cd0f-2457-4041-37dddecb9137.png)
無事、毎分cronでdateコマンドが実行されていることを確認できました。

今回のソースコードを自分のGitHubにも挙げているので参考にしてみてください。
https://github.com/messhii/docker-cron

# まとめ
Dockerのコンテナ内にcronをインストールして実行する方法について書きました。
ホスト側から設定した環境変数を読み込まない点や元のイメージファイルのエントリーポイントが上書きされる点などに気をつけてコンテナを起動すればコンテナ内でもcronを実行することができます。


---
参考サイト
[Dockerコンテナで"環境変数込みで"Cronを実行する方法](https://create-it-myself.com/know-how/how-to-run-cron-on-docker-container/)
[Docker コンテナ内でタスクを cron 起動する](https://qiita.com/rerofumi/items/fc0126c4e985b78f769b)
[Docker で Cron を設定しようとしたときにハマったこと](https://qiita.com/yokra9/items/f527a7892434d2464886)