複数環境に対してCircleCI Workflowで同じコードをデプロイする（ElasticBeanstalk編）

### 概要
CircleCI Workflow ではテストを通した後に待たせておいて、任意のタイミングでワンクリックでデプロイするという方法をとることができます。
特に本番デプロイは手作業が多いと心理的負荷が高くて億劫になるので手順をできるだけ自動化したいです。

この記事では特に、AWS ElasticBeanstalk の複数の環境に同じバージョンのアプリケーションをデプロイする方法について書きます。ElasticBeanstalk以外でもworkflowの流れと、ジョブ間での情報の受け渡しの方法は同じなので応用できるかと思います。

具体的用途としては、以下のものを想定しています

* Webアプリケーションとジョブ実行するWorker環境の両方に同じコードを勝手にデプロイして欲しい
* 開発環境と本番環境の両方にワンクリックで同じコードをデプロイしたい

### 完成イメージ
こちらはCircleCI Workflow 実行時の画面イメージです。

dev の二つの環境にデプロイする場合には以下のような見た目になります。
![貼り付けた画像_2019_11_22_12_26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144064/c115a12e-1ea1-769a-c1c2-e4fcaf50910b.png)

テストが通った状態で止まっており、 require-testsのところがクリック可能になって、クリックするとデプロイが始まります（テスト終わった直後は一回リロードしないとクリック可能にならないのでそこだけ注意）


さらに本番へのデプロイを足した場合には以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144064/d9f02a3f-39f5-ce3f-7eba-13e626d16442.png)

新たに require-dev-deploy が足されており、それをクリックすることで本番デプロイに進むことができます。

### 設定の記述方法
`.circleci/config.yml` に CircleCI Workflow の設定を書くことができます。
前提となるセットアップについては省略し、公式ドキュメントなどにお任せします。

ファイルの全体像がわかるように全部が入ったものを載せます。`※`マークをつけたところについて個別の項目について解説をページ下部でつけました。

```.circleci/config.yml
version: 2.1
# (※１) executors と commands について を参照のこと
executors:
  setup-eb-environment:
    docker:
      - image: circleci/python:3.6.4
    working_directory: ~/repo
commands:
  setup-eb-command:
    steps:
      - checkout
      # 今回の本流とは関係ないので一部省略。awscli入れて access key 設定などをします
      - run:
          name: Install awscli

jobs:
  test-js:
    # 今回は略
  test-php:
    # 今回は略

  deploy-to-dev:
    executor: setup-eb-environment
    steps:
      # 上で定義したコマンドを使っています
      - setup-eb-command
      - run:
          name: Deploying to dev webapp
          # ここでsedなどで出力整形するのもありではあるが、eb deploy コマンドの終了ステータスが破棄されてしまって異常時に正しく止まらないことが考えられたためファイルリダイレクトだけにしている
          command: eb deploy develop-webapp > /tmp/eb_deploy_output
      - run:
          name: Deploying to dev worker
          # こういう感じのアウトプットを期待してダブルクオートの中を取り出しています。 => Creating application version archive "app-2019-11-18-2-57-gd83f8-191122_104221".
          command: |
            cat /tmp/eb_deploy_output | grep "version archive" | sed -E 's/.*\"(.*)\".*/\1/' > /tmp/app_version_label
            eb deploy develop-worker --label `cat /tmp/app_version_label`
      - run:
          name: check app version
          command: cat /tmp/app_version_label
      # （※２） job間での情報受け渡し方法について を参照のこと 
      - persist_to_workspace:
          root: /tmp/
          paths:
            - app_version_label

  deploy-to-production:
    executor: setup-eb-environment
    steps:
      - setup-eb-command
      - checkout
      # workspaceに保存したものを取り出せます
      - attach_workspace:
          at: /tmp/
      - run:
          name: check app version
          command: cat /tmp/app_version_label
      - run:
          name: Deploying to production worker
          # (※3) ebコマンドの `--label` でのバージョン指定について　を参照のこと
          command: eb deploy production-worker --label `cat /tmp/app_version_label`
      - run:
          name: Deploying to production webapp
          command: eb deploy production-webapp --label `cat /tmp/app_version_label`

workflows:
  version: 2.1
  build-and-test-approval-deploy:
    jobs:
      - test-js:
          # (※4) filters の動きについて　を参照のこと
          filters:
            tags:
              only: /.*/
      - test-php:
          requires:
            - test-js
          filters:
            tags:
              only: /.*/
      - require-tests:
          type: approval
          requires:
            - test-php
          filters:
            tags:
              only: /.*/
      - deploy-to-dev:
          requires:
            - require-tests
          filters:
            tags:
              only: /.*/
      - require-dev-deploy:
          type: approval
          requires:
            - deploy-to-dev
          # リリースタグ 2019-11-01 や 2019-11-01-fix などの形に対応。tagがある時にだけリリース可能。
          filters:
            tags:
              only: /^(\d){4}-(\d){2}-(\d){2}.*/
            branches:
              ignore: /.*/
      - deploy-to-production:
          requires:
            - require-dev-deploy
          filters:
            tags:
              only: /^(\d){4}-(\d){2}-(\d){2}.*/
            branches:
              ignore: /.*/
```


