포스트

[k8s] Ingress에서 Gateway API 전환

Azure 환경으로 작성된 글입니다.

2026년 3월을 기점으로 쿠버네티스 커뮤니티가 Nginx Ingress를 EOL시키면서 추후 릴리즈 및 버그 수정이 중단되었습니다.

웹서버 Nginx와는 다릅니다. 쿠버네티스에서 Ingress 리소스 중 내부 구현체로 nginx.conf를 사용하는 Ingress를 Nginx Ingress라고 합니다.

이에 추후 보안 및 안정적인 지원을 받을 수 있는 차세대 리소스로 전환을 해야 하며, 그 대체제 중 하나가 Gateway API입니다. 이에 따라 기존 Nginx Ingress를 Gateway API로 전환하는 작업이 필요합니다.

Ingress가 Nginx를 내부 구현체로 사용하듯 Gateway 또한 여러 구현체가 있습니다.

  • Istio Gateway — 서비스 메시를 이미 쓰는 조직에 적합, istio-ingressgateway를 데이터 플레인으로 사용
  • Envoy Gateway — Envoy Proxy 기반으로 서비스 메시가 아닌 라우팅용에 집중
  • Traefik v3 — 간단하고 운영하기 쉬움, Gateway API 직접 지원, 소규모~중규모 클러스터에 적합

이번에 Azure 환경에서 Azure Nginx Ingress를 Istio Gateway 기반으로 작업을 진행할 예정입니다.

Istio Gateway 특징

image.png

기존 Gateway의 외부 → 내부 라우팅에 추가적으로 서비스 메시가 조합된 특징이 있습니다.

  • 서비스 메시(Service Mesh)란 MSA 서비스 간의 내부 통신을 관리해주는 인프라 레이어로, MSA 특성상 Pod 간 내부 통신이 많아지는데 이 통신에 대해 암호화/트래픽 제어/모니터링을 추가로 해줍니다.

Pod 옆에 사이드카 프록시(Envoy)를 하나씩 띄우는 형태입니다.

다만 이 기능은 Namespace별로 istio-injection: enabled 라벨을 활성화해야 작동되며, 기본적으로는 작동하지 않습니다.

Ingress 파일 확인

기존에 Ingress는 다음과 같은 일을 수행했습니다.

  • 진입점 정의 (어떤 포트로 받을지)
  • TLS 종료 (인증서 지정)
  • HTTP→HTTPS 리다이렉트 (annotation으로: ssl-redirect: "true")
  • 호스트/경로 기반 라우팅 규칙
  • 백엔드 서비스 지정

상세한 동작 및 TLS 인증을 annotation으로 땜빵했고, 이 문법조차 벤더사마다 다릅니다. 특히 Ingress 내부적으로 설정과 라우팅이 모두 함께 있는 상태입니다.

표준적인 Ingress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: front-be-ingress
  namespace: my-service
  annotations:
    kubernetes.azure.com/tls-cert-keyvault-uri: "https://<kv-name>.vault.azure.net/certificates/<cert-name>"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
spec:
  ingressClassName: webapprouting.kubernetes.azure.com
  tls:
  - hosts:
    - front-be.example.com
    secretName: front-be-tls
  rules:
  - host: front-be.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: front-be-service
            port:
              number: 8080

기존에는 서비스별로 bo-be-ingress, bo-fe-ingress, front-be-ingress, front-fe-ingress, common-ingress로 분리되어 있습니다.

디렉토리 구조

1
2
3
4
5
6
7
8
9
10
11
ops/
├── bo-be/
│   └── ingress.yaml
├── bo-fe/
│   └── ingress.yaml
├── front-be/
│   └── ingress.yaml
├── front-fe/
│   └── ingress.yaml
└── common/
    └── ingress.yaml

이거를 Gateway API는 아래와 같이 분리합니다.

 담당내용
Gateway (1개)진입점/리스너포트, 프로토콜, TLS 인증서, 어떤 LB로 받을지
HTTPRoute (6개)라우팅 로직호스트/경로별로 어느 Service로 보낼지, 리다이렉트, 헤더 변경 등

Gateway는 가장 앞단에서 하나로 정리되고, 상세 라우팅 규칙만 서비스별 HTTPRoute로 분리됩니다.

