본문으로 건너뛰기

CKA 개념 노트: Pod 네트워크와 CoreDNS

  • kubernetes
  • cka
  • networking
  • kube-proxy
  • coredns
  • dns

기준: kubeadm 클러스터 v1.35.9 (libvirt VM 3대, 노드 네트워크 192.168.122.0/24), Calico v3.33.0, CoreDNS v1.13.1에서 명령어 확인

Service type, selector와 EndpointSlice, Ingress, NetworkPolicy 기본은 CKAD 개념 노트: Service, Ingress, NetworkPolicy에 정리했음. 여기서는 노드와 클러스터 쪽에서 보는 것만.

목차

네트워크 모델

Kubernetes가 CNI에게 요구하는 것:

  • Pod마다 클러스터 안에서 고유한 IP
  • 모든 Pod는 NAT 없이 다른 모든 Pod와 통신 (같은 노드든 다른 노드든)
  • 노드의 agent(kubelet 등)는 그 노드의 Pod와 통신

IP 대역 세 가지가 겹치면 안 됨.

대역이 클러스터정하는 곳
노드 네트워크192.168.122.0/24인프라 (libvirt)
Pod 네트워크10.244.0.0/16kubeadm init --pod-network-cidr, CNI 설정
Service 네트워크10.96.0.0/12kubeadm init --service-cidr (기본값)

Pod IP는 어디서 오나

$ kubectl get nodes -o custom-columns=NAME:.metadata.name,PODCIDR:.spec.podCIDR
NAME      PODCIDR
cka-cp1   10.244.0.0/24
cka-w1    10.244.1.0/24
cka-w2    10.244.2.0/24

controller-manager가 --allocate-node-cidrs=true라서 노드마다 /24를 나눠 줌. 그런데 실제 Pod IP는 10.244.95.x, 10.244.62.x였음. Calico는 node.spec.podCIDR를 쓰지 않고 자체 IPAM 블록을 씀.

$ kubectl get blockaffinities -o custom-columns=NODE:.spec.node,CIDR:.spec.cidr
NODE      CIDR
cka-cp1   10.244.212.128/26
cka-w1    10.244.95.128/26
cka-w2    10.244.62.128/26

podCIDR를 쓰는 CNI(flannel 등)도 있어서, Pod IP가 어느 대역에서 나오는지는 CNI마다 다름.

노드를 넘는 Pod 통신

cka-w1의 busybox Pod에서 cka-w2의 nginx Pod IP로 직접 요청:

kubectl exec c -- wget -qO- -T3 http://10.244.62.167
# <title>Welcome to nginx!</title>

Service 없이 Pod IP로 바로 됨. Calico는 노드 사이를 VXLAN(vxlan.calico 인터페이스)으로 감쌈.

kube-proxy

Service는 실체가 없는 가상 IP. 각 노드의 kube-proxy가 Service와 EndpointSlice를 watch해서 패킷 규칙으로 바꿈.

mode방식비고
iptablesiptables NAT 규칙Linux 기본
ipvs커널 IPVS
nftablesnftables 규칙
kubectl -n kube-system get cm kube-proxy -o jsonpath='{.data.config\.conf}' | grep '^mode'
# mode: ""        ← 빈 값 = Linux에서 iptables

Service net/web(ClusterIP 10.111.80.136, endpoint 2개)가 만든 규칙:

