07. Week8 쿠버네티스 네트워킹(1) - chuirang/DevOps GitHub Wiki

쿠버네티스 네트워킹(1)

목차

쿠버네티스 네트워킹 Overview

리눅스 네트워크 feature와 쿠버네티스 파드

  • veth 와 container namespace
  • Pod 네트워크 (pod to pod, node to pod), Pause container

IaaS 네트워크에서 쿠버네티스 클러스터는 어떻게 구성되는가?

  • flat routed network, overlay

  • 온프레미스 CNI 와 퍼블릭클라우드의 CNI의 implementaiton 차이

1. 쿠버네티스 네트워킹 Overview

쿠버네티스 네트워킹을 이야기 하기 위해 두 가지 관점에서 주제를 다뤄보려고 한다.

  1. IaaS 네트워크에서 쿠버네티스 클러스터는 어떻게 구성되는가?
  2. 쿠버네티스 클러스터 내부의 파드는 어떻게 통신하는가?

그리고 쿠버네티스 내부의 통신에서 애플리케이션 관점의 주제는 '쿠버네티스 네트워킹 (2)' 에서 다시 이야기 하고자 한다.

  1. 쿠버네티스는 어떻게 서비스를 노출 시키는가?
  2. 쿠버네티스의 트래픽 라우팅에 영향을 주는 기타 방법 (Ingress, Service Mesh)

2. 리눅스 네트워크 feature와 쿠버네티스 파드

컨테이너 네트워크를 이해하기 위해서는 리눅스 네트워크 feature 중 몇 가지 개념을 알 필요가 있다.

  • Linux namespace (특히 network namespace): 리눅스의 네임스페이스를 통해 컨테이너가 실행된 환경을 격리 시킬 수 있다. 특히 network namespace를 격리하여 OS의 default namespace와 분리한다.
  • eth(virtual ethernet) pair : pair로 존재하며 서로 다른 namespace에 연결되어 한 pair로 들어온 트래픽을 다른 pair로 복사 (양 단을 연결해주는 케이블/파이프와 같은 역할)
  • bridge: 리눅스 브리지는 가상 스위치의 개념으로 연결된 인터페이스들 간의 패킷을 포워딩 해주는 역할

https://user-images.githubusercontent.com/20136723/133547984-a00d2aee-26c3-4ca6-b092-cef3bd7d0527.png

출처: https://platform9.com/blog/container-namespaces-deep-dive-container-networking/

*다양한 용어에 대한 설명: https://developers.redhat.com/blog/2018/10/22/introduction-to-linux-interfaces-for-virtual-networking#macvlan

(실습) 컨테이너 네트워크 확인해보기

아래 실습을 수행한다. (동일한 장비에서 실습하는 경우 중복된 name을 사용하지 않도록 box1, box2 등과 같이 임의의 숫자를 사용)

참고: ip netns 명령으로 도커로 실행된 컨테이너들의 네트워크 네임스페이스를 확인을 할 수 없는 이유? Docker는 기본적으로 컨테이너의 network 네임스페이스를 runtime data에 추가하지 않는다(/run의 tmpfs로 마운트 되는 /var/run을 의미함). 그렇기 때문에 실행된 컨테이너의 pid로 심볼릭링크를 만들어서 네트워크 네임스페이스를 확인 할 수 있다.

$ docker run --name box -it busybox

$ pid="$(docker inspect -f '{{.State.Pid}}' "box")"
$ echo $pid
2620

## 만약 docker inspect로 pid를 가져오는 방법이 되지 않는 경우는 아래 명령으로 pid를 가져온다
$ docker container ls
CONTAINER ID       IMAGE               COMMAND             CREATED             STATUS             PORTS               NAMES
4792b3988d40       busybox             "sh"                29 minutes ago     Up 29 minutes                           box

$ docker container top 4792
UID                 PID                 PPID               C                   STIME               TTY                 TIME               CMD
root                2033                2017                0                   04:15               pts/0               00:00:00            sh
##

