본문 바로가기

k8s/AWS EKS

[AWS] Amazon VPC Lattice for Amazon EKS

Kubernetes의 애플리케이션 네트워킹 변화 과정

Kubernetes의 네트워킹 스택은 MSA의 발전과 함께 진화

 

첫 시작 : 한 개의 Kubernetes 클러스터를 사용할 때

단일 클러스터 내부에 배포되는 Kubernetes 서비스들은 클러스터 내부의 DNS 인 CoreDNS 및 Ingress 리소스를 통해서 내부 서비스 간 통신 및 외부로의 통신을 모두 네이티브하게 구현할 수 있음.

사용되는 방법들

  • 기본 Service 리소스로 시작
  • NodePort, ClusterIP, LoadBalancer 타입의 서비스 구현
  • Ingress 활용
    • 경로 기반 라우팅으로 서비스 구조화 및 관리 개선
    • SSL/TLS 종단점 제공을 통한 보안 강화
  • 단순한 내부 통신과 외부 노출 기능에 중점

 

워크로드의 진화 : 여러 개의 Kubernetes 클러스터를 사용할 때

애플리케이션이 진화하고, 조직이 발전하면서 서비스들이 독자적인 VPC 및 Kubernetes 클러스터에 운영되기 시작하면서 클러스터 운영자들은 이런 VPC 간 / 클러스터 간의 애플리케이션 네트워킹에 대해서 고민하게 됨.

  • 애플리케이션 끼리 통신을 하기 위해선 가장 먼저 내가 통신하고 싶은 서비스 혹은 마이크로 서비스가 어디에 있는지를 아는 것이 선행 되어야 함 → Service Discovery
    • Service Discovery
      • 서비스의 IP 주소 및 포트 정보를 동적으로 관리
      • 각 서비스 인스턴스를 지속적으로 Health Check
      • 서비스 관련 메타데이터와 구성 정보를 저장하고 관리
    • Service Discovery 패턴
      • 클라이언트 사이드
        • 클라이언트가 직접 서비스 레지스트리를 쿼리하여 서비스 위치를 찾는 방식(예: Netflix Eureka, Spring Cloud)

  • 서버 사이드
    • 로드 밸런서가 서비스 레지스트리를 쿼리하고 클라이언트 요청을 적절한 서비스로 라우팅

  • 두 개 이상의 여러 VPC 사이에서는?
    • AWS의 경우에는 VPC Peering, PrivateLink, AWS Transit Gateway 등의 다양한 옵션을 통해 각 VPC를 연결

 

마이크로 서비스가 늘어나면서 네트워크 복잡성 증가 및 공통 기능 적용(서킷브레이커, 재시도 로직, 타임아웃, 인증 등)의 요구사항 이 생김 → Service Mesh

  • Service Mesh
    • AWS AppMesh, Istio, Linkerd 등으로 트래픽 관리, 보안, 관찰성 강화
    • 마이크로서비스 간 통신의 복잡성 추상화
    • 서비스 디스커버리와 로드밸런싱의 고도화 및 자동화 (서비스 메시는 서비스 디스커버리를 기반으로 함)
  • 동작 원리
    • 컨테이너 애플리케이션 앞에 AWS AppMesh, Istio, Linkerd 등의 사이드카 프록시 컨테이너를 배치

컨테이너 애플리케이션으로 들어오고 나가는 모든 트래픽은 프록시 컨테이너를 통해 라우팅 되는 방식

프록시 컨테이너의 역할

  • 데이터 영역
    • 사이드카가 요청을 가로챔 → 요청을 별도의 네트워크 연결로 캡슐화 → 소스 프록시와 대상 프록시 간에 안전하고 암호화된 채널을 설정
    • 서비스 간 로우 레벨 메시징을 처리
    • 서킷 브레이킹, 요청 재시도와 같은 기능을 구현하여 복원력을 높이고 서비스 성능 저하를 방지
  • 제어 영역
    • 메시 내의 모든 서비스를 추적하는 서비스 레지스트리 기능 수행
    • 신규 서비스 자동 검색 기능 및 비활성 서비스 제거 기능 수행
    • 지표, 로그, 분산 추적 정보와 같은 텔레메트리 데이터의 수집 및 집계

 

위 방식의 애플리케이션 네트워킹의 한계점

Ingress와 Service Discovery, Service Mesh는 Kubernetes에서 트래픽 라우팅을 관리하는 대표적인 방식이었지만, 다음과 같은 한계점을 가지고 있음.

 

Ingress의 한계

  • HTTP, HTTPS와 같은 L7 트래픽에 최적화 되어있어 gRPC 및 TPC, UDP와 같은 비 L7 프로토콜에 대한 라우팅 기능이 제한
  • 고급 기능(인증, 속도 제한 정책, 고급 트래픽 관리 등)을 사용하려면 벤더별(AWS, NGINX, HAProxy 등) 사용자 정의 어노테이션이 필요하여 이식성과 표준화에 한계 존재

Service Mesh의 한계

  • 주로 서비스 간 내부 통신에 초점을 맞추어 설계되어 외부-내부 통신에 대한 기능이 제한적이였음.
    • Amazon EKS의 경우, VPC Peering 및 TGW가 필요 → 점점 늘어나는 설정들과 네트워크 리소스들
    • 여러 AWS 계정을 사용할 경우, 각각 교차 계정 액세스 설정이 필요 → 권한 관리의 어려움
  • 사이드카 프록시 배포가 필수적이며, 멀티 VPC 클라우드 환경에서 네트워크 복잡도 증가. (멀티 클러스터, 멀티 메시의 수많은 프록시 운영 및 관리의 어려움)

또한, 클라우드 환경에서 점차 마이크로 서비스가 확장되고 인프라 구성이 복잡해짐에 따라 아래와 같은 요구사항도 생김.

  • 다양한 팀 간의 책임 분리(인프라 담당자, 애플리케이션 개발자, DevOps 엔지니어 등)가 필요해짐.
  • 여러 VPC 또는 멀티 클러스터 환경에 걸친 리소스들을 일관성 있게 관리 필요.
  • 다양한 컴퓨팅 형태(인스턴스, 컨테이너, 서버리스 등)를 통합적으로 관리할 수 있는 네트워킹 레이어 필요.

 

Gateway API의 등장

위에서 설명한 한계를 극복하기 위해 SIG-NETWORK 커뮤니티에서 Gateway API가 고안됨.

  • 역할 기반 설계: 인프라 관리자, 클러스터 운영자, 애플리케이션 개발자 각각의 요구사항을 반영할 수 있도록 설계.
  • 범용성: HTTP, HTTPS, gRPC 등 다양한 프로토콜을 지원하고 확장성 있게 설계.
  • 표준화: Kubernetes Ingress와 같이 포터블한 표준 API가 되도록 설계.
  • 확장성: 멀티 클러스터 환경 및 다양한 VPC 간의 원활한 네트워크 통합이 가능하도록 설계.

 

Gateway API의 주요 구성요소

Gateway API는 계층적 구조로 설계되어 인프라 관리자와 애플리케이션 개발자의 역할과 책임을 명확히 구분함.

 

 

GatewayClass

  • 공통 구성을 가진 게이트웨이 집합을 정의하며, 이 클래스를 구현하는 컨트롤러에 의해 관리됩니다.
  • 게이트웨이는 각기 다른 컨트롤러에 의해 다양한 구성으로 구현될 수 있습니다. 게이트웨이는 반드시 해당 클래스를 구현하는 컨트롤러의 이름을 포함하는 GatewayClass를 참조해야 합니다.

Gateway

  • 외부 트래픽을 수신하고 내부 서비스로 라우팅하는 진입점을 정의합니다.
  • 하나 이상의 Route 리소스와 연결될 수 있습니다.

Routes

  • 트래픽을 특정 서비스로 매핑하는 프로토콜 별 규칙을 정의합니다.
  • HTTPRoute, TLSRoute, TCPRoute, UDPRoute, GRPCRoute 등이 있습니다.
  • Gateway 리소스에 연결되어 트래픽을 대상 서비스로 전달합니다.

이 계층적 구조를 통해 인프라 관리자는 GatewayClass와 Gateway를 관리하며 전체 인프라의 일관성을 유지하고, 애플리케이션 개발자는 다양한 Route를 통해 자신의 애플리케이션에 대한 세부 라우팅 규칙을 독립적으로 정의할 수 있음.

 

AWS는 이러한 계층적 구조를 Amazon VPC Lattice와 통합하여 제공함으로써, 클라우드 네이티브 환경에서의 네트워킹 요구사항을 효과적으로 충족시킴.

 

Gateway API Controller

Gateway API도 다른 Kubernetes 컴포넌트와 마찬가지로 Controller를 통해 동작. AWS에서는 AWS Gateway API Controller를 제공하고 있음.

벤더 별 Gateway API Controller 구현체를 참조: https://gateway-api.sigs.k8s.io/implementations/

 

Amazon VPC Lattice란?

Amazon VPC Lattice 는 AWS에서 제공하는 완전관리형 애플리케이션 네트워킹 서비스로, 다양한 컴퓨팅 환경에서 애플리케이션 서비스를 연결, 보호 및 모니터링하는 통합 솔루션. VPC Lattice는 단일 VPC 내에서는 물론, 계정 간 여러 VPC에 걸쳐 서비스를 연결하는 데 활용할 수 있음.

 

현대적 애플리케이션은 마이크로서비스 아키텍처를 통해 여러 개의 소규모 모듈형 서비스로 분할되어 개발되는 추세임.

이러한 접근 방식은 개발 속도와 확장성 측면에서 많은 이점을 제공하지만, 다양한 마이크로서비스 간의 효율적인 연결과 관리에 대한 새로운 네트워킹 문제를 야기함.

 

Amazon VPC Lattice는 이러한 과제를 해결하기 위해 네트워크 액세스, 트래픽 관리, 모니터링에 대한 일관된 정책을 정의하여 EC2 인스턴스, EKS 컨테이너, Lambda 서버리스 함수 등 다양한 컴퓨팅 서비스 간에 간단하고 표준화된 연결 방식을 제공.

 

