[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 특징
기존 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-gateway의 listeners에서 대체합니다.
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
RequestRedirect에 hostname을 따로 지정하지 않으면 원래 요청의 호스트/경로/쿼리스트링은 그대로 유지된 채 스킴만 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 하나로 처리하던 게 리소스 하나로 분리되긴 했지만, 서비스 수만큼 늘어나지는 않는 부분입니다.

