[KANS3] Ingress & Gateway API

xgro·2024년 10월 12일
0

KANS3

목록 보기
6/9
post-thumbnail

📌 Notice

Kubernetes Advanced Networking Study (=KANS)
k8s 네트워크 장애 시, 네트워크 상세 동작 원리를 기반으로 원인을 찾고 해결하는 내용을 정리한 블로그입니다.

CloudNetaStudy 그룹에서 스터디를 진행하고 있습니다.

Gasida님께 다시한번 🙇 감사드립니다.

EKS 관련 이전 스터디 내용은 아래 링크를 통해 확인할 수 있습니다.



📌 Summary

  • Kubernetes에서 서비스를 외부로 노출하는 다양한 방식(Ingress, LoadBalancer, Gateway API 등)과 네트워크 동작 원리를 학습했습니다.

  • Ingress 컨트롤러와 Gateway API를 통해 HTTP 트래픽을 처리하고 고급 라우팅 규칙을 설정하는 방법을 실습했습니다.

  • Gloo Gateway를 활용하여 Kubernetes 클러스터에서 고가용성 네트워크 및 마이그레이션 배포 전략을 구현했습니다.



📌 Study

👉 Step 00. Ingress

✅ 00. Ingress 요약

Nginx 인그레스 컨트롤러 경우 : 외부에서 인그레스로 접속 시 Nginx 인그레스 컨트롤러 파드로 인입되고, 이후 애플리케이션 파드의 IP로 직접 통신

인그레스와 파드간 내부 연결의 효율화

인그레스 컨트롤러 파드(Layer7 동작)에서 서비스 파드의 IP로 직접 연결

  • 인그레스 컨트롤러 파드는 K8S API서버로부터 서비스의 엔드포인트 정보(파드 IP)를 획득 후 바로 파드의 IP로 연결 - 링크
  • 지원되는 인그레스 컨트롤러 : Nginx, Traefix 등 현재 대부분의 인그레스 컨트롤러가 지원함

✅ 01. Ingress 소개

인그레스 소개 : 클러스터 내부의 서비스(ClusterIP, NodePort, Loadbalancer)를 외부로 노출(HTTP/HTTPS) - Web Proxy 역할

  • 인그레스(Ingress)는 쿠버네티스 클러스터 내부의 서비스를 외부로 HTTP/HTTPS 프로토콜을 통해 노출시키기 위한 웹 프록시 역할을 수행합니다. 이때 인그레스는 프로토콜 인식 구성 메커니즘을 사용하여 URI, 호스트 이름, 경로 등 웹 관련 개념을 이해하고 이를 기반으로 트래픽을 관리합니다. 이를 통해 사용자는 인그레스 리소스를 이용해 정의한 규칙에 따라 다른 백엔드 서비스로 트래픽을 분배할 수 있습니다.

  • 인그레스는 클러스터 내 서비스에 대한 외부 접근을 관리하는 API 객체로 주로 HTTP를 처리합니다.

  • 인그레스는 로드 밸런싱, SSL 종료 및 이름 기반 가상 호스팅을 지원할 수 있습니다.

  • 인그레스는 현재 더 이상 새로운 기능이 추가되지 않고 있으며, 대신 Gateway API로 새로운 기능이 추가되고 있습니다. Gateway API를 참고해 주세요.


👉 Step 01. 인그레스(Ingress) 실습 및 통신 흐름 확인

실습 구성도

  • 컨트롤플레인 노드에 인그레스 컨트롤러(Nginx) 파드를 생성, NodePort 로 외부에 노출
  • 인그레스 정책 설정 : Host/Path routing, 실습의 편리를 위해서 도메인 없이 IP로 접속 설정 가능


출처 - https://kschoi728.tistory.com/266


✅ 00. Nginx 인그레스 컨트롤러 설치

인그레스(Ingress) 소개
클러스터 내부의 HTTP/HTTPS 서비스를 외부로 노출(expose) - 링크

인그레스 컨트롤러
인그레스의 실제 동작 구현은 인그레스 컨트롤러(Nginx, Kong 등)가 처리 - 링크

  • 쿠버네티스는 Ingress API 만 정의하고 실제 구현은 add-on 에 맡김
  • Ingress-Nginx Controller - 링크 ⇒ 간편한 테스트를 위해서 NodePort 타입(externalTrafficPolicy: Local) 설정

출처 - https://github.com/kubernetes/ingress-nginx/blob/master/docs/deploy/baremetal.md

Ingress-Nginx 컨트롤러 생성 - ArtifactHub

# Ingress-Nginx 컨트롤러 생성
cat <<EOT> ingress-nginx-values.yaml
controller:
  service:
    type: NodePort
    nodePorts:
      http: 30080
      https: 30443
  nodeSelector:
    kubernetes.io/hostname: "k3s-s"
  metrics:
    enabled: true
  serviceMonitor:
      enabled: true
EOT

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

kubectl create ns ingress
helm install ingress-nginx ingress-nginx/ingress-nginx -f ingress-nginx-values.yaml --namespace ingress --version 4.11.2

# 확인
kubectl get all -n ingress
kc describe svc -n ingress ingress-nginx-controller

# externalTrafficPolicy 설정
kubectl patch svc -n ingress ingress-nginx-controller -p '{"spec":{"externalTrafficPolicy": "Local"}}'

# 기본 nginx conf 파일 확인
kc describe cm -n ingress ingress-nginx-controller
kubectl exec deploy/ingress-nginx-controller -n ingress -it -- cat /etc/nginx/nginx.conf

# 관련된 정보 확인 : 포드(Nginx 서버), 서비스, 디플로이먼트, 리플리카셋, 컨피그맵, 롤, 클러스터롤, 서비스 어카운트 등
kubectl get all,sa,cm,secret,roles -n ingress
kc describe clusterroles ingress-nginx
kubectl get pod,svc,ep -n ingress -o wide -l app.kubernetes.io/component=controller

# 버전 정보 확인
POD_NAMESPACE=ingress
POD_NAME=$(kubectl get pods -n $POD_NAMESPACE -l app.kubernetes.io/name=ingress-nginx --field-selector=status.phase=Running -o name)
kubectl exec $POD_NAME -n $POD_NAMESPACE -- /nginx-ingress-controller --version


✅ 01. 디플로이먼트와 서비스를 생성

svc1-pod.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy1-websrv
spec:
  replicas: 1
  selector:
    matchLabels:
      app: websrv
  template:
    metadata:
      labels:
        app: websrv
    spec:
      containers:
      - name: pod-web
        image: nginx
---
apiVersion: v1
kind: Service
metadata:
  name: svc1-web
spec:
  ports:
    - name: web-port
      port: 9001
      targetPort: 80
  selector:
    app: websrv
  type: ClusterIP

svc2-pod.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy2-guestsrv
spec:
  replicas: 2
  selector:
    matchLabels:
      app: guestsrv
  template:
    metadata:
      labels:
        app: guestsrv
    spec:
      containers:
      - name: pod-guest
        image: gcr.io/google-samples/kubernetes-bootcamp:v1
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: svc2-guest
spec:
  ports:
    - name: guest-port
      port: 9002
      targetPort: 8080
  selector:
    app: guestsrv
  type: NodePort

svc3-pod.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy3-adminsrv
spec:
  replicas: 3
  selector:
    matchLabels:
      app: adminsrv
  template:
    metadata:
      labels:
        app: adminsrv
    spec:
      containers:
      - name: pod-admin
        image: k8s.gcr.io/echoserver:1.5
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: svc3-admin
spec:
  ports:
    - name: admin-port
      port: 9003
      targetPort: 8080
  selector:
    app: adminsrv

생성 및 확인

# 모니터링
watch -d 'kubectl get ingress,svc,ep,pod -owide'

# 생성
kubectl taint nodes k3s-s role=controlplane:NoSchedule
curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/7/svc1-pod.yaml
curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/7/svc2-pod.yaml
curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/7/svc3-pod.yaml
kubectl apply -f svc1-pod.yaml,svc2-pod.yaml,svc3-pod.yaml

# 확인 : svc1, svc3 은 ClusterIP 로 클러스터 외부에서는 접속할 수 없다 >> Ingress 는 연결 가능!
kubectl get pod,svc,ep


✅ 02. 인그레스(정책) 생성

ingress1.yaml

cat <<EOT> ingress1.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-1
  annotations:
    #nginx.ingress.kubernetes.io/upstream-hash-by: "true"
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc1-web
            port:
              number: 80
      - path: /guest
        pathType: Prefix
        backend:
          service:
            name: svc2-guest
            port:
              number: 8080
      - path: /admin
        pathType: Prefix
        backend:
          service:
            name: svc3-admin
            port:
              number: 8080