### ※1 executors と commands について
https://circleci.com/docs/ja/2.0/configuration-reference/#executorsversion21-%E3%81%8C%E5%BF%85%E9%A0%88

* executors は `Executors は、ジョブステップの実行環境を定義`
* commands は `ジョブ内で実行するステップシーケンスをマップとして定義`

要するに、実行環境とあとで使用するコマンドを先に定義しておいて、使いまわすことができます。

### ※2 job間での情報受け渡し方法について

永続化ではなくてジョブ間でのファイル受け渡しに persist_to_workspace（ファイルをworkspaceに30日保存） と attach_workspace（指定のパスに対してworkspaceにあったらアタッチする） のワンセットを使えることがわかった。

参考ドキュメント：  
https://circleci.com/blog/build-cicd-piplines-using-docker/  
https://circleci.com/docs/ja/2.0/configuration-reference/#persist_to_workspace

`$BASH_ENV` に保存すれば良いという情報もあったが、`$BASH_ENV`に保存されるファイルパスがジョブによって変わってしまって使えなかった。

### ※3 ebコマンドの `--label` でのバージョン指定について

`--label` を**指定せずに** eb deploy を複数回実行することもできるかと思いますが、それだと環境が増えるとElasticbeanstalkの管理コンソール上でみれるアプリケーションバージョンが複数発行されてしまい、邪魔になってしまいます。
`--label` で以前のバージョンを指定すればその問題が起こりません。1回目の eb deploy のアウトプットを取得して整形し、2回目のeb deployコマンドの`--label`オプションの引数に渡しています。

### ※4 filters の動きについて
参考: https://qiita.com/sanemat/items/4ddbd4016a5269265166

* branch はデフォルトで全て実行
* tag はデフォルトで全て無視  

なのでtagのfilterを明示的に指定していない場合にgithubでタグが追加された時は最初のジョブからなくなるのでワークフローのリストにも表示されない。
そういうわけで、タグがついているタスクに対してジョブを実行したければ全てのジョブに以下のfilterを追加してあげる必要があった。

```
 filters:
   tags:
     only: /.*/
```

### おまけ　CircleCI config のシンタックスチェック方法
circleci コマンドを用いると、いちいちデプロイしなくてもシンタックスチェックができて良いです。こういう仕事は大体時間がかかるので、待ち時間を減らしましょう。

```
$ circleci config validate -c .circleci/config.yml
Config file at .circleci/config.yml is valid.
```

このコマンドのインストール方法などは公式ドキュメントを参照のこと https://circleci.com/docs/ja/2.0/local-cli/

補足： mac PC の環境においてはbrewで入れると circleci update ができなかったのでbrew以外の方法で入れたほうが良さそう。