AWS FargateではVPC Endpointを使う

AmazonECRからコンテナイメージをpullし、private-subnetにおいてFargateでホストされるAmazonECSタスクを運用します。コンテナイメージをpullする際は、インターフェイスタイプのVPCエンドポイントを使用するようにし、それ以外はNatGatewayを使用することで通信費を大幅に低減させつつ、セキュリティを高めることができるのでおすすめです。
ちなみにNatGatewayを使用する場合、後で請求を見て驚かないように下記3点のコストを意識する必要があります。
だとしてもSquidを構築して運用する気にはならない...
- 時間単位料金
- データ処理料金
- 転送料金：インターネットへの通信、NatGatewayとEC2インスタンスが異なるアベイラビリティーゾーンにある場合に発生
冗長構成で1GB使用する想定だと **$90.64**となります。

```bash:runningcost
730 時間 (1 か月) x 0.062 USD = 45.26 USD (ゲートウェイの 1 時間あたりの使用料金)
1 GB/月 x 0.062 USD = 0.06 USD (NAT ゲートウェイのデータ処理料金)
45.26 USD + 0.06 USD = 45.32 USD (NAT ゲートウェイ処理および月間使用時間)
2 NAT ゲートウェイ x 45.32 USD = 90.64 USD (NAT ゲートウェイの使用料金とデータ処理コストの合計額)
NAT ゲートウェイの使用料金とデータ処理料金の合計額 (毎月): 90.64 USD
```

ややこしいのですが、 **FargateでホストされるAmazonECSタスクでは、下記のAmazonECSインターフェイスVPCエンドポイントは必要ありません。**
1. com.amazonaws.region.ecs-agent
2. com.amazonaws.region.ecs-telemetry
3. com.amazonaws.region.ecs


Fargateを使用する場合、下記のエンドポイントが必要です。
- プラットフォームバージョン1.3.0以前
  1. com.amazonaws.region.ecr.dkr
  2. com.amazonaws.ap-northeast-1.s3

- プラットフォームバージョン1.4.0以降（４〜６は用途では必要）
  1. com.amazonaws.ap-northeast-1.ecr.dkr
  2. com.amazonaws.ap-northeast-1.ecr.api
  3. com.amazonaws.ap-northeast-1.s3
  4. com.amazonaws.ap-northeast-1.logs(CloudWatchLogsを使用)
  5. com.amazonaws.ap-northeast-1.ssmmessages(ECSExecを使用)
  6. com.amazonaws.ap-northeast-1.kms(ECSExecを使用)

<details><summary>data source</summary>

```hcl:main.tf
data "aws_vpc" "example_function" {
  tags = {
    Name = "example-function"
  }
}

data "aws_subnet" "example_function_private_1a" {
  filter {
    name   = "tag:Name"
    values = ["example-function-private-1a"]
  }
}

data "aws_subnet" "example_function_private_1c" {
  filter {
    name   = "tag:Name"
    values = ["example-function-private-1c"]
  }
}

data "aws_subnet" "example_function_public_1a" {
  filter {
    name   = "tag:Name"
    values = ["example-function-public-1a"]
  }
}

data "aws_subnet" "example_function_public_1c" {
  filter {
    name   = "tag:Name"
    values = ["example-function-public-1c"]
  }
}

data "aws_route_table" "example_function_private_1a" {
  vpc_id = data.aws_vpc.example_function.id
  filter {
    name   = "tag:Name"
    values = ["example-function-private-1a"]
  }
}

data "aws_route_table" "example_function_private_1c" {
  vpc_id = data.aws_vpc.example_function.id
  filter {
    name   = "tag:Name"
    values = ["example-function-private-1c"]
  }
}
```
</details>

<details><summary>security_group</summary>
VPCEndpointにアタッチしたセキュリティグループでは、VPCプライベートサブネットから、TCP:443で着信接続を許可する

```hcl:security_group.tf
resource "aws_security_group" "example_function_ecr" {
  vpc_id      = data.aws_vpc.example_function.id
  name        = "example-function-ecr"
  description = "example-function-ecr traffic"

  ingress {
    from_port   = "443"
    to_port     = "443"
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name        = "example-function-ecr"
    Service     = "example-function"
  }
}
```
</details>


<details><summary>variables</summary>

```hcl:variables.tf
variable "example_function_ecr_service_names" {
  type = map(any)

  default = {
    example-function-ecr-dkr = {
      service_name = "com.amazonaws.ap-northeast-1.ecr.dkr"
    }
    example-function-ecr-api = {
      service_name = "com.amazonaws.ap-northeast-1.ecr.api"
    }
  }
}

variable "example_function_ecr_ssm_kms_service_names" {
  type = map(any)

  default = {
    example-function-ecr-ssm = {
      service_name = "com.amazonaws.ap-northeast-1.ssmmessages"
    }
    example-function-ecr-kms = {
      service_name = "com.amazonaws.ap-northeast-1.kms"
    }
  }
}
```
</details>

<details><summary>vpc_endpoint</summary>

