안녕하세요?
AEWS 스터디의 2주차 과제로,
Traefik API Gateway 사용시 AWS EKS의 NLB에서 Backend까지의 Flow 라는 주제에 대해 작성해보겠습니다.
Traefik 은 Kubernetes 상에서 사용할 수 있는 API Gateway 제품입니다.
Traefik의 설치
EKS에 Traefik을 설치하는 방법은 여러가지가 있습니다만
공식 문서의 내용을 참고해주시기 바랍니다.
Traefik Installation Documentation - Traefik
Helm으로는 이렇게 설치할 수 있습니다.
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik
로드밸런서를 NLB, IP mode로 설치하기 위해서 helm 파일에서 아래 항목을 바꿔주시면 되겠습니다.
또한 Ingress가 아니라 Kubernetes GatewayAPI 를 쓰기 위해 옵션을 바꿔줍니다.
service:
enabled: true
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: external
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
service.beta.kubernetes.io/aws-load-balancer-manage-backend-security-group-rules: "true"
...
kubernetesGateway:
enabled: true
...
그리고 Kubernetes GatewayAPI 형태로 배포되어 있기 때문에 httproute를 배포해서 경로를 지정해줍니다.
테스트의 대상이 되는 뒷단의 앱은 9000번 포트로 네트워크 트래픽을 받습니다.
kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1beta1
metadata:
name: http-routes
spec:
parentRefs:
- name: traefik-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /url
backendRefs:
- kind: Service
name: target
port: 9000
AWS NLB의 Instance모드와 IP 모드에 대해
AWS에서 LoadBalancer는 CLB, NLB, ALB 타입을 지원합니다.
그 중에서 NLB를 생성할 때, Instance type, IP type 2가지 중 하나를 지정할 수 있습니다.
Instance type일 경우 로드밸런서에서 Node -> Pod로의 network flow는 아래와 같이 일어납니다.
반면 IP type일 경우 는 아래와 같습니다.
LoadBalancer Controller Pod에서 대상 Pod 들의 IP를 지속적으로 제공받고 있기 때문에,
또한 AWS CNI는 Node와 Pod의 대역이 같기 때문에 아래와 같은 통신이 가능합니다.
NLB에서 바로 Pod의 IP로 통신하는 것이죠. 그래서 각 Node의 iptable을 거쳐서 다른 곳으로 가도 되지 않아서 통신이 더 빠르다는 장점이 있습니다.
그리고 NLB로부터 Target Group을 지나 Pod로 이르는 동작을 좀 더 자세히 살펴보면 아래와 같이 이루어집니다.
NLB -> Traefik
Traefik은 이미 설치되어 있습니다.
먼저 Traefik에 대한 LoadBalancer는 NLB 타입으로 아래와 같은 형태로 NLB 타입으로 배포되어 있는 모습을 볼 수 있습니다.
배포된 LoadBalancer의 정보를 한번 살펴볼까요?
kubectl describe svc
...
Name: traefik
Annotations: service.beta.kubernetes.io/aws-load-balancer-manage-backend-security-group-rules: true
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-type: external
Type: LoadBalancer
Port: traefik 8080/TCP
TargetPort: traefik/TCP
NodePort: traefik XXXXX/TCP
Endpoints: 100.XXX.XXX.XXX:8080,100.XXX.XXX.XXX:8080
Port: web 80/TCP
TargetPort: web/TCP
NodePort: web XXXXX/TCP
Endpoints: 100.XXX.XXX.XXX:8000,100.XXX.XXX.XXX:8000
External Traffic Policy: Cluster
Internal Traffic Policy: Cluster
Target Endpoint로 특정 pod들의 ip를 갖고있는 모습을 볼 수 있습니다.
실제 콘솔에서 해당 로드밸런서의 타겟그룹을 보면
Target type이 IP이고 대상이 되는 Pod의 ip와 port가 대상으로 되어있는 모습을 확인할 수 있습니다.
그러니까 NLB에서 IP모드로 되어 있으니
Target group으로 연결된 Traefik API Gateway pod로 직접 통신을 할 것이라고 생각할 수 있습니다.
Traefik -> Backend Pod
Traefik을 배포하고나면 insecure한 설정에서 Dashboard를 확인할 수 있습니다.
그 모습은 아래와 같습니다.
Entrypoint라고 표현되어 있는 부분은 Traefik pod로 인입되는 port를 뜻합니다.
아마 이 Traefik이 배포되어 있는 노드에는 위와 같은 포트설정을 가지고 있는 iptable이 세팅되어 있을 것 같습니다.
위로 들어온 트래픽이 어디를 향하고 어떤 미들웨어를 거치는지는 아래 화면으로 알 수 있습니다.
현재는 9000 포트를 열고 있는 뒷단의 backend pod에 연결되어 있습니다.
Kube-proxy가 iptables mode 인 상태에서 Kubernetes Service로 연결되는 과정
위의 경우 Treafik에서는 뒤에 있는 App을 향하는 Kubernetes Service를 향하는 과정은 아래와 같은 도식으로 설명할 수 있습니다.
API Server를 통해서 kube-proxy에 service에 대한 정보가 들어갑니다.
그러면 kube-proxy는 해당 노드와 호스트 네트워크를 공유하며, iptables를 수정하여 이에 대한 내용을 반영하게 됩니다.
iptables 모드에서는 kube-proxy가 iptables api를 통해 packet forwarding rules을 수정하고,
실제 ip를 발견하고 전달하는 일은 kernel의 netfilter가 수행하게 됩니다.
통신 확인
반복적으로 확인을 위한 호출을 보내보겠습니다.
curl -X POST -H "Content-Type: application/json" \
-d "{\"message\": \"timestamp: $(date +%Y-%m-%dT%H:%M:%S), random: $(openssl rand -hex 8)\"}" \
"http://{URL}/{app}"
traefik pod의 로그를 보면 해당 데이터가 뒷단의 pod로 잘 전송되었다는 사실을 알 수 있습니다.
127.0.0.1 - - [15/Feb/2025:15:37:44 +0000] "POST /XXXX HTTP/1.1" 200 0 "-" "-" 137 "xxxx@kubernetesgateway" "http://100.XXX.XXX.XXX:9000" 1ms
127.0.0.1 - - [15/Feb/2025:15:37:44 +0000] "POST /XXXX HTTP/1.1" 200 0 "-" "-" 138 "xxxx@kubernetesgateway" "http://100.XXX.XXX.XXX:9000" 0ms
127.0.0.1 - - [15/Feb/2025:15:37:58 +0000] "POST /XXXX HTTP/1.1" 200 0 "-" "-" 139 "xxxx@kubernetesgateway" "http://100.XXX.XXX.XXX:9000" 1ms
traefik pod가 떠 있는 노드와 같은 곳의 netshoot pod에서
conntrack으로 조회해보겠습니다.
conntrack -L | grep 9000
tcp 6 86397 ESTABLISHED src=100.XXX.XXX.XXX dst=100.XXX.XXX.XXX sport=58950 dport=9000 src=100.XXX.XXX.XXX dst=100.XXX.XXX.XXX sport=9000 dport=58950 [ASSURED] mark=0 secctx=system_u:object_r:local_t:s0 use=1
conntrack v1.4.8 (conntrack-tools): 761 flow entries have been shown.
iptables도 한번 뜯어보겠습니다.
iptables-legacy -L -t nat -v --line-numbers | grep {url}
...
2 0 0 DNAT tcp -- any any anywhere anywhere /* URL/URL:http-server */ tcp to:100.XXX.XXX.XXX:9000
...
5 0 0 KUBE-SVC-4RGZ5FUYG3LXHSBE tcp -- any any anywhere ip-XXX-XXX-XXX-XXX.ec2.internal /* URL/URL:api cluster IP */ tcp dpt:port
...
1 0 0 KUBE-SEP-2J5B6GHPSAALLPBF all -- any any anywhere anywhere /* URL/URL:http-server -> 100.XXX.XXX.XXX:9000 */ statistic mode random probability 0.50000000000
2 0 0 KUBE-SEP-ESOJKTAYIUDSXL2G all -- any any anywhere anywhere /* URL/URL:http-server -> 100.XXX.XXX.XXX:9000 */
kube-proxy를 통해 업데이트된 iptable의 룰은 이런 방식으로 조회할 수 있습니다.
KUBE-SVC-* 체인은 ClusterIP를 통한 서비스 라우팅을 담당합니다. 저 서비스의 cluster ip로 요청이 올 경우 적절한 백엔드 pod로 라우팅이 될 것입니다.
KUBE-SEP-* 체인을 통해 실제 Pod으로 트래픽 분배가 됩니다. app pod가 두개이기 때문에 50%로 로드밸런싱이 되는 모습을 볼 수 있습니다.
그리고 호출을 하는 동안
뒷단의 app pod에 접근해서, tcpdump로 9000번 포트로 들어온 데이터를 확인해보겠습니다.
tcpdump -i eth0 port 9000 -nn -vv
15:56:34.075383 IP (tos 0x0, ttl 127, id 62451, offset 0, flags [DF], proto TCP (6), length 52)
100.XXX.XXX.XXX.9000 > 100.XXX.XXX.XXX.58950: Flags [.], cksum 0x16f4 (incorrect -> 0x236a), seq 76, ack 404, win 487, options [nop,nop,TS val ...
100.XXX.XXX.XXX.9000 > 100.XXX.XXX.XXX.58950: Flags [F.], cksum 0x16f4 (incorrect -> 0x4f2f), seq 76, ack 405, win 487, options [nop,nop,TS val 1743979965 ecr 2346063772], length 0
15:57:03.937800 IP (tos 0x0, ttl 125, id 15751, offset 0, flags [DF], proto TCP (6), length 52)
100.XXX.XXX.XXX.58950 > 100.XXX.XXX.XXX.9000: Flags [.], cksum 0x4f2a (correct), seq 405, ack 77, win 491, options [nop,nop,TS val 2346063773 ecr 1743979965], length 0
통신이 잘 이루어지고 있는 것을 확인할 수 있습니다.
'Kubernetes' 카테고리의 다른 글
Datadog Agent로 Traefik pod의 로그를 Datadog으로 전송하기 (0) | 2025.02.26 |
---|---|
Mountpoint for Amazon S3로 EKS Pod에 S3 mount하기 (0) | 2025.02.18 |
ECR Pull Through Cache를 써서 Private ECR에 Container Image 넣기 (4) | 2025.02.06 |
AWS VPC CNI 공부하기 (1) | 2024.11.02 |
Cilium CNI 공부하기 (3) | 2024.10.25 |