はじめに
数日前にCircleCIでCI部分(自動ビルト、自動テスト)が実装できたので
次の実装としてCD部分(自動デプロイ)に取り組みました。
なお、CircleCIからcapistranoを介して
EC2に対してデプロイを実行する設定です。
私の場合、AWSセキュリティグループやcapistranoといった
CircleCIではない箇所でエラーに遭遇しました。
同じ状況に陥った方の参考になれば幸いです。
前提
・CircleCIのCI部分(自動ビルド、自動テスト)は実装できている
・ローカル上からcapistranoによってEC2にデプロイができている
使用技術
・Rails: 6.1.4
・Ruby: 2.7.3
・mysql: 8.0.25
・capistrano: 3.16.0
・EC2: AmazonLinux2
CircleCIの実装前に
実装目標はCircleCIでの自動デプロイです。
そのため、まずは ローカルから
1. SSH接続が可能か
2. Capistranoでデプロイが可能か
の2点を検証すべきです。
私の場合は、AWSインフラを構築後、数週間の期間が空いておりましたが
ここの確認を飛ばしていましたため、
エラーが発生した際の原因突き止めに時間がかかりました。
(1) ssh接続の確認
ここでは、sshコマンドを一部省略するために、下記ファイルを確認しています。
(この設定は任意です。通常のsshコマンドでEC2ログインするなら不要です。)
Host 自分のアプリ名
Hostname 自分のElasticIP
User ec2-user(AWSデフォルトのユーザーの場合)
IdentityFile ~/.ssh/アプリ名.pem
ローカルのターミナルからコマンドを入力し、
EC2にssh接続できるか検証します。
# 省略コマンド
% ssh 自分のアプリ名
# 通常コマンド
% ssh -i ~/.ssh/アプリ名.pem ec2-user@自分のElasticIP
〜〜返答〜〜
__| __|_ )
_| ( / Amazon Linux 2 AMI
___|\___|___|
↑ ssh接続が成功してる
何故かログインできない場合は、EC2のセキュリティグループで
インバウンドルールを疑ってみてください。
接続先をマイIP(ローカルPC)にしている場合は、
wifiの切断などがあると、再びDHCPが働き、
別のグローバルIPが割り振られる可能性があります。
その場合は、
・再度AWSコンソールでマイIPを選び直す
・Anywhere-IPv4を選択する
で解決します。
(2) capistranoデプロイの確認
ローカルからcapistranoのデプロイ実行を起動させます。
# Railsアプリのディレクトリで実行
% bundle exec cap production deploy
〜〜返答〜〜
省略
00:57 puma:restart
↑ puma再起動などエラーなく一連の動作が実行されている。
途中でデプロイが止まってしまう場合、Gemfile.lock(gem)が
・ローカル
・本番環境
で差異がないか疑ってみてください。
このエラーであれば、
ローカルでbundle installコマンドを実行した後に
git pushコマンドでGitHubのmasterブランチに
コードを反映させる
で解決します。
### Railsアプリのディレクトリで実行
# gemインストールして、Gemfile.lockに反映
% bundle install
# 変更したファイル(Gemfile.lock)をGitHubのmasterブランチに反映
% git push
# 反映したmasterブランチのコードを元にcapistranoがデプロイ実行
% bundle exec cap production deploy
私の場合は、CircleCIの実装に入ってから
Gemfile差異によるエラーが発生して、capistranoがどこのGitHubコードを
もとに実行しているのか整理がつかなくなり、悩みました。
(capistranoの設定によっては、masterブランチではなく
所属するブランチに変更できるのかもしれません。)
(1),(2)も初期実装から間が空いているほど
エラーになる可能性が高いかと思います。
-> wifiいじったり、gem追加/削除したりしてる可能性で
なので、CircleCIの自動デプロイ実装前に確認しておくことが
良いと思います。
では、本題のCircleCI(自動デプロイ設定)
工程としては以下の通りです。
・CircleCIコンソール: ssh秘密鍵を登録
・CircleCI設定ファイル: ssh秘密鍵を読み込ませる
・CircleCI設定ファイル: capistranoのデプロイが起動させる
・CircleCI設定ファイル: masterブランチの時だけデプロイされる様に条件をつける
次の章から実装していきます!
ssh秘密鍵を登録
今までは、ローカルからEC2へSSH接続しておりましたが、
CircleCIからEC2へSSH接続できる様にSSH秘密鍵を登録します。
ここでは、CircleCIのコンソールを操作します。
まず、対象リポジトリのプロジェクトで画面右上にある
Project Settingsボタン
をクリックします。
次に画面が切り替わったら、画面左側にある
SSH Keysタブ
をクリックします。
SSH Keyタブに切り替わったら、画面を下にスクロールして
Add SSH Keyボタン
をクリックします。
すると、ポップアップが出現しますので
下記の2つを入力して、Add SSH Keyボタンを押します。
- ドメイン名かElasticIPを入力
- ssh接続の認証に必要な鍵のデータを貼り付け
2.は以下の様にターミナルからコマンドでコピーもできます。
% pbcopy < ~/.ssh/〇〇.pem
↑ 裏でクリップボードにコピーされている状態(後は貼り付けるだけ)
Add SSH Keyボタンを押すと、
●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●といった
Fingerprintが生成されます。
これは次章で使いますので、コピーしておきましょう。
以上で、SSH秘密鍵の登録が完了です。
ssh秘密鍵を読み込ませる
先ほど登録したSSH秘密鍵を
CircleCI設定ファイル(.circleci/config.yml)で読み取らせます。
省略
- add_ssh_keys:
fingerprints:
- "●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●"
●●部分はCircleCIコンソール画面のfingerprintsに表示されているものを
そのままコピペします。
ここでSSH接続の検証のために、git pushコマンドをしてみましょう。
上手くいけば、Installing additional ssh keysが緑色で表示されて
ssh秘密鍵のインストールが成功していることを確認できます。
ここまでの作業により、CircleCIからEC2へのssh接続ができる様になりました。
capistranoのデプロイが起動させる
ここでも同じくCircleCI設定ファイル(.circleci/config.yml)を
編集していきます。
次はcapistranoの実行コマンドを追記します。
省略
- add_ssh_keys:
fingerprints:
- "●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●"
# capistrano実行コマンドを追記
- deploy:
name: Capistrano deploy
command:
bundle exec cap production deploy
追記できたら、git pushしてみましょう。
これでCircleCIからcapistranoに橋渡しがされて
capistranoがデプロイ実行をしてくれます!
masterブランチの時だけデプロイされる様に条件をつける
前章までの実装では、git pushした時点で
毎回デプロイが実装されてしまいます。
そこで、masterブランチにマージされた時だけ
デプロイが実行される様に修正していきます。
省略
- add_ssh_keys:
fingerprints:
- "●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●"
# capistrano実行コマンドにif文で条件分岐をさせる
- deploy:
name: Capistrano deploy
command: |
if [ "${CIRCLE_BRANCH}" != "master" ]; then
exit 0
fi
bundle exec cap production deploy
以上で完成です!
実装後のconfig.yml
参考までに自動デプロイの実装を終えて完成したconfig.ymlを載せておきます。
ただ、自動ビルド/自動テスト/自動デプロイをまとめて記述しているので
可読性としてどうかな〜って気持ちがあります。
ですので、個人的には各フェーズで3つのjobに分けて
見やすく変更していこうと考えています。
当記事では、自動デプロイの簡易記述として
上記の改善点は考慮せずにこれで完了とします。
version: 2.1
jobs:
build:
docker:
- image: circleci/ruby:2.7.3-node-browsers
environment:
RAILS_ENV: 'test'
- image: circleci/mysql:8.0
command: mysqld --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 'true'
MYSQL_ROOT_HOST: '%'
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "Gemfile.lock" }}
- v1-dependencies-
- run:
name: install dependencies
command: |
bundle install --jobs=4 --retry=3 --path vendor/bundle
- save_cache:
paths:
- ./vendor/bundle
key: v1-dependencies-{{ checksum "Gemfile.lock" }}
- run: yarn add @fortawesome/fontawesome-free
- run: mv config/database.yml.ci config/database.yml
- run: bundle exec rake db:create
- run: bundle exec rake db:schema:load
- run:
name: Rubocop
command: bundle exec rubocop
- run:
name: RSpec
command: |
mkdir /tmp/test-results
TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
circleci tests split --split-by=timings)"
bundle exec rspec \
--format progress --format RspecJunitFormatter \
--out /tmp/test-results/rspec.xml \
$TEST_FILES
- store_test_results:
path: /tmp/test-results
- store_artifacts:
path: /tmp/test-results
destination: test-results
- add_ssh_keys:
fingerprints:
- "●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●:●●"
- deploy:
name: Capistrano deploy
command: |
if [ "${CIRCLE_BRANCH}" != "master" ]; then
exit 0
fi
bundle exec cap production deploy
ちょっと疑問に思ってる事
CircleCIの自動デプロイを実装しているQiita記事を見ていると
・RAILS_MASTER_KEY
・PRODUCTION_SSH_KEY
の環境変数をCircleCIコンソールで設定している情報が多かった。
CircleCIではなくcapistranoが上記を読み込めば上手く動作するのでは…
という仮説のもと、今回の実装としているけど自分の勉強不足かな?
上記2点の設定はしていないために
セキュリティ的に甘くなっている可能性もあるので
今後に不都合が出てきた際には修正しよう!
はい。独り言でした。
参考記事
CircleCIの自動ビルド/自動テスト編
CI部分はこちらの記事で紹介しております。
終わりに
これで、CI/CDどちらも実装はできたものの最小限の実装しか出来ておりません。
CircleCIには、並列作業したり、ワークフローで実行順序を決めたり、
もっと効率化させる実装があるはずなので、
先人たちの実装を参考にしてブラッシュアップをしていこうと思います。
最後までお読み頂き、ありがとうございました。