Gateway API는 Kubernetes에서 서비스 트래픽의 진입 및 라우팅을 관리하기 위한 표준 API다. 기존 Ingress 리소스의 한계를 보완하고, 복잡한 L7 트래픽 관리, 멀티-테넌시, 확장성을 제공한다. Gateway API는 CRD(Custom Resource Definition) 형태로 제공되어 Kubernetes 네이티브 리소스처럼 사용한다.
GatewayClass
GatewayClass는 특정 Gateway 구현체(컨트롤러)의 유형을 정의한다.
예를 들어, istio, nginx, haproxy 등 컨트롤러 이름으로 GatewayClass를 설정한다.
Gateway
Gateway는 실제 트래픽 진입 지점을 정의한다.
NodePort, LoadBalancer, HostNetwork 등 외부에서 들어오는 트래픽을 수신한다.
Gateway에는 여러 개의 Listener를 설정할 수 있어, 다양한 포트와 프로토콜을 동시에 처리할 수 있다.
Gateway는 GatewayClass를 참조하여 해당 컨트롤러에 의해 관리된다.
Listener
Listener는 Gateway에서 수신할 포트와 프로토콜을 정의한다.
Listener는 Hostname, TLS 인증서 등 L7 속성을 포함할 수 있다.
Listener는 Gateway와 연결되어 트래픽 라우팅의 세부 규칙을 설정한다.
Route (HTTPRoute, TCPRoute 등)
Route는 Gateway로 들어오는 트래픽을 실제 서비스로 전달하는 규칙을 정의한다.
Route는 Gateway의 Listener와 연결되어, 특정 호스트명, 경로, 헤더 조건 등을 기반으로 트래픽을 분기한다.
여러 Route를 조합하여 멀티-테넌시 환경에서 서비스별 트래픽을 관리할 수 있다.
Gateway API를 사용하기 위해선 NodePort를 활성화하도록 구성되어야 한다.
nodePort.enabled=true를 설정하거나, kubeProxyReplacement=true를 활성화해야 한다.
L7 프록시가 활성화된 상태여야 한다.
l7Proxy=true로 설정하며, 기본적으로 활성화되어 있다.
Gateway API v1.2.0에서 사용하는 CRD들은 사전에 설치되어 있어야 한다.
# CRD 설치
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/standard/gateway.networking.k8s.io_gatewayclasses.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/standard/gateway.networking.k8s.io_gateways.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/standard/gateway.networking.k8s.io_httproutes.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/standard/gateway.networking.k8s.io_referencegrants.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/standard/gateway.networking.k8s.io_grpcroutes.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml
# 확인
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get crd | grep gateway.networking.k8s.io
gatewayclasses.gateway.networking.k8s.io 2025-08-23T15:20:10Z
gateways.gateway.networking.k8s.io 2025-08-23T15:20:10Z
grpcroutes.gateway.networking.k8s.io 2025-08-23T15:20:12Z
httproutes.gateway.networking.k8s.io 2025-08-23T15:20:11Z
referencegrants.gateway.networking.k8s.io 2025-08-23T15:20:11Z
tlsroutes.gateway.networking.k8s.io 2025-08-23T15:20:12Z
Cilium Gateway API 설정을 진행한다. Cilium Ingress와 병행하여 사용할 수 없기 때문에, ingressController를 false로 설정한다.
(⎈|HomeLab:N/A) root@k8s-ctr:~# helm upgrade cilium cilium/cilium --version 1.18.1 --namespace kube-system --reuse-values \
--set ingressController.enabled=false --set gatewayAPI.enabled=true
kubectl -n kube-system rollout restart deployment/cilium-operator
kubectl -n kube-system rollout restart ds/cilium
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get GatewayClass
NAME CONTROLLER ACCEPTED AGE
cilium io.cilium/gateway-controller True 60s
Gateway와 HTTP Route를 배포하여 Envoy에 설정값을 주입한다.
cat << EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: my-gateway
spec:
gatewayClassName: cilium
listeners:
- protocol: HTTP
port: 80
name: web-gw
allowedRoutes:
namespaces:
from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http-app-1
spec:
parentRefs:
- name: my-gateway
namespace: default
rules:
- matches:
- path:
type: PathPrefix
value: /details
backendRefs:
- name: details
port: 9080
- matches:
- headers:
- type: Exact
name: magic
value: foo
queryParams:
- type: Exact
name: great
value: example
path:
type: PathPrefix
value: /
method: GET
backendRefs:
- name: productpage
port: 9080
EOF
Gateway LoadBalancer 서비스가 생성되었고, EXT-IP가 할당되어 있다.
Gateway에도 동일한 EXT-IP가 할당되어 있다.
Gateway를 보면 상태가 PROGRAMMED인데, 이는 Gateway 설정이 Cilium Operator와 Cilium agent에 의해서 Envoy에 정상 주입되었다는 뜻이다.
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get svc,ep cilium-gateway-my-gateway
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/cilium-gateway-my-gateway LoadBalancer 10.96.184.183 192.168.10.211 80:31179/TCP 12s
NAME ENDPOINTS AGE
endpoints/cilium-gateway-my-gateway 192.192.192.192:9999 12s
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get gateway
NAME CLASS ADDRESS PROGRAMMED AGE
my-gateway cilium 192.168.10.211 True 57s
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get httproutes -A
NAMESPACE NAME HOSTNAMES AGE
default http-app-1 118s
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl logs -n kube-system deployments/cilium-operator | grep gateway
time=2025-08-23T15:30:18.512590875Z level=info source=/go/src/github.com/cilium/cilium/operator/pkg/gateway-api/gateway_reconcile.go:47 msg="Reconciling Gateway" module=operator.operator-controlplane.leader-lifecycle.gateway-api controller=gateway resource=default/my-gateway
time=2025-08-23T15:30:18.517371014Z level=info source=/go/src/github.com/cilium/cilium/operator/pkg/gateway-api/httproute_reconcile.go:143 msg="Successfully reconciled HTTPRoute" module=operator.operator-controlplane.leader-lifecycle.gateway-api controller=httpRoute parentResource=default/http-app-1
time=2025-08-23T15:30:18.519187964Z level=info source=/go/src/github.com/cilium/cilium/operator/pkg/gateway-api/gateway_reconcile.go:202 msg="Successfully reconciled Gateway" module=operator.operator-controlplane.leader-lifecycle.gateway-api controller=gateway resource=default/my-gateway
HTTPRoute에 설정을 한 대로 통신을 확인해보자.
curl -s --fail -v http://"$GATEWAY"/details/1로 호출을 하면 details-v1를 호출할 것이고,
curl -s -v -H 'magic: foo' http://"$GATEWAY"\?great\=example로 호출을 하면 productpage-v1를 호출할 것이다.
root@router:~# GATEWAY=192.168.10.211
root@router:~# curl -s --fail -v http://"$GATEWAY"/details/1
* Trying 192.168.10.211:80...
* Connected to 192.168.10.211 (192.168.10.211) port 80
> GET /details/1 HTTP/1.1
> Host: 192.168.10.211
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: application/json
< server: envoy
< date: Sat, 23 Aug 2025 15:40:51 GMT
< content-length: 178
< x-envoy-upstream-service-time: 7
<
* Connection #0 to host 192.168.10.211 left intact
{"id":1,"author":"William Shakespeare","year":1595,"type":"paperback","pages":200,"publisher":"PublisherA","language":"English","ISBN-10":"1234567890","ISBN-13":"123-1234567890"}
# Header 설정이 누락되어 호출 실패
root@router:~# curl -s -v http://"$GATEWAY"\?great\=example
...
< HTTP/1.1 404 Not Found
< date: Sat, 23 Aug 2025 15:41:18 GMT
< server: envoy
< content-length: 0
<
* Connection #0 to host 192.168.10.211 left intact
# Header 추가하여 호출 시 정상 호출
root@router:~# curl -s -v -H 'magic: foo' http://"$GATEWAY"\?great\=example
* Trying 192.168.10.211:80...
* Connected to 192.168.10.211 (192.168.10.211) port 80
> GET /?great=example HTTP/1.1
> Host: 192.168.10.211
> User-Agent: curl/8.5.0
> Accept: */*
> magic: foo
>
< HTTP/1.1 200 OK
< server: envoy
< date: Sat, 23 Aug 2025 15:41:20 GMT
< content-type: text/html; charset=utf-8
< content-length: 2080
< x-envoy-upstream-service-time: 10
...
* Connection #0 to host 192.168.10.211 left intact
Gateway API또한 eBPF 프로그램이 직접 TPROXY를 통해 커널 내부에서 트래픽을 전달하는 것을 확인할 수 있다.
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl -n kube-system get lease | grep "cilium-l2announce"
cilium-l2announce-default-cilium-gateway-my-gateway k8s-ctr 15m
(⎈|HomeLab:N/A) root@k8s-ctr:~# sudo iptables -t mangle -L CILIUM_PRE_mangle --line-numbers -n -v
Chain CILIUM_PRE_mangle (1 references)
num pkts bytes target prot opt in out source destination
4 0 0 TPROXY 6 -- * * 0.0.0.0/0 0.0.0.0/0 mark match 0x13320200 /* cilium: TPROXY to host default/cilium-gateway-my-gateway/listener proxy */ TPROXY redirect 127.0.0.1:12819 mark 0x200/0xffffffff
# Gateway 호출
root@router:~# curl -s -v -H 'magic: foo' http://"$GATEWAY"\?great\=example
# TPROXY pkts 증가
(⎈|HomeLab:N/A) root@k8s-ctr:~# sudo iptables -t mangle -L CILIUM_PRE_mangle --line-numbers -n -v
Chain CILIUM_PRE_mangle (1 references)
num pkts bytes target prot opt in out source destination
4 1 60 TPROXY 6 -- * * 0.0.0.0/0 0.0.0.0/0 mark match 0x13320200 /* cilium: TPROXY to host default/cilium-gateway-my-gateway/listener proxy */ TPROXY redirect 127.0.0.1:12819 mark 0x200/0xffffffff
TLSRoute는 HTTPS/TLS 트래픽을 Gateway를 통해 처리할 때, 트래픽의 종단(termination) 방식과 전달 방식을 정의하는 리소스이다. 주로 TLS 연결을 어디에서 종료할지와 Pod로 전달할 때 어떤 프로토콜을 사용할지를 결정한다.
Terminate (종단)
Passthrough (통과)
Sample App과 Gateway를 배포한다.
그 이전에 TLS Certificate와 Private Key를 생성한다.
apt install mkcert -y
mkcert '*.cilium.rocks'
kubectl create secret tls demo-cert --key=_wildcard.cilium.rocks-key.pem --cert=_wildcard.cilium.rocks.pem
mkcert -install
mkcert -CAROOT
tail -n 50 /etc/ssl/certs/ca-certificates.crt
vi 1.pem
cat <<'EOF' > nginx.conf
events {
}
http {
log_format main '$remote_addr - $remote_user [$time_local] $status '
'"$request" $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;
server {
listen 443 ssl;
root /usr/share/nginx/html;
index index.html;
server_name nginx.cilium.rocks;
ssl_certificate /etc/nginx-server-certs/tls.crt;
ssl_certificate_key /etc/nginx-server-certs/tls.key;
}
}
EOF
kubectl create configmap nginx-configmap --from-file=nginx.conf=./nginx.conf
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
ports:
- port: 443
protocol: TCP
selector:
run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 1
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 443
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx
readOnly: true
- name: nginx-server-certs
mountPath: /etc/nginx-server-certs
readOnly: true
volumes:
- name: nginx-config
configMap:
name: nginx-configmap
- name: nginx-server-certs
secret:
secretName: demo-cert
EOF
cat << EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: cilium-tls-gateway
spec:
gatewayClassName: cilium
listeners:
- name: https
hostname: "nginx.cilium.rocks"
port: 443
protocol: TLS
tls:
mode: Passthrough
allowedRoutes:
namespaces:
from: All
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
name: nginx
spec:
parentRefs:
- name: cilium-tls-gateway
hostnames:
- "nginx.cilium.rocks"
rules:
- backendRefs:
- name: my-nginx
port: 443
EOF
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get gateway cilium-tls-gateway
NAME CLASS ADDRESS PROGRAMMED AGE
cilium-tls-gateway cilium 192.168.10.211 True 6s
root@router:~# GATEWAY=192.168.10.211
TLS Request를 호출하면 정상 호출되는 것을 확인할 수 있다.
(⎈|HomeLab:N/A) root@k8s-ctr:~# curl -v --resolve "nginx.cilium.rocks:443:$GATEWAY" "https://nginx.cilium.rocks:443"
* Added nginx.cilium.rocks:443:192.168.10.211 to DNS cache
* Hostname nginx.cilium.rocks was found in DNS cache
* Trying 192.168.10.211:443...
* Connected to nginx.cilium.rocks (192.168.10.211) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/ssl/certs/ca-certificates.crt
* CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
* ALPN: server accepted http/1.1
* Server certificate:
* subject: O=mkcert development certificate; OU=root@k8s-ctr
* start date: Aug 23 16:34:26 2025 GMT
* expire date: Nov 23 16:34:26 2027 GMT
* subjectAltName: host "nginx.cilium.rocks" matched cert's "*.cilium.rocks"
* issuer: O=mkcert development CA; OU=root@k8s-ctr; CN=mkcert root@k8s-ctr
* SSL certificate verify ok.
* Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 1: Public key type RSA (3072/128 Bits/secBits), signed using sha256WithRSAEncryption
* using HTTP/1.x
> GET / HTTP/1.1
> Host: nginx.cilium.rocks
> User-Agent: curl/8.5.0
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
< HTTP/1.1 200 OK
< Server: nginx/1.29.1
< Date: Sat, 23 Aug 2025 16:42:01 GMT
< Content-Type: text/html
< Content-Length: 615
< Last-Modified: Wed, 13 Aug 2025 14:33:41 GMT
< Connection: keep-alive
< ETag: "689ca245-267"
< Accept-Ranges: bytes
<
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
* Connection #0 to host nginx.cilium.rocks left intact