LabHub
배우기 러닝패스 코스

Kubernetes Networking — On a Real Cluster

Carve Pod CIDRs and design the plugin chain

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

파드 주소가 어디서 나오는지를 노드 오브젝트와 플러그인 설정 양쪽에서 확인하고, 대역을 겹치지 않게 자르는 계산을 손으로 해 본다.

왜 중요한가

클러스터를 새로 세울 때 가장 먼저 정해야 하고 가장 되돌리기 어려운 것이 주소 대역이다. 파드·서비스·노드 세 종류를 각각 다른 주체가 배정하는데 서로 상의하지 않으므로, 겹치지 않게 잡는 일은 설계자의 몫이다. 또 hostPort 나 대역 제한처럼 매니페스트에는 적히지만 플러그인이 없으면 조용히 무시되는 기능들이 있어서, 무엇을 누가 맡는지 한 번 갈라 두지 않으면 '설정은 맞는데 안 된다' 를 오래 헤매게 된다.

단계

  1. 클러스터 파드 대역 10.244.0.0/16/24 로 갈라 lab-node-0·lab-node-1·lab-node-2 에 앞에서부터 차례로 못박으세요(spec.podCIDRspec.podCIDRs 둘 다). 그다음 /root/k8nd-cni/01-podcidr.txt노드이름 대역 세 줄을 노드 이름 순서로 적으세요.
  2. /root/k8nd-cni/02-capacity.txt 에 다섯 줄을 적으세요 — subnets=(10.244.0.0/16/24 로 자를 때 나오는 조각 수), addresses_per_subnet=(한 조각의 주소 총 개수), usable_per_subnet=(네트워크 주소와 게이트웨이를 뺀 수), node_pod_limit=(노드 오브젝트가 말하는 status.allocatable.pods 값), binding_limit=(둘 중 먼저 걸리는 쪽을 subnet 또는 node 로).
  3. /root/k8nd-cni/10-k8nd.conflist 를 만드세요 — cniVersion1.0.0, namek8nd-pod-network, plugins정확히 셋이고 순서대로 bridge·portmap·bandwidth 입니다. bridgeipamhost-local 이고 subnetlab-node-0 의 파드 대역, portmap 에는 capabilities.portMappings 가 참, bandwidth 에는 capabilities.bandwidth 가 참이어야 합니다.
  4. /root/k8nd-cni/shaped.yaml 에 파드 매니페스트를 쓰고 적용하세요 — 네임스페이스 k8nd-cni, 이름 shaped, 어노테이션 kubernetes.io/ingress-bandwidth: 1Mkubernetes.io/egress-bandwidth: 1M, 컨테이너 web(nginx:1.27-alpine)에 containerPort 8080·hostPort 8080 입니다. 그리고 /root/k8nd-cni/04-chain.txthostport_plugin=·bandwidth_plugin=·ingress_annotation=·egress_annotation= 네 줄을 적으세요(플러그인은 3단계 conflist 에서 그 일을 맡은 플러그인의 type, 어노테이션은 실제로 단 값).
  5. /root/k8nd-cni/edge-node.yaml 에 노드 k8nd-cni-edge 를 쓰고 적용하세요 — spec.unschedulable 을 참으로 두고, spec.taints 에 직접 정한 키 k8nd.example.com/cni-missing(effect NoSchedule) 하나를 답니다. 적용한 뒤 /root/k8nd-cni/05-notready.txtnode=·custom_taint=(내가 단 키)·controller_taint=(컨트롤러가 더 붙인 내장 테인트 키)·unschedulable= 네 줄을 적으세요.
  6. /root/k8nd-cni/06-node1.conflist/root/k8nd-cni/06-node2.conflist 를 만드세요. 각각 lab-node-1·lab-node-2 에 못박은 대역을 ipam.subnet 으로 쓰고, ipam.gateway 는 그 대역의 첫 번째 사용 가능 주소이며, ipam.routes 에는 0.0.0.0/0 과 클러스터 파드 대역 10.244.0.0/16 둘 다 들어가야 합니다. ipam.typehost-local 입니다.
  7. 네 가지 대역 배치를 판정하세요 — p1 은 파드 10.244.0.0/16·서비스 10.96.0.0/16·노드 192.168.10.0/24, p2 는 파드 10.96.0.0/12·서비스 10.96.0.0/16·노드 192.168.10.0/24, p3 은 파드 10.244.0.0/16·서비스 10.245.0.0/16·노드 10.244.3.0/24, p4 는 파드 172.16.0.0/16·서비스 10.96.0.0/16·노드 192.168.10.0/24 입니다. /root/k8nd-cni/07-overlap.txtp1= 부터 p4= 까지 네 줄(값은 ok 또는 overlap)과 rule=pod,service,node 한 줄, 모두 다섯 줄을 적으세요.
  8. /root/k8nd-cni/08-report.mdcluster_pod_cidr=·per_node_prefix=·nodes_addressable=(그 프리픽스로 담을 수 있는 노드 수)·notready_node=(5단계에서 만든 노드 이름)·chained_plugins=(3단계 conflist 에서 bridge 뒤에 이어 붙인 플러그인들을 쉼표로) 다섯 줄을 적고, 그 아래 - 로 시작하는 줄을 네 줄 이상 적으세요.

