【AWS】public subnet を使用せずに private subnet 内で ECR と ECS の接続を設定してみた

## 0. 初めに
ある日、 ECS から ECR への接続を private subnet 内で完結させる必要が生じたため、備忘録としてここに記しておこうと思います。

基本的にはこちらの記事[^1]を参照させていただきました。

[^1]:今回の記事の執筆にあたって参考にさせていただきました。（参考文献にも記載）
https://qiita.com/KurokawaKouhei/items/9389f36420ebaa0390bd

今回の要件に必要なリソースの作成から、作成したコンテナへ、ローカルの AWS CLI から接続してみるところまでを実施します。

全体の流れとしては次の通りです。

1. VPCエンドポイント及びセキュリティグループの作成
2. IAM Policy, IAM Roleの作成
3. ECR, ECSの作成
4. 最後に

## 1. VPC, VPCエンドポイント及びセキュリティグループの作成

まず今回の条件である「public subnet を使用せずに private subnet 内で ECR と ECS の接続を構築する」を満たすためには下記の３つの VPC エンドポイントを用意する必要があります。

・com.amazonaws.region.ecr.dkr （インターフェース）
・com.amazonaws.region.ecr.api（インターフェース）
・com.amazonaws.region.s3（ゲートウェイ）

また、今回は作成したコンテナへ AWS CLI から接続するため、次のエンドポイントも必要になります。

・com.amazonaws.us-east-1.ssmmessages （インターフェース）

:::note info
本記事では com.amazonaws.region.s3 は VPC 作成時にまとめて作成するため、ルーティング等の詳細は省きます。
また、なぜ com.amazonaws.region.s3 が必要なのかは[^1]に詳しく記載されています。
:::

これら４つのエンドポイントを作成する前に、VPCとセキュリティグループを作成していきます。
また、今回はあくまで ECR と ECS の接続に重点を置いているため CIDR 等の設定については説明を省略します。

![スクリーンショット 2025-01-13 010208.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/870f9fd5-4222-1aa6-506f-b76cb961138f.png)

次の VPC endpoints の項目を S3 Gateway にしておくことで、作成とルーティングをしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/78b82c1f-083e-eaad-6c94-6fd34bc53c10.png)

これでメインとなる VPC の作成が完了したため、セキュリティグループの作成に移ります。

最初に ECS 用のセキュリティグループを作成します。

![スクリーンショット 2025-01-13 013750.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/c6eba179-8f91-fac0-bf35-49c2a41b3042.png)

ECR との接続用にアウトバウンドのみ許可しておきます。
今回は簡単のためにインバウンドルールを削除しています。

次に４つのエンドポイント用のセキュリティグループを作成します。

![スクリーンショット 2025-01-13 014044.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/353a7ed2-8962-6a5a-51ee-b4733037a99d.png)

ECSからの通信はhttpsの443ポートが使用されるため、インバウンドを許可しておきます。
今回は簡単のためにアウトバウンドルールを削除しています。
これでセキュリティグループの作成は完了です。

最後に VPC エンドポイントを作成していきます。
Create endpoint から AWS service の項目を選択し、 ecr.api の項目を選びます。

![スクリーンショット 2025-01-13 010922.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/3ddd4112-9f06-6974-245a-e41f32fbaf1a.png)

Network settings の項目 vpc は先ほど作成したものを選択し、subnet も同様に先ほど作成した private subnet を選択します。

![スクリーンショット 2025-01-13 011413.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/04c29ece-75f2-5044-35f9-f537841c97b7.png)

:::note warn
まれに subnet の欄に作成した subnet が反映されていない場合があります。
その際はリロードや時間を置いたりすると反映される場合があります。
:::

セキュリティグループは先ほど作成したエンドポイント用のものを選択します。

![スクリーンショット 2025-01-13 014754.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/30cdbca6-c646-a705-c1f9-0150cf56b602.png)

同様に dkr.api, ssmmessages のエンドポイントを作成しておきます。

## 2. IAM Policy, IAM Roleの作成
次に ecs execution コマンドを実行するのに必要な IAM Policy と Role を作成していきます。
必要なものは次の通りです。

IAM Policy
・ssmmessage full access

```ssmmessage-fullaccess.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssmmessages:CreateControlChannel",
                "ssmmessages:CreateDataChannel",
                "ssmmessages:OpenControlChannel",
                "ssmmessages:OpenDataChannel"
            ],
            "Resource": "*"
        }
    ]
}
```

こちらの json をコピペすることで作成可能です。

IAM Role
・ssmmessage full access

こちらは上記の policy をアタッチして作成することができます。
今回は ecs-exec という名前でロールを作成したものとして進めます。

## 3. ECR, ECSの作成
まず ECR のプライベートリポジトリを作成します。

![スクリーンショット 2025-01-13 103400.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/1f8f6e9a-5528-caaf-3cb4-49af008b1a66.png)

そして今回作成したプライベートリポジトリへ DockerImage を push していきます。
今回は特段こだわりがないため nginx を ubuntu 上で動かすようなものを使用します。