$ sudo mkdir -p /var/run/netns
$ sudo ln -s /proc/$pid/ns/net /var/run/netns/box

$ ip netns
box
$ ip netns exec box ip a
$ sudo ip netns exec box ip a
1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
7: eth0@if8: mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::42:acff:fe11:2/64 scope link
valid_lft forever preferred_lft forever

위의 eth0@if8는 veth pair의 한 부분인 네트워크 인터페이스이다. 한 끝 부분은 컨테이너의 네트워크 네임스페이스에 연결되고, 다른 한 끝부분은 docker0 브릿지에 연결된다.

docker0에 연결된 인터페이스를 확인하는 다양한 방법 중의 하나는 network 네임스페이스에 있는 인터페이스의 인덱스를 확인하는 것이다. 이 경우 eth0가 인덱스 7을 가진다(7: eth0@if8).

리눅스는 모든 인터페이스를 배열에 저장하기 때문에 veth pair의 다른 끝 부분은 다음 인덱스를 가진다. 이 인덱스의 배열은 글로벌 변수(system wide)이므로 ip a | grep "8: veth" 를 해보면 veth pair의 다른 한 쪽을 확인 할 수 있다.

다음과 같이 brctl 명령을 실행해 어떤 브리지에 속해있는지 확인 할 수 있다.

$ ip a | grep "8: veth"
8: vethb20d133@if7: mtu 1500 qdisc noqueue master docker0 state UP group default

$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024258596b8d no vethb20d133
virbr0 8000.000000000000 yes

veth or eth XXXX@ifZ 라고 작성된 인터페이스 명을 보면 숫자 Z는 반대편 peer의 인터페이스 인덱스이다.

브릿지와 브릿지에 파이프로 연결된 한 쪽의 veth로 흘러들어온 패킷은 컨테이너의 네트워크 네임스페이스에 연결된 veth 로 보여진다. 컨테이너의 네트워크 스택은 네트워크 네임스페이스 자체이므로 데이터도 컨테이너 내부의 네트워크 인터페이스에 보인다. 패킷은 tcpdump 나 pcap 툴로 veth pair의 어느 쪽이든 디버깅 용으로 캡쳐 할 수 있다.

파드 간 통신의 tcp dump도 이와 유사하게 veth를 확인해 수행할 수 있다. 이 부분은 이후에 다시 확인해보도록 한다.

도커 컨테이너의 네트워크 제약사항

앞서 컨테이너와 도커에 대해 이야기하며 도커 자체가 가진 제약사항을 단순한 네트워크 Feature 라고 이야기한 바 있다.

도커로 컨테이너를 실행한 경우는 아래와 같은 형태로 실행이 된다.

https://user-images.githubusercontent.com/20136723/133548005-1f0c2daa-d027-479d-8ce5-f9c6a0e94f95.png

출처: https://medium.com/finda-tech/kubernetes-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%A0%95%EB%A6%AC-fccd4fd0ae6

이 형태의 도커 네트워크에서 컨테이너의 외부 통신은 아래와 같이 이루어진다.

  • 컨테이너가 실행하는 포트를 호스트의 포트와 바인딩하여 외부에서 호출을 받는다.
  • 컨테이너에서 다른 호스트의 컨테이너로 통신할 때 NAT가 수행된다.

이 경우 여러 호스트에서 컨테이너와 컨테이너가 통신을 하기에는 포트 바인딩과 NAT로 통신이 복잡해 진다.

쿠버네티스 파드

애초 쿠버네티스가 해결하려는 네트워크 문제는 아래와 같다.

  • Every Pod gets it own IP address

  • pods on a node can communicate with all pods on all nodes without NAT

  • agents on a node (e.g. system daemons, kubelet) can communicate with all pods on that node

  • Pods in the host network of a node can communicate with all pods on all nodes without NAT

쿠버네티스는 이를 파드와 CNI(Container Network Interface)를 통하여 구현한다.