-A KUBE-SERVICES -d 10.111.80.136/32 -p tcp -m comment --comment "net/web cluster IP" -m tcp --dport 80 -j KUBE-SVC-RTHZXQ2YZKSHM2PI
-A KUBE-SVC-RTHZXQ2YZKSHM2PI -m comment --comment "net/web -> 10.244.62.167:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-S3UT2MK4P7AJ67WG
-A KUBE-SVC-RTHZXQ2YZKSHM2PI -m comment --comment "net/web -> 10.244.95.170:80" -j KUBE-SEP-JIQR6UIXHEWKPI63
-A KUBE-NODEPORTS -p tcp -m comment --comment "net/web" -m tcp --dport 31777 -j KUBE-EXT-RTHZXQ2YZKSHM2PI
  • KUBE-SERVICES: ClusterIP:port를 보고 Service 체인으로
  • KUBE-SVC-*: endpoint 중 하나를 확률로 고름 (2개면 0.5)
  • KUBE-SEP-*: 실제 Pod IP:port로 DNAT
  • KUBE-NODEPORTS: NodePort로 들어온 것

Service에 endpoint가 없으면 KUBE-SVC-*에 갈 곳이 없음. “Service는 있는데 연결이 안 된다”면 EndpointSlice부터 확인.

kubectl get endpointslice -l kubernetes.io/service-name=web
# NAME        ADDRESSTYPE   PORTS   ENDPOINTS
# web-4bnv6   IPv4          80      10.244.62.167,10.244.95.170

NodePort와 LoadBalancer

kubectl expose deploy web --port=80 --type=NodePort
# web   NodePort   10.111.80.136   <none>   80:31777/TCP

NodePort는 모든 노드에서 열림. Pod가 없는 control plane IP로 요청해도 응답함.

192.168.122.11:31777  <title>Welcome to nginx!</title>   (cka-cp1, Pod 없음)
192.168.122.21:31777  <title>Welcome to nginx!</title>
192.168.122.22:31777  <title>Welcome to nginx!</title>

LoadBalancer는 외부 LB를 만들어 주는 구현(클라우드 controller, MetalLB 등)이 있어야 IP가 붙음. 이 클러스터에는 없어서 <pending>.

$ kubectl create svc loadbalancer lb --tcp=80:80
lb   LoadBalancer   10.111.57.195   <pending>   80:30363/TCP

LoadBalancer Service도 NodePort(30363)를 같이 받기 때문에 <pending> 상태에서도 NodePort로는 접근 가능.

CoreDNS

구성

$ kubectl -n kube-system get deploy coredns
coredns   2/2

$ kubectl -n kube-system get svc kube-dns
kube-dns   ClusterIP   10.96.0.10   53/UDP,53/TCP,9153/TCP
  • Deployment 이름은 coredns, Service 이름은 호환성 때문에 kube-dns
  • 10.96.0.10은 kubelet 설정의 clusterDNS와 같은 값. kubelet이 Pod의 /etc/resolv.conf에 이 주소를 넣음

Corefile

설정은 kube-system/coredns ConfigMap의 Corefile.

.:53 {
    errors
    health {
       lameduck 5s
    }
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
       pods insecure
       fallthrough in-addr.arpa ip6.arpa
       ttl 30
    }
    prometheus :9153
    forward . /etc/resolv.conf {
       max_concurrent 1000
    }
    cache 30 {
       disable success cluster.local
       disable denial cluster.local
    }
    loop
    reload
    loadbalance
}
plugin하는 일
kubernetes cluster.local ...Service, Pod 레코드 응답. pods insecure면 Pod IP 레코드도
forward . /etc/resolv.conf클러스터 밖 이름은 노드(CoreDNS Pod)의 resolv.conf 서버로
cache 3030초 캐시
reloadCorefile이 바뀌면 재시작 없이 다시 읽음
health, readyliveness(8080), readiness(8181)

Corefile 수정

hosts plugin으로 클러스터 밖 이름 하나를 고정해 봄.

