[Rails6] CircleCI + CapistranoでIP制限のあるEC2への自動デプロイを実装した

# こういう方の参考になれば幸いです

（滅茶苦茶ハマり続けた結果何とか自動デプロイできた者が書いた記事です。ご了承ください。）

初学者の方で、
- ローカルからCapistranoでEC2にデプロイした
- CircleCIでCI(ビルド、テスト)部分は実装済みで、CD（自動デプロイ）もやろうとしているところ
- EC2のセキュリティグループのインバウンドルールで、sshをローカルのIPのみに指定している（＝任意のIPにしていない）。もしくはそうしようと考えている方

３つ目を一応具体的に説明すると、アプリケーションを配置したEC2インスタンスのセキュリティグループに、インバウンドルールとしてsshに関するルールを追加しているはずです。そのソースに「任意の場所」を選択している場合、"0.0.0.0./0"と"::/0"という２つのインバウンドルールが定められていると思います。
もしくは私のように「マイIP」を選択している場合は、ご自身のパソコンのIPアドレスのみを許可する設定になっています。

前者の場合は、「任意のIPアドレスからのサーバーへのssh接続を許可している状態」
後者は、「自分のパソコンからしかssh接続ができない状態」

両者を比較して、セキュリティ的に安全度が高いのは明らかに後者だと思います。外部のPCなどからssh接続することができません。
任意のIPアドレスを許可しておけば設定面でもかなり負担が減るのですが、エンジニアを目指す上でセキュリティに対するケアも常に意識するべきかなと思ったので、後者を選択しました。

そして、今回自動デプロイを行うCircleCIは、仮想コンテナを構築しそのコンテナの内部でデプロイに関わる様々な処理を行います。そして**そのコンテナにも個別のIPアドレスが存在します（しかもコンテナが立ち上がる毎にアドレスは変わります）。**
**デプロイをする為にはCircleCIがEC2インスタンスにssh接続する必要があり**、もし自分のパソコンのIPアドレスしかssh接続を許可していない場合は、CircleCIのコンテナがEC2に接続することができないということです。

じゃあ許可しよう！と思っても毎回実行する毎にIPアドレスが変わるので、「どうやりゃいいんだよ！」ってなったわけです。

今回はこの問題の解決策も含めて自動デプロイまでの手順を書いていきます。
ただし、特にこの記事がすごく参考になった！という部分に関しては記事のリンクをそのまま貼り付けていますので、そちらを参照してください。

一応ですが私はMacOS Catalina10.15.7を使用しています

# おおまかな流れ
1. とりあえずこの記事通りにやってみよう
2. IAMユーザー作成と、作成したユーザーをCircleCIに環境変数で設定
3. CircleCI用のセキュリティグループ作成、アタッチ
4. .circleci/config.ymlの編集。シェルスクリプトを作成


## 1. とりあえずこの記事通りにやってみよう
場合によってはここで完結します。僕の記事は不要です。どうもありがとうございました
以下の方の記事が非常にわかり易く丁寧でしたので、是非こちらを読んでトライしてみてください。

