crondで実行されるジョブに環境変数を与えるアイデア

# やりたい事
タイトルの通り、crondで実行されるジョブに環境変数を与えたい

## 前提
パラメータストアから取得した値を環境変数にセットするシステム上でcrondが動いており、定期実行のジョブがある

## 課題
crondから実行したジョブから見える環境変数が制限される
crontabに環境変数を列挙することも可能だがパラメータストアから取得した情報を更新する場合、都度crontabの内容を書き換える必要が出てくる
パラメータストアに設定したらできるだけ自動で反映されてほしい

# 解決策
あるプロセスに環境変数をセットしておきcronジョブが動くタイミングでそのプロセスのprocfsから環境変数を取り出してセットする

例えばDockerならコンテナ起動時にセットされた環境変数は `PID=1` のプロセスに設定されている

なのでcronジョブが動く際にそこから環境変数を取り出してexportした上で本来実行したい処理を子プロセスとして動かせばcrondから実行するジョブも期待した環境変数を読めるようになる

## procfsから環境変数を読み出す

https://qiita.com/m-m-n/items/1a6282d0d5cd167dfa21

以前書いた記事を参照

## crondで実行するジョブに環境変数をセットするためのラッパーを用意する

```bash:/path/to/cron-wrapper.sh
#!/bin/bash

/path/to/process-env 1 | while read line; do
  echo "export $line"
done > /path/to/loadenv-$$
source /path/to/loadenv-$$
rm /path/to/loadenv-$$

"$@" 1> /proc/1/fd/1 2> /proc/1/fd/2
```

Dockerの場合は `PID=1` のプロセスに起動時に与えられた環境変数がセットされているので `process-env` の引数に `1` を与える
KEY=VALUEの形で取得できるのでexportを付与するため一度whileで回してexport KEY=VALUEの形にした上で一時ファイルに保存する

このファイルをsourceで読み込むことで `cron-wrapper.sh` のプロセスに環境変数がセットされる
そして最後の `"$@"` で引数に与えられたコマンドラインが実行され、標準出力、標準エラーに吐かれた文字列は `PID=1` のプロセスの標準出力、標準エラーにリダイレクトすることでDockerコンテナのログに出力される

`"$@"` は引数をそれぞれ""でくくったように展開される
`command arg1 arg2 arg3` の場合、 `"arg1" "arg2" "arg3"` となる
このため `command php a.php foo` などと実行すると `"php" "a.php" "foo"` と解釈されるのでphpコマンドに `a.php foo` を与えたものが実行される

## cronで実行してみる

```crontab
* * * * * /path/to/cron-wrapper.sh printenv
```

環境変数取れていれば無事成功
パラメータストア使ってたら秘匿したい情報が入っているはずなのでprintenvで全部吐き出すのはオススメしない
実際には環境変数を読み出して内容が正しいかを検証するようなスクリプトを用意すると良いと思われる

---

# コンテナ環境以外を考えてみる

例えばEC2のサーバーでcrondを動かしており、そこで環境変数をセットしたいケースを考える
(むしろコンテナでcrondを動かそうと思うケースが稀では？)

この場合は一旦パラメータストアから情報を取得し、環境変数にセットして何らかの子プロセスを立ち上げておき、そのプロセスの環境変数を読めるようにすればDockerの場合を考えた際の `PID=1` の部分が状況に応じて変わるだけで同じように処理することが可能になる

なお、systemd経由して立ち上げたプロセスのファイルディスクリプタにリダイレクトすることは出来ないのでコマンド実行結果はリダイレクトしないようにしておく必要がある

## 雑にsystemdのユニットファイルを作る
```systemd:/etc/systemd/system/get-params.service
[Unit]
Description=パラメータストア取得
After=network.target

[Service]
Type=simple
ExecStart=/path/to/get-params.sh
WorkingDirectory=/tmp
Restart=always
User=user
Group=user

[Install]
WantedBy=multi-user.target
```

## /path/to/get-params.shを作る
```bash:/path/to/get-params.sh
#!/bin/bash

### パラメータストアから取得した値環境変数にセットする処理を記述する

tail -f /dev/null &

TAIL_PID=$!

echo $TAIL_PID > /path/to/get-params.pid

wait $TAIL_PID
```

パラメータストアから取得した値を環境変数にセットする処理とその後 `tail -f /dev/null` で待ち続ける処理を記載する
一旦tailコマンドをバックグラウンドで実行しておき、PIDをファイルに保存する
ちなみにsystemctl status get-params.serviceすればtailのプロセスのPIDも知ることが出来るのでファイルにPIDを保存しないでも作れるはず

## 雑にsystemdでデーモンを動かす
準備が出来たらsystemdのいつものやつをやる
```bash
sudo systemctl daemon-reload
sudo systemctl enable get-params.service
sudo systemctl start get-params.service
```

あとは `$(cat /path/to/get-params.pid)` で環境変数を読み込みたいPIDを参照できるので適宜先述のcron-wrapper.shを書き換えればOKで〜す！

systemdのデーモンをリロードする際にcronジョブが起動したケースを考えて数秒の待ちを入れて数回リトライするようにしておくと良いかもしれません

## パラメータストアの更新をした場合
```bash
sudo systemctl restart get-params.service
```
get-params.serviceが起動する際にパラメータストアを参照するようにしたのでパラメータストアの更新があった場合にはデーモンを再起動すると反映される

---

それでは良いLinuxライフを

# 採用PR

株式会社viviONでは一緒に働く~~仲魔~~仲間を募集しております
オタク文化を盛り上げたいWebエンジニアのご応募お待ちしております！

https://hrmos.co/pages/vivion/jobs?category=1687770395095928832

Devトークもあります！

https://plus.jobs.qiita.com/dev-talk-company-pickup-vivion/