EOT

생성 및 확인

# 모니터링
watch -d 'kubectl get ingress,svc,ep,pod -owide'

# 생성
kubectl apply -f ingress1.yaml

# 확인
kubectl get ingress
kc describe ingress ingress-1
...
Rules:
  Host        Path  Backends
  ----        ----  --------
  *           
              /        svc1-web:80 ()
              /guest   svc2-guest:8080 ()
              /admin   svc3-admin:8080 ()
...

# 설정이 반영된 nginx conf 파일 확인
kubectl exec deploy/ingress-nginx-controller -n ingress -it -- cat /etc/nginx/nginx.conf
kubectl exec deploy/ingress-nginx-controller -n ingress -it -- cat /etc/nginx/nginx.conf | grep 'location /' -A5
		location /guest/ {

			set $namespace      "default";
			set $ingress_name   "ingress-1";
			set $service_name   "svc2-guest";
			set $service_port   "8080";
--
  		location /admin/ {

			set $namespace      "default";
			set $ingress_name   "ingress-1";
			set $service_name   "svc3-admin";
			set $service_port   "8080";
--
  		location / {

			set $namespace      "default";
			set $ingress_name   "ingress-1";
			set $service_name   "svc1-web";
			set $service_port   "80";
--
...

✅ 03. 인그레스를 통한 내부 접속

Nginx 인그레스 컨트롤러를 통한 접속(HTTP 인입) 경로 : 인그레스 컨트롤러 파드에서 서비스 파드의 IP로 직접 연결
출처 - CloudNet@

인그레스(Nginx 인그레스 컨트롤러)를 통한 접속(HTTP 인입) 확인***
HTTP 부하분산 & PATH 기반 라우팅, 애플리케이션 파드에 연결된 서비스는 Bypass

출처 - https://daniel00324.tistory.com/13

# (krew 플러그인 설치 시) 인그레스 정책 확인
kubectl ingress-nginx ingresses
INGRESS NAME   HOST+PATH   ADDRESSES       TLS   SERVICE      SERVICE PORT   ENDPOINTS
ingress-1      /           192.168.10.10   NO    svc1-web     80             1
ingress-1      /guest      192.168.10.10   NO    svc2-guest   8080           2
ingress-1      /admin      192.168.10.10   NO    svc3-admin   8080           3

#
kubectl get ingress
NAME        CLASS   HOSTS   ADDRESS        PORTS   AGE
ingress-1   nginx   *       10.10.200.24   80      3m44s

kubectl describe ingress ingress-1 | sed -n "5, \$p"
Rules:
  Host        Path   Backends
  ----        ----   --------
  *           /      svc1-web:80 ()
              /guest svc2-guest:8080 ()
              /admin svc3-admin:8080 ()


# 접속 로그 확인 : kubetail 설치되어 있음 - 출력되는 nginx 의 로그의 IP 확인
kubetail -n ingress -l app.kubernetes.io/component=controller

-------------------------------
# 자신의 집 PC에서 인그레스를 통한 접속 : 각각 
echo -e "Ingress1 sv1-web URL = http://$(curl -s ipinfo.io/ip):30080"
echo -e "Ingress1 sv2-guest URL = http://$(curl -s ipinfo.io/ip):30080/guest"
echo -e "Ingress1 sv3-admin URL = http://$(curl -s ipinfo.io/ip):30080/admin"

# svc1-web 접속
MYIP=<EC2 공인 IP>
MYIP=13.124.93.150
curl -s $MYIP:30080

# svvc2-guest 접속
curl -s $MYIP:30080/guest
curl -s $MYIP:30080/guest
for i in {1..100}; do curl -s $MYIP:30080/guest ; done | sort | uniq -c | sort -nr

# svc3-admin 접속 > 기본적으로 Nginx 는 라운드로빈 부하분산 알고리즘을 사용 >> Client_address 와 XFF 주소는 어떤 주소인가요?
curl -s $MYIP:30080/admin
curl -s $MYIP:30080/admin | egrep '(client_address|x-forwarded-for)'
for i in {1..100}; do curl -s $MYIP:30080/admin | grep Hostname ; done | sort | uniq -c | sort -nr


# (옵션) 디플로이먼트의 파드 갯수를 증가/감소 설정 후 접속 테스트 해보자

Nginx 파드가 endpoint 정보 등을 모니터링 가능한 이유
클러스터롤과 롤(엔드포인트 list, watch)를 바인딩된 서비스 어카운트를 파드가 사용!

# (옵션) Nginx 파드가 endpoint 정보 등을 모니터링 가능한 이유 : 클러스터롤과 롤(엔드포인트 list, watch)를 바인딩된 서비스 어카운트를 파드가 사용!
kubectl describe clusterrole ingress -n ingress | egrep '(Verbs|endpoints)'
[root@k8s-m:~/yaml (ctx-k8s:default)]# kubectl describe clusterrole ingress-nginx -n ingress-nginx | egrep '(Verbs|endpoints)'
  Resources                           Non-Resource URLs  Resource Names  Verbs
  endpoints                           []                 []              [list watch]

kubectl describe roles ingress-nginx -n ingress | egrep '(Verbs|endpoints)'
[root@k8s-m:~/yaml (ctx-k8s:default)]# kubectl describe roles ingress-nginx -n ingress-nginx | egrep '(Verbs|endpoints)'
  Resources                           Non-Resource URLs  Resource Names                     Verbs
  endpoints                           []                 []                                 [get list watch]

✅ 04. 패킷 분석

외부에서 접속(그림 왼쪽) 후 Nginx 파드(Layer7 동작)는 HTTP 헤더에 정보 추가(XFF)파드의 IP로 직접 전달

(옵션) 패킷 분석 >> 파드(veth) 패킷 캡쳐 후 merge

Proxy(대리인) 존재감 확인!

  • 중간 빨간색 부분이 Nginx 파드의 IP(172.16.29.11)입니다 → 외부에서 Nginx 파드에 접속 후 Nginx 파드가 내부의 파드 IP로 직접 연결(옵션:부하분산)
# 아래 ENDPOINTS 출력된 지점으로 요청이 바로 전달됩니다.
kubectl get ep | grep -v kubernetes
NAME         ENDPOINTS                                                AGE
svc1-web     172.16.228.72:80                                         14h
svc2-guest   172.16.197.8:8080,172.16.46.7:8080                       14h
svc3-admin   172.16.228.78:8080,172.16.46.13:8080,172.16.46.14:8080   14h                        

✅ 05. (옵션) Nginx 분산 알고리즘 변경

nginx 는 기본 RR 라운드 로빈 이지만, IP-Hash 나 Session Cookie 설정으로 대상 유지 가능 - 링크

출처 - https://daniel00324.tistory.com/13

# mypc
for i in {1..100}; do curl -s $MYIP:30080/admin | grep Hostname ; done | sort | uniq -c | sort -nr
while true; do curl -s --connect-timeout 1 $MYIP:30080/admin | grep Hostname ; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done

# 아래 ingress 설정 중 IP-Hash 설정 > # 주석 제거
sed -i 's/#nginx.ingress/nginx.ingress/g' ingress1.yaml
kubectl apply -f ingress1.yaml

# 접속 확인
for i in {1..100}; do curl -s $MYIP:30080/admin | grep Hostname ; done | sort | uniq -c | sort -nr
while true; do curl -s --connect-timeout 1 $MYIP:30080/admin | grep Hostname ; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done

# 다시 원복(라운드 로빈) > # 주석 추가
sed -i 's/nginx.ingress/#nginx.ingress/g' ingress1.yaml
kubectl apply -f ingress1.yaml

# 접속 확인
for i in {1..100}; do curl -s $MYIP:30080/admin | grep Hostname ; done | sort | uniq -c | sort -nr
while true; do curl -s --connect-timeout 1 $MYIP:30080/admin | grep Hostname ; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done

✅ 06. AWS Ingress (ALB) 모드

인스턴스 모드
AWS ALB(Ingress)로 인입 후 각 워커노드의 NodePort 로 전달 후 IPtables 룰(SEP)에 따라 파드로 분배
출처 - CloudNet@

IP 모드
nginx ingress controller 동작과 유사하게 AWS LoadBalancer Controller 파드가 kube api 를 통해서 파드의 IP를 제공받아서 AWS ALB 에 타켓(파드 IP)를 설정
출처 - CloudNet@


✅ 07. Host 기반 라우팅

출처 - https://kschoi728.tistory.com/266

ingress2.yaml

cat <<EOT> ingress2.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-2
spec:
  ingressClassName: nginx
  rules:
  - host: kans.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc3-admin
            port:
              number: 8080
  - host: "*.kans.com"
    http:
      paths:
      - path: /echo
        pathType: Prefix
        backend:
          service:
            name: svc3-admin
            port:
              number: 8080
EOT

인그레스 생성 및 확인

# 터미널1
watch -d 'kubectl get ingresses,svc,ep,pod -owide'

# 도메인 변경
MYDOMAIN1=<각자 자신의 닉네임의 도메인> 예시) gasida.com
MYDOMAIN1=gasida.com
sed -i "s/kans.com/$MYDOMAIN1/g" ingress2.yaml

# 생성
kubectl apply -f ingress2.yaml,svc3-pod.yaml

# 확인
kubectl get ingress
kubectl describe ingress ingress-2

kubectl describe ingress ingress-2 | sed -n "5, \$p"
Rules:
  Host        Path  Backends
  ----        ----  --------
  kans.com    /     svc3-admin:8080 ()
  *.kans.com  /echo svc3-admin:8080 ()
...

인그레스(Nginx 인그레스 컨트롤러)를 통한 접속(HTTP 인입) 확인

# 로그 모니터링
kubetail -n ingress -l app.kubernetes.io/component=controller

# (옵션) ingress nginx 파드 vethY 에서 패킷 캡처 후 확인 해 볼 것

------------
# 자신의 PC 에서 접속 테스트
# svc3-admin 접속 > 결과 확인 : 왜 접속이 되지 않는가? HTTP 헤더에 Host 필드를 잘 확인해보자!
curl $MYIP:30080 -v
curl $MYIP:30080/echo -v

# mypc에서 접속을 위한 설정
## /etc/hosts 수정 : 도메인 이름으로 접속하기 위해서 변수 지정
## 윈도우 C:\Windows\System32\drivers\etc\hosts
## 맥 sudo vim /etc/hosts
MYDOMAIN1=<각자 자신의 닉네임의 도메인>
MYDOMAIN2=<test.각자 자신의 닉네임의 도메인>
MYDOMAIN1=kans.com
MYDOMAIN2=test.kans.com
echo $MYIP $MYDOMAIN1 $MYDOMAIN2

echo "$MYIP $MYDOMAIN1" | sudo tee -a /etc/hosts
echo "$MYIP $MYDOMAIN2" | sudo tee -a /etc/hosts
cat /etc/hosts | grep $MYDOMAIN1

# svc3-admin 접속 > 결과 확인
curl $MYDOMAIN1:30080 -v
curl $MYDOMAIN1:30080/admin
curl $MYDOMAIN1:30080/echo
curl $MYDOMAIN1:30080/echo/1

curl $MYDOMAIN2:30080 -v
curl $MYDOMAIN2:30080/admin
curl $MYDOMAIN2:30080/echo
curl $MYDOMAIN2:30080/echo/1
curl $MYDOMAIN2:30080/echo/1/2

## (옵션) /etc/hosts 파일 변경 없이 접속 방안
curl -H "host: $MYDOMAIN1" $MYIP:30080

✅ 08. 카나리 업그레이드

출처 - https://kschoi728.tistory.com/266

롤링 업데이트
출처 - https://tech.devsisters.com/posts/blue-green-canary-deployment/

카나리 업데이트
출처 - https://tech.devsisters.com/posts/blue-green-canary-deployment/

블루-그린 업데이트
출처 - https://tech.devsisters.com/posts/blue-green-canary-deployment/

canary-svc1-pod.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dp-v1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: svc-v1
  template:
    metadata:
      labels:
        app: svc-v1
    spec:
      containers:
      - name: pod-v1
        image: k8s.gcr.io/echoserver:1.5
        ports:
        - containerPort: 8080
      terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Service
metadata:
  name: svc-v1
spec:
  ports:
    - name: web-port
      port: 9001
      targetPort: 8080
  selector:
    app: svc-v1

canary-svc2-pod.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dp-v2
spec:
  replicas: 3
  selector:
    matchLabels:
      app: svc-v2
  template:
    metadata:
      labels:
        app: svc-v2
    spec:
      containers:
      - name: pod-v2
        image: k8s.gcr.io/echoserver:1.6
        ports:
        - containerPort: 8080
      terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Service
metadata:
  name: svc-v2
spec:
  ports:
    - name: web-port
      port: 9001
      targetPort: 8080
  selector:
    app: svc-v2

생성 및 확인

# 터미널1
watch -d 'kubectl get ingress,svc,ep,pod -owide'

# 생성
curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/7/canary-svc1-pod.yaml
curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/7/canary-svc2-pod.yaml
kubectl apply -f canary-svc1-pod.yaml,canary-svc2-pod.yaml

# 확인
kubectl get svc,ep,pod

# 파드 버전 확인: 1.13.0 vs 1.13.1
for pod in $(kubectl get pod -o wide -l app=svc-v1 |awk 'NR>1 {print $6}'); do curl -s $pod:8080 | egrep '(Hostname|nginx)'; done
	Hostname: dp-v1-cdd8dc687-gcgsz
		server_version=nginx: 1.13.0 - lua: 10008
for pod in $(kubectl get pod -o wide -l app=svc-v2 |awk 'NR>1 {print $6}'); do curl -s $pod:8080 | egrep '(Hostname|nginx)'; done
	Hostname: dp-v2-785f69bd6-hh624
		server_version=nginx: 1.13.1 - lua: 10008

canary-ingress1.yaml

cat <<EOT> canary-ingress1.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-canary-v1
spec:
  ingressClassName: nginx
  rules:
  - host: kans.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc-v1
            port:
              number: 8080
EOT

canary-ingress2.yaml

cat <<EOT> canary-ingress2.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-canary-v2
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
  ingressClassName: nginx
  rules:
  - host: kans.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc-v2
            port:
              number: 8080
EOT

카나리 업그레이드 확인

# 터미널1
watch -d 'kubectl get ingress,svc,ep'

# 도메인 변경
MYDOMAIN1=<각자 자신의 닉네임의 도메인> 예시) gasida.com
sed -i "s/kans.com/$MYDOMAIN1/g" canary-ingress1.yaml
sed -i "s/kans.com/$MYDOMAIN1/g" canary-ingress2.yaml

# 생성
kubectl apply -f canary-ingress1.yaml,canary-ingress2.yaml

# 로그 모니터링
kubetail -n ingress -l app.kubernetes.io/component=controller

# 접속 테스트
curl -s $MYDOMAIN1:30080
curl -s $MYDOMAIN1:30080 | grep nginx

# 접속 시 v1 v2 버전별 비율이 어떻게 되나요? 왜 이렇게 되나요?
for i in {1..100};  do curl -s $MYDOMAIN1:30080 | grep nginx ; done | sort | uniq -c | sort -nr
for i in {1..1000}; do curl -s $MYDOMAIN1:30080 | grep nginx ; done | sort | uniq -c | sort -nr
while true; do curl -s --connect-timeout 1 $MYDOMAIN1:30080 | grep Hostname ; echo "--------------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done

# 비율 조정 >> 개발 배포 버전 전략에 유용하다!
kubectl annotate --overwrite ingress ingress-canary-v2 nginx.ingress.kubernetes.io/canary-weight=50

# 접속 테스트
for i in {1..100};  do curl -s $MYDOMAIN1:30080 | grep nginx ; done | sort | uniq -c | sort -nr
for i in {1..1000}; do curl -s $MYDOMAIN1:30080 | grep nginx ; done | sort | uniq -c | sort -nr

# (옵션) 비율 조정 << 어떻게 비율이 조정될까요?
kubectl annotate --overwrite ingress ingress-canary-v2 nginx.ingress.kubernetes.io/canary-weight=100
for i in {1..100};  do curl -s $MYDOMAIN1:30080 | grep nginx ; done | sort | uniq -c | sort -nr

# (옵션) 비율 조정 << 어떻게 비율이 조정될까요?
kubectl annotate --overwrite ingress ingress-canary-v2 nginx.ingress.kubernetes.io/canary-weight=0
for i in {1..100};  do curl -s $MYDOMAIN1:30080 | grep nginx ; done | sort | uniq -c | sort -nr

✅ 09. HTTPS 처리 (TLS 종료)

svc-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-https
  labels:
    app: https
spec:
  containers:
  - name: container
    image: k8s.gcr.io/echoserver:1.6
  terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Service
metadata:
  name: svc-https
spec:
  selector:
    app: https
  ports:
  - port: 8080

ssl-termination-ingress.yaml
출처 - https://kschoi728.tistory.com/266

cat <<EOT> ssl-termination-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: https
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - kans.com
    secretName: secret-https
  rules:
  - host: kans.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc-https
            port:
              number: 8080
EOT

생성 및 확인

출처 - https://daniel00324.tistory.com/13

# 서비스와 파드 생성
curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/7/svc-pod.yaml
kubectl apply -f svc-pod.yaml

# 도메인 변경
MYDOMAIN1=<각자 자신의 닉네임의 도메인> 예시) gasida.com
MYDOMAIN1=kans.com
echo $MYDOMAIN1
sed -i "s/kans.com/$MYDOMAIN1/g" ssl-termination-ingress.yaml

