1. 개요

CNI(Container Network Interface)는 컨테이너가 만들어질 때 네트워크를 붙이는 방법을 정한 규격이다. 컨테이너 런타임이 플러그인 실행 파일을 부르고, 플러그인이 컨테이너의 네트워크 네임스페이스(netns) 안에 인터페이스를 만들고 IP 와 경로를 넣는다.

CNI 가 정하는 것은 둘 사이의 약속뿐이다. 어떤 명령을 어떻게 넘기고, 결과를 어떤 모양으로 돌려받는지. 네트워크를 실제로 어떻게 꾸밀지는 플러그인마다 다르다. 쿠버네티스는 이 약속을 지키는 플러그인이면 무엇이든 쓸 수 있다.

런타임containerd · CRI-O플러그인/opt/cni/binPod 의 netnseth0 이 생긴다123① 런타임이 플러그인을 실행한다환경 변수: 명령 · netns 경로 · eth0stdin: 설정 JSON② 플러그인이 netns 안에eth0 을 만들고 IP·경로를 넣는다③ stdout 으로 결과 JSON받은 IP · 인터페이스 · 경로CNI 가 정한 것은 이 약속뿐안을 어떻게 채울지는 플러그인 몫런타임은 실행만 하고, 네트워크를 실제로 꾸미는 건 플러그인이다

2. 호출 흐름

쿠버네티스에서 CNI 플러그인을 부르는 건 kubelet 이 아니라 컨테이너 런타임(containerd, CRI-O)이다. kubelet 은 CRI 로 “Pod 를 만들라” 고만 전하고, 런타임이 Pod 의 netns 를 만든 뒤 플러그인을 실행한다. Kubernetes 1.24 에서 kubelet 의 CNI 관련 옵션이 빠지면서 이렇게 정리됐다.

kubelet1containerd2pause + netns3CNI 플러그인4IPAM5① kubelet: Pod 를 만들라는 지시CRI 로 런타임에 넘긴다② 컨테이너 런타임③ pause 컨테이너와 netns 를 먼저Pod 의 네트워크 칸막이④ CNI 플러그인 실행 (ADD)veth · eth0 · 경로⑤ IPAM 플러그인빈 IP 하나를 고른다1.24 부터 kubelet 은CNI 를 직접 부르지 않는다위에서 아래로 한 칸씩 넘긴다 — CNI 는 네 번째 칸부터다

Pod 안의 컨테이너들은 이때 만든 netns 하나를 함께 쓴다. 그래서 같은 Pod 의 컨테이너끼리는 localhost 로 통한다.

3. 명령

런타임은 환경 변수 CNI_COMMAND 로 무엇을 할지 알린다.

ADD
Pod 를 네트워크에 붙인다. 인터페이스를 만들고 IP 와 경로를 넣은 뒤 결과를 돌려준다
DEL
Pod 를 떼어 낸다. 만든 인터페이스를 지우고 IP 를 반납한다. ADD 가 실패해도 반드시 불린다
CHECK
지금 상태가 ADD 결과와 같은지 확인한다
STATUS · GC · VERSION
플러그인이 일할 준비가 됐는지, 남은 찌꺼기 정리, 지원하는 규격 버전

명령과 함께 넘어가는 값은 환경 변수와 표준 입력 두 갈래다.

  • 환경 변수: CNI_COMMAND(명령), CNI_CONTAINERID(컨테이너 ID), CNI_NETNS(netns 경로), CNI_IFNAME(만들 인터페이스 이름, 보통 eth0), CNI_PATH(플러그인을 찾을 디렉터리)
  • 표준 입력: 설정 파일에서 읽은 JSON
  • 표준 출력: 결과 JSON (만든 인터페이스, 받은 IP, 경로, DNS)

4. ADD 의 동작

가장 흔한 bridge 방식의 플러그인이 ADD 를 받으면 다음 일을 한다. flannel 도 노드 안에서는 이 방식을 쓴다.

Pod 의 netnseth0 10.244.1.5/24vethcni0 10.244.1.1IPAM노드 (호스트 netns)12345① veth 한 쌍을 만든다두 끝이 이어진 가상 선② 한 끝을 Pod netns 로 옮겨이름을 eth0 으로 바꾼다③ IPAM 이 빈 IP 를 고른다④ Pod 안에 경로를 넣는다default via 10.244.1.1⑤ 다른 끝을 cni0 에 꽂는다끝나면 결과 JSON 을 돌려준다ADD 한 번에 일어나는 일 — 선을 만들고, 한쪽을 Pod 안에 넣고, 주소와 길을 정한다

veth 는 한쪽 끝에 넣은 프레임이 다른 끝으로 나오는 가상 선이다. 한 끝은 Pod 안에서 eth0 이 되고, 다른 끝은 노드의 cni0 브리지에 꽂힌다. cni0 은 같은 노드의 Pod 들을 잇는 가상 스위치다(LAN).

5. 설정 파일

런타임은 /etc/cni/net.d/ 에서 설정 파일을 읽고, /opt/cni/bin/ 에서 type 과 같은 이름의 실행 파일을 찾는다. 설정 파일이 여러 개면 containerd 는 기본적으로 이름순으로 첫 번째 파일 하나만 쓴다. 파일 이름이 10- 같은 숫자로 시작하는 이유다.

