AWS FargateでEFSを使う - EC2起動タイプからの移行 (bridge → awsvpc)

2020/4/8, ついにFargateでEFSが使えるようになりました！

> - [Amazon ECS and AWS Fargate support for Amazon EFS File Systems now generally available](https://aws.amazon.com/jp/about-aws/whats-new/2020/04/amazon-ecs-aws-fargate-support-amazon-efs-filesystems-generally-available/)
> - [Amazon Elastic Container Service now supports Amazon EFS file systems \| AWS News Blog](https://aws.amazon.com/jp/blogs/aws/amazon-ecs-supports-efs/)

この記事では、Fargateサポートを待ちつつも無念の『ECS on EC2 + EFS』構成を取っていた環境を移行するまでにハマった落とし穴を紹介していきます。

Fargateに移行してしまえば、こんな悩みは解消されるはず。

- コンテナインスタンスのパッチ当てどうしよう...
- 定期的にインスタンス入れ替える？
- EFS以外のディスクは揮発性でいいのに、EBSの料金とか空き領域監視とかついてくる…
- デプロイするときにコンテナインスタンスを一時的に増やすの面倒
- スケールインとかインスタンス入れ替え時に乗ってるタスクが巻き添えにならない？

## 参考記事

新規でFargateを作成する場合や、全体像を知って動かしてみたい方は以下の記事が参考になります。

> - [【全世界待望】Fargateから共有ファイルストレージのEFSが使えるようになりました！ \| Developers\.IO](https://dev.classmethod.jp/articles/efs-fargate/)
> - [FargateからのEFSマウントを試す \- Qiita](https://qiita.com/cumet04/items/4b5cbdb29d76c72eb26c)

## ネットワークモードが`bridge`だった

ECSのEFSサポートがプレビュー版として使用可能になった2020/1/17から今日までの間、ECSタスク定義の`efsVolumeConfiguration`を使用したEFSマウントはネットワークモードが`awsvpc`の場合使用できませんでした。このため、移行前はネットワークモードを`bridge`で構成していました。

[EFS ファイルシステムのための Amazon ECS プレビューサポートが利用可能に](https://aws.amazon.com/jp/about-aws/whats-new/2020/01/amazon-ecs-preview-support-for-efs-file-systems-now-available/)

なお、`efsVolumeConfiguration`以外にもEFSのマウント方法はいくつかあります。下記の方法の場合は`awsvpc`モードでも使用可能です。ただ、ECSコンテナインスタンスへの依存ができてしまうためあまりやりたくありませんでした。

- バインドマウント方式: ECSコンテナインスタンス(EC2)でNFSを使ってマウントしておき、マウント先のパスをバインドマウントでコンテナに見せる
    - [チュートリアル: Amazon ECS での Amazon EFS ファイルシステムの使用 \- Amazon Elastic Container Service](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/using_efs.html)
    - [バインドマウント \- Amazon Elastic Container Service](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/bind-mounts.html)
    - [コンテナインスタンスのユーザーデータ設定スクリプトの例 \- Amazon Elastic Container Service](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/example_user_data_scripts.html#alinux-efs)
- Dockerボリューム方式: `dockerVolumeConfiguration`でNFSとしてEFSをマウントして使う
    - [Amazon ECS で Amazon EFS を使用して Docker ボリュームを作成する](https://aws.amazon.com/jp/premiumsupport/knowledge-center/ecs-create-docker-volume-efs/)

## 起動タイプをFargateに、ネットワークモードを`awsvpc`に変更

起動タイプをEC2からFargateに変えると、Fargateはネットワークモード`awsvpc`のみをサポートしているため、必然的に`bridge`から変更する必要があります。今回のECSでのEFSサポートの発表の中で、明確に`awsvpc`をサポートするようになったとの記載はありませんでしたが、問題ないようです。

> タスクのコンテナで使用する Docker ネットワーキングモード。Fargate 起動タイプを使用している場合、awsvpc ネットワークモードが必要です。

[タスク定義パラメータ \- Amazon ECS](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/task_definition_parameters.html#network_mode)

### セキュリティグループの設定先変更

ECSコンテナインスタンス(EC2)に設定していたセキュリティグループを、ECSサービスのセキュリティグループに変更します。設定内容は環境によりますが、今回は変更なしでそのまま付け替えました。

### ECSサービス定義に`networkConfiguration`を追加

```yaml
networkConfiguration:
  awsvpcConfiguration:
    subnets:
      - subnetid-1
    securityGroups:
      - sg-1
```

### FireLensの環境変数`FLUENT_HOST`, `FLUENT_PORT`が挿入されなくなる

これはハマりました。ドキュメントには何も書いていませんが、`awsvpc`モードの場合これらの環境変数は挿入されないようです。

AWSさん、これはどこかに書いておいてください…。

> - [カスタムログルーティング \- Amazon ECS](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/using_firelens.html#firelens-fluent-logger)

`awsvpc`の場合、同一タスク内の通信は`localhost`で可能なため、FireLensのようにサイドカー構成の場合は確かに環境変数で明示する必要はありません。でも、これに思い当たるまで時間がかかりました。`bridge`のときはあったんだもの…。

> 同じタスクに属するコンテナが、localhost インターフェイス経由で通信できるようになります。

[タスクネットワーキングと awsvpc ネットワークモード \- Amazon ECS](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/task-networking.html)

なお、普通にfluentdなどをサイドカーでなく立ち上げていた場合は、サービスディスカバリを考える必要があります。

というわけで、タスク定義にfirelensを動かすコンテナにポートを定義しておきます。

```yaml
    portMappings:
      - containerPort: 24224
        protocol: tcp
```

元々、環境変数`FLUENT_HOST`, `FLUENT_PORT`を読んでアプリの設定を生成していたので、環境変数を自分で定義しておきます。

```yaml
     environment:
     - name: FLUENT_HOST
       value: localhost
     - name: FLUENT_PORT
       value: "24224"
```

### ALBターゲットグループのターゲットタイプ変更は再作成が必要

ALBターゲットグループのターゲットタイプは`bridge`ネットワークモードの場合は`instance`を指定します。`awsvpc`モードの場合は`ip`に変える必要があります。これに伴い、ALBターゲットグループの再作成が必要になり、さらに、CloudFormationでターゲットグループの`Name`を指定している場合はリプレースに失敗するので名称を変える必要があります。RDS同様、`Name`は未指定でもいいかもしれません。

### ECSサービス削除後、ステータスが`INACTIVE`になるまで待つ必要がある

ECSサービスを作成済みの場合、後からALBターゲットグループを変更できないので、ECSサービスも再作成が必要になります。awscliで`delete-service`しただけではすぐに消えないので、ステータスが`INACTIVE`になるまで待ってから再作成する必要があります。

```sh
echo "delete service"
aws ecs delete-service --cluster hoge-ECSCluster --service hoge-ECSService

echo "wait until status is INACTIVE"
aws ecs wait services-inactive --cluster hoge-ECSCluster --service hoge-ECSService
```

### ALBからの通信ポートが変わる

ネットワークモード`bridge`の場合、ECSコンテナインスタンスがALBからの通信を受けるポートはエフェメラルポート(32768-65535)ですが、`awsvpc`の場合はコンテナポートそのものになります。

```diff
-  ECSContainerInstanceSecurityGroup:
+  ECSTaskSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: hoge-ECSContainerInstanceSecurityGroup
      GroupDescription: ECS Allowed Ports
      SecurityGroupIngress:
        # ALBからの通信許可 (ephemeral port)
        - IpProtocol: tcp
-          FromPort: 32768
+          FromPort: 8080
-          ToPort: 65535
+          ToPort: 8080
          SourceSecurityGroupId: !ImportValue hoge-ALBSecurityGroup
      VpcId: !ImportValue hoge-VPC:Id
```

### SecretsManagerのJSONキーが指定できない

これも気付かずハマりました。シークレットをJSONで登録し、そのキーをタスク定義の`secrets`に指定して環境変数を挿入していた場合、シークレットをそれぞれバラして登録し直すか、コンテナ内で`jq`等で読み出す必要があります。今回はそんなに数も多くなかったのでバラしました。

> 現時点（2020年2月26日）では、Fargateはこの機能未サポートです。
> 
> For tasks that use the Fargate launch type, the following should be considered:
> 
> It is only supported to inject the full contents of a secret as an environment variable. Specifying a specific JSON key or version is not supported at this time.
> 引用：Specifying Sensitive Data Using Secrets Manager - Amazon Elastic Container Service

[ECSがSecrets ManagerのバージョンやJSONキーに対応。機密情報管理がめっちゃ便利になりました\! \| Developers\.IO](https://dev.classmethod.jp/articles/ecs-support-secrets-manager-version/)

## ECSサービス定義で `platformVersion` を指定する必要がある

FargateでEFSを使うためには`1.4.0`以上が必要です。未指定のデフォルト値`LATEST`が`1.3.0`を向いています。これはこちらで紹介されていました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">そしてもう一つ重要なお知らせです.<br>これまで ECS/Fargate をご利用いただいていた方は、PV &quot;LATEST&quot; というタスク起動時に常に最新の PV を自動的に利用するエイリアスがあったことをご存知かもしれませんが、PV 1.4 を機に &quot;LATEST&quot; は即座に最新バージョンを向く挙動ではなくなります(続. (14/19)</p>&mdash; ポジティブな Tori (@toricls) <a href="https://twitter.com/toricls/status/1247975937074843648?ref_src=twsrc%5Etfw">April 8, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">続き) &quot;LATEST&quot; が PV 1.4 を向くようになるのは PV 1.4 のリリースからおよそ1ヵ月程度経った5月の中頃になる予定です. 今回のこの &quot;LATEST&quot; モデルの変更により、お客様は自身が利用するプラットフォームバージョンと移行のタイミングをこれまで以上に明示的に調整できるようになります(続. (15/19)</p>&mdash; ポジティブな Tori (@toricls) <a href="https://twitter.com/toricls/status/1247975938492493824?ref_src=twsrc%5Etfw">April 8, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## ECSタスク定義の無効なパラメータを消す

タスク定義、サービス定義のドキュメントを見直しておきましょう。

> 次のタスク定義パラメータは Fargate 起動タイプでは無効です。
>
> ...

[タスク定義の考慮事項 \- Amazon ECS](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/fargate-task-defs.html)

上限値が違うパラメータもあります。

```diff
-    stopTimeout: 300
+    stopTimeout: 120 # less than 120
```

## まとめ

ネットワークモードを理解していればそれほど難しくないようです。同じECSとはいえ、起動タイプがEC2とFargateではサポートされている機能にそれなりに違いがあるため、注意が必要です。この機会にパラメータの見直しをしておくのもいいと思います。