今回解決したい問題
現在CapistranoでAuto Scaling環境にデプロイしていますが、以下のような課題があります
- Capistranoのデプロイ先のサーバが設定ファイルに直書きされている。Auto Scalingでスケールアウト・インするたびに書き直さなければいけない
- ソースコードを更新するたびにAMIを作り直さなければいけない。それに付随してAuto Scalingの起動設定やAuto Scalingグループの設定変更が必要
上記を解決すれば、ソース更新後のデプロイがいつでも可能になり、スケールアウトしても最新のコードがインスタンスに反映されるようになります
環境
- Bitbucket
- Rails: 5.1.1
- Ruby: 2.4.1
- デプロイ系の各種Gem
- capistrano (3.8.X)
- capistrano-bundler (1.2.X)
- capistrano-rails (1.1.X)
- capistrano-rbenv (2.1.X)
- capistrano3-delayed-job (1.7.X)
- capistrano3-unicorn (0.2.X)
- aws-sdk (2.9.29)
作業ログ
IAMでユーザ作成
すでに権限があるユーザがいれば別ですが、新規作成する場合は以下の権限がついたユーザを作成しておきます。(もっと権限絞れるかもしれませんが、ReadOnlyなので大きくは気にせず以下とします)
Auto Scalingされているインスタンスを自動で取得する設定
現在、config/deploy/環境.rb
にデプロイ先がドメイン名とともに直書きされているので、aws-sdk
を使ってAuto Scalingグループ名から取得できるようにします。
aws-sdk
は設定を記述しない場合は、~/.aws/config
などから情報を取得するのですが、今回は特定ディレクトリ以下で異なるAWS設定を使いたいためdirenvで設定をわけます。
Railsアプリディレクトリ直下でdirenv edit .
と実行し、以下の設定を記述します。xxxとyyyは各自変えてください。
export AWS_ACCESS_KEY_ID=xxx
export AWS_SECRET_ACCESS_KEY=yyy
export AWS_DEFAULT_REGION=ap-northeast-1
export AWS_DEFAULT_OUTPUT=json
次に、Capistrano側に変更を加えます。まずAWSのAuto Scalingグループからインスタンスの情報を取得するためにlib/capistrano/helpers/aws_utils.rb
に以下の記述をします。なお、aws-sdkはGemfileに記載されているものとしてすすめます。
lib/capistrano/helpers/aws_utils.rb
require 'aws-sdk'
module AWSUtils
def self.auto_scaling_dns_list(group_name)
instances_of_as = auto_scaling_instances(group_name)
instances_ids = instances_of_as.map(&:instance_id)
instances_of_ec2 = instances_ids.map do |instance_id|
Aws::EC2::Resource.new.instance(instance_id)
end.sort_by { |v| [v.launch_time, v.id] }
instances_of_ec2.map(&:public_dns_name)
end
def self.auto_scaling_instances(group_name)
as = Aws::AutoScaling::Client.new
instances_of_as = as.describe_auto_scaling_groups(
auto_scaling_group_names: [group_name],
max_records: 1
).auto_scaling_groups[0].instances
return [] if instances_of_as.empty?
instances_of_as
end
end
次に、config/deploy/環境.rb
に以下の記述を追記し、既存のserverの記述を削除します。
userやrolesなどは適宜かえてください。auto_scaling_dns_listで返ってくるリストは、インスタンス起動順にソートされているので必ず古いインタスンスでmigrationなどが実行されるようになっています。
config/deploy/環境.rbに追記
group_name = 'auto_scalning_test'
AWSUtils.auto_scaling_dns_list(group_name).each_with_index do |dns, i|
roles = i.zero? ? %w[web app db] : %w[web app]
server dns, user: 'ec2-user', roles: roles
end
最後にCapfile
に以下の記述を追記し、deployスクリプトからAWSUtilsが使えるようにします
Capfile
Dir.glob('lib/capistrano/helpers/*.rb').each { |r| import r }
スケールアウトした際に最新のコードが適用されるようにする
以下のように解決します。以下増えたインスタンス自身が起動後に自動で行います。
- 最新ソースコードを特定のディレクトリにpullする
- 最新のソースコード上からlocalhost(自インスタンス)に向けてデプロイする
最初に、localhostにsshでログインできるようにします。~/.ssh/id_rsa
と~/.ssh/id_rsa.pub
は存在している前提です。
~/.ssh/configに以下を追加
Host *
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
# 権限をかえる
chmod 400 ~/.ssh/config
# ローカルホストに繋げれるか確かめる
ssh localhost
次に、ソースコードをpullできるためにはBitbucketに鍵が登録されている必要があります。~/.ssh/id_rsa.pub
を以下のように追加します。
次に、特定ディレクトリ以下に最新ソースコードをcloneします。今回は、/home/ec2-user/deploy/プロジェクト名
のフォルダにcloneしました。
インスタンスが新規作成されたときのユーザデータを作ります。Auto Scalingの起動設定を作成するときに以下のユーザデータを追記しておきます。
ユーザデータのログは/var/log/cloud-init-output.log
や/var/log/cloud-init.log
に出力されるのでうまくスクリプトが起動できなければそちらをみてデバッグします。
#!/bin/bash -ex
su - ec2-user /home/ec2-user/bin/self-deploy.sh production
上記のユーザデータで実行される/home/ec2-user/bin/self-deploy.sh
は以下のスクリプトとなります。作成後、chmod 755 ~/bin/self-deploy.sh
で権限をかえておきます。
/home/ec2-user/bin/self-deploy.sh
#!/bin/bash
####################################
# /home/ec2-user/bin/self-deploy.sh
####################################
# path
app_path=/var/www/aws-rails-cap-autoscale/current
deploy_path=/home/ec2-user/deploy/aws-rails-cap-autoscale
# environment
rails_env=$1
# change directory
cd $deploy_path
#️ export $PATH
export PATH
# update rails app for deploy
echo "=== [${rails_env}] update rails app for deploy ==="
git fetch origin master
git reset --hard origin/master
echo ""
# bundle install for deploy
echo "=== [${rails_env}] bundle install for deploy ==="
bundle install --path vendor/bundle --without production staging --quiet -j8
echo ""
# self-deploy
echo "=== [${rails_env}] self-deploy to localhost ==="
bundle exec cap local_${rails_env} deploy
echo ""
Capistrano側にconfig/deploy/local_production.rbを以下のように記載します。
config/deploy/local_production.rb
server 'localhost', user: 'ec2-user', roles: %i[web app db]
set :stage, :production
set :rails_env, :production
最後に上記を適用した、EC2のAMIイメージを作成し、Auto Scalingを更新すればスケールアウト後に自動で最新コードが適用されます。
まとめ
今回の作業でデプロイが結構楽になりましたが、まだ以下のような、今後解決したい課題があるのでいずれやろうと思います。
- デプロイするたびにbundlerが2回はしるのでやや時間がかかる。rsyncなどで踏み台サーバから更新する