[KANS 3기] Ingress & Gateway API
들어가며
이번주에는 ingress와 gateway api 에대해 알아 보겠습니다. KANS 3기 6주차 스터디를 시작하겠습니다.
Ingress
Ingress란?
- Ingress는 클러스터 외부에서 클러스터 내부로 HTTP 및 HTTPS 트래픽을 라우팅하는 Web Proxy 역할을 수행합니다.
- 지난주에 스터디했던 LoadBalancer와 비슷한 역할을 수행하지만, LoadBalancer는 Layer 4에서 동작하는 반면 Ingress는 Layer 7에서 동작한다는 차이가 있습니다.
- Ingress는 HTTP와 HTTPS를 이해하기 때문에 호스트명, 경로 등에 따라 트래픽을 라우팅할 수도 있고, SSL Offloading 등의 기능도 제공합니다.
- 이렇게 다양한 기능이 있지만 Ingress는 동결처리 되었으며, 신규 기능들은 Gateway API라는 다른 API에 추가되고 있고, 향후에는 Ingress 대신 Gateway API를 사용하는 것이 권장될것으로 보입니다.
- 특이한 점은 Ingress 를 통한 트래픽은 서비스를 통하지 않고, 서비스를 통해서 파드의 IP를 확인하고, 위의 그림과 같이 서비스를 거치지 않고 파드와 직접 통신합니다.
Ingress Controller의 종류
- Ingress는 Kubernetes에 내장된 기능이 아니어서 별도의 Ingress Controller를 설치해야만 사용할 수 있습니다. 많이 사용되는 Ingress Controller는 다음과 같습니다.
구분 | 특징 |
---|---|
Pomerium | 보안에 특화 된 Ingress로 Identity-Aware 접근이 가능하며 Zero Trust 모델을 지원합니다. |
NGINX Ingress Controller | 신뢰할 수 있는 안정적으로, 라우팅이 유연하고, Lua 스크립트 등으로 기능확장이 가능합니다. |
Traefik | Auto-discovery를 제공하고, 실시간으로 업데이트 되며, 관리 대시보드를 제공합니다. 동적으로 운영하기 좋습니다. |
HAProxy Ingress | 고성능이며, 다양한 고급 기능을 제공합니다. |
Envoy | 확장성이 있으며 재시도, 서킷 브레이커, 레이트 제한 등 다양한 기능을 제공합니다. |
Istio Ingress Gateway | 트래픽 관리에 강점이 있으며, Istio 서비스 메시와 연동하기 좋습니다. |
Contour | HTTP/2와 gRPC를 지원하는 경량의 고성능을 제공합니다. |
Kong Ingress Controller | Kong은 API Gateway로 널리 알려져있지만 Ingress Controller 기능도 제공합니다. NGINX Ingress Controller의 기능에 추가적인 기능을 제공하지만 학습 곡선이 높은 편입니다. |
이외에도 다양한 Ingress Controller가 존재하며 다음의 링크에서 확인 할 수 있습니다. Kubernetes Ingress Controllers 비교
실습 환경 준비
- 이번 실습에는 k3s라는 경량 Kubernetes 클러스터를 사용하겠습니다. k3s는 Rancher에서 개발한 경량 Kubernetes 클러스터로, 쉽게 설치가 가능하고, 전체가 100MB보다 적을 정도로 적은 자원으로도 Kubernetes를 사용할 수 있습니다.
- 하지만 K8S와는 기능 차이가 있기 때문에, 이러한 부분을 감안하고 사용하시면 됩니다.
k3s 특징
- k3s의 특징은 다음과 같습니다
- 단일 바이너리 또는 최소 컨테이너 이미지로 배포됩니다.
- 기본 저장소 백엔드로 sqlite3를 기반으로 한 경량 데이터 저장소가 사용됩니다. etcd, MySQL 및 Postgres도 사용할 수 있습니다.
- TLS 및 옵션의 복잡성을 처리하는 런처에 포함되어 있습니다.
- 경량 환경에 적합한 합리적인 기본값으로 보안에 신경을 썼습니다.
- 모든 Kubernetes 컨트롤 플레인 구성 요소의 작동이 단일 바이너리 및 프로세스에 캡슐화되어 있고, k3s가 인증서 배포와 같은 복잡한 클러스터 작업을 자동화합니다.
- 외부 종속성이 최소화되었습니다. 필요한 것은 최신 커널과 cgroup 마운트뿐입니다.
- 손쉬운 클러스터 생성을 위해 필요한 패키지를 기본 제공합니다:
- containerd / cri-dockerd 컨테이너 런타임 (CRI)
- Flannel 컨테이너 네트워크 인터페이스 (CNI)
- CoreDNS 클러스터 DNS
- Traefik Ingress 컨트롤러
- ServiceLB 로드 밸런서 컨트롤러
- Kube-router 네트워크 정책 컨트롤러
- Local-path-provisioner 영구 볼륨 컨트롤러
- Spegel 분산 컨테이너 이미지 레지스트리 미러
- 호스트 유틸리티 (iptables, socat 등)
k3s의 아키텍쳐
- k3s는 서버 (Control Plane)와 에이전트 (Worker Node)로 구성되어 있습니다.
- 서버 노드는 Kubernetes의
k3s server
명령으로 실행되며 모든 컨트롤 플레인 구성 요소와 데이터 저장 컴포넌트를 실행하며 k3s가 관리합니다. - 에이전트 노드는
k3s agent
명령으로 실행되며 컨트롤 플레인 요소등 없이 워커 노드로 동작합니다. - 모든 서버와 에이전트는 kublet, 컨테이너 런타임, CNI 등을 포함한 모든 Kubernetes 구성 요소를 실행합니다.
- 더 자세한 내용은 다음 링크를 참고하세요. 링크
- 서버 노드는 Kubernetes의
- 단일 서버 구성 : 1대 K3S 서버(경량 DB = SQLite), 필요한 만큼의 K3S Agents (Worker Node) 구성
- 고가용성 구성 : Embedded DB (etcd 등), 외부 DB (MySQL, PostgreSQL 등) 사용 가능
k3s 설치
- k3s는 기본적으로
traefik
을 Ingress Controller로 사용하는데 이번 실습에서는 nginx ingress controller를 사용할 것이기 때문에traefik
을 설치하지 않겠습니다. -
INSTALL_K3S_EXEC=" --disable=traefik"
옵션을 사용하여 traefik을 설치하지 않을 수 있습니다.# Install k3s-server $ curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC=" --disable=traefik" sh -s - server --token [[인증토큰]] --cluster-cidr "172.16.0.0/16" --service-cidr "10.10.200.0/24" --write-kubeconfig-mode 644 # Install k3s-agent $ curl -sfL https://get.k3s.io | K3S_URL=https://192.168.10.10:6443 K3S_TOKEN=[[인증토큰]] sh -s -
- k3s 설치 후, k3의 설정을 확인해보겠습니다.
# 노드 확인
$ kubectl get node -owide
# => NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
# k3s-m Ready control-plane,master 30m v1.30.5+k3s1 10.0.2.15 <none> Ubuntu 22.04.5 LTS 5.15.0-119-generic containerd://1.7.21-k3s2
# k3s-w1 Ready <none> 4m24s v1.30.5+k3s1 10.0.2.15 <none> Ubuntu 22.04.5 LTS 5.15.0-119-generic containerd://1.7.21-k3s2
# k3s-w2 Ready <none> 26m v1.30.5+k3s1 10.0.2.15 <none> Ubuntu 22.04.5 LTS 5.15.0-119-generic containerd://1.7.21-k3s2
# k3s-w3 Ready <none> 24m v1.30.5+k3s1 10.0.2.15 <none> Ubuntu 22.04.5 LTS 5.15.0-119-generic containerd://1.7.21-k3s2
$ kubectl describe node k3s-m | grep Taint # Taints 없음
# => Taints: <none>
$ kubectl describe node k3s-w1 | grep Taint # Taints 없음
# => Taints: <none>
# 파드 확인
$ kubectl get pod -n kube-system
# => NAME READY STATUS RESTARTS AGE
# coredns-7b98449c4-8l64d 1/1 Running 0 31m
# local-path-provisioner-6795b5f9d8-b5gt6 1/1 Running 0 31m
# metrics-server-cdcc87586-d87gv 1/1 Running 0 31m
#
$ kubectl top node
# => NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
# k3s-m 147m 3% 1128Mi 28%
# k3s-w1 147m 3% 1128Mi 57%
# k3s-w2 147m 3% 1128Mi 57%
# k3s-w3 147m 3% 1128Mi 57%
$ kubectl top pod -A --sort-by='cpu'
# => NAMESPACE NAME CPU(cores) MEMORY(bytes)
# kube-system metrics-server-cdcc87586-d87gv 15m 19Mi
# kube-system coredns-7b98449c4-8l64d 4m 13Mi
# kube-system local-path-provisioner-6795b5f9d8-b5gt6 1m 6Mi
$ kubectl top pod -A --sort-by='memory'
$ kubectl get storageclass
# => NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
# local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 32m
# config 정보(위치) 확인
$ kubectl get pod -v=6
# => I1012 05:55:56.507623 6817 loader.go:395] Config loaded from file: /etc/rancher/k3s/k3s.yaml
# I1012 05:55:56.518338 6817 round_trippers.go:553] GET https://127.0.0.1:6443/api/v1/namespaces/default/pods?limit=500 200 OK in 5 milliseconds
# No resources found in default namespace.
$ cat /etc/rancher/k3s/k3s.yaml
# => apiVersion: v1
# clusters:
# - cluster:
# certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJlRENDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUzTWpnM01UQTJNREF3SGhjTk1qUXhNREV5TURVeU16SXdXaGNOTXpReE1ERXdNRFV5TXpJdwpXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUzTWpnM01UQTJNREF3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFReEdLOFFEcHMvNmNHdE45RWRCYmZJRmg2UjBpQlFLYUhHYWhVQXVMdjUKWHhpd1JjTVdia1FZNmxBdWM1RC9zWWYrTmhZYUFjcmNzMk01LzAyTkQ5bERvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVXJzL1ZVODFCZEJnS3N2YmJDRmhjCkJ5aStxUTB3Q2dZSUtvWkl6ajBFQXdJRFNRQXdSZ0loQUxwWXpzZkVMdjZScG56OGdqcDZXYkZuUFk2S3FrQ2gKTWYwRWZvMnRzM2d5QWlFQXhkaDM4akJCMWJrTWlwWDNSMTFyTnBtZmc2S2huZzliNUJDTUs0M3UyTjA9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
# server: https://127.0.0.1:6443
# name: default
# ...
$ export | grep KUBECONFIG
# => (공백)
# 네트워크 정보 확인 : flannel CNI(vxlan mode), podCIDR
$ ip -c addr
# => ...
# 4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
# link/ether 02:21:77:da:a3:91 brd ff:ff:ff:ff:ff:ff
# inet 172.16.0.0/32 scope global flannel.1
# valid_lft forever preferred_lft forever
# 5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
# link/ether ca:30:a0:c8:5c:cd brd ff:ff:ff:ff:ff:ff
# inet 172.16.0.1/24 brd 172.16.0.255 scope global cni0
# valid_lft forever preferred_lft forever
# 6: veth41d9e3b2@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
# link/ether fa:47:5c:4a:8d:af brd ff:ff:ff:ff:ff:ff link-netns cni-9c26655e-b22f-97a1-f97c-db88daccc77f
# 7: veth5c3de18a@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
# link/ether 6a:5e:39:72:4f:4c brd ff:ff:ff:ff:ff:ff link-netns cni-cff25bf8-d23b-790a-91d0-ed5c4ee526d5
# 8: vethfaeebb1c@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
# link/ether c6:f5:31:a1:56:21 brd ff:ff:ff:ff:ff:ff link-netns cni-06a3672f-70dc-7445-48c9-8cf8c26e7fb3
$ ip -c route
# => default via 10.0.2.2 dev enp0s3 proto dhcp src 10.0.2.15 metric 100
# ...
# 172.16.0.0/24 dev cni0 proto kernel scope link src 172.16.0.1
# 172.16.1.0/24 via 172.16.1.0 dev flannel.1 onlink
# 172.16.2.0/24 via 172.16.2.0 dev flannel.1 onlink
# 172.16.3.0/24 via 172.16.3.0 dev flannel.1 onlink
# 192.168.10.0/24 dev enp0s8 proto kernel scope link src 192.168.10.10
$ cat /run/flannel/subnet.env
# => FLANNEL_NETWORK=172.16.0.0/16
# FLANNEL_SUBNET=172.16.0.1/24
# FLANNEL_MTU=1450
# FLANNEL_IPMASQ=true
$ kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}' ;echo
# => 172.16.0.0/24 172.16.3.0/24 172.16.1.0/24 172.16.2.0/24
$ kubectl describe node | grep -A3 Annotations
# => Annotations: alpha.kubernetes.io/provided-node-ip: 10.0.2.15
# flannel.alpha.coreos.com/backend-data: {"VNI":1,"VtepMAC":"02:21:77:da:a3:91"}
# flannel.alpha.coreos.com/backend-type: vxlan
# flannel.alpha.coreos.com/kube-subnet-manager: true
# --
# Annotations: alpha.kubernetes.io/provided-node-ip: 10.0.2.15
# flannel.alpha.coreos.com/backend-data: {"VNI":1,"VtepMAC":"72:95:9e:3d:c6:35"}
# flannel.alpha.coreos.com/backend-type: vxlan
# flannel.alpha.coreos.com/kube-subnet-manager: true
# --
# Annotations: alpha.kubernetes.io/provided-node-ip: 10.0.2.15
# flannel.alpha.coreos.com/backend-data: {"VNI":1,"VtepMAC":"ae:28:43:65:df:f4"}
# flannel.alpha.coreos.com/backend-type: vxlan
# flannel.alpha.coreos.com/kube-subnet-manager: true
# --
# Annotations: alpha.kubernetes.io/provided-node-ip: 10.0.2.15
# flannel.alpha.coreos.com/backend-data: {"VNI":1,"VtepMAC":"8e:91:37:7d:c1:d7"}
# flannel.alpha.coreos.com/backend-type: vxlan
# flannel.alpha.coreos.com/kube-subnet-manager: true
$ brctl show
# => bridge name bridge id STP enabled interfaces
# cni0 8000.ca30a0c85ccd no veth41d9e3b2
# veth5c3de18a
# vethfaeebb1c
# 서비스와 엔드포인트 확인
$ kubectl get svc,ep -A
# => NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# default service/kubernetes ClusterIP 10.10.200.1 <none> 443/TCP 38m
# kube-system service/kube-dns ClusterIP 10.10.200.10 <none> 53/UDP,53/TCP,9153/TCP 38m
# kube-system service/metrics-server ClusterIP 10.10.200.103 <none> 443/TCP 38m
#
# NAMESPACE NAME ENDPOINTS AGE
# default endpoints/kubernetes 10.0.2.15:6443 38m
# kube-system endpoints/kube-dns 172.16.0.4:53,172.16.0.4:53,172.16.0.4:9153 38m
# kube-system endpoints/metrics-server 172.16.0.3:10250 38m
# iptables 정보 확인
$ iptables -t filter -S
$ iptables -t nat -S
$ iptables -t mangle -S
# tcp listen 포트 정보 확인
$ ss -tnlp
- flannel CNI를 사용하고 있고, 클러스터 IP는 172.16.0.0/16이며, 컨트롤 플레인의기능들이 많이 내장되어있어 실행중인 파드가 적음을 확인 할 수 있습니다.
Nginx Ingress Controller 설치
- Nginx Ingress Controller는 가장 많이 사용되는 Ingress Controller 중 하나로 Ingress 실습을 위해 설치해보겠습니다.
- 먼저 NGINX Ingress 의 특징을 살펴보겠습니다.
- NGINX Ingress는 고성능 웹서버인 NGINX를 기반으로 동작하며, Layer 7에서 동작합니다.
- k8s의 configmap 설정을 lua 스크립트로 가공하여 nginx config로 변환하여 사용합니다.
- 설정을 변경하면 내부의 nginx가 reload 되면서 자동으로 적용되며, 설정을 쉽게 변경할 수 있습니다.
# 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
# => "ingress-nginx" has been added to your repositories
$ helm repo update
# => ...Successfully got an update from the "ingress-nginx" chart repository
$ kubectl create ns ingress
# => namespace/ingress created
$ helm install ingress-nginx ingress-nginx/ingress-nginx -f ingress-nginx-values.yaml --namespace ingress --version 4.11.2
# => Release "ingress-nginx" has been upgraded. Happy Helming!
# NAME: ingress-nginx
# NAMESPACE: ingress
# STATUS: deployed
# ...
# The ingress-nginx controller has been installed.
# Get the application URL by running these commands:
# export HTTP_NODE_PORT=30080
# export HTTPS_NODE_PORT=30443
# export NODE_IP="$(kubectl get nodes --output jsonpath="{.items[0].status.addresses[1].address}")"
#
# echo "Visit http://${NODE_IP}:${HTTP_NODE_PORT} to access your application via HTTP."
# echo "Visit https://${NODE_IP}:${HTTPS_NODE_PORT} to access your application via HTTPS."
# ...
# 확인
$ kubectl get all -n ingress
# => NAME READY STATUS RESTARTS AGE
# pod/ingress-nginx-controller-7b67846f8f-jdt65 1/1 Running 0 47s
#
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# service/ingress-nginx-controller NodePort 10.10.200.113 <none> 80:30080/TCP,443:30443/TCP 47s
# service/ingress-nginx-controller-admission ClusterIP 10.10.200.176 <none> 443/TCP 47s
# service/ingress-nginx-controller-metrics ClusterIP 10.10.200.218 <none> 10254/TCP 47s
#
# NAME READY UP-TO-DATE AVAILABLE AGE
# deployment.apps/ingress-nginx-controller 1/1 1 1 47s
#
# NAME DESIRED CURRENT READY AGE
# replicaset.apps/ingress-nginx-controller-7b67846f8f 1 1 1 47s
$ kubectl describe svc -n ingress ingress-nginx-controller
# => Name: ingress-nginx-controller
# Namespace: ingress
# Labels: app.kubernetes.io/component=controller
# app.kubernetes.io/instance=ingress-nginx
# ...
# Selector: app.kubernetes.io/component=controller,app.kubernetes.io/instance=ingress-nginx,app.kubernetes.io/name=ingress-nginx
# Type: NodePort
# IP Family Policy: SingleStack
# IP Families: IPv4
# IP: 10.10.200.113
# ...
# Port: http 80/TCP
# TargetPort: http/TCP
# NodePort: http 30080/TCP
# Endpoints: 172.16.0.16:80
# Port: https 443/TCP
# TargetPort: https/TCP
# NodePort: https 30443/TCP
# Endpoints: 172.16.0.16:443
# ...
# externalTrafficPolicy 설정
$ kubectl patch svc -n ingress ingress-nginx-controller -p '{"spec":{"externalTrafficPolicy": "Local"}}'
# => service/ingress-nginx-controller patched
# 기본 nginx conf 파일 확인
$ kubectl describe cm -n ingress ingress-nginx-controller
# => ...
# Data
# ====
# allow-snippet-annotations:
# ----
# false
# ...
$ kubectl exec deploy/ingress-nginx-controller -n ingress -it -- cat /etc/nginx/nginx.conf
# => # Configuration checksum: 13054992059071414660
# # setup custom paths that do not require root access
# pid /tmp/nginx/nginx.pid;
#
# daemon off;
# worker_processes 4;
# worker_rlimit_nofile 1047552;
# worker_shutdown_timeout 240s ;
#
# events {
# multi_accept on;
# worker_connections 16384;
# use epoll;
# }
#
# http {
# lua_package_path "/etc/nginx/lua/?.lua;;";
# lua_shared_dict balancer_ewma 10M;
# ...
# 관련된 정보 확인 : 포드(Nginx 서버), 서비스, 디플로이먼트, 리플리카셋, 컨피그맵, 롤, 클러스터롤, 서비스 어카운트 등
$ kubectl get all,sa,cm,secret,roles -n ingress
# => NAME READY STATUS RESTARTS AGE
# pod/ingress-nginx-controller-7b67846f8f-jdt65 1/1 Running 0 4m21s
#
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# service/ingress-nginx-controller NodePort 10.10.200.113 <none> 80:30080/TCP,443:30443/TCP 4m21s
# service/ingress-nginx-controller-admission ClusterIP 10.10.200.176 <none> 443/TCP 4m21s
# service/ingress-nginx-controller-metrics ClusterIP 10.10.200.218 <none> 10254/TCP 4m21s
#
# NAME READY UP-TO-DATE AVAILABLE AGE
# deployment.apps/ingress-nginx-controller 1/1 1 1 4m21s
#
# NAME DESIRED CURRENT READY AGE
# replicaset.apps/ingress-nginx-controller-7b67846f8f 1 1 1 4m21s
#
# NAME SECRETS AGE
# serviceaccount/default 0 4m29s
# serviceaccount/ingress-nginx 0 4m21s
#
# NAME DATA AGE
# configmap/ingress-nginx-controller 1 4m21s
# configmap/kube-root-ca.crt 1 4m30s
#
# NAME TYPE DATA AGE
# secret/ingress-nginx-admission Opaque 3 4m24s
# secret/sh.helm.release.v1.ingress-nginx.v1 helm.sh/release.v1 1 4m26s
#
# NAME CREATED AT
# role.rbac.authorization.k8s.io/ingress-nginx 2024-01-01T08:53:04Z
$ kubectl 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
# => -------------------------------------------------------------------------------
# NGINX Ingress controller
# Release: v1.11.2
# Build: 46e76e5916813cfca2a9b0bfdc34b69a0000f6b9
# Repository: https://github.com/kubernetes/ingress-nginx
# nginx version: nginx/1.25.5
# -------------------------------------------------------------------------------
- Ingress Controller가 설치되었으며, NodePort로 서비스가 생성된것을 확인할 수 있습니다.
-
또한 Nginx Ingress Controller의 경우 내부적으로는 일반적인 nginx 서버가 동일하게 동작하고, lua 스크립트를 사용하여 configmap의 설정이 적용/관리되고 있음을 확인할 수 있습니다.
- (옵션) kubectl krew 설치 - 링크 & ingress-nginx plugin 설치 - 링크
# (참고) 운영체제 확인 : linux
$ OS="$(uname | tr '[:upper:]' '[:lower:]')"
# (참고) CPU 아키텍처 확인 : amd64
$ ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')"
# (참고) KREW 지정 : krew-linux_amd64
$ KREW="krew-${OS}_${ARCH}"
# kubectl krew 설치
# curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/${KREW}.tar.gz"
$ curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/krew-linux_amd64.tar.gz" && tar zxvf krew-linux_amd64.tar.gz && ./krew-linux_amd64 install krew
$ export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"
# 플러그인 정보 업데이트 후 확인 - 링크
$ kubectl krew update
$ kubectl krew search
# ingress-nginx 플러그인 설치
$ kubectl krew install ingress-nginx
# => (아쉽게도 옛날 버전이라서 설치가 안 됩니다.)
# ingress-nginx 플러그인 명령어 실행(도움말 출력)
$ kubectl ingress-nginx
# nginx ctrl 의 backends 설정 정보 출력
$ kubectl ingress-nginx backends -n ingress-nginx --list
$ kubectl ingress-nginx backends -n ingress-nginx
# conf 출력
$ kubectl ingress-nginx conf -n ingress-nginx
## 특정 호스트(도메인) 설정 확인
$ kubectl ingress-nginx conf -n ingress-nginx --host gasida.cndk.link
$ kubectl ingress-nginx conf -n ingress-nginx --host nasida.cndk.link
# 정보 보기 편함!
$ kubectl ingress-nginx ingresses
$ kubectl ingress-nginx ingresses --all-namespaces
인그레스(Ingress) 실습 및 통신 흐름 확인
- 실습 구성도
- 컨트롤플레인 노드에 인그레스 컨트롤러(Nginx) 파드를 생성하고, NodePort 로 외부에 노출합니다.
- 인그레스 정책 설정 : Host/Path routing, 실습의 편리를 위해서 도메인 없이 IP로 접속 설정 가능하도록 합니다.
deployment와 service 생성
- svc1-pod.yml 생성
# svc1-pod.yml 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.yml 생성
# svc2-pod.yml 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.yml 생성
# svc3-pod.yml 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-m role=controlplane:NoSchedule # <span style="color: green;"></span> $ kubectl apply -f svc1-pod.yml,svc2-pod.yml,svc3-pod.yml # => deployment.apps/deploy1-websrv created # service/svc1-web created # deployment.apps/deploy2-guestsrv created # service/svc2-guest created # deployment.apps/deploy3-adminsrv created # service/svc3-admin created # 확인 : svc1, svc3 은 ClusterIP 로 클러스터 외부에서는 접속할 수 없다 >> Ingress 는 연결 가능! $ kubectl get pod,svc,ep # => NAME READY STATUS RESTARTS AGE # pod/deploy1-websrv-5c6b88bd77-ht5hl 1/1 Running 0 34s # pod/deploy2-guestsrv-649875f78b-8wh8r 1/1 Running 0 34s # pod/deploy2-guestsrv-649875f78b-jcvrf 1/1 Running 0 34s # pod/deploy3-adminsrv-7c8f8b8c87-4mzv7 1/1 Running 0 34s # pod/deploy3-adminsrv-7c8f8b8c87-4sqmh 1/1 Running 0 34s # pod/deploy3-adminsrv-7c8f8b8c87-ztltl 1/1 Running 0 34s # # NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE # service/kubernetes ClusterIP 10.10.200.1 <none> 443/TCP 5h52m # service/svc1-web ClusterIP 10.10.200.141 <none> 9001/TCP 34s # service/svc2-guest NodePort 10.10.200.60 <none> 9002:30901/TCP 34s # service/svc3-admin ClusterIP 10.10.200.171 <none> 9003/TCP 34s # <span style="color: green;"># ingress는 pod 정보로 바로 접근 가능하므로 서비스가 ClusterIP이든 NodePort 타입이든 관계 없습니다.</span> # # NAME ENDPOINTS AGE # endpoints/kubernetes 192.168.10.10:6443 5h52m # endpoints/svc1-web 172.16.1.8:80 34s # endpoints/svc2-guest 172.16.2.8:8080,172.16.3.7:8080 34s # endpoints/svc3-admin 172.16.1.7:8080,172.16.2.9:8080,172.16.3.8:8080 34s
인그레스(정책) 생성
ingress 정책 적용 구조 (출처)
- ingress1.yml 파일 생성
cat <<EOT> ingress1.yml
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
- ingress 정책 생성
# 모니터링
$ watch -d 'kubectl get ingress,svc,ep,pod -owide'
# 생성
$ kubectl apply -f ingress1.yml
# => ingress.networking.k8s.io/ingress-1 created
# 확인
$ kubectl get ingress
# => NAME CLASS HOSTS ADDRESS PORTS AGE
# ingress-1 nginx * 80 11s
$ kubectl 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";
# ...
ingress를 통한 내부 접속
-
Nginx ingress controller를 통해 접속시 서비스는 파드의 엔드포인트의 정보만 참조되고, 서비스를 거치지 않고 바로 파드로 전달됩니다.
인그레스 접속 경로(서비스 Bypass) : Ingress → 애플리케이션(Deploy, Pod 등)
- 참고 : URI(Uniform Resource Identifier)는 RFC 3986에 정의된 통합 자원 식별자로, 흔히 사용되는 URL(Uniform Resource Locator)과 URN(Uniform Resource Name)을 포함합니다.
- Request URI는 서버 주소나 파일이름, 파라미터 등 다양한 리소스를 식별하기 위해 사용되는 문자열입니다.
- 절대 URI(absolute URI)는 스키마와 호스트를 포함한 완전한 URI를 의미하며, 상대 URI(relative URI)는 스키마와 호스트를 포함하지 않고 현재 위치에서 상대적인 위치를 기록한 URI를 의미합니다.
- URI의 구조는 아래와 같습니다.
책 ‘그림으로 공부하는 TCP/IP 구조’ 중 발췌
- URI의 구조는 아래와 같습니다.
- 참고 : X-Forwarded-For 헤더, X-Forwarded-Proto 헤더
- X-Forwarded-For 헤더는 송신지 IP 주소가 변환되는 환경(장비, 서버, 솔루션 등)에서, 변환 전 송신지(클라이언트) IP 주소를 저장하는 헤더입니다.
- 여러 장비나 솔루션을 거칠 경우
,
로 구분하여 여러 건이 넘어올 수도 있습니다. 그럴 경우 가장 왼쪽 것이 클라이언트 IP이고, 오른쪽으로 갈 수록 나중에 처리된 장비/솔루션의 IP가 됩니다.
- 여러 장비나 솔루션을 거칠 경우
- X-Forwarded-Proto 헤더는 변환 전 프로토콜을 저장합니다. (예. SSL Offload 환경에서 서버 측에서 클라이언트가 요청 시 사용한 원래 프로토콜을 확인)
- 이러한 헤더는 클라이언트의 IP 주소를 확인하거나, 프로토콜을 확인하는 등의 용도로 사용되며, 어플리케이션에서 NodePort나 LoadBalancer를 통해서 접속되었을때도 원래의 클라이언트의 IP를 확인할 수 있게 해줍니다. (
externalTrafficPolicy: Local
를 사용할 필요가 줄어듭니다!) - 원래의 IP를 가져오는 방법은 다음의 방법들이 있습니다.
- Http request header 중 다음 값들에서 원래의 IP 찾기
- X-Forwarded-For : HTTP RFC 표준에는 없지만 사실상 표준!!!
- Proxy-Client-IP : 특정 웹 어플리케이션에서 사용 (예. WebLogic Connector - mod_wl)
- WL-Proxy-Client-IP : 특정 웹 어플리케이션에서 사용 (예. WebLogic Connector - mod_wl)
- CLIENT_IP
- Http request header 중 다음 값들에서 원래의 IP 찾기
- X-Forwarded-For 헤더는 송신지 IP 주소가 변환되는 환경(장비, 서버, 솔루션 등)에서, 변환 전 송신지(클라이언트) IP 주소를 저장하는 헤더입니다.
- 인그레스(Nginx 인그레스 컨트롤러)를 통한 접속(HTTP 인입)을 확인해보겠습니다.
# (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.113 80 18m
$ 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 또는 컨트롤플레인 node ip>
$ MYIP=192.168.10.10
$ curl -s $MYIP:30080
# => ...
# <h1>Welcome to nginx!</h1>
# <p>If you see this page, the nginx web server is successfully installed and
# working. Further configuration is required.</p>
#
# <p>For online documentation and support please refer to
# <a href="http://nginx.org/">nginx.org</a>.<br/>
# Commercial support is available at
# <a href="http://nginx.com/">nginx.com</a>.</p>
#
# <p><em>Thank you for using nginx.</em></p>
# ...
# svc2-guest 접속
$ curl -s $MYIP:30080/guest
# => Hello Kubernetes bootcamp! | Running on: deploy2-guestsrv-649875f78b-jcvrf | v=1
$ for i in {1..100}; do curl -s $MYIP:30080/guest ; done | sort | uniq -c | sort -nr
# => 51 Hello Kubernetes bootcamp! | Running on: deploy2-guestsrv-649875f78b-8wh8r | v=1
# 49 Hello Kubernetes bootcamp! | Running on: deploy2-guestsrv-649875f78b-jcvrf | v=1
# svc3-admin 접속 > 기본적으로 Nginx 는 라운드로빈 부하분산 알고리즘을 사용 >> Client_address 와 XFF 주소는 어떤 주소인가요?
$ curl -s $MYIP:30080/admin
$ curl -s $MYIP:30080/admin | egrep '(client_address|x-forwarded-for)'
# => client_address=172.16.0.16
# x-forwarded-for=172.16.0.1
$ for i in {1..100}; do curl -s $MYIP:30080/admin | grep Hostname ; done | sort | uniq -c | sort -nr
# => 34 Hostname: deploy3-adminsrv-7c8f8b8c87-4sqmh
# 33 Hostname: deploy3-adminsrv-7c8f8b8c87-ztltl
# 33 Hostname: deploy3-adminsrv-7c8f8b8c87-4mzv7
# (옵션) 디플로이먼트의 파드 갯수를 증가/감소 설정 후 접속 테스트 해보자
$ kubectl scale deployment deploy3-adminsrv --replicas 2 # svc3-admin의 파드 갯수를 2개로 감소
# => deployment.apps/deploy3-adminsrv scaled
$ kubectl get deploy deploy3-adminsrv
# => NAME READY UP-TO-DATE AVAILABLE AGE
# deploy3-adminsrv 2/2 2 2 80m
# <span style="color: green;">파드수가 3개 => 2개로 줄었습니다.</span>
# 접속 테스트
$ for i in {1..100}; do curl -s $MYIP:30080/admin | grep Hostname ; done | sort | uniq -c | sort -nr
# => 50 Hostname: deploy3-adminsrv-7c8f8b8c87-4sqmh
# 50 Hostname: deploy3-adminsrv-7c8f8b8c87-4mzv7
# <span style="color: green;">2개로 줄어든 파드수만큼 2개의 파드에 부하가 분산 되는것을 확인하였습니다.</span>
- 노드에서 패킷 캡쳐 확인 : flannel vxlan의 파드간 통신시 IP정보 확인
# ngrep을 이용해 패킷 캡쳐
$ ngrep -tW byline -d enp0s8 '' udp port 8472 or tcp port 80
# => interface: enp0s8 (192.168.10.0/255.255.255.0)
# filter: ( udp port 8472 or tcp port 80 ) and ((ip || ip6) || (vlan && (ip || ip6)))
# #
# U 2024/10/12 12:42:39.071289 192.168.10.10:39828 -> 192.168.10.102:8472 #1
# .........(Ce...!w.....E..<..@.?............|.P...........\...........
# d9?.........
# #
# U 2024/10/12 12:42:39.072521 192.168.10.102:37126 -> 192.168.10.10:8472 #2
# .........!w....(Ce....E..<..@.?............P.|(3c@.......4.y.........
# ....d9?.....
# #
# U 2024/10/12 12:42:39.072734 192.168.10.10:39828 -> 192.168.10.102:8472 #3
# .........(Ce...!w.....E..4..@.?............|.P....(3cA.....K.....
# d9?.....
# #
# U 2024/10/12 12:42:39.072855 192.168.10.10:39828 -> 192.168.10.102:8472 #4
# .........(Ce...!w.....E..c..@.?..Y.........|.P....(3cA...........
# d9?.....GET / HTTP/1.1.
# Host: localhost:30080.
# X-Request-ID: e8aa4e70150ae6ae8de5a34637e294e6.
# X-Real-IP: 172.16.0.1.
# X-Forwarded-For: 172.16.0.1.
# X-Forwarded-Host: localhost:30080.
# X-Forwarded-Port: 80.
# X-Forwarded-Proto: http.
# X-Forwarded-Scheme: http.
# X-Scheme: http.
# User-Agent: curl/7.81.0.
# Accept: */*.
# ...
# tcp dump를 이용해 vxlan(udp 8472) 통신 확인
$ tcpdump -i enp0s8 udp port 8472 -nn
# => tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
# listening on enp0s8, link-type EN10MB (Ethernet), snapshot length 262144 bytes
# 12:42:13.504948 IP 192.168.10.10.55617 > 192.168.10.102.8472: OTV, flags [I] (0x08), overlay 0, instance 1
# IP 172.16.0.16.57692 > 172.16.1.8.80: Flags [S], seq 911277209, win 64860, options [mss 1410,sackOK,TS val 1681447926 ecr 0,nop,wscale 7], length 0
# ...
# vethY는 각자 k3s-s 의 가장 마지막 veth 를 지정
$ tcpdump -i vethY tcp port 8080 -nn
# => tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
# listening on veth5ae3dd58, link-type EN10MB (Ethernet), snapshot length 262144 bytes
# 12:44:11.609334 IP 172.16.0.16.41240 > 172.16.2.9.8080: Flags [S], seq 1593288127, win 64860, options [mss 1410,sackOK,TS val 3487210526 ecr 0,nop,wscale 7], length 0
# 12:44:11.610875 IP 172.16.2.9.8080 > 172.16.0.16.41240: Flags [S.], seq 1820942288, ack 1593288128, win 64308, options [mss 1410,sackOK,TS val 257720908 ecr 3487210526,nop,wscale 7], length 0
$ tcpdump -i vethY tcp port 8080 -w /tmp/ingress-nginx.pcap
---
# 다른 터미널에서 svc3-admin 접속
$ curl -s $MYIP:30080/admin
---
# 자신의 PC에서 k3s-s EC2 공인 IP로 pcap 다운로드
# $ scp ubuntu@<k3s-s EC2 공인 IP>:/tmp/ingress-nginx.pcap ~/Downloads
$ scp ubuntu@43.202.1.177:/tmp/ingress-nginx.pcap ~/Downloads
인그레스를 통한 접속 흐름
- 패킷 캡쳐 결과 ingress controller에서 파드의 ip로 바로 접속 됨을 확인할 수 있었습니다.
-
또한, flannel CNI를 사용하기 때문에 vxlan을 통해 통신이 이루어지고 있음을 확인할 수 있습니다.
- Nginx 파드가 endpoint 정보 등을 모니터링 가능한 이유는 클러스터롤과 롤(엔드포인트 list, watch)를 바인딩된 서비스 어카운트를 파드가 사용하기 때문입니다.
$ kubectl describe deployments.apps -n ingress ingress-nginx-controller | grep 'Service Account'
# => Service Account: ingress-nginx
$ kubectl get -n ingress clusterrolebindings ingress-nginx
# => NAME ROLE AGE
# ingress-nginx ClusterRole/ingress-nginx 4h9m
$ kubectl get -n ingress rolebindings ingress-nginx
# => NAME ROLE AGE
# ingress-nginx Role/ingress-nginx 4h9m
$ kubectl describe clusterrole ingress -n ingress | egrep '(Verbs|endpoints)'
# => Resources Non-Resource URLs Resource Names Verbs
# endpointslices.discovery.k8s.io [] [] [list watch get]
# endpoints [] [] [list watch]
$ kubectl describe roles ingress-nginx -n ingress | egrep '(Verbs|endpoints)'
# => Resources Non-Resource URLs Resource Names Verbs
# endpoints [] [] [get list watch]
# endpointslices.discovery.k8s.io [] [] [list watch get]
패킷 분석
- 클러스터 외부에서 접속 후 내부로 접속하는 패킷을 분석해보겠습니다.
- 위의 실습과 동일하지만 veth에서 8080을 캡쳐하고 노드의 nic에서 8472 (vxnet)를 캡쳐하여 병합(merge)하여 확인하였습니다.
- 또한, 클라이언트의 IP 주소를 확인하기 위해 X-Forwarded-For 헤더를 확인하였습니다.
- 위의 그림과 같이 프로토콜의 정보를 그림으로 보려면 아래와 같이 환경설정에서 Appearance > Layout에서 Pane 3에
“Packet Diagram”을 선택하시면 됩니다.
Nginx 분산 알고리즘 변경
- nginx는 기본 RR(Round Robin) 방식으로 부하분산을 수행하지만, IP-Hash나 Session Cookie 설정으로 변경할 수 있습니다.
- 특히 IP-Hash 나 Session Cookie를 사용하면 각 클라이언트에서 대상 파드를 고정할 수 있습니다.
- 이를 변경하기 위해서는
nginx.ingress.kubernetes.io/upstream-hash-by
annotation을 사용하여 변경하여야 하는데 실습을 통해 확인해보겠습니다.
# mypc
$ for i in {1..100}; do curl -s $MYIP:30080/admin | grep Hostname ; done | sort | uniq -c | sort -nr
# => 51 Hostname: deploy3-adminsrv-7c8f8b8c87-4sqmh
# 49 Hostname: deploy3-adminsrv-7c8f8b8c87-4mzv7
$ 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.yml
$ kubectl apply -f ingress1.yml
# => ingress.networking.k8s.io/ingress-1 configured
# 접속 확인
$ for i in {1..100}; do curl -s $MYIP:30080/admin | grep Hostname ; done | sort | uniq -c | sort -nr
# => 100 Hostname: deploy3-adminsrv-7c8f8b8c87-4sqmh
$ 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.yml
$ kubectl apply -f ingress1.yml
# => ingress.networking.k8s.io/ingress-1 configured
# 접속 확인
$ for i in {1..100}; do curl -s $MYIP:30080/admin | grep Hostname ; done | sort | uniq -c | sort -nr
# => 50 Hostname: deploy3-adminsrv-7c8f8b8c87-4sqmh
# 50 Hostname: deploy3-adminsrv-7c8f8b8c87-4mzv7
$ while true; do curl -s --connect-timeout 1 $MYIP:30080/admin | grep Hostname ; date "+%Y-%m-%d %H:%M:%S" ; echo "--------------" ; sleep 1; done
- ip-hash 설정을 통해 클라이언트의 IP 주소를 해싱하여 특정 파드로 접속되는 것을 확인할 수 있습니다.
- 오브젝트 삭제
$ kubectl delete deployments,svc,ingress --all
Host 기반 라우팅
- ingress2.yml 파일 생성
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
- ingress 정책 생성
# 터미널1
$ watch -d 'kubectl get ingresses,svc,ep,pod -owide'
# 도메인 변경
# $ MYDOMAIN1=<각자 자신의 닉네임의 도메인> 예시) gasida.com
$ MYDOMAIN1=sweetlittlebird.com
$ sed -i "s/kans.com/$MYDOMAIN1/g" ingress2.yaml
# 생성
$ kubectl apply -f ingress2.yaml,svc3-pod.yaml
# => ingress.networking.k8s.io/ingress-2 created
# deployment.apps/deploy3-adminsrv created
# service/svc3-admin created
# 확인
$ kubectl get ingress
# => NAME CLASS HOSTS ADDRESS PORTS AGE
# ingress-2 nginx sweetlittlebird.com,*.sweetlittlebird.com 10.10.200.113 80 14s
$ kubectl describe ingress ingress-2
$ kubectl describe ingress ingress-2 | sed -n "5, \$p"
# => ...
# Default backend: <default>
# Rules:
# Host Path Backends
# ---- ---- --------
# sweetlittlebird.com / svc3-admin:8080 ()
# *.sweetlittlebird.com /echo svc3-admin:8080 ()
# ...
- Host 기반 라우팅을 통해 서비스에 접속해보겠습니다.
# 로그 모니터링
$ kubetail -n ingress -l app.kubernetes.io/component=controller
# => Will tail 1 logs...
# ingress-nginx-controller-7b67846f8f-jdt65
# => [ingress-nginx-controller-7b67846f8f-jdt65] 192.168.10.1 - - [01/Jan/2024:13:52:42 +0000] "GET / HTTP/1.1" 200 677 "-" "curl/8.5.0" 88 0.002 [default-svc3-admin-8080] [] 172.16.3.10:8080 852 0.002 200 f22ddba305f55138796fc866f7416890
# [ingress-nginx-controller-7b67846f8f-jdt65] 192.168.10.1 - - [01/Jan/2024:13:53:47 +0000] "GET /admin HTTP/1.1" 200 687 "-" "curl/8.5.0" 93 0.002 [default-svc3-admin-8080] [] 172.16.2.10:8080 863 0.002 200 966f7a55060f1497eed20818d4bef890
# ...
------------
# 자신의 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=sweetlittlebird.com
$ MYDOMAIN2=test.sweetlittlebird.com
$ echo $MYIP $MYDOMAIN1 $MYDOMAIN2
# => 192.168.10.10 sweetlittlebird.com test.sweetlittlebird.com
$ echo "$MYIP $MYDOMAIN1" | sudo tee -a /etc/hosts
$ echo "$MYIP $MYDOMAIN2" | sudo tee -a /etc/hosts
$ cat /etc/hosts | grep $MYDOMAIN1
# => 192.168.10.10 sweetlittlebird.com
# 192.168.10.10 test.sweetlittlebird.com
# svc3-admin 접속 > 결과 확인
$ curl $MYDOMAIN1:30080 -v
# => * Host sweetlittlebird.com:30080 was resolved.
# * IPv4: 192.168.10.10
# * Trying 192.168.10.10:30080...
# * Connected to sweetlittlebird.com (192.168.10.10) port 30080
# > GET / HTTP/1.1
# > Host: sweetlittlebird.com:30080
# > User-Agent: curl/8.5.0
# > Accept: */*
# >
# < HTTP/1.1 200 OK
# < Date: Sat, 01 Jan 2024 13:52:42 GMT
# < Content-Type: text/plain
# < Transfer-Encoding: chunked
# < Connection: keep-alive
# <
#
# Hostname: deploy3-adminsrv-7c8f8b8c87-48xwp
#
# Pod Information:
# -no pod information available-
#
# Server values:
# server_version=nginx: 1.13.0 - lua: 10008
#
# Request Information:
# client_address=172.16.0.16
# method=GET
# real path=/
# query=
# request_version=1.1
# request_uri=http://sweetlittlebird.com:8080/
#
# Request Headers:
# accept=*/*
# host=sweetlittlebird.com:30080
# user-agent=curl/8.5.0
# x-forwarded-for=192.168.10.1
# x-forwarded-host=sweetlittlebird.com:30080
# x-forwarded-port=80
# x-forwarded-proto=http
# ...
$ curl $MYDOMAIN1:30080/admin
# =>
#
# Hostname: deploy3-adminsrv-7c8f8b8c87-bm7dq
# ...
# client_address=172.16.0.16
# method=GET
# real path=/admin
# ...
# request_uri=http://sweetlittlebird.com:8080/admin
#
# Request Headers:
# accept=*/*
# host=sweetlittlebird.com:30080
# user-agent=curl/8.5.0
# x-forwarded-for=192.168.10.1
# x-forwarded-host=sweetlittlebird.com:30080
# x-forwarded-port=80
# x-forwarded-proto=http
# x-forwarded-scheme=http
# ...
$ curl $MYDOMAIN1:30080/echo
# => Hostname: deploy3-adminsrv-7c8f8b8c87-ndwkj
# ...
# client_address=172.16.0.16
# method=GET
# real path=/echo
# ...
# request_uri=http://sweetlittlebird.com:8080/echo
#
# Request Headers:
# accept=*/*
# host=sweetlittlebird.com:30080
# user-agent=curl/8.5.0
# x-forwarded-for=192.168.10.1
# x-forwarded-host=sweetlittlebird.com:30080
# x-forwarded-port=80
# x-forwarded-proto=http
# x-forwarded-scheme=http
# ...
$ curl $MYDOMAIN1:30080/echo/1
# => Hostname: deploy3-adminsrv-7c8f8b8c87-48xwp
#
# Pod Information:
# -no pod information available-
#
# Server values:
# server_version=nginx: 1.13.0 - lua: 10008
#
# Request Information:
# client_address=172.16.0.16
# method=GET
# real path=/echo/1
# query=
# request_version=1.1
# request_uri=http://sweetlittlebird.com:8080/echo/1
# ...
$ curl $MYDOMAIN2:30080 -v
# => * Host test.sweetlittlebird.com:30080 was resolved.
# * IPv4: 192.168.10.10
# * Trying 192.168.10.10:30080...
# * Connected to test.sweetlittlebird.com (192.168.10.10) port 30080
# > GET / HTTP/1.1
# > Host: test.sweetlittlebird.com:30080
# ...
# < HTTP/1.1 404 Not Found
# ...
$ curl $MYDOMAIN2:30080/admin
# => <html>
# <head><title>404 Not Found</title></head>
# <body>
# <center><h1>404 Not Found</h1></center>
# <hr><center>nginx</center>
# </body>
# </html>
$ curl $MYDOMAIN2:30080/echo
# => Hostname: deploy3-adminsrv-7c8f8b8c87-ndwkj
#
# Pod Information:
# -no pod information available-
#
# Server values:
# server_version=nginx: 1.13.0 - lua: 10008
#
# Request Information:
# client_address=172.16.0.16
# method=GET
# real path=/echo
# ...
# request_uri=http://test.sweetlittlebird.com:8080/echo
#
# Request Headers:
# accept=*/*
# host=test.sweetlittlebird.com:30080
# user-agent=curl/8.5.0
# x-forwarded-for=192.168.10.1
# x-forwarded-host=test.sweetlittlebird.com:30080
# x-forwarded-port=80
# x-forwarded-proto=http
# x-forwarded-scheme=http
# ...
$ curl $MYDOMAIN2:30080/echo/1
$ curl $MYDOMAIN2:30080/echo/1/2
## (옵션) /etc/hosts 파일 변경 없이 접속 방안
$ curl -H "host: $MYDOMAIN1" $MYIP:30080
# => (정상)
$ curl -H "host: $MYDOMAIN2" $MYIP:30080
# => (404 에러)
$ curl -H "host: $MYDOMAIN2" $MYIP:30080/echo
# => (정상 응답 옴)
- 실습결과 sweetlittlebird.com으로는 모든 응답이 200 OK 응답이 오고, test.sweetlittlebird.com으로 접속시에는 /echo 경로로 접속해야만 200 OK 응답이 오고, 그 외의 경로로 접속시에는 404 에러가 발생하는 것을 확인할 수 있습니다.
- 아래의 룰대로 잘 접속이 되는 것을 확인할 수 있습니다.
# sweetlittlebird.com이라는 호스트로 접속시 모든 경로에 대해서 200 OK 응답 sweetlittlebird.com / svc3-admin:8080 # test.sweetlittlebird.com 처럼 서브 도메인이 있는 호스트명으로 접속시 /echo 경로로만 200 OK 응답 *.sweetlittlebird.com /echo svc3-admin:8080
- 오브젝트 삭제
$ kubectl delete deployments,svc,ingress --all
카나리 업데이트
- 카나리 업데이트는 새로운 버전의 파드를 배포하고, 일부 트래픽만 새로운 버전으로 전환하고, 새로운 버전의 정상동작 확인 후 전체를 새로운 버전으로 전환하는 업데이트 방식입니다.
- 배포 자동화시 최소 중단/무중단으로 하는 방법을 몇가지 살펴보겠습니다.
- 롤링 업데이트
- 파드를 하나씩 새로운 버전으로 교체하는 방식으로, 기존 버전의 파드가 정상동작하는지 확인 후 다음 파드로 교체하는 방식입니다.
- 카나리 업데이트
- 일부 트래픽을 새로운 버전으로 전환하고, 정상동작 확인 후 전체로 전환하는 방식입니다.
- 블루/그린 업데이트
- 새로운 버전의 파드를 새로운 서비스로 배포하고, 모든 파드 배포 후, 하나씩 전환하는 롤링 업데이트와는 다르게 전체 트래픽을 한꺼번에 새로운 서비스로 전환하는 방식입니다.
- 롤링 업데이트
- 실습을 통해 nginx ingress controller를 이용한 카나리 업데이트를 진행해보겠습니다.
-
canary-svc1-pod.yml 파일 생성
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.yml 파일 생성
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' # 생성 $ kubectl apply -f canary-svc1-pod.yml,canary-svc2-pod.yml # => deployment.apps/dp-v1 created # service/svc-v1 created # deployment.apps/dp-v2 created # service/svc-v2 created # 확인 $ kubectl get svc,ep,pod # => NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE # service/kubernetes ClusterIP 10.10.200.1 <none> 443/TCP 12m # service/svc-v1 ClusterIP 10.10.200.231 <none> 9001/TCP 18s # service/svc-v2 ClusterIP 10.10.200.216 <none> 9001/TCP 18s # # NAME ENDPOINTS AGE # endpoints/kubernetes 192.168.10.10:6443 12m # endpoints/svc-v1 172.16.1.10:8080,172.16.2.12:8080,172.16.3.11:8080 18s # endpoints/svc-v2 172.16.1.11:8080,172.16.2.11:8080,172.16.3.12:8080 18s # # NAME READY STATUS RESTARTS AGE # pod/dp-v1-8684d45558-22nbv 1/1 Running 0 18s # pod/dp-v1-8684d45558-59pnl 1/1 Running 0 18s # pod/dp-v1-8684d45558-87xrs 1/1 Running 0 18s # pod/dp-v2-7757c4bdc-5xmcm 1/1 Running 0 18s # pod/dp-v2-7757c4bdc-bm2gq 1/1 Running 0 18s # pod/dp-v2-7757c4bdc-h7fzl 1/1 Running 0 18s # 파드 버전 확인: 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-8684d45558-22nbv # 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-7757c4bdc-5xmcm # server_version=nginx: 1.13.1 - lua: 10008 # ...
-
canary-ingress1.yml
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
-
canary-ingress2.yml
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
-
카나리 업그레이드 확인
# 터미널1 $ watch -d 'kubectl get ingress,svc,ep' # 도메인 변경 # $ MYDOMAIN1=<각자 자신의 닉네임의 도메인> 예시) gasida.com $ MYDOMAIN1=sweetlittlebird.com $ sed -i "s/kans.com/$MYDOMAIN1/g" canary-ingress1.yml $ sed -i "s/kans.com/$MYDOMAIN1/g" canary-ingress2.yml # 생성 $ kubectl apply -f canary-ingress1.yml,canary-ingress2.yml # => ingress.networking.k8s.io/ingress-canary-v1 created # ingress.networking.k8s.io/ingress-canary-v2 created # 로그 모니터링 $ kubetail -n ingress -l app.kubernetes.io/component=controller # 접속 테스트 $ curl -s $MYDOMAIN1:30080 $ curl -s $MYDOMAIN1:30080 | grep nginx # => <hr><center>nginx</center> # 접속 시 v1 v2 버전별 비율이 어떻게 되나요? 왜 이렇게 되나요? $ for i in {1..100}; do curl -s $MYDOMAIN1:30080 | grep nginx ; done | sort | uniq -c | sort -nr # => 84 server_version=nginx: 1.13.0 - lua: 10008 # 16 server_version=nginx: 1.13.1 - lua: 10008 $ for i in {1..1000}; do curl -s $MYDOMAIN1:30080 | grep nginx ; done | sort | uniq -c | sort -nr # => 919 server_version=nginx: 1.13.0 - lua: 10008 # 81 server_version=nginx: 1.13.1 - lua: 10008 $ while true; do curl -s --connect-timeout 1 $MYDOMAIN1:30080 | grep Hostname ; echo "--------------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done # <span style="color: green;">👉 v2의 canary 비율을 10%로 두어서 그렇습니다.</span> # 비율 조정하여 절반을 v2로 전환하겠습니다. >> 개발 배포 버전 전략에 유용하다! $ 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 # => 53 server_version=nginx: 1.13.1 - lua: 10008 # 47 server_version=nginx: 1.13.0 - lua: 10008 $ for i in {1..1000}; do curl -s $MYDOMAIN1:30080 | grep nginx ; done | sort | uniq -c | sort -nr # => 526 server_version=nginx: 1.13.1 - lua: 10008 # 474 server_version=nginx: 1.13.0 - lua: 10008 # (옵션) 비율 조정 << 어떻게 비율이 조정될까요? $ 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 # => 100 server_version=nginx: 1.13.1 - lua: 10008 # <span style="color: green;">👉 카나리 비율을 100%로 하니 v2버전으로 100% 전환 되었습니다.</span> # (옵션) 비율 조정 << 어떻게 비율이 조정될까요? $ 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 # => 100 server_version=nginx: 1.13.0 - lua: 10008 # <span style="color: green;">👉 카나리 비율을 0%로 하니 v2버전으로 0% 로 전환 되고 모든 트래픽이 v1으로 전달되었습니다.</span>
-
- 오브젝트 삭제
$ kubectl delete deployments,svc,ingress --all
Gateway API
Gateway API 소개
앞서 Ingress를 살펴볼때 말씀드린것 처럼 Ingress는 Frozen 되어서 더이상 업데이트 되지 않고, Gateway API에 기능을 추가할 계획이라고 합니다. 이어서 Gateway API에 대해 알아보겠습니다.
Gateway API는 Kubernetes에서 API Gateway를 정의하고 구성하기 위한 API를 제공하는 프로젝트입니다. Gateway API 소개 Gateway API는 서비스 메시(예) istio 등)에서 제공하는 풍부한 기능 중 일부 기능들과 운영 관리에 필요한 기능들을 추가하였습니다. 추가된 기능의 예로는 헤더 기반 라우팅, 헤더 변조, 트래픽 미러링(쉽게 트래픽 복제), 역할 기반 접근 제어 등이 있습니다.
Gateway API는 이를 통해 동적 인프라 구성을 지원하고, 고급 트래픽 라우팅을 지원합니다.
- Gateway API의 주요 기능은 다음과 같습니다.
-
- 개선된 리소스 모델
- API는 GatewayClass, Gateway 및 Route(HTTPRoute, TCPRoute 등)와 같은 새로운 사용자 정의 리소스를 도입하여 라우팅 규칙을 정의하는 보다 세부적이고 표현력 있는 방법을 제공합니다.
-
- 프로토콜 독립적
- 주로 HTTP용으로 설계된 Ingress와 달리 Gateway API는 TCP, UDP, TLS를 포함한 여러 프로토콜을 지원합니다.
-
- 강화된 보안
- TLS 구성 및 보다 세부적인 액세스 제어에 대한 기본 제공 지원.
-
- 교차 네임스페이스 지원
- 서로 다른 네임스페이스의 서비스로 트래픽을 라우팅하여 보다 유연한 아키텍처를 구축할 수 있는 기능을 제공합니다.
-
- 확장성
- API는 사용자 정의 리소스 및 정책으로 쉽게 확장할 수 있도록 설계되었습니다.
-
- 역할 지향
- 클러스터 운영자, 애플리케이션 개발자, 보안 팀 간의 우려를 명확하게 분리합니다.
-
- 다음의 구성요소 (Resource)를 가집니다.
- GatewayClass, Gateway, HTTPRoute, TCPRoute, Service
- GatewayClass: 공통 구성을 가진 게이트웨이 세트를 정의하고 클래스를 구현하는 컨트롤러에 의해 관리됩니다.
- Gateway: 클라우드 로드 밸런서와 같은 트래픽 처리 인프라의 인스턴스를 정의합니다.
-
HTTPRoute: Gateway 리스너에서 백엔드 네트워크 엔드포인트의 표현으로 트래픽을 매핑하기 위한 HTTP 전용 규칙을 정의합니다. 이러한 엔드포인트는 종종 Service로 표현됩니다
출처 : https://gateway-api.sigs.k8s.io/
- GatewayClass, Gateway, HTTPRoute, TCPRoute, Service
- 리퀘스트 흐름
- role-oriented API 가 중요한 이유
- 담당 업무의 역할에 따라서 동작/권한을 유연하게 제공할 수 있습니다.
- 아래 그림 처럼 ‘스토어 개발자’는 Store 네임스페이스내에서 해당 store PATH 라우팅 관련 정책을 스스로 관리 할 수 있습니다.
- 역할의 예시입니다.
- 인프라 제공자: 여러 격리된 클러스터가 여러 테넌트를 서비스할 수 있도록 인프라를 관리합니다. 예: 클라우드 제공자.
- 클러스터 운영자: 클러스터를 관리하며 주로 정책, 네트워크 접근, 애플리케이션 권한 등을 관리합니다.
- 애플리케이션 개발자: 클러스터에서 실행되는 애플리케이션을 관리하며 주로 애플리케이션 수준의 구성 및 서비스 구성에 관심이 있습니다.
- 추천글
Gloo Gateway
Gloo Gateway는 Solo.io에서 개발한 API Gateway로, Gateway API를 구현한 대표적인 제품 중 하나입니다. Gloo Gateway는 다양한 환경에서 사용할 수 있도록 설계되어 있으며, 다음과 같은 특징을 가지고 있습니다.
- Envoy Proxy 기반 : Gloo Gateway는 고성능의 Envoy Proxy를 기반으로 하여 뛰어난 확장성과 성능을 제공합니다.
- API 관리 및 라우팅 : 다양한 API 라우팅 옵션을 제공하며, REST, gRPC, GraphQL 등의 다양한 프로토콜을 지원합니다. 이를 통해 복잡한 트래픽 관리와 라우팅이 가능합니다.
- 보안 기능 : 인증, 인가, TLS 암호화, OAuth, OpenID Connect 등 다양한 보안 기능을 제공합니다. 이를 통해 API를 안전하게 보호할 수 있습니다.
- 확장성 : 플러그인 아키텍처를 통해 쉽게 확장할 수 있으며, 다양한 서드파티 통합을 지원합니다. 필요에 따라 기능을 확장하거나 사용자 정의 기능을 추가할 수 있습니다.
- 서비스 디스커버리 : Kubernetes, Consul, EC2 등 다양한 서비스 디스커버리 메커니즘을 지원하여 동적 환경에서도 효율적으로 작동합니다.
- Observability : 트래픽 모니터링, 로깅, 트레이싱 등의 기능을 제공하여 운영 중인 시스템의 상태를 쉽게 파악하고 문제를 해결할 수 있습니다.
- 유연한 배포 : 클라우드, 온프레미스, 하이브리드 환경 등 다양한 배포 옵션을 지원합니다. 이를 통해 다양한 인프라 환경에 맞춰 유연하게 배포할 수 있습니다.
Gloo Gateway Architecture
- Envoy를 통해서 Http의 L7 라우팅을 지원하며, Gloo의 역할은 라우팅 규칙을 관리하고 Envoy에 전달하는 역할을 합니다.
Gloo Gateway는 내용이 방대하기 때문에 아래의 링크들로 설명을 대체하겠습니다.
- 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를 설치하고, Gateway API를 사용해보겠습니다. 실습은 [Tutorial] Hands-On with the Kubernetes Gateway API and Envoy Proxy를 참고하였습니다. kind를 통해서 실습할 수 있도록 잘 구성되었고 30분 정도면 따라할 수 있다고 합니다.
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
# => NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
# myk8s-control-plane Ready control-plane 32s v1.30.0 172.20.0.2 <none> Debian GNU/Linux 12 (bookworm) 5.10.76-linuxkit containerd://1.7.15
$ kubectl get pod -A
# => NAMESPACE NAME READY STATUS RESTARTS AGE
# kube-system coredns-7db6d8ff4d-45mzg 1/1 Running 0 38s
# kube-system coredns-7db6d8ff4d-gc4zp 1/1 Running 0 38s
# kube-system etcd-myk8s-control-plane 1/1 Running 0 52s
# kube-system kindnet-h4dwk 1/1 Running 0 38s
# kube-system kube-apiserver-myk8s-control-plane 1/1 Running 0 54s
# kube-system kube-controller-manager-myk8s-control-plane 1/1 Running 0 52s
# kube-system kube-proxy-sptf6 1/1 Running 0 38s
# kube-system kube-scheduler-myk8s-control-plane 1/1 Running 0 52s
# local-path-storage local-path-provisioner-988d74bc-gl679 1/1 Running 0 38s
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
# => NAME CREATED AT
# gatewayclasses.gateway.networking.k8s.io 2024-10-01T15:50:32Z
# gateways.gateway.networking.k8s.io 2024-10-01T15:50:32Z
# httproutes.gateway.networking.k8s.io 2024-10-01T15:50:32Z
# referencegrants.gateway.networking.k8s.io 2024-10-01T15:50:32Z
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
# => Server: version undefined, could not find any version of gloo running
#
# {
# "client": {
# "version": "1.17.7"
# },
# "kubernetesCluster": {
# "major": "1",
# "minor": "30",
# "gitVersion": "v1.30.0",
# "buildDate": "2024-05-13T22:02:25Z",
# "platform": "linux/arm64"
# }
# }
# <span style="color: green;">👉 서버가 설치되지 않았기 때문에 클라이언트 정보만 나옵니다.</span>
----------------------------------------
Install Gloo Gateway : 오픈소스 버전
rosetta 비활성화 방법
-
[macOS m시리즈] Docker Desktop : 아래 옵션 Uncheck 해둘 것 → Apply & restart
-
[macOS m시리즈] Orbstack : 터미널에서 아래 입력
# rosetta 비활성화 $ orb config set rosetta false # orb 설정 확인 $ orb config show # orbstack 재시작 $ orb stop $ orb start
# [신규 터미널] 모니터링
$ 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
# => "gloo" has been added to your repositories
$ 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
# => NAME: gloo-gateway
# LAST DEPLOYED: Sun Oct 13 00:57:27 2024
# NAMESPACE: gloo-system
# STATUS: deployed
# REVISION: 1
# TEST SUITE: None
# Confirm that the Gloo control plane has successfully been deployed using this command
$ kubectl rollout status deployment/gloo -n gloo-system
# => deployment "gloo" successfully rolled out
# 설치 확인
$ kubectl get crd | grep 'networking.k8s.io'
# => gatewayclasses.gateway.networking.k8s.io 2024-10-01T15:50:32Z
# gateways.gateway.networking.k8s.io 2024-10-01T15:50:32Z
# httproutes.gateway.networking.k8s.io 2024-10-01T15:50:32Z
# referencegrants.gateway.networking.k8s.io 2024-10-01T15:50:32Z
$ kubectl get crd | grep -v 'networking.k8s.io'
$ kubectl get pod,svc,endpointslices -n gloo-system
# => NAME READY STATUS RESTARTS AGE
# pod/gateway-proxy-57c49d4f48-xm8vv 1/1 Running 0 87s
# pod/gloo-748d877c4-24ngk 1/1 Running 0 87s
# pod/gloo-resource-rollout-5bt7d 0/1 Completed 0 87s
# pod/gloo-resource-rollout-check-xwxd4 0/1 Completed 0 86s
#
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# service/gateway-proxy LoadBalancer 10.96.69.126 <pending> 80:30172/TCP,443:32484/TCP 87s
# service/gloo ClusterIP 10.96.100.145 <none> 9977/TCP,9976/TCP,9988/TCP,9966/TCP,9979/TCP,443/TCP 87s
#
# NAME ADDRESSTYPE PORTS ENDPOINTS AGE
# endpointslice.discovery.k8s.io/gateway-proxy-n7f7v IPv4 8080,8443 10.244.0.7 87s
# endpointslice.discovery.k8s.io/gloo-9bf7g IPv4 9979,9988,9966 + 3 more... 10.244.0.8 87s
#
$ kubectl explain gatewayclasses
$ kubectl get gatewayclasses
# => NAME CONTROLLER ACCEPTED AGE
# gloo-gateway solo.io/gloo-gateway True 2m18s
$ 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
#
$ 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
# => namespace/httpbin created
# serviceaccount/httpbin created
# service/httpbin created
# deployment.apps/httpbin created
# 설치 확인
$ kubectl get deploy,pod,svc,endpointslices,sa -n httpbin
# => NAME READY UP-TO-DATE AVAILABLE AGE
# deployment.apps/httpbin 0/1 1 0 10s
#
# NAME READY STATUS RESTARTS AGE
# pod/httpbin-5855dc8bdd-xh2vf 0/1 ContainerCreating 0 10s
#
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# service/httpbin ClusterIP 10.96.169.139 <none> 8000/TCP 10s
#
# NAME ADDRESSTYPE PORTS ENDPOINTS AGE
# endpointslice.discovery.k8s.io/httpbin-6zhsk IPv4 <unset> <unset> 10s
#
# NAME SECRETS AGE
# serviceaccount/default 0 10s
# serviceaccount/httpbin 0 10s
$ kubectl rollout status deploy/httpbin -n httpbin
# => deployment "httpbin" successfully rolled out
# (옵션) 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
# => service/httpbin configured
# (옵션) 로컬 접속 확인
$ echo "httpbin web - http://localhost:30000" # macOS 사용자
$ echo "httpbin web - http://192.168.50.10:30000" # Windows 사용자
httpbin 설치결과
Gateway API 종류 - Docs
- 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. - 예) 개발자가 관리
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.
Configure a Gateway Listener
- Let’s begin by establishing a Gateway resource that sets up an HTTP listener on port 8080 to expose routes from all our namespaces. Gateway custom resources like this are part of the Gateway API standard.
# 02-gateway.yaml
$ cat <<EOF > 02-gateway.yaml
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: http
namespace: gloo-system
spec:
gatewayClassName: gloo-gateway
listeners:
- protocol: HTTP
port: 8080
name: http
allowedRoutes:
namespaces:
from: All
EOF
# gateway 리소스 생성
$ kubectl apply -f 02-gateway.yaml
# => gateway.gateway.networking.k8s.io/http created
# 확인 : Now we can confirm that the Gateway has been activated
$ kubectl get gateway -n gloo-system
# => NAME CLASS ADDRESS PROGRAMMED AGE
# http gloo-gateway True 8s
$ kubectl get gateway -n gloo-system -o yaml
# => 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 66s
# envoy 사용 확인
$ kubectl get pod -n gloo-system
# => NAME READY STATUS RESTARTS AGE
# gateway-proxy-57c49d4f48-xm8vv 1/1 Running 0 13m
# gloo-748d877c4-24ngk 1/1 Running 0 13m
# gloo-proxy-http-587765f6b6-mpnt5 1/1 Running 0 78s
$ kubectl describe pod -n gloo-system |grep Image:
# => Image: quay.io/solo-io/gloo-envoy-wrapper:1.17.7 # <span style="color: green;">👉 이름에서 알 수 있듯이 envoy가 들어있고 감싸고 있는것으로 보입니다.</span>
# 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.104.226 <pending> 8080:31461/TCP 101s
# 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
# => service/gloo-proxy-http configured
$ kubectl get svc -n gloo-system gloo-proxy-http
# => NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# gloo-proxy-http LoadBalancer 10.96.104.226 <pending> 8080:30001/TCP 2m17s
# <span style="color: green;">👉 노드포트가 30001로 변경되었습니다.</span>
Establish External Access to Proxy
# 간편한 테스트를 위해 port-forward를 사용하여 외부로 노출하겠습니다.
$ kubectl port-forward deployment/gloo-proxy-http -n gloo-system 8080:8080 &
Configure Simple Routing with an HTTPRoute
Let’s begin our routing configuration with the simplest possible route to expose the /get operation on httpbin
HTTPRoute
is one of the new Kubernetes CRDs introduced by the Gateway API, as documented here. We’ll start by introducing a simple HTTPRoute
for our service.
HTTPRoute Spec
- ParentRefs-Define which Gateways this Route wants to be attached to.
- Hostnames (optional)- Define a list of hostnames to use for matching the Host header of HTTP requests.
-
Rules-Define a list of rules to perform actions against matching HTTP requests.
- Each rule consists of matches, filters (optional), backendRefs (optional) and timeouts (optional) fields.
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
This example attaches to the default Gateway
object created for us when we installed Gloo Gateway earlier.
See the gloo-system/http
reference in the parentRefs
stanza.
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
# => httproute.gateway.networking.k8s.io/httpbin created
#
$ kubectl get httproute -n httpbin
# => NAME HOSTNAMES AGE
# httpbin ["api.example.com"] 12s
$ 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
# <span style="color: red;">server: envoy</span> # <span style="color: green;">👉 서버가 envoy임을 확인할 수 있습니다.</span>
# date: Sat, 1 Oct 2024 16:19:18 GMT
# content-type: application/json
# content-length: 239
# access-control-allow-origin: *
# access-control-allow-credentials: true
# x-envoy-upstream-service-time: 13
#
# {
# "args": {},
# "headers": {
# "Accept": "*/*",
# "Host": "api.example.com",
# "User-Agent": "curl/8.1.2",
# "X-Envoy-Expected-Rq-Timeout-Ms": "15000"
# },
# "origin": "10.244.0.12",
# "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
# => Handling connection for 8080
# HTTP/1.1 404 Not Found
# date: Sat, 1 Oct 2024 16:20:23 GMT
# server: envoy
# content-length: 0
# <span style="color: green;">👉 gloo를 통했을때는 HTTProute 설정에서 /get 이라는 경로에대해서 정확하게 일치(Exact) 할 경우에만 라우팅하도록 해서 그렇습니다.</span>
# nodeport 직접 접속 테스트
$ echo "httproute - http://api.example.com:30000/delay/1" # 1초 후 응답
$ echo "httproute - http://api.example.com:30000/delay/5" # 5초 후 응답
# <span style="color: green;">👉 노드포트로 직접 접속할 경우 gloo HTTPRoute를 거치지 않기 때문에 접속이 가능합니다.</span>
kind 클러스터를 구성할때 30000 포트를 열었기 때문에 NodePort로 직접 접속이 가능합니다.
http://api.example.com:30000/delay/1 호출 결과 => 1초후 응답
[정규식 패턴 매칭] Explore Routing with Regex Matching Patterns
Let’s assume that now we DO want to expose other httpbin
endpoints like /delay
. Our initial HTTPRoute
is inadequate, because it is looking for an exact path match with /get
.
We’ll modify it in a couple of ways. First, we’ll modify the matcher to look for path prefix matches instead of an exact match. Second, we’ll add a new request filter to rewrite the matched /api/httpbin/
prefix with just a /
prefix, which will give us the flexibility to access any endpoint available on the httpbin
service. So a path like /api/httpbin/delay/1
will be sent to httpbin
with the path /delay/1
.
URL에 패턴이 매치가 되면 rewrite해서 실제 접속되는 경로를 변경할 수 있습니다.
- 예시)
/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
# => httproute.gateway.networking.k8s.io/httpbin configured
# 확인
$ 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
When we used only a single route with an exact match pattern, we could only exercise the httpbin /get
endpoint. Let’s now use curl
to confirm that both /get
and /delay
work as expected.
#
$ 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: Sat, 1 Oct 2024 16:33:20 GMT
# content-type: application/json
# content-length: 289
# access-control-allow-origin: *
# access-control-allow-credentials: true
# x-envoy-upstream-service-time: 20
#
# {
# "args": {},
# "headers": {
# "Accept": "*/*",
# "Host": "api.example.com",
# "User-Agent": "curl/8.1.2",
# "X-Envoy-Expected-Rq-Timeout-Ms": "15000",
# "X-Envoy-Original-Path": "/api/httpbin/get"
# },
# "origin": "10.244.0.12",
# "url": "http://api.example.com/get"
# }
# 아래 NodePort 와 GW API 통한 접속 비교
$ echo "httproute - http://api.example.com:30001/api/httpbin/get"
# => HTTP/1.1 200 OK
# ...
# "url": "<span style="color: red;">http://api.example.com/get</span>"
# }
# <span style="color: green;">👉 gloo gateway가 /app/httpbin/get => / 로 변경하여 잘 접속이 되었습니다.</span>
$ echo "httproute - http://api.example.com:30000/api/httpbin/get" # NodePort 직접 접근
# => HTTP/1.1 404 NOT FOUND
# ...
# <span style="color: green;">👉 NodePort에 직접 접근시에는 /app/httpbin/get이 그대로 파드에 전달되어 없는 경로라서 404 에러가 납니다.</span>
---
#
$ 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: Sat, 1 Oct 2024 16:36:49 GMT
# content-type: application/json
# content-length: 343
# access-control-allow-origin: *
# access-control-allow-credentials: true
# x-envoy-upstream-service-time: 1049 # envoy 가 업스트림 httpbin 요청 처리에 걸리 시간 1초 이상
#
# {
# "args": {},
# "data": "",
# "files": {},
# "form": {},
# "headers": {
# "Accept": "*/*",
# "Host": "api.example.com",
# "User-Agent": "curl/8.1.2",
# "X-Envoy-Expected-Rq-Timeout-Ms": "15000",
# "X-Envoy-Original-Path": "/api/httpbin/delay/1"
# },
# "origin": "10.244.0.12",
# "url": "http://api.example.com/delay/1"
# }
$ curl -is -H "Host: api.example.com" http://localhost:8080/api/httpbin/delay/2
# => ...
# x-envoy-upstream-service-time: 2133
# ...
Perfect! It works just as expected! Note that the /delay
operation completed successfully and that the 1-second delay was applied. The response header x-envoy-upstream-service-time: 1023
indicates that Envoy reported that the upstream httpbin
service required just over 1 second (1,023 milliseconds) to process the request. In the initial /get
operation, which doesn’t inject an artificial delay, observe that the same header reported only 14 milliseconds of upstream processing time.
[업스트림 베어러 토큰을 사용한 변환] Test Transformations with Upstream Bearer Tokens
목적 : 요청을 라우팅하는 백엔드 시스템 중 하나에서 인증해야 하는 요구 사항이 있는 경우는 어떻게 할까요? 이 업스트림 시스템에는 권한 부여를 위한 API 키가 필요하고, 이를 소비하는 클라이언트에 직접 노출하고 싶지 않다고 가정해 보겠습니다. 즉, 프록시 계층에서 요청에 주입할 간단한 베어러 토큰을 구성하고 싶습니다. (정적 API 키 토큰을 직접 주입)
What if we have a requirement to authenticate with one of the backend systems to which we route our requests?
Let’s assume that this upstream system requires an API key for authorization, and that we don’t want to expose this directly to the consuming client. In other words, we’d like to configure a simple bearer token to be injected into the request at the proxy layer.
We can express this in the Gateway API by adding a filter that applies a simple transformation to the incoming request.
This will be applied along with the URLRewrite filter we created in the previous step.
# 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: Sat, 1 Oct 2024 16:40:59 GMT
# content-type: application/json
# content-length: 332
# access-control-allow-origin: *
# access-control-allow-credentials: true
# x-envoy-upstream-service-time: 19
#
# {
# "args": {},
# "headers": {
# "Accept": "*/*",
# <span style="color: red">"Authorization": "Bearer my-api-key",</span>
# "Host": "api.example.com",
# "User-Agent": "curl/8.1.2",
# "X-Envoy-Expected-Rq-Timeout-Ms": "15000",
# "X-Envoy-Original-Path": "/api/httpbin/get"
# },
# "origin": "10.244.0.12",
# "url": "http://api.example.com/get"
# }
# <span style="color: green;">👉 클라이언트에서는 Authorization 헤더를 안 주었지만, Gloo gateway를 통하자</span>
# <span style="color: green;"> Authorization 헤더에 Bearer my-api-key 가 추가되어 있습니다.</span>
Migrate
In this section, we’ll explore how a couple of common service migration techniques, dark launches with header-based routing and canary releases with percentage-based routing, are supported by the Gateway API standard.
Configure Two Workloads for Migration Routing
Let’s first establish two versions of a workload to facilitate our migration example. We’ll use the open-source Fake Service to enable this.
- Fake service that can handle both HTTP and gRPC traffic, for testing upstream service communications and testing service mesh and other scenarios.
Let’s establish a v1
of our my-workload
service that’s configured to return a response string containing “v1”. We’ll create a corresponding my-workload-v2
service as well.
- ingress의 카나리 배포와 유사하게 V1의 일부 트래픽을 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
# => namespace/my-workload created
# serviceaccount/my-workload created
# deployment.apps/my-workload-v1 created
# deployment.apps/my-workload-v2 created
# service/my-workload-v1 created
# service/my-workload-v2 created
# 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 15s
# deployment.apps/my-workload-v2 1/1 1 1 15s
#
# NAME READY STATUS RESTARTS AGE
# pod/my-workload-v1-644f98bbd9-q6cs5 1/1 Running 0 15s
# pod/my-workload-v2-5bb5fcfcbc-bq88c 1/1 Running 0 15s
#
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# service/my-workload-v1 ClusterIP 10.96.203.193 <none> 8080/TCP 15s
# service/my-workload-v2 ClusterIP 10.96.210.160 <none> 8080/TCP 15s
#
# NAME ADDRESSTYPE PORTS ENDPOINTS AGE
# endpointslice.discovery.k8s.io/my-workload-v1-d9sqd IPv4 8080 10.244.0.14 15s
# endpointslice.discovery.k8s.io/my-workload-v2-mv7fq IPv4 8080 10.244.0.13 15s
Test Simple V1 Routing
Before we dive into routing to multiple services, we’ll start by building a simple HTTPRoute
that sends HTTP requests to host api.example.com
whose paths begin with /api/my-workload
to the v1
workload:
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
# => httproute.gateway.networking.k8s.io/my-workload created
#
$ kubectl get httproute -A
# => NAMESPACE NAME HOSTNAMES AGE
# httpbin httpbin ["api.example.com"] 29m
# my-workload my-workload ["api.example.com"] 29s
#
$ 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
#
$ for i in {1..100}; do curl -s http://api.example.com:8080/api/my-workload | grep Workload; done | sort | uniq -c | sort -nr
# => 100 "body": "Hello From My Workload (v1)!",
# <span style="color: green;">👉 현재는 모든 연결이 v1으로 향합니다.</span>
Simulate a v2 Dark Launch with Header-Based Routing
Dark Launch is a great cloud migration technique that releases new features to a select subset of users to gather feedback and experiment with improvements before potentially disrupting a larger user community.
- Dark Launch : 일부 사용자에게 새로운 기능을 출시하여 피드백을 수집하고 잠재적으로 더 큰 사용자 커뮤니티를 방해하기 전에 개선 사항을 실험하는 훌륭한 클라우드 마이그레이션 기술
We will simulate a dark launch in our example by installing the new cloud version of our service in our Kubernetes cluster, and then using declarative policy to route only requests containing a particular header to the new v2
instance. The vast majority of users will continue to use the original v1
of the service just as before.
- 우리는 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
# => httproute.gateway.networking.k8s.io/my-workload configured
#
$ 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
# version: v2 헤더가 없는 경우 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)!",
$ for i in {1..100}; do curl -s http://api.example.com:8080/api/my-workload | grep Workload; done | sort | uniq -c | sort -nr
# => 100 "body": "Hello From My Workload (v1)!",
# 하지만 version: v2 헤더가 있는 경우 v2로 라우팅됩니다.
$ 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
# => "body": "Hello From My Workload (v2)!",
$ for i in {1..100}; do curl -s http://api.example.com:8080/api/my-workload | grep Workload; done | sort | uniq -c | sort -nr
# => 100 "body": "Hello From My Workload (v1)!",
Expand V2 Testing with Percentage-Based Routing
After a successful dark-launch, we may want a period where we use a blue-green strategy of gradually shifting user traffic from the old version to the new one. Let’s explore this with a routing policy that splits our traffic evenly, sending half our traffic to v1
and the other half to v2
.
- 성공적인 다크 런칭 이후, 우리는 점진적으로 이전 버전에서 새 버전으로 사용자 트래픽을 옮기는 블루-그린 전략을 사용하는 기간을 원할 수 있습니다. 트래픽을 균등하게 분할하고 트래픽의 절반을 로 보내고
v1
나머지 절반을 로 보내는 라우팅 정책으로 이를 살펴보겠습니다v2
.
We will modify our HTTPRoute
to accomplish this by removing the header-based routing rule that drove our dark launch. Then we will replace that with a 50-50 weight
applied to each of the routes, as shown below:
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
# => httproute.gateway.networking.k8s.io/my-workload configured
#
$ kubectl describe httproute -n my-workload
# => Spec:
# ...
# Rules:
# Backend Refs:
# Group:
# Kind: Service
# Name: my-workload-v1
# Namespace: my-workload
# Port: 8080
# Weight: 50
# Group:
# Kind: Service
# Name: my-workload-v2
# Namespace: my-workload
# Port: 8080
# Weight: 50
# Matches:
# Path:
# Type: PathPrefix
# Value: /api/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
# => 51 "body": "Hello From My Workload (v1)!",
# 49 "body": "Hello From My Workload (v2)!",
$ 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
# => 116 "body": "Hello From My Workload (v1)!",
# 84 "body": "Hello From My Workload (v2)!",
Debug
Solve a Problem with Glooctl CLI
A common source of Gloo configuration errors is mistyping an upstream reference, perhaps when copy/pasting it from another source but “missing a spot” when changing the name of the backend service target. In this example, we’ll simulate making an error like that, and then demonstrating how glooctl
can be used to detect it.
- Gloo 구성 오류의 일반적인 원인은 업스트림 참조를 잘못 입력하는 것입니다. 아마도 다른 소스에서 복사/붙여넣을 때이지만 백엔드 서비스 대상의 이름을 변경할 때 “한 군데를 놓친” 것입니다. 이 예에서 우리는 그런 오류를 만드는 것을 시뮬레이션하고,
glooctl
그것을 감지하는 데 어떻게 사용할 수 있는지 보여줍니다.
First, let’s apply a change to simulate the mistyping of an upstream config so that it is targeting a non-existent my-bad-workload-v2
backend service, rather than the correct my-workload-v2
.
-
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
# => httproute.gateway.networking.k8s.io/my-workload configured
#
$ kubectl describe httproute -n my-workload
# => ...
# Spec:
# 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-12T16:55:06Z
# Message: Service "my-bad-workload-v2" not found
# Observed Generation: 4
# Reason: BackendNotFound
# Status: False
# Type: ResolvedRefs
# Last Transition Time: 2024-10-12T16:45:41Z
# 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
# Events: <none>
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
# => HTTP/1.1 500 Internal Server Error
$ curl -is -H "Host: api.example.com" http://localhost:8080/api/my-workload
# => HTTP/1.1 200 OK
# vary: Origin
# date: Sat, 12 Oct 2024 16:56:37 GMT
# content-length: 292
# content-type: text/plain; charset=utf-8
# x-envoy-upstream-service-time: 5
# server: envoy
# ...
#
$ 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
# => 55 "body": "Hello From My Workload (v1)!",
# <span style="color: green;">👉 디버깅 테스트를 위해 일부러 50%의 워크로드에는 오타를 내어서</span>
# <span style="color: green;"> 50%의 요청은 v1로 라우팅되어 성공하고 나머지 50%는 실패합니다.</span>
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:
- gloo에서 제공하는
glooctl check
명령으로 구성 오류를 확인합니다.
#
$ 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: "2024-10-12T16:55:06Z"
# message: Service "my-bad-workload-v2" not found
# observedGeneration: 4
# 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
# => httproute.gateway.networking.k8s.io/my-workload configured
$ kubectl get httproute my-workload -n my-workload -o yaml
#
$ glooctl check
# => Checking Deployments... OK
# Checking Pods... OK
# Checking Upstreams... OK
# Checking UpstreamGroups... OK
# Checking AuthConfigs... OK
# Checking RateLimitConfigs... OK
# Checking VirtualHostOptions... OK
# Checking RouteOptions... OK
# Checking Secrets... OK
# Checking VirtualServices... OK
# Checking Gateways... OK
# Checking Proxies... OK
#
# Detected Kubernetes Gateway integration!
# Checking Kubernetes GatewayClasses... OK
# Checking Kubernetes Gateways... OK
# Checking Kubernetes HTTPRoutes... OK
#
# Skipping Gloo Instance check -- Gloo Federation not detected.
# No problems detected.
# <span style="color: green;">👉 이제 문제가 없다고 합니다. 😀</span>
Observe
Explore Envoy Metrics
Envoy publishes a host of metrics that may be useful for observing system behavior. In our very modest kind cluster for this exercise, you can count over 3,000 individual metrics! You can learn more about them in the Envoy documentation here.
For this 30-minute exercise, let’s take a quick look at a couple of the useful metrics that Envoy produces for every one of our backend targets.
First, we’ll port-forward the Envoy administrative port 19000 to our local workstation:
#
$ 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"tZBli7jsXv'XALZnaKnB2MSBvJNI
For this exercise, let’s view two of the relevant metrics from the first part of this exercise: one that counts the number of successful (HTTP 2xx) requests processed by our httpbin
backend (or cluster
, in Envoy terminology), and another that counts the number of requests returning server errors (HTTP 5xx) from that same backend:
# 2xx, 5xx 요청 확인
$ curl -s http://localhost:19000/stats | grep -E "(^cluster.kube-svc_httpbin-httpbin-8000_httpbin.upstream.*(2xx|5xx))"
# => cluster.kube-svc_httpbin-httpbin-8000_httpbin.upstream_rq_2xx: 7
# If we apply a curl request that forces a 500 failure from the httpbin backend, using the /status/500 endpoint, I’d expect the number of 2xx requests to remain the same, and the number of 5xx requests to increment by one:
$ curl -is -H "Host: api.example.com" http://localhost:8080/api/httpbin/status/500
# => HTTP/1.1 500 Internal Server Error
# server: envoy
# date: Sat, 12 Oct 2024 17:02:53 GMT
# content-type: text/html; charset=utf-8
# access-control-allow-origin: *
# access-control-allow-credentials: true
# content-length: 0
# x-envoy-upstream-service-time: 38
# 500에러를 발생시키자 500에러가 1개 증가하고 2xx는 변화가 없습니다.
$ curl -s http://localhost:19000/stats | grep -E "(^cluster.kube-svc_httpbin-httpbin-8000_httpbin.upstream.*(2xx|5xx))"
# => cluster.kube-svc_httpbin-httpbin-8000_httpbin.upstream_rq_2xx: 7
# cluster.kube-svc_httpbin-httpbin-8000_httpbin.upstream_rq_5xx: 1
정리
$ kind delete cluster --name myk8s
# => Deleted nodes: ["myk8s-control-plane"]
기타 Gateway API 구현체
-
Cilium
- Cilium은 CNI로 알려져있지만 Gateway API 역할도 지원합니다.
-
Istio
- Istio는 Service Mesh로 알려져있지만 Gateway API 역할도 지원합니다. Gateway API 자체가 Service Mesh인 Istio 등을 참조하였기에 어찌보면 당연한 일입니다.
- Kubernetes Traffic Management: Combining Gateway API with Service Mesh for North-South and East-West Use Cases - Blog
- Istio Gateway API 활용하기 https://devops-james.tistory.com/317
-
Kong API Gateway
- Kong API Gateway 를 Gateway API 형태 설치 https://mokpolar.tistory.com/68
-
Envoy Gateway
- Envoy Gateway 사용하여 + 부하분산 https://devops-james.tistory.com/320
마치며
파드 통신에서 부터 CNI, 서비스(ClusterIP, NodePort, LoadBalancer)를 거쳐, ingress, gateway api까지 왔습니다. 나중에 배운 기술이 이전 기술을 필요없게 만드는 부분도 있지만, 기초의 중요성을 알기에 더욱 중요하다고 생각합니다.
그런데 gateway api를 만들면서 ingress를 frozen 하게 된것은 살짝 충격적입니다. ingress를 없앤다는 얘기는 없지만 결국 gateway api가 더 좋은 기술이고, ingress는 점점 점유율을 잃다가 조용히 deprecated 될것 같은 느낌입니다. ingress가 심심하지 않도록 더 자주 써줘야겠습니다.
이번주는 특히나 실습이 많았던것 같은데, 다른 분들도 다들 잘 생존했으면 좋겠습니다.
(일단 저부터 스터디에서 생존하기를 빕니다.. )