파드는 하나 혹은 그 이상의 컨테이너를 실행하는 쿠버네티스의 빌딩 블록이다.

쿠버네티스는 아래와 같은 이유로 직접 컨테이너를 사용하지 않고, 파드라는 개념을 사용한다.

  • 여러 프로세스로 구성되어 항상 같은 노드에서 실행해야하는 애플리케이션을 파드로 묶을 수 있다.
  • 단, 파드는 실제로는 단일 프로세스를 실행하는 목적으로 설계되었다. 다만 이를 파드 형태로 만든 이유는 개별 프로세스가 실패하는 경우 새로운 IP 등이 아닌 기존의 인프라스트럭처에서 재시작하는 매커니즘을 포함하기 위해 이런 구성이 효과적이다.
  • 하나의 컨테이너에서 여러 프로세스를 실행할 수 있지만 여러 프로세스가 실행될 때 동일한 표준 출력으로 로그를 기록하면 어떤 프로세스가 남긴 것인지 확인하기 어렵기 때문에, 개별 컨테이너로 실행하는 것이 권장된다.

이러한 파드의 네트워크 구성은 파드 인프라스트럭처 컨테이너라고 불리는 pause 컨테이너를 통해 이루어진다. https://user-images.githubusercontent.com/20136723/133548025-108ed9c7-bad3-453f-9ffc-3e9782e29c99.png

출처: [도서] Kubernetes in Action

(실습) Pause 컨테이너 확인하기

# 컨트롤 플레인 노드에서 실행
$ kubectl run nginx1234 --image=nginx
$ kubectl get po -owide # 어떤 노드에 실행 중인지 확인

# 해당 노드로 접속
$ docker ps |grep nginx1234 # 해당 pod 이름으로 두개의 container가 실행되고, 그중 pause가 먼서 실행된 것을 알 수 있다.
ubuntu@u2004-third:~$ sudo docker ps |grep nginx1234
9e102765d083   nginx                     "/docker-entrypoint.…"   About a minute ago   Up About a minute             k8s_nginx1234_niginx_wonkileelee_4d1eb2e7-0dfe-4347-9f28-9ecfdfbb747a_0
3dd082d89069   k8s.gcr.io/pause:3.2      "/pause"                 2 minutes ago        Up 2 minutes                  k8s_POD_nginx1234_wonkileelee_4d1eb2e7-0dfe-4347-9f28-9ecfdfbb747a_0

# 컨트롤 플레인 노드에서 테스트 pod를 삭제한다.
$ kubectl delete po niginx

보조자료를 통해 veth 인터페이스에 대한 tcpdump를 확인해본다. (시간이 되면)

파드는 각 고유한 IP를 가지고, 모든 파드는 NAT 없이 flat 네트워크로 서로 통신이 가능해야한다. 이러한 구성은 실제로 쿠버네티스가 하지 않고, CNI 플러그인에 의해 제공된다.

https://user-images.githubusercontent.com/20136723/133548050-4c0d66a2-b562-412b-84a3-4133cfbb11e2.png

출처: [도서] Kubernetes in Action

앞서 살펴본 리눅스 네트워크 기능과 쿠버네티스의 파드를 모두 한 곳에 모아보면 이런 형태가 된다.

https://user-images.githubusercontent.com/20136723/133548065-cfc8ed47-582c-4b84-8b5c-0af3fb3a91f6.png

파드가 통신하기 위해 어떤 형태로든 물리 네트워크와 연결이 되어야 하는데, 노드의 IP 대역과 파드의 IP대역이 다르기 때문에 동일한 네트워크 이거나 혹은 라우팅이 적절하게 이뤄지지 않으면 통신이 이뤄지지 않는다. 이를위해 SDN(Software Defined Network) 형식의 Overlay 네트워크를 사용하면 하부 물리 네트워크 구성과 관련없이 파드 간의 연결성을 얻을 수 있다.

이를 다음 절에서 살펴본다.

3. IaaS 네트워크에서 쿠버네티스 클러스터는 어떻게 구성되는가?