Amazon VPC Lattice 주요 구성 요소

Amazon VPC Lattice는 Service, Service Network, Auth Policy, Service Directory의 네 가지 핵심 구성 요소로 이루어짐.

https://docs.aws.amazon.com/ko_kr/vpc-lattice/latest/ug/what-is-vpc-lattice.html

 

Service

Service는 특정 작업이나 기능을 수행하는 독립적으로 배포가능한 소프트웨어 단위.

어떤 Amazon Virtual Private Cloud(VPC)나 계정에도 존재할 수 있으며, 다양한 유형의 컴퓨팅 환경(가상머신, 컨테이너, 서버리스 함수)에서 실행할 수 있음.

서비스 구성은 다음과 같은 요소로 이루어짐.

  • 리스너 : 리스너에서는 Service가 트래픽을 받아들일 포트와 프로포콜을 정의합니다. 지원되는 프로토콜은 HTTP/1.1, HTTP/2, gRPC이며, TLS가 활성화된 Service의 경우 HTTPS도 포함됩니다.
  • 규칙 : 리스너의 기본 구성 요소로, 요청을 대상 그룹의 대상들로 전달합니다. 각 규칙은 우선 순위, 하나 이상의 작업, 그리고 리스너가 클라이언트 요청을 라우팅하는 기준이 되는 하나 이상의 조건으로 구성됩니다.
  • 대상 그룹 : 애플리케이션이나 서비스를 실행하는 리소스들의 집합으로, 이를 대상 이라고도 합니다. 대상은 EC2 인스턴스, IP주소, Lambda 함수, 또는 Kubernetes Pod가 될 수 있습니다.

Service 특징

  • 고유한 VPC Lattice 도메인 네임 제공
  • 리스너를 통해 들어오는 연결 처리
  • 특정 경로나 헤더 값에 기반한 라우팅 규칙 정의
  • EC2, ECS, Lambda 등 다양한 AWS 컴퓨팅 서비스를 대상으로 설정 가능
  • 상태 확인을 통한 트래픽 안정성 보장

 

Service Network

ervice Network는 VPC Lattice의 핵심 구성 요소로, 여러 서비스를 논리적으로 그룹화하고 이들 간의 통신을 관리.

하나 이상의 VPC를 Service Network에 연결하여 해당 네트워크 내의 서비스 간 통신을 가능하게 함.

Service Network의 주요 특징

  • 여러 VPC와 서비스를 단일 네트워크로 통합
  • 계정 간, 리전 간 서비스 연결 지원
  • 중앙 집중식 액세스 제어 및 모니터링 제공
  • VPC 연결을 통한 Service Network 내 서비스에 대한 접근 관리

 

Auth Policy

Auth Policy는 VPC Lattice 서비스에 대한 액세스를 제어하는 IAM 기반 정책임. 특정 IAM 보안 주체나 역할이 서비스에 접근할 수 있는지 여부를 세밀하게 제어할 수 있음.

Auth Policy의 주요 기능

  • IAM 기반의 세분화된 접근 제어
  • 서비스 레벨 또는 서비스 네트워크 레벨에서 적용 가능
  • HTTP 메소드, 경로, 헤더 등을 기준으로 조건부 액세스 규칙 설정
  • AWS 리소스 간의 안전한 통신 보장

 

Service Directory

Service Directory는 모든 VPC Lattice 서비스를 중앙에서 검색하고 관리할 수 있는 카탈로그임.

개발자와 운영팀이 사용 가능한 서비스를 쉽게 찾고 접근할 수 있도록 지원.

Service Directory의 핵심 기능

  • 사용 가능한 모든 서비스의 중앙 집중식 카탈로그 제공
  • 서비스 메타데이터 및 설명 관리
  • 서비스 검색 및 필터링 기능
  • 접근 가능한 서비스에 대한 가시성 제공

 

Amazon VPC Lattice의 장점

  • 여러 VPC, EC2 인스턴스, 컨테이너, 서버리스로 구성한 애플리케이션의 네트워크 구성을 중앙 집중화 할 수 있음.
  • 별도의 사이드카 프록시를 구성할 필요가 없음. (+ 완전 관리형 서비스)
  • 복잡한 네트워크 구성을 단순화 해서 쉽게 사용할 수 있음.
  • IAM 및 SigV4를 통해 각 애플리케이션으로의 보안 구성을 쉽게 적용 할 수 있음.
  • CloudWatch, S3, Kinesis Data Firehose를 통해 쉽게 로깅, 트래픽 패턴 분석 등을 수행할 수 있음.

 

사용 사례 #1 : 단일 리전의 애플리케이션 간 연결

단일 AWS 리전에서 프로덕션, 개발 및 공유 서비스 애플리케이션 네트워킹을 사용하는 일반적인 엔터프라이즈 아키텍처

 

사용 사례 #2 : 온프레미스에서 AWS에 배포된 애플리케이션으로 접근하는 경우

서비스 네트워크 엔드포인트가 있는 하이브리드 인그레스 VPC를 사용하여 온프레미스에서 Prod, Dev 및 Shared 서비스의 서비스 네트워크 접근하기

 

사용 사례 #3 : 여러 AWS 리전에 걸친 애플리케이션 간의 연결

리전 간 액티브-패시브 서비스 접속 구성

 

사용 사례 #4 : 다중 계정 간 애플리케이션의 연결

다중 계정 간 네트워크 연결 구성

 

AWS Gateway API Controller

AWS Gateway API Controller는 Gateway API에 의해 정의된 사용자 지정 리소스를 확장하여 Kubernetes API를 사용하여 VPC Lattice 리소스를 생성.

이 Controller가 클러스터에 설치되면 Controller는 Gateway API의 리소스(Gateway 및 Route)의 생성을 감시하고 적절한 Amazon VPC Lattice 오브젝트를 프로비저닝 함.

사용자는 커스텀 코드를 작성하거나 사이드카 프록시를 관리할 필요 없이 Kubernetes API를 사용하여 VPC Lattice Service, VPC Lattice Service Network 및 Target Group을 구성할 수 있음.

 

AWS Gateway API Controller는 Amazon VPC Lattice와 통합되어 다음 작업을 수행할 수 있음.

  • VPC 및 계정 전반에 걸친 서비스 간 네트워크 연결을 원활하게 처리.
  • 여러 Kubernetes 클러스터에 걸쳐 있는 VPC Lattice Service 검색.
  • 서비스 간 통신의 보안을 구성하기 위한 깊이 있는 방어 전략 구현.
  • 서비스 간 요청/응답 트래픽을 관찰.

AWS Gateway API Controller는 오픈소스 프로젝트이며, Amazon의 완전한 지원을 받고 있음.

 

Gateway API 구성 요소와 VPC Lattice 오브젝트 간 매핑 관계

 

Amazon EKS에서 VPC Lattice로 Gateway API를 사용하기 위한 요구사항

쿠버네티스 1.28 이상 필요: 공식 문서에서 v1.28부터 기존 Ingress가 Frozen 상태로 전환되었으며, Gateway API를 통한 현대화 아키텍처 구현 권장

AWS Gateway API Controller 설치: VPC Lattice 리소스와 쿠버네티스 API 객체 간 매핑을 처리하는 컨트롤러 배포 필요

 

[실습 1] Simple Client to Server communication

이번 실습에서는 Amazon EKS Blueprints for Terraform : Amazon VPC Lattice - Multi-cluster secure communication을 활용합니다.

이 패턴에서는 Terraform을 활용하여 클라이언트 애플리케이션이 한 곳에서 실행되고 서버 애플리케이션이 다른 곳에서 실행되는 두 개의 별개의 VPC를 배포합니다. 서버 애플리케이션은 EKS 클러스터 내에 배포되며 두 애플리케이션 간의 연결을 설정하는 Amazon VPC Lattice를 통해 클라이언트 애플리케이션에 노출됩니다. 또한 Amazon Route53 및 External DNS Add-on을 사용하여 노출된 서비스에 대한 사용자 지정 도메인 이름을 구성하는 방법을 보여줍니다.

 

 

Terraform 코드 준비 및 프로비저닝

# 코드 git clone
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 파일 내 reigon 값 ap-northeast-2로 수정
'''
locals {
  name   = basename(path.cwd)
  region = "ap-northeast-2"
'''

# Terraform init
terraform init

# Terraform 모듈 프로비저닝
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

# Terraform 나머지 리소스 프로비저닝
terraform apply -auto-approve

# Kubeconfig 설정
aws eks update-kubeconfig --name client-server-communication --alias client-server-communication --region ap-northeast-2

# Pod 확인
kubectl get po -A

 

Terraform 코드 살펴보기

아래 그림과 같이 IDE를 통해, terraform-aws-eks-blueprints 디렉토리를 엽니다.

이후 페이지 왼쪽 메뉴바의 terraform-aws-eks-blueprints > patterns > vpc-lattice > client-server-communication 순서로 폴더를 엽니다.

 

client.tf 살펴보기

client.tf 파일을 살펴보겠습니다. 이 파일에서는 VPC Lattice를 통해 EKS Cluster의 애플리케이션으로 요청을 전달하는 클라이언트 역할을 하는 리소스에 대한 명세가 작성되어 있습니다.

client 모듈에서는 프로비저닝 할 EC2 인스턴스에 대한 명세가 적혀있습니다.

  • 인스턴스 유형은 t2.micro 입니다.
  • AmazonSSMManagedInstanceCore IAM Role을 설정합니다.
  • client_sg 모듈을 참조하여 Security Group을 설정합니다.
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
}

 

이어서 vpc_endpoints 모듈에서는 client에서 Systems Manager를 사용하기 위한 VPC Endpoint를 구성하기 위한 명세가 적혀있습니다.

  • VPC Endpoint에서 구성할 서비스는 ssm, ssmmessages, ec2messages 입니다.
  • 적용할 서브넷은 client_vpc 모듈을 참조하여 private_subnets을 대상으로 합니다.
  • endpoint_sg 모듈을 참조하여 Security Group을 설정합니다.
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
}

 

