CloudNet@ Team Study/EKS Workshop 4th Cohort

Traffic Tool: LBC API Gateway

"Everything about infra" 2026. 3. 27. 04:28
포스팅은 CloudNet@팀 서종호(Gasida)님이 진행하시는
AWS EKS Workshop Study 내용을 참고하여 작성합니다.

 

안녕하세요!

오늘은 LoadBalancer Controller를 이용한

차세대 트래픽 관리 도구인 API Gateway를 사용해보도록 하겠습니다.

 

 

 

01. API Gateway란?

📌 개요

Kubernetes community에서 대표적인 오픈소스 도구인 ingress nginx controller가 2026년 3월 이후 EOL

종료 이후에는 업데이트, 보안 패치, 버그 수정 등이 완전히 중단 된다고 합니다.
이는 오랜 기간 소수의 개발자(1~2명)가 야간·주말에 유지보수를 이어온 구조적 한계와,
올해 발생한 중대한 보안 취약점(IngressNightmare, CVE-2025-1974) 을 해결하는 과정에서
프로젝트가 보유한 자원만으로는 안정적인 지원이 어렵다는 판단이 배경이 되었습니다.

이에 따라 다른 ingress controller나 API Gateway로 마이그레이션을 권장하고 있습니다.

 

API Gateway는 클라이언트와 백엔드 서비스 사이의 단일 진입점 입니다.

  • 모든 요청을 받아서 적절한 서비스로 라우팅한다.
  • 그 과정에서 인증/인가, 트래픽 제어, 로깅, 프로토콜 변환 등을 처리합니다.

Ingress nginx controller와 비교하면, Ingress는 단순히 L7 트래픽을 어디에 보낼지?에 집중하는 반면,

API Gateway는 그보다 훨씬 넓은 횡단 관심사(cross-cutting concerns)를 처리합니다.

 

API Gateway가 처리하는 기능들을 소개합니다.

  1. 인증/인가 (AuthN/AuthZ)
    1. JWT 토큰 검증, OAuth 2.0 / OIDC 처리
    2. API Key 검증
    3. 백엔드 서비스는 인증 로직을 몰라도 됨 (offloading)
  2. Rate Limiting / Throttling
    1. IP별, 사용자별, API Key별 요청 수 제한
    2. 서킷 브레이커 패턴 (Circuit Breaker)
  3. 라우팅
    1. /api/v1/users → User Service
    2. /api/v1/orders → Order Service
    3. ath rewriting, Header 기반 라우팅
  4. 프로토콜 변환
    1. REST ↔ gRPC 변환
    2. WebSocket 지원
    3. GraphQL 집계
  5. 관찰가능성 (Observability)
    1. 요청/응답 로깅
    2. 분산 트레이싱 (Jaeger, Zipkin)
    3. Prometheus 메트릭 노출

 

그렇다면, API Gateway를 도입한다면 Ingress는 완전히 필요가 없는가?

즉 API Gateway를 어떻게 구현하느냐에 따라서 Ingress를 대체할 수도 있고 공존할 수도 있습니다.

 

주요 API Gateway 제품을 아래와 같이 비교해보도록 하겠습니다.

제품 종류 Ingress 역할 가능 여부 특징
Kong Self-hosted / SaaS
(Kong Ingress Controller)
가장 널리 쓰임, 플러그인 생태계
APISIX Self-hosted 고성능, 활발한 오픈소스
Traefik Self-hosted K8s 네이티브, 설정 간단
AWS API Gateway 완전 관리형 ❌ (K8s 외부) 서버리스/Lambda에 최적,
VPC Link로 EKS 연결
AWS App Mesh + API GW Hybrid 서비스 메시와 결합

 

오늘 포스팅 내용은 패턴A (Ingress Controller 대체) 방안으로 실습을 해볼 예정이며,

API Gateway 표준 스텍 + AWS LoadBalancer Controller로 구현하는 방식입니다.

 


 

02. API Gateway 도입 전 사전 조건.