HTTPRoute는 서비스별 라우팅용 5개 + HTTP→HTTPS 리다이렉트용 공용 1개로 구성됩니다. 리다이렉트는 서비스마다 만들 필요 없이 Gateway와 같은 위치에 하나만 두면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
manifests/
├── gateway-system/
│   ├── gateway.yaml          # 공용 Gateway 1개 (전 서비스 공유)
│   └── http-redirect.yaml    # HTTP→HTTPS 리다이렉트 공용 1개
├── bo-be/
│   └── httproute.yaml        # 서비스별 라우팅용 1개씩
├── bo-fe/
│   └── httproute.yaml
├── front-be/
│   └── httproute.yaml
├── front-fe/
│   └── httproute.yaml
└── common/
    └── httproute.yaml

이관된 Gateway 리소스

Gateway.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: shared-gateway
  namespace: gateway-system
spec:
  gatewayClassName: approuting-istio
  listeners:
  - name: http
    port: 80
    protocol: HTTP
    allowedRoutes:
      namespaces:
        from: All
  - name: front-be
    port: 443
    protocol: HTTPS
    hostname: "front-be.example.com"
    tls:
      mode: Terminate
      options:
        kubernetes.azure.com/tls-cert-keyvault-uri: "https://<kv-name>.vault.azure.net/certificates/<cert-name>"
        kubernetes.azure.com/tls-cert-service-account: "<gateway-kv-sa>"  # gateway와 동일 namespace, Workload Identity 연동용
    allowedRoutes:
      namespaces:
        from: All
  - name: bo-be
# 위와 동일한 패턴으로 작성
# 이 아래에 bo-fe, front-fe, common 리스너 추가

Gateway 리소스는 공통으로 하나만 적고 listeners에 서비스별로 항목을 추가합니다.

HTTPRoute.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# HTTP -> HTTPS 용 (공용 1개, gateway-system에 배포)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: global-http-redirect
  namespace: gateway-system
spec:
  parentRefs:
  - name: shared-gateway
    namespace: gateway-system
    sectionName: http
  hostnames:
  - "example.com"        # 루트 도메인
  - "*.example.com"      # 1단계 서브도메인 전체
  rules:
  - filters:
    - type: RequestRedirect
      requestRedirect:
        scheme: https
        statusCode: 301
---
# 실제 서비스 라우팅용 (서비스별로 각 1개)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: front-be-route
  namespace: my-service
spec:
  parentRefs:
  - name: shared-gateway
    namespace: gateway-system
    sectionName: front-be
  hostnames: ["front-be.example.com"]
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: front-be-service
      port: 8080

상세 설명

Ingress에서 Gateway로 마이그레이션하는 작업에 대해 상세 설정을 설명합니다.

1. URL 기반 라우팅 기능

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 실제 서비스 라우팅용
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: front-be-route
  namespace: my-service
spec:
  parentRefs:
  - name: shared-gateway
    namespace: gateway-system
    sectionName: front-be # 상위 Gateway와 매핑
  hostnames: ["front-be.example.com"]
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: front-be-service
      port: 8080

기존 Ingress에서 해주던 라우팅을 HTTPRoute가 직접적으로 대체합니다.

TLS 인증서 및 정책 설정은 shared-gatewaylisteners에서 대체합니다.

1
2
3
4
5
6
7
8
9
10
11
12
  - name: front-be
    port: 443
    protocol: HTTPS
    hostname: "front-be.example.com"
    tls:
      mode: Terminate
      options:
        kubernetes.azure.com/tls-cert-keyvault-uri: "https://<kv-name>.vault.azure.net/certificates/<cert-name>"
        kubernetes.azure.com/tls-cert-service-account: "<gateway-kv-sa>"  # gateway와 동일 namespace, Workload Identity 연동용
    allowedRoutes:
      namespaces:
        from: All

위 형식대로 각각 Gateway의 listeners를 추가하고 그에 맞는 HTTPRoute를 만드는 작업을 반복하면 됩니다.

2. 인증서 처리

Ingress에서는 아래 annotation으로 Key Vault 인증서를 자동으로 가져왔습니다.