살펴본 리눅스 네트워크 기능을 바탕으로 IaaS N/W와 같이 그림을 그려보면 아래와 같은 구성이 된다.

https://user-images.githubusercontent.com/20136723/133548106-a8d39f73-dcbc-4ed7-a96a-f1d22d04742d.png

CNI (Container Network Interface) Plug in

쿠버네티스는 앞서 설명한 네트워크 모델에 따라 CNI가 구현해야할 일들을 정의하고, CNI에서 아래와 같이 쿠버네티스 네트워크를 implementation 한다.

CNI는 쿠버네티스의 Virtual Netowrk를 구성하는 역할을 한다.

  • virtual device 생성 (bridge, router, tunnel 등)
  • virtual device network 설정

또한 Pod interface를 생성하고 IP, subnet, routing table을 설정한다.

  • veth pair 를 만들어 pod와 virtual network device를 연결
  • L2/L3 또는 overlay 통신과 같은 정책에 따라 pod routing table 설정

이때, 클러스터 관리자는 클러스터의 사용 요건에 맞게 CNI를 선택할 수 있다. On-Premise 에서는 flannel, weave-net, calico 등을 사용할 수 있다.

CNI의 역할은 리눅스 네트워크를 직접 컨트롤 해야 한다는 것을 알 수 있다. calico를 예를 들어 어떻게 이런 권한을 얻게 되는지 살펴본다.

먼저 kube-system 에서 calico로 검색해보면 calico-kube-controllers 가 있고, 한편으로 각 노드에 calico-node가 실행되는 것을 알 수 있다. 알다시피 전체 노드에 실행되야하는 구성요소는 DaemonSet 형태로 실행한다.

user@u2004-master:~/07_sechduling$ kubectl get po -n kube-system -owide |grep calico
calico-kube-controllers-7f4f5bf95d-bgldn   1/1     Running   3          78d   192.168.44.204    u2004-master   <none>           <none>
calico-node-5mfqr                          1/1     Running   2          78d   172.16.3.142      u2004-second   <none>           <none>
calico-node-g5fv4                          1/1     Running   2          77d   172.16.3.143      u2004-third    <none>           <none>
calico-node-vl5gn                          1/1     Running   3          78d   172.16.3.141      u2004-master   <none>           <none>
calico-node-zsdch                          1/1     Running   2          61d   172.16.3.144      u2004-fourth   <none>           <none>

user@u2004-master:~/07_sechduling$ kubectl get ds -n kube-system
NAME           DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
calico-node    4         4         4       4            4           kubernetes.io/os=linux   78d
csi-nfs-node   4         4         4       4            4           kubernetes.io/os=linux   77d
kube-proxy     4         4         4       4            4           kubernetes.io/os=linux   78d

그리고 직접 네트워크를 컨트롤 하는 권한은 두 가지 속성으로 이뤄진다.

  1. calico-node는 호스트 네트워크에 직접 실행된다.

hostNetwork: true

  1. calico-node는 privileged 컨테이너로 실행되어 노드의 네트워크를 직접 구성할 수 있다.

securityContext: privileged: true

user@u2004-master:~/07_sechduling$ kubectl get po calico-node-5mfqr -n kube-system -oyaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2021-06-29T23:13:11Z"
  generateName: calico-node-
  labels:
    controller-revision-hash: 7dc9d677bf
    k8s-app: calico-node
    pod-template-generation: "1"
<생략>
    manager: kubelet
    operation: Update
    time: "2021-08-07T06:42:33Z"
  name: calico-node-5mfqr
  namespace: kube-system
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: DaemonSet               <-------------------
    name: calico-node
    uid: 1bcb8a31-6bde-4a63-8027-3db8d554eae6
  resourceVersion: "6220739"
  uid: 48b927b8-30c0-4172-9464-86d58e9768fc
