LoginSignup
0
0

More than 3 years have passed since last update.

第4回 AWSに自動でテスト/デプロイしてくれるインフラの設定・構築(Capistrano編)

Last updated at Posted at 2020-12-21

本シリーズ集

タイトル
0 目標・やりたいこと
1 AWS編
2 rails開発環境構築編
3 Nginx・MySQL編
4 Capistrano編
5 CircleCI編
6 総集編

Capistranoとは?

 Rubyのgemの一つ。スクリプトにより、デプロイを簡単にしてくれる代物。
 ただ、rubyに限らず、JavaやPHPなど、いろんな言語でのデプロイ操作に適用可能。
 これとGithub、CircleCIを使い、「『githubのmasterブランチにmergeされた時』に自動でデプロイされる」流れを構築していきたいと思います。が、
 今回は、Capistranoの実装 (not yet パイプラインの構築) までとなっております。

 本記事は、Capistranoの概要、及び実装しか示していないので、Capistranoについて深く知りたい方は、Capistrano3の動作まとめ。公式サイト入門 Capistrano 3 ~ 全ての手作業を生まれる前に消し去りたい 等々を参考にしてください。

Capistrano使い方

Capistranoのインストール

 Capistranoをbundlerを使用してインストールしていく。
 
 Gemfileの開発環境の部分(本番環境では使用しないであろうため)に、Capistranoをインストールする旨の記述する

Gemfile
group :development do
  ...
  gem 'capistrano', '~> 3.14', '>= 3.14.1', require: false
end

のように記述し、下記に従ってコマンドを実行する。

$ docker-compose run web bash // dockerコンテナ内にbashで起動
root@~:/sample# bundle install  // Gemfileに新たに記入したgemをインストール
root@~:/sample# bundle exec cap install // Capistranoのコマンド

# bundle exec cap install を実行した時点で、新規ファイルが生成される。

smaple
 ├ config
 | ├ deploy
 | |  ├ production.rb
 | |  └ staging.rb 
 | └ deploy.rb
 ├ Capfile
 └ lib
   └ capistrano
      └ tasks

まずはCapfileを編集

 一度ファイルの中身を全消去して、下記のように編集する

Capfile
require "capistrano/setup"
require "capistrano/deploy"
# 以下の2行を書かないで実行すると、
# 『現在はSCM(Source Code Management)として、デフォルトでgitを使用していますが、将来バージョンアップした際に変更となる可能性があるので、なるべく書き加えてください。』
# といった旨のログが表示されるので、追記しておく
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git

次は config/deploy.rb ファイル

config/deploy.rb
# capistranoのバージョン指定
lock "~> 3.14.1"

# アプリケーション名
set :application, "sample"

# cloneするgitのリポジトリ (githubのサイトのsshキーアクセスのコピーを貼り付けるだけ)
set :repo_url, "git@github.com:/account_name/repository"

# デプロイするブランチ。デフォルトではmasterブランチだから、ここでわざわざ明示する必要ない。
set :branch, 'master'

# デプロイ先のディレクトリ
set :deploy_to, "/home/ec2-user/#{fetch :application}"

# 何世代前までリリースしたものを残しておくか
set :keep_releases, 5

# サーバ上でシンボリックリンクを貼る。
set :linked_files, %w[config/master.key config/database-pro.yml]

# シンボリックリンク元のフォルダを指定。(デフォルトで設定されているらしいけど、なぜか認識されないから定義)
set :shared_path, "/home/ec2-user/#{fetch 

 各設定項目については、下記の公式サイト(+α)でconfiguration 項目について詳しく書いてあるので、気になる方は参照しよう。

Capistranoの公式サイト(各設定項目について)
Capistranoがデフォルトで用意している変数一覧

最後に、config/prodution.rb

config/production.rb
# EC2のIPアドレスをip変数に格納
set :ip, "xx.xx.xx.xx"

# EC2のアクセスに必要な、IPアドレス、ユーザ名、使用するsshキーのPATH
# そしてEC2インスタンスに割り当てるサーバのロールを記述
server "#{fetch :ip}",
  user: "ec2-user",
  roles: %w{app},
  ssh_options: {
    keys: %w(/path/to/key),
    auth_methods: %w(publickey)
    # forward_agent: false,
  }

 最後のforward_agent: false は、ssh-agentを利用する際は、trueにする(デフォルトではfalse)。
 『ssh-agentとはなんぞや?』っと思ったそこの方、こちらの記事が参考になります。(僕も執筆するまでssh-agentの存在を知らなかった笑)