그 다음으로는 client의 EC2 인스턴스와 VPC Endpoint에서 사용할 각각의 Security Group(모듈 client_sg 및 endpoint_sg)에 대한 명세가 적혀있습니다.

  • client_sg 모듈에서는 EC2 인스턴스의 Egress 설정이 명세되어 있습니다.
  • endpoint_sg 모듈에서는 client_vpc의 모듈을 참조하여 private_subnets_cidr_blocks 값에 대해 443 포트를 허용하는 Ingress 설정이 명세되어 있습니다.
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
}

 

마지막으로, client_vpc 모듈에서는 이 모든 client를 위한 리소스가 구성될 VPC에 대한 내용이 명세되어 있습니다.

  • client vpc에 대한 cidr 값은 main.tf 내에 명세되어있는 client_vpc_cidr 값을 참조합니다.
  • 가용영역(azs) 값도 마찬가지로 main.tf 내에 명세되어있는 azs 값을 참조합니다.
  • terraform의 cidrsubnet  함수는 주어진 대역 내에서 새 비트 수에 따른 서브네팅을 수행합니다.
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
}

 

eks.tf 살펴보기

이번에는 eks.tf 파일을 살펴보겠습니다. 이 파일에서는 VPC Lattice를 통해 서비스를 제공할 EKS 클러스터의 애플리케이션 및 그 인프라에 대한 명세가 작성되어 있습니다.

가장 첫번째로 볼 수 있는 eks 모듈은 EKS 클러스터에 대한 명세입니다.

  • EKS 클러스터의 버전은 1.30 입니다.
  • EKS 클러스터의 public access를 허용합니다.
  • EKS 클러스터의 VPC 정보는 cluster_vpc 모듈을 참조합니다.
  • 관리형 노드그룹을 구성하며, m5.large 인스턴스 유형을 사용하고 최소 3개, 최대 10개, 기본 3개의 노드 갯수를 설정합니다.
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
}

 

다음은 cluster_vpc 모듈입니다. 이 모듈에서는 EKS 클러스터가 프로비저닝 될 VPC에 대한 명세가 담겨있습니다.

  • 해당 VPC의 cidr 값은 main.tf 내에 명세되어있는 cluster_vpc_cidr 값을 참조합니다.
  • NAT Gateway를 한 개만 프로비저닝 합니다.
  • Ingress 구성을 위한 elb 태그를 추가합니다.
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
}

 

다음은 addons 모듈입니다. 이 모듈에서는 EKS 클러스터에 설치할 EKS Add-on에 대한 명세가 담겨있습니다. 해당 모듈을 통해서 AWS Gateway API Controller를 추가합니다.

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",
    ]
  }
}

 

다음은 demo_application 리소스 입니다. 이 리소스를 통해서 EKS 클러스터 내에 간단한 데모 애플리케이션을 배포합니다.

  • ./charts/demo-application 디렉터리 내의 yaml을 참조하여 데모 애플리케이션을 배포합니다.
  • depends_on을 통해 addons 모듈이 구성되고 난 이후에 리소스를 배포하도록 설정합니다.
resource "helm_release" "demo_application" {
  name             = "demo-application"
  chart            = "./charts/demo-application"
  create_namespace = true
  namespace        = "apps"

  depends_on = [module.addons]
}

 

데모 애플리케이션에 대한 yaml 명세서를 간단히 살펴보겠습니다. charts/demo-application 폴더를 참조해주세요.

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

 

마지막으로 아래 데이터 및 리소스를 통해 VPC Lattice에 대한 IPv4 주소의 EC2 Prefix List를 생성하고, 이 Prefix List에 대한 Security Group Ingress 규칙을 생성합니다.

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"
}

 

lattice.tf 살펴보기

lattice.tf 파일을 열어 VPC Lattice에 대한 구성을 살펴보겠습니다.

우선, VPC Lattice service network를 위한 리소스 명세를 살펴보겠습니다.

  • this 리소스에서는 my-services라는 이름의 VPC Lattice service network를 생성합니다.
  • cluster_vpc 리소스에서는 앞서 만든 VPC Lattice service network에 cluster vpc를 association 합니다.
  • client_vpc 리소스에서는 앞서 만든 VPC Lattice service network에 client vpc를 association 합니다.
  • demo_application이 배포되고 난 이후 120초를 기다립니다.
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"
}

 

다음으로 VPC Lattice service를 위한 커스텀 도메인을 생성하는 부분을 살펴보겠습니다.

  • example.com 이라는 명칭의 public hosted zone을 생성합니다.
  • client vpc의 id 값을 hosted zone과 연결합니다.
  • 여기서 records는 external-dns에서 HTTPRoute를 생성할 때, VPC Lattice Gateway API Copntroller에서 생성한 DNSEndpoint 객체를 사용해서 생성됩니다.
resource "aws_route53_zone" "primary" {
  name = "example.com"

  vpc {
    vpc_id = module.client_vpc.vpc_id
  }

  tags = local.tags
}
Terraform 용어 가이드

모듈(module) : 재사용 가능한 Terraform 코드 블록으로, 여러 리소스를 그룹화하여 하나의 템플릿으로 관리할 수 있습니다. 리소스(resource) : Terraform의 핵심 구성 요소로, 실제 인프라 리소스를 생성하거나 관리합니다. 데이터(data) : 이미 존재하는 인프라 리소스나 다른 Terraform 코드에서 생성된 리소스 정보를 가져오는 데 사용됩니다.

 

프로비저닝 된 인프라 확인

  • client vpc가 10.1.0.0/16 CIDR 주소 범위로 구성되어 있습니다.
  • cluster vpc가 10.0.0.0/16 CIDR 주소 범위로 구성되어 있습니다.

 

Subnets

서브넷도 함께 확인해보겠습니다. 왼쪽 메뉴의 Subnets 메뉴를 클릭하여 서브넷 목록을 확인합니다.

 

구성된 워크로드 확인

이어서 테스트를 수행할 대상인 워크로드를 한번 확인해보겠습니다. 아래 그림을 따라 EC2 콘솔로 이동해 EC2 목록을 확인합니다.

  • client 측 워크로드로 1개의 ec2 인스턴스가 구성되어 있습니다.
  • cluster 측 워크로드로 3개의 ec2 인스턴스(워커 노드)가 구성되어 있습니다.

아래와 같이 kubectl 명령을 수행하면 EKS cluster의 워커 노드를 확인해볼 수 있습니다.

kubectl get nodes

 

VPC Lattice를 통한 Client to Server 통신 테스트

간단한 테스트를 통해 client 워크로드에서 cluster의 워크로드로 통신이 가능한지 한번 테스트 해보겠습니다.

 

client 서버에 ssh 접속

아래 그림과 같이 client라는 이름의 EC2 인스턴스를 선택하고, 상단의 Connect 버튼을 클릭합니다.

 

client 애플리케이션에서 cluster 애플리케이션으로 http 요청 전송

client에서 cluster의 server pod로 요청이 잘 수행되는지 명령을 수행해보겠습니다. 이 과정에서 VPC Lattice로 구성된 서비스 네트워크를 사용해보겠습니다.

 

어떻게 이런 동작이 가능한걸까요? client의 터미널에서 아래와 같이 nslookup 명령을 수행해보겠습니다.

nslookup server.example.com

 

그럼 아래와 같이 Amazon VPC Lattice로 구성된 엔드포인트가 조회됩니다.

즉, Amazon VPC Lattice의 네트워크 구성을 통해서 이런 환경이 구성된 건데요, 한번 VPC Lattice의 구성된 내용을 자세히 살펴보겠습니다.

 

Amazon VPC Lattice 확인

그 다음, 왼쪽 메뉴바에서 PrivateLink and Lattice > Lattice services 를 찾아 클릭합니다.

그럼 아래와 같이 VPC Lattice Service를 조회할 수 있습니다.

 

Amazon VPC Lattice Target Group 확인

VPC Lattice Service의 세부 항목 중, Routing 섹션을 클릭하면 아래와 같은 Listener 규칙과 함께 대상 그룹을 확인할 수 있습니다. 대상 그룹의 세부 내용을 보기 위해서 아래 그림과 같이 Listener rules > Action > Forward to 순서로 찾아 대상 그룹을 클릭합니다.

 

아래는 대상 그룹 상세 페이지 입니다. 여기의 Registered targets 섹션을 보면 IP address와 Port 번호를 통해 VPC Lattice Service로의 라우팅 대상을 안내하고 있습니다.

 

아래 kubectl 명령을 수행하면, 해당 IP address와 일치하는 server pod의 IP 목록을 조회할 수 있습니다.

kubectl get svc -n apps

 

이어서 아래 명령을 수행하면, server pod에서 사용하는 service의 정보 및 Port 번호를 조회할 수 있습니다.

kubectl get svc -n apps

 

이를 통해 VPC Lattice로 구성된 DNS 엔드포인트는 EKS 클러스터 내의 server pod로 라우팅 된다는 것을 알 수 있습니다.

 

AWS Gateway API Controller의 동작 체크

이렇게 VPC Lattice를 통해 EKS 클러스터 내의 server pod로 라우팅 할 수 있는 환경을 구성하기 위해서는 EKS 클러스터 내에 GatewayClass, Gateway, Route 리소스를 생성해야 합니다.

AWS Gateway API Controller는 생성된 GatewayClass, Gateway, Route 리소스를 참조하여 필요한 VPC Lattice 네트워크 환경을 구성하게 됩니다.

아래 kubectl 명령을 수행하여, AWS Gateway API Controller의 로그를 lattice.log라는 파일로 저장합니다.

kubectl logs deployment/aws-gateway-api-controller-aws-gateway-controller-chart -n aws-application-networking-system --all-containers=true > lattice.log

 