/etc/cni/net.d/10-flannel.conflist (flannel 기본 설정):

{
  "name": "cbr0",
  "cniVersion": "0.3.1",
  "plugins": [
    {
      "type": "flannel",
      "delegate": { "hairpinMode": true, "isDefaultGateway": true }
    },
    {
      "type": "portmap",
      "capabilities": { "portMappings": true }
    }
  ]
}

type 이 flannel 이면 /opt/cni/bin/flannel 을, portmap 이면 /opt/cni/bin/portmap 을 차례로 실행한다. flannel 플러그인은 스스로 인터페이스를 만들지 않는다. 노드에 배정된 Pod 서브넷을 읽어 bridge 플러그인과 host-local IPAM 설정을 만든 뒤, 그 둘에게 일을 넘긴다(delegate).

6. 플러그인 종류

인터페이스 플러그인
Pod 안에 인터페이스를 만들고 연결까지 책임진다. bridge, ptp, macvlan, ipvlan, host-device, 그리고 flannel·calico·cilium 의 본체
IPAM 플러그인
주소만 고른다. 인터페이스 플러그인이 대신 불러 쓴다. host-local(파일로 기록), dhcp, static
체인 플러그인
이미 만든 인터페이스에 설정을 덧붙인다. portmap(hostPort), bandwidth(속도 제한), tuning(sysctl)

6.1. 체이닝

설정 파일의 plugins 는 목록이다. 런타임은 ADD 때 목록을 앞에서부터, DEL 때 뒤에서부터 실행한다. 앞 플러그인의 결과는 prevResult 로 다음 플러그인에 넘어간다.

flannel인터페이스 + IPportmaphostPort 규칙bandwidth속도 제한ADD — 앞에서부터DEL — 뒤에서부터파랑: ADD 순서앞 플러그인이 인터페이스와 IP 를만들고, 뒤는 그 위에 덧붙인다초록 상자: prevResult앞의 결과를 다음 플러그인에 넘긴다주황 점선: DEL 순서만든 순서의 반대로 걷어 낸다설정 파일의 plugins 목록이 곧 실행 순서다

7. 대표 구현체

같은 CNI 규격을 지키지만, 노드 사이를 잇는 방법과 추가로 하는 일이 다르다.

Flannel
노드마다 Pod 서브넷을 하나씩 주고, 노드 사이는 VXLAN 으로 잇는다. 가장 단순하다. NetworkPolicy 는 없다
Calico
터널 없이 라우팅으로 잇는 게 기본이다(BGP). 필요하면 VXLAN·IPIP. NetworkPolicy 를 지원한다
Cilium
eBPF 로 커널 안에서 패킷을 처리한다. L7 까지 보는 정책, kube-proxy 대체까지 맡는다

NetworkPolicy(Pod 사이의 방화벽 규칙)는 CNI 규격의 일이 아니라 구현체가 따로 제공하는 기능이다. flannel 처럼 지원하지 않는 구현체에서는 정책을 만들어도 아무 일도 일어나지 않는다.

8. 확인 명령

# 노드에서
ls /etc/cni/net.d/                      # 설정 파일 — 이름순 첫 파일이 쓰인다
ls /opt/cni/bin/                        # 플러그인 실행 파일 (bridge, host-local, portmap, flannel …)
ip -d link show cni0                    # Pod 들을 잇는 브리지
ip link show master cni0                # 브리지에 꽂힌 veth 들 (Pod 수만큼)
ls /var/lib/cni/networks/cbr0/          # host-local IPAM 이 나눠 준 IP 기록
# Pod 안에서
kubectl exec -it <pod> -- ip addr show eth0   # 받은 IP
kubectl exec -it <pod> -- ip route            # default via 10.244.x.1 (cni0)
# 클러스터에서
kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}'   # 노드마다 배정된 Pod 서브넷
kubectl -n kube-system get pods -o wide | grep -Ei 'flannel|calico|cilium'   # 어떤 CNI 가 도는지

9. 관련 문서

  • LAN — 노드 안의 cni0 브리지가 만드는 LAN
  • Subnet — 노드마다 나눠 주는 Pod 대역
  • DHCP — IPAM 의 한 방식. dhcp IPAM 플러그인도 있다
  • BGP — Calico 가 터널 없이 노드 사이를 잇는 방법
  • VXLAN — flannel 이 노드 사이를 잇는 터널
  • veth (예정) — Pod 와 노드를 잇는 가상 선
  • NetworkPolicy (예정) — Calico·Cilium 이 제공하는 Pod 방화벽
  • eBPF (예정) — Cilium 의 패킷 처리 방식

10. 출처

CNI 규격은 containernetworking/cni 의 SPEC.md(현재 1.1.0) 가 정의한다. 런타임과 플러그인의 역할, 실행 파일 호출 방식, 체이닝 순서가 모두 여기 있다.

“The CNI protocol is based on execution of binaries invoked by the container runtime. CNI defines the protocol between the plugin binary and the runtime.”

“The list of plugins is executed in reverse order”

쿠버네티스 문서는 CNI 플러그인 관리가 1.24 부터 kubelet 이 아니라 컨테이너 런타임의 일이라고 명시한다.

“These command-line parameters were removed in Kubernetes 1.24, with management of the CNI no longer in scope for kubelet.”