spec:
<생략>
  containers:
  - env:
    - name: DATASTORE_TYPE
      value: kubernetes
    - name: WAIT_FOR_DATASTORE
      value: "true"
    - name: NODENAME
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: spec.nodeName
    - name: CALICO_NETWORKING_BACKEND
      valueFrom:
        configMapKeyRef:
          key: calico_backend
          name: calico-config
    - name: CLUSTER_TYPE
      value: k8s,bgp
    - name: IP
      value: autodetect
    - name: CALICO_IPV4POOL_IPIP
      value: Always
    - name: CALICO_IPV4POOL_VXLAN
      value: Never
    - name: FELIX_IPINIPMTU
      valueFrom:
        configMapKeyRef:
          key: veth_mtu
          name: calico-config
    - name: FELIX_VXLANMTU
      valueFrom:
        configMapKeyRef:
          key: veth_mtu
          name: calico-config
    - name: FELIX_WIREGUARDMTU
      valueFrom:
        configMapKeyRef:
          key: veth_mtu
          name: calico-config
    - name: CALICO_DISABLE_FILE_LOGGING
      value: "true"
    - name: FELIX_DEFAULTENDPOINTTOHOSTACTION
      value: ACCEPT
    - name: FELIX_IPV6SUPPORT
      value: "false"
    - name: FELIX_HEALTHENABLED
      value: "true"
    envFrom:
    - configMapRef:
        name: kubernetes-services-endpoint
        optional: true
    image: docker.io/calico/node:v3.19.1
    imagePullPolicy: IfNotPresent
    livenessProbe:
      exec:
        command:
        - /bin/calico-node
        - -felix-live
        - -bird-live
      failureThreshold: 6
      initialDelaySeconds: 10
      periodSeconds: 10
      successThreshold: 1
      timeoutSeconds: 1
    name: calico-node
    readinessProbe:
      exec:
        command:
        - /bin/calico-node
        - -felix-ready
        - -bird-ready
      failureThreshold: 3
      periodSeconds: 10
      successThreshold: 1
      timeoutSeconds: 1
    resources:
      requests:
        cpu: 250m
    securityContext:               <-------------------
      privileged: true
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /lib/modules
      name: lib-modules
      readOnly: true
    - mountPath: /run/xtables.lock
      name: xtables-lock
    - mountPath: /var/run/calico
      name: var-run-calico
    - mountPath: /var/lib/calico
      name: var-lib-calico
    - mountPath: /var/run/nodeagent
      name: policysync
    - mountPath: /sys/fs/
      mountPropagation: Bidirectional
      name: sysfs
    - mountPath: /var/log/calico/cni
      name: cni-log-dir
      readOnly: true
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: calico-node-token-59ms7
      readOnly: true
  dnsPolicy: ClusterFirst
  enableServiceLinks: true
  hostNetwork: true               <-------------------