그리고 아래의 vi 명령을 통해 로그를 조회하면, AWS Gateway API Controller의 동작을 확인 해볼 수 있습니다.

vi lattice.log

 

아래 로그의 내용처럼 EKS 클러스터 내에 GatewayClass, Gateway, Route에 대한 오브젝트가 추가되면 AWS Gateway API Controller에서 이를 감지하여 조정(reconcile)합니다.

 

설명은 아래와 같습니다.

  • 로그를 보면, server라는 이름의 service와 my-services라는 이름의 gateway의 생성을 감지하여, reconcile 작업을 수행하는 것을 확인할 수 있습니다.
  • 이어서 server-apps라는 이름(HTTPRoute의 metadata.name 및 metadata.namespace를 사용)으로 구성된 Route 대상에 라우팅 하기 위해 server.example.com이라는 사용자 지정 도메인 이름을 세팅하고, VPC Lattice Service를 구성합니다.
  • 그리고 VPC Lattice Service에 대해 Listener rule을 생성하고 HTTPRoute 매니페스트 내용에 맞춰 PathPrefix를 구성하고, backendRefs에 대한 대상 그룹을 생성합니다.
  • 대상 그룹에는 Service를 참조하여 라우팅 대상 Pod의 IP 주소 및 Port 번호를 세팅합니다.

 

이렇게 우리는 AWS Gateway API Controller가 GatewayClass, Gateway, Route 리소스를 참조하여 VPC Lattice 네트워크 환경을 구성하여 정상적으로 통신이 가능한 환경을 구성했다는 것을 확인할 수 있습니다.

 

실습 리소스 정리

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

 

[실습 2] Multi Cluster secure communication

이번 실습에서는 Amazon EKS Blueprints for Terraform : Amazon VPC Lattice - Multi-cluster secure communication을 활용합니다.

이번 섹션에서는 조금 더 심화 과정으로, 멀티 클러스터 환경 간에서 Amazon VPC Lattice를 활용한 안전한 통신 방법에 대해 실습을 진행합니다.

 

이 패턴에서는 Amazon VPC Lattice와 IAM authorization을 사용하여 서로 다른 VPC에 있는 두 EKS 클러스터 간의 안전한 멀티 클러스터 통신 방식을 보여줍니다.

  • 이 솔루션에서는 Service Discovery가 어떻게 이루어지는지를 설명하고, VPC Lattice가 겹치는 CIDR을 가진 EKS 클러스터 간 통신을 어떻게 용이하게 하는지 강조합니다.
  • 이를 통해 프라이빗 NAT 게이트웨이와 Transit Gateway와 같은 네트워킹 구조가 필요하지 않도록 하는 방법을 소개합니다.
  • 클러스터 간 통신을 안전하게 수행하기 위해, 인증서 관리자(ACM)에서 발행하고 AWS Private CA에서 지원하는 TLS 인증서로 보호되는 Amazon Route53 Private Hosted Zone을 사용하여 Amazon VPC Lattice를 통해 설정됩니다.
  • 이를 위해 Kyverno를 사용하여 Envoy SigV4 proxy 컨테이너를 Pod에 추가하고, 여기에 PCA를 주입합니다.

 

주요 컴포넌트

인프라 구성

  • 두 개의 VPC: VPC cluster 1과 VPC cluster 2가 독립적으로 구성
  • 두 개의 EKS 클러스터: 각 VPC에 EKS Cluster 1과 EKS Cluster 2 배포
  • VPC Lattice: 두 VPC 간의 서비스 통신을 위한 중앙 관리형 연결 서비스

애플리케이션 구성

  1. EKS Cluster 1 애플리케이션:
    • App1 Service가 Pod로 배포됨
    • Envoy sidecar 프록시를 통한 서비스 메시 구성
    • HTTP Route를 통한 외부 트래픽 라우팅
  2. EKS Cluster 2 애플리케이션:
    • App2 Service가 Pod로 배포됨
    • 마찬가지로 Envoy sidecar 프록시와 HTTP Route 구성

정책 및 보안

  • Kyverno: 각 클러스터에 적용된 정책 관리 도구로 사이드카 주입 등을 관리
  • IAM Auth Policy: 두 클러스터 간의 인증 및 권한 정책 설정
  • Private Certificate Authority: 중앙 인증서 관리
  • Certificate Manager: *.example.com 도메인을 위한 인증서 관리

네트워킹

  • AWS Gateway API Controller: 각 클러스터의 API 트래픽 제어
  • DNS 구성:
    • ExternalDNS를 통한 DNS 레코드 관리
    • demo-cluster1.example.com 및 demo-cluster2.example.com으로 각 클러스터 서비스 노출
    • Amazon Route 53을 통한 도메인 관리

주요 흐름 및 프로세스

  1. VPC Lattice를 통해 HTTPS 트래픽이 두 클러스터 간에 라우팅됨
  2. 각 클러스터의 IAM Auth Policy가 접근 권한을 제어
  3. Pod Identity 기능을 통해 Envoy SigV4 proxy 컨테이너에 ACM에 대한 권한 및 VPC Lattice service invoke 권한 부여
  4. AWS Gateway API Controller가 각 클러스터의 인그레스 트래픽을 관리
  5. ExternalDNS가 자동으로 DNS 레코드를 생성하여 서비스 디스커버리 지원

 

Terraform 코드 준비 및 프로비저닝

environment 프로비저닝

environment의 Terraform을 통해 구성되는 내용들

 

environment 환경에서는 실습에 필요한 사전 환경(Route53, VPC Lattice, IAM Role, ACM 등)을 프로비저닝 합니다.

자세한 내용은 아래 코드를 파악하여 확인해보겠습니다.

 

environment :Terraform 코드 살펴보기

environment 디렉터리 / main.tf 살펴보기

아래 region 부분을 수정하여 인프라를 프로비저닝 하고자 하는 리전(이 실습에서는 ap-northeast-2)로 변경할 수 있습니다.

name, region, domain 명으로 로컬 변수를 정의하였습니다.

provider "aws" {
  region = local.region
}

locals {
  name   = "vpc-lattice"
  region = "us-west-2" # 이 부분을 ap-northeast-2로 수정

  domain = var.custom_domain_name

  tags = {
    Blueprint  = local.name
    GithubRepo = "github.com/aws-ia/terraform-aws-eks-blueprints"
  }
}

 

Amazon Route53의 Private Hosted Zone을 구성하는 명세입니다.

Private Hosted Zone을 만들기 위해 필요한 Example VPC라는 이름의 더미 VPC를 만들고 있습니다.

#-------------------------------
# Create Private Hosted Zone
#-------------------------------

resource "aws_route53_zone" "private_zone" {
  name = local.domain

  vpc {
    vpc_id = aws_vpc.example.id
  }

  #we will add vpc association in other terraform stack, prevent this one to revert this
  lifecycle {
    ignore_changes = [
      vpc,
    ]
  }

  force_destroy = true
  tags          = local.tags
}

#dummy VPC that will not be used, but needed to create private hosted zone
resource "aws_vpc" "example" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "Example VPC"
  }
}

 

VPC Lattice Service 및 ACM을 위한 IAM Role을 만들고, 필요한 Polic를 attach 합니다.

################################################################################
# Create IAM role to talk to VPC Lattice services and get Certificate from Manager
################################################################################
data "aws_iam_policy_document" "eks_assume" {
  statement {
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["pods.eks.amazonaws.com"]
    }
    actions = ["sts:AssumeRole", "sts:TagSession"]
  }
}
resource "aws_iam_role" "vpc_lattice_role" {
  name               = "${local.name}-sigv4-client"
  description        = "IAM role for aws-sigv4-client VPC Lattice access"
  assume_role_policy = data.aws_iam_policy_document.eks_assume.json
}

resource "aws_iam_role_policy_attachment" "vpc_lattice_invoke_access" {
  role       = aws_iam_role.vpc_lattice_role.name
  policy_arn = "arn:aws:iam::aws:policy/VPCLatticeServicesInvokeAccess"
}

resource "aws_iam_role_policy_attachment" "private_ca_read_only" {
  role       = aws_iam_role.vpc_lattice_role.name
  policy_arn = "arn:aws:iam::aws:policy/AWSCertificateManagerPrivateCAReadOnly"
}

 

environment 디렉터리 / pca.tf 살펴보기

실습에 필요한 Private CA를 만듭니다.

#-------------------------------
# Associates a certificate with an AWS Certificate Manager Private Certificate Authority (ACM PCA Certificate Authority).
# An ACM PCA Certificate Authority is unable to issue certificates until it has a certificate associated with it.
# A root level ACM PCA Certificate Authority is able to self-sign its own root certificate.
#-------------------------------

# # https://docs.aws.amazon.com/acm-pca/latest/userguide/pca-rbp.html

resource "aws_acmpca_certificate_authority" "this" {
  enabled = true
  type    = "ROOT"

  certificate_authority_configuration {
    key_algorithm     = "RSA_4096"
    signing_algorithm = "SHA512WITHRSA"

    subject {
      common_name  = var.custom_domain_name
      organization = var.organization
    }
  }

  permanent_deletion_time_in_days = 7

  tags = local.tags
}

resource "aws_acmpca_certificate" "this" {
  certificate_authority_arn   = aws_acmpca_certificate_authority.this.arn
  certificate_signing_request = aws_acmpca_certificate_authority.this.certificate_signing_request
  signing_algorithm           = "SHA512WITHRSA"

  template_arn = "arn:aws:acm-pca:::template/RootCACertificate/V1"

  validity {
    type  = "YEARS"
    value = 10
  }
}

resource "aws_acmpca_certificate_authority_certificate" "this" {
  certificate_authority_arn = aws_acmpca_certificate_authority.this.arn

  certificate       = aws_acmpca_certificate.this.certificate
  certificate_chain = aws_acmpca_certificate.this.certificate_chain
}
#-------------------------------
# Create certificate in AWS Certificate Manager
#-------------------------------