# 인그레스 생성
kubectl apply -f ssl-termination-ingress.yaml

# 인증서 생성
# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=dkos.com/O=dkos.com"mkdir key && cd key
MYDOMAIN1=kans.com
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=$MYDOMAIN1/O=$MYDOMAIN1"
tree

# Secret 생성
kubectl create secret tls secret-https --key tls.key --cert tls.crt

# Secret 확인 
kubectl get secrets secret-https
kubectl get secrets secret-https -o yaml

-------------------
# 자신의 PC 에서 접속 확인 : PC 웹브라우저
# 접속 확인 : -k 는 https 접속 시 : 접속 포트 정보 확인
curl -Lk https://$MYDOMAIN1:30443

## (옵션) /etc/hosts 파일 변경 없이 접속 방안
curl -Lk -H "host: $MYDOMAIN1" https://$MYDOMAIN1:30443

Nginx SSL Termination 패킷 확인 : 중간 172.16.29.11 이 nginx controller

# 패킷 캡처 명령어 참고
export IngHttp=$(kubectl get service -n ingress-nginx ingress-nginx-controller -o jsonpath='{.spec.ports[0].nodePort}')
export IngHttps=$(kubectl get service -n ingress-nginx ingress-nginx-controller -o jsonpath='{.spec.ports[1].nodePort}')
tcpdump -i <nginx 파드 veth> -nnq tcp port 80 or tcp port 443 or tcp port 8080 or tcp port $IngHttp or tcp port $IngHttps
tcpdump -i <nginx 파드 veth> -nn  tcp port 80 or tcp port 443 or tcp port 8080 or tcp port $IngHttp or tcp port $IngHttps -w /tmp/ingress.pcap