- VPCEndpoint policyは、明示的に定義しない場合、サービスへのフルアクセスを許可するデフォルトのポリシーがAWSによって自動的にアタッチされます
- AmazonECRに最低限のAmazonS3バケットアクセス許可のみを許可
```
          "Action" : [
            "s3:GetObject",
            "s3:GetBucketLocation"
          ],
          "Effect" : "Allow",
          "Resource" : [
            "arn:aws:s3:::ap-northeast-1-starport-layer-bucket",
            "arn:aws:s3:::ap-northeast-1-starport-layer-bucket/*",
            "arn:aws:s3:::example-function-front",
            "arn:aws:s3:::example-function-front/*",
           ]
```
```hcl:vpc_endpoint.tf
resource "aws_vpc_endpoint" "example_function_ecr_cw" {
  vpc_id            = data.aws_vpc.example_function.id
  service_name      = "com.amazonaws.ap-northeast-1.logs"
  vpc_endpoint_type = "Interface"

  security_group_ids = [
    aws_security_group.example_function_ecr.id,
  ]

  private_dns_enabled = true

  tags = {
    Name        = "example-function-ecr-cw"
    Service     = "example-function"
  }
}

resource "aws_vpc_endpoint_subnet_association" "sn_private_1a_cw" {
  vpc_endpoint_id = aws_vpc_endpoint.example_function_ecr_cw.id
  subnet_id       = data.aws_subnet.example_function_private_1a.id
}

resource "aws_vpc_endpoint_subnet_association" "sn_private_1c_cw" {
  vpc_endpoint_id = aws_vpc_endpoint.example_function_ecr_cw.id
  subnet_id       = data.aws_subnet.example_function_private_1c.id
}

resource "aws_vpc_endpoint" "example_function_ecr_s3" {
  vpc_id            = data.aws_vpc.example_function.id
  service_name      = "com.amazonaws.ap-northeast-1.s3"
  vpc_endpoint_type = "Gateway"

  policy = jsonencode(
    {
      "Statement" : [
        {
          "Sid" : "Access-to-specific-bucket-only",
          "Principal" : "*",
          "Action" : [
            "s3:GetObject",
            "s3:GetBucketLocation"
          ],
          "Effect" : "Allow",
          "Resource" : [
            "arn:aws:s3:::ap-northeast-1-starport-layer-bucket",
            "arn:aws:s3:::ap-northeast-1-starport-layer-bucket/*",
            "arn:aws:s3:::example-function-front",
            "arn:aws:s3:::example-function-front/*",
           ]
        }
      ]
    }
  )
  tags = {
    Name        = "example-function-ecr-s3"
    Service     = "example-function"
  }
}

resource "aws_vpc_endpoint_route_table_association" "sn_private_1a" {
  vpc_endpoint_id = aws_vpc_endpoint.example_function_ecr_s3.id
  route_table_id  = data.aws_route_table.example_function_private_1a.id
}

resource "aws_vpc_endpoint_route_table_association" "sn_private_1c" {
  vpc_endpoint_id = aws_vpc_endpoint.example_function_ecr_s3.id
  route_table_id  = data.aws_route_table.example_function_private_1c.id
}

resource "aws_vpc_endpoint" "example_function_ecr" {
  for_each = var.example_function_ecr_service_names

  vpc_id            = data.aws_vpc.example_function.id
  service_name      = each.value.service_name
  vpc_endpoint_type = "Interface"

  security_group_ids = [
    aws_security_group.example_function_ecr.id,
  ]

  private_dns_enabled = true

  tags = {
    Name        = each.key
    Service     = "example-function"
  }
}

resource "aws_vpc_endpoint_subnet_association" "sn_private_1a" {
  for_each = var.example_function_ecr_service_names

  vpc_endpoint_id = aws_vpc_endpoint.example_function_ecr[each.key].id
  subnet_id       = data.aws_subnet.example_function_private_1a.id
}

resource "aws_vpc_endpoint_subnet_association" "sn_private_1c" {
  for_each = var.example_function_ecr_service_names

  vpc_endpoint_id = aws_vpc_endpoint.example_function_ecr[each.key].id
  subnet_id       = data.aws_subnet.example_function_private_1c.id
}




resource "aws_vpc_endpoint" "example_function_ecr_ssm_kms" {
  for_each = var.example_function_ecr_ssm_kms_service_names

  vpc_id            = data.aws_vpc.example_function.id
  service_name      = each.value.service_name
  vpc_endpoint_type = "Interface"

  security_group_ids = [
    aws_security_group.example_function_ecr.id,
  ]

  private_dns_enabled = true

  tags = {
    Name        = each.key
    Service     = "example-function"
  }
}

resource "aws_vpc_endpoint_subnet_association" "sn_public_1a" {
  for_each = var.example_function_ecr_ssm_kms_service_names

  vpc_endpoint_id = aws_vpc_endpoint.example_function_ecr_ssm_kms[each.key].id
  subnet_id       = data.aws_subnet.example_function_public_1a.id
}

resource "aws_vpc_endpoint_subnet_association" "sn_public_1c" {
  for_each = var.example_function_ecr_ssm_kms_service_names

  vpc_endpoint_id = aws_vpc_endpoint.example_function_ecr_ssm_kms[each.key].id
  subnet_id       = data.aws_subnet.example_function_public_1c.id
}
```
</details>

- link
https://docs.aws.amazon.com/AmazonECR/latest/userguide/vpc-endpoints.html
https://docs.aws.amazon.com/AmazonECS/latest/developerguide/vpc-endpoints.html