resource "aws_acm_certificate" "private_domain_cert" {
  domain_name = var.custom_domain_name
  #validation_method = "DNS"

  subject_alternative_names = [
    "*.${var.custom_domain_name}"
  ]

  options {
    certificate_transparency_logging_preference = "DISABLED"
  }

  certificate_authority_arn = aws_acmpca_certificate_authority.this.arn

  tags = local.tags
}

 

인프라 프로비저닝을 위해 아래 내용을 진행합니다.

 

1. 아래 명령을 수행하여 cross-cluster-pod-communication/environment/ 디렉터리로 이동합니다.

cd terraform-aws-eks-blueprints/patterns/vpc-lattice/cross-cluster-pod-communication/environment/

 

2. main.tf 파일을 열고, 7번째 라인의 region을 ap-northeast-2로 수정합니다.

 

3. 아래 명령을 수행하여 environment의 인프라를 프로비저닝 합니다.

terraform init
terraform apply --auto-approve

 

cluster 프로비저닝

이어서 cluster 환경에서는 실습에 사용할 두 개의 EKS 클러스터 및 데모 애플리케이션을 구성합니다.

자세한 내용은 아래 코드를 파악하여 확인해보겠습니다.

 

cluster :Terraform 코드 살펴보기

[cluster 디렉터리 / main.tf 살펴보기]

테라폼에서 사용할 공통 변수, 데모 애플리케이션 배포를 위한 helm 설정 등이 정의되어 있습니다.

provider "aws" {
  region = local.region
}

data "aws_caller_identity" "current" {}

data "aws_availability_zones" "available" {
  #Do not include local zones
  filter {
    name   = "opt-in-status"
    values = ["opt-in-not-required"]
  }
}

provider "helm" {
  kubernetes {
    host                   = module.eks.cluster_endpoint
    cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)

    exec {
      api_version = "client.authentication.k8s.io/v1beta1"
      command     = "aws"
      # This requires the awscli to be installed locally where Terraform is executed
      args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name]
    }
  }
}

locals {
  name   = "eks-${terraform.workspace}"
  region = "us-west-2" # 이 부분을 ap-northeast-2로 수정

  cluster_vpc_cidr = "10.0.0.0/16"
  azs              = slice(data.aws_availability_zones.available.names, 0, 3)

  domain          = data.terraform_remote_state.environment.outputs.custom_domain_name
  certificate_arn = data.terraform_remote_state.environment.outputs.aws_acm_cert_arn
  acmpca_arn      = data.terraform_remote_state.environment.outputs.aws_acmpca_cert_authority_arn
  custom_domain   = data.terraform_remote_state.environment.outputs.custom_domain_name

  app_namespace = "apps"

  tags = {
    Blueprint  = local.name
    GithubRepo = "github.com/aws-ia/terraform-aws-eks-blueprints"
  }
}

 

[cluster 디렉터리 / eks.tf 살펴보기]

EKS 클러스터 생성에 대한 명세 입니다. 클러스터 버전 및 관리형 노드 그룹, VPC 및 서브넷에 대한 정의가 되어있으며, 데모 애플리케이션 배포를 위한 설정(enable_cluster_creator_admin_permissions)을 true로 설정합니다.

################################################################################
# Cluster
################################################################################

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.8"

  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.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  eks_managed_node_groups = {
    initial = {
      instance_types = ["m5.large"]

      min_size     = 3
      max_size     = 10
      desired_size = 3
    }
  }

  tags = local.tags
}

 

EKS Blueprints Addons를 활용하여 실습에 필요한 에드온(aws gateway api controller, external-dns, kyverno 등)을 추가합니다.

################################################################################
# EKS Blueprints Addons
################################################################################

module "eks_blueprints_addons" {
  source  = "aws-ia/eks-blueprints-addons/aws"
  version = "~> 1.1"

  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

  # EKS Addons
  eks_addons = {
    coredns    = {}
    kube-proxy = {}
    vpc-cni = {
      preserve    = true
      most_recent = true

      timeouts = {
        create = "25m"
        delete = "10m"
      }

    }
    eks-pod-identity-agent = {}
  }

  enable_aws_gateway_api_controller = true
  aws_gateway_api_controller = {
    chart_version           = "v1.0.5"
    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.vpc.vpc_id
      },
      {
        name  = "defaultServiceNetwork"
        value = "lattice-gateway" # Specify this parameter to create a default VPC lattice service
      }
    ]
    wait = true
  }
  enable_external_dns            = true
  external_dns_route53_zone_arns = try([data.terraform_remote_state.environment.outputs.route53_private_zone_arn], [])
  external_dns = {
    create_role = true,
    set = [
      {
        name  = "domainFilters[0]"
        value = local.domain
      },
      {
        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
}

module "eks_blueprints_addon_kyverno" {
  source  = "aws-ia/eks-blueprints-addon/aws"
  version = "~> 1.1" #ensure to update this to the latest/desired version

  chart            = "kyverno"
  chart_version    = "3.0.5"
  repository       = "https://kyverno.github.io/kyverno"
  description      = "Kyverno"
  namespace        = "kyverno"
  create_namespace = true
}

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",
    ]
  }
}

 

helm_release를 통해 /chart/platform 디렉터리 내의 helm char를 배포합니다.

################################################################################
# Platform applications
################################################################################

resource "helm_release" "platform_application" {
  name             = "platform-${terraform.workspace}"
  chart            = "./charts/platform"
  create_namespace = true
  namespace        = "lattice-gateway"
  force_update     = true

  #replace = true # This will force a re-deployment

  values = [
    <<EOF
    awsAccountID: "${data.aws_caller_identity.current.account_id}"
    version : "v1"

    allowedCluster: ${terraform.workspace == "cluster1" ? "eks-cluster2" : "eks-cluster1"}
    allowedNamespace: "apps"

    certificateArn: ${local.certificate_arn}

    acmpCAArn: ${local.acmpca_arn}

    customDomain: ${local.custom_domain}
  EOF
  ]

  depends_on = [module.eks_blueprints_addons, aws_eks_pod_identity_association.apps]
}

 

아래는 platform 디렉터리 내의 yaml 명세서 내용입니다.

## gateway-class.yaml

apiVersion: gateway.networking.k8s.io/v1beta1
kind: GatewayClass
metadata:
  name: amazon-vpc-lattice
spec:
  controllerName: application-networking.k8s.aws/gateway-api-controller
## gateway.yaml

apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
  name: {{ .Release.Namespace }}
  namespace: {{ .Release.Namespace }}
spec:
  gatewayClassName: amazon-vpc-lattice
  listeners:
  - name: http-listener
    port: 80
    protocol: HTTP
    allowedRoutes:
      kinds:
      - kind: HTTPRoute
      namespaces:
        from: Selector
        selector:
          matchLabels:
            allow-attachment-to-infra-gw: "true"
  - name: https-listener-with-default-domain
    port: 443
    protocol: HTTPS
    allowedRoutes:
      kinds:
      - kind: HTTPRoute
      namespaces:
        from: Selector
        selector:
          matchLabels:
            allow-attachment-to-infra-gw: "true"
  - name: https-listener-with-custom-domain
    port: 443
    protocol: HTTPS
    allowedRoutes:
      kinds:
      - kind: HTTPRoute
      namespaces:
        from: Selector
        selector:
          matchLabels:
            allow-attachment-to-infra-gw: "true"
    tls:
      mode: Terminate
      options:
        application-networking.k8s.aws/certificate-arn: {{ .Values.certificateArn }}
---
#https://github.com/aws/aws-application-networking-k8s/blob/main/docs/api-types/iam-auth-policy.md?plain=1
apiVersion: application-networking.k8s.aws/v1alpha1
kind: IAMAuthPolicy
metadata:
    name: {{ .Release.Namespace }}-iam-auth-policy
    namespace: {{ .Release.Namespace }}
spec:
    targetRef:
        group: "gateway.networking.k8s.io"
        kind: Gateway
        name: {{ .Release.Namespace }}
    policy: |
        {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": "*",
                    "Action": "vpc-lattice-svcs:Invoke",
                    "Resource": "*",
                    "Condition": {
                        "StringNotEqualsIgnoreCase": {
                            "aws:PrincipalType": "anonymous"
                        }
                    }
                }
            ]
        }
## kyverno-cluster-policy.yaml

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: inject-sidecar
  annotations:
    policies.kyverno.io/title: Inject Sidecar Container
spec:
  rules:
    - name: inject-sidecar-envoy
      match:
        any:
          - resources:
              kinds:
                - Deployment
      mutate:
        patchStrategicMerge:
          spec:
            template:
              metadata:
                annotations:
                  (vpc-lattices-svcs.amazonaws.com/agent-inject): "true"
              spec:
                initContainers: # IPtables 규칙은 init container에서 업데이트됩니다. (링크 : init container란?)
                  - image: public.ecr.aws/seb-demo/iptables:v1
                    name: iptables-init
                    securityContext:
                      capabilities:
                        add:
                          - NET_ADMIN
                    command: # Envoy 프록시 자체의 트래픽이 리디렉션되는 것을 방지하기위한 코드를 작성합니다.(무한 루프 방지)
                      - /bin/sh
                      - -c
                      - >
                        iptables -t nat -N EGRESS_PROXY;
                        iptables -t nat -A OUTPUT -p tcp -d 169.254.171.0/24 -j EGRESS_PROXY;
                        iptables -t nat -A EGRESS_PROXY -m owner --gid-owner 0 -j RETURN;
                        iptables -t nat -A EGRESS_PROXY -p tcp -j REDIRECT --to-ports 8080;
                        iptables -t nat -L -n -v;
                containers:
                  - name: envoy-sigv4
                    image: public.ecr.aws/seb-demo/envoy-sigv4:v0.5
                    securityContext:
                      runAsGroup: 0
                    env:
                      - name: APP_DOMAIN
                        value: "example.com"
                      - name: CA_ARN
                        value: "{{ .Values.acmpCAArn }}"
                    args: ["-l", "info"]
                    ports:
                      - containerPort: 8080
                        name: proxy
                        protocol: TCP