:::note warn
あまりにも適当な Dockerfile を使用してしまうと、コンテナが起動後すぐに終了してしまい、サービスが立ち上がらなくなってしまうので気を付けましょう（１敗）
:::

```ubuntu.dockerfile
FROM ubuntu:latest
 
RUN apt-get update && apt-get install -y nginx
 
EXPOSE 80
ENTRYPOINT ["nginx", "-g", "daemon off;"]
```

そしてこちらの Dcokerfile をローカル環境で build し、先ほど作成した ECR へ push します。

```push-comand.sh
#最初に ECR との認証を通しておく
aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <aws_account_id>.dkr.ecr.<region>.amazonaws.com

#先ほどの Dockerfile を build しておく
docker buuild -t ecr-ecs .

#build した Dockerfile をタグ付けする
docker tag ecr-ecs <aws_account_id>.dkr.ecr.<region>.amazonaws.com/ecr-ecs:latest  

#ECR へ push する
docker push <aws_account_id>.dkr.ecr.<region>.amazonaws.com/ecr-ecs:latest
```

これで ECR 側の準備は完了になります。

次にECS側の作成に移ります。
最初にクラスターの作成をします。今回は CloudWatch の設定を切っていますが、必要な場合は CloudWatch のエンドポイントを立てる必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/5ff5a68e-c44a-5c99-65c2-675ca9d3c493.png)

次にタスク定義を作成します。
この時にタスクロールを ecs-xec、タスク実行ロールを ecsTaskExecutionRole に設定しておきます。
また、今回は CloudWatch の設定を切っておきます。

![imagepng](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/366cfad9-d5f1-17fb-f906-0dd9660c25b5.png)

![スクリーンショット 2025-01-13 112053.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/d0f17890-23c4-c760-dd0d-b41a5f3cc8c6.png)

タスク定義の作成が完了したので、サービスの作成に移ります。
先ほど作成したタスク定義を使用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/ea1f32e2-5728-5d1f-d27b-13ad5c4a8060.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/0792b7e9-7f79-3ca9-4582-7f224b752f8e.png)

Networking の設定は事前に作成しておいた ECS 用のセキュリティグループを使用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/6bcf12e7-81ea-7c52-9237-409f3f6382cd.png)

これでサービスの作成が完了し、少し時間が経過した後にコンテナがデプロイされます。

最後に、作成したコンテナに対してローカル環境からアクセスすることができることを確認していきます。

AWS CLI と SSM がローカル環境にインストールされていることを前提としています。
それぞれの入れ方はこちらの公式サイト[^2][^3]を参照してください。

[^2]:AWS CLI のインストール方法
https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html
[^3]:SSM のインストール方法
https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/plugin-version-history.html

最初に aws ecs execute-command を利用できるように、有効化します。

```exec-enable.sh
aws ecs update-service --region <region> --cluster ecs-ecr-cls --service ecs-ecr-service --enable-execute-command 
```

:::note info
このときに表示される json の内容を確認して、次のように ExecuteCommand が有効化されていることを確認してください。
        "enableExecuteCommand": true,
:::

ExecuteCommand が有効化されたことを確認したら、タスクを再デプロイします。

```new-deployment.sh
aws ecs update-service --region <region> --cluster ecs-ecr-cls --service ecs-ecr-service --task-definition ecs-ecr-taskdef:1 --force-new-deployment
```

次に aws ecs execute-command を利用して、稼働しているコンテナに入ってみます。

```ecs-exec.sh
aws ecs execute-command --region <region> --cluster ecs-ecr-cls --task <task_ID> --container <container_name> --interactive --command "/bin/bash"
```

:::note info
下図の赤枠で囲われている部分の値を使用します。
TaskをクリックしてコピーするとARNがコピーされることに注意しましょう（１敗）
![スクリーンショット 2025-01-13 120345.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/ba7c9186-f831-f71f-739c-c94836b62cf8.png)
:::

無事に interactive mode で bash が表示されていれば一連の作業が完了です。

![スクリーンショット 2025-01-13 121236.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984186/c900e202-2499-49b1-341a-670fad0fd128.png)

## 4. 最後に
private subnet のみで ECS と ECR の接続を一から作成している記事がなかなか見当たらなかったので、記事を執筆してみました。
レイアウトなどの見やすさへ直結する部分に苦戦しましたが、なんとか書き終えることができました。
CloudWatch や Route 53 、ALB を追加すれば、外部向けのサービスをある程度セキュアに作成できたりするので、AWS を使用すれば気軽にインフラを作成することができるなぁと改めて実感しました。
private subnet のみで ECS と ECR の接続する場合の最小限の手順を用意することは出来たと思っていますので、どなたかのお役に立てば幸いです。

## 参考文献
https://qiita.com/KurokawaKouhei/items/9389f36420ebaa0390bd

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/plugin-version-history.html

https://qiita.com/okubot55/items/b1fb07b2de08c354275b