ssh-agentを利用して、安全にSSH認証を行う

 サーバのロールについては、この記事に書いてあるのがわかりやすかったです。
・(再掲)入門 Capistrano 3 ~ 全ての手作業を生まれる前に消し去りたい
 要は、それぞれのサーバ上で何か作業をしたい場合は、別個ロールを指定することで作業ができるというもの。

 これで、
$ bundle exec cap production deploy
というコマンドを打つことで、

config/deploy/production.rb
    or
config/deploy.rb
ファイルに書いてある、task :deploy do ~ end(後述) を実行してくれ、その記述がなかったら、デフォルトで用意されているgit cloneを使用したデプロイ方法のみ が実施される。

作業中に発生したエラー

結局EC2インスタンスにgitがインストールされてないが故のエラーで、それについてデバッグ方法から解決に至るまでの道のりを長々と書いているので、興味がある方のみ読んでください。(前にEC2インスタンスにgitをインストールさせておいたのはこのため)
 以下のコマンドで、設定したものがきちんと動作するかをチェックしてくれる(実際には動作しない)。
# cap production deploy --dry-run --trace


--traceオプションは、タスクの実行を追ってくれる。)
 そこでは上手くいったのに、本番(下記のコマンド)ではなぜか上手くいかなかった。
# cap pruduction deploy --trace


 ログに出力 or log/capistrano.log ファイルに一連のログが出力されるので、エラーが発生した際は、そこからエラー内容が読み取れ、その内容は以下のものだった。
 DEBUG [2d37ddb5]   /usr/bin/env:
 DEBUG [2d37ddb5]   git
 DEBUG [2d37ddb5]   : No such file or directory


 これは、Capistranoでgitを使用する際は、EC2インスタンスにあらかじめgitを入れておかなきゃダメみたい...。
 これもあり、AWSの構築編で、gitをあらかじめインストールさせておいた。
 あとは、
  ・EC2上でSSHキーを生成する
      or
  ・いつも自分が使用しているSSHキーを使用する
 でgithubにアクセスできるようにする。

EC2におけるgitのssh接続方法
 自分のローカルのPCにあるSSHキーでは、パスワードを求められるので、新しく生成した。
 EC2インスタンス上で、sshキー(秘密鍵・公開鍵)の作成と認証 流れ に則って、
/home/ec2-user/.ssh
 ディレクトリ配下に id_rsa ・ id_rsa.pub ファイルを生成する。
 生成されたキーの公開鍵を、githubのsshキーに新規登録すればOK。

サーバのディレクトリ構成

 解決後、もう一度 $ bundle exec cap production deployコマンドを打つと、EC2サーバに以下のディレクトリ構成でデプロイされる。

/home/ec2-user/sample  (:deploy_toで指定したパス)
├ revisions.log (更新履歴を表示。)
├ current
| └ (cloneしたアプリの中身)
├ releases
|  ├ 20201120135029  (デプロイした日時。:keep_releases に設定した値だけ保存される。)
|  └ 20201120135543 (ディレクトリ内には、その時のcloneしたアプリ内容が保存される)
├ repo
|  ├ FETCH_HEAD
|  ├ config
|  ├ HEAD 
|  ├ description
|  ├ packed-refs 
|  ├ info
|  |  └ exclude
|  ├ refs
|  |  ├ heads
|  |  └ tags
|  ├ objects
|  |  ├ info
|  |  └ pack 
|  ├ hooks
|  └ branches
└ shared (:linked_files、:linked_dirs という設定項目があり、その設定値によって結果が変わる。実際に動かしてみて確認しよう。)

 config/deploy.rb ファイルに書いてある設定を変更すれば、例えば、上記の『current』ディレクトリ(現行のもの。実はこのディレクトリ自体もシンボリックリンクだったりする。)に相当するディレクトリ名を変更するなどできるので、変更したい場合は、
(再掲)Capistranoの公式サイト(各設定項目について)
 を参照しよう。

デプロイしてそのまま動作させたい場合

 gitによるデプロイのみならず、他のファイルをアップロードしたり、その流れのまま直接インスタンス上でサーバを起動させたい場合は、task :deploy do ~ end を記述する。
 config/deploy/production.rb ファイルと config/deploy.rb ファイルどちらに記述しても動くが、今回は前者に記述する。
