【CRONコマンド活用講座】AWS-CLIをCRONからシェルコマンドとして利用するときの注意点


2022/04/30
蛸壺の技術ブログ|AWS-CLIをCRONからシェルコマンドとして利用するときの注意点

以前の記事にてCronでカスタムしたシェルスクリプトなどを定期実行させてみる内容の話をポストしましたことがありました。

cron内部のシステム的な事情で、カスタムスクリプトを定期実行させるときの暗黙的なお約束がいくつか存在しており、例えば前回の内容でチラッと説明させていただいた
「%」文字の取扱などはそれにあたります。

ちょっとしたことですが、理解しておかないと突如としてスクリプトが期待通りに動かなかったのに、エラーも何も起こらないというニッチモサッチモいかない状況になってしまいます。

今回は、aws-cliなどの他の実行環境から追加インストールするような類のCLIコマンドなどを、cronに実行させる際につまずかないポイントを説明していきます。


CRONに分かるように自作コマンドは絶対パスを利用する

ズバッと今回の話の要点なのですが、cronスケジューラーから呼び出す外部コマンドは、「絶対パス」を指定してから利用する必要があります。

            
            #/!bin/bash

#👇cronでは動かない
aws --version

#👇cronで動く
AWS_SHELL_PATH=/home/user/.local/bin
$AWS_SHELL_PATH/aws --version
        
なお、お手持ちの環境によってaws-cliの実行ファイルのインストール先が異なると思いますので、

            
            $ which aws
/home/user/.local/bin
        
などで実行ファイルの所在を確認しましょう。


CRONスケジューラーで文字列からコマンド実行するときの注意点

このブログの結論としては先ほどの項目でズバッと書いた内容そのものです。

ブログの尺が余るのも寂しいので、cronで文字列をコマンドとして実行させる技をすこしと語ってみます。

なお手元の環境では主にLinuxOSでcronの動作確認していますが、Windowsなどの他のOSでもcronの扱いはほぼ同等かと思います。

まずはテストのスクリプトをホームディレクトリに
tmpフォルダを作成してそこに以下のような内容でhoge.shを新規作成し、動作確認してみます。

            
            $ mkdir ~/tmp

$ cat << EOF > ~/tmp/hoge.sh
#!/bin/bash

echo 'HELLO CRON!' >> ~/tmp/hoge.txt
EOF

$ chmod +x ~/tmp/hoge.sh

$ . ~/tmp/hoge.sh
HELLO CRON!
        
とりあえずテキストに文字列を追加するだけのテストスクリプトができました。

続いてコレをつかってテストします。

素朴にCronのテストは
crontab -eで1分毎に実行してテキストに記録されるかで判断します。

            
            #...中略
* * * * * $HOME/tmp/hoge.sh >/dev/null 2>&1
        
をスケジュールの末尾へ追記して保存し、スクリプトが実行されるのを1分待ちます。

$HOME/tmp/hoge.txt内にコメントが追加されているようであれば、cronは期待通りに動作しています。

スクリプト内でevalを使う方式

ではここから先ほどのhoge.shスクリプトちょこちょこと変えて、文字列がコマンドとして実行されるかどうかを実験をしてみます。

まず
「evalコマンド」は文字列をコマンドとして評価し、実行することが出来ます。

            
            $ MY_CMD='echo "HELLO CRON FROM EVAL!"'

$ eval "$MY_CMD"
HELLO CRON FROM EVAL!
        
cronで動作するかを確認するために先程のhoge.shを以下のように修正してみます。

            
            #!/bin/bash

echo 'HELLO CRON!' >> ~/tmp/hoge.txt

MY_CMD='echo "HELLO CRON?"'

eval "$MY_CMD" >> ~/tmp/hoge.txt
        
手元の環境で確認すると、eval方式でもcronスケジュールは問題なく動作するようです。

スクリプト内でbash -cを使う方式

次に「bash -c」で引数に文字を指定しても同じように文字列を実行してみる形式です。

            
            $ MY_CMD='echo "HELLO CRON FROM BASH-C!"'

$ bash -c "$MY_CMD"
HELLO CRON FROM BASH-C!
        
スクリプトも以下のように修正します。

            
            #!/bin/bash

echo 'HELLO CRON!' >> ~/tmp/hoge.txt

MY_CMD='echo "HELLO CRON!?"'

bash -c "$MY_CMD" >> ~/tmp/hoge.txt
        
こちらも問題なくcronで動作すると思います。

スクリプト内で「$」を使う方式

さらにダブルクオーテーション無しで文字列を「$」で呼び出すことで中身をコマンドとして実行することもできます。

            
            $ MY_CMD='echo "HELLO CRON FROM DOLLAR!"'

$ $MY_CMD
"HELLO CRON FROM EVAL!"
        