iptables -t nat -N EGRESS_PROXY; # EGRESS_PROXY라는 새로운 체인(체인은 규칙 그룹)을 만듭니다.
iptables -t nat -A OUTPUT -p tcp -d 169.254.171.0/24 -j EGRESS_PROXY; # 목적지 IP가 169.254.171.0/24 대역인 모든 TCP 패킷을 EGRESS_PROXY 체인으로 보냅니다. (169.254.171.0/24 - VPC Lattice 링크 로컬 주소 범위)
iptables -t nat -A EGRESS_PROXY -m owner --gid-owner 0 -j RETURN; # EGRESS_PROXY 체인에서 그룹 ID가 0인 사용자(root 사용자)의 패킷은 규칙을 거치지 않고 통과시킵니다.
iptables -t nat -A EGRESS_PROXY -p tcp -j REDIRECT --to-ports 8080; # EGRESS_PROXY 체인에서 나머지 TCP 패킷은 8080 포트로 리다이렉트됩니다. (envoy-sigv4 컨테이너로 리다이렉트)
iptables -t nat -L -n -v # 현재 설정된 NAT 테이블 규칙을 자세히 출력합니다.

 

helm_release를 통해 /chart/demo 디렉터리 내의 helm char를 배포합니다.

################################################################################
# Demo applications
################################################################################

resource "helm_release" "demo_application" {
  name             = "demo-${terraform.workspace}"
  chart            = "./charts/demo"
  create_namespace = true
  namespace        = local.app_namespace
  force_update     = true

  #replace = true # This will force a re-deployment

  values = [
    <<EOF
    awsAccountID: "${data.aws_caller_identity.current.account_id}"
    version : "v1"

    allowedCluster: ${terraform.workspace == "cluster1" ? "eks-cluster2" : "eks-cluster1"}
    allowedNamespace: "apps"

    certificateArn: ${local.certificate_arn}

    customDomain: ${local.custom_domain}
  EOF
  ]

  depends_on = [module.eks_blueprints_addons, aws_eks_pod_identity_association.apps, helm_release.platform_application]
}

 

아래는 demo 디렉터리 내의 yaml 명세서 내용입니다.

## app-deploy.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-{{ .Values.version }}
  namespace: {{ .Release.Namespace }}
  labels:
    app: {{ .Release.Name }}-{{ .Values.version }}
spec:
  replicas: 1
  selector:
    matchLabels:
      app: {{ .Release.Name }}-{{ .Values.version }}
  template:
    metadata:
      annotations:
       vpc-lattices-svcs.amazonaws.com/agent-inject: "true"
      labels:
        app: {{ .Release.Name }}-{{ .Values.version }}
    spec:
      containers:
      - name: {{ .Release.Name }}-{{ .Values.version }}
        image: public.ecr.aws/seb-demo/http-server:latest
        env:
        - name: PodName
          value: "Hello from {{ .Release.Name }}-{{ .Values.version }}"
        securityContext:
          runAsUser: 0
          runAsGroup: 1000  #different GID to prevent allow routing from iptable rule
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 15"]
      terminationGracePeriodSeconds: 15
---
apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}-{{ .Values.version }}
  namespace: {{ .Release.Namespace }}
spec:
  selector:
    app: {{ .Release.Name }}-{{ .Values.version }}
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8090
## app-httproute.yaml

apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
  name: {{ .Release.Name }}
  namespace: {{ .Release.Namespace }}
spec:
  hostnames:
  - {{ .Release.Name }}.{{ .Values.customDomain }}
  parentRefs:
  - kind: Gateway
    name: lattice-gateway
    namespace: lattice-gateway
    sectionName: http-listener
  - kind: Gateway
    name: lattice-gateway
    namespace: lattice-gateway
    sectionName: https-listener-with-custom-domain
  rules:
  - backendRefs:
    - name: {{ .Release.Name }}-{{ .Values.version }}
      kind: Service
      port: 80
    matches:
      - path:
          type: PathPrefix
          value: /
---
apiVersion: application-networking.k8s.aws/v1alpha1
kind: IAMAuthPolicy
metadata:
    name: {{ .Release.Name }}-iam-auth-policy
    namespace: {{ .Release.Namespace }}
spec:
    targetRef:
        group: "gateway.networking.k8s.io"
        kind: HTTPRoute
        namespace: {{ .Release.Namespace }}
        name: {{ .Release.Name }}
    policy: |
        {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                      "AWS": "arn:aws:iam::{{ .Values.awsAccountID }}:root"
                    },
                    "Action": "vpc-lattice-svcs:Invoke",
                    "Resource": "*",
                    "Condition": {
                        "StringEquals": {
                            "aws:PrincipalTag/eks-cluster-name": "{{ .Values.allowedCluster }}",
                            "aws:PrincipalTag/kubernetes-namespace": "{{ .Values.allowedNamespace }}"
                        }
                    }
                }
            ]
        }

 

EKS 클러스터의 Security Group의 ingress 규칙을 수정합니다. vpc lattice의 ipv4 주소 범위를 허용합니다.

################################################################################
# Update cluster security group to allow access from VPC Lattice
################################################################################

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"
}

 

EKS 클러스터에 pod identity를 적용합니다. 이 pod identity는 데모 애플리케이션에 적용됩니다.

################################################################################
# Associate EKS pod identity for out application
################################################################################

resource "aws_eks_pod_identity_association" "apps" {
  cluster_name    = module.eks.cluster_name
  namespace       = local.app_namespace
  service_account = "default"
  role_arn        = data.terraform_remote_state.environment.outputs.vpc_lattice_client_role_arn
}

 

[cluster 디렉터리 / vpc.tf 살펴보기]

EKS 클러스터를 위한 VPC를 정의합니다.

################################################################################
# Cluster VPC
################################################################################

module "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
}

 

해당 VPC를 Route53 Private Hosted Zone에 associate 합니다.

################################################################################
# Associate private_hosted_zone with VPC
################################################################################

resource "aws_route53_zone_association" "private_zone_association" {
  zone_id = data.terraform_remote_state.environment.outputs.private_zone_id
  vpc_id  = module.vpc.vpc_id
}

 

인프라 프로비저닝 및 데모 애플리케이션 배포를 위해 아래 내용을 진행합니다.

 

1. 아래 명령을 수행하여 cross-cluster-pod-communication/cluster/ 디렉터리로 이동합니다.

cd ../cluster/

 

2. main.tf 파일을 열고, 31번째 라인의 region을 ap-northeast-2로 수정합니다.

 

3. 이어서 아래 명령을 통해 첫번째 EKS 클러스터를 배포합니다.

./deploy.sh cluster1

# 배포 완료 후, 아래 명령으로 kubectl config 설정을 완료합니다.
eval `terraform output -raw configure_kubectl`

 

4. 이어서 아래 명령을 통해 두번째 EKS 클러스터를 배포합니다.

./deploy.sh cluster2

# 배포 완료 후, 아래 명령으로 kubectl config 설정을 완료합니다.
eval `terraform output -raw configure_kubectl`

 

프로비저닝 된 인프라 및 애플리케이션 확인

Terraform을 통해 어떤 인프라가 구성되었는지 확인해보겠습니다.

 

Amazon EKS 클러스터

Amazon EKS 콘솔에 접속해보면 아래와 같이 두 개의 EKS 클러스터가 생성된 것을 확인할 수 있습니다.

 

 

eks-cluster1을 들어가보겠습니다.

Access > Pod Identity associations 순으로 콘솔을 이동하면 아래와 같은 내용을 확인할 수 있습니다.

vpc-lattice-sig4-client라는 IAM role이 apps라는 네임스페이스의 default라는 Service account에 associate 된 것을 확인할 수 있습니다.

 

해당 IAM role을 클릭해보면 아래와 같이 IAM 콘솔로 이동하여 자세히 확인할 수 있습니다.

이 IAM role은 ACM의 PCA에 대한 여러 액세스 권한과 VPC Lattice Service에 대한 invoke 권한이 설정되어 있습니다.

 

확인한 바와 같이 해당 Pod Identity 및 IAM role은 데모 애플리케이션 Pod에 적용됩니다.

 

VPC Lattice

VPC 콘솔 > PrivateLink and Lattice > Service networks 순으로 콘솔을 이동하면 아래와 같이 VPC Lattice Service network 콘솔로 이동할 수 있습니다.

VPC 콘솔 > PrivateLink and Lattice > Lattice services 순으로 콘솔을 이동하면 아래와 같이 VPC Lattice의 Service 콘솔로 이동할 수 있습니다. 각 Service가 커스텀 도메인 명에 매핑된 것을 확인할 수 있습니다.

 

그리고 아래와 같이 각 Service가 Private Hosted Zone에 associate 되어있고 PCA를 사용하고 있는 것을 확인할 수 있습니다.

 

Route53 Private Hosted Zone 확인

 

AWS Certificate Manager(ACM) 확인

 

EKS 클러스터 별 애플리케이션

아래 명령을 수행하여 eks-cluster1으로 context를 스위칭 할 수 있습니다.

kubectl config use-context eks-cluster1

 

eks-cluster1의 Pod 목록을 조회하기 위해 아래 명령을 수행합니다.

kubectl get po -A

 

이어서 아래 명령을 통해 데모 애플리케이션 Pod(demo-cluster1-v1)의 구성을 확인해보겠습니다.

kubectl describe po demo-cluster1-v1-<Pod명> -n apps

 

Init Containers를 통해 envoy-sigv4 컨테이너를 위한 IPTables 세팅이 수행되었습니다.

 

그리고 아래와 같이 envoy-sigv4 컨테이너와 데모 애플리케이션 컨테이너가 잘 배포되어 있습니다.