👉 Step 03. Gateway API 소개

Gateway API의 주요 기능

  1. 개선된 리소스 모델
    API는 GatewayClass, Gateway 및 Route(HTTPRoute, TCPRoute 등)와 같은 새로운 사용자 정의 리소스를 도입하여 라우팅 규칙을 정의하는 보다 세부적이고 표현력 있는 방법을 제공합니다.

  2. 프로토콜 독립적
    주로 HTTP용으로 설계된 Ingress와 달리 Gateway API는 TCP, UDP, TLS를 포함한 여러 프로토콜을 지원합니다.

  3. 강화된 보안
    TLS 구성 및 보다 세부적인 액세스 제어에 대한 기본 제공 지원.

  4. 교차 네임스페이스 지원
    서로 다른 네임스페이스의 서비스로 트래픽을 라우팅하여 보다 유연한 아키텍처를 구축할 수 있는 기능을 제공합니다.

  5. 확장성
    API는 사용자 정의 리소스 및 정책으로 쉽게 확장할 수 있도록 설계되었습니다.

  6. 역할 지향
    클러스터 운영자, 애플리케이션 개발자, 보안 팀 간의 우려를 명확하게 분리합니다.

Gateway API 소개
기존의 Ingress 에 좀 더 기능을 추가, 역할 분리(role-oriented)

  • 서비스 메시(istio)에서 제공하는 Rich 한 기능 중 일부 기능들과 혹은 운영 관리에 필요한 기능들을 추가
  • 추가 기능 : 헤더 기반 라우팅, 헤더 변조, 트래픽 미러링(쉽게 트래픽 복제), 역할 기반

출처 - https://youtu.be/GiFQNevrxYA?t=172

Gateway API는 동적 인프라 프로비저닝 및 고급 트래픽 라우팅 기능을 제공하는 여러 API 종류의 모음입니다. 이 API는 네트워크 서비스를 제공할 때 확장 가능하고 역할 중심의 프로토콜 인식 구성 메커니즘을 사용하여 설정할 수 있습니다.

Gateway API는 API 객체의 일종으로, 클러스터에 필요한 네트워크 인프라를 동적으로 프로비저닝하고 고급 트래픽 라우팅을 지원합니다.

쿠버네티스의 애드온(add-on) 형태로 제공되며, 여러 API 종류를 통해 인프라와 트래픽을 관리할 수 있습니다.

구성 요소 (Resource)
- GatewayClass,GatewayHTTPRouteTCPRouteService

출처 - https://kubernetes.io/docs/concepts/services-networking/gateway/

  • GatewayClass: Defines a set of gateways with common configuration and managed by a controller that implements the class.
  • Gateway: Defines an instance of traffic handling infrastructure, such as cloud load balancer.
  • HTTPRoute: Defines HTTP-specific rules for mapping traffic from a Gateway listener to a representation of backend network endpoints. These endpoints are often represented as a Service.

출처 - https://gateway-api.sigs.k8s.io/

Request flow

Why does a role-oriented API matter?

  • 담당 업무의 역할에 따라서 동작/권한을 유연하게 제공할 수 있음
  • 아래 그림 처럼 '스토어 개발자'는 Store 네임스페이스내에서 해당 store PATH 라우팅 관련 정책을 스스로 관리 할 수 있음

  • Infrastructure Provider: Manages infrastructure that allows multiple isolated clusters to serve multiple tenants, e.g. a cloud provider.
  • Cluster Operator: Manages clusters and is typically concerned with policies, network access, application permissions, etc.
  • Application Developer: Manages an application running in a cluster and is typically concerned with application-level configuration and Service composition.

👉 Step 04. Gloo Gateway

Gloo Gateway Architecture : These components work together to translate Gloo and Kubernetes Gateway API custom resources into Envoy configuration
출처 - https://docs.solo.io/gateway/latest/about/architecture/
1. gloo 파드의 구성(config) 및 시크릿(secret) 감시자는 클러스터에서 새로운 쿠버네티스 Gateway API 및 Gloo Gateway 리소스(예: Gateways, HTTPRoutes, RouteOptions)를 감시합니다.

  1. 구성 또는 시크릿 감시자가 새로운 리소스나 업데이트된 리소스를 감지하면 해당 리소스 구성을 Gloo Gateway 변환 엔진으로 전송합니다.
  1. 변환 엔진은 쿠버네티스 Gateway API와 Gloo Gateway 리소스를 Envoy 구성으로 변환합니다. 모든 Envoy 구성은 xDS 스냅샷으로 통합됩니다.
  1. 리포터(reporter)는 변환기에 의해 처리된 각 리소스의 상태 보고서를 수신합니다.
  1. 리포터는 리소스 상태를 etcd 데이터 저장소에 기록합니다.
  1. xDS 스냅샷은 gloo 파드의 Gloo Gateway xDS 서버 컴포넌트에 제공됩니다.
  1. 클러스터 내 게이트웨이 프록시는 Gloo Gateway xDS 서버로부터 최신 Envoy 구성을 가져옵니다.
  1. 사용자는 게이트웨이 프록시가 노출된 IP 주소나 호스트 이름으로 요청을 보냅니다.
  1. 게이트웨이 프록시는 xDS 스냅샷에 제공된 리스너 및 라우트별 구성을 사용하여 라우팅 결정을 수행하고 요청을 클러스터 내의 목적지로 전달합니다.

