Egress Gateway
Egress Gateway (이그레스 게이트웨이)
Egress Gateway는 파드에서 시작되어 특정 클러스터 외부 CIDR로 향하는 IPv4·IPv6 연결을 특정 노드(게이트웨이 노드)를 통해 라우팅하는 기능이에요. 이 기능을 쓰면 클러스터를 떠나는 트래픽을 게이트웨이 노드에 연결된 예측 가능한 IP로 SNAT할 수 있어서, 레거시 방화벽과 함께 쓰기 좋아요. 이 문서는 기능 활성화 방법과 CiliumEgressGatewayPolicy 정책 작성 방법을 설명해요.
출처: Egress Gateway
본문
Egress Gateway 기능은 파드에서 시작되어 특정 클러스터 외부 CIDR로 향하는 모든 IPv4·IPv6 연결을 특정 노드(이하 "게이트웨이 노드")를 통해 라우팅해요.
Egress Gateway 기능이 활성화되고 egress gateway 정책이 설정되어 있으면, 클러스터를 떠나는 매칭 패킷은 게이트웨이 노드에 연결된 선택된 예측 가능한 IP로 마스커레이드돼요. 예를 들어 레거시 방화벽과 함께 사용해 특정 네임스페이스 안의 특정 파드에서만 레거시 인프라로 가는 트래픽을 허용할 수 있어요. 파드는 보통 IP가 수시로 바뀌는데, 이를 완화하기 위해 마스커레이딩을 쓴다고 해도 노드의 IP도 시간이 지나면서 자주 바뀔 수 있어요.
이 문서에서는 egress gateway 기능을 활성화하고, 특정 워크로드의 이그레스 트래픽을 라우팅·SNAT하는 egress gateway 정책을 구성하는 방법을 설명해요.
Note 이 가이드는 Kubernetes 클러스터에 Cilium이 올바르게 설치되어 있다고 가정해요. 자세한 내용은 Cilium Quick Installation 문서를 참고하세요. 확실하지 않다면
cilium status를 실행해 Cilium이 잘 실행 중인지 확인하세요.
Video Cilium Egress Gateway에 대한 더 많은 인사이트는 eCHO episode 76: Cilium Egress Gateway를 확인하세요.
사전 고려 사항 (Preliminary Considerations)
Cilium은 지정된 게이트웨이 노드에 존재하는 네트워크 facing 인터페이스와 IP 주소를 사용해야 해요. 이 인터페이스와 IP 주소는 운영자가 자신의 네트워킹 환경에 맞게 프로비저닝하고 설정해야 해요. 이 과정은 해당 네트워킹 환경에 크게 의존해요. 예를 들어 AWS/EKS에서는 요구 사항에 따라 하나 이상의 IP 주소를 가진 Elastic Network Interface를 하나 이상 만들고 이를 게이트웨이 노드 역할을 하는 인스턴스에 연결해서, AWS가 인스턴스로/인스턴스에서 나가는 트래픽을 적절히 라우팅할 수 있게 해야 해요. 다른 클라우드 제공자도 비슷한 네트워킹 요구 사항과 구조를 갖고 있어요.
또한 egress gateway 기능을 활성화하려면 BPF 마스커레이딩과 kube-proxy replacement가 모두 활성화되어 있어야 해요.
새 파드에 대한 egress 정책 적용 지연 (Delay for enforcement of egress policies on new pods)
새 파드가 시작되면 해당 파드에 egress gateway 정책이 적용되기까지 지연이 있어요. 즉, 그 파드의 트래픽이 egress gateway IP와 일치하지 않는 소스 IP(파드 IP 또는 노드 IP)로 클러스터를 떠날 수 있어요. 그 egress 트래픽은 게이트웨이 노드를 통해서도 리다이렉트되지 않아요.
다른 기능과의 비호환성 (Incompatibility with other features)
egress gateway는 identity 할당 모드 kvstore와 호환되지 않으므로, Kubernetes를 Cilium의 identity 스토어로 사용해야 해요 (identityAllocationMode를 crd로 설정). 이는 새 설치의 기본 설정이에요.
Egress gateway는 Cluster Mesh 기능과 호환되지 않아요. egress gateway 정책이 선택한 게이트웨이는 선택된 파드와 같은 클러스터에 있어야 해요.
Egress gateway는 CiliumEndpointSlice 기능과 호환되지 않아요 (자세한 내용은 GitHub issue 24833 참고).
Egress gateway 활성화 (Enable egress gateway)
egress gateway 기능과 모든 요구 사항은 다음과 같이 활성화할 수 있어요:
Helm ConfigMap Helm Repository OCI Registry
helm upgrade cilium cilium/cilium --version 1.20.2 \
--namespace kube-system \
--reuse-values \
--set egressGateway.enabled=true \
--set bpf.masquerade=true \
--set kubeProxyReplacement=true
helm upgrade cilium oci://quay.io/cilium/charts/cilium 1.20.2 \
--namespace kube-system \
--reuse-values \
--set egressGateway.enabled=true \
--set bpf.masquerade=true \
--set kubeProxyReplacement=true
enable-bpf-masquerade: true
enable-egress-gateway: true
kube-proxy-replacement: true
변경 사항을 적용하려면 agent 파드와 operator 파드를 모두 롤아웃하세요:
$ kubectl rollout restart ds cilium -n kube-system
$ kubectl rollout restart deploy cilium-operator -n kube-system
egress gateway 정책 작성 (Writing egress gateway policies)
Cilium이 egress gateway 기능을 구동하는 데 제공하는 API는 CiliumEgressGatewayPolicy 리소스예요.
메타데이터 (Metadata)
CiliumEgressGatewayPolicy는 클러스터 범위의 커스텀 리소스 정의이므로 .metadata.namespace 필드는 지정하면 안 돼요.
apiVersion: cilium.io/v2
kind: CiliumEgressGatewayPolicy
metadata:
name: example-policy
특정 네임스페이스에 속한 파드를 타겟팅하려면 대신 라벨/표현식만 사용해야 해요 (아래 설명 참고).
소스 파드 선택 (Selecting source pods)
CiliumEgressGatewayPolicy 리소스의 selectors 필드는 라벨 셀렉터로 소스 파드를 선택하는 데 사용돼요. matchLabels로 할 수 있어요:
selectors:
- podSelector:
matchLabels:
labelKey: labelVal
matchExpressions로도 할 수 있어요:
selectors:
- podSelector:
matchExpressions:
- {key: testKey, operator: In, values: [testVal]}
- {key: testKey2, operator: NotIn, values: [testVal2]}
또한 여러 개의 podSelector를 지정할 수 있어요:
selectors:
- podSelector:
[..]
- podSelector:
[..]
특정 네임스페이스에 속한 파드를 선택하려면 특수 라벨 io.kubernetes.pod.namespace를 사용해야 해요.
특정 노드에 있는 파드만 선택하려면 nodeSelector를 사용할 수 있어요:
selectors:
- podSelector:
matchLabels:
labelKey: labelVal
nodeSelector:
matchLabels:
nodeLabelKey: nodeLabelVal
Note 보안 identity만 고려돼요. 자세한 내용은 Limiting Identity-Relevant Labels를 참고하세요.
nodeSelector는 단독으로 쓸 수 없고 반드시podSelector와 함께 사용해야 해요.
목적지 선택 (Selecting the destination)
destinationCIDRs로 하나 이상의 목적지 CIDR을 지정할 수 있어요:
destinationCIDRs:
- "a.b.c.d/32"
- "e.f.g.0/24"
- "a:b::/48"
Note 이 범위에 속하면서 동시에 내부 클러스터 IP(예: 파드, 노드, Kubernetes API server)인 모든 IP는 egress gateway SNAT 로직에서 제외돼요.
excludedCIDRs로 destinationCIDRs 목록에 예외를 지정할 수 있어요:
destinationCIDRs:
- "a.b.0.0/16"
- "a:b::/48"
excludedCIDRs:
- "a.b.c.0/24"
- "a:b:c::/64"
이 경우 a.b.0.0/16 CIDR로 향하는 트래픽 중 a.b.c.0/24 목적지를 제외한 트래픽만 egress gateway를 통해 나가고, 지정된 egress IP로 클러스터를 떠나요.
게이트웨이 노드 선택 및 구성 (Selecting and configuring the gateway node)
특정 정책의 게이트웨이 노드 역할을 할 노드는 egressGateway 필드로 구성할 수 있어요. 노드는 nodeSelector 필드로 그 라벨을 기준으로 매칭돼요:
egressGateway:
nodeSelector:
matchLabels:
testLabel: testVal
Note 주어진 라벨 집합과 매칭되는 노드가 여러 개면, 이름 기준 사전순(lexical order)에서 첫 번째 노드가 선택돼요.
Note 주어진 라벨 집합과 매칭되는 노드가 없으면, Cilium은 목적지 CIDR(들)과 매칭되는 트래픽을 드롭해요.
정책은 게이트웨이 노드의 egress 네트워크 인터페이스와 트래픽을 SNAT할 때 사용할 IP 주소를 선택해야 해요. 이를 달성하는 방법은 3가지예요:
-
인터페이스 지정:
egressGateway:
nodeSelector: matchLabels: testLabel: testVal interface: ethX
이 경우 `ethX` 인터페이스에 할당된 첫 번째 IPv4, IPv6 주소가 사용돼요.
2. egress IP를 명시적으로 지정. 여기서 egress 네트워크 인터페이스는 데이터 경로에서 라우트 조회를 수행해 패킷마다 동적으로 결정돼요.
egressGateway: nodeSelector: matchLabels: testLabel: testVal egressIP: a.b.c.d
> **Warning**
> egress IP는 노드의 네트워크 디바이스에 할당되어 있어야 해요.
3. `egressIP`와 `interface` 속성을 모두 생략. 이 경우 기본 라우트가 있는 네트워크 인터페이스를 egress 인터페이스로 선택하고, 이 인터페이스의 첫 번째 IPv4, IPv6 주소를 egress IP로 사용해요.
egressGateway: nodeSelector: matchLabels: testLabel: testVal
egress IP를 어떻게 구성하든, `--devices` 에이전트 옵션을 설정해서 Cilium이 선택된 네트워크 인터페이스에서 실행 중인지 확인해야 해요.
> **Warning**
> `egressGateway` spec에서 `egressIP`와 `interface` 속성은 둘 다 지정할 수 없어요. 두 속성을 모두 포함한 Egress Gateway Policy는 Cilium이 무시해요.
> **Note**
> Cilium이 Egress Gateway 정책의 Egress IP를 선택하지 못하면 (예: 지정한 `egressIP`가 게이트웨이 노드의 어떤 네트워크 인터페이스에도 설정되어 있지 않은 경우), 게이트웨이 노드는 정책과 매칭되는 트래픽을 `No Egress IP configured` 사유로 드롭해요.
> **Note**
> Cilium이 Egress Gateway 정책의 네트워크 인터페이스와 Egress IP를 선택한 후(또는 실패한 후)에는 게이트웨이 노드의 네트워크 구성 변경(예: IP 주소 추가·삭제)에 자동으로 대응하지 않아요. Egress Gateway 정책을 재적용하면 새로 선택하도록 강제할 수 있어요.
#### 여러 게이트웨이 노드 선택 (Selecting multiple gateway nodes)
같은 정책에서 여러 게이트웨이 노드를 선택할 수 있어요. 이 경우 `egressGateways` 목록 필드로 게이트웨이 노드를 구성할 수 있어요. 이 목록의 항목들은 `egressGateway` 필드와 완전히 동일한 구성 옵션을 가져요:
egressGateways:
- nodeSelector: matchLabels: testLabel: testVal1
- nodeSelector: matchLabels: testLabel: testVal2
> **Note**
> `egressGateway` 필드와 동일한 제한이 `egressGateways` 목록의 각 항목에도 적용돼요.
> **Note**
> 여러 게이트웨이를 사용할 때 정책이 매칭한 소스 엔드포인트는 여전히 하나의 게이트웨이를 통해 트래픽을 내보내며, 모든 게이트웨이를 사용하지는 않아요. 엔드포인트는 CiliumEndpoint의 UID에 따라 게이트웨이에 배정돼요. 따라서 `nodeSelector` 필드가 매칭하는 게이트웨이 노드가 바뀌지 않는 한 엔드포인트는 수명 동안 같은 게이트웨이를 사용해야 해요. `nodeSelector` 필드가 추가·삭제·수정되거나, `nodeSelector` 중 하나와 매칭되는 노드가 추가·삭제되면 게이트웨이 목록이 바뀌고 엔드포인트가 재배정돼요.
> **Warning**
> 단일 게이트웨이 정책과 마찬가지로, 게이트웨이 노드를 바꾸면 기존 egress 연결이 끊겨요. 이 문제를 추적하는 [GitHub issue 39245](https://github.com/cilium/cilium/issues/39245)를 읽어보세요.
#### 예제 정책 (Example policy)
아래는 위 명세를 만족하는 `CiliumEgressGatewayPolicy` 리소스 예시예요:
apiVersion: cilium.io/v2 kind: CiliumEgressGatewayPolicy metadata: name: egress-sample spec:
Specify which pods should be subject to the current policy.
Multiple pod selectors can be specified.
selectors:
- podSelector: matchLabels: org: empire class: mediabot # The following label selects default namespace io.kubernetes.pod.namespace: default nodeSelector: # optional, if not specified the policy applies to all nodes matchLabels: node.kubernetes.io/name: node1 # only traffic from this node will be SNATed
Specify which destination CIDR(s) this policy applies to.
Multiple CIDRs can be specified.
destinationCIDRs:
- "0.0.0.0/0"
- "::/0"
Configure the gateway node.
egressGateway: # Specify which node should act as gateway for this policy. nodeSelector: matchLabels: node.kubernetes.io/name: node2
# Specify the IP address used to SNAT traffic matched by the policy.
# It must exist as an IP associated with a network interface on the instance.
egressIP: 10.168.60.100
# Alternatively it's possible to specify the interface to be used for egress traffic.
# In this case the first IPv4 and IPv6 addresses assigned to that interface will be used
# as egress IP.
# interface: enp0s8
위 `CiliumEgressGatewayPolicy` 리소스를 생성하면, `default` 네임스페이스의 노드 `node1`에서 `org: empire`, `class: mediabot` 라벨을 가진 파드에서 시작되어 `0.0.0.0/0` 또는 `::/0`(즉, 클러스터를 떠나는 모든 트래픽)로 향하는 모든 트래픽이 `node.kubernetes.io/name: node2` 라벨을 가진 게이트웨이 노드를 통해 라우팅되고, 그 노드가 `10.168.60.100` egress IP로 SNAT을 수행해요.
### egress 네트워크 인터페이스 선택 (Selection of the egress network interface)
네트워크 인터페이스가 여러 개인 게이트웨이 노드의 경우, Cilium은 노드의 라우팅 구성을 기반으로 egress 네트워크 인터페이스를 선택해요 (`ip route get <externalIP> from <egressIP>`).
### egress gateway 기능 테스트 (Testing the egress gateway feature)
이 섹션에서는 이 기능을 테스트하기 위한 필요한 단계를 보여줄게요. 먼저 클러스터 외부 서비스에 연결하는 파드를 배포해요. 그다음 `CiliumEgressGatewayPolicy`를 적용하고 파드의 연결이 Gateway 노드를 통해 리다이렉트되는 것을 관찰해요. IP가 `192.168.60.11`(node1)과 `192.168.60.12`(node2)인 2-노드 클러스터를 가정해요. 클라이언트 파드는 node1에 배포되고, CEGP는 node2를 Gateway 노드로 선택해요.
#### 외부 서비스 생성 (선택 사항) (Create an external service (optional))
실험할 외부 서비스가 없다면 Nginx를 사용하면 돼요. 서버 접근 로그에서 요청이 어느 IP에서 오는지 보여주기 때문이에요. 기존 Kubernetes 클러스터 외부의 Linux 노드에 nginx 서비스를 만들고, 이를 egress 트래픽의 목적지로 사용하세요:
$ # Install and start nginx $ sudo apt install nginx $ sudo systemctl start nginx
이 예시에서 Nginx 인스턴스를 실행하는 호스트에 연결된 IP는 `192.168.60.13`이에요.
#### 클라이언트 파드 배포 (Deploy client pods)
Nginx 인스턴스에 연결하는 데 사용할 클라이언트 파드를 배포해요:
$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/kubernetes-dns/dns-sw-app.yaml $ kubectl get pods NAME READY STATUS RESTARTS AGE pod/mediabot 1/1 Running 0 14s
$ kubectl exec mediabot -- curl http://192.168.60.13:80
Nginx 접근 로그(또는 다른 외부 서비스)에서 요청이 Kubernetes 클러스터의 노드 중 하나에서 오는지 확인하세요. 이 예시에서 접근 로그는 다음과 비슷해야 해요:
$ tail /var/log/nginx/access.log [...] 192.168.60.11 - - [04/Apr/2021:22:06:57 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.52.1"
클라이언트 파드가 노드 `192.168.60.11`에서 실행 중이므로, Cilium egress gateway 정책이 없는 경우 트래픽은 노드의 IP로 클러스터를 떠날 것으로 예상돼요.
#### egress gateway 정책 적용 (Apply egress gateway policy)
`egress-sample` Egress Gateway Policy yaml을 내려받으세요:
지정한 외부 서비스가 실행 중인 호스트의 IP를 포함하도록 `destinationCIDRs`를 수정하세요.
`egressIP` 필드에 IP 주소를 지정하는 것은 선택사항이에요. 이 예시를 쉽게 하기 위해 해당 줄을 주석 처리할 수 있어요. 그러면 agent가 기본 라우트가 있는 인터페이스에 할당된 첫 번째 IPv4, IPv6 주소를 사용해요.
정책이 Egress Gateway로 지정할 노드를 선택하게 하려면 해당 노드에 `egress-node: true` 라벨을 적용하세요:
$ kubectl label nodes
Egress Gateway 노드는 `mediabot` 파드가 실행 중인 노드와 다른 노드여야 해요.
`egress-sample` egress gateway Policy를 적용해, mediabot 파드의 모든 트래픽이 Egress Gateway 노드의 IP로 클러스터를 떠나게 하세요:
$ kubectl apply -f egress-gateway-policy.yaml
#### 설정 확인 (Verify the setup)
클라이언트 파드로 정책이 제대로 동작하는지 확인할 수 있어요:
$ kubectl exec mediabot -- curl http://192.168.60.13:80
[...] ```Nginx의 접근 로그는 요청이 파드가 실행 중인 노드의 IP가 아니라 선택된 Egress IP에서 오는 것으로 보여야 해요:
$ tail /var/log/nginx/access.log
[...]
192.168.60.100 - - [04/Apr/2021:22:06:57 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.52.1"
트러블슈팅 (Troubleshooting)
예상대로 동작하지 않는 정책을 트러블슈팅하려면, cilium agent에서 egress 구성을 볼 수 있어요 (구성은 모든 agent에 전파되므로 어느 것을 선택해도 상관없어요):
$ kubectl -n kube-system exec ds/cilium -- cilium-dbg bpf egress list
Defaulted container "cilium-agent" out of: cilium-agent, config (init), mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), wait-for-node-init (init), clean-cilium-state (init)
Source IP Destination CIDR Egress IP Gateway IP
192.168.2.23 192.168.60.13/32 0.0.0.0 192.168.60.12
Source IP 주소는 정책의 podSelector와 매칭되는 각 파드의 IP 주소와 일치해요. Gateway IP 주소는 정책의 nodeSelector와 매칭되는 egress 노드의 (내부) IP 주소와 일치해요. Egress IP는 egress gateway 노드에서 실행 중인 agent를 제외한 모든 agent에서 0.0.0.0이며, egress gateway 노드에서는 이 트래픽에 사용되는 Egress IP 주소(정책에서 egressIP를 지정했다면 그 값)가 보여요.
표시된 egress 목록에 정책과 매칭되는 항목이 예상대로 없다면, 파드와 egress 노드가 정책 셀렉터와 매칭되도록 올바르게 라벨링되어 있는지 확인하세요.
SNAT 연결 한도 트러블슈팅 (Troubleshooting SNAT Connection Limits)
더 고급 트러블슈팅 주제는 SNAT connection limits 항목의 고급 egress gateway 트러블슈팅 문서를 참고하세요.