CircleCI + Capistrano + AWS(EC2) + Railsで自動デプロイしてみた

# はじめに
先日、Capistranoを使って自作のポートフォリオをAWSにデプロイしたので、CircleCIと組み合わせて自動デプロイしてみました。

筆者はプログラミング学習を始めて４ヶ月ぐらいですが、一週間ほどで実装できました。

**ポートフォリオにCircleCI/CDを組み込んでみたい！**という方の参考になれば嬉しいです。

ご指摘等あれば、コメントいただければ幸いです。

# 前提
- Railsアプリ作成済
- CIrcleCIによる自動テストを導入済
- Capistranoを使ってAWSにデプロイ済

CircleCIとCapistranoに関しては別の記事でまとめています。


[CircleCIでSystemSpec(RSpec)とRubocopを走らせる](https://qiita.com/YK0214/items/bbed63ea7ca5367dae2f))
[Capistrano + AWS(EC2) + Rails で簡単デプロイ](https://qiita.com/YK0214/items/355ba23f7bf7758e232d)

# 手順
複数の設定をする必要があるので、順を追ってやっていきましょう。

1. CircleCIにssh秘密鍵を登録して、AWSのEC2インスタンスにアクセスできるようにする
2. CircleCIのコンソール上で環境変数を設定する
3. .circleci/config.ymlを編集して、SSH接続できることを確認する
4. GithubへpushしたときにCapistranoデプロイを走らせる
5. Githubのブランチがmasterの時にだけCapistranoデプロイを走らせるように設定する


# 1.CircleCIにSSH秘密鍵を登録

CIrcleCIのGUI上で使用するプロジェクトを選択した後に"Project settings >> SSH KEYS >> Add SSH key" を選択して`Host Name`と`Private Key`を記入します。

`Host Name`はドメイン、またはIPを記述します。
筆者は独自ドメインを取得していたので`appname.com`を記述しました。

`Private Key`は秘密鍵の中身を記述します、
ただ、ここで注意点が2点があるので説明します。(筆者はここで２〜３日詰まりました...)

## 秘密鍵を登録する時の注意点
### 1. **ローカルからEC2にログインする際に使用する秘密鍵の中身を記述する**

自作アプリを作成してAWSにデプロイまでやっていると、AWSアクセスキーやGithubとの紐付けのための秘密鍵など、複数の秘密鍵があるはずです。
そのため、どの秘密鍵を使用すればいいのかが迷うかもしれません。(筆者は迷いました...)

必要な鍵は**ローカルからEC2にログインする際に使用する秘密鍵**です。

もし、秘密鍵を~/.sshに格納している場合、秘密鍵は以下のコマンドで一覧が確認します。

```console:(local)
[~]$ cd ~/.ssh
[.ssh]$ ls
```

筆者の場合はローカルからEC2にログインする際に以下のコマンドを使用します。

```console:(local)
[~]$ ssh appname_rsa
```

その場合、ターミナル上で以下のコマンドを打ち、秘密鍵の中身をコピーできます。

```console:(local)
[~]$ pbcopy < ~/.ssh/appname_rsa
```
コピーした中身を`Private Key`にペーストしましょう。

そして、記述内容の冒頭が`-----BEGIN RSA PRIVATE KEY-----`であればOK
です。

もし、`-----BEGIN OPENSSH PRIVATE KEY-----`であれば次の注意点に進みましょう。


### 2. **秘密鍵のファイル形式はPEM形式でなければならない**

SSH Keyのファイル形式は`OPENSSH`と`PEM`があり、CircleCIに設定するSSH Keyのファイル形式は`PEM`に指定されています。

ファイル形式の見分け方は秘密鍵の中身の冒頭部分でわかります。
`OPENSSH`の場合：`-----BEGIN OPENSSH PRIVATE KEY-----`
`PEM`の場合：`-----BEGIN RSA PRIVATE KEY-----`

もし、ローカルからEC2にログインする際に使用する秘密鍵が`OPENSSH`形式だった場合は、`PEM`形式の秘密鍵を作成し、EC2にログインできるように設定する必要があります。

筆者は`OPENSSH`形式で作成していたので、次の手順で秘密鍵を作成しなおしました。


### PEM形式の秘密鍵の作成・ログイン設定方法

まずローカルで鍵の生成を行います。

```console:(local)
[~]$ cd .ssh
[.ssh]$ ssh-keygen -m pem
(#公開鍵を作成)
-----------------------------
Enter file in which to save the key ():appname_rsa 
(#ここでファイルの名前を記述して、エンター)
Enter passphrase (empty for no passphrase): 
(#何もせずそのままエンター)
Enter same passphrase again: 
(#何もせずそのままエンター)
-----------------------------

[.ssh]$ ls
#「appname_rsa」と「appname_rsa.pub」が生成されたことを確認
[.ssh]$ cat appname_rsa.pub
(#鍵の中身をターミナル上に出力→ssh-rsa~~~~localまでをコピーしておく)
```

次にサーバー側(EC2)で先ほど作成した公開鍵を設定します。

```console:(server)
[yuki|~]$ mkdir .ssh
[yuki|~]$ chmod 700 .ssh
[yuki|~]$ cd .ssh
[yuki|.ssh]$ vim authorized_keys
(#vimが開く)
-----------------------------
ssh-rsa sdfjerijgviodsjcIKJKJSDFJWIRJGIUVSDJFKCNZKXVNJSKDNVMJKNSFUIEJSDFNCJSKDNVJKDSNVJNVJKDSNVJKNXCMXCNMXNVMDSXCKLMKDLSMVKSDLMVKDSLMVKLCA -------@--------no-MacBook-Air.local
(#先ほどコピーした鍵の中身を貼り付け)
-----------------------------
[yuki|.ssh]$ chmod 600 authorized_keys
[yuki|.ssh]$ exit
[ec2-user|~]$ exit
```

完了したら、ローカルに戻って鍵をどの通信の認証時に使用するか等を設定します。

```console:(local)
[~]$ cd .ssh
[.ssh]$ vim config
(#Vimを起動し、設定ファイルを編集する)

# 以下を追記
Host appname_rsa
  Hostname EC2のElastic IP (#自分の設定に合わせて)
  Port 22
  User yuki (#EC2のユーザー名)
  IdentityFile ~/.ssh/appname_rsa (#秘密鍵の設定)
-----------------------------
```
これで`PEM`形式の秘密鍵を使ったSSH通信が可能になります。
ローカルで下記コマンドを入力し、実際にログインできるか試してみましょう。

```console:(local)
ssh appname_rsa
```
ログインできれば、設定完了です。

ここまでできたら、注意点１を参考にしてCircleCIの`Private Key`に秘密鍵の中身を記述して登録しましょう。


# 2. CircleCIのコンソール上で環境変数を設定する

CircleCIは**Githubのソースコード**をベースにデプロイを行います。
そのため、gitignoreに記述されているようなGithub上にpushされていないファイルは認識できません。

そしてCircleCIではコンソール上で環境変数として設定することで、そういったファイルを管理する機能があるのでそれを利用します。

CircleCIのプロジェクトの設定から`Environment Variables`のページへ行き、`Add Variable`を選択します。

そして、二つの環境変数を設定します。

```
Name:'RAILS_MASTER_KEY' Value: ローカルにある'master.key'の中身を記述。

Name:'PRODUCTION_SSH_KEY'　Value:'~/.ssh/appname_rsa_xxxxxxxxxxxxxxx~'
```

`PRODUCTION_SSH_KEY`のValueの`appname_rsa_`の後ろには、先ほど登録した`SSH Key`のHost Nameの隣に記述されているFingerprintsの：抜きの文字列を記述してください。

次に本番環境でのCapistranoのSSH接続設定を行います。
ここでは、`PRODUCTION_SSH_KEY`を使ってconfig/deploy/production.rbを記述しましょう。

```rb:config/deploy/production.rb
server 'EC2のElastic IPを記述', user: 'yuki', roles: %w[app db web]

# CircleCIのGUIで設定した環境変数を使ってSSH接続
set :ssh_options, {
  keys: [ENV.fetch('PRODUCTION_SSH_KEY').to_s],
  forward_agent: true,
  auth_methods: %w[publickey]
}
```

# 3. SSH通信できることを確認する

`.circleci/config.yml`に以下の記述を追加しましょう。
Fingerprintsには先ほど登録した`SSH Key`のHost Nameの隣に記述されているのでコピペしてください。

```yml:.circleci/config.yml
     - add_ssh_keys:
          fingerprints:
            - "XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX"
```

そしてgithubへpushしてみましょう。

実行できていれば、CircleCI側のコンソールで、`Installing additional ssh keys`という処理に対して

```
Installed key XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
```
と表示されるはずです。

# 4. GithubへpushしたときにCapistranoを走らせる
`.circleci/config.yml`に以下の記述をします。


```yml:.circleci/config.yml
      - add_ssh_keys:
          fingerprints:
            - "XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX"

      - deploy:
          name: Capistrano deploy
          command: bundle exec cap production deploy
```

Githubにpushしてみましょう。
自動デプロイできるはず！...です。

# 5. Githubのブランチがmasterの時にだけCapistranoデプロイを走らせるように設定する
`circleci/config.yml`のworkflowsの最後に`filters`追記します。

```yml:.circleci/config.yml
# build,test,deployを記述。
・
・
・

workflows:
  version: 2
  build_accept_deploy:
    jobs:
      - build
      - test:
          requires:
            - build
      - deploy:
          requires:
            - test
          filters:
            branches:
              only: master
```

これでGithubのブランチがmasterの時にだけCapistranoデプロイが走るようになります！

# まとめ

筆者はSSH認証の部分でかなりつまずきました笑。

同じようにつまづいている方の手助けになれば嬉しいです☺️


最後にCircleCIとCapistranoのソースコードと参考記事を載せておくのでご参考までに。


## ソースコード

```yml:.circleci/config.yml
version: 2.1

orbs:
  ruby: circleci/ruby@1.1.0

jobs:
  build:
    docker:
      - image: circleci/ruby:2.5.1-node-browsers
        environment:
          BUNDLER_VERSION: 2.1.4
    steps:
      - checkout
      - ruby/install-deps

  test:
    parallelism: 3
    docker:
      - image: circleci/ruby:2.5.1-node-browsers
        environment:
          DB_HOST: 127.0.0.1
          RAILS_ENV: test
          BUNDLER_VERSION: 2.1.4
      - image: circleci/mysql:8.0
        command: --default-authentication-plugin=mysql_native_password
        environment:
          MYSQL_ALLOW_EMPTY_PASSWORD: 'true'
          MYSQL_ROOT_HOST: '%'
    steps:
      - checkout
      - ruby/install-deps
      - run: mv config/database.yml.ci config/database.yml 
      - run:
          name: Wait for DB
          command: dockerize -wait tcp://localhost:3306 -timeout 1m
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load
      # Run rspec in parallel
      - ruby/rspec-test
      - ruby/rubocop-check

  deploy:
    docker:
      - image: circleci/ruby:2.5.1-node-browsers
        environment:
          BUNDLER_VERSION: 2.1.4
    steps:
      - checkout
      - ruby/install-deps
      - add_ssh_keys:
          fingerprints: "XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX"
      - deploy:
          name: Capistrano deploy
          command: bundle exec cap production deploy
  

workflows:
  version: 2
  build_accept_deploy:
    jobs:
      - build
      - test:
          requires:
            - build
      - deploy:
          requires:
            - test
          filters:
            branches:
              only: master
```

```rb:config/deploy.rb
# capistranoのバージョン固定
lock '3.14.1'

# デプロイするアプリケーション名
set :application, 'golfour'

# cloneするgitのレポジトリ
set :repo_url, 'git@github.com:xxxxxx/xxxxxx.git'

# deployするブランチ。デフォルトはmasterなのでなくても可。
set :branch, 'master'

# deploy先のディレクトリ。
set :deploy_to, '/var/www/rails/appname'

# secret_base_keyを読み込ませるため追記
set :linked_files, %w[config/master.key]

# シンボリックリンクをはるファイル。
set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/settings.yml', '.env')

# シンボリックリンクをはるフォルダ。
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')

# 保持するバージョンの個数。過去５つまで履歴を保存。
set :keep_releases, 5

# rubyのバージョン
set :rbenv_ruby, '2.5.1'

# 出力するログのレベル。
set :log_level, :debug

namespace :deploy do
  desc 'Restart application'
  task :restart do
    invoke 'unicorn:restart'
  end

  desc 'Create database'
  task :db_create do
    on roles(:db) do |_host|
      with rails_env: fetch(:rails_env) do
        within current_path do
          execute :bundle, :exec, :rake, 'db:create'
        end
      end
    end
  end

  desc 'Run seed'
  task :seed do
    on roles(:app) do
      with rails_env: fetch(:rails_env) do
        within current_path do
          execute :bundle, :exec, :rake, 'db:seed'
        end
      end
    end
  end

  after :publishing, :restart

  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
    end
  end
end
```

```rb:config/deploy/production.rb
server 'EC2のElastic IP', user: 'yuki', roles: %w[app db web]

# CircleCIのGUIで設定した環境変数を使ってSSH接続
set :ssh_options, {
  keys: [ENV.fetch('PRODUCTION_SSH_KEY').to_s],
  forward_agent: true,
  auth_methods: %w[publickey]
}
```


## 参考記事
[【circleCI】rails5.2/Capistrano/CICD環境によるAWSへの自動デプロイ](https://qiita.com/AK4747471/items/882b70f663f4a80ae770)
[CircleCIでデプロイを自動化](https://bbbbruno.qrunch.io/entries/9070Lw9PVmQBJnvP)