$ bundle exec cap 『production』 deployなので。ここが例えば、『staging』になっていたら、処理対象ファイルは、『conifg/deploy/staging.rb』・『config/deploy.rb』の(中のtask :deploy ...の部分)二つになる。)

config/deploy/production.rb
set :ip, "xx.xx.xx.xx"

server "#{fetch :ip}",
  user: "ec2-user",
  roles: %w{app},
  ssh_options: {
    keys: %w( /path/to/key ),
    auth_methods: %w(publickey)
  }

task :upload do
  shared_path = fetch :shared_path

  on roles(:app) do
    # シンボリックリンク元のファイルをサーバに直接アップロードする。
    # これがないとデプロイで『シンボリックリンク元のファイル、ディレクトリがありません』という旨の エラーが発生する。
    upload! "config/database.yml", "#{shared_path}/config"
    upload! "config/master.key", "#{shared_path}/config"
  end
end

task :deploy => :upload do
  appName = fetch :application
  on roles(:app) do
    execute "sudo service docker start"
    execute "cd #{appName}/current; docker-compose -f docker-compose_pro.yml up -d"
  end
end

 注意点として、upload! "config... の処理命令は、task :upload do ... end の中に書いていること。
 これは、taskの大まかな(本当に大まかです。)実行順序として、
1. task :upload
2. シンボリックリンクを貼る
3. task :deploy
 となっているので、task :uploadの中に記述していないと、『シンボリックリンク先がありません』とCapistranoに怒られる。
 

タスクの実行順序の確認方法
 $cap production deploy --dry-run --traceで実際にデプロイすることなく、デプロイ処理を疑似的に行い、本番環境を汚さずに実行順序を確認することができる。
root@b92e8bf7a35d:/sample# bundle exec cap production deploy --dry-run --trace
(...something)
** Execute upload
00:00 upload
      01 config/master.key /home/ec2-user/sample/current/config/
      02 config/database.yml /home/ec2-user/sample/current/config/
(...something)
** Execute git:clone
(...something)
** Execute deploy:log_revision


追加で、deploy.rbファイルにも少し手を加える。

config/deploy.rb
...
# デプロイしたcurrentディレクトリ内に、shared/config/* ファイルの内容をシンボリックリンクする。
set :linked_files, %w[ config/database.yml config/master.key ]
...

また、新たにDockerfile_prodocker-compose_pro.ymlファイルを追加。
 Dockerfile_proは、元々のDockerfileから、COPY 鍵PATH /root/.ssh/の部分を削除し、RUN bundle install--without test developmentを足しただけ。
Dockerfile_pro
FROM ruby:2.7.1
RUN apt-get update && apt-get install -y \
    build-essential \
    nodejs
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update && apt-get install -y --no-install-recommends yarn
WORKDIR /sample
COPY Gemfile /sample/Gemfile
COPY Gemfile.lock /sample/Gemfile.lock
RUN bundle install --without test development
COPY . /sample


 docker-compose_pro.yml は、docker-compose.ymlと大きく異なっている。
docker-compose_pro.yml
version: '3'
services: 
  web:
    build:
      context: .
      dockerfile: Dockerfile_pro
    image: test-rails-image
    container_name: test-rails-container
    command: bundle exec rails s -p 3000 -b '0.0.0.0' -e production
    ports:
      - 3000:3000
# logを取得したいならボリュームをマウントするべき?


 これで、OK...なはずだったのだが、実際にはいろいろエラーが発生した。

エラーその1

 database.ymlファイルがシンボリックリンクだとダメらしい。

File "/sample/config/database.yml" is a symlink that does not point to a valid file (RuntimeError)

エラーその2

secret_key_baseがないらしい。

Missing `secret_key_base` for 'production' environment, set this string with `rails credentials:edit`

 https://qiita.com/scivola/items/cc06ddbfd94d3118f693
 この記事によると、原因はmaster.keyがないせい?
 まーたシンボリックリンクじゃあかんかったんか。。。

解決

 2つを通して、なぜシンボリックリンクだとうまくいかなかったのかがわかった。dockerのコンテナ内で ls -al config/database.ymlコマンドを打ってみる。