LoadBalancer Controller 설치 방법은 이전 포스팅 (https://ssunghwan.tistory.com/65) 참고 바랍니다.

LBC를 먼저 파드로 띄운 후에 실습을 진행하도록 하겠습니다.

 

먼저 설치하는 것들이 두 계층으로 나뉩니다.

[1] Kubernetes Gateway API CRDs → 표준 스펙 (vendor 중립)
[2] AWS LBC Gateway 전용 CRDs → AWS 확장 (AWS 전용 세부설정)

 

Step1. LBC 버전 확인

kubectl describe pod -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller \
  | grep Image: | uniq
# Image: public.ecr.aws/eks/aws-load-balancer-controller:v3.1.0

Gateway API는 LBC v2.13.0 이상에서만 지원됩니다.

(이전 버전 LBC는 Ingress 오브젝트만 처리할 수 있고 Gateway / HTTPRoute CRD를 아예 인식하지 못합니다.)

 

Step2. 표준 Gateway API CRDs 설치

kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml     # [REQUIRED] # Standard Gateway API CRDs
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/experimental-install.yaml # [OPTIONAL: Used for L4 Routes] # Experimental Gateway API CRDs

이 CRD들은 Kubernetes SIG-Network에서 관리하는 표준입니다.

이렇게 설치되면 아래와 같은 CRD들이 생깁니다.

CRD 역할 비고
GatewayClass "이 Gateway는 어느 컨트롤러가 처리하나" 클러스터 전역
(namespace 없음)
Gateway 실제 ALB 생성 요청 인프라팀이 관리
HTTPRoute L7 라우팅 규칙 앱팀이 관리
GRPCRoute gRPC 라우팅  
TCPRoute / TLSRoute / UDPRoute L4 라우팅 experimental만 설치하면 생김
ReferenceGrant 다른 namespace의 리소스 참조 허용 크로스 네임스페이스 보안
BackendTLSPolicy 백엔드까지 TLS 설정  

 

API Gateway를 사용할 때 필요한 CRDs를 아래와 같이 확인이 가능합니다.

 

Step3. AWS LBC 전용 CRDs 설치

kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/refs/heads/main/config/crd/gateway/gateway-crds.yaml

기존에 Ingress annotation에 모두 몰아넣던 것들이 3개의 전용 오브젝트로 분리된 겁니다.

이렇게 설치되면 아래와 같은 CRD들이 생깁니다.

CRD 역할 기존 Ingress annotation 대응
LoadBalancerConfiguration ALB 자체 설정 alb.ingress.kubernetes.io/scheme, ip-address-type,
load-balancer-attributes 등
ListenerRuleConfiguration 리스너/규칙 설정 alb.ingress.kubernetes.io/listen-ports, ssl-policy,
actions.* 등
TargetGroupConfiguration 타겟 그룹 설정 alb.ingress.kubernetes.io/target-type, healthcheck-*,
deregistration-delay 등

 

왜 이렇게 분리했을까요?

기존 Ingress 방식의 문제는 아래와 같습니다.

# 기존: 한 오브젝트에 다 몰려있음
kind: Ingress
metadata:
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/certificate-arn: arn:...
    alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS13-1-2
    alb.ingress.kubernetes.io/healthcheck-path: /health
    alb.ingress.kubernetes.io/healthcheck-interval-seconds: "15"
    # ... 끝도 없이 계속

 

반면 API Gateway 방식에서는

# LoadBalancerConfiguration: ALB 수준 설정 (인프라팀)
kind: LoadBalancerConfiguration
spec:
  scheme: internet-facing
  ipAddressType: ipv4

---
# TargetGroupConfiguration: 타겟 그룹 설정 (인프라팀)
kind: TargetGroupConfiguration
spec:
  targetType: ip
  healthCheckConfig:
    path: /health
    intervalSeconds: 15

---
# HTTPRoute: 라우팅 규칙 (앱팀)
kind: HTTPRoute
spec:
  rules:
    - matches:
        - path:
            value: /api/products
      backendRefs:
        - name: product-service
          port: 8080

이런식으로 각 Team마다 역할과 소유권이 명확해집니다.

 

그래서 역할 계층 구조를 보면 아래와 같습니다.

 


 

LBC에 API Gateway 활성화

📌 feature flag 활성화

왜 필요할까요? LBC는 기본적으로 하위 호환성 때문에 Gateway API를 기본 비활성화로 출시했습니다.

기존에 Ingress로 운영 중인 클러스터에 LBC를 업그레이드했을 때
갑자기 Gateway API 리소스까지 감시하면서 예상치 못한 동작이 생기는 걸 방지하기 위해서입니다.

지금은 LBC가 Ingress 오브젝트만 감시하고 있고, Gateway / HTTPRoute CRD는 무시하는 상태입니다.

 

feature flag를 활성화 해줍시다.

kubectl patch deployment aws-load-balancer-controller \
  -n kube-system \
  --type=json \
  -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--feature-gates=NLBGatewayAPI=true,ALBGatewayAPI=true"}]'

 

patch가 되면서 Deployment가 자동으로 롤링 업데이트됩니다

ALBGatewayAPI=true : (HTTP/HTTPS) 오브젝트 감시 시작 → ALB 프로비저닝
NLBGatewayAPI=true : TCP/UDP) 오브젝트 감시 시작 → NLB 프로비저닝

