Docker上でpythonのプログラムをcronで定期実行する

# はじめに
docker上でpythonのプログラムを定期実行しようと思い，cronで動かそうと思ったらかなりハマったのでそのメモ．
pythonならscheduleやcronのライブラリがあるのでそれらを利用すれば早いし，そもそもdockerプロセス自体をcronで定期実行すれば良いが，1コンテナ1プロセスの精神や，移植性を考えて無理やりコンテナ内で動かした．

実験に使ったプログラムは[こちら](https://github.com/sey323/docker-python-cron)

# 実行するPythonプログラム
```python:load_json.py
# coding: utf-8
import json

f = open( './test.json' )
json_data = json.load( f )
f.close()

print( json_data )
```

```json:test.json
{
  "国語":90,
  "数学":100,
  "理科":85,
  "社会":100,
  "英語":20
}
```

テスト用に，単純にjsonを解析して，コンソール上に出力するだけのプログラムを作った．このプログラムの実行結果は以下の通り．

```sh
$ python load_json.py
{'国語': 90, '数学': 100, '理科': 85, '社会': 100, '英語': 20}
```

これをコンテナ上でcronで定期実行してみる．

## PythonのDockerコンテナの作成
```dockerfile:Dockerfile
FROM python:latest

RUN pip install --upgrade pip

RUN apt-get update
RUN apt-get install -y cron

# cron設定ファイルの移動
ADD python-cron /etc/cron.d/python-cron
RUN chmod 0644 /etc/cron.d/python-cron

ADD ./ /

CMD cron && touch /etc/cron.d/simple-cron && tail -f /dev/null
```


```:python-cron
* * * * * root python /load_json.py >> /var/log/cron.log 2>&1
```
cronでpythonプログラムの出力結果を`/var/log/cron.log`に出力する．コンテナにログインし．正しく実行できているかを確認する．

```sh:
root@798d72ea2442:/# cat var/log/cron.log
root@798d72ea2442:/#
root@798d72ea2442:/# python /load_json.py
{'国語': 90, '数学': 100, '理科': 85, '社会': 100, '英語': 20}
root@798d72ea2442:/#
```
コンテナ内での実行はうまくいくのだが，cronの実行ではうまくいっていない．
cronのプログラムの実行の方でエラーが起きて,実行できていないっぽい．

## シェルスクリプトにプログラムを記述してそれを実行
先人の投稿を見てみると，cronではシェルスクリプトにプログラムを記述して，それを定期実行させているケースがほとんどだった．ここは思考停止でパクってやってみる．

```dockerfile:Dockerfile
FROM python:latest

RUN pip install --upgrade pip

RUN apt-get update
RUN apt-get install -y cron

# cron設定ファイルの移動
ADD python-cron /etc/cron.d/python-cron
RUN chmod 0644 /etc/cron.d/python-cron

ADD ./ /
RUN chmod +x /script.sh　　#追加

CMD cron && touch /etc/cron.d/simple-cron && tail -f /dev/null
```

```shell:script.sh
python /load_json.py
```

ファイルが準備できたらコンテナを動かし，動作確認．

```sh
root@8004b8e2114c:/# cat /var/log/cron.log
{u'\u7406\u79d1': 85, u'\u793e\u4f1a': 100, u'\u56fd\u8a9e': 90, u'\u6570\u5b66': 100, u'\u82f1\u8a9e': 20}
```
実行はできたが，文字化けが起きている.

```sh:
root@8004b8e2114c:/# python
Python 3.6.4 (default, Mar 14 2018, 17:49:05)
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print(sys.getdefaultencoding())
utf-8
```

対話モードでpython文字コードを確認して見ても，utf-8となっており，間違っていないはず．．．

## 解決：Cronの環境設定
コンテナ内ではプログラムが実行できるのに，cronで定期実行できないのは，文字コードが異なるため，エラーが出て止まっていた．調べたところ，cronではcron内部で環境変数が独立しているため，pythonなどのプログラムは，環境変数を設定しなければ実行ができなかったり，文字化けが起きることがあるらしい．

[こちら](https://qiita.com/rerofumi/items/fc0126c4e985b78f769b)を参考に，環境変数をcron実行時に読み込ませ，その後pythonプログラムを実行するようにした．

流れとしては，
1. `init.sh`でコンテナ内の環境変数の出力とcronの実行.
2. cronで指定した時間に`script.sh`を実行．
3. `script.sh`で実行したいプログラムの前に，コンテナ内の環境変数をcronに反映させる．
4. 実行したいプログラムの実行．


```sh:init.sh
#!/bin/sh
printenv | awk '{print "export " $1}' > /root/env.sh
/usr/sbin/cron -f
```

```shell:script.sh
#!/bin/bash
. /root/env.sh
python /load_json.py >> /var/log/cron.log
```

合わせてDockerfileも，`init.sh`を実行するように修正する．

```dockerfile:Dockerfile
FROM python:latest

RUN pip install --upgrade pip

RUN apt-get update
RUN apt-get install -y cron

# cron設定ファイルの移動
ADD python-cron /etc/cron.d/python-cron
RUN chmod 0644 /etc/cron.d/python-cron

ADD ./ /
# init.shにも実行権限の追加
RUN chmod +x /script.sh /init.sh

# CMD cron && touch /etc/cron.d/simple-cron && tail -f /dev/null
CMD /init.sh
```

動作確認．

```sh
root@e411646bab21:/# cat /var/log/cron.log
{'国語': 90, '数学': 100, '理科': 85, '社会': 100, '英語': 20}
```

# まとめ
docker内で動かしたいプログラムをcron実行させるに結構手間がかかることがわかった．
もっとスマートなやり方があったら誰か教えて欲しいです．

# 参考
https://qiita.com/rerofumi/items/fc0126c4e985b78f769b
https://qiita.com/jmatsu/items/0a5d80abe188b09644c1