AmazonECRからコンテナイメージをpullし、private-subnetにおいてFargateでホストされるAmazonECSタスクを運用します。コンテナイメージをpullする際は、インターフェイスタイプのVPCエンドポイントを使用するようにし、それ以外はNatGatewayを使用することで通信費を大幅に低減させつつ、セキュリティを高めることができるのでおすすめです。
ちなみにNatGatewayを使用する場合、後で請求を見て驚かないように下記3点のコストを意識する必要があります。
だとしてもSquidを構築して運用する気にはならない...
- 時間単位料金
- データ処理料金
- 転送料金:インターネットへの通信、NatGatewayとEC2インスタンスが異なるアベイラビリティーゾーンにある場合に発生
冗長構成で1GB使用する想定だと $90.64となります。
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エンドポイントは必要ありません。
- com.amazonaws.region.ecs-agent
- com.amazonaws.region.ecs-telemetry
- com.amazonaws.region.ecs
Fargateを使用する場合、下記のエンドポイントが必要です。
-
プラットフォームバージョン1.3.0以前
- com.amazonaws.region.ecr.dkr
- com.amazonaws.ap-northeast-1.s3
-
プラットフォームバージョン1.4.0以降(4〜6は用途では必要)
- com.amazonaws.ap-northeast-1.ecr.dkr
- com.amazonaws.ap-northeast-1.ecr.api
- com.amazonaws.ap-northeast-1.s3
- com.amazonaws.ap-northeast-1.logs(CloudWatchLogsを使用)
- com.amazonaws.ap-northeast-1.ssmmessages(ECSExecを使用)
- com.amazonaws.ap-northeast-1.kms(ECSExecを使用)
data source
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"]
}
}
security_group
VPCEndpointにアタッチしたセキュリティグループでは、VPCプライベートサブネットから、TCP:443で着信接続を許可する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"
}
}
variables
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"
}
}
}
vpc_endpoint
- 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/*",
]
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
}