📌 Notice
Kubernetes Advanced Networking Study (=KANS)
k8s 네트워크 장애 시, 네트워크 상세 동작 원리를 기반으로 원인을 찾고 해결하는 내용을 정리한 블로그입니다.
CloudNetaStudy
그룹에서 스터디를 진행하고 있습니다.
Gasida
님께 다시한번 🙇 감사드립니다.
EKS 관련 이전 스터디 내용은 아래 링크를 통해 확인할 수 있습니다.
Kubernetes에서 서비스를 외부로 노출하는 다양한 방식(Ingress, LoadBalancer, Gateway API 등)과 네트워크 동작 원리를 학습했습니다.
Ingress 컨트롤러와 Gateway API를 통해 HTTP 트래픽을 처리하고 고급 라우팅 규칙을 설정하는 방법을 실습했습니다.
Gloo Gateway를 활용하여 Kubernetes 클러스터에서 고가용성 네트워크 및 마이그레이션 배포 전략을 구현했습니다.
Nginx 인그레스 컨트롤러 경우 : 외부에서 인그레스로 접속 시 Nginx 인그레스 컨트롤러 파드로 인입되고, 이후 애플리케이션 파드의 IP로 직접 통신
인그레스와 파드간 내부 연결의 효율화
인그레스 컨트롤러 파드(Layer7 동작)에서 서비스 파드의 IP로 직접 연결
- 인그레스 컨트롤러 파드는 K8S API서버로부터 서비스의 엔드포인트 정보(파드 IP)를 획득 후 바로 파드의 IP로 연결 - 링크
- 지원되는 인그레스 컨트롤러 : Nginx, Traefix 등 현재 대부분의 인그레스 컨트롤러가 지원함
인그레스 소개
: 클러스터 내부의 서비스(ClusterIP, NodePort, Loadbalancer)를 외부로 노출(HTTP/HTTPS) - Web Proxy 역할
인그레스(Ingress)는 쿠버네티스 클러스터 내부의 서비스를 외부로 HTTP/HTTPS 프로토콜을 통해 노출시키기 위한 웹 프록시 역할을 수행합니다. 이때 인그레스는 프로토콜 인식 구성 메커니즘을 사용하여 URI, 호스트 이름, 경로 등 웹 관련 개념을 이해하고 이를 기반으로 트래픽을 관리합니다. 이를 통해 사용자는 인그레스 리소스를 이용해 정의한 규칙에 따라 다른 백엔드 서비스로 트래픽을 분배할 수 있습니다.
인그레스는 클러스터 내 서비스에 대한 외부 접근을 관리하는 API 객체로 주로 HTTP를 처리합니다.
인그레스는 로드 밸런싱, SSL 종료 및 이름 기반 가상 호스팅을 지원할 수 있습니다.
인그레스는 현재 더 이상 새로운 기능이 추가되지 않고 있으며, 대신 Gateway API로 새로운 기능이 추가되고 있습니다. Gateway API를 참고해 주세요.
실습 구성도
- 컨트롤플레인 노드에 인그레스 컨트롤러(Nginx) 파드를 생성, NodePort 로 외부에 노출
- 인그레스 정책 설정 : Host/Path routing, 실습의 편리를 위해서 도메인 없이 IP로 접속 설정 가능
출처 - https://kschoi728.tistory.com/266
인그레스(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
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
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"; -- ...
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]
외부에서 접속(그림 왼쪽) 후 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
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
인스턴스 모드
AWS ALB(Ingress)로 인입 후 각 워커노드의 NodePort 로 전달 후 IPtables 룰(SEP)에 따라 파드로 분배
출처 - CloudNet@
IP 모드
nginx ingress controller 동작과 유사하게 AWS LoadBalancer Controller 파드가 kube api 를 통해서 파드의 IP를 제공받아서 AWS ALB 에 타켓(파드 IP)를 설정
출처 - CloudNet@
출처 - 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
출처 - 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
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
Gateway API의 주요 기능
개선된 리소스 모델
API는 GatewayClass, Gateway 및 Route(HTTPRoute, TCPRoute 등)와 같은 새로운 사용자 정의 리소스를 도입하여 라우팅 규칙을 정의하는 보다 세부적이고 표현력 있는 방법을 제공합니다.프로토콜 독립적
주로 HTTP용으로 설계된 Ingress와 달리 Gateway API는 TCP, UDP, TLS를 포함한 여러 프로토콜을 지원합니다.
강화된 보안
TLS 구성 및 보다 세부적인 액세스 제어에 대한 기본 제공 지원.교차 네임스페이스 지원
서로 다른 네임스페이스의 서비스로 트래픽을 라우팅하여 보다 유연한 아키텍처를 구축할 수 있는 기능을 제공합니다.확장성
API는 사용자 정의 리소스 및 정책으로 쉽게 확장할 수 있도록 설계되었습니다.역할 지향
클러스터 운영자, 애플리케이션 개발자, 보안 팀 간의 우려를 명확하게 분리합니다.
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
,Gateway
,HTTPRoute
,TCPRoute
,Service
출처 - 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.
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.
- 참고 링크 - Gloo Blog , Docs
- https://www.solo.io/blog/gateway-api-tutorial-blog/*
- https://www.solo.io/blog/gateway-api-workshop/
- https://www.solo.io/blog/fast-and-furious-gateway-api-at-scale-with-envoy-proxy-and-gloo-gateway/
- https://www.solo.io/blog/getting-the-most-out-of-gateway-api-lessons-learned-from-gloo-migrations/
- https://www.solo.io/blog/solving-an-information-leakage-problem-with-the-envoy-extproc-filter-and-kube-gateway-api/
- https://www.solo.io/blog/gateway-api-gitops-argo-tutorial-blog/*
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)를 감시합니다.
- 구성 또는 시크릿 감시자가 새로운 리소스나 업데이트된 리소스를 감지하면 해당 리소스 구성을 Gloo Gateway 변환 엔진으로 전송합니다.
- 변환 엔진은 쿠버네티스 Gateway API와 Gloo Gateway 리소스를 Envoy 구성으로 변환합니다. 모든 Envoy 구성은 xDS 스냅샷으로 통합됩니다.
- 리포터(reporter)는 변환기에 의해 처리된 각 리소스의 상태 보고서를 수신합니다.
- 리포터는 리소스 상태를 etcd 데이터 저장소에 기록합니다.
- xDS 스냅샷은 gloo 파드의 Gloo Gateway xDS 서버 컴포넌트에 제공됩니다.
- 클러스터 내 게이트웨이 프록시는 Gloo Gateway xDS 서버로부터 최신 Envoy 구성을 가져옵니다.
- 사용자는 게이트웨이 프록시가 노출된 IP 주소나 호스트 이름으로 요청을 보냅니다.
- 게이트웨이 프록시는 xDS 스냅샷에 제공된 리스너 및 라우트별 구성을 사용하여 라우팅 결정을 수행하고 요청을 클러스터 내의 목적지로 전달합니다.
Translation engine
- 변환 사이클은 모든 설정된 Upstream 및 쿠버네티스 서비스 리소스로부터 Envoy 클러스터를 정의하는 것으로 시작됩니다. 이 컨텍스트에서 클러스터는 유사한 호스트들의 그룹을 의미합니다. 각 Upstream은 해당 Upstream이 어떻게 처리될지를 결정하는 타입을 가집니다. 올바르게 설정된 Upstream 및 쿠버네티스 서비스는 그 타입에 맞는 Envoy 클러스터로 변환되며, 클러스터 메타데이터와 같은 정보를 포함합니다.
- 변환 사이클의 다음 단계에서는 각 Upstream에 대한 모든 함수를 처리합니다. 함수별 클러스터 메타데이터가 추가되며, 이는 나중에 함수별 Envoy 필터에 의해 처리됩니다.
- 그 다음 단계에서는 모든 Envoy 경로(Route)가 생성됩니다. 경로는 HTTPRoute 및 RouteOption 리소스에 정의된 각 경로 규칙에 대해 생성됩니다. 모든 경로가 생성된 후 변환기는 VirtualHostOption, ListenerOption, HttpListenerOption 리소스를 처리하고 이를 Envoy 가상 호스트로 집계하여 새로운 Envoy HTTP 연결 관리자 구성에 추가합니다.
- 필터 플러그인이 필터 구성을 쿼리하여 Envoy 리스너에 추가될 HTTP 및 TCP 필터 목록을 생성합니다.
- 마지막으로 유효한 엔드포인트(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
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/
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
출처 - https://kschoi728.tistory.com/267
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 thehttpbin
service, it will fail with a404 Not Found error
. Why? Because ourHTTPRoute
policy is only exposing access to/get
, one of the many endpoints available on the service. If we try to consume an alternativehttpbin
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" }
이 섹션에서는 일반적인 서비스 마이그레이션 기법인 dark launches with header-based routing과 canary releases with percentage-based routing가 Gateway API 표준에서 어떻게 지원되는지 살펴보겠습니다.
마이그레이션 라우팅을 위한 두 개의 워크로드 구성
우선 마이그레이션 예시를 위해 두 버전의 워크로드를 설정해 보겠습니다. 이를 위해 오픈 소스 Fake Service를 사용합니다.
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 forv2
that will be accessed by specifying a request header with nameversion
and valuev2
. Let’s apply the modifiedHTTPRoute
:# 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
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-existentmy-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 ...
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"
이번 스터디를 통해 Kubernetes에서 클러스터 내부 서비스를 외부로 노출하는 다양한 방법과 그 작동 원리에 대해 깊이 있게 이해할 수 있었습니다. 특히 Ingress와 Gateway API를 중심으로 실제 실습을 통해 통신 흐름과 다양한 라우팅 방법이 어떻게 구성되고 동작하는지를 분석하였습니다.
Ingress는 Kubernetes에서 HTTP/HTTPS 트래픽을 외부로 노출하는 가장 일반적인 방법으로, Nginx와 같은 인그레스 컨트롤러를 통해 서비스 파드로의 직접적인 연결을 설정했습니다. 이를 통해 경로 기반 라우팅, 호스트 기반 라우팅 등 다양한 라우팅 전략을 이해할 수 있었습니다.
Gateway API는 Ingress보다 향상된 기능을 제공하며, 프로토콜 독립적인 설정과 보안 기능을 포함하여 더욱 유연하고 확장 가능한 네트워크 구성을 가능하게 합니다. 특히 Gloo Gateway를 사용하여 Envoy 프록시와의 통합을 통해 고급 라우팅 및 배포 전략을 구현했습니다. 이러한 전략에는 다크 런칭과 카나리 배포와 같은 점진적 배포 방식이 포함되어 있어, 실제 운영 환경에서의 적용 가능성을 탐구할 수 있었습니다.
실습을 통해 인그레스와 게이트웨이의 차이점, iptables 및 IPVS의 규칙 설정 방식, 그리고 Kubernetes 네트워킹의 복잡성을 명확히 이해할 수 있었습니다. 이 과정에서 Kubernetes 네트워크의 내부 동작을 이해함으로써 클러스터 운영 시 발생할 수 있는 네트워크 문제를 효과적으로 해결할 수 있는 능력을 키울 수 있었습니다. 이번 스터디는 Kubernetes 네트워크 관리에 있어 보다 깊이 있는 시야를 제공해 준 의미 있는 경험이었습니다.