LaravelアプリケーションをAWS上のDockerで動かす

## 概要
LaravelのアプリケーションをAWS上のDockerで動かすための環境を構築します。
具体的には、以下を行います。

1. コンテナを構築するためのDockerfileを作成する
2. ECRリポジトリの作成 → AWS CLIで作成
3. 1.で作成したDockerイメージをECRにをpushする
4. ECSの環境構築 → マネジメントコンソールから作成

## 前提条件

- AWS CLIがインストールがインストールされていること

## コンテナを構築

`php`と`nginx`の2つのコンテナを構築します。Dockerfileは、それぞれ下記の通りです。

### phpコンテナのDockerfile

必要なソースコードをコンテナ内にコピーし、依存ライブラリのインストールを行なっています。

```Dockerfile
FROM php:7.2.10-fpm-alpine

COPY . .
WORKDIR /var/www/html

RUN set -x && \
  apk update && \
  apk add --no-cache libxml2 libxml2-dev curl curl-dev autoconf $PHPIZE_DEPS && \
  docker-php-ext-install opcache mysqli pdo pdo_mysql xml mbstring curl session tokenizer json && \
  curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer && \
  composer global require hirak/prestissimo && \
  composer install && \
  chmod -R a+w storage/ bootstrap/cache

COPY ./docker/php/config/php.ini /usr/local/etc/php/php.ini
COPY ./docker/php/config/docker-php-ext-opcache.ini /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini

```

### nginxのDockerfile
`default.conf`の設定を行なっています。
`public`ディレクトリ配下は、`nginx`コンテナに配置しています。

```Dockerfile
FROM nginx:1.15.5-alpine

ADD ./docker/nginx/config/default.conf /etc/nginx/conf.d/default.conf

RUN mkdir -p /var/www/html/public
ADD ./public/ /var/www/html/public
```
### `.dockerignore`の追加
Dockerのイメージを小さくするために、`.dockerignore`を追加します。
`node_modules`のような容量が大きいものは追加しておく方がいいと思います。