둘 다 켜야 GatewayClass에서 ALB/NLB 구분해서 사용 가능합니다.

LBC의 파드의 args 섹션을 확인해보면 내용이 추가되었습니다.

 


 

03. API Gateway를 사용한 http 접속

📌 loadbalancerconfigurations 생성

cat << EOF | kubectl apply -f -
apiVersion: gateway.k8s.aws/v1beta1
kind: LoadBalancerConfiguration
metadata:
  name: lbc-config
  namespace: default
spec:
  scheme: internet-facing
EOF

기존 Ingress에서 annotation으로 하던 ALB 설정을 별도 오브젝트로 분리한 겁니다.

# 기존 Ingress 방식
annotations:
  alb.ingress.kubernetes.io/scheme: internet-facing  # ← 이게

# Gateway API 방식
kind: LoadBalancerConfiguration
spec:
  scheme: internet-facing                             # ← 이렇게 분리됨

나중에 Gateway 오브젝트가 이 설정을 참조해서 ALB를 프로비저닝할 때 사용됩니다.

 

📌 gatewayclasses 생성

cat << EOF | kubectl apply -f -
# 기존 IngressClass 방식
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: alb
spec:
  controller: ingress.k8s.aws/alb  # ← 컨트롤러 지정

# Gateway API 방식
kind: GatewayClass
spec:
  controllerName: gateway.k8s.aws/alb  # ← 컨트롤러 지정
  parametersRef:                        # ← 추가로 설정 참조 가능
    kind: LoadBalancerConfiguration
    name: lbc-config
    namespace: default
EOF

클러스터 전역 오브젝트입니다.

"이 클러스터에서 ALB를 만들 때 어느 컨트롤러가 처리하고, 어떤 기본 설정을 쓸지" 를 선언합니다.
나중에 Gateway를 만들 때 gatewayClassName: aws-alb 로 이걸 참조하면 자동으로 internet-facing ALB가 생성됩니다.

 

📌 gateway 생성

cat << EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: alb-http
spec:
  gatewayClassName: aws-alb      # 방금 만든 GatewayClass 참조
  listeners:
    - name: http
      protocol: HTTP             # L7 HTTP
      port: 80                   # 80 포트 리스너
EOF

실제 ALB 프로비저닝을 요청하는 오브젝트입니다. 이 오브젝트를 생성하는 순간

LBC가 AWS에 ALB를 만들기 시작합니다.

# 실시간 로그 확인
kubectl logs -l app.kubernetes.io/name=aws-load-balancer-controller -n kube-system -f

아래와 같이 게이트웨이를 생성하니, ALB가 만들어졌습니다.

 

📌 게임 파드/서비스 배포

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-2048
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: app-2048
  replicas: 2
  template:
    metadata:
      labels:
        app.kubernetes.io/name: app-2048
    spec:
      containers:
      - image: public.ecr.aws/l6m2t8p7/docker-2048:latest
        imagePullPolicy: Always
        name: app-2048
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: service-2048
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  type: ClusterIP
  selector:
    app.kubernetes.io/name: app-2048
