【circleCI】rails5.2/Capistrano/CICD環境によるAWSへの自動デプロイ 

## 概要
railsでアプリ作って、capistranoでAWSに自動デプロイして、circleCIでgithubへのpushごとにテストコードを走らせるようにしました。
ここまできたらやるっきゃないっしょ！ってことでcircleCIによるCD環境も構築してみました。


## 前回記事

この記事の続きです。事前に読んどいてね。
[RailsにcircleCIを構築し、githubと連携してrubocopとrspecテストを走らせる](https://qiita.com/AK4747471/items/b2161784065f21cd1645)

## 前提条件
・Rails5.2
・capistranoを使った自動デプロイを構築できている
・circleCIとgithubを連携させて自動テストまでは実装できている

circleCIのセッティングについては他フレームワークでも共通概念として使えたりしますが、Rails5.1(だったかな？)以降ではmaster.keyを使ったcredentials系の概念が絡んできたりするので一応Railsを前提条件に加えときました。

## 参考記事
[CapistranoデプロイをCircleCIでやってみた](https://qiita.com/maecho/items/f0445672c1c536a36e4c)
[CircleCI に SSH 鍵を登録する](https://circleci.com/docs/ja/2.0/add-ssh-key/#%E8%BF%BD%E5%8A%A0%E6%89%8B%E9%A0%86)
[【CircleCI】Capistranoでデプロイする設定](http://thoames.hatenadiary.jp/entry/2018/04/11/004637)

## 手順
ざっくり手順としてはこんな感じ。
まとめていっぺんに設定すると問題の切り分けができずに地獄を見るのでこんな感じで進めました。

・circleCIにssh秘密鍵を登録して、AWSのEC2インスタンスにアクセスできるようにする
・.circleci/config.ymlを編集して、ssh接続できることを確認する
・githubへpushしたときにcapistranoデプロイを走らせる
・githubのブランチがmasterの時にだけcapistranoデプロイを走らせるように条件文を加える

## circleCIでssh秘密鍵を登録する
まずcircleCIのsetting⇨projectsから自動デプロイの設定をしたいリポジトリで歯車マークをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434850/2ebf2c43-2ecc-f5ef-a4d8-aa2e000d4d4b.png)

次の画面に遷移するので、左側のメニューから「SSH permission」を選んでAdd SSH Keyをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434850/2e5322be-d1fe-db79-3477-3fcc91623195.png)

HostnameとPrivateKeyを入力することになります。
次はPrivateKeyの取得です。

## PrivateKeyの取得
もしかしたら僕は一番ここで迷ったかもしれません。
具体的になんのSSHキーを登録すればいいのか、どこにも書いてありませんでした。
多かったのではデプロイの時に使ったSSH Keyだよ！っていうやつ。
AWSアクセスキーか？本番環境でgithubと紐づけるために取得したやつか？どれだよってなりました・・

結論から言うと、EC2インスタンス作成時に取得したpemファイルが必要になります。
もしAWSでデプロイしているのであれば、EC2インスタンスを使用しているかと思います。
そのときにKeyPairの作成の際に取得したやつです。
そいつからPrivateKeyを取得することになります。
(デプロイしたいプロジェクトで使ってるEC2インスタンスに紐づいてるpemファイルを使ってね！)

僕の場合、このプロジェクト用のpemファイルはローカルの~/.ssh内部に格納していました。
PrivateKeyをコピーするコマンドは、ターミナルで下記を実行します。

```bash:terminal
pbcopy < ~/.ssh/hogehoge.pem
```
これをしてから貼り付けてみると、-----BEGIN RSA PRIVATE KEY-----と-----END RSA PRIVATE KEY-----に囲まれためちゃくちゃ長い暗号が貼り付けられるはずです。

これをcircleCIのページのPrivate Keyに貼り付けます。

Hostnameはドメイン名かIPを指定するそうです。
僕の場合は独自ドメインを取得しているのでhogehoge.comと記入しています。

成功するとcircleCIのsetting >> user >> projectname >> SSH permissionのページにFingerprintというカラムができたと思います。
ここに書いてある値をあとで使うことになります。

## SSH認証がうまくいくか試してみよう

.circleci/config.ymlに以下の記述を追加してください。
fingerprintsには先ほどのページの値をいれましょう。

そして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と表示されているはずです。

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

## githubへpushしたときにcapistranoデプロイを走らせる

いよいよデプロイしてみます。
まずconfig.ymlに下記記述を追記

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

これでpushしてみましょう！
ちゃんとCapistranoは動こうとしてくれたでしょうか？
とりあえず動こうとしてくれれば成功です。

Rails5.2であれば、おそらくmaster.keyがないんですけどどうするんすかエラーが出るはずです。

## なぜエラーが出るのか
master.keyがgithubリポジトリに存在していないからです。

おそらく、capistranoによるデプロイをセッティングしたとき、master.keyを本番環境にuploadするような記述をしたのではないでしょうか。
ですが、circleCIはgithubのソースコードベースにデプロイを行います。
しかしmaster.keyは秘匿しなければならないためgitignoreに記述している・・。
なのでcircleCI側ではmaster.keyを認知できずにどうするんすか？わかんないっすって言ってきます。

## master.key対策をしよう

まず、僕の場合はcapistranoによるデプロイの際にmaster.keyを本番環境にアップロードする記述をしてました。
それをコメントアウトします。

```config/deploy.rb
(省略)

namespace :deploy do
  task :restart do
    invoke 'unicorn:restart'
  end

  desc 'upload master.key'
  task :upload do
    on roles(:app) do |_host|
      execute "mkdir -p #{shared_path}/config" if test "[ ! -d #{shared_path}/config ]"
      # upload!('config/master.key', "#{shared_path}/config/master.key")
    end
  end
  before :starting, 'deploy:upload'
  after :finishing, 'deploy:cleanup'
end
```

次にcircleCI側に環境変数を設定してあげましょう。
かなり驚いたんですが、circleCIではデプロイ時に必要になる環境変数をGUI上から設定して、config.ymlから変数を呼び出してあげれば機能するという超便利機能があるのです。

そしてrailsのmaster.key用の環境変数まで用意されているのです。作った人天才かと・・・。
[定義済み環境変数](https://circleci.com/docs/ja/2.0/env-vars/#%E5%AE%9A%E7%BE%A9%E6%B8%88%E3%81%BF%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0)もあるみたいなので軽く目を通しておきましょう

では、master.keyの設定をしていきます。

settings >> user >> project >> Environment Variablesを開きます。
ここでAdd Variableボタンを押してnameとvalueを埋めていきます。
nameはRAILS_MASTER_KEYとしてください。この名前で機能してくれます。
valueはmaster.keyをテキストエディタで開くと確認できると思います。そいつを貼り付けてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434850/dbacdf21-b3e4-4a48-ee42-2fa02ff4c6b7.png)

では、もう一度githubにpushしてみてください。
僕と似た感じでcapistranoによるデプロイを設定しているのであれば、うまくいくはずです！

## githubのブランチがmasterの時にだけcapistranoデプロイを走らせるように条件文を加える
最後ですね。ここまでこればあとはもう簡単です。

config.ymlの記述を追加してください。

```yml: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: |
            if [ "${CIRCLE_BRANCH}" != "master" ]; then
              exit 0
            fi
            bundle exec cap production deploy
```

これだけでブランチをきっている場合はデプロイが走らず、masterへのマージなどが関わってきた場合はデプロイが走るようになります

## 最終系のコード
参考までに僕のコードを載せておきます。

```yml:.circleci/config.yml
version: 2
jobs:
  build:
    docker:
      - image: circleci/ruby:2.5.1-node-browsers
      - image: circleci/mysql:5.6

    environment:
      - MYSQL_ALLOW_EMPTY_PASSWORD: 'true'
      - MYSQL_ROOT_HOST: '%'
      - RAILS_ENV: 'test'
      - BUNDLER_VERSION: 2.0.2

    working_directory: ~/repo
    steps:
      - checkout
      # Download and cache dependencies
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "Gemfile.lock" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-
            
      - run:
          name: install dependencies
          command: |
            gem install bundler -v 2.0.2
            bundle install --jobs=4 --retry=3 --path vendor/bundle

      - save_cache:
          paths:
            - ./vendor/bundle
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}

      - run: mv config/database.yml.ci config/database.yml 

      # Database setup
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load

      - run:
          name: Rubocop
          command: bundle exec rubocop


      # run tests!
      - run:
          name: run tests
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
              circleci tests split --split-by=timings)"

            bundle exec rspec \
              --format progress \
              --format RspecJunitFormatter \
              --out /tmp/test-results/rspec.xml \
              --format progress \
              $TEST_FILES

      # collect reports
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results

      - 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: |
            if [ "${CIRCLE_BRANCH}" != "master" ]; then
              exit 0
            fi
            bundle exec cap production deploy
```

```ruby:config/deploy.rb
# config valid only for current version of Capistrano
# capistranoのバージョンを記載。固定のバージョンを利用し続け、バージョン変更によるトラブルを防止する
lock '3.11.2'

# Capistranoのログの表示に利用する
set :application, 'hogehoge'

# どのリポジトリからアプリをpullするかを指定する
set :repo_url, 'git@github.com:piyopiyo/hogehoge.git'

# バージョンが変わっても共通で参照するディレクトリを指定
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system', 'public/uploads')

set :rbenv_type, :user
set :rbenv_ruby, '2.5.1'

# どの公開鍵を利用してデプロイするか
set :ssh_options, auth_methods: ['publickey'],
                  keys: ['~/.ssh/hogehoge.pem']

# プロセス番号を記載したファイルの場所
set :unicorn_pid, -> { "#{shared_path}/tmp/pids/unicorn.pid" }

# Unicornの設定ファイルの場所
set :unicorn_config_path, -> { "#{current_path}/config/unicorn.rb" }
set :keep_releases, 5

# master.key用のシンボリックリンクを追加
# set :linked_files, %w[config/master.key]

# デプロイ処理が終わった後、Unicornを再起動するための記述
after 'deploy:publishing', 'deploy:restart'
namespace :deploy do
  task :restart do
    invoke 'unicorn:restart'
  end

  desc 'upload master.key'
  task :upload do
    on roles(:app) do |_host|
      execute "mkdir -p #{shared_path}/config" if test "[ ! -d #{shared_path}/config ]"
      # upload!('config/master.key', "#{shared_path}/config/master.key")
    end
  end
  before :starting, 'deploy:upload'
  after :finishing, 'deploy:cleanup'
end

```

## workflow
本当はworkflowというものがあって、テストの後にデプロイを走らせる、とか２つの処理を平行に走らせるとかできるみたいです。
で、色々試したんですが僕にはできなかったです・・・・。
誰か教えていただけると本当に助かります

[公式のcapistranoの記述](https://circleci.com/docs/ja/2.0/deployment-integrations/#capistrano)をみてみたし参考に動かしてみたんですが・・・。できない。
ていうか僕レベルでもworkflowsの記述がおかしいように見える。

自分で色々構築してみたんですが、executorと言うものを指定する必要があり、machineもしくはmacosというexecutorを指定すると有料？っぽいのです・・。