`.dockerignore`については、下記の記事に詳しく説明されていますので載せておきます。
[.dockerignore アンチパターン](https://qiita.com/munisystem/items/b0f08b28e8cc26132212)

## ECRにDockerイメージをpushする
### ECRとは

ECR(Amazon Elastic Container Registry)は、Dockerのコンテナイメージを保存しておくためのレジストリサービスです。ECRに保存したコンテナイメージは、Amazon ECSへのデプロイが可能であったり、他の AWS サービスと簡単に連携することができます。

[Amazon ECR](https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/what-is-ecr.html)


### 事前準備

AWS CLIを使用して、ECR用のプロファイルを作成します。

```console
$ aws configure --profile ecr
```

### リポジトリ作成

`php`と`eginx`の２つのリポジトリを作成します。

```console
$ aws ecr create-repository --repository-name php --profile ecr
$ aws ecr create-repository --repository-name nginx --profile ecr
```

マネジメントコンソールで作成する場合は、以下の通りです。

`Amazon ECR` > `Repositories`を選択し、`リポジトリの作成`をクリックします。
<img width="1107" alt="スクリーンショット 2019-02-26 16.07.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/206741/f4bd4ac9-8e5a-d489-2bcb-4c78984c08e4.png">

リポジトリ名を入力し、`リポジトリの作成`をクリックして、リポジトリを作成します。
![スクリーンショット 2019-02-26 16.08.30.png](https://qiita-image-store.s3.amazonaws.com/0/206741/20820f18-b1d2-798d-4a72-e3e2ff36625a.png)

### nginx/phpコンテナのビルド

上記で作成したリポジトリ名と同じになるように、dockerイメージ名を指定しビルドします。
タグには`latest`としていますが、必要に応じて変更してください。

```console
$ docker build -t {AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/php:latest -f docker/php/Dockerfile .
$ docker build -t {AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/nginx:latest -f docker/nginx/Dockerfile .
```

以下の通りDockerイメージが作成されているはずです。

```console
$ docker image ls
REPOSITORY                                                TAG                 IMAGE ID            CREATED             SIZE
************.dkr.ecr.ap-northeast-1.amazonaws.com/php     latest              e7cc55e81372        23 seconds ago      376MB
************.dkr.ecr.ap-northeast-1.amazonaws.com/nginx   latest              c9bfc2840cbc        3 minutes ago       18.2MB
```

### ECRにログイン

ECRにログインする為のパスワードを取得します。

```console
$ aws ecr get-login --region ap-northeast-1 --no-include-email --profile ecr
```

以下のコマンドが表示されるので、全てコピペし、`docker login`でログインします。

```sh
docker login -u AWS -p password https://{AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
```
`Login Succeeded`と表示されればログイン成功です。


`~/.docker/config.json`を確認すると`auths`に追加されていることが確認できます。

```~/.docker/config.json
"auths": {
		"{AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com": {}
	},

```

### ECRへDockerイメージをpush

上記で作成したリポジトリにDockerイメージをpushします。

```console
$ docker push {AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/php:latest
$ docker push {AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/nginx:latest
```

## ECSの環境構築

### セキュリティグループの作成

ECSを構築する前に、以下を実現するためのセキュリティグループを2つ作成します。

- インターネットからALBの 443/tcp に対するアクセス許可
- ALBからphpコンテナの 80/tcp に対するアクセス許可

#### ALBのセキュリティグループ

- 外部から 443/tcp へのアクセス許可

以降の手順で作成するALBを割り当てます。

#### ECSサービスのセキュリティグループ

- ALBのセキュリティグループから 80/tcp へのアクセス許可

ECSサービスのセキュリティグループとしてますが、実際にはECSでクラスターを作成する際にEC2インスタンスに割り当てるセキュリティグループです。

### ALBを作成する

通常のALB作成手順に従ってください。
セキュリティグループは、上記で作成したALBのセキュリティグループを割り当てます。

ALBを利用せずにECSをPublicサブネットに配置する場合は、作成する必要はありません。

これ以降、ECSの構築を行なっていきます。

### タスクを定義する
コンテナの起動方法をタスクの定義として登録します。
phpコンテナとnginxコンテナの組み合わせを定義していきます。

`Amazon ECS` > `タスク定義`から、`新しいタスク定義の作成`を選択します。
起動タイプは、`EC2`を選択します。

**タスクとコンテナの定義の設定**

| 項目 | 値 |
| --- | --- |
| タスク定義名 | sample-api |
| タスクロール | なし |
| ネットワークモード | default |

補足：
タスクロールは、コンテナ内から他のサービスにアクセスするためのロール設定です。
ネットワークモードについては、下記の記事にわかりやすくまとめられていましたので、掲載します。
[ECSでEC2インスタンスを利用する際のネットワークモードについて調べてみた ｜ DevelopersIO](https://dev.classmethod.jp/etc/ecs-networking-mode/)

次に、コンテナの定義で、phpコンテナとnginxコンテナを追加します。

#### phpコンテナを追加
| 項目 | 値 |
| --- | --- |
| コンテナ名 | php |
| イメージ | {AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/php:latest |
| 作業ディレクトリ | /var/www/html |
| メモリ制限（MB） | ハード制限 300(MB) |

#### nginxコンテナを追加
| 項目 | 値 |
| --- | --- |
| コンテナ名 | nginx |
| イメージ | {AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/nginx:latest |
| メモリ制限（MB） | ハード制限 300(MB) | 
| リンク | php | 
| ポートマッピング | tcp 80:80 | 

以上で設定は完了です。`作成`を押下して、タスクの定義を作成します。

### ECSクラスタを作成する
`Amazon ECS` > `クラスター`から、`クラスターの作成`を選択します。
`クラスターテンプレートの選択`は、`EC2 Linux + ネットワーキング`を使用します。

**インスタンスの設定**

| 項目 | 値 |
| --- | --- |
| クラスター名 | sample-api-cluster |
| EC2 インスタンスタイプ | t2.micro |
| プロビジョニングモデル | スポット |
| スポットインスタンスの配分戦略 | 最低価格 |
| インスタンス数 | 1 |
| Maximum bid price (per instance/hour) | 10($) |

補足：
> スポットインスタンスは中断される可能性があります。中断できないアプリケーションに対しては、スポットインスタンスを使用しないことをお勧めします。
[クラスターの作成 - Amazon Elastic Container Service](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/create_cluster.html)

sshログインを可能とする場合、キーペアを指定している必要があります。
この場合、セキュリティグループで22番ポートも解放してください。

**ネットワーキング**
VPCは、ALB作成で指定したVPC指定。
セキュリティグループは、作成済みのECSサービスのセキュリティグループを指定。

**コンテナインスタンス IAM ロール**
デフォルトで選択されている`ecsInstanceRole`を指定。

**スポット群 IAM ロール**
デフォルトで選択されている`ecsSpotFleetRole`を指定。

以上で設定は完了です。`作成`を押下して、クラスタを作成します。
クラスタが作成されると、コンテナが起動するEC2インスタンスが作成されます。


### サービスを作成する

`Amazon ECS` > `クラスター` > `サービスタブ`から、`作成`を選択します。


| 項目 | 値 |
| --- | --- |
| 起動タイプ | EC2 |
| タスク定義 | sample-api |
| クラスタ | sample-api-cluster |
| サービス名 | sample-api |
| サービスタイプ | REPLICA |
| タスクの数 | 1 |

**Elastic Load Balancing（オプション）**

| 項目 | 値 |
| --- | --- |
| ELB タイプ | Application Load Balancer |
| サービス用の IAM ロールの選択 | デフォルトで選択されているecsServiceRoleを選択 | 
| ELB 名 | 作成したALBを選択 |

**負荷分散用のコンテナ**

`nginx:80:80`を指定し、`ELBへの追加`を選択。
ターゲットグループ名に、`作成したALBに紐づくターゲットグループを選択`を選択。

以上で、ECSの環境構築は完了です。

## 動作確認
作成したALBの`DNS名`にアクセスし、動作確認を行います。

## Fargateに移行
この記事で作成した環境をFargateに変更する手順を簡単にまとめました。
[ECSのバックエンドをEC2からFargateに変更](https://qiita.com/kobayashi-m42/items/aecc34b39743fdcfb2ff)

## 参考記事
下記を参考にさせていただきました。
[【AWS】初めてのECR](https://qiita.com/3utama/items/b19e2239edb6996a735f)
[LaravelアプリケーションをローカルでもAWSでもDockerで動かす](https://qiita.com/imunew/items/1e4826030d725beb4710)