EOF

아래와 같이 배포를 완료하였지만, 현재 ALB에 타겟 그룹이 존재하지 않습니다.

그래서 아래에서 백엔드로 라우팅 하는 설정을 해보도록 하겠습니다.

 

📌 TargetGroupConfiguration 생성

cat << EOF | kubectl apply -f -
apiVersion: gateway.k8s.aws/v1beta1
kind: TargetGroupConfiguration
metadata:
  name: backend-tg-config
spec:
  targetReference:
    name: service-2048
  defaultConfiguration:
    targetType: ip
    protocol: HTTP
EOF

타겟 그룹의 세부 설정을 정의하는 오브젝트입니다. 기존 Ingress annotation으로 하던 것들이 분리된 겁니다.

# 기존 Ingress annotation 방식
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/backend-protocol: HTTP
alb.ingress.kubernetes.io/healthcheck-path: /health

# Gateway API 방식 → TargetGroupConfiguration으로 분리
kind: TargetGroupConfiguration
spec:
  targetReference:
    name: service-2048      # 어느 Service에 적용할지
  defaultConfiguration:
    targetType: ip          # ip or instance
    protocol: HTTP

EKS에서는 targetType을 ip로 하는 것을 권장합니다. (Pods가 직접 타겟이 되어 불필요한 홉을 줄입니다)

TargetGroupConfiguration은 Service와 1:1로 연결됩니다.

 

AWS 리소스와 맵핑해보면 아래와 같습니다.

ALB
├── LoadBalancerConfiguration  ← ALB 자체 설정
│     scheme, ipAddressType
│     access logs, idle timeout
│     security groups
│
├── Listener (80, 443)
│     └── ListenerRuleConfiguration  ← 리스너/규칙 설정
│           ssl policy, redirect rules
│
└── Target Group
      └── TargetGroupConfiguration  ← 타겟 그룹 설정
            targetType: ip/instance
            healthcheck path/interval
            deregistration delay
LoadBalancerConfiguration은 ALB 전체에 한 번만 적용되고,
TargetGroupConfiguration은 Service마다 개별 설정이 가능합니다.
기존 Ingress 방식에선 이게 annotation 하나에 섞여 있어서 관리가 어려웠던 겁니다.

백엔드 서비스와 맵핑을 완료 하였습니다. 타겟 그룹 설정은 아래와 정의하였으니, 설정을 해볼까요?

 

📌 HTTProute 설정 - 타겟 그룹 설정

실제 AWS 타겟 그룹은 HTTPRoute가 Service를 참조하는 순간 LBC가 프로비저닝합니다.

# 변수 설정
GWMYDOMAIN=gwapi.ssunghwan.cloud

# HTTPRoute 생성
cat << EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: alb-http-route
spec:
  parentRefs:
  - group: gateway.networking.k8s.io
    kind: Gateway
    name: alb-http
    sectionName: http
  hostnames:
  - $GWMYDOMAIN
  rules:
  - backendRefs:
    - name: service-2048
      port: 80
EOF

아래와 같이 타겟 그룹 설정을 완료하여서 리스너 규칙 및 타겟 그룹이 생성된 것을 확인 가능합니다.

AWS Console에서 확인해볼까요?

현재 게임 서비스를 가용중인 파드의 IP가 타겟 대상으로 잡혔습니다.

 


04. 게임 서비스 파드 접속

📌 접속 테스트

이렇게 Route53에 등록된 도메인으로 직접 서비스 접근이 가능

 


 

도전과제 : API Gateway의 HTTPS 라우팅 적용 - ALB편

먼저, HTTPS를 적용하려면 SSL/TLS 인증서 발급이 필요하므로, ACM에서 인증서 요청을 해봅시다.

 

1) 인증서 요청

# 인증서 요청
aws acm request-certificate \
  --domain-name "*.ssunghwan.cloud" \
  --validation-method DNS \
  --region ap-northeast-2

 

