CircleCIでIP制限のあるEC2インスタンスに自動デプロイできるようにする
はじめに
PythonのWebアプリケーションフレームワークFlaskを使って、「消耗品買い物リスト」を作ってみるシリーズ第7回目です。
今回はEC2へのデプロイ自動化の設定を行います。
前回まで
- 0回 : Flaskで簡単なWebサービスを作ってみる
- 1回 : スプレッドシート+Google Apps Scriptでプロトタイプを作る
- 2回 : FigmaでUI制作をしてみたが早々に次のフェーズに進んだ件
- 3回 : DockerでFlaskが動き、簡単なテストが通る状態を作る
- 4回 : [スクショ付き]AWSにVPCを構築し、EC2を立てる
- 5回 : EC2でDockerコンテナを起動し、Flaskを動かす
- 6回 : CircleCIでFlaskアプリの自動テストができるようにする
今回やること
前回までで、CircleCIを使ってのテスト自動化ができるようになったところで、デプロイ自動化の設定をしていきます。
実現したいこと
- テスト自動化 ← 前回の記事はここまで
- 変更がPushされたらテストが自動で走る
- デプロイ自動化 ← 今回の記事はここまで
- masterに変更がPush(基本的にはブランチのマージ)されたらテストが自動で走り、テストが通ると自動で本番環境にデプロイする
EC2へのデプロイですが、やっていることはEC2インスタンスにSSH接続して、masterブランチをPullする、というシンプルな動きになります。
公式ドキュメントのSSHの項を参考にします。
流れ
- SSHキーを追加
- EC2インスタンスの接続ユーザー情報を環境変数に設定
- IPを開ける設定
- (AWS)デプロイ用IAMユーザーを作成
- (CircleCI)IAMユーザーの接続に情報をCircleCIの環境変数に設定
- config.ymlを編集
- 動作確認
SSHキーを追加
- CircleCIダッシュボードを開く
- Projectsから対象リポジトリを選択
- Pipelinesの画面右上
Project Settings
- 左側のメニュー
SSH Keys
Additional SSH Keys
>Add SSH Key
- Hostname: (EC2のIP)
- SSHキーを使用するHost先を指定します。未指定だとすべての接続先に対して同じSSHキーを使用します。
- まだドメインの紐付けをしていないので、ここでは使用しているEC2のIPを入力します。(ElasticIPで固定IPにしたものが必要です)
- Private Key: 秘密鍵の中身
$ cat ~/.ssh/[秘密鍵]
として出た結果を貼り付けます- ※
-----BEGIN RSA PRIVATE KEY-----
から始まるすべてを貼り付けないとエラーになるので注意
Add SSH Key
を押下
EC2インスタンスの接続ユーザー情報を環境変数に設定
引き続きCircleCIダッシュボードのProject Settingsでの操作です。
- 左側のメニュー
Environment Variables
Add Environment Variable
- Name: DEPLOY_USER
- Value: (EC2に接続するuser名)
Add Environment variables
- 同じ要領で以下も追加
- Name: HOST
- Value: (EC2のIP)
IPを開ける設定
EC2インスタンスに接続できるIPを制限している場合、CircleCIコンテナからの接続を受け付けることができません。
なので、ビルド時にCircleCIコンテナのIPからのSSH接続を許可し、処理が完了したら許可を取り消す、という方法を取ります。
具体的な手順は以下です。
- AWSでCircleCI用のIAMユーザー/セキュリティグループを作成
- CircleCIの環境変数にAWSアクセスキーを登録
- シェルを作成
AWSでCircleCI用のIAMユーザー/セキュリティグループを作成
ここはAWSコンソールでの作業です。
IAMユーザー作成
- AWSコンソール > IAM > ポリシー
- ポリシーの作成 > JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"ec2:AuthorizeSecurityGroupIngress",
"ec2:RevokeSecurityGroupIngress"
],
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:security-group/*"
}
]
}
- ポリシーの作成が完了
- ユーザー > ユーザーを追加
- ユーザー名: circleci
- アクセスの種類: プログラムによるアクセス
- アクセス許可の設定 > 既存のポリシーを追加 > さきほど作成したポリシーを追加
- CSVをダウンロード
セキュリティグループ作成
CircleCI用のセキュリティグループを作成します。
もともと使っていたセキュリティグループをそのまま使ってもいいのですが、管理しやすさを考えてCI/CD用のグループを作っておきます。
- AWSコンソール > EC2 > セキュリティグループ
- セキュリティグループを作成
- セキュリティグループ名や説明などは任意
- インバウンド、アウトバウンドともにデフォルトでそのまま作成
- 作成したセキュリティグループをEC2インスタンスにアタッチ
- インスタンス一覧からインスタンスを右クリック > ネットワーキング > セキュリティグループを変更
- 前項で作成したセキュリティグループを追加(既存のセキュリティグループを外す必要は無いです)
CircleCIの環境変数にAWSアクセスキーを登録
再びCircleCIダッシュボードに戻り、前項でダウンロードしたCSVの認証情報を登録します。
それぞれ以下の変数名で設定します。
- アクセスキー : AWS_ACCESS_KEY_ID
- シークレットアクセスキー : AWS_SECRET_ACCESS_KEY
シェルを作成
セキュリティグループに自身のIPを追加→SSH接続してデプロイ→セキュリティグループから自身のIPを削除するシェルを作成します。
shells/deploy.sh
#!/bin/sh
set -ex
export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
export AWS_DEFAULT_REGION="ap-northeast-1"
export DEPLOY_USER=${DEPLOY_USER}
export HOST=${HOST}
MY_SECURITY_GROUP="[セキュリティグループID]"
MY_IP=`curl -f -s ifconfig.me`
trap "aws ec2 revoke-security-group-ingress --group-id $MY_SECURITY_GROUP --protocol tcp --port 22 --cidr $MY_IP/32" 0 1 2 3 15
aws ec2 authorize-security-group-ingress --group-id $MY_SECURITY_GROUP --protocol tcp --port 22 --cidr $MY_IP/32
ssh $DEPLOY_USER@$HOST "cd [プロジェクトディレクトリ] && git pull origin master"
シェルはこちらの記事を参考にしています。記事に詳しい解説があるのでご参考ください。
config.ymlを編集する
version: 2
jobs:
build-and-test:
(省略)
deploy:
machine:
enabled: true
steps:
- checkout
- run: sudo pip install awscli
- run:
name: SSH経由のデプロイ
command: |
./shells/deploy.sh
workflows:
version: 2
workflows:
jobs:
- build-and-test
- deploy:
requires:
- build-and-test
filters:
branches:
only: master
動作確認
ここまでの変更内容を、ci-test
ブランチにプッシュした後、ブランチをmaster
にマージします。
CircleCIダッシュボードで確認してみると…
ci-test
ブランチではbuild-and-test
ジョブのみが実行され、
master
ブランチにマージされると、build-and-test
ジョブに加えdeploy
ジョブが動いているのがわかります。
無事通っているので、ローカルマシンからインスタンスに接続し、git logで変更内容がpullされていることを確認できたら自動ビルド設定は完了です。
これでmasterブランチに変更があったら自動でデプロイが動くようになりました。
ハマったポイント
設定でハマったポイントとその対応策を紹介します。
構文エラー
Error: Config does not conform to schema: {:workflows {:workflows {:jobs [nil {:deploy (not (map? nil)), :requires (not (map? a-clojure.lang.LazySeq)), :filters {:branches disallowed-key}}]}}}
workflows:のインデントが一段足りなかったので調整
CIでshellが動かない
$ chmod 755 [filepath]
して再度commit
shellが失敗してる
#!/bin/bash -eo pipefail
./shells/deploy.sh
/usr/local/lib/python2.7/dist-packages/urllib3/util/ssl_.py:394: SNIMissingWarning: An HTTPS request has been made, but the SNI (Server Name Indication) extension to TLS is not available on this platform. This may cause the server to present an incorrect TLS certificate, which can cause validation failures. You can upgrade to a newer version of Python to solve this. For more information, see https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
SNIMissingWarning,
ssh: Could not resolve hostname ***********}: Name or service not known
/usr/local/lib/python2.7/dist-packages/urllib3/util/ssl_.py:394: SNIMissingWarning: An HTTPS request has been made, but the SNI (Server Name Indication) extension to TLS is not available on this platform. This may cause the server to present an incorrect TLS certificate, which can cause validation failures. You can upgrade to a newer version of Python to solve this. For more information, see https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
SNIMissingWarning,
CircleCI received exit code 0
シェルスクリプトのタイポを修正。
※set -ex
を付けて実行すると以下のように出力されてめちゃくちゃ便利です。
+ export AWS_ACCESS_KEY_ID=XXX
+ AWS_ACCESS_KEY_ID=XXX
+ export AWS_SECRET_ACCESS_KEY=XXX
+ AWS_SECRET_ACCESS_KEY=XXX
+ export AWS_DEFAULT_REGION=ap-northeast-1
+ AWS_DEFAULT_REGION=ap-northeast-1
+ export DEPLOY_USER=XXX
+ DEPLOY_USER=XXX
+ export 'HOST=X.XXX.XX.XX}'
+ HOST='X.XXX.XX.XX}'
+ MY_SECURITY_GROUP=sg-xxx
++ curl -f -s ifconfig.me
+ MY_IP=XXX.XXX.XX.XX
+ aws ec2 authorize-security-group-ingress --group-id sg-xxx --protocol tcp --port 22 --cidr xxx.xxx.xx.Xx/32
+ ssh 'xxx@x.xxx.xx.xx}' 'cd to-buy-list && git pull origin master'
ssh: Could not resolve hostname x.xxx.xx.xx}: nodename nor servname provided, or not known
(HOSTを指定する部分で閉じカッコ}
が一つ多いという凡ミスでした)
終わりに
今回でテスト・デプロイ自動化の設定が完了し、開発基盤がようやく整いました。長かった…
次回からようやく機能開発に着手します。
ディスカッション
コメント一覧
まだ、コメントがありません