AWSでAuto Scaling + Capistranoでデプロイはしているが、ソースコードが更新されるたびにAMIを作り変える手順をちょっと楽にする

  • 5
    Like
  • 0
    Comment

今回解決したい問題

現在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なので大きくは気にせず以下とします)

IAM_Management_Console.png

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 }

スケールアウトした際に最新のコードが適用されるようにする

以下のように解決します。以下増えたインスタンス自身が起動後に自動で行います。

  1. 最新ソースコードを特定のディレクトリにpullする
  2. 最新のソースコード上から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を以下のように追加します。

image.png

次に、特定ディレクトリ以下に最新ソースコードを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などで踏み台サーバから更新する

参考