Node.js+mongoDBのDockerアプリをCircleCI+AWS Elastic BeanstalkでCI/CDできるようにする

# この記事について
「ローカル環境を汚さずにいろんなツールを入れて試して勉強・アプリを開発してみたい！」「本番環境にアプリをのせるときに、サーバーに直接接続して作業するのではなくて、イケてるデプロイフローを構築して使いたい！」と思う"目指せ初心者脱却マン"は多いと思います。

今回は、開発環境のコンテナ化+AWS Elastic BeanstalkとCircle CIを使った自動デプロイフローを構築するまでの一連の流れを紹介したいと思います。いろんな記事・本を行ったり来たりしないでこの記事だけで完結させることを目標にします。

## 使用する環境・バージョン
### 開発環境
- OS : macOS Mojave 10.14.5
- git : 2.20.1 (Apple Git-117)
- EB CLI 3.15.3 (Python 3.7.4) 
- docker : 19.03.8, build afacb8b
- Docker.app : 2.2.0.5
- VS Code : 1.44.2
- VS Code Remote Development : 0.20.0

### アプリ
- Node.js : v12.13.0
- npm : 6.13.4
- express : 4.17.1 
- mongoose : 5.9.11

## 前提条件

- 以下のことがすでに行われている・インストール済みであることとします。
 - VS Codeと拡張機能 Remote Development
 - Docker
 - AWSのアカウント
 - EB CLI
 - Githubのアカウント
 - Circle CIへのGithub連携サインアップ
- ローカルアプリのソースコードは一応載せますが、そのコードについて事細かに解説するのは記事の主旨から外れるので今回はなしです。

## 読者に要求する前提知識
- 基本的なターミナルコマンドが扱えること
- gitが問題なく扱えること
- コンテナ(Docker)という概念と関連用語・コマンドについて知っていること
 - イメージ・ボリューム・ネットワークやコマンドについては今回深く解説しません。
- TCP/IP等の基本的なネットワークの知識
 - 「何番ポートを開ける」という言葉がわかれば最低限OKです

# 作業工程
## 0. アーキテクチャ・ディレクトリ構造
目指すローカル+本番環境は以下の通りです。
<img width="1014" alt="architecture.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/d02fc12f-309a-2fae-f5ca-3f950515839b.png">
また、ローカル環境での最終的なディレクトリ構造は以下のようになります。

```
/app #アプリのルートディレクトリ
  ├─.circieci # CircleCIの設定ファイルを格納
  │   └─config.yml
  ├─.devcontainer # VSCodeRemoteContainerの設定ファイルを格納
  │   ├─devcontainer.json
  │   └─docker-compose.yml
  ├─.elasticbeanstalk # ElasticBeanstalkの設定ファイルを格納
  │   └─config.yml
  ├─.ebextensions # ElasticBeanstalkのオプション設定ファイルを格納
  │   └─env.config
  ├─.gitignore
  ├─Dockerfile
  ├─docker-compose.yml
  ├─.dockerignore
  ├─node_modules
  │  └─(略)
  ├─package.json
  ├─package-lock.json
  └─src # アプリのソースコード
     ├─controllers
     │  └─personsController.js
     ├─models
     │  └─person.js
     └─main.js
```

## 1. ローカルアプリのソースコードを作成
### アプリの初期化
アプリのルートディレクトリで以下のコマンドを実行します。

```bash
$ npm init -y
$ npm i express -S
$ npm i mongoose -S
```
そうしたら、作成された`package.json`を以下のように書き換えます。

```package.json
{
  (略)
  "main": "src/main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node src/main"
  },
  (略)
}
```

### アプリのソースコード作成
今回は環境構築がメインなので、アプリ自体は「ルートにアクセスしたら、mongoDBに格納されている全ての情報をjsonで返す」という簡単なものにします。
以下コードです。

```src/main.js
"use strict";

const express = require("express");
const app = express();

const personsController = require("./controllers/personsController");

const mongoose = require("mongoose");
mongoose.connect(
    "mongodb://mongo-db:27017/testdb",
    {useNewUrlParser: true}
);
const db = mongoose.connection;
db.once("open", () => {
    console.log("successfully connected to mongoDB");
})

app.set('port', 3000);

app.get("/", personsController.getAllPersons, (req, res, next) => {
    console.log("server get request on root");
    res.send(req.data);
});

app.listen(app.get('port'), () => {
    console.log(`The Express.js server has started and is listening on port number: ${app.get('port')}`);
});
```
```src/models/person.js
"use strict";

const mongoose = require("mongoose");
const personSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true,
    lowercase: true,
    unique: true
  },
  zipCode: {
    type: Number,
    min: [10000, "Zip code too short"],
    max: 99999
  },
});

module.exports = mongoose.model("Person", personSchema);
```
```src/controllers/personsController.js
"use strict";

const Person = require("../models/person");

exports.getAllPersons = (req, res, next) => {
  Person.find({}, (error, persons) => {
    if (error) next(error);
    req.data = persons;
    next();
  });
};
```