Translation engine

  1. 변환 사이클은 모든 설정된 Upstream 및 쿠버네티스 서비스 리소스로부터 Envoy 클러스터를 정의하는 것으로 시작됩니다. 이 컨텍스트에서 클러스터는 유사한 호스트들의 그룹을 의미합니다. 각 Upstream은 해당 Upstream이 어떻게 처리될지를 결정하는 타입을 가집니다. 올바르게 설정된 Upstream 및 쿠버네티스 서비스는 그 타입에 맞는 Envoy 클러스터로 변환되며, 클러스터 메타데이터와 같은 정보를 포함합니다.
  1. 변환 사이클의 다음 단계에서는 각 Upstream에 대한 모든 함수를 처리합니다. 함수별 클러스터 메타데이터가 추가되며, 이는 나중에 함수별 Envoy 필터에 의해 처리됩니다.
  1. 그 다음 단계에서는 모든 Envoy 경로(Route)가 생성됩니다. 경로는 HTTPRoute 및 RouteOption 리소스에 정의된 각 경로 규칙에 대해 생성됩니다. 모든 경로가 생성된 후 변환기는 VirtualHostOption, ListenerOption, HttpListenerOption 리소스를 처리하고 이를 Envoy 가상 호스트로 집계하여 새로운 Envoy HTTP 연결 관리자 구성에 추가합니다.
  1. 필터 플러그인이 필터 구성을 쿼리하여 Envoy 리스너에 추가될 HTTP 및 TCP 필터 목록을 생성합니다.
  1. 마지막으로 유효한 엔드포인트(EDS), 클러스터(CDS), 경로 구성(RDS), 리스너(LDS)로 구성된 xDS 스냅샷이 작성됩니다. 이 스냅샷은 Gloo Gateway xDS 서버에 전송됩니다. 클러스터 내의 게이트웨이 프록시들은 xDS 서버에서 새로운 구성을 감시하며, 새로운 구성이 감지되면 해당 구성을 게이트웨이 프록시로 가져옵니다.

Deployment patterns

simple ingress

Shared gateway

Sharded gateway with central ingress

기존 설정에 따라 중앙 인그레스 엔드포인트로 다른 유형의 프록시를 사용하고 싶을 수 있습니다.
예를 들어 모든 트래픽이 통과해야 하는 HAProxy 또는 AWS NLB/ALB 인스턴스가 있을 수 있습니다

API gateway for a service mesh


✅ 00. Tutorial

  • Hands-On with the Kubernetes Gateway API and Envoy Proxy : 양이 상당히 매우 많음 😅 - Blog Github

    Kubernetes-hosted application accessible via a gateway configured with policies for routing, service discovery, timeouts, debugging, access logging, and observability


✅ 01. Install

Install KinD Cluster

#
cat <<EOT> kind-1node.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
  - containerPort: 30002
    hostPort: 30002
EOT

# Install KinD Cluster
kind create cluster --image kindest/node:v1.30.0 --config kind-1node.yaml --name myk8s

# 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bsdmainutils bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

# 노드/파드 확인
kubectl get nodes -o wide
kubectl get pod -A

Install Gateway API CRDs
The Kubernetes Gateway API abstractions are expressed using Kubernetes CRDs.

# CRDs 설치 및 확인
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
kubectl get crd

Install Glooctl Utility
GLOOCTL is a command-line utility that allows users to view, manage, and debug Gloo Gateway deployments - Link

# [신규 터미널] 아래 bash 진입 후 glooctl 툴 사용
docker exec -it myk8s-control-plane bash
----------------------------------------
# Install Glooctl Utility
## glooctl install gateway     # install gloo's function gateway functionality into the 'gloo-system' namespace
## glooctl install ingress     # install very basic Kubernetes Ingress support with Gloo into namespace gloo-system
## glooctl install knative     # install Knative serving with Gloo configured as the default cluster ingress
## curl -sL https://run.solo.io/gloo/install | sh
curl -sL https://run.solo.io/gloo/install | GLOO_VERSION=v1.17.7 sh
export PATH=$HOME/.gloo/bin:$PATH

# 버전 확인
glooctl version

----------------------------------------

Install Gloo Gateway : 오픈소스 버전
필수 - [macOS m시리즈] Docker Desktop : 아래 옵션 Uncheck 해둘 것 → Apply & restart

# [신규 터미널] 모니터링
watch -d kubectl get pod,svc,endpointslices,ep -n gloo-system

# Install Gloo Gateway
## --set kubeGateway.enabled=true: Kubernetes Gateway 기능을 활성화합니다.
## --set gloo.disableLeaderElection=true: Gloo의 리더 선출 기능을 비활성화합니다. (단일 인스턴스에서 Gloo를 실행 시 유용)
## --set discovery.enabled=false: 서비스 디스커버리 기능을 비활성화합니다.
helm repo add gloo https://storage.googleapis.com/solo-public-helm
helm repo update
helm install -n gloo-system gloo-gateway gloo/gloo \
--create-namespace \
--version 1.17.7 \
--set kubeGateway.enabled=true \
--set gloo.disableLeaderElection=true \
--set discovery.enabled=false

# Confirm that the Gloo control plane has successfully been deployed using this command
kubectl rollout status deployment/gloo -n gloo-system

# 설치 확인
kubectl get crd | grep 'networking.k8s.io'
kubectl get crd | grep -v 'networking.k8s.io'
kubectl get pod,svc,endpointslices -n gloo-system

#
kubectl explain gatewayclasses
kubectl get gatewayclasses
NAME           CONTROLLER             ACCEPTED   AGE
gloo-gateway   solo.io/gloo-gateway   True       21m

kubectl get gatewayclasses -o yaml
apiVersion: v1
items:
- apiVersion: gateway.networking.k8s.io/v1
  kind: GatewayClass
  metadata:
    labels:
      app: gloo
    name: gloo-gateway
  spec:
    controllerName: solo.io/gloo-gateway
...

Install Httpbin Application
A simple HTTP Request & Response Service - Link
출처 - https://kschoi728.tistory.com/267

#
watch -d kubectl get pod,svc,endpointslices,ep -n httpbin

# Install Httpbin Application
kubectl apply -f https://raw.githubusercontent.com/solo-io/solo-blog/main/gateway-api-tutorial/01-httpbin-svc.yaml

# 설치 확인
kubectl get deploy,pod,svc,endpointslices,sa -n httpbin
kubectl rollout status deploy/httpbin -n httpbin

# (옵션) NodePort 설정
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  labels:
    app: httpbin
    service: httpbin
  name: httpbin
  namespace: httpbin
spec:
  type: NodePort
  ports:
  - name: http
    port: 8000
    targetPort: 80
    nodePort: 30000
  selector:
    app: httpbin
EOF

# (옵션) 로컬 접속 확인
echo "httpbin web - http://localhost:30000"     # macOS 사용자
echo "httpbin web - http://192.168.50.10:30000" # Windows 사용자

Gateway API kinds - Docs

  • GatewayClass: 공통 설정을 가진 게이트웨이 집합을 정의하며, 해당 클래스를 구현하는 컨트롤러에 의해 관리됩니다.
  • Gateway: 트래픽 처리 인프라의 인스턴스를 정의하며, 예를 들어 클라우드 로드 밸런서와 같은 것을 포함합니다.
  • HTTPRoute: 게이트웨이 리스너에서 백엔드 네트워크 엔드포인트의 표현으로 트래픽을 매핑하기 위한 HTTP 전용 규칙을 정의합니다. 이러한 엔드포인트는 종종 서비스(Service)로 표현됩니다.
    출처 - https://kubernetes.io/docs/concepts/services-networking/gateway/

✅ 02. Configure a Gateway Listener

Control : Envoy data plane and the Gloo control plane.

  • Now we’ll configure a Gateway listener, establish external access to Gloo Gateway, and test the routing rules that are the core of the proxy configuration.

출처 - https://kschoi728.tistory.com/267

gateway 리스너를 생성합니다.

# 02-gateway.yaml
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
  name: http
spec:
  gatewayClassName: gloo-gateway
  listeners:
  - protocol: HTTP
    port: 8080
    name: http
    allowedRoutes:
      namespaces:
        from: All

# gateway 리소스 생성
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/02-gateway.yaml

# 확인 : Now we can confirm that the Gateway has been activated
kubectl get gateway -n gloo-system
kubectl get gateway -n gloo-system -o yaml | k neat
apiVersion: v1
items:
- apiVersion: gateway.networking.k8s.io/v1
  kind: Gateway
  metadata:
    name: http
    namespace: gloo-system
  spec:
    gatewayClassName: gloo-gateway
    listeners:
    - allowedRoutes:
        namespaces:
          from: All
      name: http
      port: 8080
      protocol: HTTP
...

# You can also confirm that Gloo Gateway has spun up an Envoy proxy instance in response to the creation of this Gateway object by deploying gloo-proxy-http:
kubectl get deployment gloo-proxy-http -n gloo-system
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
gloo-proxy-http   1/1     1            1           5m22s

# envoy 사용 확인
kubectl get pod -n gloo-system
kubectl describe pod -n gloo-system  |grep Image:
    Image:         quay.io/solo-io/gloo-envoy-wrapper:1.17.7
    Image:          quay.io/solo-io/gloo:1.17.7
    Image:         quay.io/solo-io/gloo-envoy-wrapper:1.17.7