1
kubernetes.azure.com/tls-cert-keyvault-uri: "https://<kv-name>.vault.azure.net/certificates/<cert-name>"

HTTPRoute에서는 라우팅만 담당한다는 원칙에 맞추어, 해당 기능은 Gateway에서 처리해야 합니다.

1
2
3
4
5
6
7
8
9
  - name: front-be
    port: 443
    protocol: HTTPS
    hostname: "front-be.example.com"
    tls:
      mode: Terminate
      options:
        kubernetes.azure.com/tls-cert-keyvault-uri: "https://<kv-name>.vault.azure.net/certificates/<cert-name>"
        kubernetes.azure.com/tls-cert-service-account: "<gateway-kv-sa>"  # gateway와 동일 namespace, Workload Identity 연동용

거의 동일하지만, TO-BE에서는 아래 annotation이 추가되었습니다.

1
kubernetes.azure.com/tls-cert-service-account: "<gateway-kv-sa>"

AKS → Key Vault로 인증서를 가져올 때 권한을 부여하는 페더레이션은 AKS의 정보와 Namespace 단위로 결정됩니다.

기존 Azure의 Nginx Ingress는 기본적으로 app-routing-system namespace에 배포되기 때문에, Azure에서 자체적으로 이에 맞춘 페더레이션 설정과 ServiceAccount를 제공해줍니다. 하지만 Gateway는 사용자가 만드는 리소스이기 때문에 namespace가 고정되어 있지 않습니다.

이에 따라 이 작업은 수동으로 전환되었고, 사용자가 별도로 관리 ID에 대한 페더레이션 설정과 ServiceAccount 배포를 직접 수행해야 합니다.

ServiceAccount.yaml

1
2
3
4
5
6
7
8
9
 apiVersion: v1
 kind: ServiceAccount
 metadata:
   name: gateway-kv-sa
   namespace: gateway-system
   annotations:
     azure.workload.identity/client-id: "<Managed Identity Client ID>"
   labels:
     azure.workload.identity/use: "true"

ServiceAccount란 쿠버네티스 안에서 Pod나 컨트롤러가 다른 리소스(API, 외부 서비스 등)에 접근할 때 쓰는 신원(identity)입니다.

사람 대신 프로그램이 인증받을 때 쓰는 계정으로, 이를 배포하고 위 annotation을 추가하면 정상적으로 인증서가 적용됩니다.

3. HTTP 처리

Ingress에서는 아래 annotation으로 HTTP를 HTTPS로 리다이렉트 처리했습니다.

1
nginx.ingress.kubernetes.io/ssl-redirect: "true"

Gateway에서는 이 기능이 없기 때문에 이를 별도 리소스로 처리해줘야 합니다.

1
2
3
4
5
6
7
  listeners:
  - name: http
    port: 80
    protocol: HTTP
    allowedRoutes:
      namespaces:
        from: All

별도의 hostname 없이 와일드카드 형태로 모든 HTTP 요청을 위 http라는 리스너로 받습니다. 그리고 여기에 붙는 리다이렉트용 HTTPRoute를 만들어주면 되는데, 이건 서비스별로 만들 필요 없이 공용 1개로 처리할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# HTTP -> HTTPS 용 (공용)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: global-http-redirect
  namespace: gateway-system   # 특정 서비스가 아닌 Gateway와 같은 위치
spec:
  parentRefs:
  - name: shared-gateway
    namespace: gateway-system
    sectionName: http         # 상위 Gateway의 리스너 이름
  hostnames:
  - "example.com"             # 루트 도메인
  - "*.example.com"           # 1단계 서브도메인 전체 (front-be, bo-be ...)
  rules:
  - filters:
    - type: RequestRedirect
      requestRedirect:
        scheme: https
        statusCode: 301

RequestRedirecthostname을 따로 지정하지 않으면 원래 요청의 호스트/경로/쿼리스트링은 그대로 유지된 채 스킴만 https로 바뀝니다.

1
2
GET http://front-be.example.com/some/path?query=1
→ 301 Location: https://front-be.example.com/some/path?query=1

기존에 annotation 하나로 처리하던 게 리소스 하나로 분리되긴 했지만, 서비스 수만큼 늘어나지는 않는 부분입니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.