<생략>
    image: docker.io/calico/cni:v3.19.1
    imagePullPolicy: IfNotPresent
    name: upgrade-ipam
    resources: {}
    securityContext:
      privileged: true
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/lib/cni/networks
      name: host-local-net-dir
    - mountPath: /host/opt/cni/bin
      name: cni-bin-dir
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: calico-node-token-59ms7
      readOnly: true
  - command:
    - /opt/cni/bin/install
    env:
    - name: CNI_CONF_NAME
      value: 10-calico.conflist
    - name: CNI_NETWORK_CONFIG
      valueFrom:
        configMapKeyRef:
          key: cni_network_config
          name: calico-config
    - name: KUBERNETES_NODE_NAME
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: spec.nodeName
    - name: CNI_MTU
      valueFrom:
        configMapKeyRef:
          key: veth_mtu
          name: calico-config
    - name: SLEEP
      value: "false"
    envFrom:
    - configMapRef:
        name: kubernetes-services-endpoint
        optional: true
    image: docker.io/calico/cni:v3.19.1
    imagePullPolicy: IfNotPresent
    name: install-cni
    resources: {}
    securityContext:
      privileged: true
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /host/opt/cni/bin
      name: cni-bin-dir
    - mountPath: /host/etc/cni/net.d
      name: cni-net-dir
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: calico-node-token-59ms7
      readOnly: true
  - image: docker.io/calico/pod2daemon-flexvol:v3.19.1
    imagePullPolicy: IfNotPresent
    name: flexvol-driver
    resources: {}
    securityContext:
      privileged: true
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /host/driver
      name: flexvol-driver-host
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: calico-node-token-59ms7
      readOnly: true
  nodeName: u2004-second
  nodeSelector:
    kubernetes.io/os: linux
  preemptionPolicy: PreemptLowerPriority
  priority: 2000001000
  priorityClassName: system-node-critical
  restartPolicy: Always
  schedulerName: default-scheduler
  securityContext: {}
  serviceAccount: calico-node
  serviceAccountName: calico-node
  terminationGracePeriodSeconds: 0
  tolerations:               <-------------------
  - effect: NoSchedule
    operator: Exists
  - key: CriticalAddonsOnly
    operator: Exists
  - effect: NoExecute
    operator: Exists
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
  - effect: NoSchedule
    key: node.kubernetes.io/disk-pressure
    operator: Exists
  - effect: NoSchedule
    key: node.kubernetes.io/memory-pressure
    operator: Exists
  - effect: NoSchedule
    key: node.kubernetes.io/pid-pressure
    operator: Exists
  - effect: NoSchedule
    key: node.kubernetes.io/unschedulable
    operator: Exists
  - effect: NoSchedule
    key: node.kubernetes.io/network-unavailable
    operator: Exists
  volumes:
<생략>
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2021-08-07T06:42:34Z"
    status: "True"
    type: Initialized
  - lastProbeTime: null
    lastTransitionTime: "2021-08-07T06:42:47Z"
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2021-08-07T06:42:47Z"
    status: "True"
    type: ContainersReady
  - lastProbeTime: null
    lastTransitionTime: "2021-06-29T23:13:11Z"
    status: "True"
    type: PodScheduled
  containerStatuses:
  - containerID: docker://768328cff207a2a9d5156d3c109350d728f96a823187a24f893a2518e91e7828
    image: calico/node:v3.19.1
    imageID: docker-pullable://calico/node@sha256:bc4a631d553b38fdc169ea4cb8027fa894a656e80d68d513359a4b9d46836b55
    lastState:
      terminated:
        containerID: docker://e3ac89e1b2073c3f399af27d66b6b6570b3fb294579a1107a507cad7cee31bb4
        exitCode: 0
        finishedAt: "2021-08-07T06:34:01Z"
        reason: Completed
        startedAt: "2021-07-08T15:23:19Z"
    name: calico-node
    ready: true
    restartCount: 2
    started: true
    state:
      running:
        startedAt: "2021-08-07T06:42:38Z"
  hostIP: 172.16.3.142
  initContainerStatuses:
  - containerID: docker://7e7f31d7521f2e7dbfc310ae54372dbfc1c7f3d74578f86daedfb71892039a8c
    image: calico/cni:v3.19.1
    imageID: docker-pullable://calico/cni@sha256:f301171be0add870152483fcce71b28cafb8e910f61ff003032e9b1053b062c4
    lastState: {}
    name: upgrade-ipam
    ready: true
    restartCount: 2
    state:
      terminated:
        containerID: docker://7e7f31d7521f2e7dbfc310ae54372dbfc1c7f3d74578f86daedfb71892039a8c
        exitCode: 0
        finishedAt: "2021-08-07T06:34:28Z"
        reason: Completed
        startedAt: "2021-08-07T06:34:28Z"
  - containerID: docker://0d675719933c0d9a4a09bf8a1b10ad869ab5145cfcaaec48e7919138a0e9a891
    image: calico/cni:v3.19.1
    imageID: docker-pullable://calico/cni@sha256:f301171be0add870152483fcce71b28cafb8e910f61ff003032e9b1053b062c4
    lastState: {}
    name: install-cni
    ready: true
    restartCount: 0
    state:
      terminated:
        containerID: docker://0d675719933c0d9a4a09bf8a1b10ad869ab5145cfcaaec48e7919138a0e9a891
        exitCode: 0
        finishedAt: "2021-08-07T06:34:32Z"
        reason: Completed
        startedAt: "2021-08-07T06:34:29Z"
  - containerID: docker://76ed32f3e9eea04aa9dbfda762af9a0300a5ca6aae2b9e2667c482fd12383fb5
    image: calico/pod2daemon-flexvol:v3.19.1
    imageID: docker-pullable://calico/pod2daemon-flexvol@sha256:bcac7dc4f1301b062d91a177a52d13716907636975c44131fb8350e7f851c944
    lastState: {}
    name: flexvol-driver
    ready: true
    restartCount: 0
    state:
      terminated:
        containerID: docker://76ed32f3e9eea04aa9dbfda762af9a0300a5ca6aae2b9e2667c482fd12383fb5
        exitCode: 0
        finishedAt: "2021-08-07T06:42:35Z"
        reason: Completed
        startedAt: "2021-08-07T06:42:34Z"
  phase: Running
  podIP: 172.16.3.142               <-------------------
  podIPs:
  - ip: 172.16.3.142
  qosClass: Burstable
  startTime: "2021-06-29T23:13:11Z"