Containers:
  envoy-sigv4:
    Container ID:  containerd://c686569fc1b2545f93dae4645529d94ffeb97c47e531e0c17156657f39665c8a
    Image:         public.ecr.aws/seb-demo/envoy-sigv4:v0.5
    Image ID:      public.ecr.aws/seb-demo/envoy-sigv4@sha256:097a68853c38c9cc2cf44d1de31e10538dd5b312cbc9092b12d2e49f7f92fdee
    Port:          8080/TCP
    Host Port:     0/TCP
    Args:
      -l
      info
    State:          Running
      Started:      Sat, 26 Apr 2025 22:12:22 +0900
    Ready:          True
    Restart Count:  0
    Environment:
      APP_DOMAIN:                              example.com
      CA_ARN:                                  arn:aws:acm-pca:ap-northeast-2:12345678910:certificate-authority/b1f4b72e-5cdf-449c-addf-70a065e3f8cb
      AWS_STS_REGIONAL_ENDPOINTS:              regional
      AWS_DEFAULT_REGION:                      ap-northeast-2
      AWS_REGION:                              ap-northeast-2
      AWS_CONTAINER_CREDENTIALS_FULL_URI:      http://169.254.170.23/v1/credentials
      AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE:  /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-5btss (ro)
      /var/run/secrets/pods.eks.amazonaws.com/serviceaccount from eks-pod-identity-token (ro)
  demo-cluster1-v1:
    Container ID:   containerd://5e2856e2df93b0fb167ddc7ef6db6b992b5758cf92f6eff619ad8f5c4a501901
    Image:          public.ecr.aws/seb-demo/http-server:latest
    Image ID:       public.ecr.aws/seb-demo/http-server@sha256:05b913f6c411303f8967ed556b12c80ca49c63a9a37005bc8651c18df54266a6
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Sat, 26 Apr 2025 22:12:35 +0900
    Ready:          True
    Restart Count:  0
    Environment:
      PodName:                                 Hello from demo-cluster1-v1
      AWS_STS_REGIONAL_ENDPOINTS:              regional
      AWS_DEFAULT_REGION:                      ap-northeast-2
      AWS_REGION:                              ap-northeast-2
      AWS_CONTAINER_CREDENTIALS_FULL_URI:      http://169.254.170.23/v1/credentials
      AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE:  /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-5btss (ro)
      /var/run/secrets/pods.eks.amazonaws.com/serviceaccount from eks-pod-identity-token (ro)

 

통신 테스트 및 동작방식 확인

이제 두 클러스터 간 연결성을 확인하기 위해 아래의 명령을 실행해보겠습니다.

kubectl --context eks-cluster1 \
  exec -ti -n apps deployments/demo-cluster1-v1 -c demo-cluster1-v1 \
  -- curl demo-cluster2.example.com

이 명령은 eks-cluster1의 데모 애플리케이션에서 eks-cluster2의 데모 애플리케이션으로 간단한 요청을 전송합니다.

 

예상되는 응답은 아래와 같습니다.

Requsting to Pod(demo-cluster2-v1-578bcfbc7-8vhxl): 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

이 명령은 eks-cluster1의 데모 애플리케이션에서 그대로 eks-cluster1의 데모 애플리케이션으로 요청을 수행합니다.

 

그러면 아래와 같은 권한 오류 메시지를 확인할 수 있습니다.

AccessDeniedException: User: arn:aws:sts::12345678910:assumed-role/vpc-lattice-sigv4-client/eks-eks-cluste-demo-clust-1361ddca-754c-42f7-990c-cc93f2a8d744 is not authorized to perform: vpc-lattice-svcs:Invoke on resource: arn:aws:vpc-lattice:us-east-1:12345678910:service/svc-0340c2435f9aff773/ because no service-based policy allows the vpc-lattice-svcs:Invoke action

 

이렇게 에러가 발생하는 이유는, eks-cluster1의 IAMAuthPolicy에서 eks-cluster2 으로만 호출 가능하도록 했기 때문입니다.

 

아래 명령을 실행해보겠습니다.

kubectl --context eks-cluster1 \
  get IAMAuthPolicy -n apps demo-cluster1-iam-auth-policy  \
  -o json | jq ".spec.policy | fromjson"

 

그럼 아래와 같은 응답 결과를 확인할 수 있습니다.

 

위의 정책 내용을 해석하면 아래와 같습니다.

  • VPC Lattice Serivce를 invoke하는 액션 중에서 클러스터 명(eks-cluster-name)이 eks-cluster2 여야하고, 네임스페이스가 apps일 때에만 Invoke를 허용한다.

 

아래 명령을 통해 IAMAuthPolicy의 자세한 내용을 확인해보겠습니다.

kubectl describe IAMAuthPolicy demo-cluster1-iam-auth-policy -n apps

 

해당 IAMAuthPolicy의 적용 대상(Target Ref)이 demo-cluster1 라는 이름을 가진, apps 네임스페이스의 HTTPRoute 리소스임을 확인할 수 있습니다.

 

대상이 되는 HTTPRoute를 조회하기 위해 아래 명령을 수행합니다.

kubectl describe HTTPRoute demo-cluster1 -n apps

 

이 HTTPRoute 설정을 통해 라우팅 되는 대상을 아래와 같이 확인할 수 있습니다.

 

HTTPRoute로 라우팅 되는 대상이 demo-cluster1-v1 이라는 Service임을 알 수 있습니다.

 

마찬가지로 아래 명령을 통해 해당 Service 명세를 확인해보겠습니다.

kubectl describe svc demo-cluster1-v1 -n apps

 

이 Service를 통해 라우팅 되는 대상이 10.0.26.50:8090 임을 확인할 수 있습니다. (demo-cluster1-v1)

 

이어서 VPC 콘솔 > PrivateLink and Lattice > Lattice service 로 이동하여 demo-cluster1-apps의 체크박스를 클릭하고, Routing 탭을 클릭하면 아래와 같이 라우팅 대상 그룹을 확인할 수 있습니다.

 

결과적으로 이 데모 애플리케이션 Pod가 IAMAuthPolicy의 규칙을 적용받고 있음을 확인할 수 있습니다.

이렇게 IAMAuthPolicy를 사용하면 멀티 클러스터 간에 각 애플리케이션에 대한 액세스 제어를 쉽게 구성할 수 있습니다.

 

Kyverno의 역할

이번 실습에서는 Kyverno의 ClusterPolicy를 사용하여 데모 애플리케이션 Pod에 iptables 규칙과 envoy 사이드카 프록시를 주입했습니다:

  • iptables 규칙은 애플리케이션에서 envoy 프록시로 트래픽을 라우팅합니다(소스 프로세스 gid가 0인 경우 규칙이 적용되지 않으므로 애플리케이션에 다른 gid를 제공합니다: runAsGroup: 1000).
  • envoy 프록시는 시작 시 Private CA 인증서를 검색하여 시작 스크립트를 통해 VPC lattice 서비스를 신뢰하도록 설치합니다:

아래 명령을 수행하여 envoy 프록시 컨테이너가 뜰 때 실행하는 스크립트를 참조합니다.

kubectl --context eks-cluster1 \
	exec -it deploy/demo-cluster1-v1 -c envoy-sigv4 -n apps \
	-- cat /usr/local/bin/launch_envoy.sh

 

출력 결과는 아래와 같습니다.

#!/bin/sh

# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

cat /etc/envoy/envoy.yaml.in | envsubst \$AWS_REGION,\$JWT_AUDIENCE,\$JWT_JWKS,\$JWT_ISSUER,\$JWKS_HOST,\$APP_DOMAIN > /etc/envoy/envoy.yaml
aws acm-pca get-certificate-authority-certificate --certificate-authority-arn $CA_ARN --region $AWS_REGION --output text > /etc/pki/ca-trust/source/anchors/internal.pem
update-ca-trust extract

cat /etc/envoy/envoy.yaml
/usr/local/bin/envoy --base-id 1 -l trace -c /etc/envoy/envoy.yaml

 

설명

더보기

1. 환경 변수 치환을 통한 Envoy 구성 파일 생성

cat /etc/envoy/envoy.yaml.in | envsubst \$AWS_REGION,\$JWT_AUDIENCE,\$JWT_JWKS,\$JWT_ISSUER,\$JWKS_HOST,\$APP_DOMAIN > /etc/envoy/envoy.yaml
  • 템플릿 파일(envoy.yaml.in)을 읽어서
  • 환경 변수들(AWS_REGION, JWT_AUDIENCE, JWT_JWKS, JWT_ISSUER, JWKS_HOST, APP_DOMAIN)을 실제 값으로 대체
  • 결과를 Envoy 구성 파일(/etc/envoy/envoy.yaml)로 저장

 

2. AWS 사설 인증 기관(PCA)에서 인증서 가져오기

aws acm-pca get-certificate-authority-certificate --certificate-authority-arn $CA_ARN --region $AWS_REGION --output text > /etc/pki/ca-trust/source/anchors/internal.pem
  • AWS 명령줄 도구를 사용하여 지정된 인증 기관(CA)에서 인증서를 가져옴
  • $CA_ARN 환경 변수에 지정된 인증 기관의 Amazon 리소스 이름을 사용
  • 획득한 인증서를 시스템의 신뢰 저장소 경로에 저장

 

3. 시스템 인증서 신뢰 저장소 업데이트

update-ca-trust extract
  • 방금 추가한 인증서를 시스템이 신뢰하도록 CA 신뢰 저장소를 갱신

 

4. 구성 파일 내용 출력 (디버깅 용도)

cat /etc/envoy/envoy.yaml
  • 생성된 Envoy 구성 파일의 내용을 출력하여 확인

 

5. Envoy 프록시 실행

/usr/local/bin/envoy --base-id 1 -l trace -c /etc/envoy/envoy.yaml
  • Envoy 프록시를 시작
  • --base-id 1: 기본 ID 설정 (여러 Envoy 인스턴스 구분용)
  • -l trace: 로그 레벨을 trace로 설정하여 상세 로깅 활성화
  • -c /etc/envoy/envoy.yaml: 앞서 생성한 구성 파일을 사용

