AR ホームベーカリー

オイラのアウトプット用ホームベーカリー!

whenever で cron を設定すると bundle: command not found

産業で

whenever 実行するユーザの 環境変数 PATH/usr/local/bin を追加しろ

2020/01/10 追記

以下のように whenever の設定ファイルに PATH を設定すると PATH を設定できる。 ログ出力と合わせて記述すると確認しやすくて良い、こっちのほうが楽ちんですね。 しかし、他の cronjob が設定されていると、ぶつかって crontab が壊れるので注意しないといけない。

config/schedule.rb

set :output, "#{Rails.root}/log/cron.log"
env :PATH, "/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin"

なんでこうなる

whenever を設定すると、登録される cron はだいたいの場合において、以下のようなものになると思われる。

0 0 * * * /bin/bash -l -c 'cd ${RAILS_ROOT} && RAILS_ENV=${環境名} bundle exec rake ${タスク名} --silent >> log/crontab.log 2>&1'

ここで /bin/bash -l -c を見てみましょう。 AmazonLinux2 の man bash ではこう書かれています。

  • -l
    • Make bash act as if it had been invoked as a login shell (see INVOCATION below).
    • ログインシェル (後述の 起動 セクションを参照) として起動されたかのように bash を動作させます。
  • -c string
    • If the -c option is present, then commands are read from string. If there are arguments after the string, they are assigned to the positional parameters, starting with $0.
    • -c オプションが指定されると、コマンドが string から読み込まれます。 string の後に引き数があれば、これらは 位置パラメータ (positional parameter: $0 から始まるパラメータ) に代入されます。

和訳はこちらから。 Man page of BASH

つまり、cron 起動時に、ユーザログインした状態(環境変数などを)を再現して、引数に指定されたコマンドを実行する、といったテイストですね。何もしないと cron のジョブ実行時の環境変数やらはお寒いので、( -l は余計なものも読み込むからやめろ!と言われますが ) 解決するアプローチとしては良いと、個人的には思っております。

で、何が問題かというと、上記の状態で cron が動作すると、 log.crontab.log にこのように記録されます。

/bin/bash: bundle: command not found

なして!

なぜコマンドがないのか

ここでコマンドの有無を、 cron を設定した&実行を期待されるユーザ ec2-user で確認しましょう。

$ which bundle
/usr/local/bin/bundle
$ whereis bundle
bundle: /usr/local/bin/bundle

ありますねえ……? ログインシェルのように起動して振る舞うなら、このコマンドを見失うのはおかしいですね。 では、実際に cron での動作を確認してみましょう。

0 0 * * * /bin/bash -lx -c 'which bundle' > /tmp/result.txt

ハイ、見事に空ですね。 コマンドは見当たりませんでした、ナンデ! という訳でこういう時は環境変数 PATH ですね、上記と同じようにそれぞれ確認してみましょう。

PATH の中身を確認する

$ echo $PATH
/usr/bin:/usr/local/sbin:/usr/sbin:/home/ec2-user/.local/bin:/home/ec2-user/bin:/usr/local/bin
0 0 * * * /bin/bash -lx -c 'env $PATH'

→結果
+ PATH=/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/home/ec2-user/.local/bin:/home/ec2-user/bin

/usr/local/bin おらんなった

おしりにいたはずの /usr/local/bin おらんなった、なんじゃこれ。

どうしていないのか

どうやら bash のバグかなんからしいです。デフォルトの PATH として、 /usr/local/binハードコートされているらしいのですが、どこかでポロっとおっことしているようで。 なので対策として、 sshd_configssh でログインしてきたときに、 PATH に自動的に付与しているようです。

serverfault.com

これずっと治ってないって外人兄貴達が言ってるんだけど実際どうなんじゃろ?

結局どうすればいいのか

  • cron の先頭に PATH を記載して、実行時に環境変数を増やす
  • bash -l でログインシェル起動っぽく振る舞うので、そもそも .bash_profile なりの環境変数 PATH に /usr/local/bin を追加してしまう
    • /usr/bin はベンダの動作確認したコマンドで、それで足りなかったりオーバーライドしたい場合、 /usr/local/bin を使う、というふうに僕は聞いたんだけど、そうなら先頭に追加したほうがよさそうすねこれ

whenever を使う場合であれば、管理する箇所が増えるのだるいので .bash_profile なりの環境変数 PATH に追加するのが良いと思います。 ただその場合、以下のように sshd_config の親切心と合わせてちょっと PATH が汚くなるので、それはご愛嬌ってことで。

/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/ec2-user/.local/bin:/home/ec2-user/bin:/usr/local/bin