참고

노드마다 파드 대역을 잘라 준다

클러스터 파드 대역 10.244.0.0/16/24 로 갈라 lab-node-0·lab-node-1·lab-node-2 에 앞에서부터 차례로 못박으세요(spec.podCIDRspec.podCIDRs 둘 다). 그다음 /root/k8nd-cni/01-podcidr.txt노드이름 대역 세 줄을 노드 이름 순서로 적으세요.

파드 주소는 API 서버가 주지 않습니다. 노드마다 자기 몫의 대역을 받고, 그 안에서 네트워크 플러그인의 IPAM 이 파드에 하나씩 떼어 줍니다. 그래서 노드가 늘어나도 주소 관리가 노드 안에서만 끝납니다. kubectl patch node <이름> --type=merge -p '{"spec":{"podCIDR":"...","podCIDRs":["..."]}}' 로 못박고, kubectl get node <이름> -o jsonpath='{.spec.podCIDR}' 로 되읽으세요. 한 번 값이 들어간 뒤에는 다른 값으로 바꿀 수 없습니다.

그 대역으로 파드를 몇 개나 담을 수 있나

/root/k8nd-cni/02-capacity.txt 에 다섯 줄을 적으세요 — subnets=(10.244.0.0/16/24 로 자를 때 나오는 조각 수), addresses_per_subnet=(한 조각의 주소 총 개수), usable_per_subnet=(네트워크 주소와 게이트웨이를 뺀 수), node_pod_limit=(노드 오브젝트가 말하는 status.allocatable.pods 값), binding_limit=(둘 중 먼저 걸리는 쪽을 subnet 또는 node 로).

주소를 세는 일과 파드를 세는 일은 다릅니다. /24 한 조각에는 주소가 256개 들어 있지만 네트워크 주소와 게이트웨이가 각각 하나씩 빠집니다. 그런데 노드가 받아 주는 파드 수는 kubelet 쪽 한도로 따로 정해져 있어서, 보통은 주소가 아니라 그쪽이 먼저 걸립니다. 노드 한도는 kubectl get node lab-node-0 -o jsonpath='{.status.allocatable.pods}' 로 읽습니다.

플러그인 설정을 이어 붙인다

/root/k8nd-cni/10-k8nd.conflist 를 만드세요 — cniVersion1.0.0, namek8nd-pod-network, plugins정확히 셋이고 순서대로 bridge·portmap·bandwidth 입니다. bridgeipamhost-local 이고 subnetlab-node-0 의 파드 대역, portmap 에는 capabilities.portMappings 가 참, bandwidth 에는 capabilities.bandwidth 가 참이어야 합니다.

설정 파일 하나에 플러그인을 여러 개 적는 형식이 conflist 입니다. 앞에서부터 차례로 불리고, 앞 플러그인이 만든 결과를 뒤 플러그인이 받아 손봅니다. 그래서 주소를 붙이는 일과 hostPort 를 여는 일과 대역을 조이는 일이 서로 다른 플러그인으로 갈라져 있습니다. 기본 설정 디렉터리는 /etc/cni/net.d, 바이너리 디렉터리는 /opt/cni/bin 입니다. 다 쓰고 나서 jq . <파일> 로 문법을 확인하세요.

hostPort 와 대역 제한은 누가 하나

/root/k8nd-cni/shaped.yaml 에 파드 매니페스트를 쓰고 적용하세요 — 네임스페이스 k8nd-cni, 이름 shaped, 어노테이션 kubernetes.io/ingress-bandwidth: 1Mkubernetes.io/egress-bandwidth: 1M, 컨테이너 web(nginx:1.27-alpine)에 containerPort 8080·hostPort 8080 입니다. 그리고 /root/k8nd-cni/04-chain.txthostport_plugin=·bandwidth_plugin=·ingress_annotation=·egress_annotation= 네 줄을 적으세요(플러그인은 3단계 conflist 에서 그 일을 맡은 플러그인의 type, 어노테이션은 실제로 단 값).

파드 명세의 hostPort 와 대역 어노테이션은 API 서버가 하는 일이 아닙니다. 둘 다 체인에 이어 붙인 플러그인이 읽어서 실행합니다. 그래서 그 플러그인이 노드에 깔려 있지 않으면 매니페스트는 통과하는데 아무 일도 일어나지 않습니다 — 조용히 무시되는 종류의 고장입니다. 파드는 kubectl apply -f 로 만들고, 어노테이션은 kubectl -n <ns> get pod shaped -o jsonpath='{.metadata.annotations}' 로 되읽으세요.

플러그인이 없으면 노드가 통째로 막힌다