kubectl -n kube-system edit cm coredns
    ready
    hosts {
       192.168.122.200 legacy.example.internal
       fallthrough
    }
    kubernetes cluster.local in-addr.arpa ip6.arpa {
  • reload plugin이 있어서 CoreDNS를 재시작하지 않아도 반영됨. 로그에 [INFO] Reloading
  • 반영까지 82초 걸렸음. ConfigMap volume이 Pod 안 파일에 반영되는 시간 + reload 주기. 급하면 kubectl -n kube-system rollout restart deploy coredns
  • 특정 도메인만 다른 DNS 서버로 보내려면 server block을 추가 (example.internal:53 { forward . 10.0.0.53 })

DNS 이름

대상이름
Service<service>.<namespace>.svc.cluster.local
Pod<IP의 . 를 - 로>.<namespace>.pod.cluster.local (예: 10-244-62-167.net.pod.cluster.local)
headless Service의 Pod<hostname>.<service>.<namespace>.svc.cluster.local

Pod의 /etc/resolv.conf (dnsPolicy: ClusterFirst, 기본값):

search net.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
  • 같은 namespace면 web, 다른 namespace면 web.other로도 찾아야 정상
  • busybox:1.36의 nslookup web은 ** server can't find web.svc.cluster.local: NXDOMAIN으로 실패했음. FQDN(web.net.svc.cluster.local)으로는 성공. busybox로 확인할 때는 FQDN을 쓰는 게 안전

dnsPolicy

dnsPolicyresolv.conf실습 결과
ClusterFirst (기본)CoreDNS + search 목록위와 같음
DefaultPod가 뜬 노드의 resolv.conf를 그대로nameserver 192.168.122.1. 클러스터 이름 해석 안 됨
ClusterFirstWithHostNethostNetwork Pod에서도 CoreDNS 사용nameserver 10.96.0.10 + search 목록
NonednsConfig만 사용아래
  • 이름과 달리 Default가 기본값이 아님
  • hostNetwork: true에 dnsPolicy를 안 쓰면(ClusterFirst) 노드 resolv.conf(192.168.122.1)가 들어감. CoreDNS를 쓰려면 ClusterFirstWithHostNet
spec:
  dnsPolicy: "None"
  dnsConfig:
    nameservers: [1.1.1.1]
    searches: [example.internal]
    options:
      - name: ndots
        value: "2"
search example.internal
nameserver 1.1.1.1
options ndots:2

헷갈리는 것 비교

비교AB
--pod-network-cidr vs --service-cidrPod IPService ClusterIP
node.spec.podCIDR vs 실제 Pod IPcontroller-manager가 나눠 준 대역CNI IPAM이 정함. Calico는 자체 블록
kube-proxy vs CNIService → Pod (DNAT)Pod ↔ Pod 연결
ClusterIP vs NodePort클러스터 안에서만모든 노드 IP의 30000-32767
NodePort vs LoadBalancer노드 IP:port외부 LB IP. 구현 없으면 <pending>. NodePort도 같이 받음
kube-dns Service vs coredns Deployment이름만 kube-dns실제 서버
dnsPolicy: Default vs ClusterFirst노드 DNSCoreDNS (실제 기본값)

시험 포인트

빨리 보는 명령어

kubectl get nodes -o wide; kubectl get pods -o wide            # 노드 IP, Pod IP
kubectl -n kube-system get cm kubeadm-config -o yaml | grep -E "podSubnet|serviceSubnet"
kubectl -n kube-system get cm kube-proxy -o yaml | grep mode
kubectl get svc,endpointslice
kubectl -n kube-system get cm coredns -o yaml
kubectl -n kube-system edit cm coredns
kubectl -n kube-system rollout restart deploy coredns
kubectl run t --image=busybox:1.36 --rm -it --restart=Never -- nslookup <svc>.<ns>.svc.cluster.local
sudo iptables -t nat -S | grep <namespace>/<service>
  • Pod CIDR, Service CIDR를 묻는 문제는 kubeadm-config ConfigMap이나 apiserver(--service-cluster-ip-range), controller-manager(--cluster-cidr) manifest에서 찾음
  • Pod 레코드 이름은 IP의 점을 대시로

kubectl explain

kubectl explain pod.spec.dnsPolicy
kubectl explain pod.spec.dnsConfig
kubectl explain service.spec.type

참고 문서