注目すべきは先程のevalおよびbash -cと違って、ダブルクオーテーション(")まで文字として認識されてしまいます。

            
            #!/bin/bash

echo 'HELLO CRON!' >> ~/tmp/hoge.txt

MY_CMD='echo "HELLO CRON!??"'

$MY_CMD >> ~/tmp/hoge.txt
        
これも正しく動作しますが、書き込む結果にダブルクオーテーションまでが入り込むのを良しとするかどうかに注意を払う必要があります。

ひとえに文字からコマンド実行と言っても、出力される結果に微妙な差が付きますので、cron内で利用する前に各作法のクセを押さえておきたいところです。


詳しく解説〜AWS-CLIがCronで動かない場合の対策

改めて本題をより詳しく解説していきましょう。

コンソールからオペレーターが直接コマンドを叩くのと、cronが定期実行されるのとでは、環境変数が異なるので、この点に注意が必要です。

特にユーザーローカルにインストールして利用するツールコマンドなどでは、cronが実行するプログラムを内部で参照できなくなっている可能性もあります。

たとえばユーザーローカルにpythonからインストールした
aws-cliを使う時を考えてみます。

最初に一度、aws-cliの実行プログラムの所在と、環境変数を確認しておきます。

            
            $ aws --version
aws-cli/1.18.192 Python/3.7.3 Linux/5.4.51-v7+ botocore/1.19.32

$ which aws
/home/user/.local/bin/aws

$ echo $PATH
/home/user/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

$ echo $HOME
/home/user
        

crontab -eで以下のようなテストクリプトを仕込んでみます。

            
            #...中略
* * * * * bash $HOME/aws_cron_test.sh
        
まずは失敗するケースのスクリプトですが、以下の内容にして試してみます。

            
            #!/bin/bash

STORE_DIR=/home/user/aws_cron_result.txt

cp /dev/null $STORE_DIR

echo $PWD >> $STORE_DIR

which aws >> $STORE_DIR

aws --version >> $STORE_DIR

echo $PATH >> $STORE_DIR
        
cronで適切な設定をしていないと、このログファイル(aws_cron_result.txt)に書き込まれる結果は、

            
            /home/user
/usr/bin:/bin
        
となり、aws-cliコマンドが探せなかったので実行されずに終了しています。

つまり
aws-cliコマンドの在り処をcronスケジューラーが探せていないことが原因で期待した動作になっていないようです。

ちゃんとcronスケジューラーが外部コマンドを探せるようにするためには、環境変数に絶対パスを追加してあげることが一番手っ取り早いので以下のように修正します。

            
            #!/bin/bash

#👇実行ファイルをローカルインストールした場所を環境変数に追加
PATH=$PATH:/home/user/.local/bin

STORE_DIR=/home/user/aws_cron_result.txt

cp /dev/null $STORE_DIR

echo $PWD >> $STORE_DIR

which aws >> $STORE_DIR

aws --version >> $STORE_DIR

echo $PATH >> $STORE_DIR
        
これによりcronがプログラムを利用することが可能となり、

            
            /home/user
/home/user/.local/bin/aws
aws-cli/1.18.192 Python/3.7.3 Linux/5.4.51-v7+ botocore/1.19.32
/usr/bin:/bin:/home/user/.local/bin
        
が正しく得られました。

また、cronの実行するルートディレクトリがユーザーデフォルトと異なる場合にもaws-cliのクレデンシャル情報(
$HOME/.aws/configとか$HOME/.aws/credentials)が読み込めずaws側にアクセスできなくなってしまうことにも注意が必要です。

            
            $ aws --profile user s3 ls
        
例えば、上のようなaws-cliコマンドは使用者がコンソールで直接手で叩くと、クレデンシャルが当然問題なく読み出せると思います。

上記の理屈で、このコマンドはcronスケジューラーでそのまま使うことは出来ません。

cronでも認証情報を伴うコマンドを利用するには、$HOMEをユーザーデフォルトの絶対パスへリダイレクトさせるか、クレデンシャル情報の場所をシェルスクリプト内に指定してあげるなど、IAM情報の直書きを行う必要もあるでしょう。

            
            #👇ユーザーのデフォルトのホームディレクトリにする
export HOME=/home/user

#👇クレデンシャルファイルを指定する
export AWS_CONFIG_FILE="/home/user/.aws/config"

#👇IAM情報の直書き
export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXX
        

参考サイト:
CRONからAWS CLIを実行できません


まとめ

以上のことをまとめると、cronでスケジュールさせて呼び出すコマンドで使ってみたポイントは以下です。

            
            + eval は最終手段に使ったほうがよい
+ できるだけ bash -c で組めるかを検討する
+ $ はダブルクォーテーションの有無に注意する
        

とにかくインストール後の実行ファイルの所在が不明瞭なサードパーティ製のコマンドアプリケーションをcronで利用する際には、
絶対パスで呼び出すことにしていればおおよその問題はクリアされると思います。

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。