이 글은 CloudNet@팀의 AWS EKS Workshop Study(AEWS) 3기 스터디 내용을 바탕으로 작성되었습니다.
AEWS는 CloudNet@의 '가시다'님께서 진행하는 스터디로, EKS를 학습하는 과정입니다.
EKS를 깊이 있게 이해할 기회를 주시고, 소중한 지식을 나눠주시는 가시다님께 다시 한번 감사드립니다.
이 글이 EKS를 학습하는 분들께 도움이 되길 바랍니다.
Amazon VPC Lattice를 활용한 클라이언트-서버 통신 및 멀티 클러스터 보안 통신 환경 구축 실습 과정입니다.
이 실습은 Terraform을 활용하여, 서로 다른 두 개의 VPC에 클라이언트와 서버 애플리케이션을 각각 배포하고, Amazon VPC Lattice를 통해 이들 간 통신을 구현하는 과정입니다.
서버 애플리케이션은 Amazon EKS 클러스터에 배포되며, 클라이언트 애플리케이션은 별도의 VPC에 배포됩니다.
Amazon Route53 및 External DNS Add-on을 통해 서버 서비스에 대해 사용자 지정 도메인 이름을 설정하는 방법도 함께 다룹니다.
공식 실습 문서: Amazon VPC Lattice - Client to Server Communication
실습에 필요한 코드를 아래 명령어로 내려받습니다.
git clone https://github.com/aws-ia/terraform-aws-eks-blueprints.git
클론한 프로젝트 내에서 실습에 해당하는 디렉터리로 이동합니다.
cd terraform-aws-eks-blueprints/patterns/vpc-lattice/client-server-communication
main.tf 파일의 29번째 줄에서 region을 ap-northeast-2(서울 리전)로 수정합니다.
이 작업을 통해 리소스가 한국 리전에 배포됩니다.
모듈과 provider 다운로드 및 초기 설정을 수행합니다.
terraform init
아래 명령어를 순서대로 실행해 단계별로 리소스를 배포합니다.
각 단계는 서로 의존성이 있으므로 제시된 순서대로 실행해야 합니다.
terraform apply -target="module.client_vpc" -auto-approve
terraform apply -target="module.cluster_vpc" -auto-approve
terraform apply -target=aws_route53_zone.primary -auto-approve
terraform apply -target="module.client_sg" -auto-approve
terraform apply -target="module.endpoint_sg" -auto-approve
terraform apply -target="module.client" -auto-approve
terraform apply -target="module.vpc_endpoints" -auto-approve
terraform apply -target="module.eks" -auto-approve
terraform apply -target="module.addons" -auto-approve
...
모든 리소스가 정상적으로 배포되었는지 최종적으로 전체를 apply합니다.
terraform apply -auto-approve
EKS 클러스터에 kubectl로 접근할 수 있도록 kubeconfig 파일을 설정합니다.
aws eks update-kubeconfig --name client-server-communication --alias client-server-communication --region ap-northeast-2
클러스터에 배포된 모든 Pod가 정상적으로 기동되는지 확인합니다.
kubectl get po -A
모든 Pod가 정상적으로 조회된다면 인프라 구축이 완료된 것입니다.
폴더 경로
terraform-aws-eks-blueprints > patterns > vpc-lattice > client-server-communication
해당 경로에는 실습에 필요한 주요 Terraform 코드와 관련 리소스들이 위치합니다.
목적:
클라이언트 역할을 하는 EC2 인스턴스를 별도의 VPC 내에 배포하여, EKS 내 서버 애플리케이션에 접근하도록 구성합니다.
주요 코드 설명
module "client" {
source = "terraform-aws-modules/ec2-instance/aws"
version = "~> 5.0"
name = "client"
instance_type = "t2.micro"
subnet_id = module.client_vpc.private_subnets[0]
create_iam_instance_profile = true
iam_role_description = "IAM role for client"
iam_role_policies = {
AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
vpc_security_group_ids = [module.client_sg.security_group_id]
tags = local.tags
}
AmazonSSMManagedInstanceCore
)을 할당합니다.client_sg
)은 별도 모듈로 구성되어 있습니다.목적:
SSM을 비롯한 관리 서비스를 EC2 인스턴스가 프라이빗 네트워크에서 사용할 수 있도록 VPC Endpoint를 설정합니다.
주요 코드 설명
module "vpc_endpoints" {
source = "terraform-aws-modules/vpc/aws//modules/vpc-endpoints"
version = "~> 5.0"
vpc_id = module.client_vpc.vpc_id
endpoints = { for service in toset(["ssm", "ssmmessages", "ec2messages"]) :
replace(service, ".", "_") =>
{
service = service
subnet_ids = module.client_vpc.private_subnets
private_dns_enabled = true
tags = { Name = "${local.name}-${service}" }
}
}
security_group_ids = [module.endpoint_sg.security_group_id]
tags = local.tags
}
endpoint_sg
)이 함께 적용되어, 통신 보안을 강화합니다.module "client_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "~> 5.0"
name = "client"
description = "Security Group for EC2 Instance Egress"
vpc_id = module.client_vpc.vpc_id
egress_with_cidr_blocks = [
{
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = "0.0.0.0/0"
},
]
tags = local.tags
}
module "endpoint_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "~> 5.0"
name = "ssm-endpoint"
description = "Security Group for EC2 Instance Egress"
vpc_id = module.client_vpc.vpc_id
ingress_with_cidr_blocks = [for subnet in module.client_vpc.private_subnets_cidr_blocks :
{
from_port = 443
to_port = 443
protocol = "TCP"
cidr_blocks = subnet
}
]
tags = local.tags
}
목적:
클라이언트 리소스가 배치될 별도의 VPC를 정의합니다.
주요 설정
module "client_vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = local.name
cidr = local.client_vpc_cidr
azs = local.azs
private_subnets = [for k, v in local.azs : cidrsubnet(local.client_vpc_cidr, 4, k)]
tags = local.tags
}
main.tf
의 변수 값을 참조합니다.cidrsubnet
함수로 서브넷을 동적으로 생성합니다.module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.11"
cluster_name = local.name
cluster_version = "1.30"
cluster_endpoint_public_access = true
# Give the Terraform identity admin access to the cluster
# which will allow resources to be deployed into the cluster
enable_cluster_creator_admin_permissions = true
vpc_id = module.cluster_vpc.vpc_id
subnet_ids = module.cluster_vpc.private_subnets
eks_managed_node_groups = {
initial = {
instance_types = ["m5.large"]
min_size = 3
max_size = 10
desired_size = 3
}
}
tags = local.tags
}
module "cluster_vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = local.name
cidr = local.cluster_vpc_cidr
azs = local.azs
private_subnets = [for k, v in local.azs : cidrsubnet(local.cluster_vpc_cidr, 4, k)]
public_subnets = [for k, v in local.azs : cidrsubnet(local.cluster_vpc_cidr, 8, k + 48)]
enable_nat_gateway = true
single_nat_gateway = true
public_subnet_tags = {
"kubernetes.io/role/elb" = 1
}
private_subnet_tags = {
"kubernetes.io/role/internal-elb" = 1
}
tags = local.tags
}
module "addons" {
source = "aws-ia/eks-blueprints-addons/aws"
version = "~> 1.16"
cluster_name = module.eks.cluster_name
cluster_endpoint = module.eks.cluster_endpoint
cluster_version = module.eks.cluster_version
oidc_provider_arn = module.eks.oidc_provider_arn
enable_aws_gateway_api_controller = true
aws_gateway_api_controller = {
chart_version = "v1.0.3"
create_namespace = true
namespace = "aws-application-networking-system"
source_policy_documents = [data.aws_iam_policy_document.gateway_api_controller.json]
set = [
{
name = "clusterName"
value = module.eks.cluster_name
},
{
name = "log.level"
value = "debug"
},
{
name = "clusterVpcId"
value = module.cluster_vpc.vpc_id
},
{
name = "defaultServiceNetwork"
value = ""
},
{
name = "latticeEndpoint"
value = "https://vpc-lattice.${local.region}.amazonaws.com"
}
]
wait = true
}
enable_external_dns = true
external_dns_route53_zone_arns = try([aws_route53_zone.primary.arn], [])
external_dns = {
set = [
{
name = "domainFilters[0]"
value = "example.com"
},
{
name = "policy"
value = "sync"
},
{
name = "sources[0]"
value = "crd"
},
{
name = "sources[1]"
value = "ingress"
},
{
name = "txtPrefix"
value = module.eks.cluster_name
},
{
name = "extraArgs[0]"
value = "--crd-source-apiversion=externaldns.k8s.io/v1alpha1"
},
{
name = "extraArgs[1]"
value = "--crd-source-kind=DNSEndpoint"
},
{
name = "crdSourceApiversion"
value = "externaldns.k8s.io/v1alpha1"
},
{
name = "crdSourceKind"
value = "DNSEndpoint"
}
]
}
tags = local.tags
}
data "aws_iam_policy_document" "gateway_api_controller" {
statement {
sid = ""
effect = "Allow"
resources = ["*"] # For testing purposes only (highly recommended limit access to specific resources for production usage)
actions = [
"vpc-lattice:*",
"iam:CreateServiceLinkedRole",
"ec2:DescribeVpcs",
"ec2:DescribeSubnets",
"ec2:DescribeTags",
"ec2:DescribeSecurityGroups",
"logs:CreateLogDelivery",
"logs:GetLogDelivery",
"logs:UpdateLogDelivery",
"logs:DeleteLogDelivery",
"logs:ListLogDeliveries",
"tag:GetResources",
]
}
}
resource "helm_release" "demo_application" {
name = "demo-application"
chart = "./charts/demo-application"
create_namespace = true
namespace = "apps"
depends_on = [module.addons]
}
apiVersion: gateway.networking.k8s.io/v1beta1
kind: GatewayClass
metadata:
name: amazon-vpc-lattice
spec:
controllerName: application-networking.k8s.aws/gateway-api-controller
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: my-services
namespace: apps
spec:
gatewayClassName: amazon-vpc-lattice
listeners:
- name: http
protocol: HTTP
port: 80
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: server
namespace: apps
spec:
hostnames:
- server.example.com
parentRefs:
- name: my-services
sectionName: http
rules:
- backendRefs:
- name: server
kind: Service
port: 8090
matches:
- path:
type: PathPrefix
value: /
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: server
labels:
app: server
spec:
replicas: 2
selector:
matchLabels:
app: server
template:
metadata:
labels:
app: server
spec:
containers:
- name: server
image: public.ecr.aws/x2j8p8w7/http-server:latest
env:
- name: PodName
value: "server pod"
---
apiVersion: v1
kind: Service
metadata:
name: server
spec:
selector:
app: server
ports:
- protocol: TCP
port: 8090
targetPort: 8090
data "aws_ec2_managed_prefix_list" "vpc_lattice_ipv4" {
name = "com.amazonaws.${local.region}.vpc-lattice"
}
resource "aws_vpc_security_group_ingress_rule" "cluster_sg_ingress" {
security_group_id = module.eks.node_security_group_id
prefix_list_id = data.aws_ec2_managed_prefix_list.vpc_lattice_ipv4.id
ip_protocol = "-1"
}
NONE
으로 설정 (내부 통신만 허용)resource "aws_vpclattice_service_network" "this" {
name = "my-services"
auth_type = "NONE"
tags = local.tags
}
resource "aws_vpclattice_service_network_vpc_association" "cluster_vpc" {
vpc_identifier = module.cluster_vpc.vpc_id
service_network_identifier = aws_vpclattice_service_network.this.id
}
resource "aws_vpclattice_service_network_vpc_association" "client_vpc" {
vpc_identifier = module.client_vpc.vpc_id
service_network_identifier = aws_vpclattice_service_network.this.id
}
resource "time_sleep" "wait_for_lattice_resources" {
depends_on = [helm_release.demo_application]
create_duration = "120s"
}
resource "aws_vpclattice_service_network_vpc_association" "cluster_vpc" {
vpc_identifier = module.cluster_vpc.vpc_id
service_network_identifier = aws_vpclattice_service_network.this.id
}
resource "aws_vpclattice_service_network_vpc_association" "client_vpc" {
vpc_identifier = module.client_vpc.vpc_id
service_network_identifier = aws_vpclattice_service_network.this.id
}
10.1.0.0/16
CIDR 범위로 생성10.0.0.0/16
CIDR 범위로 생성kubectl get nodes
kubectl logs -f deployment/server -n apps --all-containers=true --since=1m
curl -i http://server.example.com
nslookup server.example.com
PrivateLink 및 Lattice > Lattice 서비스
메뉴에서, 실습에서 생성된 VPC Lattice Service를 확인할 수 있습니다.kubectl get po -n apps -o wide
명령으로 server pod의 IP를 확인할 수 있고,kubectl get svc -n apps
명령으로 서비스의 Port 번호를 확인할 수 있습니다.EKS 클러스터 내에서 Gateway API Controller의 동작을 아래 명령으로 확인할 수 있습니다.
kubectl logs deployment/aws-gateway-api-controller-aws-gateway-controller-chart -n aws-application-networking-system --all-containers=true > lattice.log
vi lattice.log
로그에서는 다음과 같은 동작이 확인됩니다.
server
라는 Service와 my-services
라는 Gateway의 생성을 감지server-apps
)에 따른 사용자 도메인(server.example.com
) 및 PathPrefix, Backend 설정 반영terraform destroy -target="module.client_vpc" -auto-approve
terraform destroy -target="module.cluster_vpc" -auto-approve
terraform destroy -target=aws_route53_zone.primary -auto-approve
terraform destroy -target="module.client_sg" -auto-approve
terraform destroy -target="module.endpoint_sg" -auto-approve
terraform destroy -target="module.client" -auto-approve
terraform destroy -target="module.vpc_endpoints" -auto-approve
terraform destroy -target="module.eks" -auto-approve
terraform destroy -target="module.addons" -auto-approve
terraform destroy -auto-approve
이번 실습은 Amazon EKS Blueprints for Terraform에서 제공하는
Multi-cluster Secure Communication 패턴을 기반으로 합니다.
목표는 서로 다른 VPC에 위치한 두 개의 Amazon EKS 클러스터가 Amazon VPC Lattice와 IAM 인증, TLS 보안 구성을 활용하여
안전하게 서비스 간 통신을 수행하는 전체 과정을 이해하고 직접 실습하는 것입니다.
*.example.com
와일드카드 인증서 발급 및 라이프사이클 관리demo-cluster1.example.com
demo-cluster2.example.com
실습에 필요한 코드는 GitHub에서 아래 명령어로 내려받습니다. (실습 1에서 받은 경우 생략)
git clone https://github.com/aws-ia/terraform-aws-eks-blueprints.git
프로젝트 디렉터리는 사용하기 편한 위치에 설정합니다.
main.tf
pca.tf
디렉터리 이동
cd terraform-aws-eks-blueprints/patterns/vpc-lattice/cross-cluster-pod-communication/environment/
main.tf
에서 region을 실습 리전으로 수정
Terraform 명령어로 인프라 배포
terraform init
terraform apply --auto-approve
cluster1
, cluster2
)에서 배포됩니다.main.tf
eks.tf
enable_cluster_creator_admin_permissions
로 Terraform 계정에 admin 권한 부여/charts/platform
차트로 GatewayClass, Gateway, IAMAuthPolicy 등 인프라 배포/charts/demo
차트로 실제 데모 서비스 및 HTTPRoute, IAMAuthPolicy 등 배포vpc.tf
디렉터리 이동
cd ../cluster/
main.tf
에서 region을 실습 리전으로 수정
첫 번째 EKS 클러스터 배포 실행
./deploy.sh cluster1
# 배포 완료 후, 아래 명령으로 kubectl config 설정을 완료합니다.
eval `terraform output -raw configure_kubectl`
두 번째 EKS 클러스터 배포 실행
./deploy.sh cluster2
# 배포 완료 후, 아래 명령으로 kubectl config 설정을 완료합니다.
eval `terraform output -raw configure_kubectl`
apps
네임스페이스의 default ServiceAccount에 vpc-lattice-sig4-client
IAM Role이 연결되어 있음을 확인합니다.demo-cluster1.example.com
)에 매핑되어 노출된 것을 확인할 수 있습니다.kubectl config use-context eks-cluster1
kubectl get po -A
kubectl describe po demo-cluster1-v1-xxxxxx -n apps
kubectl config use-context eks-cluster2
kubectl get po -A
kubectl --context eks-cluster1 \
exec -ti -n apps deployments/demo-cluster1-v1 -c demo-cluster1-v1 \
-- curl demo-cluster2.example.com
Requsting to Pod(demo-cluster2-v1-xxxxxx): Hello from demo-cluster2-v1
kubectl --context eks-cluster1 \
exec -ti -n apps deployments/demo-cluster1-v1 -c demo-cluster1-v1 \
-- curl demo-cluster1.example.com
AccessDeniedException: User: arn:aws:sts::12345678910:assumed-role/vpc-lattice-sigv4-client/eks-... is not authorized to perform: vpc-lattice-svcs:Invoke on resource: ...
kubectl --context eks-cluster1 \
get IAMAuthPolicy -n apps demo-cluster1-iam-auth-policy -o json | jq ".spec.policy | fromjson"
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::12345678910:root"
},
"Action": "vpc-lattice-svcs:Invoke",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:PrincipalTag/eks-cluster-name": "eks-cluster2",
"aws:PrincipalTag/kubernetes-namespace": "apps"
}
}
}
]
}
eks-cluster2
의 apps
네임스페이스에서 온 요청에만 부여됨kubectl describe IAMAuthPolicy demo-cluster1-iam-auth-policy -n apps
명령을 통해,apps
네임스페이스의 demo-cluster1
HTTPRoute에 적용되어 있음을 알 수 있습니다.kubectl describe HTTPRoute demo-cluster1 -n apps
demo-cluster1-v1
Service로 라우팅됨을 확인할 수 있습니다.kubectl describe svc demo-cluster1-v1 -n apps
10.0.26.50:8090
)로 트래픽을 전달합니다.# 클러스터 정리
cd /workshop/terraform-aws-eks-blueprints/patterns/vpc-lattice/cross-cluster-pod-communication/cluster/
./destroy.sh cluster2
./destroy.sh cluster1
# environment 환경 정리
SN=$(aws vpc-lattice list-service-networks --query 'items[?name==`lattice-gateway`].id' --output text)
if [ -n "$SN" ]; then
aws vpc-lattice delete-service-network --service-network-id "$SN"
fi
cd /workshop/terraform-aws-eks-blueprints/patterns/vpc-lattice/cross-cluster-pod-communication/environment/
terraform destroy -auto-approve