Continuous Integration (CI) が徐々にDockerに対応し始める機運です。先行してWerckerがDocker対応を始めたので、その流れに乗るべくWerckerをDocker化してみました。
というわけで、まずはCI上のDockerに載せてから次の手(GAEあたり)を考えることにしました。CIはWerckerを使用。以前から使っていたのですが、今回はボックスがDockerになったのでそちらに対応しました。
まず、Werckerは「Docker」「環境変数」による環境管理、「パイプライン」によるワークフロー管理を行っています。
gcr.io/google_appengine/ruby:xxx
)と共通化することもできます。ただし、HerokuのHobby Dynosはプロセス数に制限があるのでコンテナ運用は工夫が必要です。次に、Werckerのふるまいを定義するwercker.yml
は、下記シークエンス図のようにパイプラインごとに記述されています。今回は各パイプラインの詳細を見ていくことにします。
devパイプラインは wercker dev
コマンドをローカルでたたく際に使います。下記の例だとRSpec走らせているだけなのでおまけ程度。ただ、ローカル開発でDockerを使うことになったらこういう提案もありだと思います。プロジェクトレポジトリすべてをDockerにしてローカル開発するペイン、所謂git-dockerのバージョン管理問題があるので代替案として。
box: ruby:2.3.1
services:
- postgres:9.6.1
- redis:3.0.3
dev:
steps:
- bundle-install
- script:
name: Install ImageMagick
code: |
apt-get update
apt-get install -y nodejs imagemagick
- script:
name: Setup database
code: |
RAILS_ENV=test bundle exec rake db:create db:migrate
- internal/watch:
name: Run rspec
code: |
RAILS_ENV=test bundle exec rake spec
reload: true
buildパイプラインもdevパイプラインと同じDockerボックスを使っています。やっていることはdevパイプラインと変わらず、すべてのブランチで走ります。
build:
steps:
- bundle-install
- script:
name: Install ImageMagick
code: |
apt-get update
apt-get install -y nodejs imagemagick
- script:
name: Echo Ruby information
code: |
env
echo "ruby version $(ruby --version) running!"
echo "from location $(which ruby)"
echo -p "gem list: $(gem list)"
- script:
name: Setup database
code: |
RAILS_ENV=test bundle exec rake db:create db:migrate
- script:
name: Run rspec
code: |
RAILS_ENV=test bundle exec rake spec
deploy-stageパイプラインはステージング環境用。現在Herokuを本番環境で利用しているので、デプロイごとにそれをフォークして環境構築しています。また、Railsのアセットプリコンパイルの時間短縮はほかのCIと同様にキャッシュを利用しています。
他のPaaSに移った場合に現在行っている本番環境のフォークをどうするかが検討課題となります。
deploy-stage-heroku:
steps:
- bundle-install
- script:
name: Install NodeJS
code: |
apt-get update
apt-get install -y nodejs
- nabinno/heroku-install:
key: $HEROKU_KEY
user: $HEROKU_USER
app-name: $HEROKU_APP_NAME
- script:
name: Fork Application - destroy application
code: |
heroku apps:destroy --app $HEROKU_APP_NAME --confirm $HEROKU_APP_NAME
- script:
name: Fork Application - fork
code: |
heroku fork --from $FROM_HEROKU_APP_NAME --to $HEROKU_APP_NAME
- script:
name: Fork Application - setup addons of rediscloud
code: |
heroku addons:create rediscloud:30 --app $HEROKU_APP_NAME
- script:
name: Fork Application -change dynos
code: |
heroku ps:scale web=1:Free worker=1:Free --app $HEROKU_APP_NAME
- script:
name: Fork Application - change environment variables
code: |
_rediscloud_url=$(heroku run 'env | grep -e REDISCLOUD_.*_URL' --app $HEROKU_APP_NAME | awk -F= '{print $2}')
heroku config:set \
S3_BUCKET=$S3_BUCKET \
HEROKU_APP=$HEROKU_APP_NAME \
REDISCLOUD_URL=$_rediscloud_url \
--app $HEROKU_APP_NAME
- script:
name: Assets Precompile - restore assets cache
code: |
[ -e $WERCKER_CACHE_DIR/public/assets ] && cp -fr $WERCKER_CACHE_DIR/public/assets $WERCKER_SOURCE_DIR/public || true
mkdir -p $WERCKER_SOURCE_DIR/tmp/cache
[ -e $WERCKER_CACHE_DIR/tmp/cache/assets ] && cp -fr $WERCKER_CACHE_DIR/tmp/cache/assets $WERCKER_SOURCE_DIR/tmp/cache || true
- script:
name: Assets Precompile - main process
code: |
RAILS_ENV=production bundle exec rake assets:precompile --trace
- script:
name: Assets Precompile - store assets cache
code: |
mkdir -p $WERCKER_CACHE_DIR/public/assets
cp -fr $WERCKER_SOURCE_DIR/public/assets $WERCKER_CACHE_DIR/public
mkdir -p $WERCKER_CACHE_DIR/tmp/cache/assets
cp -fr $WERCKER_SOURCE_DIR/tmp/cache/assets $WERCKER_CACHE_DIR/tmp/cache
- add-ssh-key:
host: github.com
keyname: GITHUB
- add-to-known_hosts:
hostname: github.com
fingerprint: 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48
- script:
name: Assets Precompile - git commit
code: |
{
git add public/assets/.sprockets-manifest-*.json
git commit -m 'Run `rake assets:precompile` on Wercker.'
} || {
echo 'Skip: keep precompiled assets manifest.'
}
- heroku-deploy:
key: $HEROKU_KEY
user: $HEROKU_USER
app-name: $HEROKU_APP_NAME
- script:
name: DB Migrate
code: |
heroku run 'bundle exec rake db:migrate --trace' --app $HEROKU_APP_NAME
after-steps:
- wantedly/pretty-slack-notify:
webhook_url: ${SLACK_WEBHOOK_URL}
channel: general
deploy-prod-herokuパイプラインは本番環境へのリリース用。環境変数以外はdeploy-stageパイプラインと同じものです。
deploy-prod-heroku:
steps:
- bundle-install
- script:
name: Install NodeJS
code: |
apt-get update
apt-get install -y nodejs
- script:
name: Assets Precompile - restore assets cache
code: |
[ -e $WERCKER_CACHE_DIR/public/assets ] && cp -fr $WERCKER_CACHE_DIR/public/assets $WERCKER_SOURCE_DIR/public || true
mkdir -p $WERCKER_SOURCE_DIR/tmp/cache
[ -e $WERCKER_CACHE_DIR/tmp/cache/assets ] && cp -fr $WERCKER_CACHE_DIR/tmp/cache/assets $WERCKER_SOURCE_DIR/tmp/cache || true
- script:
name: Assets Precompile - main process
code: |
RAILS_ENV=production bundle exec rake assets:precompile --trace
- script:
name: Assets Precompile - store assets cache
code: |
mkdir -p $WERCKER_CACHE_DIR/public/assets
cp -fr $WERCKER_SOURCE_DIR/public/assets $WERCKER_CACHE_DIR/public
mkdir -p $WERCKER_CACHE_DIR/tmp/cache/assets
cp -fr $WERCKER_SOURCE_DIR/tmp/cache/assets $WERCKER_CACHE_DIR/tmp/cache
- add-ssh-key:
host: github.com
keyname: GITHUB
- add-to-known_hosts:
hostname: github.com
fingerprint: 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48
- script:
name: Assets Precompile - git commit
code: |
{
git add public/assets/.sprockets-manifest-*.json
git commit -m 'Run `rake assets:precompile` on Wercker.'
} || {
echo 'Skip: keep precompiled assets manifest.'
}
- script:
name: Add git-tag
code: |
_tag=$(date -u -d '9 hours' +%Y-%m-%d-%H-%M-%S)
git config --global user.email 'wercker@blahfe.com'
git config --global user.name 'Wercker Bot'
git tag -a $_tag master -m 'wercker deploy'
git push origin $_tag
- heroku-deploy:
key: $HEROKU_KEY
user: $HEROKU_USER
app-name: $HEROKU_APP_NAME
install-toolbelt: true
- script:
name: DB Migrate
code: |
heroku run 'bundle exec rake db:migrate --trace' --app $HEROKU_APP_NAME
after-steps:
- wantedly/pretty-slack-notify:
webhook_url: ${SLACK_WEBHOOK_URL}
channel: general
deploy-prod-gaeパイプラインはdeploy-prod-herokuパイプラインと同じく本番環境へのリリース用。GAEにいつでも移行できるように走らせています。
GAEのデプロイは癖があって、gcloud app deploy
コマンドをつかってDockerビルドを走らせますが、その時にDocker内に外部から環境変数を設定することができません。そのため、アセットプリコンパイルのビルドの際、asset_sync
を使っていると別サーバーへ同期に失敗します。また、パイプライン上の別ステップに環境変数を当てて行うことはできるが、gcloud
のデプロイステップとアセットプリコンパイルが重複して適切なダイジェストを発行できません。従って、GAEをつかう場合は ./public
ディレクトリをつかうのが現状の正解です。HerokuのSlugの取り扱い方針と違うので注意が必要です。
GAEのコンテナの中身は、gcloud beta app gen-config --runtime=ruby --custom
で出力されるDockerfileを参照ください。
deploy-prod-gae:
steps:
- bundle-install
- script:
name: Install ImageMagick
code: |
apt-get update
apt-get install -y nodejs imagemagick
- script:
name: Echo Ruby information
code: |
env
echo "ruby version $(ruby --version) running!"
echo "from location $(which ruby)"
echo -p "gem list: $(gem list)"
- script:
name: DB Migrate
code: |
RAILS_ENV=production \
DATABASE_URL=${DATABASE_URL} \
bundle exec rake db:create db:migrate --trace
- script:
name: Install gcloud
code: |
curl https://sdk.cloud.google.com | bash
source ~/.bashrc
- script:
name: Authenticate gcloud
code: |
gcloud config set project utagaki-v2
openssl aes-256-cbc -k ${DECRYPT_KEY} -d -in ./gcloud.json.encrypted -out ./gcloud.json
gcloud auth activate-service-account --key-file ./gcloud.json
- script:
name: Deploy app to Google App Engine
code: |
gcloud app deploy ./app.yaml --promote --stop-previous-version
after-steps:
- wantedly/pretty-slack-notify:
webhook_url: ${SLACK_WEBHOOK_URL}
channel: general
post-deployパイプラインは本番環境にデプロイした後の後処理用です。参考程度に git tag
をつけています。
post-deploy:
steps:
- add-ssh-key:
host: github.com
keyname: GITHUB
- add-to-known_hosts:
hostname: github.com
fingerprint: 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48
- script:
name: Add git-tag
code: |
_tag=$(date -u -d '9 hours' +%Y-%m-%d-%H-%M-%S)
git remote add origin git@github.com:nabinno/utagaki.git
git config --global user.email 'wercker@blahfe.com'
git config --global user.name 'Wercker Bot'
git tag -a $_tag master -m 'wercker deploy'
git push origin $_tag
after-steps:
- wantedly/pretty-slack-notify:
webhook_url: ${SLACK_WEBHOOK_URL}
channel: general
こうしてWerckerの設定ファイルを書いてみるに、どのCI、どの仮想環境も同じ書き味ということが分かります。当処懸念していたDocker化することによる嵌まり事はなく、すんなり移行することができました。
手軽さ、管理のしやすさから、今後はすべてのCIがDockerに移行するでしょう。
テクノロジーの進化は、絶え間ない変化の中で私たちの日常を塗り替えてきました。時には経済的な危機が、新たな可能性を切り拓く契機となることもあります。そこで、過去のリセッション期に生まれたテクノロジーの足
ATKerneyの課題解決パターン は、課題の本質を見極め、効果的な戦略的構造化を通じて解決策を導き出す手法にフォーカスしています。この冒険の旅は、解決者と協力者たちが心を一つにし、課題に立ち向かう様
私はいわゆる就職氷河期世代です。周囲から時折漏れ聞こえる不平のような言葉がありますが、それを単なる不平として片付けるのはもったいない気がします。できれば、その中に新しい視点を見つけ、次のチャンスへ繋げ