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

# 本シリーズ集
|  | タイトル |
|:-:|:-:|
| 0 | [目標・やりたいこと](https://qiita.com/drafts/c59c0df9eaf5e6b10111/edit) |
| 1 | [AWS編](https://qiita.com/Dadadada-TT/items/86e0a578f934e6578c04)|
| 2 | [rails開発環境構築編](https://qiita.com/Dadadada-TT/items/f4ce8d997cc0bb3e0aca)|
| 3 | [Nginx・MySQL編](https://qiita.com/Dadadada-TT/items/4a7edd61a5d7c75f772d) |
| 4 | ___Capistrano編___ |
| 5 | [CircleCI編](https://qiita.com/Dadadada-TT/items/a9530630782c6f4c7cbc) |
| 6 | [総集編](https://qiita.com/Dadadada-TT/items/3d1fa7854d562ae1db1b) |


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

　本記事は、Capistranoの__概要__、及び__実装__しか示していないので、Capistranoについて深く知りたい方は、[Capistrano3の動作まとめ。](https://qiita.com/syou007/items/9b8a2f4d1ff099c64fc5) や [公式サイト](https://capistranorb.com/)・[入門 Capistrano 3 ~ 全ての手作業を生まれる前に消し去りたい]
(https://labs.gree.jp/blog/2013/12/10084/) 等々を参考にしてください。


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

``` ruby: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を編集
　一度ファイルの中身を全消去して、下記のように編集する

```ruby: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 ファイル

```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の公式サイト（各設定項目について）](https://capistranorb.com/documentation/getting-started/configuration/)
・[Capistranoがデフォルトで用意している変数一覧](https://blog.44uk.net/2012/04/06/capistrano-variables/)



####最後に、config/prodution.rb

```.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認証を行う](https://zenn.dev/naoki_mochizuki/articles/ce381be617cd312ffe7f)

　サーバのロールについては、この記事に書いてあるのがわかりやすかったです。
・（再掲）[入門 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`を使用したデプロイ方法のみ__ が実施される。

## 作業中に発生したエラー
<details><summary>結局EC2インスタンスにgitがインストールされてないが故のエラーで、それについてデバッグ方法から解決に至るまでの道のりを長々と書いているので、興味がある方のみ読んでください。（前にEC2インスタンスにgitをインストールさせておいたのはこのため）</summary>
　以下のコマンドで、設定したものがきちんと動作するかをチェックしてくれる（実際には動作しない）。<div>

```
# cap production deploy --dry-run --trace
```
</div>
（__--trace__オプションは、タスクの実行を追ってくれる。）
　そこでは上手くいったのに、本番（下記のコマンド）ではなぜか上手くいかなかった。
<div>

```
# cap pruduction deploy --trace
```

</div>
　__ログに出力__ or __log/capistrano.log__ ファイルに一連のログが出力されるので、エラーが発生した際は、そこからエラー内容が読み取れ、その内容は以下のものだった。
<div>

```
 DEBUG [2d37ddb5] 	/usr/bin/env:
 DEBUG [2d37ddb5] 	git
 DEBUG [2d37ddb5] 	: No such file or directory
```

</div>
　これは、Capistranoでgitを使用する際は、EC2インスタンスにあらかじめgitを入れておかなきゃダメみたい...。
　これもあり、[AWSの構築](https://qiita.com/Dadadada-TT/items/86e0a578f934e6578c04)編で、`git`をあらかじめインストールさせておいた。
　あとは、
　　・EC2上でSSHキーを生成する
　　　 　　or 
　　・いつも自分が使用しているSSHキーを使用する
　でgithubにアクセスできるようにする。
</details>


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

# サーバのディレクトリ構成
　解決後、もう一度 `$ 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の公式サイト（各設定項目について）](https://capistranorb.com/documentation/getting-started/configuration/)
　を参照しよう。


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

　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 ...`の部分）二つになる。）

```.rb: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に怒られる。
　<details><summary>__タスクの実行順序の確認方法__</summary>
　`$cap production deploy --dry-run --trace`で実際にデプロイすることなく、デプロイ処理を疑似的に行い、本番環境を汚さずに実行順序を確認することができる。
<div>

```
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
```

</div>
</details>

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

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

<details><summary>また、新たに__Dockerfile_pro__と__docker-compose_pro.yml__ファイルを追加。</summary>
　__Dockerfile_pro__は、元々のDockerfileから、`COPY 鍵PATH /root/.ssh/ `の部分を削除し、`RUN bundle install` に `--without test development`を足しただけ。
<div>

```Dockerfile: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
```
</div>
　__docker-compose_pro.yml__ は、__docker-compose.yml__と大きく異なっている。
<div>

```.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を取得したいならボリュームをマウントするべき？
```

</div>
</details>


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

####エラーその１
　__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`コマンドを使用して実体ファイルを作成することにした。

```.rb:config/deploy.rb
# (...something)
set :linked_files, %w[.master-pro.key config/database-pro.yml]
# (...something)
```

```.rb: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`コマンド一つで実現するタスクを示して終了したいと思う。

```.rb: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/557656/80a84e45-5510-fc46-6d83-fad2cbf0fedb.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を使用したデプロイ時に必須になります。