RHEL · Rocky 계열 노드에 직접 올린 쿠버네티스에서 firewalld 를 켜면 응용이 줄줄이 깨지고, 내리면 멀쩡해진다. 겉으로 드러나는 모습은 제각각이라 원인을 찾기 어렵다.
nslookup kubernetes.default.svc.cluster.local 10.96.0.10 이 connection timed out; no servers could be reached 로 끝난다.No cluster leader 를 반복한다. 로그 앞쪽에 memberlist: Failed to resolve <peer>.<svc>.<ns>.svc.cluster.local ... i/o timeout 이 먼저 찍힌다.포트를 아무리 열어도 낫지 않는 것이 이 문제의 특징이다. 막히는 것은 포트가 아니라 인터페이스와 대역이기 때문이다.
CNI 는 노드마다 가상 인터페이스를 만들고 그 위로 파드 트래픽을 흘린다. Calico 는 cali* 와 vxlan.calico, Flannel 은 flannel.1 과 cni0, Cilium 은 cilium_* 를 쓴다. firewalld 는 새로 생긴 인터페이스를 기본 존(보통 public)에 넣는데, 이 존은 들어오는 패킷을 기본적으로 버린다. 그래서 파드에서 나가 서비스 ClusterIP 로 향하는 패킷과 노드 사이를 오가는 오버레이 패킷이 조용히 사라진다.
DNS 가 가장 먼저 티가 난다. 파드는 무엇을 하든 CoreDNS 의 ClusterIP 로 UDP 53 질의를 먼저 보내기 때문이다. DNS 가 죽으면 그 위의 모든 것이 도미노로 무너진다. Consul 의 No cluster leader 는 결과이지 원인이 아니다. 서버 파드끼리 이름을 풀지 못해 Raft 피어를 찾지 못한 것뿐이다.
firewalld 를 재시작할 때 kube-proxy 와 CNI 가 넣어 둔 iptables 규칙이 밀려나 함께 깨지는 경우도 겹친다.
포트 문제인지 인터페이스 문제인지부터 가른다. 파드 안에서 클러스터 DNS 로 직접 질의해 본다.
kubectl run -n default -it --rm dnsdiag --image=busybox:1.36 --restart=Never -- \
nslookup kubernetes.default.svc.cluster.local 10.96.0.10
여기서 timeout 이 나면 응용 이전의 클러스터 네트워크 문제다. 다음을 이어서 확인한다.
kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide
kubectl -n kube-system get endpoints kube-dns -o wide
kubectl -n kube-system get pods -l k8s-app=kube-proxy -o wide
firewall-cmd --list-all-zones | grep -A6 -E '^(public|trusted)'
CoreDNS 파드가 Running·Ready 이고 endpoints 에 파드 IP 가 들어 있는데도 질의가 timeout 이면, 파드가 CoreDNS 까지 못 가는 것이다. 확인 삼아 systemctl stop firewalld 후 같은 질의를 다시 던져 본다. 여기서 되면 방화벽이 원인으로 확정된다.
CNI 인터페이스와 클러스터 대역을 trusted 존에 넣는다. firewalld 를 내린 채 운영하지 않아도 된다.
# CNI 가상 인터페이스 (Calico 기준)
firewall-cmd --permanent --zone=trusted --add-interface=cali+
firewall-cmd --permanent --zone=trusted --add-interface=vxlan.calico
firewall-cmd --permanent --zone=trusted --add-interface=tunl0
# 파드 CIDR 과 서비스 CIDR
firewall-cmd --permanent --zone=trusted --add-source=10.244.0.0/16
firewall-cmd --permanent --zone=trusted --add-source=10.96.0.0/12
firewall-cmd --reload
firewall-cmd --zone=trusted --list-all
인터페이스 이름은 CNI 마다 다르므로 ip -br link 로 실제 이름을 보고 맞춘다. Flannel 이면 flannel.1 과 cni0, Cilium 이면 cilium_host · cilium_net · cilium_vxlan 이다.
파드 CIDR 은 kubectl cluster-info dump | grep -m1 cluster-cidr, 서비스 CIDR 은 kubectl cluster-info dump | grep -m1 service-cluster-ip-range 로 실제 값을 확인해 쓴다. 기본값을 그대로 믿지 않는다.
적용한 뒤에는 kube-proxy 를 다시 띄워 밀려난 규칙을 재생성시킨다.
kubectl -n kube-system rollout restart daemonset kube-proxy
kubectl -n kube-system rollout restart deployment coredns
--add-source 만 넣고 인터페이스를 넣지 않으면 절반만 고쳐진다. firewalld 는 패킷이 들어온 인터페이스로 존을 먼저 고르고, 그다음 출발지 주소로 존을 고른다. 오버레이 캡슐화가 걸린 트래픽은 바깥쪽 헤더가 노드 IP 라서 출발지 규칙에 걸리지 않는다. 그래서 인터페이스 쪽을 함께 넣어야 한다.
반대로 인터페이스만 넣으면 호스트 네트워크로 도는 파드나 노드 간 직접 통신이 빠진다. 둘 다 넣는 것이 안전하다.
trusted 존은 그 인터페이스·대역에서 오는 모든 트래픽을 통과시킨다. 노드가 외부에 직접 노출된 구성이라면 파드 CIDR 과 서비스 CIDR 만 넣고, 물리 NIC 는 public 에 그대로 두어야 한다. 물리 인터페이스를 trusted 에 넣는 것은 방화벽을 끄는 것과 같다.
클러스터 노드에서 firewalld 와 CNI 를 함께 쓰는 구성은 배포판과 CNI 조합에 따라 권장 여부가 다르다. 설치 문서가 포트 목록을 제시한다면 그쪽을 먼저 따른다.