user@u2004-master:~/07_sechduling$

온프레미스와 퍼블릭클라우드의 CNI의 implementaiton 차이

앞서 설명한 그림에서 알 수 있듯이, 쿠버네티스 클러스터를 구성할 때 IaaS N/W를 제어할 수 있느냐의 차이가 바로 온프레미스와 퍼블릭 클라우드에서 제공하는 CNI의 차이가 아닐까 싶다.

온프레미스 쿠버네티스의 CNI

보통 온레미스에서 구성하는 쿠버네티스 클러스터는 하위 인프라스트럭처와 독립적으로 관리되고, 하나의 virtual network 에서 가상머신의 형태로 구성된다. 쿠버네티스 구성을 위해 가상머신이 생성되고, 쿠버네티스 클러스터에서 파드가 사용하는 네트워크가 외부 네트워크에서 직접적으로 인식되지 않는 형태이다.

flannel이나 weave-net 와 같은 CNI에서는 overlay 네트워크 사용하며 그 구성을 간략하게 설명한다. overlay에서 사용하는 VXLAN의 개요는 아래와 같다.

https://user-images.githubusercontent.com/20136723/133548138-d3fd3a5f-cc04-4444-bf42-07ed24de637d.png

출처: https://ssup2.github.io/theory_analysis/Overlay_Network_VXLAN/

VXLAN (Virtual Extensible LAN)은 Overlay Netowrk 구축을 위한 Network Protocol 중 하나이다. VXLAN은 Tunneling을 기반으로 하는 기법이다. 가상 Network안에서 발생한 Packet은 Encapsulation되어 물리 Network를 통과하고 다시 Decapsulation되어 가상 Network로 전달된다. 이러한 Packet의 Encapsulation/Decapsulation이 발생하는 지점을 VXLAN에서는 **VTEP(VXLAN Tunnel End Point)**이라고 한다. VTEP은 가상 Software 장치가 될 수도 있고, VXLAN을 지원하는 물리 장치가 될 수도 있다. 쿠버네티스에서 각 노드는 CNI가 제공하는 Software VTEP를 이용하고 있고, 베어메탈 서버의 경우 물리 VTEP을 이용하고 있다.

Encapsulation된 Packet은 VXLAN Header에 있는 **VNI(VXLAN ID)**를 통해서 어느 가상 Network의 Packet인지 구분되고 격리된다. 따라서 VXI 하나당 하나의 가상 Network를 의미한다. VNI는 VLAN의 VLAN ID와 동일한 역할을 수행한다고 할 수 있다.

사내에서 활용하는 weave-net 에 대해 보조자료를 활용하여 살펴본다.

→ 보조자료 확인

