Herokuでzip_code_jp gemの郵便番号データを定期更新したい

日本の郵便番号データを扱うRuby gemとしてはzip_code_jpが有名です。ECサイトなどでは郵便番号から住所を補完する機能はもはや当たり前になっていますが、そのような機能を作りたいときに便利なライブラリです。

この郵便番号データはZipCodeJp.export_jsonで更新できますが、このときgemのdata/zip_codeディレクトリ内にあるJSONファイルを直接更新します。
一般的なサーバ環境であればcronで回すなどすれば普通に更新できますが、Herokuの場合はローカルにファイルを書き込んでも再起動のタイミングで自動で消滅します。Heroku Schedulerなどで定期実行しても、既にパッケージされたslug内のデータは変更できないので意味がありません。
Release Phaseでいけないかな?とも思いましたが、Release Phaseはslugのビルド後に実行されるのでこれも使えません。

どうしようかなと考えた結果、デプロイ時に更新処理を実行するようにして、slugに最新の郵便番号データを入れてあげることにしました。

lib/tasks以下に適当な名前でRakeタスクを作り、以下のように書きます。

1
2
3
4
5
6
# lib/tasks/heroku_deploy.rake
Rake::Task['assets:precompile'].enhance do
Rails.logger.info 'Update zip code data'
ZipCodeJp.export_json
Rails.logger.info 'Done!'
end

任意のタスクのあとに別のタスクを実行するため、Rake::Task#enhanceを使っています。ドキュメントには「自身に事前タスクとアクションを追加します。」としか書かれておらず使い方がわかりにくいですが、enhanceの引数に指定したタスクは事前に実行され、ブロック内に記述したタスクは事後に実行されます

Herokuへのデプロイ時にはassets:precompileが毎回実行されるので、Rake::Task['assets:precompile'].enhanceとすることで、デプロイ時に任意のタスクを実行できるようになります。ここで郵便番号データを更新するようにしました。
郵便番号データ更新に限らず、デプロイ時に実行したい処理があれば同じように書けます。

更新にかかる時間は約20秒でした。少し時間はかかりますが、デプロイのたびに最新の郵便番号データを含めることができるので安心ですね。

