本シリーズ集
タイトル | |
---|---|
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 ~ 全ての手作業を生まれる前に消し去りたい]
(https://labs.gree.jp/blog/2013/12/10084/) 等々を参考にしてください。
#Capistrano使い方
##Capistranoのインストール
Capistranoをbundlerを使用してインストールしていく。
Gemfileの開発環境の部分(本番環境では使用しないであろうため)に、Capistranoをインストールする旨の記述する
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を編集
一度ファイルの中身を全消去して、下記のように編集する
require "capistrano/setup"
require "capistrano/deploy"
# 以下の2行を書かないで実行すると、
# 『現在はSCM(Source Code Management)として、デフォルトでgitを使用していますが、将来バージョンアップした際に変更となる可能性があるので、なるべく書き加えてください。』
# といった旨のログが表示されるので、追記しておく
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git
####次は 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
# 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 ~ 全ての手作業を生まれる前に消し去りたい]
(https://labs.gree.jp/blog/2013/12/10084/)
要は、それぞれのサーバ上で何か作業をしたい場合は、別個ロールを指定することで作業ができるというもの。
これで、
$ 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
# cap pruduction deploy --trace
DEBUG [2d37ddb5] /usr/bin/env:
DEBUG [2d37ddb5] git
DEBUG [2d37ddb5] : No such file or directory
EC2におけるgitのssh接続方法
自分のローカルのPCにあるSSHキーでは、パスワードを求められるので、新しく生成した。 EC2インスタンス上で、[sshキー(秘密鍵・公開鍵)の作成と認証 流れ](https://qiita.com/soma_sekimoto/items/35845495bc565c38ae9d) に則って、 `/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 ...
の部分)二つになる。)
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の大まかな(本当に大まかです。)実行順序として、
- task :upload
- シンボリックリンクを貼る
- 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ファイルにも少し手を加える。
...
# デプロイしたcurrentディレクトリ内に、shared/config/* ファイルの内容をシンボリックリンクする。
set :linked_files, %w[ config/database.yml config/master.key ]
...
また、新たに__Dockerfile_pro__と__docker-compose_pro.yml__ファイルを追加。
__Dockerfile_pro__は、元々のDockerfileから、`COPY 鍵PATH /root/.ssh/ `の部分を削除し、`RUN bundle install` に `--without test development`を足しただけ。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
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
コマンドを使用して実体ファイルを作成することにした。
# (...something)
set :linked_files, %w[.master-pro.key config/database-pro.yml]
# (...something)
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
コマンド一つで実現するタスクを示して終了したいと思う。
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
にアクセスすると、
という画面が出て、きちんと動作しているというのがわかった!(ちなみに、config/environments/production.rb ファイルを少し変更しています。)
この感じで、web(Nginx)サーバにもon roles(:web) do ... end
といった形で定義すれば、デプロイできる。
(その際は、task :deploy do ... end
ではなく、task :someOtherTaskName do ... end
というブロックの中で行った方がいいと思われる。(多分)deploy では、Capistranoがデフォルトで用意している :deploy タスクが呼び出されてしまうため。 )
最後に
シンボリックリンクのところでだいぶ時間を使いました。
また、本文中にも記載したが、 unless ...
文はここではなくても構わないが、次回のcircleciを使用したデプロイ時に必須になります。