쿠버네티스 클러스터에서 사용하는 오버레이 네트워크가 복잡한 온프레미스 네트워크에서 사용되는 경우 문제가 발생할 수도 있다.

  • 만약 podCIDR이 실제 고객의 가상 NW와 동일한 네트워크라면?

그리고 파드 네트워크 특성이 NAT 없이 상호 통신이 가능한 점을 들었다. 다만 이는 쿠버네티스 클러스터 내부의 통신에 한한다. 만약 파드가 외부 자원(클러스터 외부에 위치한 DB 등)과의 통신을 해야 한다면 노드의 IP로 NAT가 되어 통신된다.

이때 파드의 특성으로 어느 노드에 파드가 위치하는지 알 수 없으므로, 파드와 통신하는 외부 자원은 모든 노드 IP에 대해서 접근제어를 허용시켜줘야 하는 문제가 있다.

퍼블릭 클라우드 쿠버네티스의 CNI

퍼블릭 클라우드의 Managed Kubernetes 의 네트워크 아키텍처는 이를 간소화한다.

퍼블릭 클라우드는 쿠버네티스와 네트워크를 한번에 컨트롤 하는 방식으로 오버레이 네트워크가 가지는 layer를 줄여서 latency를 줄이고, Pod-to-External 통신을 간소화 시킨다.

아래는 Azure의 AKS(Azrue Kubernetes Service)에서 제공하는 Azure CNI의 예시이다.

https://user-images.githubusercontent.com/20136723/133548154-678318b7-15a4-47f9-8461-34e9f4be1466.png

출처: https://docs.microsoft.com/en-us/azure/aks/concepts-network

그림만 봐서는 큰 차이가 없지만 Azure CNI는 파드가 Azure의 Virtual Network와 동일한 IP 대역을 가질 수 있다. 여기서 가질 수 있는 장점이 앞서 파드가 직접 외부 자원을 호출할 때 발생하는 NAT 문제를 회피 할 수 있다는 것이다.

살펴보기 - AKS

lee@Azure:~$  kubectl run nginx1234 --image=nginx
pod/nginx1234 created
lee@Azure:~$ kubectl get po 
NAME        READY   STATUS    RESTARTS   AGE
nginx1234   1/1     Running   0          8s
lee@Azure:~$ kubectl get po -owide
NAME        READY   STATUS    RESTARTS   AGE   IP            NODE                                NOMINATED NODE   READINESS GATES
nginx1234   1/1     Running   0          12s   10.240.0.37   aks-nodepool1-38786725-vmss000001   <none>           <none>
lee@Azure:~$ kubectl get no -owide
NAME                                STATUS   ROLES   AGE    VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE  KERNEL-VERSION     CONTAINER-RUNTIME
aks-nodepool1-38786725-vmss000000   Ready    agent   4m8s   v1.21.2   10.240.0.4    <none>        Ubuntu 18.04.5 LTS  5.4.0-1056-azure   containerd://1.4.8+azure
aks-nodepool1-38786725-vmss000001   Ready    agent   4m6s   v1.21.2   10.240.0.35   <none>        Ubuntu 18.04.5 LTS  5.4.0-1056-azure   containerd://1.4.8+azure

살펴 보듯이 파드와 노드의 IP 대역이 동일하다.

노드의 IP가 각 10.240.0.4, 10.240.0.35 인데 이는 노드에 할당되는 Pod 개수와 연관이 있다.

lee@Azure:~$ kubectl describe no aks-nodepool1-38786725-vmss000000
Name:               aks-nodepool1-38786725-vmss000000
Roles:              agent
Labels:             agentpool=nodepool1
<생략>
Capacity:
  attachable-volumes-azure-disk:  8
  cpu:                            2
  ephemeral-storage:              129900528Ki
  hugepages-1Gi:                  0
  hugepages-2Mi:                  0
  memory:                         7120612Ki
  pods:                           30
<생략>
⚠️ **GitHub.com Fallback** ⚠️