S3への同期が、手動では動くのに cronでは動かない場合に確認すること

先日、圧縮した Linux サーバーのログを awscli を使って S3 に送ろうとしたところ、掲題の通り、「手動」では問題なく動くのに、「cron」で実行するとうまく動作しないという問題が発生しました。(手動で動いていたので当然動くと思い cron 仕掛けて、数週間後に気づく・・。汗)

なにはともあれググってみたところ、以下の方と同じ症状です。
https://teratail.com/questions/111370

上記 teratail の質問は投稿時期が1年以上前と古いので、書き込むか迷いましたが、ステータスは「受付中」だったので書き込んでみました。私自身が原因がわからずとりあえず検索し、検索結果にあるような AWS 関連の環境変数では解決しなかったので、同じ境遇(環境)の誰かのお役に立てばと思います。

スポンサーリンク

cronだと動かなかった(理由:私の場合)

私の環境で、手動では動くのに全く同じコマンドを corn.d で実行できなかった理由を結論から言うと、Proxy 設定が抜けていたからです。Proxy 設定がなかったため、S3への接続ができず要求がタイムアウトしていました。

うちの EC2 達はセキュリティーグループで、VPNでのローカル通信以外は、外部アクセス用 Proxy を介して外と通信する設定になっていて、S3 との通信にもこの Proxy の設定が必要でした。この Proxy の設定が抜けていたとう残念なオチです。

以下のような構成です。同じような構成の方いるはず・・・??

結局、上記 teratail の質問や回答にも書かれている AWS_CONFIG_FILE=/root/.aws/config やら AWS_CREDENTIAL_FILE=/root/.aws/credentials やらの設定が足りてないのかと追加してみたり、Web の情報から AWS 関連の環境変数の問題と思い込みで試行錯誤してしまいました。最終的に、本記事後半の cron と手動の実行での環境変数の違いの洗い出しで、Proxyの設定に行き当たるまで、約2時間くらい捨てました。

この場合の解決方法

今回の私の環境の場合は、問題となった cron の設定(/etc/cron.d/s3sync)にProxyの設定を追加します。

修正前:

30 4 * * * root /root/.local/bin/aws s3 sync /var/log.a/ s3://my-bucket-name/LogArchive >/dev/null 2>&1

修正後:

HTTP_PROXY=http://172.16.20.250:8080
HTTPS_PROXY=https://172.16.20.250:8080
30 4 * * * root /root/.local/bin/aws s3 sync /var/log.a/ s3://my-bucket-name/LogArchive >/dev/null 2>&1

これで、問題なく cron でも実行できました。結局AWS_CONFIG_FILE=/root/.aws/configAWS_CREDENTIAL_FILE=/root/.aws/credentials は記載していません。これらはデフォルトのパスのままなので aws コマンドからは問題なく見えているようです。(ちなみに、これらのファイルを移動させたら、手動でも動かなくなりました。)

AWS_CREDENTIAL_FILE に関しては、AWS_SHARED_CREDENTIALS_FILE を使うなどの情報も目にしました。

参考:
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-files.html
https://aws.amazon.com/jp/releasenotes/release-aws-command-line-interface-1-7-45/

cron がうまく動かないときに確認すること

結局のところまとめると、S3(や AWS)に限った話ではないですが、cron で実行した場合と 手動で実行した場合とで動作が異なる場合に確認することを二つあげます。

cron.d の実行権限を確認する

crontab ではなく cron.d を利用していている場合、cron.d のパーミッションや所有者の設定ミスの可能性があります。今回も私は、一番最初これを疑いました。

たとえば、/etc/cron.d/s3sync を cron.d で実行したい場合を考えます。

/etc/cron.d/s3sync:

30 4 * * * root /root/.local/bin/aws s3 sync /var/log.a/ s3://my-bucket-name/LogArchive >/dev/null 2>&1

パーミッションは以下のように、本人以外触れないよう設定します。(実際には、R:読み込み権限 はついていても問題ないですが、私は取っています。)

ls -la /etc/cron.d/s3sync

正しい設定:

# 所有者(cron.d に記載の実行者)のみが読み書き可能
-rw------- 1 root root 104 723 13:50 s3sync
# または、(所有者以外は R:読み込み権限 はついていてもよい)
-rw-r--r-- 1 root root 104 723 13:50 s3sync

間違った設定:

# 別の人に書き込み権限がある
-rw-rw-rw- 1 root root 104 723 13:50 s3sync
# 所有者(cron.d に記載の実行者)が違う
-rw------- 1 user user 104 723 13:50 s3sync

このようにパーミッションの設定が間違っている場合は、cron.d の実行ログを確認しても、コマンドが実行されたログが残ることはありません。

$cat /var/log/cron-20190723 | grep aws
Jul 23 04:30:01 ip-172-16-20-11 CROND[1129]: (root) CMD (/root/.local/bin/aws s3 sync /var/log.a/ s3://my-bucket-name/LogArchive >/dev/null 2>&1)

環境変数を確認する

パーミッションに問題がない場合は、環境変数の差分を確認します。以下のコマンドを実行して、env.manuenv.cron のように出力して比較します。必要な環境変数が漏れていないか、今一度確認してみましょう。

今回、cron で実行したときに HOME がちゃんと /root であるかが気になっていました。HOME が /root でないと、/root/.aws/config や /root/.aws/credentials が見えない可能性があるからです。以下で確認の結果、HOME は /root になっていたので、/root/.aws/config や /root/.aws/credentials はデフォルトの場合、記載不要で問題はありません。

以下を手動実行:

printenv | sort > /tmp/env.manu

以下のように /etc/cron.d/envchk を作成しcron.dで実行:

* * * * * root /bin/printenv | /bin/sort > /tmp/env.cron
前述の通り、パーミッションは、600 で、所有者 rootで作成