노트북에 초록색 터널이 보이고, 클라이언트 로그는 경로가 푸시되었다고 보고하며, 응답하는 유일한 주소는 VPN 서버 자체의 터널 IP입니다. 그 너머로의 핑은 무엇을 겨냥하든 죽습니다, ClusterIP든 다른 포드의 주소든 공용 호스트 이름이든 마찬가지입니다. 이 조합은 모호한 것이 아니라 진단적인데, 캡슐 해제는 동작하고 두 번째 홉은 동작하지 않습니다.

저는 chart로 설치한 OpenVPN 이미지를 실행하는 Debian 노드에서 이 문제를 만났고, 터널은 쉬운 부분이었습니다. 클라이언트는 10.8.0.x 임대를 받았고, 10.8.0.1이 응답했으며, 10.96.0.10, 즉 클러스터 DNS 서비스는 시간 초과되었습니다. 서버 로그에는 아무것도 병든 것처럼 보이지 않았고, 바로 그래서 사람들이 건드릴 필요도 없었던 iptables 규칙을 다시 쓰기 시작합니다.

VPN 포드는 라우터이며, 이를 분명히 말할 가치가 있습니다. 암호화된 패킷이 도착해 포드 자체의 네트워크 네임스페이스에서 풀리고, 그다음 다른 인터페이스로 나가야 합니다. Linux는 바로 그 네임스페이스 안에서 포워딩이 켜져 있지 않으면 두 번째 단계를 거부합니다.

클라이언트가 도달할 수 있는 것과 없는 것

응답하는 주소는 터널 엔드포인트에 속하고 VPN 프로세스가 이를 로컬로 소유하므로 포워딩이 전혀 관여하지 않습니다. 다른 포드, 서비스 VIP, 인터넷을 포함한 나머지 모두는 커널이 패킷을 tun0에서 eth0로 옮겨야 합니다. 그 이동이 거부되면 증상은 거부가 아니라 균일한 침묵인데, 터널 아래로 ICMP 오류를 돌려보내는 것이 아무것도 없기 때문입니다. 도달 불가 메시지가 아니라 시간 초과가 오며, 그것이 아무도 라우팅하지 않는다는 단서입니다.

몇 분의 분류가 계층을 좁힙니다. DNS 이름이 실패하는데 서비스 IP로의 원시 핑이 성공한다면 문제는 포워딩이 아니라 DNS 배관입니다. 피어 주소 너머의 모든 목적지가 시간 초과된다면, 맨 IP도 포함해, 네임스페이스부터 시작하세요.

네임스페이스 안에서 설정을 읽기

모든 포드는 자체 네트워크 네임스페이스를 받고, net.ipv4.ip_forward는 네임스페이스별 sysctl입니다. 노드에서 바꾸는 것은 컨테이너에 대해 아무것도 알려주지 않으며, 잘못된 머신에서 오후를 보내는 확실한 방법입니다.

kubectl exec -it deploy/openvpn -- sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 0

영은 진단을 확인합니다. 값은 노드의 런타임 구성에서 상속되지도 않고, 컨테이너가 생성될 때마다 이미지 기본값에서 시작합니다. 그래서 kubectl exec ... sysctl -w로 손수 적용한 수정은 다음 재시작에서 사라지고 간헐적으로 보이는 결함으로 돌아옵니다.

매니페스트가 유지하도록 활성화하기

지속되는 선택은 같은 포드 샌드박스에서 실행되고 VPN 컨테이너가 시작되기 전에 값을 쓰는 init 컨테이너입니다. 포드의 컨테이너는 네트워크 네임스페이스를 공유하므로 설정이 기본 컨테이너로 전달됩니다.

initContainers:
- name: enable-forwarding
  image: busybox:1.36
  command: ["sysctl", "-w", "net.ipv4.ip_forward=1"]
  securityContext:
    privileged: true

바로 privileged: true가 리뷰어들이 반대하는 줄이며, 그들이 옳습니다. sysctl을 쓰려면 특권이나 좁게 제한된 능력이 필요하고, 제한된 Pod Security Standard를 운영하는 클러스터는 특권 init 컨테이너를 아예 거부할 수 있습니다. admission이 포드를 거부하면 생성 시점에 알게 되며, 적어도 정직하고 읽기 쉽습니다.

더 깔끔한 대안은 포드 수준의 securityContext.sysctls 목록으로, 어떤 컨테이너에도 특권을 주지 않고 kubelet이 네임스페이스 sysctl을 적용하게 합니다. 클러스터의 허용 목록에 있는 이름만 다룹니다. net.ipv4.ip_forward가 그 목록에 있는지는 kubelet 구성 방식에 달려 있고, 목록 밖의 sysctl을 요청하는 포드는 admission에서 거부되거나 샌드박스가 재시도되는 동안 CreateContainerConfigError에 갇힙니다. 필드가 고장 났다고 가정하기 전에 허용 목록을 확인하세요.

두 접근법 중 하나를 손수 만들기 전에, chart가 이미 ipForwardInitContainer: true 같은 형태로 스위치를 노출하는지 보세요. chart 값을 사용하면 override 파일이 작게 유지되고 다음 업그레이드를 견디지만, 손으로 패치한 Deployment 편집은 그러지 못합니다.

후반부: 반환 경로와 정책

포워딩을 켜는 것은 필요 조건이지 충분 조건이 아닙니다. 클라이언트가 클러스터 네트워크에 경로가 없는 사설 풀을 사용하면 응답이 터널로 돌아갈 길을 찾지 못하며, 포드는 그 트래픽을 목적지 쪽으로 마스커레이드해야 합니다.

정책은 그 위에 놓입니다. NetworkPolicy 객체와 클라우드 방화벽 규칙은 패킷이 변환 후 지니는 출발지 주소에 대해 평가되므로, 클라이언트 풀용으로 작성한 규칙은 마스커레이드가 켜지면 조용히 일치를 멈출 수 있습니다.

클라이언트가 클러스터 내부 서비스에만 도달하면 될 때, VPN을 서비스 VIP에서 종료하는 것이 포드 네트워크로 라우팅하는 것보다 작은 설계입니다. 또한 클라이언트 풀을 CNI 라우팅 테이블에서 완전히 밖에 둡니다.

실패하는 홉을 클라이언트로부터 분리하기

바깥으로 작업하고 첫 번째 조용한 단계에서 멈추세요. VPN 피어로 시작해, 다음으로 어떤 포드의 주소, 다음으로 서비스 VIP, 그다음에야 공용 목적지를 시도하세요. 성공할 때마다 경계가 한 홉 이동하고, 실패할 때마다 살펴볼 가치가 있는 계층을 지목합니다. 어떤 롤아웃 이후에도 exec로 들어가 sysctl을 다시 읽으세요, 교체된 포드는 이미지 기본값에서 시작하고 매니페스트에 없던 수정은 이미 사라졌기 때문입니다.

Kubernetes 문서 클러스터에서 sysctl 사용에는 포드 보안 컨텍스트로 설정할 수 있는 이름이 나열되어 있으며, admission이 변경을 거부할 때 가장 먼저 볼 곳입니다.

관련 글