/root/k8nd-cni/edge-node.yaml 에 노드 k8nd-cni-edge 를 쓰고 적용하세요 — spec.unschedulable 을 참으로 두고, spec.taints 에 직접 정한 키 k8nd.example.com/cni-missing(effect NoSchedule) 하나를 답니다. 적용한 뒤 /root/k8nd-cni/05-notready.txtnode=·custom_taint=(내가 단 키)·controller_taint=(컨트롤러가 더 붙인 내장 테인트 키)·unschedulable= 네 줄을 적으세요.

네트워크 플러그인이 설정되지 않은 노드는 파드를 하나도 받지 못합니다. 그 격리를 실제로 만들어 내는 것이 테인트이고, 공식 문서의 내장 테인트 목록에 node.kubernetes.io/network-unavailable(네트워크를 쓸 수 없음)과 node.kubernetes.io/unschedulable 이 나란히 들어 있습니다. 중요한 차이가 하나 있습니다 — 조건에서 파생되는 내장 테인트는 컨트롤러가 관리합니다. 손으로 붙여도 조건이 그 상태가 아니면 곧바로 지워지고, 반대로 spec.unschedulable 을 참으로 두면 컨트롤러가 테인트를 알아서 하나 붙입니다. 그래서 사람이 붙일 수 있는 것은 자기 키를 쓰는 테인트뿐입니다. 목록의 첫 번째가 내가 적은 것이라고 가정하지 말고 kubectl get node <이름> -o json | jq -r '.spec.taints[].key' 로 전부 확인하세요.

노드마다 다른 IPAM 설정을 만든다

/root/k8nd-cni/06-node1.conflist/root/k8nd-cni/06-node2.conflist 를 만드세요. 각각 lab-node-1·lab-node-2 에 못박은 대역을 ipam.subnet 으로 쓰고, ipam.gateway 는 그 대역의 첫 번째 사용 가능 주소이며, ipam.routes 에는 0.0.0.0/0 과 클러스터 파드 대역 10.244.0.0/16 둘 다 들어가야 합니다. ipam.typehost-local 입니다.

host-local 은 이름 그대로 그 노드 안에서만 주소를 관리합니다. 노드끼리 서로 무엇을 나눠 줬는지 묻지 않습니다 — 그래서 대역을 겹치게 주면 두 노드가 같은 주소를 태연히 내줍니다. 겹치지 않게 자르는 일은 사람이나 컨트롤 플레인의 몫입니다. 클러스터 대역으로 가는 경로를 따로 적는 이유는, 기본 경로로 내보내면 노드 밖으로 나가 버리는 파드 사이 트래픽을 노드 안에서 처리하기 위해서입니다.

겹치면 어디로 갈지 정해지지 않는다

네 가지 대역 배치를 판정하세요 — p1 은 파드 10.244.0.0/16·서비스 10.96.0.0/16·노드 192.168.10.0/24, p2 는 파드 10.96.0.0/12·서비스 10.96.0.0/16·노드 192.168.10.0/24, p3 은 파드 10.244.0.0/16·서비스 10.245.0.0/16·노드 10.244.3.0/24, p4 는 파드 172.16.0.0/16·서비스 10.96.0.0/16·노드 192.168.10.0/24 입니다. /root/k8nd-cni/07-overlap.txtp1= 부터 p4= 까지 네 줄(값은 ok 또는 overlap)과 rule=pod,service,node 한 줄, 모두 다섯 줄을 적으세요.

쿠버네티스 공식 문서는 파드·서비스·노드 세 종류의 주소가 서로 겹치지 않아야 한다고 못박습니다. 겹치면 어느 쪽 규칙이 먼저 걸리는지가 구현에 따라 달라지고, 그 차이는 '어떤 노드에서만 안 된다' 는 형태로 나타납니다. 판정은 눈으로 하지 말고 계산하세요 — python3 -c "import ipaddress; print(ipaddress.ip_network('10.96.0.0/12').overlaps(ipaddress.ip_network('10.96.0.0/16')))" 한 줄이면 됩니다. 프리픽스가 짧을수록 넓은 대역이라는 점을 놓치기 쉽습니다.

파드 네트워크 설계 메모를 남긴다

/root/k8nd-cni/08-report.mdcluster_pod_cidr=·per_node_prefix=·nodes_addressable=(그 프리픽스로 담을 수 있는 노드 수)·notready_node=(5단계에서 만든 노드 이름)·chained_plugins=(3단계 conflist 에서 bridge 뒤에 이어 붙인 플러그인들을 쉼표로) 다섯 줄을 적고, 그 아래 - 로 시작하는 줄을 네 줄 이상 적으세요.

값은 기억이 아니라 앞 단계에서 만든 파일에서 가져오세요. 설명 줄에는 다음에 클러스터를 설계할 때 실제로 꺼내 쓸 문장을 적습니다 — '주소를 주는 주체가 누구인가', '무엇이 조용히 무시되는가' 같은 것들입니다.