# gloo-proxy-http 서비스는 External-IP는 Pending 상태
kubectl get svc -n gloo-system gloo-proxy-http
NAME              TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
gloo-proxy-http   LoadBalancer   10.96.71.22   <pending>     8080:31555/TCP   2m4s

# gloo-proxy-http NodePort 30001 설정
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/instance: http
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: gloo-proxy-http
    app.kubernetes.io/version: 1.17.7
    gateway.networking.k8s.io/gateway-name: http
    gloo: kube-gateway
    helm.sh/chart: gloo-gateway-1.17.7
  name: gloo-proxy-http
  namespace: gloo-system
spec:
  ports:
  - name: http
    nodePort: 30001
    port: 8080
  selector:
    app.kubernetes.io/instance: http
    app.kubernetes.io/name: gloo-proxy-http
    gateway.networking.k8s.io/gateway-name: http
  type: LoadBalancer
EOF

kubectl get svc -n gloo-system gloo-proxy-http


✅ 03. Configure Simple Routing with an HTTPRoute

출처 - https://kschoi728.tistory.com/267

  • ParentRefs: 이 라우트(Route)가 연결하고자 하는 게이트웨이(Gateways)를 정의합니다.
  • Hostnames (선택 사항): HTTP 요청의 호스트 헤더와 일치시키기 위해 사용할 호스트 이름 목록을 정의합니다.
  • Rules: 일치하는 HTTP 요청에 대해 작업을 수행할 규칙 목록을 정의합니다.

HTTPRoute 구조

apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
  name: httpbin
  namespace: httpbin
  labels:
    example: httpbin-route
spec:
  parentRefs:
    - name: http
      namespace: gloo-system
  hostnames:
    - "api.example.com"
  rules:
  - matches:
    - path:
        type: Exact
        value: /get
    backendRefs:
      - name: httpbin
        port: 8000

The Gateway object simply represents a host:port listener that the proxy will expose to accept ingress traffic.

# Our route watches for HTTP requests directed at the host api.example.com with the request path /get and then forwards the request to the httpbin service on port 8000.
# Let’s establish this route now:
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/03-httpbin-route.yaml

#
kubectl get httproute -n httpbin
NAME      HOSTNAMES             AGE
httpbin   ["api.example.com"]   3m15s

kubectl describe httproute -n httpbin
...
Spec:
  Hostnames:
    api.example.com
  Parent Refs:
    Group:      gateway.networking.k8s.io
    Kind:       Gateway
    Name:       http
    Namespace:  gloo-system
  Rules:
    Backend Refs:
      Group:   
      Kind:    Service
      Name:    httpbin
      Port:    8000
      Weight:  1
    Matches:
      Path:
        Type:   Exact
        Value:  /get
...

Test the Simple Route with Curl

# let’s use curl to display the response with the -i option to additionally show the HTTP response code and headers.
echo "127.0.0.1 api.example.com" | sudo tee -a /etc/hosts
echo "httproute - http://api.example.com:30001/get" # 웹브라우저
혹은
curl -is -H "Host: api.example.com" http://localhost:8080/get # kubectl port-forward 사용 시
HTTP/1.1 200 OK
server: envoy
date: Sun, 06 Oct 2024 07:55:34 GMT
content-type: application/json
content-length: 239
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 25

{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "api.example.com", 
    "User-Agent": "curl/8.7.1", 
    "X-Envoy-Expected-Rq-Timeout-Ms": "15000"
  }, 
  "origin": "10.244.0.11", 
  "url": "http://api.example.com/get"
}

Note that if we attempt to invoke another valid endpoint /delay on the httpbin service, it will fail with a 404 Not Found error. Why? Because our HTTPRoute policy is only exposing access to /get, one of the many endpoints available on the service. If we try to consume an alternative httpbin endpoint like /delay:

# 호출 응답 왜 그럴까?
curl -is -H "Host: api.example.com" http://localhost:8080/delay/1
HTTP/1.1 404 Not Found
date: Wed, 03 Jul 2024 07:19:21 GMT
server: envoy
content-length: 0

#
echo "httproute - http://api.example.com:30001/delay/1" # 웹브라우저


# nodeport 직접 접속
echo "httproute - http://api.example.com:30000/delay/1" # 1초 후 응답
echo "httproute - http://api.example.com:30000/delay/5" # 5초 후 응답

[정규식 패턴 매칭] Explore Routing with Regex Matching Patterns

예시) /api/httpbin/delay/1 ⇒ /delay/1

# Here are the modifications we’ll apply to our HTTPRoute:

    - matches:
        # Switch from an Exact Matcher(정확한 매팅) to a PathPrefix (경로 매팅) Matcher
        - path:
            type: PathPrefix
            value: /api/httpbin/
      filters:
        # Replace(변경) the /api/httpbin matched prefix with /
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /

2가지 수정 내용 적용 후 확인

#
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/04-httpbin-rewrite.yaml

# 확인
kubectl describe httproute -n httpbin
...
Spec:
  Hostnames:
    api.example.com
  Parent Refs:
    Group:      gateway.networking.k8s.io
    Kind:       Gateway
    Name:       http
    Namespace:  gloo-system
  Rules:
    Backend Refs:
      Group:   
      Kind:    Service
      Name:    httpbin
      Port:    8000
      Weight:  1
    Filters:
      Type:  URLRewrite
      URL Rewrite:
        Path:
          Replace Prefix Match:  /
          Type:                  ReplacePrefixMatch
    Matches:
      Path:
        Type:   PathPrefix
        Value:  /api/httpbin/


Test Routing with Regex Matching Patterns

#
echo "httproute - http://api.example.com:30001/api/httpbin/get" # 웹브라우저
혹은
curl -is -H "Host: api.example.com" http://localhost:8080/api/httpbin/get # kubectl port-forward 사용 시
HTTP/1.1 200 OK
server: envoy
date: Sun, 06 Oct 2024 08:08:09 GMT
content-type: application/json
content-length: 289
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 18  # envoy 가 업스트림 httpbin 요청 처리에 걸리 시간 0.018초

{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "api.example.com", 
    "User-Agent": "curl/8.7.1", 
    "X-Envoy-Expected-Rq-Timeout-Ms": "15000", 
    "X-Envoy-Original-Path": "/api/httpbin/get"
  }, 
  "origin": "10.244.0.11", 
  "url": "http://api.example.com/get"
}

# 아래 NodePort 와 GW API 통한 접속 비교
echo "httproute - http://api.example.com:30001/api/httpbin/get"
echo "httproute - http://api.example.com:30000/api/httpbin/get" # NodePort 직접 접근

---
#
echo "httproute - http://api.example.com:30001/api/httpbin/delay/1" # 웹브라우저
혹은
curl -is -H "Host: api.example.com" http://localhost:8080/api/httpbin/delay/1 # kubectl port-forward 사용 시
HTTP/1.1 200 OK
server: envoy
date: Wed, 03 Jul 2024 07:31:47 GMT
content-type: application/json
content-length: 342
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 1023   # envoy 가 업스트림 httpbin 요청 처리에 걸리 시간 1초 이상

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "api.example.com", 
    "User-Agent": "curl/8.6.0", 
    "X-Envoy-Expected-Rq-Timeout-Ms": "15000", 
    "X-Envoy-Original-Path": "/api/httpbin/delay/1"
  }, 
  "origin": "10.244.0.7", 
  "url": "http://api.example.com/delay/1"
}

curl -is -H "Host: api.example.com" http://localhost:8080/api/httpbin/delay/2

[업스트림 베어러 토큰을 사용한 변환] Test Transformations with Upstream Bearer Tokens

목적 : 요청을 라우팅하는 백엔드 시스템 중 하나에서 인증해야 하는 요구 사항이 있는 경우는 어떻게 할까요? 이 업스트림 시스템에는 권한 부여를 위한 API 키가 필요하고, 이를 소비하는 클라이언트에 직접 노출하고 싶지 않다고 가정해 보겠습니다. 즉, 프록시 계층에서 요청에 주입할 간단한 베어러 토큰을 구성하고 싶습니다. (정적 API 키 토큰을 직접 주입)

# The new filters stanza in our HTTPRoute now looks like this:

      filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /
  
        # Add a Bearer token to supply a static API key when routing to backend system
        - type: RequestHeaderModifier
          requestHeaderModifier:
            add:
              - name: Authorization
                value: Bearer my-api-key
#
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/05-httpbin-rewrite-xform.yaml

