PHP Slim 環境構築(14) ECS(手動で設定するEC2編)

# PHP Slim 環境構築(14) ECS(手動で設定するEC2編)

## Introduction

[前回](https://qiita.com/dynamitecoolguy/items/06f7e7ba33fb8b84f841)は、EC2上にNginxとPHP-FPMの両方を格納したDockerコンテナを構築し、AWSの各種サービス(RDB、DynamoDB、S3)とアクセスさせてみました。
今回は、同じDockerコンテナをECS上に構築します。

この一連のシリーズは、自分への備忘録が第一目的のため、だいぶ不親切です。
申し訳ございません。


## 変更点

ソースコードの変更は、Dockerfileのみです。
このシリーズで構築した環境では、Dockerfileのパス(/compose/web_hoge)とソースコード(/src/hogeや/src/vendor)が離れており、docker-composeのvolumesを使ってコンテナにソースを組み込んでいます。

```text:ソースツリー
$(PROJECTROOT)
  /compose
    /web_hoge
      Dockerfile
  /src
    /hoge
    /vendor
```

```yml:docker-compose.yml
  web_hoge:
    build:
      context: ./web_hoge
    volumes:
      - ../src/hoge:/var/www/hoge
      - ../src/vendor:/var/www/vendor
      - ../credentials:/usr/local/etc/myapp/credentials
```

### Dockerfileの変更

Dockerfileでは、作業ディレクトリよりも親への参照ができません。(例: COPY ../../src/web_hoge など)
したがって、 `docker build -f compose/web_hoge/Dockerfile .` のように-fオプションで子ディレクトリ内のDockerfileを指定することになります。これに伴い、Dockerfile内のファイル参照のパスも変更されます。
具体的な変更箇所は、COPYコマンドのコピー元が$(PROJECTROOT)からの相対パスで記述されるようになったことと、docker-composeのvolumesの代わりにソースをコピーするようになったことです。

```dockerfile:Dockerfile
FROM alpine:3.10.2

ARG environment

..(略)..

# supervisord
RUN mkdir -p /etc/supervisor.d
COPY compose/web_hoge/supervisord.conf /etc/supervisord.conf

# settings
COPY compose/web_hoge/settings-${environment}.yml /usr/local/etc/myapp/settings.yml

# PHP
COPY compose/web_hoge/php.ini /php.ini
COPY compose/web_hoge/php-fpm.conf /php-fpm.conf
COPY compose/web_hoge/php-fpm-supervisor.conf /php-fpm-supervisor.conf
RUN cp /php-fpm.conf /etc/php7/php-fpm.d/z-php.conf \
  && cp /php.ini /etc/php7/conf.d/config.ini \
  && cp /php-fpm-supervisor.conf /etc/supervisor.d/php-fpm.conf

# Nginx
COPY compose/web_hoge/nginx.conf /nginx.conf
COPY compose/web_hoge/nginx-server.conf /nginx-server.conf
COPY compose/web_hoge/nginx-supervisor.conf /nginx-supervisor.conf
RUN cp /nginx.conf /etc/nginx/nginx.conf \
  && cp /nginx-server.conf /etc/nginx/conf.d/default.conf \
  && cp /nginx-supervisor.conf /etc/supervisor.d/nginx.conf

..(略)..

# ソース追加(以下三行追加)
COPY src/hoge /var/www/hoge
COPY src/vendor /var/www/vendor
COPY credentials /usr/local/etc/myapp/credentials

EXPOSE 3128

ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
```

## AWS

### ECR

今回はDockerリポジトリとして、Amazon ECRを使用することにします。
ECRサービスメニューから、リポジトリの作成を選びます。

|パラメータ|値|
|:--|:--|
| リポジトリ名 | hoge-repo |  

このリポジトリのURIは、`<<AWS-ACCOUNT-ID>>.dkr.ecr.ap-northeast-1.amazonaws.com/hoge-repo` になります。

### ビルド

適当なEC2上でビルドすることにします。なお、以下の条件が整っているものとします。

* aws configureで接続設定済み (regionはap-northeast-1)
* dockerコマンドがインストール済み
* gitコマンドやファイルコピーなどで最近のソースが取得可能
* ECRへの操作権限を持っている

```shell
# $(aws ecr get-login --no-include-email)
# ACCOUNT_ID=$(aws sts get-caller-identity | grep Account | grep -oE '[0-9]+')
# cd <<PROJECT_ROOT>>
# docker build -t hoge-repo:latest --build-arg environment=devaws -f compose/web_hoge/Dockerfile .
# docker tag hoge-repo:latest $ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/hoge-repo:latest
# docker push $ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/hoge-repo:latest
```
 
### IAM(ロールの作成)

Dockerコンテナの持つロールを定義しておきます。
RDS、DynamoDBなどにアクセスすることからそれらを操作する権限が必要です。
(接続確認を優先したため、権限が大きい(FullAccess)です! 本番注意)。

|パラメータ|値|
|:--|:--|
| ロール名 | hoge-ecs-user |  
| AWSサービス | Elastic Container Service Task |
| ポリシー | AmazonRDSFullAccess, AmazonDynamoDBFullAccess, AmazonS3FullAccess |


### EC2(ターゲットグループ)

ロードバランサの設定前に、そのターゲットグループを設定しておきます。
ポートは、nginxへのアクセスポートである3128です。

|パラメータ|値|
|:--|:--|
| 名前 | hoge-target |
| ターゲットの種類 | インスタンス |
| ポート | 3128 | 
| ヘルスチェック | http /favicon.ico | 


### EC2(セキュリティグループ)

以下の二種類のアクセスに対するセキュリティグループを定義します。

* ロードバランサへのアクセス

外部からのhttp(s)のアクセスです。

|パラメータ|値|
|:--|:--|
| 名前 | hoge-http-sg |
| インバウンド | 80(0.0.0.0/0), 443(0.0.0.0/0) | 


* Dockerコンテナへのアクセス

ECSのインスタンスへ直接SSHアクセスするための定義(ポート22)と、ALBからのアクセス(動的ポートマッピングを使用するのでその範囲)。

|パラメータ|値|
|:--|:--|
| 名前 | hoge-instance-sg |
| インバウンド | 22(SSH接続可能なIP), 32768-65535(ロードバランサのVPC) |


### AWS Certificate Manager

SSLを使う場合は、こちらで証明書を発行します。発行後、承認されるまでは最大72時間かかるので注意が必要です
(詳細は本筋と外れるので省略)。

### EC2(ロードバランサ)

ALBの設定を行います。

|パラメータ|値|
|:--|:--|
| 名前 | hoge-alb |
| スキーム | インターネット向け |
| リスナー | 80, 443(証明書が必要です) |
| AZ | ap-northeast-1a, 1c, 1d全部 |
| セキュリティグループ | hoge-http-sg |
| ターゲットグループ | hoge-target |

### ECS(クラスター)

ECS用のクラスターを作成します。なお、この設定を行った際に、自動的にEC2(Auto Scalingグループ)とEC2(起動設定)が作成されます。

|パラメータ|値|
|:--|:--|
| クラスターテンプレート | EC2 Linux + ネットワーキング |
| クラスター名 | hoge-cluster |
| プロビジョニングモデル | オンデマンド |
| EC2インスタンス | t2.micro |
| インスタンス数 | 1 |
| キーペア | 直接インスタンスにアクセスする場合は設定する。本番環境等ではなし |
| VPC | 既存のVPC |
| サブネット | ap-northeast-1a, 1c, 1dの全部 |
| セキュリティグループ | hoge-instance-sg |
| コンテナインスタンスIAMロール | ecsInstanceRole(自動生成される) |


### EC2(起動設定)

クラスターを生成したときに自動生成された起動設定をコピーし、修正します。
ユーザーデータでは、サーバに必要な設定の変更なども行います (ここに示しているのはあくまでサンプルです)。
クラスター名を変更する場合は、ユーザーデータ内のECS_CLUSTERを変えないと動作しないので注意です(はまったことあります・・)。

|パラメータ|値|
|:--|:--|
| 名前 | hoge-launch-config |
| IAMロール | ecsInstanceRole |
| モニタリング | 詳細モニタリングを無効に |
| ユーザーデータ | (下記参照) |
| IPアドレスタイプ | 直接インスタンスにアクセスする場合は割り当てる。本番環境等では割り当てない |

```text:ユーザーデータ
#!/bin/bash
cat <<'ECS_EOF' >> /etc/ecs/ecs.config
ECS_CLUSTER=hoge-cluster
ECS_BACKEND_HOST=
#ECS_LOGLEVEL=debug
ECS_EOF

cat <<'SYS_EOF' >> /etc/sysctl.conf
net.core.somaxcon=65535
SYS_EOF

sysctl -p /etc/sysctl.conf

cat <<'FLIMIT_EOF' > /etc/security/limits.d/filelimits.conf
* soft nofile 32768
* hard nofile 32768
FLIMIT_EOF

ulimit -n 32768
```

### EC2(Auto Scalingグループ)

クラスターを生成したときに自動生成されたオートスケールグループ設定をコピーし、修正します。

|パラメータ|値|
|:--|:--|
| グループ名 | hoge-asg |
| 起動設定 | hoge-launch-config |

作成した後、ターゲットグループを"hoge-target"に指定します。


### ECS(タスク定義)

ECSで生成されたインスタンスにDockerコンテナを載せるためのタスク定義を行います。
下のメモリ・CPU数は、t2.micro(vCPU=1, memory=約1000MB)で二個のタスクが動作する設定です。

|パラメータ|値|
|:--|:--|
| 起動タイプ | EC2 |
| タスク定義名 | hoge-task |
| タスクロール | hoge-ecs-user |
| ネットワークモード | &lt;default&gt; |
| タスク実行ロール | ecsTaskExecuteRole(自動生成される) |
| タスクメモリ | 400 (本当はちゃんと設定する) |
| タスクCPU | 512 (本当はちゃんと設定する) |
| コンテナ名 | hoge-container |
| イメージ | &lt;&lt;AWS-ACCOUNT-ID>>.dkr.ecr.ap-northeast-1.amazonaws.com/hoge-repo:latest |
| メモリ制限 | ハード/400MB, ソフト/300MB (本当はちゃんと設定する) |
| CPUユニット数 | 512 (本当はちゃんと設定する) |
| ポートマッピング | ホスト(0) : コンテナ(3128) (自動ポートマッピングを使用するのでホストポートは0) |


### ECS(サービス追加)

タスクを元にECSにサービスを追加します。

|パラメータ|値|
|:--|:--|
| 起動タイプ | EC2 |
| タスク定義 | hoge-task |
| クラスタ | hoge-cluster |
| サービス名 | hoge-service |
| サービスタイプ | REPLICA |
| タスクの数 | 1 |
| デプロイ | ローリングアップデート |
| ロードバランサー | hoge-alb |
| サービス用IAMロール | AWSServiceRoleForECS |
| ロードバランス用コンテナ | hoge-container:0:3128 |
| サービスの検出の統合の有効性 | 無効 |
| ロードバランス用のコンテナターゲット | hoge-target |
| AutoScaling | 無効 |


## 接続確認

ここまで完了すれば、うまく動いている(はず)です。
うまくいっていれば、以下のURLにアクセスすれば"BEFORE:Hello, World!:AFTER"が表示されます。
SSL接続するケースや、独自ドメインを使用するケースはここでは割愛します。

`http://hoge-alb-<<ALBに割り当てられたID>>.ap-northeast-1.elb.amazonaws.com/`