Fly.io の「Secrets」に設定した値を「cron -f」経由の定期処理実行時に利用したい

### 前提

- 検証環境
    - MacBook Pro(Intel)
        - macOS Sonoma 14.5
- ローカル環境にFly.ioにデプロイ済みのアプリケーションがある。
    - デプロイ用のDockerfileとfly.tomlが作成済みの状態。
    - 「[Fly.io の「Cron and Queues」のページに従って定期処理の設定をやってみた](https://qiita.com/akase244/items/05e00f3b1e33a326ff29)」のように、「cron -f」による定期処理の環境が構築されていること。

---


Fly.ioの「Secrets」の機能で登録した値は「[Secrets and Fly Apps](https://fly.io/docs/apps/secrets/)」にて、環境変数として利用可能であることが説明されています。

Secretsで登録した値を「 `cron -f` 」経由で実行する定期処理の中で利用したいと考え、動作検証のために、まずSecretsの設定画面で以下のように「FLY_IO_SECRETS_VALUE」という値を追加しました。

![スクリーンショット 2024-09-09 0.48.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23093/0db40cf2-978c-f365-a92a-2d3408409f47.png)

`fly console` で仮想マシンにログインして、以下のコマンドを実行すると環境変数として設定した「FLY_IO_SECRETS_VALUE」にアクセスできることが確認できました。

```
root@XXXXXXXXXX0e98:/app# echo $FLY_IO_SECRETS_VALUE
fly_io_secrets_value
```

次に、仮想マシン内の `/etc/cron.d/crontab` に以下のような内容の定期処理を設定します。

```
*/1 * * * * root echo $FLY_IO_SECRETS_VALUE > /var/log/cron.log
```

上記の定期処理が実行されると、 `/var/log/cron.log` には何も記録されません。
これは、Fly.ioの問題という訳ではなく、cronがログインシェル用の環境変数を引き継がない仕組みだからのようです。

このあたりについては、「 `login shell` / `non-login shell` / `interactive shell` / `non-interactive shell` 」といったキーワードで検索してみると、以下のような情報が見つかりますので、こちらを参考にされるとよいかと思います。

- [What are interactive and non-interactive shells in Linux](https://medium.com/@linuxadminhacks/what-are-interactive-and-non-interactive-shells-in-linux-5f25ce19e537)
- [Differentiate Interactive login and non-interactive non-login shell](https://askubuntu.com/questions/879364/differentiate-interactive-login-and-non-interactive-non-login-shell)
- [login/non-login and interactive/non-interactive shells [duplicate]](https://unix.stackexchange.com/questions/170493/login-non-login-and-interactive-non-interactive-shells)
- [Do cron jobs run non-interactive, non-login shells? [closed]](https://stackoverflow.com/questions/23571928/do-cron-jobs-run-non-interactive-non-login-shells)
- [bashの.profileや.bashrc等を実行する動作仕様](https://blog.sgry.jp/entry/2019/11/09/232927)
- [interactive shell, login shellとは](https://qiita.com/noymer/items/8a21ec12be2de2892f1b)
- [ログインシェルとインタラクティブシェルと~/.bashrc達の関係](https://qiita.com/incep/items/7e5760de0c2c748296aa)
- [ログインシェル／インタラクティブシェルの分類と zsh の dotfiles 呼び出し順序の話](https://qiita.com/miorgash/items/4aa38c9b753e16ac5434)
- [ログインシェル、インタラクティブシェルの見分け方(bash)](https://happynap.net/2022-12-04-112257/)
- [ログインシェルとは？非ログインシェルとの違いも併せてご紹介](https://rainbow-engine.com/loginshell-nonloginshell-difference/)
- [cronを使って起動したプログラムはインタラクティブにできない？](https://qiita.com/24601_cold/items/fcca81001600909976c6)

cronの場合は「非ログインシェル」かつ「非インタラクティブシェル」ということらしく、この場合は `PATH` などの環境変数を定期処理の実行時に設定する必要があるとのこと。
そう言われると、たしかに `/etc/crontab` では `PATH` が設定されています。これはそういう意味だったのかと今更知りました。

``` 
root@XXXXXXXXXX0e98:/app# grep PATH /etc/crontab
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
```

---

ちなみに、`fly console` で仮想マシンにログインして、以下のコマンドを実行することで、ログインシェルか否か、インタラクティブシェルか否かを確認できます。

`shopt` コマンドに引数 `login_shell` を付与して実行すると `on` と表示されているので、この状態はログインシェル。

```
root@XXXXXXXXXXXe98:/app# shopt login_shell
login_shell    	on
```

シェルの特殊変数 `$-` を表示すると「 `i` 」が含まれているので、この状態はインタラクティブシェル。

```
root@XXXXXXXXXXXe98:/app# echo $-
himBHs
```

では、cronの場合はどのようになるのか気になったので、仮想マシン内の `/etc/cron.d/crontab` に以下のような内容の定期処理を設定してみました。

```
*/1 * * * * root /bin/bash -c 'shopt login_shell' > /var/log/cron.log 2>&1
*/1 * * * * root /bin/bash -c 'echo $-' >> /var/log/cron.log 2>&1
```

結果は以下のとおりで、cron経由の場合は `login_shell` が `off` と表示されているので、非ログインシェルであり、かつ、 `$-` を表示すると「 `i` 」が含まれていないので、非インタラクティブシェルであることがわかりました。

```
root@XXXXXXXXXXXe98:/app# tail -f /var/log/cron.log 
login_shell    	off
hBc
```

ログイン・非ログインシェル、インタラクティブ・非インタラクティブシェルの確認方法については、以下の記事を参考にさせていただきました。

- [ログインシェル、インタラクティブシェルの見分け方(bash)](https://happynap.net/2022-12-04-112257/)
- [bashの特殊変数”$-”から現在実行中のシェルのオプションを読み取る](https://shuzo-kino.hateblo.jp/entry/2022/08/16/235728)
- [$-]()

---

それでは、どのような方法で定期処理の実行時に環境変数にアクセスしたらよいのでしょうか。
結論としては、`fly.toml` 内の PROCESS GROUP に指定する処理のタイミング（Dockerの「ENTRYPOINT/CMD」にあたる）で、環境変数の値を取得する形になります。

色々な記事を読んでみたところ、ざっくりと2つの方法がありそうだということがわかりました。

- `printenv` を実行して、その結果（「KEY=VALUE」の形式）を `/etc/environment` に出力する方法。
- `printenv` を実行して、各変数を `export` する形（「export KEY=VALUE」の形式）にしたスクリプトを作成し、定期処理実行時に読み込む方法。

前者の `/etc/environment` に出力する方法は、OSの標準的な仕組みが利用でき、かつ、 `export` の形式に整形する手間もないため、こちらの方が良さそうに感じましたが、システム全体の環境変数に影響を与える設定になるので、今回  `/etc/environment` を利用する方法は採用しないことにしました。

---

ここからは具体的な設定に入っていきます。

#### PROCESS GROUPの起動時に呼び出すスクリプトを準備する

まずは、`fly.toml` 内の PROCESS GROUPの起動時（Dockerの「ENTRYPOINT/CMD」にあたる）に指定するスクリプトファイル( `entrypoint.sh` )を準備します。
このスクリプトで、 `printenv` の結果を整形し、 `export` で環境変数をセットするようにしています。

```
% cat entrypoint.sh 
#!/bin/bash

printenv | awk '{print "export " $1}' > /app/environment.sh
cron -f
```

---

#### fly.tomlを修正する

次に `fly.toml` 内の定期処理用（今回の場合は `cron` ）の PROCESS GROUP に上記手順で作成した `entrypoint.sh` を指定します。

##### 修正前

```
[processes]
  cron = 'cron -f'
```

##### 修正後

```
[processes]
  cron = '/app/entrypoint.sh'
```

---

#### 定期処理で呼び出される側のスクリプトを準備する

次は、定期処理で呼び出される側となるスクリプト( `cron.sh` )を準備します。
PROCESS GROUP の起動時に出力していた `/app/environment.sh` を読み込んだあとに、実際の定期処理を記述します。（ここでは `echo $FLY_IO_SECRETS_VALUE` ）

```
% cat cron.sh 
#!/bin/bash

. /app/environment.sh
echo $FLY_IO_SECRETS_VALUE
```

---

#### 定期処理を呼び出す側の設定ファイルを準備する

定期処理の呼び出し部分を `crontab` に記述します。

```
% cat crontab
*/1 * * * * root /app/cron.sh > /var/log/cron.log 2>&1
```

---

#### Dockerfileを修正する

上記の内容を踏まえて、 `Dockerfile` にファイルコピーとパーミッション変更の処理を追記します。

```
・
・
WORKDIR /app
・
・
COPY entrypoint.sh /app/
RUN chmod 0755 /app/entrypoint.sh

COPY cron.sh /app/
RUN chmod 0755 /app/cron.sh

COPY crontab /etc/cron.d/
RUN chmod 0644 /etc/cron.d/crontab
```

---

#### デプロイを実行する

準備が整いましたのでデプロイを実行します。

```
% fly deploy
```

---

#### 動作確認

`/app/environment.sh` に `FLY_IO_SECRETS_VALUE` を環境変数にセットするための `export` コマンドが出力されていることが確認できました。

```
root@XXXXXXXXXXXe98:/app# grep FLY_IO_SECRETS_VALUE /app/environment.sh 
export FLY_IO_SECRETS_VALUE=fly_io_secrets_value
```

`/var/log/cron.log` を確認すると、定期処理の実行時に環境変数 `FLY_IO_SECRETS_VALUE` の内容が取得できていることがわかります。

```
root@XXXXXXXXXXXe98:/app# cat /var/log/cron.log 
fly_io_secrets_value
```

---

- 参考URL
    - [Accessing environment secrets from cron-initiated shell script](https://community.fly.io/t/accessing-environment-secrets-from-cron-initiated-shell-script/13563/1)
    - [When Cron and Docker are not 🤝](https://roboslang.blog/post/2017-12-06-cron-docker/)
    - [nitesh-/docker-cron-with-environment-variables/Dockerfile](https://github.com/nitesh-/docker-cron-with-environment-variables/blob/master/Dockerfile)
    - [Access Docker-Compose Environment Variables Inside a Cron Job](https://blog.emumba.com/access-docker-compose-environment-variables-inside-a-cron-job-b24e41cf33da)
    - [【docker】コンテナ内で環境変数を読み込んでcronを実行する](https://qiita.com/messhii222/items/fccc7705f05ff96260cf)