#
kubectl describe httproute -n httpbin
...
Spec:
  ...
  Rules:
    Backend Refs:
      Group:   
      Kind:    Service
      Name:    httpbin
      Port:    8000
      Weight:  1
    Filters:
      Type:  URLRewrite
      URL Rewrite:
        Path:
          Replace Prefix Match:  /
          Type:                  ReplacePrefixMatch
      Request Header Modifier:
        Add:
          Name:   Authorization
          Value:  Bearer my-api-key
      Type:       RequestHeaderModifier
    Matches:
      Path:
        Type:   PathPrefix
        Value:  /api/httpbin/

동작 테스트

#
echo "httproute - http://api.example.com:30001/api/httpbin/get" # 웹브라우저
혹은
curl -is -H "Host: api.example.com" http://localhost:8080/api/httpbin/get # kubectl port-forward 사용 시
HTTP/1.1 200 OK
server: envoy
date: Sun, 06 Oct 2024 08:20:00 GMT
content-type: application/json
content-length: 332
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 11

{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Authorization": "Bearer my-api-key", 
    "Host": "api.example.com", 
    "User-Agent": "curl/8.7.1", 
    "X-Envoy-Expected-Rq-Timeout-Ms": "15000", 
    "X-Envoy-Original-Path": "/api/httpbin/get"
  }, 
  "origin": "10.244.0.11", 
  "url": "http://api.example.com/get"
}


✅ 04. Migrate

이 섹션에서는 일반적인 서비스 마이그레이션 기법인 dark launches with header-based routingcanary releases with percentage-based routing가 Gateway API 표준에서 어떻게 지원되는지 살펴보겠습니다.

마이그레이션 라우팅을 위한 두 개의 워크로드 구성

우선 마이그레이션 예시를 위해 두 버전워크로드를 설정해 보겠습니다. 이를 위해 오픈 소스 Fake Service를 사용합니다.

  • Fake ServiceHTTPgRPC 트래픽을 처리할 수 있으며, 업스트림 서비스 통신 테스트 및 서비스 메시와 같은 다양한 시나리오를 테스트하기 위한 목적으로 사용됩니다.

my-workload 서비스의 v1 버전을 설정하고, 응답 문자열에 "v1"이 포함되도록 구성하겠습니다. 또한 이에 대응하는 my-workload-v2 서비스도 생성합니다.

# You should see the response below, indicating deployments for both v1 and v2 of my-workload have been created in the my-workload namespace.
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/06-workload-svcs.yaml

# v1,v2 2가지 버전 워크로드 확인
kubectl get deploy,pod,svc,endpointslices -n my-workload
NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/my-workload-v1   1/1     1            1           77s
deployment.apps/my-workload-v2   1/1     1            1           77s

NAME                                  READY   STATUS    RESTARTS   AGE
pod/my-workload-v1-7577fdcc9d-4cv5r   1/1     Running   0          77s
pod/my-workload-v2-68f84654dd-8725x   1/1     Running   0          77s

NAME                     TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/my-workload-v1   ClusterIP   10.96.35.183   <none>        8080/TCP   77s
service/my-workload-v2   ClusterIP   10.96.56.232   <none>        8080/TCP   77s

NAME                                                  ADDRESSTYPE   PORTS   ENDPOINTS    AGE
endpointslice.discovery.k8s.io/my-workload-v1-bpzgg   IPv4          8080    10.244.0.9   77s
endpointslice.discovery.k8s.io/my-workload-v2-ltp7d   IPv4          8080    10.244.0.8   77s

Test Simple V1 Routing
다중 서비스로의 라우팅을 시작하기 전에, 먼저 간단한 HTTPRoute를 만들어 보겠습니다. 이 HTTPRoute호스트api.example.com이고 경로가 /api/my-workload로 시작하는 HTTP 요청을 v1 워크로드로 전송하도록 설정합니다.

apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
  name: my-workload
  namespace: my-workload
  labels:
    example: my-workload-route
spec:
  parentRefs:
    - name: http
      namespace: gloo-system
  hostnames:
    - "api.example.com"
  rules:
    - matches:
      - path:
          type: PathPrefix
          value: /api/my-workload
      backendRefs:
        - name: my-workload-v1
          namespace: my-workload
          port: 8080

Now apply this route:

#
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/07-workload-route.yaml

#
kubectl get httproute -A
NAMESPACE     NAME          HOSTNAMES             AGE
httpbin       httpbin       ["api.example.com"]   41m
my-workload   my-workload   ["api.example.com"]   39s

#
kubectl describe httproute -n my-workload
...
Spec:
  Hostnames:
    api.example.com
  Parent Refs:
    Group:      gateway.networking.k8s.io
    Kind:       Gateway
    Name:       http
    Namespace:  gloo-system
  Rules:
    Backend Refs:
      Group:      
      Kind:       Service
      Name:       my-workload-v1
      Namespace:  my-workload
      Port:       8080
      Weight:     1
    Matches:
      Path:
        Type:   PathPrefix
        Value:  /api/my-workload

Simulate a v2 Dark Launch with Header-Based Routing

Dark Launch : 일부 사용자에게 새로운 기능을 출시하여 피드백을 수집하고 잠재적으로 더 큰 사용자 커뮤니티를 방해하기 전에 개선 사항을 실험하는 훌륭한 클라우드 마이그레이션 기술

Kubernetes 클러스터에 서비스의 새로운 클라우드 버전을 설치한 다음 선언적 정책을 사용하여 특정 헤더를 포함하는 요청만 새 인스턴스로 라우팅하여 예제에서 다크 런치를 시뮬레이션할 것입니다 . 대다수의 사용자는 이전과 마찬가지로 서비스의 v1을 계속 사용할 것 입니다.

  rules:
    - matches:
      - path:
          type: PathPrefix
          value: /api/my-workload
        # Add a matcher to route requests with a v2 version header to v2
        # version=v2 헤더값이 있는 사용자만 v2 라우팅
        headers:
        - name: version
          value: v2
      backendRefs:
        - name: my-workload-v2
          namespace: my-workload
          port: 8080      
    - matches:
      # Route requests without the version header to v1 as before
      # 대다수 일반 사용자는 기존 처럼 v1 라우팅
      - path:
          type: PathPrefix
          value: /api/my-workload
      backendRefs:
        - name: my-workload-v1
          namespace: my-workload
          port: 8080

Configure two separate routes, one for v1 that the majority of service consumers will still use, and another route for v2 that will be accessed by specifying a request header with name version and value v2. Let’s apply the modified HTTPRoute:

#
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/08-workload-route-header.yaml


# 
kubectl describe httproute -n my-workload
...
Spec:
  ...
  Rules:
    Backend Refs:
      Group:      
      Kind:       Service
      Name:       my-workload-v2
      Namespace:  my-workload
      Port:       8080
      Weight:     1
    Matches:
      Headers:
        Name:   version
        Type:   Exact
        Value:  v2
      Path:
        Type:   PathPrefix
        Value:  /api/my-workload
    Backend Refs:
      Group:      
      Kind:       Service
      Name:       my-workload-v1
      Namespace:  my-workload
      Port:       8080
      Weight:     1
    Matches:
      Path:
        Type:   PathPrefix
        Value:  /api/my-workload
# Now we’ll test the original route, with no special headers supplied, and confirm that traffic still goes to v1:
curl -is -H "Host: api.example.com" http://localhost:8080/api/my-workload
curl -is -H "Host: api.example.com" http://localhost:8080/api/my-workload | grep body
"body": "Hello From My Workload (v1)!",

# But it we supply the version: v2 header, note that our gateway routes the request to v2 as expected:
curl -is -H "Host: api.example.com" -H "version: v2" http://localhost:8080/api/my-workload
curl -is -H "Host: api.example.com" -H "version: v2" http://localhost:8080/api/my-workload | grep body



Expand V2 Testing with Percentage-Based Routing
성공적인 다크 런칭 이후, 우리는 점진적으로 이전 버전에서 새 버전으로 사용자 트래픽을 옮기는 블루-그린 전략을 사용하는 기간을 원할 수 있습니다. 트래픽을 균등하게 분할하고 트래픽의 절반을 로 보내고 v1 나머지 절반을 로 보내는 라우팅 정책으로 이를 살펴보겠습니다 v2

  rules:
    - matches:
      - path:
          type: PathPrefix
          value: /api/my-workload
      # Configure a 50-50 traffic split across v1 and v2 : 버전 1,2 50:50 비율
      backendRefs:
        - name: my-workload-v1
          namespace: my-workload
          port: 8080
          weight: 50
        - name: my-workload-v2
          namespace: my-workload
          port: 8080
          weight: 50
# Apply this 50-50 routing policy with kubectl:
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/09-workload-route-split.yaml