이 스크립트의 주요 목적은 AWS VPC Lattice 서비스와 안전하게 통신하기 위해 필요한 인증서를 설정하고, 환경에 맞게 구성된 Envoy 프록시를 실행하는 것입니다. SigV4 인증을 사용하여 AWS 서비스에 대한 요청에 서명하는 프로세스를 자동화합니다.

launch_envoy.sh에서 활용한 envoy.yaml.in 스크립트도 함께 살펴보겠습니다. 해당 스크립트는 여기서 참조할 수 있습니다.

# envoy.yaml.in

static_resources:
  listeners:
  - name: http_connect
    address:
      socket_address:
        protocol: TCP
        address: 0.0.0.0
        port_value: 8080
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains:
              - "*"
              routes:
              - match:
                  prefix: '/'
                route:
                  cluster: outbound_proxy
          # Ignore traffic to /health and respond with a 200
          http_filters:
          - name: envoy.filters.http.dynamic_forward_proxy
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig
              dns_cache_config:
                name: dynamic_forward_proxy_cache_config
                dns_lookup_family: V4_ONLY
                typed_dns_resolver_config:
                  name: envoy.network.dns_resolver.cares
                  typed_config:
                    "@type": type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig
                    use_resolvers_as_fallback: true
                    resolvers:
                    - socket_address:
                        address: "127.0.0.1"
                        port_value: 53
                    dns_resolver_options:
                      use_tcp_for_dns_lookups: true
                      no_default_search_domain: true
          # SigV4 signing configuration
          - name: envoy.filters.http.aws_request_signing
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.aws_request_signing.v3.AwsRequestSigning
              service_name: vpc-lattice-svcs
              region: ${AWS_REGION}
              use_unsigned_payload: true
              match_excluded_headers:
              - prefix: x-envoy
              - prefix: x-forwarded
              - exact: x-amzn-trace-id
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
  clusters:
  - name: outbound_proxy
    lb_policy: CLUSTER_PROVIDED
    cluster_type:
      name: envoy.clusters.dynamic_forward_proxy
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig
        dns_cache_config:
          name: dynamic_forward_proxy_cache_config
          dns_lookup_family: V4_ONLY
          typed_dns_resolver_config:
            name: envoy.network.dns_resolver.cares
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig
              use_resolvers_as_fallback: true
              resolvers:
              - socket_address:
                  address: "127.0.0.1"
                  port_value: 53
              dns_resolver_options:
                use_tcp_for_dns_lookups: true
                no_default_search_domain: true
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
        common_tls_context:
          validation_context:
            trusted_ca:
              filename: /etc/ssl/certs/ca-bundle.crt

 

설명

더보기

이 YAML 파일은 Envoy 프록시의 구성을 정의하며, 특히 AWS VPC Lattice 서비스와의 안전한 통신을 위해 설계되었습니다. 주요 부분별로 설명하겠습니다.

 

1. Listeners (리스너) 구성

listeners:
  - name: http_connect
    address:
      socket_address:
        protocol: TCP
        address: 0.0.0.0
        port_value: 8080

 

  • name: http_connect: 리스너의 이름을 정의
  • address: 모든 네트워크 인터페이스(0.0.0.0)의 8080 포트에서 TCP 연결을 수신함

 

2. HTTP 연결 관리자 및 라우팅 설정

filter_chains:
- filters:
  - name: envoy.filters.network.http_connection_manager
    typed_config:
      ...
      route_config:
        name: local_route
        virtual_hosts:
        - name: local_service
          domains: ["*"]
          routes:
          - match: { prefix: '/' }
            route: { cluster: outbound_proxy }
  • 모든 도메인(*)에 대한 HTTP 요청을 처리
  • 모든 경로(/)로 시작하는 요청을 outbound_proxy 클러스터로 라우팅

 

3. 동적 포워드 프록시 필터

http_filters:
- name: envoy.filters.http.dynamic_forward_proxy
  typed_config:
    ...
    dns_cache_config:
      name: dynamic_forward_proxy_cache_config
      dns_lookup_family: V4_ONLY
  • 요청 중 호스트 이름을 동적으로 확인하는 필터
  • IPv4 주소만 사용하도록 구성됨
  • 로컬 DNS 리졸버(127.0.0.1:53)를 사용

 

4. AWS SigV4 서명 필터

- name: envoy.filters.http.aws_request_signing
  typed_config:
    ...
    service_name: vpc-lattice-svcs
    region: ${AWS_REGION}
    use_unsigned_payload: true
    match_excluded_headers:
    - prefix: x-envoy
    - prefix: x-forwarded
    - exact: x-amzn-trace-id
  • AWS Signature Version 4(SigV4) 알고리즘을 사용하여 요청에 서명
  • VPC Lattice 서비스(vpc-lattice-svcs)로 가는 요청에 서명
  • 환경 변수 ${AWS_REGION}을 통해 AWS 리전 지정
  • 특정 헤더는 서명 프로세스에서 제외됨

 

5. 라우터 필터

- name: envoy.filters.http.router
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
  • 구성된 라우트에 따라 요청을 업스트림 클러스터로 전달하는 필수 필터

 

6. 클러스터 구성

clusters:
- name: outbound_proxy
  lb_policy: CLUSTER_PROVIDED
  cluster_type:
    name: envoy.clusters.dynamic_forward_proxy
    ...
  • outbound_proxy 클러스터는 동적 포워드 프록시로 동작
  • 동적으로 호스트를 발견하고 연결 관리

 

7. TLS 설정

transport_socket:
  name: envoy.transport_sockets.tls
  typed_config:
    ...
    common_tls_context:
      validation_context:
        trusted_ca:
          filename: /etc/ssl/certs/ca-bundle.crt
  • 업스트림 서비스(VPC Lattice)와 통신할 때 TLS를 사용
  • 시스템의 CA 인증서 번들을 사용하여 서버 인증서 검증

 

요약

  1. 8080 포트에서 HTTP 요청 수신
  2. 모든 요청을 동적 DNS 조회를 통해 해당 서비스로 전달
  3. AWS VPC Lattice로 가는 요청에 SigV4 인증 서명 적용
  4. TLS를 사용하여 안전한 통신 보장

 

envoy의 Dockerfile

FROM envoyproxy/envoy:distroless-v1.30.0 as envoy

FROM public.ecr.aws/amazonlinux/amazonlinux:latest
RUN yum -y update && \
    yum clean all && \
    rm -rf /var/cache/yum

COPY --from=envoy /usr/local/bin/envoy /usr/local/bin/envoy
RUN yum install -y python3 jq tar bind-utils procps gettext which unzip tar less util-linux
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
    unzip awscliv2.zip && \
    ./aws/install

COPY envoy.yaml.in /etc/envoy/envoy.yaml.in
COPY launch_envoy.sh /usr/local/bin/launch_envoy.sh

RUN chmod 755 /usr/local/bin/launch_envoy.sh
ENTRYPOINT ["launch_envoy.sh"]

 

설명

더보기

이 Dockerfile은 AWS VPC Lattice와 통신하기 위한 사용자 정의 Envoy 프록시 이미지를 생성합니다. 주요 부분별로 살펴보겠습니다.

 

1. 멀티 스테이지 빌드 설정

FROM envoyproxy/envoy:distroless-v1.30.0 as envoy

FROM public.ecr.aws/amazonlinux/amazonlinux:latest
  • 첫 번째 단계: 공식 Envoy 프록시의 distroless 버전을 envoy라는 별칭으로 가져옵니다.
  • 두 번째 단계: Amazon Linux를 최종 기본 이미지로 사용합니다.

 

2. 시스템 패키지 업데이트

RUN yum -y update && \
    yum clean all && \
    rm -rf /var/cache/yum
  • 시스템 패키지를 최신 상태로 업데이트하고
  • 패키지 캐시를 정리하여 이미지 크기를 최적화합니다.

 

3. Envoy 바이너리 복사

COPY --from=envoy /usr/local/bin/envoy /usr/local/bin/envoy
  • 첫 번째 단계(distroless Envoy)에서 Envoy 실행 파일만 추출하여 현재 이미지로 복사합니다.

 

4. 필수 도구 설치

RUN yum install -y python3 jq tar bind-utils procps gettext which unzip tar less util-linux
  • 다음 도구들을 설치합니다:
    • python3: Python 스크립트 실행용
    • jq: JSON 처리 도구
    • bind-utils: DNS 조회 도구(dig, nslookup 등)
    • gettext: 환경 변수 치환 도구(envsubst)
    • procps: 프로세스 관리 도구
    • 그 외 여러 유틸리티 도구들

 

5. AWS CLI 설치

RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
    unzip awscliv2.zip && \
    ./aws/install
  • AWS 명령줄 인터페이스(CLI) 버전 2를 다운로드하고 설치
  • AWS 서비스(특히 인증서 관리)와의 상호작용에 필요합니다.

 

6. Envoy 구성 파일 및 시작 스크립트 복사

COPY envoy.yaml.in /etc/envoy/envoy.yaml.in
COPY launch_envoy.sh /usr/local/bin/launch_envoy.sh
  • envoy.yaml.in: 이전에 살펴본 Envoy 구성의 템플릿 파일을 복사
  • launch_envoy.sh: 이전에 설명한 시작 스크립트를 복사

 

7. 실행 권한 설정 및 시작점 정의

RUN chmod 755 /usr/local/bin/launch_envoy.sh
ENTRYPOINT ["launch_envoy.sh"]
  • 시작 스크립트에 실행 권한을 부여
  • 컨테이너가 시작될 때 자동으로 launch_envoy.sh 스크립트를 실행하도록 설정

 

실습 리소스 정리

클러스터 정리

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

'k8s > AWS EKS' 카테고리의 다른 글

[AWS] ML Infra(GPU) on EKS  (0) 2025.04.18
[AWS] EKS Upgrade  (0) 2025.04.02
[AWS] EKS Mode/Nodes  (1) 2025.03.22
[AWS] EKS Security  (0) 2025.03.16
[AWS] EKS AutoScaling  (0) 2025.03.08