2) 발급 후 검증 자동화

# ARN 저장
CERT_ARN=$(aws acm list-certificates \
  --query 'CertificateSummaryList[?DomainName==`*.ssunghwan.cloud`].CertificateArn' \
  --output text)

echo $CERT_ARN

# CNAME 검증 레코드 자동 생성
aws acm describe-certificate \
  --certificate-arn $CERT_ARN \
  --query 'Certificate.DomainValidationOptions[0].ResourceRecord'

 

3) Route53에 레코드 등록

위 사진과 같이 ACM에서 발급받은 인증서의 CNAME 레코드를 Route53에 등록해줍니다.

5분 내외로 바로 사용이 가능한 인증서 생성이 완료됩니다 !

 

📌 LoadBalancerConfiguration 업데이트 (HTTPS 추가)

cat <<EOF | kubectl apply -f -
apiVersion: gateway.k8s.aws/v1beta1
kind: LoadBalancerConfiguration
metadata:
  name: lbc-config
  namespace: default
spec:
  scheme: internet-facing
  listenerConfigurations:
  - protocolPort: "HTTPS:443"
    defaultCertificate: "arn:aws:acm:ap-northeast-2:093359840099:certificate/db9db8b5-40d8-4093-b0d7-f8187b22dca1"
    sslPolicy: "ELBSecurityPolicy-TLS13-1-2-2021-06"
EOF

sslPolicy 필드에서 현재 권장 사항인 TLS1.3 버전을 사용해줍니다.

 

📌 Gateway에 HTTPS 리스너 추가

cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: alb-http
spec:
  gatewayClassName: aws-alb
  listeners:
  - name: http
    protocol: HTTP
    port: 80
  - name: https
    protocol: HTTPS
    port: 443
    tls:
      mode: Terminate
      certificateRefs:
      - kind: Secret
        name: dummy-ref
EOF

 

📌 HTTPRoute도 https 리스너 참조하도록 업데이트

cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: alb-http-route
spec:
  parentRefs:
  - group: gateway.networking.k8s.io
    kind: Gateway
    name: alb-http
    sectionName: https
  hostnames:
  - $GWMYDOMAIN
  rules:
  - backendRefs:
    - name: service-2048
      port: 80
EOF

 

https를 사용하도록 업데이트를 하였으니, 상태 확인과 로그 확인을 해봅시다.

https 적용이 완료되었다는 로그를 확인할 수 있고, 브라우저에 접근해볼까요?

실제로 브라우저에서 https로 접근 시 인증서 확인을 할 수 있습니다 !

하지만, http로 브라우저에 접근하게 된다면 https로 리다이렉트가 되지 않아서 여전히 취약합니다.

 

📌 HTTPRoute filter 기능 업데이트

cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: http-redirect
  namespace: default
spec:
  parentRefs:
  - group: gateway.networking.k8s.io
    kind: Gateway
    name: alb-http
    sectionName: http
  hostnames:
  - $GWMYDOMAIN
  rules:
  - filters:
    - type: RequestRedirect
      requestRedirect:
        scheme: https
        statusCode: 301
EOF

filter 기능으로 redirect를 추가하여 아래와 같이 테스트 시 정상 동작 하는것을 확인하였습니다.

 

최종 동작 확인 - 브라우저 개발자 도구

Request URL이 http인데, https로 변경된 것을 확인하실 수 있습니다.

그런데 왜 코드가 307일까?
브라우저의 HttpsUpgrads 기능 덕분인데요, Chrome의 HttpsUpgrades는 ALB의 301보다 먼저 동작합니다.
HTTP 요청이 네트워크로 아예 안 나가는 겁니다.

"이 사이트 전에 HTTPS로 접속한 적 있네" → 자체 307로 업그레이드
제가 테스트 하면서 https로 먼저 접근을 확인하여서 그런 것 같습니다.

 


 

긴 글 읽어주셔서 감사합니다.

 

3주차 내용인 EKS 스케줄 (HPA, CPA, Karpenter etc..)으로 찾아뵙겠습니다.