시작
EKS에서 yaml을 작성하며 헷갈리는 사항 몇 가지가 있습니다.
Service yaml 작성 시, type을 ClusterIP로 할지 Nodeport로 할지와
Ingress yaml 작성 시, target-type을 ip로 할지 instance로 할지 입니다.
그래서 아래 4가지 케이스에 대해 적용해보며 확인하고 AWS에서는 어떻게 사용해야하는지 이야기 해보려고 합니다.
- Service Type: ClusterIP / Ingress Target Type : ip
- Service Type: NodePort / Ingress Target Type : ip
- Service Type: ClusterIP / Ingress Target Type : instance
- Service Type: NodePort / Ingress Target Type : instance
TEST 환경
- TEST 환경은 EKS 클러스터의 Node 4EA에서 진행합니다.
- 2개의 NginX Pod가 실행됩니다.
첫 번째 CASE (Service Type: ClusterIP / Ingress Target Type : ip)
첫 번째 CASE의 경우 POD는 아래와 같이 생성됩니다.
그리고 Ingress Target Group을 IP로 지정했으므로, Target Group을 확인해보면 Pod IP가 지정되어 있음을 볼 수 있습니다.
다음은 파드가 떠 있는 노드에서 iptables Rule과 파드가 없는 노드에서의 iptables Rule 입니다.
Service Type이 ClusterIP 이기 때문에 노드 iptables Rule에는 특별한 설정이 더는 없습니다.
여기서 하나 확인 할 수 있는건 파드가 있는 노드건 없는 노드건, 둘 다 hello라는 Service로 요청이 온다면, DNAT Rule을 통해 목적지 파드로 보내고 있다는 겁니다.
즉, 그림처럼 Service Type이 ClusterIP이고 ingress가 따로 없다면 Pod는 클러스터 내부에서만 알 수 있는 상황이 됩니다.
두 번째 CASE (Service Type : NodePort / Ingress Target Type : ip)
두 번째 CASE의 경우 POD는 아래와 같이 생성됩니다.
첫 번째와 동일하게 Ingress Target Group을 IP로 지정했으므로, Target Group을 확인해보면 Pod IP가 지정되어 있음을 볼 수 있습니다.
Service Type 이 NodePort인 경우는 hello라는 Service로 요청이 온다면, DNAT Rule을 통해 목적지 파드로 보내는 룰 이외에 아래 그림처럼 또 다른 룰이 더 만들어 집니다.
바로 노드의 30155 포트를 열고, 30155 포트로 둘어온 요청을 KUBE-EXT-LWT6K5FLZ63QAKX7 체인으로 전달 (jump)한다는 것입니다. (빨간 화살표)
그리고 첫 번째 Case와 같이 KUBE-EXT-LWT6K5FLZ63QAKX7 체인은 Pod로 라우팅 하게 되는 것입니다.
즉, 아래 그림과 같습니다.
하지만 이 경우 문제가 있습니다. 바로 파드가 떠 있지 않은 노드에도 30155 port가 오픈된다는 점입니다. (이 경우의 해결 방법은 네 번째 Case에서 설명하겠습니다.)
그리고 외부에서 접근하기 위한 Ingress의 target-type이 ip이기 때문에, Target Group은 Instance가 아닌 Pod의 IP를 바라보므로, 굳이 node의 port를 열 필요도 없는 것입니다.
세 번째 CASE (Service Type: ClusterIP / Ingress Target Type : instance)
이 경우는 외부에서 Pod에 접근하기 위해 성립할 수 없는 조건입니다.
Ingress의 Target Group은 Instance로 설정이 되어있는데, 노드의 어떠한 포트로 가야하는지 내용도 없으며,
설령 무작위 어떤 한 포트로 가더라도, 그 포트가 KUBE-EXT-LWT6K5FLZ63QAKX7 체인으로 전달 (jump)한다는 iptables Rule도 없기 때문입니다.
네 번째 CASE (Service Type: NodePort / Ingress Target Type : instance)
두 번째 CASE의 경우 POD는 아래와 같이 생성됩니다.
그리고 Ingress Target Group을 Instance로 지정했으므로, Target Group을 확인해보면 Instance의 특정 포트가 지정되어 있음을 볼 수 있습니다.
두 번째와 마찬가지로 Service Type 이 NodePort인 경우는 hello라는 Service로 요청이 온다면, DNAT Rule을 통해 목적지 파드로 보내는 룰 이외에 아래 그림처럼 또 다른 룰이 더 만들어 집니다.
즉, 아래 그림과 같습니다.
두 번째 CASE에서 언급했듯 이 경우는 문제가 있습니다. 바로 파드가 떠 있지 않은 노드에도 30155 port가 오픈된다는 점입니다.
해당 그림처럼 ALB가 POD가 없는 노드 쪽으로 요청을 보내도, iptables Rule을 통해 요청은 정상적으로 가겠지만, 불필요한 하나의 홉을 더 건넌다는 것입니다.
이러한 경우 파드가 떠 있지 않은 노드에 포트를 차단하는 방법이 있습니다.
바로 위의 yaml 처럼 "externalTrafficPolicy: Local" 로 설정해주는 것입니다.
해당 옵션 사용 시, 노드의 또 다른 iptables 룰이 생겨 파드가 떠있지 않은 노드의 포트를 차단할 수 있게 됩니다.
위 그림처럼 파드가 떠 있지 않은 노드에는 30155로 들어오는 패킷을 DROP 시키는 iptables rule이 추가되어 노드의 포트를 차단하게 되는 것입니다.
(iptables는 위에 룰보다 아래 룰이 마지막으로 적용됩니다.)
해당 옵션 적용 후, Ingress의 Target 그룹을 살펴보면,
노드의 포트가 막혀있기 때문에 그림처럼 파드가 떠 있지 않은 노드는 Unhealthy 상태로 변하게 됩니다.
결론
네 가지의 CASE를 보면서 각각의 방식이 어떻게 동작하는지 알 수 있었습니다.
결론은 AWS 환경에서는 굳이 Service Type은 NodePort로 쓸 필요가 없으며, Ingress Target Type도 instance로 쓸 필요가 없다는 것입니다.
Service Type은 NodePort 사용 시, ingress target group을 ip로 하면, ingress가 노드 포트를 거쳐가지도 않는데 노드의 포트만 열린 상황이 되며,
Ingress Target Type을 instance로 사용 시, ip로 한 것과 달리 불필요하게 Ingress가 노드를 한번 더 거쳐 POD로 가기 때문입니다.