root@a1a44def1d0f:/sample# ls -al config/database.yml
lrwxrwxrwx 1 root root 43 Dec  2 14:01 .database.yml -> /home/ec2-user/sample/shared/config/database.yml

 つまり、シンボリックリンクを使用する際は、リンク先をDockerのボリュームをマウントしなければならないらしい。
 ただ、既にコンテナ内の作業ディレクトリ(/sample)には、プロジェクトのディレクトリがマウントされているので、シンボリックリンクをマウントすることはできない。
 試しにマウントしてみても、

Could not locate Gemfile or .bundle/ directory

 と出てくるので(そりゃアプリのルートを別のディレクトリにマウントしてたらGemfileなくなるわと思いつつ)、cpコマンドを使用して実体ファイルを作成することにした。

config/deploy.rb
# (...something)
set :linked_files, %w[.master-pro.key config/database-pro.yml]
# (...something)
config/deploy/production.rb
task :upload do
  on roles(:app) do

    upload! "config/database.yml", "#{shared_path}/config/database-pro.yml" unless test "[ -f #{shared_path}/config/database-pro.yml"
    upload! "config/master.key", "#{shared_path}/config/master-pro.key" unless test "[ -f #{shared_path}/config/master-pro.key ]"
  end
end

task :deploy => :upload do
  on roles(:app) do
    # (...something)
    execute "cp #{release_path}/config/database-pro.yml #{release_path}/database.yml; rm #{release_path}/config/database-pro.yml"
    execute "cp #{release_path}/config/master-pro.key #{release_path}/master.key; rm #{release_path}/config/master-pro.key"
    # cpコマンド使用後、一応元ファイル(シンボリックリンク)削除
    # (...something)

 (ちなみに、現段階では、unless ... 文は必要ないが、circleci では必要になる。
 また、その中のtest "[ ... ]" の部分は Capistrano 専用の記述方法である。若干シェルスクリプトに似てると思ったのは自分だけ?)

完成形

 最後に、『既に起動中のコンテナ』があった場合は、そのコンテナを停止・削除し 、 新たなコンテナを起動 する一連の処理を$ bundle exec cap production deployコマンド一つで実現するタスクを示して終了したいと思う。

config/deploy/production.rb
set :ip, "xx.xx.xx.xx"
server "#{fetch :ip}",
  user: "ec2-user",
  roles: %w{app},
  ssh_options: {
    keys: %w(/path/to/key),
    auth_methods: %w(publickey)
  }

task :upload do
  shared_path = fetch :shared_path
  on roles(:app) do
    upload! "config/database.yml", "#{shared_path}/config/database-pro.yml" unless test "[ -f #{shared_path}/config/database-pro.yml"
    upload! "config/master.key", "#{shared_path}/config/master-pro.key" unless test "[ -f #{shared_path}/config/master-pro.key ]"
  end
end

task :deploy => :upload do
  release_path = fetch :release_path
  on roles(:app) do
    execute "cp #{release_path}/config/database-pro.yml #{release_path}/databsae.yml; rm #{release_path}/config/database-pro.yml"
    execute "cp #{release_path}/config/master-pro.key #{release_path}/master.key; rm #{release_path}/config/master-pro.key"

    execute "sudo service docker start"

    container = capture "docker container ls -a -q -f name=test-rails-container"
    if container.present?
      execute "docker stop test-rails-container"
      execute "docker rm test-rails-container"
    end
    image = capture "docker image ls -q test-rails-image"
    if image.present?
      execute "docker rmi test-rails-image"
    end

    execute "docker-compose -f #{release_path}/docker-compose_pro.yml up -d"
  end
end

 これで、ローカルから(コンテナ内で) $ bundle exec cap production deploy を実行し、ローカルブラウザでhttp://【IP】:3000にアクセスすると、
スクリーンショット 2020-11-26 11.41.26.png

という画面が出て、きちんと動作しているというのがわかった!(ちなみに、config/environments/production.rb ファイルを少し変更しています。)

 この感じで、web(Nginx)サーバにもon roles(:web) do ... endといった形で定義すれば、デプロイできる。
 (その際は、task :deploy do ... end ではなく、task :someOtherTaskName do ... end というブロックの中で行った方がいいと思われる。(多分)deploy では、Capistranoがデフォルトで用意している :deploy タスクが呼び出されてしまうため。 )

最後に

 シンボリックリンクのところでだいぶ時間を使いました。
 また、本文中にも記載したが、 unless ... 文はここではなくても構わないが、次回のcircleciを使用したデプロイ時に必須になります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0