[CircleCI + Capistrano + AWS(EC2) + Railsで自動デプロイしてみた](https://qiita.com/YK0214/items/98bbdf7ce25c557c77a3)

場合によって完結するとはどういうことかというと、最初に申しましたように、**EC2のセキュリティグループで「任意のIPアドレスからのssh接続を許可」している場合はこの記事通り実装すればできるはずだから**です。多分
そうではなく私のように、「ローカルホストのIPアドレスだけを許可するルール」にするなら、更に追加の設定が必要になりますので、ここから先へと進みます。


## 2.　CircleCI用IAMユーザー作成と、作成したユーザーをCircleCIに環境変数で設定
セキュリティグループの扱いに関して、以下の記事の解説が非常にわかりやすかったです。滅茶苦茶感謝してます。
**この記事の「CircleCIの設定画面でアクセスキーを環境変数に入れておく」のところまでは同じように行いました。**

**ただその次の「CircleCIのconfig.ymlを書いてみる」に関しては、"sudo pip install awscli"のところで「pipなんてコマンド知らないよ」と怒られた為、別の方法で対応しました。**
pipはpythonのパッケージ管理システムのようで、私はpythonのイメージなども取得していなかったためそのようなエラーが出てしまったようです。（別の方法で対処しているので、それに関しては後述します）
ただし最後の方にある図を見ると、これからやろうとしていることのイメージが掴めると思うので見ることをおすすめします。

[CircleCI2.0からEC2にアクセスするときだけ特定のIPを許可したい](https://qiita.com/rintaro-ishikawa/items/02e6a63dbc90ea67a991)

## 3. CircleCI用のセキュリティグループ作成、アタッチ
既にEC2にアタッチしているセキュリティグループを使用しても良かったのですが、管理の面でわかりやすいかなと思いCircleCI専用のセキュリティグループを新たに作成します。

- セキュリティグループ名：任意
- インバウンド、アウトバウンドルールはデフォルトのままでOK
- 作成したら、そのセキュリティグループをEC2にアタッチ

ここでは何もルールを設定しませんが、***CircleCIでデプロイをする時に先程作成したIAMユーザーの権限を用いて、デプロイ時のコンテナのIPアドレスからのssh接続を許可するルールを追加し(これでCircleCIがEC2にsshできるようになる)、
デプロイが完了したら追加したそのルールを消す、という流れになります***

## 4. .circleci/config.ymlの編集。シェルスクリプトを作成

一応全部のせておきます

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

orbs:
  ruby: circleci/ruby@1.1.0
  node: circleci/node@2
  aws-cli: circleci/aws-cli@2.0.0  # これが必要（詳しくは後述）

jobs:
  build:
    docker:
      - image: circleci/ruby:2.7.1-node-browsers
    environment:
      BUNDLER_VERSION: 2.1.4 
    steps:
      - checkout
      - ruby/install-deps
      # Store bundle cache
      - node/install-packages:
          pkg-manager: yarn
          cache-key: 'yarn.lock'

  test:
    parallelism: 3
    docker:
      - image: circleci/ruby:2.7.1-node-browsers
        environment:
          BUNDLER_VERSION: 2.1.4
          DB_HOST: 127.0.0.1
          RAILS_ENV: test
      - image: circleci/mariadb:10.5
        environment:
          MYSQL_ROOT_HOST: '%'
          MYSQL_ALLOW_EMPTY_PASSWORD: true
      - image: selenium/standalone-chrome-debug
    steps:
      - checkout
      - ruby/install-deps
      - node/install-packages:
          pkg-manager: yarn
          cache-key: "yarn.lock"
      - 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 rails db:create
      - run: bundle exec rails db:schema:load
      - run: bundle exec rails db:seed
      # Run rspec in parallel
      - ruby/rspec-test
      - ruby/rubocop-check
  deploy:
    docker:
      - image: circleci/ruby:2.7.1-node-browsers
    environment:
      BUNDLER_VERSION: 2.1.4 
    steps:
      - checkout
      - ruby/install-deps
      - aws-cli/setup     # このコマンドでAWS-CLIのインストールができる
      - add_ssh_keys:
          fingerprints: "xx:xx:xx:xx:xx:xx:xx"
      - run:   # ./deploy.shにセキュリティグループのルール変更コマンド、Capistranoデプロイ実行コマンドを記述する
          name: deploy
          command: |
            ./deploy.sh

workflows:
  version: 2
  build_and_test_then_deploy:
    jobs:
      - build
      - test:
          requires:
            - build
      - deploy:
          requires:
            - test
          filters:  #mainブランチの場合のみデプロイのジョブが実行される
            branches:
              only: main
```


```deploy.sh
#!/bin/sh
set -ex

SECURITY_GROUP="作成したセキュリティグループのID"
IP=`curl -s ifconfig.me`  # 現在起動しているコンテナのIPアドレスを代入

# コンテナのIPアドレスからのsshを許可
aws ec2 authorize-security-group-ingress --group-id ${SECURITY_GROUP} --protocol tcp --port 22 --cidr ${IP}/32
bundle exec cap production deploy  # ご存知デプロイコマンド
# 許可した設定をrevoke(取り消す)
aws ec2 revoke-security-group-ingress --group-id ${SECURITY_GROUP} --protocol tcp --port 22 --cidr ${IP}/32

```


とりあえずこんな感じで行いました。
ポイントとしては、セキュリティグループの設定を変えるために"aws"というコマンドを利用するのですが、このawsコマンドを使用するためには"aws-cli"というものが必要です
一番初めに紹介した記事ではpythonのパッケージ管理システムであるpipを使用して、"sudo pip install awscli"とすることでインストールしていました。
わたしの場合だとcircleCIの公式からawscliのOrbsが出ておりまして、それを使用したという形になります。（実際このOrbsの内部でpipを用いている）

[circleci/aws-cli@2.0.0](https://circleci.com/developer/orbs/orb/circleci/aws-cli#commands-setup)

### aws-cliの為の環境変数を設定
Orbsの使用に関して以下の記事が参考になりました。
[CircleCI Orbが最高だった件](https://buildersbox.corp-sansan.com/entry/2021/01/07/110000)

ドキュメントを見るに、CircleCIの環境変数に
- AWS_ACCESS_KEY_ID
- AWS_DEFAULT_REGION
- AWS_SECRET_ACCESS_KEY

この３つの設定が必要のようです。
ACCESS_KEY_IDとSECRET_ACCESS_KEYに関しては既にIAMユーザー作成時に登録済みですね。
残るAWS_DEFAULT_REGIONを登録しましょう。
私の場合はvalueに"ap-northeast-1"を入れました。

**※注意※**
私は最初"ap-noatheast-1a"とリージョンだけでなくAZまで指定して入れており、"Could not connect to the endpoint URL: ~~"というエラーに半日以上費やしました。習ったこともないのに部屋に干してた洗濯物にハイキックし続けました。


またシェルスクリプトの作成においては、以下の記事をじっくり見比べたりしながら実装しました。デプロイまでの全体の流れについても非常に参考になります。
[CircleCIからCapistranoを利用してAWS（EC2）にデプロイする](https://qiita.com/ryshinoz/items/85eecd2b860227a45ccd)
[CircleCI から deploy させる話](https://mgi.hatenablog.com/entry/2014/10/30/085403)
[CircleCIでIP制限のあるEC2インスタンスに自動デプロイできるようにする](http://pixelbeat.jp/auto-deploy-to-ec2-instance-with-ip-restriction-using-circleci/)

### 要チェック

deploy.shという名前でシェルスクリプトを作成しましたが、権限の問題でこのままではpermission deniedされてしまいます。
実行権限を付与します。

```
$ chmod +x deploy.sh

# 下記のようになっていればOKです
$ ls -l deploy.sh
-rwxr-xr-x ~~~~~~~~~~~~
```


ここまででおそらく自動デプロイできるようになっているはず、、です。
沢山時間かけてあれこれ試行錯誤しながら実装したので、もしかしたら何か漏れがあるかもしれません。ごめんなさい！
私自身実務未経験の駆け出しなので、おかしな部分がもあるかもしれませんが、少しでも初学者の方の助けになれればと思います。
とにかく参考記事等を見比べたりしてサーチ&トライ&エラー&ハイキックしていればできるはずです！！！