## 2. gitの準備をする
アプリのルートディレクトリで以下のコマンドを実行して、gitの初期化と`.gitignore`ファイルの作成を行います。

```bash
$ git init
$ touch .gitignore
```
`.gitignore`ファイルに以下のように記述して、これらのファイルをgitの管理対象外にします。

```.gitignore
node_modules/
.gitignore
```

## 3. アプリケーションレイヤーのDocker化
「Docker化=作ったアプリをdockerのイメージにする」ということです。
ここではNode.jsで動くアプリケーションレイヤーの方のDocker化をまず先に行います。

### `Dockerfile`の作成
Dockerイメージの詳細は`Dockerfile`に記述されます。アプリのルートディレクトリに`Dockerfile`を作成し、以下のように記述します。

```Dockerfile
FROM node:12

WORKDIR /app

COPY package.json .
COPY package-lock.json .
RUN npm install
COPY . .

EXPOSE 3000
CMD [ "node", "src/main.js" ]
```
参考：[How To Deploy a Node App on AWS Elastic Beanstalk with Docker](https://medium.com/better-programming/how-to-deploy-a-node-app-on-aws-elastic-beanstalk-with-docker-9664e5841fe)
参考：[Node.js公式ドキュメント Node.js Web アプリケーションを Docker 化する](https://nodejs.org/ja/docs/guides/nodejs-docker-webapp/)

記述した設定の意味は以下の通り。

1. `FROM node:12` → node.js ver12の公式コンテナイメージを元にする。
2. `WORKDIR /app` → 以下の作業はコンテナ内のappディレクトリで行う。
3. `COPY ~ ` → ローカルの`package.json`と`package-lock.json`ファイルをコンテナ内のカレントディレクトリにコピー
4. `RUN npm install` → コンテナ内のカレントディレクトリでnpm installを実行
5. `COPY . .` → ローカルの残りのファイルをコンテナ内のカレントディレクトリにコピー
6. `EXPOSE 3000` → コンテナの3000番ポートを開ける
7. `CMD ~ ` → コンテナ起動時に`node src/main.js`を実行

`Dockerfile`に記述できるコードについては以下で詳しく説明されています。
参考：[Dockerfileの書き方と使い方](https://blog.codecamp.jp/docker-file-how-to)
参考：[Dockerfile リファレンス](http://docs.docker.jp/v1.11/engine/reference/builder.html)

### `.dockerignore`の作成
`Dockerfile`の設定により「ローカルディレクトリ内の全てのファイルをコンテナ内にコピーする」という動作が行われますが、`node_modules`のようなコピーしてたら重くてしょうがないものを対象外にします。
そもそもnodeのモジュールはnpm installで取得する仕様にしたのでわざわざローカルからコピーしなくても問題ないのです。
アプリのルートディレクトリ上に`.dockerignore`を作成して以下のように記述します。

```.dockerignore
node_modules
npm-debug.log
```
参考：[シンプルなNodeアプリをDocker化してElastic Beanstalkに移行してみた](https://mikimemo.hateblo.jp/entry/2019/01/18/195801)

## 4. mongoDB用のコンテナを作成し、アプリケーションレイヤーコンテナと連携させる
### `docker-compose.yml`の作成
複数個のDockerコンテナを連携させるためには、そのための設定を`docker-compose.yml`に記述します。
アプリのルートディレクトリに`docker-compose.yml`を作成して以下のように編集します。

```docker-compose.yml
# docker-compose記法のバージョン
version: '3.3'

# 起動するコンテナの種類を定義
services:
  #以下node.jsのアプリケーションレイヤーの設定。このコンテナには「node-web」と名前がつく
  node-web:
    # build時にカレントディレクトリにあるDockerfileのイメージを使用する
    build: .
    ports:
      # localの8888番ポートと、コンテナの3000番ポートをつなげる
      - "8888:3000"
    # コンテナ内でのホスト名
    hostname: web-server
    # このコンテナは、以下記述する「mongo-db」コンテナが立ち上がってから作成される。
    depends_on:
      - mongo-db

  # 以下mongoDBのコンテナ設定。コンテナ名は「mongo-db」とつく。
  mongo-db:
    # mongo:3.6の公式コンテナイメージを元にして作成する。
    image: "mongo:3.6"
    ports:
      - "27017:27017"
    hostname: db-server
    volumes:
      #コンテナ内の/data/dbの中身を、「db-volume」という名前のvolumeにマウント
      - db-volume:/data/db
      - db-config-volume:/data/configdb

# 作成するvolumeの名前を列挙
volumes:
  db-volume:
  db-config-volume:
```
参考：[dockerでnodejs, mongodbのローカル開発環境を構築してVisual Studio Codeでデバッグする](https://toripiyo.hatenablog.com/entry/2018/09/16/143936)
参考：[さわって理解するDocker入門第4回 Docker Composeを使った複数コンテナのデプロイ](https://www.ogis-ri.co.jp/otc/hiroba/technical/docker/part4.html)
参考：[Docker入門 〜Docker-compose, ネットワーク, ボリューム編〜](https://qiita.com/takaday/items/51553b9167fcd4789d61)

一般的に`docker-compose.yml`に記述できるキーについての説明は以下がわかりやすいです。
参考：[docker-compose の設定マニュアル最新版を日本語で！！](https://blog.w-star.work/blogs/7)

## 5. VS Code のRemote Developmentを使ってDockerに乗ったリモート開発環境を作る
開発環境をコンテナ化することで、ローカル環境を汚さなくて済んだり、環境構築に失敗した時の後片付けが簡単だったり(そのコンテナを廃棄すればいいだけ)、開発メンバー間で環境がシェアできたりと何かと便利です。
参考：[開発環境をDockerに乗せる方法とメリットを3ステップで学ぶチュートリアル](https://qiita.com/KeitaMoromizato/items/ae1a57fc62b41b942d71)

そのため、ローカルでの環境もDocker化して、そのコンテナにVS CodeのRemote Developmentという拡張機能を用いて入る方法で開発を進めます。

### VS Codeでのコンテナへの接続方法
まず、VS Codeの画面左下にある緑色の「><」ボタンを押します。
![remote-dev-status-bar.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/0678c86c-682f-d10c-4a43-9e3abcb3064d.png)
画像出典：[VS Code 公式ドキュメント Developing inside a Container](https://code.visualstudio.com/docs/remote/containers#_forwarding-a-port)

開いたコマンドパレットの中から「Remote-Containers: Open Folder in Container…」を選択して、アプリのルートディレクトリをRemote ContainerとしてOpenすると選択します。

その後、コマンドパレットに「How would you like to create your container configuration?」と表示されるので、「From 'docker-compose.yml'」を選択します。こうすることで、`docker-compose.yml`に書いた設定通りに開発環境が作られます。
<img width="660" alt="スクリーンショット 2020-05-06 22.37.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/7720f7ab-4b0c-eaaa-e282-7409aa9f5767.png">
選択した後、コマンドパレットに「Select a service」と表示されます。コードをいじるために入って開発したいのはアプリケーションレイヤー側なので、ここではnode-webを選択します。
<img width="261" alt="スクリーンショット 2020-05-06 22.37.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/3e664962-f11c-1162-71f9-49ee40adfb01.png">
すると、コンテナが自動で作成され、中身をVS Codeで編集できるようになります。

### 接続した後の状況
接続時のアーキテクチャは以下の図の通りです。
![architecture-containers.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/dc75b1a7-1e30-2199-5424-557450d41fcc.png)
画像出典：[VS Code 公式ドキュメント Developing inside a Container](https://code.visualstudio.com/docs/remote/containers#_forwarding-a-port)

この状態では、Dockerコンテナ上のファイルを直接編集が可能になり、DockerホストとDockerコンテナの間でソースコードを同期する必要がなくなります。
ファイル新規作成/削除・ファイル編集・git操作・`npm install`まで全て同期されます。
参考：[Visual Studio CodeのRemote DevelopmentとDockerで快適な開発環境をゲット](https://tech-lab.sios.jp/archives/18677)

作成される環境は、`docker-compose up`を実行したときと全く同じです。
アプリケーションレイヤーのnode-webコンテナとDBレイヤーのmongo-dbコンテナ、volumeが2つ(db-volumeとdb-config-volume)、ネットワークが1つ(app_defaultという名前)作られます。
これらは`docker-compose.yml`で設定した通り、

- mongoDBコンテナにドメイン名`mongo-db`でアクセス可能(`ping mongo-db`で確認できる)
- ホストマシンの8888番ポートとアプリ側コンテナの3000番ポートが繋げてある
- mongoDBの内容は2つのvolumeに入る

という状態なので、コンテナ内で`npm start`すれば、ローカルマシンの方でwebブラウザを開き、`http://localhost:8888`にアクセスすることでアプリの挙動が確認できます。また、ボリュームもマウントされているので、コンテナを一旦停止したとしてもDBの内容は失われません。

参考：[VS Code / Remote - Containers で docker-compose を試す](https://westa.io/@alclimb/remote-containers_docker-compose_sample)
参考：[Dockerize a Node.js app connected to MongoDb](https://itnext.io/dockerize-a-node-js-app-connected-to-mongodb-64fdeca94797)

### VS Code Remote Developmentの設定ファイル
このVS Codeの設定が`.devcontainer/devcontainer.json`と`.devcontainer/docker-compose.yml`のファイルの形で保存されます。このファイルは最初に環境を作って接続するときに自動作成されます。

```.devcontainer/devcontainer.json
// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.112.0/containers/docker-existing-docker-compose
// If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml.
{
	"name": "Existing Docker Compose (Extend)",
	"dockerComposeFile": [
		"../docker-compose.yml",
		"docker-compose.yml"
	],
	"service": "node-web",
	"workspaceFolder": "/workspace",
	"settings": { 
		"terminal.integrated.shell.linux": null
	},
	"extensions": []
}

```
```.devcontainer/docker-compose.yml
version: '3.3'
services:
  node-web:    
    volumes:
      - .:/workspace:cached
    command: /bin/sh -c "while sleep 1000; do :; done"
```

### 設定ファイルをgit管理対象から外す
設定ファイル.devcontainerはgit管理対象外にする方がいいそうです。
参考：[VSCode Remote Containerが良い](https://qiita.com/d0ne1s/items/d2649801c6f804019db7)

なので、`.gitignore`に以下の一文を追記します。

```.gitignore
.devcontainer/
```

### (おまけ)mongoDBと接続しないNode.js単独コンテナの場合
開発環境で使うコンテナが1つだけの場合は、`docker-compose.yml`ではなくて`Dockerfile`を元にして接続することができます。

「><」ボタン→「Remote-Containers: Open Folder in Container…」までは上記の手順と同じで、次に「From 'Dockerfile'」を選択すれば、単独コンテナの開発環境を構築→VS Codeで接続することができます。
この時、`docker-compose.yml`のときとはまた別の、単独コンテナ用の`.devcontainer/devcontainer.json`設定ファイルが作られます。内容は以下の通り。

```.devcontainer/devcontainer.json
{
	"name": "Existing Dockerfile",
	"context": "..",
	"dockerFile": "../Dockerfile",
	"settings": { 
		"terminal.integrated.shell.linux": null
	},
	"extensions": []
}
```

このコンテナの起動直後は、ローカル端末とコンテナーはネットワーク上隔離されているので、コンテナー内のポートへ直接接続ができません。もしもこの中で動かしているアプリの挙動を`http://localhost:XXXX`で確認したいというならば、コンテナのYYYY番ポートとローカルのXXXX番ポートをつなげる設定を`.devcontainer/devcontainer.json`に追加する必要があります。

```devcontainer.json
// local: port XXXX, container: port YYYY
"appPort": ["XXXX:YYYY"]
```
追加した設定を反映させるには、コンテナをrebuildさせる必要があります(restartではダメ)。
コマンドパレットに「>Remote-Containers: Rebuild Container」と入力して実行すれば、新しい設定に合わせて自動でコンテナが作り直されます。
参考：[VS CodeのRemote-Containersでリモートサーバー上のコンテナーの開発を行う方法](https://qiita.com/tnk4on/items/7ccb1921907bdb99002a)

## 6. 本番環境用のmongoDBサーバーを立ち上げる
今回本番環境へアプリをデプロイするのにElastic Beanstalkを使いますが、このElastic Beanstalkというサービスはステートレスなアプリのデプロイをサポートしているもので、mongoDBのようなステートフルのものをこれでやろうとするのはベストプラクティスではないようです。
参考：[Running mongodb with Elastic Beanstalk](https://forums.aws.amazon.com/thread.jspa?messageID=709959)
参考：[【初心者向け】ステートフル(Stateful)とステートレス(Stateless)の違い,IPv6やAWSでの考え方](https://milestone-of-se.nesuke.com/nw-basic/as-nw-engineer/stateful-and-stateless/)

ですので、mongoDBのサーバーはElastic Beanstalkとは別に立てて、あとからアプリサーバーと連携させる形をとります。

### EC2インスタンスを作成
AWS EC2の詳しい操作については今回は割愛します。

今回はAmazon Linux2のOSを選びました。
SSH接続用の22番ポートと、mongoDB用の27017番ポートを空けてインスタンスを作成します。

### mongoDBのインストール
作成したEC2インスタンスにSSH接続します。

```bash
$ ssh ec2-user@<サーバードメイン> -i <path to key>
       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|
```
謎の図形と「Amazon Linux 2 AMI」という文字が表示されれば接続成功です。

接続したら、`/etc/yum.repos.d/mongodb.repo`を開いて以下のように書き込みます。

```bash
$ sudo vim /etc/yum.repos.d/mongodb.repo

# 以下を書き込んで保存
[mongodb-org-3.2]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/amazon/2013.03/mongodb-org/3.2/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-3.2.asc
```
その後、以下のコマンドを打ってmongoDBをインストールします。

```bash
$ sudo yum install -y mongodb-org
```
`sudo service mongod start`や`sudo service mongod stop`といった、起動・停止コマンドが効けばインストール成功です。
参考：[MongoDB On EC2を試してみる](https://dev.classmethod.jp/articles/mongodb-on-ec2/)

その後、localhost以外からのアクセスを受け付けるように、configファイルを以下のように編集します。

```
$ sudo vim /etc/mongod.conf

# 以下をコメントアウト
# bind_ip=127.0.0.1
```
参考：[AWSでmongoを動かしてみる(AutoScale対応)](https://qiita.com/KeitaMoromizato/items/b811ddc2084e9bdc56db)

そうしたら、`sudo service mongod start`でmongoDBを起動させてからサーバーログアウトします。

## 7. 本番環境用の設定をアプリに反映させる
今のローカルアプリのコードでは、アプリケーションレイヤーは3000番ポートで通信・DBはローカルで作成したコンテナに接続するようになっています。このままだと本番環境に乗せても動きません。
なので、「本番環境を示す環境変数が存在する場合は本番用の設定を参照、そうでないならローカル用の設定を参照」とするようにします

```src/main.js
/*
mongoose.connect(
    mongodb://mongo-db:27017/testdb,
    {useNewUrlParser: true}
);
*/
const mongoDB = process.env.NODE_ENV === "production" ? 'mongodb://[mongoDBをインストールしたインスタンスのIP address]:27017/testdb' : 'mongodb://mongo-db:27017/testdb';
mongoose.connect(
    mongoDB,
    {useNewUrlParser: true}
);

// app.set('port', 3000);の部分を以下のように修正
app.set('port', process.env.PORT || 3000);
```
これで、

- NODE_ENVという環境変数の値が"production"ならば本番DBサーバーを参照、そうでないならローカルコンテナのDB
- アプリの通信用PORT番号が定められているならその値を、そうでないなら3000番ポートを開く

という設定になりました。

## 8. Elastic Beanstalkの設定
Elastic Beanstalkのサービス自体の説明は、以下のAWSのスライドでわかりやすく説明されています。
参考：[[AWSマイスターシリーズ] AWS Elastic Beanstalk](https://www.slideshare.net/AmazonWebServicesJapan/aws-aws-elastic-beanstalk)

今回は、コマンドラインからアプリのデプロイができるように設定していきます。
### EB CLIのインストール
以下の公式ドキュメントにやり方が詳しく書かれています。インストールしていない人は入れましょう。
参考：[AWS公式ドキュメント　macOS で EB CLI をインストールします。](https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/eb-cli3-install-osx.html)

### EB CLIのためのアクセスキーを取得する
CLIでコマンドを打ったときに、どのアカウントが使われるのかを設定する必要があります。
このアカウント紐付けに使われるのがアクセスキーです。
アクセスキーを取得していない人は、以下の記事を参考にして、AWSのウェブコンソールから取得してください。
参考：[AWS CLIのインストールから初期設定メモ](https://qiita.com/n0bisuke/items/1ea245318283fa118f4a)

アクセスキーを取得したら、Elastic Beanstalkの機能をフルで使えるように、「AWSElasticBeanstalkFullAccess」の権限をキーに付けます。
参考：[【AWS】Elastic BeanstalkでDjangoアプリをデプロイしてみた](https://www.royozaki.net/archives/697)
参考：[Elastic Beanstalkトラブルシューティング集](https://qiita.com/shibukk/items/bc460d0fabf23aba0f5b)

### `eb init`でElastic Beanstalkの初期化を行う
`eb init`のコマンドを叩くことによって、デプロイのための最低限の設定ファイルが作られます。
アプリのルートディレクトリ下で実行します。

```bash
$ eb init

# どこのリージョンにアプリをデプロイするか選択
Select a default region
1) us-east-1 : US East (N. Virginia)
2) us-west-1 : US West (N. California)
3) us-west-2 : US West (Oregon)
(略)
9) ap-northeast-1 : Asia Pacific (Tokyo)
(略)
(default is 3): 9

# 初めてCLIを使う場合で、アクセスキーとCLIが紐づいていない場合はここで聞かれます。
# 一度でもこれを入力したことがある場合はこの項目はスキップされます。
You have not yet set up your credentials or your credentials are incorrect.
You must provide your credentials.
(aws-access-id): your-access-key-id
(aws-secret-key): your-access-secret-key
# 入力したら、~/.aws/configファイルの中に設定が保存されて、以降はそれが参照されます。

# アプリの名前を決める
Enter Application Name
(default is "app"): 
Application app has been created.

# Dockerfileがディレクトリ内にあったことから、Dockerアプリだと自動で判断してくれた。
# あっているのでYを入力
It appears you are using Docker. Is this correct?
(Y/n): Y

# 使うDockerの種類を選択
Select a platform version.
1) Docker 18.09.9-ce
2) Docker
(default is 1): 

# アプリのコードの置き場所としてCodeCommitを使うかどうか。
# Noを選択すると、S3のバケットが自動で用意されてそこに格納されるようになる。
Do you wish to continue with CodeCommit? (y/N) (default is n): n

# アプリがデプロイされるインスタンスにSSH接続できるようにするかの設定
# SSH接続できれば、何かあったときにサーバー内に入って調べられるようになるのでYが無難
Do you want to set up SSH for your instances?
(Y/n): Y

# SSH接続するためのキーペアを何にするかを選択
Select a keypair.
1) your-keypair
2) [ Create new KeyPair ]
(default is 1): 1
```
参考：[Elastic Beanstalk にEB CLIから簡単デプロイ](https://saku.io/awsebcli-deployment-for-beanstalk/)
参考：[AWS CLIのプロファイルを切り替えてebコマンドで既存ElasticBeanstalk Applicationにアクセスする](https://dev.classmethod.jp/articles/aws-eb-multi-profile/) 

すると、アプリルートディレクトリ下に`.elasticbeanstalk/config.yml`が以下のように作成されて、設定が保存されます。

```.elasticbeanstalk/config.yml
branch-defaults:
  master:
    environment: null
    group_suffix: null
global:
  application_name: app
  branch: null
  default_ec2_keyname: my-keyname
  default_platform: Docker
  default_region: ap-northeast-1
  include_git_submodules: true
  instance_profile: null
  platform_name: null
  platform_version: null
  profile: eb-cli
  repository: null
  sc: git
  workspace_type: Application
```

また、AWSのコンソールでElastic Beanstalkのページを確認すると、きちんとappというアプリができている。この時点でこのアプリに環境はまだ存在しません。

### オプション設定を追加
7章で、本番環境ではNODE_ENVという環境変数を設定する仕様にしたので、デプロイしたコンテナにNODE_ENV=productionという値を渡すようにオプション設定をします。

凝った環境のカスタマイズをするためには、アプリのルートディレクトリに`.ebextensions`というファイルを用意して、その中に設定ファイルを作ります(ここでは`env.config`とします)。
環境変数設定のためには以下のように記述します。

```.ebextensions/env.config
option_settings:
  - option_name: NODE_ENV
    value: production
```
参考：[AWS公式ドキュメント 設定ファイル (.ebextensions) による高度な環境のカスタマイズ](https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/ebextensions.html)
参考：[AWS Elastic Beanstalkで環境変数を追加する](http://yamotonalds.hatenablog.com/entry/2013/06/18/AWS_Elastic_Beanstalkで環境変数を追加する)

### Elastic Beanstalkの環境を作成する
Elastic Beanstalkでは、一つのアプリに対して本番環境や検証環境、開発環境など複数個の環境が作られることを想定しているので、アプリ1つに対して複数個環境という関係になっています。
参考：[ebコマンドを使ってElasticBeanstalkでデプロイしてみる](https://qiita.com/toshihirock/items/593e17d42a8d1d533a49)

gitのブランチ一つに対して一つの環境が紐づくようになっているので、まずは今のコードを全てコミットしておきます。

```bash
$ git commit -m "first commit"
```
そこで、環境を作成→デプロイするための`eb create`コマンドをルートディレクトリ上で実行します。

```bash
$ eb create
Enter Environment Name
(default is app-dev):

Enter DNS CNAME prefix
(default is app-dev): 

Select a load balancer type
1) classic
2) application
3) network
(default is 2): 
```
設定の入力が終わったら、起動ログがターミナルに流れます。

```bash
Creating application version archive "your-app-deployed-version".
Uploading [your-app-file].zip to S3. This may take a while.
Upload Complete.

Environment details for: app-dev
  Application name: app
  Region: ap-northeast-1
  Deployed Version: your-app-deployed-version
  Environment ID: xxxxxxxxxxx
  Platform: arn:aws:elasticbeanstalk:ap-northeast-1::platform/Docker running on 64bit Amazon Linux 2/0.1.0
  Tier: WebServer-Standard-1.0
  CNAME: app-dev.ap-northeast-1.elasticbeanstalk.com
  Updated: yyyy-mm-dd hh:mm:ss

Printing Status:
yyyy-mm-dd hh:mm:ss    INFO    createEnvironment is starting.
yyyy-mm-dd hh:mm:ss    INFO    Using elasticbeanstalk-ap-northeast-1-[your bucket ID] as Amazon S3 storage bucket for environment data.
yyyy-mm-dd hh:mm:ss    INFO    Created target group named: [your-target-group-name]
yyyy-mm-dd hh:mm:ss    INFO    Created security group named: [your-security-group-name]
yyyy-mm-dd hh:mm:ss    INFO    Created security group named: [your-security-group-name-2]
yyyy-mm-dd hh:mm:ss    INFO    Created Auto Scaling launch configuration named: [your-configuration-name]
yyyy-mm-dd hh:mm:ss    INFO    Created Auto Scaling group named: [your-auto-scaling-group-name]
yyyy-mm-dd hh:mm:ss    INFO    Waiting for EC2 instances to launch. This may take a few minutes.
yyyy-mm-dd hh:mm:ss    INFO    Created Auto Scaling group policy named: [yout-auto-scaling-policy-name]
yyyy-mm-dd hh:mm:ss    INFO    Created Auto Scaling group policy named: [yout-auto-scaling-policy-name-2]
yyyy-mm-dd hh:mm:ss    INFO    Created CloudWatch alarm named: [your-cloudwatch-alarm-name]
yyyy-mm-dd hh:mm:ss    INFO    Created CloudWatch alarm named: [your-cloudwatch-alarm-name-2]
yyyy-mm-dd hh:mm:ss    INFO    Created load balancer named: [your-load-balancer-name]
yyyy-mm-dd hh:mm:ss    INFO    Created Load Balancer listener named: [your-load-balancer-listener-name]
yyyy-mm-dd hh:mm:ss    INFO    Successfully pulled node:12
yyyy-mm-dd hh:mm:ss    INFO    Successfully built aws_beanstalk/staging-app
yyyy-mm-dd hh:mm:ss    INFO    Docker container [docker-ID] is running aws_beanstalk/current-app.
```
これでアプリのデプロイに成功しました。

### EB CLIで実行できる操作
`eb status`で環境の状態ステータス(OK, severe, pendingなど)が確認できます。

```bash
$ eb status
Environment details for: app-env
  Application name: app
  Region: ap-northeast-1
  Deployed Version: your-app-deployed-version
  Environment ID: xxxxxxxxxxx
  Platform: arn:aws:elasticbeanstalk:ap-northeast-1::platform/Docker running on 64bit Amazon Linux 2/0.1.0
  Tier: WebServer-Standard-1.0
  CNAME: app-dev.ap-northeast-1.elasticbeanstalk.com
  Updated: yyyy-mm-dd hh:mm:ss
  Status: Ready
  Health: Green
```

`eb open`で作った環境のURLをブラウザで開くことができます。

`eb deploy`で修正デプロイを行うことができます。
例えば、今の環境に紐づいているブランチにコミットを付け足して、その状態でこのコマンドを叩くと、修正デプロイ実行の様子がログで表示されます。

```bash
$ git commit -m "bug fix"
$ eb deploy
Creating application version archive "your-app-deployed-version".
Uploading [your-fixed-app-file] to S3. This may take a while.
Upload Complete.
yyyy-mm-dd hh:mm:ss    INFO    Environment update is starting.      
yyyy-mm-dd hh:mm:ss    INFO    Deploying new version to instance(s).
yyyy-mm-dd hh:mm:ss    INFO    Successfully pulled node:12          
yyyy-mm-dd hh:mm:ss    INFO    Successfully built aws_beanstalk/staging-app
yyyy-mm-dd hh:mm:ss    INFO    Docker container [docker ID] is running aws_beanstalk/current-app.
yyyy-mm-dd hh:mm:ss    INFO    New application version was deployed to running EC2 instances.
yyyy-mm-dd hh:mm:ss    INFO    Environment update completed successfully.
```

`eb terminate --all`で、Elastic Beanstalkの環境・アプリもろとも全て破棄することができます。ローカルに自動生成された`.elasticbeanstalk`フォルダも自動でなくなります。

```bash
$ eb terminate --all
The application "app" and all its resources will be deleted.
This application currently has the following:
Running environments: xxx
Configuration templates: xx
Application versions: xxx

To confirm, type the application name: app

Removing application versions from s3.
yyyy-mm-dd hh:mm:ss    INFO    deleteApplication is starting.
yyyy-mm-dd hh:mm:ss    INFO    Invoking Environment Termination workflows.
yyyy-mm-dd hh:mm:ss    INFO    The environment termination step is done.
yyyy-mm-dd hh:mm:ss    INFO    The application has been deleted successfully.
```

参考：[AWS公式ドキュメント EB CLI による Elastic Beanstalk 環境の管理](https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/eb-cli3-getting-started.html)
参考：[AWS公式ドキュメント　Express アプリケーションを Elastic Beanstalk にデプロイする](https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/create_deploy_nodejs_express.html)
参考：[5分でAWS Elastic Beanstalk(作って、修正して、お片付け)](https://qiita.com/icck/items/e30d62e878b1b2c6f3d7)

## 9. Circle CI の導入
最後にCircle CIを使って、Githubにpushしただけで自動デプロイができるようにします。

### Circle CIが使うアクセスキーを取得
8章で作ったものとは別に、Circle CIに渡す専用のアクセスキーを作成します。これにも「AWSElasticBeanstalkFullAccess」の権限を付与します。

### Elastic Beanstalkの設定ファイルをgit管理対象に含める
Githubにプッシュした内容を見てCircle CIがデプロイ操作をするので、Elastic Beanstalkの設定はGithub上にのせる必要があります。
デフォルトでは管理外になっているので、`.gitignore`に以下の追記をして管理対象に含めます。

```.gitignore
!.elasticbeanstalk/config.yml
```
参考：[CircleCI経由でElasticBeanstalkにデプロイする方法](https://qiita.com/mikamix/items/31849dc94a3e39575240)

### Circle CIの設定ファイルを作る
設定はアプリのルートディレクトリの`.circieci/config.yml`に書くことになっています。
なので、ファイルを作成して以下のように記述します。

```.circleci/config.yml
# circleciのバージョン
version: 2

# jobの設定
jobs:
  # deployという名前のjobの定義を以下記述
  deploy:
    # circleciが検証用に用意するコンテナ内でのソースコードの置き場
    working_directory: ~/app

    # circleciが建てるdocker imageの種類
    docker:
    - image: circleci/node:12

    # deployというjobを行うためにcircleciのdocker内で動作させるコマンド
    steps:
    - checkout
    - run: npm install
    - save_cache:
        paths:
        - node_modules
        key: v1-dependencies-{{ checksum "package.json" }}
    - run:
        name: Installing EB CLI
        working_directory: /
        command: |
          sudo apt-get -y -qq update
          sudo apt-get install python-pip python-dev build-essential
          sudo pip install awsebcli --upgrade
    - run:
        name: Deploying
        command: eb deploy [created-my-env-name]

# workflow(jobを順番どうりに実行する一連のフロー)の定義
workflows:
  version: 2
  # workflowの名前をbuildとする
  build:
    # build workflowで実行するjobの順番
    jobs:
    - deploy:
        # deploy jobを行うのは、Githubのmasterブランチにpushが行われたときのみ
        filters:
          branches:
            only:
            - master
```
参考：[How to deploy your NodeJs app on Amazon Elastic Beanstalk (AWS EB) with CircleCI — Short Tutorial](https://medium.com/@vygandas/how-to-deploy-your-nodejs-app-on-amazon-elastic-beanstalk-aws-eb-with-circleci-short-tutorial-d8210d2a7f0c)

設定ファイルに書くことのできるキーの意味については以下の記事がわかりやすい。
参考：[いまさらだけどCircleCIに入門したので分かりやすくまとめてみた](https://qiita.com/gold-kou/items/4c7e62434af455e977c2)

### Githubのリモートリポジトリにpush
Githubのページから新しいリポジトリを作り、ローカルリポジトリと連携させます。

```bash
$ git remote add origin <git url>
$ git push -u origin master
```

### Circle CIのプロジェクト作成
Circle CIのページで、先ほど作ったGithubのリモートリポジトリを選択して「set up project」のボタンを押す。
すると、configファイルをどうするかを設定する画面に遷移します。先ほど手動で作ってもうすでにリポジトリ内に存在するので、「add manually」を選択します。
そうすると、projectのページに自動遷移して、ビルドの様子を監視できるようになります。

### Circle CIにEB CLIのアクセスキーを渡す
ただ、このままだとCircle CIはAWSにアクセスする権限がないので、デプロイは失敗します。
そのため、先ほど作ったCircle CI用のアクセスキーの情報を環境変数にして渡します。

project settingsを開いて、その中の「Environment Variables」というタブを選択します。
そこで、以下の環境変数を追加します。

- AWS_ACCESS_KEY_ID: [your-access-key]
- AWS_SECRET_ACCESS_KEY: [your-secret-access-key]

### もう一度アプリをpush
ソースコードのどこかを適当に変えて(挙動に影響が出ない程度に)、Githubのmasterブランチに再びpushします。
すると、pushを感知してCircle CIが動き、アプリのデプロイができます。

もしデプロイ時に「InvalidProfileError」が出るなら、`.elasticbeanstalk.config.yml`の設定ファイルの`profile: eb-cli`をコメントアウトすれば一応動くようにはなります。
参考：[How to setup Elastic Beanstalk Deployment?](https://discuss.circleci.com/t/how-to-setup-elastic-beanstalk-deployment/6154)

# まとめ
これでとりあえず最低限の環境構築ができました。お疲れ様でした。あとはアプリの内容を充実させることに集中することができます。
ただし、ここで紹介した手順・構成がベストプラクティスとは限りません。今回は動くことを重視したため、セキュリティ面ではまだまだ手を入れないといけないところがあると思います。その点だけはご了承ください。