読者です 読者をやめる 読者になる 読者になる

scramble cadenza

技術ネタのガラクタ置き場

CircleCI から deploy させる話

イントロ

CircleCI からテストが通ったら capistrano 使って deploy させたい。
けど、対象サーバーは IP 制限がかかっている。CircleCI の IP なんてコロコロ変わるし、どうしたらいいんや... って話。

これができると、PR を github ボタンでマージしたら、自動(当然テストも通した後)で deploy されるので、非常に嬉しい。

方針

before deploy

  • awscli を使い security group の inbound に自分自身の ip を付与
    • CircleCI の内部から IP address を割り出して、コマンドラインで一時的にinboud に追加させる

deploy

  • コマンド(bundle exec cap deploy)を実行

after deploy

  • awscli を使い security group から ip を取り外す
    • before deploy の逆をやればいい

前準備

IAM user や security group の名前は適当に決めてます。
やること多いです。

  1. IAM user circle_ci を作成
    • ec2 に関する権限を全て付与(今回に限って言えば、全て付与しなくても動くはず)
    • 詳細は後述
  2. CircleCI 用の security group を作成する
    • 名前は circle_ci
    • 普段は inbound の欄は空
    • 一時的に CircleCI の IP が許可される用の security group
  3. deploy したいサーバーに security group circle_ci を attach する
  4. AWS環境変数を CircleCI の設定画面上で予め登録する
    • IAM user circle_ci の credential を登録
  5. deploy 対象のサーバーにログインするための秘密鍵を CircleCI の設定画面上で登録する

実装

CircleCIからCapistranoを利用してAWS(EC2)にデプロイする - Qiita に大体書いてある。

deploy するコマンドとして、deploy.sh みたいなシェルスクリプトを作って、それを deployment で実行させればいい。

参考記事とは少し違っているけど、自分が作成した deploy.sh は以下のようになった。

#!/bin/sh
set -ex

SECURITY_GROUP_NAME="circle_ci"
IP=`curl -s ifconfig.me`

trap "aws ec2 revoke-security-group-ingress --group-name ${SECURITY_GROUP_NAME} --protocol tcp --port 22 --cidr ${IP}/32" 0 1 2 3 15
aws ec2 authorize-security-group-ingress --group-name ${SECURITY_GROUP_NAME} --protocol tcp --port 22 --cidr ${IP}/32
bundle exec cap prod deploy

ポイント

set -ex

-x は毎度おなじみ debug 用途。実行したコマンドを標準出力するオプション。
-e はコマンドに失敗したら、即時にスクリプトを終了する。一行づつ && で挟んでいるイメージ。
コマンドが 3 つ書かれているけど、どれか一つでも失敗したら、deploy せず即時失敗扱いで良いので、このようにしている。

trap コマンド

何かしら異常があった場合、変更を加えた Security Group を元に戻さないといけない。
でないと、ある IP から接続可能な状態になってしまい、セキュリティ上良くないから。

つまり、deploy に失敗しようが成功しようが、revoke-security-group-ingress は実行させたい、ということ。
そのために trap コマンドでシグナルを補足しています。

0 1 2 3 15 を書いておけば、大抵のシグナルには反応してコマンドが発動する。

肝は最終的な exit コードは trap コマンドの実行結果ではなく、trap 以前に実行した最後のコマンドであること。
つまり bundle exec cap prod deploy で exit コード 1 が返ってきた場合、trap が発動して、revoke され、exit コード 1 でシェルスクリプトが終了する。
(CircleCI では exit コード 1 ならば、build 失敗扱いになる)

circle.yml

circle.yml は以下のようになる。
pip install しないと awscli のバージョンが古く、コマンド実行に失敗する。

machine:
  timezone: Asia/Tokyo
  ruby:
    version: 2.1.3
dependencies:
  pre:
    - sudo pip install awscli
deployment:
  master:
    branch: master
    commands:
      - ./deploy.sh

まとめ

ここまで準備して、ようやく master へのマージ + テスト通ったら deploy が自動化されます。

とはいえ、これでも revoke-security-group-ingress に失敗する可能性があり、CircleCI の IP address が許可されたままになる可能性があります。が、そのリスクには目を瞑っています。
revoke-security-group-ingress に失敗したら build にも失敗するはずなので、すぐに調査すればいい話だから。
circle_ci の inbound を削除すれば元通りなので、復旧作業も楽勝。と思ってる。

なお、Circles EC2 IP addresses and AWS security group - CircleCI なんてのがあって、この情報をうまく使えば deploy できるんや... って思いがちだけど、これは孔明の罠なので要注意。

できてないこと

  • pip install awscli をキャッシュさせること
    • 頑張れば出来そうだけど、まだやってない

参照