#
kubectl describe httproute -n my-workload
...
# 반복 접속 후 대략 비률 확인
for i in {1..100}; do curl -s -H "Host: api.example.com" http://localhost:8080/api/my-workload/ | grep body; done | sort | uniq -c | sort -nr
for i in {1..200}; do curl -s -H "Host: api.example.com" http://localhost:8080/api/my-workload/ | grep body; done | sort | uniq -c | sort -nr


✅ 05. Debug

Solve a Problem with Glooctl CLI
Gloo 구성 오류의 일반적인 원인은 업스트림 참조를 잘못 입력하는 것입니다. 아마도 다른 소스에서 복사/붙여넣을 때이지만 백엔드 서비스 대상의 이름을 변경할 때 "한 군데를 놓친" 것입니다. 이 예에서 우리는 그런 오류를 만드는 것을 시뮬레이션하고, glooctl 그것을 감지하는 데 어떻게 사용할 수 있는지 보여줍니다.

my-bad-workload-v2 업스트림 구성의 오타를 시뮬레이션하여 올바른 타겟팅하는 대신 존재하지 않는 백엔드 서비스를 타겟팅하도록 변경

# [신규 터미널] 모니터링
kubectl get httproute -n my-workload my-workload -o yaml -w

#
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/10-workload-route-split-bad-dest.yaml

#
kubectl describe httproute -n my-workload
...
Spec:
  Hostnames:
    api.example.com
  Parent Refs:
    Group:      gateway.networking.k8s.io
    Kind:       Gateway
    Name:       http
    Namespace:  gloo-system
  Rules:
    Backend Refs:
      Group:      
      Kind:       Service
      Name:       my-workload-v1
      Namespace:  my-workload
      Port:       8080
      Weight:     50
      Group:      
      Kind:       Service
      Name:       my-bad-workload-v2
      Namespace:  my-workload
      Port:       8080
      Weight:     50
    Matches:
      Path:
        Type:   PathPrefix
        Value:  /api/my-workload
Status:
  Parents:
    Conditions:
      Last Transition Time:  2024-10-06T08:38:25Z
      Message:               Service "my-bad-workload-v2" not found
      Observed Generation:   4
      Reason:                BackendNotFound
      Status:                False
      Type:                  ResolvedRefs
      Last Transition Time:  2024-10-06T08:25:47Z
      Message:               
      Observed Generation:   4
      Reason:                Accepted
      Status:                True
      Type:                  Accepted
    Controller Name:         solo.io/gloo-gateway
    Parent Ref:
      Group:      gateway.networking.k8s.io
      Kind:       Gateway
      Name:       http
      Namespace:  gloo-system

When we test this out, note that the 50-50 traffic split is still in place. This means that about half of the requests will be routed to my-workload-v1 and succeed, while the others will attempt to use the non-existent my-bad-workload-v2 and fail like this:

#
curl -is -H "Host: api.example.com" http://localhost:8080/api/my-workload
curl -is -H "Host: api.example.com" http://localhost:8080/api/my-workload
HTTP/1.1 500 Internal Server Error
date: Wed, 03 Jul 2024 08:21:11 GMT
server: envoy
content-length: 0

# 
for i in {1..100}; do curl -s -H "Host: api.example.com" http://localhost:8080/api/my-workload/ | grep body; done | sort | uniq -c | sort -nr

So we’ll deploy one of the first weapons from the Gloo debugging arsenal, the glooctl check utility. It verifies a number of Gloo resources, confirming that they are configured correctly and are interconnected with other resources correctly. For example, in this case, glooctl will detect the error in the mis-connection between the HTTPRoute and its backend target:

#
docker exec -it myk8s-control-plane bash
-----------------------------------
export PATH=$HOME/.gloo/bin:$PATH
glooctl check
Checking Gateways... OK
Checking Proxies... 1 Errors!

Detected Kubernetes Gateway integration!
Checking Kubernetes GatewayClasses... OK
Checking Kubernetes Gateways... OK
Checking Kubernetes HTTPRoutes... 1 Errors!

Skipping Gloo Instance check -- Gloo Federation not detected.
Error: 2 errors occurred:
	* Found proxy with warnings by 'gloo-system': gloo-system gloo-system-http
Reason: warning: 
  Route Warning: InvalidDestinationWarning. Reason: invalid destination in weighted destination list: *v1.Upstream { blackhole_ns.kube-svc:blackhole-ns-blackhole-cluster-8080 } not found

	* HTTPRoute my-workload.my-workload.http status (ResolvedRefs) is not set to expected (True). Reason: BackendNotFound, Message: Service "my-bad-workload-v2" not found


# 원인 관련 정보 확인
kubectl get httproute my-workload -n my-workload -o yaml
...
status:
  parents:
  - conditions:
    - lastTransitionTime: "2023-11-28T21:09:20Z"
      message: ""
      observedGeneration: 6
      reason: BackendNotFound
      status: "False"
      type: ResolvedRefs
...

# 정상 설정으로 해결 configuration is again clean.
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/09-workload-route-split.yaml
kubectl get httproute my-workload -n my-workload -o yaml

#
glooctl check
...

✅ 06. Observe

Envoy 메트릭 탐색

Envoy는 시스템의 동작을 관찰하는 데 유용한 다양한 메트릭을 제공합니다. 이 예제를 위한 간단한 kind 클러스터에서도 3,000개 이상의 개별 메트릭을 확인할 수 있습니다! 더 자세한 내용은 Envoy 공식 문서를 여기에서 확인할 수 있습니다.

이번 30분간의 실습에서는 각 백엔드 타겟에 대해 Envoy가 제공하는 몇 가지 유용한 메트릭을 빠르게 살펴보겠습니다.

우선, Envoy관리 포트19000을 로컬 워크스테이션으로 포트 포워딩해 보겠습니다.

#
kubectl -n gloo-system port-forward deployment/gloo-proxy-http 19000 &

# 아래 관리 페이지에서 각각 메뉴 링크 클릭 확인
echo "Envoy Proxy Admin - http://localhost:19000"
echo "Envoy Proxy Admin - http://localhost:19000/stats/prometheus"



📌 Conclusion

이번 스터디를 통해 Kubernetes에서 클러스터 내부 서비스를 외부로 노출하는 다양한 방법과 그 작동 원리에 대해 깊이 있게 이해할 수 있었습니다. 특히 Ingress와 Gateway API를 중심으로 실제 실습을 통해 통신 흐름과 다양한 라우팅 방법이 어떻게 구성되고 동작하는지를 분석하였습니다.

Ingress는 Kubernetes에서 HTTP/HTTPS 트래픽을 외부로 노출하는 가장 일반적인 방법으로, Nginx와 같은 인그레스 컨트롤러를 통해 서비스 파드로의 직접적인 연결을 설정했습니다. 이를 통해 경로 기반 라우팅, 호스트 기반 라우팅 등 다양한 라우팅 전략을 이해할 수 있었습니다.

Gateway API는 Ingress보다 향상된 기능을 제공하며, 프로토콜 독립적인 설정과 보안 기능을 포함하여 더욱 유연하고 확장 가능한 네트워크 구성을 가능하게 합니다. 특히 Gloo Gateway를 사용하여 Envoy 프록시와의 통합을 통해 고급 라우팅 및 배포 전략을 구현했습니다. 이러한 전략에는 다크 런칭과 카나리 배포와 같은 점진적 배포 방식이 포함되어 있어, 실제 운영 환경에서의 적용 가능성을 탐구할 수 있었습니다.

실습을 통해 인그레스와 게이트웨이의 차이점, iptables 및 IPVS의 규칙 설정 방식, 그리고 Kubernetes 네트워킹의 복잡성을 명확히 이해할 수 있었습니다. 이 과정에서 Kubernetes 네트워크의 내부 동작을 이해함으로써 클러스터 운영 시 발생할 수 있는 네트워크 문제를 효과적으로 해결할 수 있는 능력을 키울 수 있었습니다. 이번 스터디는 Kubernetes 네트워크 관리에 있어 보다 깊이 있는 시야를 제공해 준 의미 있는 경험이었습니다.



🔗 Reference

profile
안녕하세요! DevOps 엔지니어 이재찬입니다. 블로그에 대한 피드백은 언제나 환영합니다! 기술, 개발, 운영에 관한 다양한 주제로 함께 나누며, 더 나은 협업과 효율적인 개발 환경을 만드는 과정에 대해 인사이트를 나누고 싶습니다. 함께 여행하는 기분으로, 즐겁게 읽어주시면 감사하겠습니다! 🚀

0개의 댓글

관련 채용 정보