1
2
remote:        I, [2018-08-28T17:24:33.195355 #823]  INFO -- : Update zip code data
remote: I, [2018-08-28T17:24:55.116512 #823] INFO -- : Done!

(もし毎回実行されるのが気に入らなければ、前回の実行日をどこかに覚えておくとか、日付を見て○日にのみ実行する…といった方法が考えられます)


Sidekiq + Heroku RedisでERROR: ERR max number of clients reachedと言われたら

Redisの同時接続数制限が原因です。

Sidekiqのconcurrencyのデフォルトは25となっており、Redisにもその数だけ接続するため、デフォルトのままだと一気に25接続を消費します。Heroku RedisのHobby Dev(接続数制限20)のような低価格なプランでは、concurrencyの設定をせずにデプロイすると一瞬でログがエラーまみれになってしまうので注意しましょう……。

上記Wikiにもあるように、変更するにはsidekiq.ymlに以下のように書きます。RAILS_ENVごとに切り替えることもできます。

1
2
3
4
5
:concurrency: 5
staging:
:concurrency: 10
production:
:concurrency: 20

HerokuのRelease PhaseでDBマイグレーション忘れを防ぐ

HerokuにはRelease Phaseという機能があります。
これはアプリケーションのビルドが終わってリリースする直前に任意のコマンドを実行するもので、DBのマイグレーションやキャッシュの削除といった用途に使えます。

設定方法はProcfilerelease: commandの形式で書くだけです。例えば、Railsでmigrateとseed-fuを実行するのであれば以下のようになります。

1
2
release: bin/rails db:migrate db:seed_fu
web: bundle exec puma -C config/puma.rb

DBマイグレーションを忘れて500エラーはあるあるな話ですが、これで防ぐことができます。
コマンドが失敗したとき(0以外のステータスで終了したとき)はもちろんデプロイは行われません。

Herokuはかゆいところに手が届いて便利ですね。


WerckerでRailsアプリをCIしてSlack通知する

オラクルに買収されてから存在感が薄くなった(?)ような気がするWerckerですが、使う機会があったのでRailsでの設定をメモしておきます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# wercker.yml
box:
id: ruby:2.5.1

services:
- id: mysql:5.7
env:
# rails-database-yml stepはrootユーザーを使わないのでランダムパスワードにしておく
MYSQL_RANDOM_ROOT_PASSWORD: 'yes'
# 以下の3つを設定する必要がある
MYSQL_DATABASE: rails_test
MYSQL_USER: rails
MYSQL_PASSWORD: password

- id: redis:4

build:
steps:
# Node.jsは入っていないのでインストールする
- script:
name: install node.js
code: curl -fsSL https://deb.nodesource.com/setup_8.x | bash - && apt-get install -qq nodejs
# bundleのキャッシュもしてくれる
# https://github.com/wercker/step-bundle-install
- bundle-install
# 自動的にdatabase.ymlの設定をしてくれる
# https://github.com/wercker/step-rails-database-yml
- rails-database-yml
- script:
name: copy .env
code: cp .env.sample .env
# Redisの接続先この方法でしか取れなかったけどもっといい方法ありそう
- script:
name: redis config
code: echo REDIS_URL=redis://$REDIS_PORT_6379_TCP_ADDR:$REDIS_PORT_6379_TCP_PORT >> .env
- script:
name: db setup
code: bundle exec rails db:schema:load db:seed_fu
- script:
name: rspec
code: bundle exec rspec

after-steps:
# Slack通知
# https://github.com/wercker/step-slack
- slack-notifier:
# 環境変数で設定
url: $SLACK_WEBHOOK_URL
channel: channel_name
username: wercker

環境変数はWeb画面上で設定し、wercker.yml内では$変数名で参照します。「Protected」にすると設定値が見えなくなるので、秘匿情報を設定するときに便利です。


better_errorsがやたら遅いときは最新版にアップデートするとよい

better_errorsが入ったRailsプロジェクトを最近触っているのですが、エラー画面の表示がやたら遅く、コンソールが使い物にならない状態になっていました。
Puma 3系で発生しており、リクエスト・レスポンス変数内のpuma.configのサイズが非常に大きく、通信に時間がかかってしまっているからのようです。

この対策として、最新バージョンの2.4.0(2017年10月リリース)でサイズの大きいインスタンス変数をフィルタする機能が追加されていました。フィルタ設定はデフォルトで有効なので、最新版にアップデートすればいい感じになります。

1
2
3
4
5
6
7
# e.g. in config/initializers/better_errors.rb
# This will stop BetterErrors from trying to render larger objects, which can cause
# slow loading times and browser performance problems. Stated size is in characters and refers
# to the length of #inspect's payload for the given object. Please be aware that HTML escaping
# modifies the size of this payload so setting this limit too precisely is not recommended.
# default value: 100_000
BetterErrors.maximum_variable_inspect_size = 100_000

最近ではweb-consoleがあるのでbetter_errorsをやめることも検討したのですが、これで快適に使えるようになりました。


Scrapboxでリアルタイム共同日報をやってみた

この記事はGMOペパボ Advent Calendar 2017の17日目……になる予定だった記事です。
担当日に風邪を引いてしまって穴を空けてしまいましたが、なんとか年内に投稿してギリギリセーフの雰囲気を出していこうと思います😇

朝会の機能不全

どのようなチームでも、人数が増えてくると「誰が何やってるのかわからない」という状況は起こるのではないかと思います。そのための情報共有の場として朝会はよく実施されていますが、自分のチームでは有効に機能していない状態でした。

  • 当初は朝会をやっていたが、チーム6人中フレックス勤務対象者が2人おり、その時間に出勤していないことが多々ある
  • 今度は夕会にしたが、会議がその時刻に入って欠席者が出たり、作業に集中していると夕会のことを忘れたりする
  • その日にやったことは翌日になると(夕方でも)割と忘れており、シュッと出てこない

また、現在弊社では自由度の高い働き方をしていくための各種施策が進められており、来年には全従業員にフレックス勤務が導入される予定です。在宅勤務の拡大も検討中であり、今後「チーム全員が確実にその場に集まれる状況」はますます減っていくことになります。そのような状況にも対応できるような形にした方がよいと感じていました。

共同で、リアルタイムに、無理なく日報を書く

このような課題を感じていたころ、先輩の@yuta25さんがScrapboxで共同日報を書くという記事を見つけ、よさそうだからやってみないかという話になりました。

Scrapboxはリアルタイムに共同編集ができるノートアプリで、一言でいえば動作が軽いGoogleドキュメントみたいな感じです。Markdownをより簡単にしたようなScrapbox記法があります。

共同日報の運用方法はほぼ上記参考記事と同様です。1日ごとにページをひとつ作り、各人が今日やるタスクや作業ログを随時箇条書きで書いていきます。自分のタスクはできるだけ一箇所にまとめるようにします。
アイコンショートカットを行末にいちいち入れるのが面倒なので、アイコンのみの行をひとつ作ってそれ以下はその人のエリア、みたいに示しています。これは明確にルールにしたわけではなく、自然とこうなりました。

面倒くさがりで飽きっぽいひとが(自分も含め)多いチームですが、12月8日から始めて今のところ毎日続いており、これは続きそう!という感触を得られています。

良かった点

情報共有の場としては、朝会や日報、分報などの取り組みがありますが、それらと比較しながら共同日報の良かった点を紹介します。

作業負荷が低い

日報ではそれを書くこと自体に結構な時間が費やされてしまいます。文章の形にまとめるのは負荷の高い作業なので、定時間際では疲れで億劫になりますし、「日報残業」になってしまうと本末転倒です。
共同日報では作業ログのような形で随時書いていって、そうしていたらいつの間にか日報ができている、という環境を作れます。箇条書きであれば文章が苦手な人でも書きやすく、続けやすいのではないかと思います。

「今日やること・やったこと」なので小さなことも書ける

タスク管理としてTrelloを使っていたこともありますが、粒度の設定がまちまちで、いつまでも動かないタスクが出たりしていました。また、「タスクにならないタスク」が出て、タスク管理してるはずなのにいま何をやってるの?という状態になりがちでした(Trelloが悪いのではなく運用が悪いので、もっとよくできたとは思います…)。
日報という形式のいいところは「今日やったこと」を書くので、チームの業務に関わらない事務作業とか、ランチの内容とか、仕事中の気持ちの面とかも書きやすい点があると思っています。実はそこにボトルネックや改善のヒントがあったりするものです。その利点を活かしつつ、随時書いていくことでToDoリスト的にも使っていくことができます。

全員が同じページに書いていくので気付きやすい

チャットを使った「分報チャンネル」では、全員が全員のチャンネルを見ていないと喫煙所で重要事項が決定されていた問題が生じることがあります。これを回避しながら分報をうまく機能させるのは難しいのではないかと思います。チーム全員でひとつの分報チャンネルを使う手もありますが、チャットでは時系列に制約されるため、話題が入り乱れたり、ちょっと前の話題には言及しにくかったりします。
共同日報ではひとりにつき1ページではなく全員が同じページに書いていくので、自分が書くときに自然と他人のコメントが目に入ります。時系列ではないので行を入れ替えたり、インデントや改行で適切に話題を整理することもできます。

悪かった点

Markdownじゃない

Markdownじゃないのは最初は面食らいます。アイコンの設定のためにページを作るなど独特の仕様もあるので、既にScrapboxのユーザーがいるほうがスムーズだと思います。
ただし綺麗に文章を整えるような使い方ではないのでそこまで困っていません。箇条書き(行頭でスペースorタブ)とアイコンショートカット(Ctrl+i)さえ覚えれば十分でした。

作業ログの粒度を調整するのが難しい

参考記事では「楽しくなって会話がはじまってしまう」「細かく実況しすぎてしまう」とありましたが、今のところそこまでの状況には至っていません。逆にもうちょっと細かくしてもいいかなと感じます。とはいえ細かくしようとして細かくすると途端に面倒になるので、うまい距離のとり方は考えどころです。メンバーの性格にもよるところがあると思います。

まとめ

  • 「チーム全員が確実にその場に集まれる状況」を前提としない、無理なく続けられるような情報共有の場が必要と感じていた
  • そのために、Scrapboxを使った共同日報を導入してみた
  • 共同でリアルタイムに書くことで、作業負荷が低くメンバーが気付きやすい日報になった

まだ導入して1ヶ月なので結論するには早いですが、今までの形よりも手応えがあり、自分のチームには合っているのではないかと感じています。

チームの状況や好みによって様々な手段がありますが、朝会や日報といった情報共有の取り組みの必要性はどのチームも感じていると思います。その手段のひとつとして共同日報をやってみても面白いかもしれません。


ImageMagickで画像処理に入門する

この記事は、ImageMagickと画像加工について発表したときの資料を文章に起こして加筆修正したものです。

ImageMagickとは

  • 画像加工といえばこれという有名ライブラリ
  • メジャーからマイナーまでさまざまな画像形式に対応
    • 機能が多すぎて脆弱性もたびたび発見されるくらい…
  • Cで実装され、多くの言語でバインディングがある

インストール

Mac Homebrew

1
$ brew install imagemagick

Debian / Ubuntu

1
$ sudo apt-get install -y imagemagick

CentOS

1
$ sudo yum -y install ImageMagick

コマンド

  • imagemagickというコマンドはない(!)
  • 機能ごとにconvertidentifyなどのコマンドにわかれている
    • ただし、ImageMagick 7からはすべてmagickコマンドへのシンボリックリンクになっている

ImageMagick 6 (Ubuntu 16.04)

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ls -Alh /usr/lib/x86_64-linux-gnu/ImageMagick-6.8.9/bin-Q16
total 88K
-rwxr-xr-x 1 root root 6.3K Jul 31 13:22 animate
-rwxr-xr-x 1 root root 6.3K Jul 31 13:22 compare
-rwxr-xr-x 1 root root 6.3K Jul 31 13:22 composite
-rwxr-xr-x 1 root root 6.3K Jul 31 13:22 conjure
-rwxr-xr-x 1 root root 6.3K Jul 31 13:22 convert
-rwxr-xr-x 1 root root 6.3K Jul 31 13:22 display
-rwxr-xr-x 1 root root 6.3K Jul 31 13:22 identify
-rwxr-xr-x 1 root root 6.3K Jul 31 13:22 import
-rwxr-xr-x 1 root root 6.3K Jul 31 13:22 mogrify
-rwxr-xr-x 1 root root 6.3K Jul 31 13:22 montage
-rwxr-xr-x 1 root root 6.3K Jul 31 13:22 stream

ImageMagick 7 (Mac Homebrew)

magickコマンドへのシンボリックリンクになっていることがわかる

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ls -Alh /usr/local/Cellar/imagemagick/7.0.7-7/bin | grep -v config
total 64
lrwxr-xr-x 1 shimoju admin 6B 10 7 21:02 animate -> magick
lrwxr-xr-x 1 shimoju admin 6B 10 7 21:02 compare -> magick
lrwxr-xr-x 1 shimoju admin 6B 10 7 21:02 composite -> magick
lrwxr-xr-x 1 shimoju admin 6B 10 7 21:02 conjure -> magick
lrwxr-xr-x 1 shimoju admin 6B 10 7 21:02 convert -> magick
lrwxr-xr-x 1 shimoju admin 6B 10 7 21:02 display -> magick
lrwxr-xr-x 1 shimoju admin 6B 10 7 21:02 identify -> magick
lrwxr-xr-x 1 shimoju admin 6B 10 7 21:02 import -> magick
-r-xr-xr-x 1 shimoju admin 18K 10 9 18:25 magick
lrwxr-xr-x 1 shimoju admin 6B 10 7 21:02 magick-script -> magick
lrwxr-xr-x 1 shimoju admin 6B 10 7 21:02 mogrify -> magick
lrwxr-xr-x 1 shimoju admin 6B 10 7 21:02 montage -> magick
lrwxr-xr-x 1 shimoju admin 6B 10 7 21:02 stream -> magick

identify

  • 画像の情報を出力できる
  • サイズをさっと知りたいときとか、大量の画像の中からサイズが違うものを探したりとかに便利
  • フォーマット文字列はこちら参照:Format and Print Image Properties @ ImageMagick
1
2
3
$ identify *.jpg
original.jpg JPEG 800x533 800x533+0+0 8-bit sRGB 104823B 0.000u 0:00.000
resize.jpg JPEG 400x267 400x267+0+0 8-bit sRGB 37116B 0.000u 0:00.000

JSON形式で出力してjqに食わせる

1
2
3
4
5
$ identify -format '{"width": %w, "height": %h}' *.jpg | jq
{
"width": 800,
"height": 533
}

grep -vで指定のサイズではない画像を抽出

1
2
$ identify -format '%wx%h %f\n' *.jpg | grep -v 800x533
200x200 crop.jpg

convert

  • さまざまな画像加工を行える、ImageMagickのメインコマンド
  • 適当な画像をoriginal.jpgとして用意しておきます

回転 (rotate)

-rotateで指定した角度回転する

1
$ convert -rotate 90 original.jpg rotate.jpg

上下反転

1
$ convert -flip original.jpg flip.jpg

左右反転

1
$ convert -flop original.jpg flop.jpg

上下左右反転

1
$ convert -flip -flop original.jpg flipflop.jpg

サンプル (sample)

ピクセルを間引く

1
$ convert -sample 10% original.jpg sample.jpg

10%になるようにピクセルを間引いたあと、1000%になるように拡大
→元画像と同じサイズでモザイクがかかる

1
$ convert -sample 10% -sample 1000% original.jpg mosaic.jpg

リサイズ (resize)

デフォルトではアスペクト比を変えない:指定した幅・高さに収まるようにリサイズされる

1
2
3
$ convert -resize 400x400 original.jpg resize.jpg
$ identify resize.jpg
resize.jpg JPEG 400x267 400x267+0+0 8-bit sRGB 37116B 0.000u 0:00.000

!をつけるとアスペクト比を無視して指定した値にリサイズする

1
$ convert -resize 400x400! original.jpg resize2.jpg

幅または高さのみ指定できる

1
$ convert -resize 400x original.jpg resize3.jpg

エッジ検出 (edge)

不連続に変化している箇所を検出する

1
$ convert -edge 5 original.jpg edge.jpg

値を変化させてみよう

1
2
$ convert -edge 10 original.jpg edge2.jpg
$ convert -edge 1 original.jpg edge3.jpg

切り抜き (crop)

-gravityで基準点を指定し、-crop widthxheightで切り抜くサイズを指定

1
$ convert -gravity center -crop 200x200+0+0 original.jpg crop.jpg

+/-で基準点からのx,y座標を指定できる
画像右上を基準に、xに140px,yに50px移動し、その点から200x200px切り抜く

1
$ convert -gravity northeast -crop 200x200+140+50 original.jpg crop.jpg

塗り足し (extent)

指定したサイズになるように余白を追加する
正方形のサイズが必要なのに4:3の画像しかないときなどに便利

1
2
$ convert -background black -gravity center \
-extent 800x800 original.jpg extent.jpg

余白の色は-backgroundで指定する
PNG(透過が扱えるフォーマット)であれば、-background transparentで透過できる

1
2
$ convert -background transparent -gravity north \
-extent 1000x1000 original.jpg extent.png

文字の画像を作る

1
2
$ convert -background transparent \
-fill '#ff6060' -font Arial -pointsize 128 label:LGTM lgtm.png

指定サイズで作成

1
2
$ convert -size 400x200 -gravity center -background transparent \
-fill '#ff6060' -font Arial -pointsize 128 label:LGTM lgtm.png

合成 (composite)

original.jpgの上にlgtm.pngを合成して、compose-over.jpgとして出力

1
2
$ convert original.jpg lgtm.png -gravity center \
-compose over -composite compose-over.jpg

-geometryで基準点から移動

1
2
$ convert original.jpg lgtm.png -gravity center -geometry +150+50 \
-compose over -composite compose-over.jpg

描画モード (blend mode)

乗算 (multiply)

1
2
$ convert original.jpg lgtm.png -gravity center -geometry +150+50 \
-compose multiply -composite compose-multiply.jpg

オーバーレイ (overlay)

1
2
$ convert original.jpg lgtm.png -gravity center -geometry +150+50 \
-compose overlay -composite compose-overlay.jpg

減算 (subtract)

1
2
$ convert original.jpg lgtm.png -gravity center -geometry +150+50 \
-compose subtract -composite compose-subtract.jpg

Herokuはスケーラブルなアプリ養成ギプス

社内勉強会でHerokuでの本番運用について発表しました。
いま携わっているSUZURIはHerokuで運用しており、個人でもHerokuで運用しているアプリがあります。その中で経験したことや知見を話しました。

Herokuで本番運用する技術

目次を見るとわかるようにテーマは多岐にわたっており、ざっと&ゆるめに発表しようという趣旨です。

Herokuはスケーラブルなアプリ養成ギプス

Herokuのいいところといえば、マネージドで手軽に使えること、開発者にとって便利な機能が豊富なこともそうですが、アプリケーション設計に良い影響を与える点もあります。

たとえば、Herokuではローカルにファイルを保存できません(正確にはできますが、1日1回自動で再起動され、そのときに消滅します)。この挙動に代表されるように、Herokuではシステムローカルな何かに依存しない、疎結合でステートレスなアプリケーション設計が強制されます。

このHerokuが提唱している方法論をまとめたものがThe Twelve-Factor Appです。初めて読んだときはよくわからなかったのですが、新卒研修でインフラを学んだり、アプリケーション設計の知識がついていくにつれて、なるほどとてもよいポリシーだなあ、としみじみ感じています。

Herokuで動くように作ればスケーラブルなアプリが自然にできていくということで、養成ギプスを思わせます。Dockerなどのコンテナでアプリを運用するときにも同じ考え方を適用できるため、コンテナ時代になってその重要性はさらに高まっているのではないかと思います。Twelve-Factor Appを学ぶのにもHerokuはおすすめです。

おまけ

「おまけ」に書いたのですが、Herokuにはテスト、ステージング、本番環境をシームレスに統合するCI/CD環境であるHeroku Pipelinesがあります。たとえばマージすると自動でデプロイできるGitHub Integration、CI環境のHeroku CIや、ChatOpsもあります。特にHeroku Review AppsはPull Requestを作るたびに新しい環境を自動的に作ってくれてとても便利です。

しかしこの機能はGitHubしかサポートしておらず、会社のリポジトリはGitHub Enterpriseなので使えません😭
"github.com"がハードコードされていて色々大変らしいという話を@hsbtさんから聞いたのですが、本当に書いてあって笑いました。なんとかしてほしいものです。

Heroku's GitHub sync features are hard-coded to connect to and use github.com.
Can I use Github Enterprise with Pipeline Review apps? - Knowledge Base

実はこの日には新卒エンジニア研修の座学で1時間の発表もしており、2回目の発表でとてもヘビーでした。あんまりうまく喋れてなかったのではないかと思います……。


新卒エンジニア研修の座学でImageMagickと画像加工の話をした

ペパボの新卒エンジニア研修では、メニューのひとつとして「座学」を行っています。

メインの研修ではWeb開発・Webオペレーション・モバイルアプリ開発に順番に取り組んでいきますが、座学では知識と興味を広げることを目的として、社内のエンジニアにお願いして1時間自由に発表してもらっています。
たとえば、テストやチーム開発、モバイルの開発手法といった研修内容に関連した話や、あるいは3DCGのシェーダーやReactive Extensionsといった、研修では触れないような技術の話まで様々です。研修の内容については、ペパボの新卒エンジニア研修2017 Vol.1 - ペパボテックブログを見ると雰囲気がわかるかと思います。

私は研修担当者をやっていて座学をお願いする立場ですが、自分でもやってみたかったので立候補しました。

資料はこちらです。「ImageMagick実践入門 画像加工サーバを作ってみよう」と題して、実際にコマンドやコードを書きながら、ImageMagickを使った画像加工と、簡単な画像加工サーバーをGo言語で実装していきます。

1時間話すのはとても疲れました。この座学はハンズオンもあるので資料は50枚程度ですが、全部口述だと70〜80枚にもなるので、資料作成もかなり時間がかかります。
作っていくうちに「本当にこんな内容でいいのだろうか……」とつらくなってきますが、いま学習中のGo言語を使ってみたり、調べる中で自分の理解も深まって、いい経験になりました。

座学講師に立候補してくださった方々には頭が上がりません。


DockerでcronしたいときはBusyBox crondが便利

Dockerコンテナでプログラムを定期実行したいとき、それぞれの言語で実装されたタスクスケジューラを使うほか、手っ取り早くcronを使ってしまう方法もあります。しかしDockerで使うにはやや面倒な点があります。

  • cronで実行するプログラムにコンテナに設定した環境変数を渡したい
  • ログは標準出力・標準エラー出力に書き出したい
    • 標準出力に出せばdocker logsで扱えるし、ファイルだとローテートが面倒

このようなとき、BusyBoxに含まれるcrondを使うと、以上の課題を解決してシンプルに定期実行することができます。

インストール

Debian系ではapt-get install busybox-staticでインストールし、busybox crondで起動します。
Alpine LinuxであればそのままBusyBoxなのでcrondで起動できます。

オプション

-dオプションは比較的最近追加されたようで、Debian jessie(BusyBox v1.22.1)には含まれていませんでした。stretchでもv1.22.1のままのようです。

Alpine Linux 3.6 / BusyBox v1.26.2

1
2
3
4
5
6
7
8
9
10
11
12
# crond --help
BusyBox v1.26.2 (2017-06-11 06:38:32 GMT) multi-call binary.

Usage: crond -fbS -l N -d N -L LOGFILE -c DIR

-f Foreground
-b Background (default)
-S Log to syslog (default)
-l N Set log level. Most verbose:0, default:8
-d N Set log level, log to stderr
-L FILE Log to FILE
-c DIR Cron dir. Default:/var/spool/cron/crontabs

Debian jessie / BusyBox v1.22.1

1
2
3
4
5
6
7
8
9
10
11
# busybox crond --help
BusyBox v1.22.1 (Debian 1:1.22.0-9+deb8u1) multi-call binary.

Usage: crond -fbS -l N -L LOGFILE -c DIR

-f Foreground
-b Background (default)
-S Log to syslog (default)
-l Set log level. 0 is the most verbose, default 8
-L Log to file
-c Working dir

ログレベル

ログレベルが0から8まであってよくわからなかったのでソースを読んだところ、

1
2
3
4
/* Log levels:
* 0 is the most verbose, default 8.
* For some reason, in fact only 5, 7 and 8 are used.
*/

とのことでした。For some reasonとは一体……という感じですが、デフォルトの8でもコマンドの実行ログは出るので特に問題なさそうです。

というわけで、Dockerで使う場合は以下のコマンドにすればよいでしょう。新しく追加された-dオプションが標準エラーに出力しているので、Debianの場合でもこれに統一しています。

1
2
3
4
5
6
7
# Alpine
# crond -f -d 8
CMD ["crond" "-f", "-d", "8"]

# Debian
# busybox crond -f -L /dev/stderr
CMD ["busybox", "crond", "-f", "-L", "/dev/stderr"]

実行する

/var/spool/cron/crontabs/rootにいつものようにcrontabを書いて実行します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ cat crontab
* * * * * echo '=== environment variables ===' && env

$ docker run --rm -e FOO=BAR -v $(pwd)/crontab:/var/spool/cron/crontabs/root alpine:3.6 crond -f -d 8
crond: crond (busybox 1.26.2) started, log level 8
crond: USER root pid 7 cmd echo '=== environment variables ===' && env
=== environment variables ===
USER=root
no_proxy=*.local, 169.254/16
HOSTNAME=eac1d9f28400
SHLVL=1
HOME=/root
LOGNAME=root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
FOO=BAR
SHELL=/bin/sh
PWD=/root

このように、コンテナに設定した環境変数FOO=BARが実行コマンドからも見えること、ログが標準エラー出力に書き出されていることを確認できました。

タイムゾーン

cronと直接関係はありませんが、タイムゾーンを日本時間にしておかないと意図した時刻に動かなくてハマるので注意しましょう……(1時間無駄にしました😇)。

1
ENV TZ=Asia/Tokyo

参考記事