Cilium - Cilium Service Mesh(2)-Gateway-API

Gyullbb·2025년 8월 23일
0

K8S

목록 보기
20/22

Gateway API

Gateway API는 Kubernetes에서 서비스 트래픽의 진입 및 라우팅을 관리하기 위한 표준 API다. 기존 Ingress 리소스의 한계를 보완하고, 복잡한 L7 트래픽 관리, 멀티-테넌시, 확장성을 제공한다. Gateway API는 CRD(Custom Resource Definition) 형태로 제공되어 Kubernetes 네이티브 리소스처럼 사용한다.

구성요소

  1. GatewayClass
    GatewayClass는 특정 Gateway 구현체(컨트롤러)의 유형을 정의한다.
    예를 들어, istio, nginx, haproxy 등 컨트롤러 이름으로 GatewayClass를 설정한다.

  2. Gateway
    Gateway는 실제 트래픽 진입 지점을 정의한다.
    NodePort, LoadBalancer, HostNetwork 등 외부에서 들어오는 트래픽을 수신한다.
    Gateway에는 여러 개의 Listener를 설정할 수 있어, 다양한 포트와 프로토콜을 동시에 처리할 수 있다.
    Gateway는 GatewayClass를 참조하여 해당 컨트롤러에 의해 관리된다.

  3. Listener
    Listener는 Gateway에서 수신할 포트와 프로토콜을 정의한다.
    Listener는 Hostname, TLS 인증서 등 L7 속성을 포함할 수 있다.
    Listener는 Gateway와 연결되어 트래픽 라우팅의 세부 규칙을 설정한다.

  4. Route (HTTPRoute, TCPRoute 등)
    Route는 Gateway로 들어오는 트래픽을 실제 서비스로 전달하는 규칙을 정의한다.

  • HTTPRoute: HTTP/HTTPS 요청을 서비스로 라우팅한다.
  • TCPRoute: TCP 요청을 서비스로 라우팅한다.

Route는 Gateway의 Listener와 연결되어, 특정 호스트명, 경로, 헤더 조건 등을 기반으로 트래픽을 분기한다.
여러 Route를 조합하여 멀티-테넌시 환경에서 서비스별 트래픽을 관리할 수 있다.

  1. BackendRef
    Route에서 실제 트래픽이 전달될 대상 서비스나 리소스를 지정한다.
    여러 개 BackendRef를 지정하여 트래픽을 분산하거나 가중치를 줄 수 있다.

Gateway API 설정

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

TPROXY

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

TLS Route

TLSRoute는 HTTPS/TLS 트래픽을 Gateway를 통해 처리할 때, 트래픽의 종단(termination) 방식과 전달 방식을 정의하는 리소스이다. 주로 TLS 연결을 어디에서 종료할지와 Pod로 전달할 때 어떤 프로토콜을 사용할지를 결정한다.

Terminate (종단)

  • Gateway가 TLS를 해제(terminate) 한다.
  • 클라이언트와 Gateway 사이에서는 HTTPS(암호화)로 통신하지만, Gateway와 백엔드 Pod 사이에서는 HTTP로 전달한다.
  • 장점: Gateway에서 TLS 관리를 중앙 집중화 → Pod에서는 TLS 처리 부담 없음

Passthrough (통과)

  • TLS를 Gateway에서 해제하지 않고 그대로 전달한다.
  • 클라이언트와 Pod 사이의 통신이 종단까지 HTTPS로 유지된다.
  • 장점: 엔드-투-엔드 암호화 유지

TLS Route 설정

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

0개의 댓글