07. Week8 쿠버네티스 네트워킹(1) - chuirang/DevOps GitHub Wiki
쿠버네티스 네트워킹 Overview
리눅스 네트워크 feature와 쿠버네티스 파드
- veth 와 container namespace
- Pod 네트워크 (pod to pod, node to pod), Pause container
IaaS 네트워크에서 쿠버네티스 클러스터는 어떻게 구성되는가?
-
flat routed network, overlay
-
온프레미스 CNI 와 퍼블릭클라우드의 CNI의 implementaiton 차이
쿠버네티스 네트워킹을 이야기 하기 위해 두 가지 관점에서 주제를 다뤄보려고 한다.
- IaaS 네트워크에서 쿠버네티스 클러스터는 어떻게 구성되는가?
- 쿠버네티스 클러스터 내부의 파드는 어떻게 통신하는가?
그리고 쿠버네티스 내부의 통신에서 애플리케이션 관점의 주제는 '쿠버네티스 네트워킹 (2)' 에서 다시 이야기 하고자 한다.
- 쿠버네티스는 어떻게 서비스를 노출 시키는가?
- 쿠버네티스의 트래픽 라우팅에 영향을 주는 기타 방법 (Ingress, Service Mesh)
컨테이너 네트워크를 이해하기 위해서는 리눅스 네트워크 feature 중 몇 가지 개념을 알 필요가 있다.
- Linux namespace (특히 network namespace): 리눅스의 네임스페이스를 통해 컨테이너가 실행된 환경을 격리 시킬 수 있다. 특히 network namespace를 격리하여 OS의 default namespace와 분리한다.
- eth(virtual ethernet) pair : pair로 존재하며 서로 다른 namespace에 연결되어 한 pair로 들어온 트래픽을 다른 pair로 복사 (양 단을 연결해주는 케이블/파이프와 같은 역할)
- bridge: 리눅스 브리지는 가상 스위치의 개념으로 연결된 인터페이스들 간의 패킷을 포워딩 해주는 역할
출처: 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 라고 이야기한 바 있다.
도커로 컨테이너를 실행한 경우는 아래와 같은 형태로 실행이 된다.
이 형태의 도커 네트워크에서 컨테이너의 외부 통신은 아래와 같이 이루어진다.
- 컨테이너가 실행하는 포트를 호스트의 포트와 바인딩하여 외부에서 호출을 받는다.
- 컨테이너에서 다른 호스트의 컨테이너로 통신할 때 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 컨테이너를 통해 이루어진다.
출처: [도서] Kubernetes in Action
# 컨트롤 플레인 노드에서 실행
$ 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 플러그인에 의해 제공된다.
출처: [도서] Kubernetes in Action
앞서 살펴본 리눅스 네트워크 기능과 쿠버네티스의 파드를 모두 한 곳에 모아보면 이런 형태가 된다.
파드가 통신하기 위해 어떤 형태로든 물리 네트워크와 연결이 되어야 하는데, 노드의 IP 대역과 파드의 IP대역이 다르기 때문에 동일한 네트워크 이거나 혹은 라우팅이 적절하게 이뤄지지 않으면 통신이 이뤄지지 않는다. 이를위해 SDN(Software Defined Network) 형식의 Overlay 네트워크를 사용하면 하부 물리 네트워크 구성과 관련없이 파드 간의 연결성을 얻을 수 있다.
이를 다음 절에서 살펴본다.
살펴본 리눅스 네트워크 기능을 바탕으로 IaaS N/W와 같이 그림을 그려보면 아래와 같은 구성이 된다.
쿠버네티스는 앞서 설명한 네트워크 모델에 따라 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
그리고 직접 네트워크를 컨트롤 하는 권한은 두 가지 속성으로 이뤄진다.
- calico-node는 호스트 네트워크에 직접 실행된다.
hostNetwork: true
- 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$
앞서 설명한 그림에서 알 수 있듯이, 쿠버네티스 클러스터를 구성할 때 IaaS N/W를 제어할 수 있느냐의 차이가 바로 온프레미스와 퍼블릭 클라우드에서 제공하는 CNI의 차이가 아닐까 싶다.
보통 온레미스에서 구성하는 쿠버네티스 클러스터는 하위 인프라스트럭처와 독립적으로 관리되고, 하나의 virtual network 에서 가상머신의 형태로 구성된다. 쿠버네티스 구성을 위해 가상머신이 생성되고, 쿠버네티스 클러스터에서 파드가 사용하는 네트워크가 외부 네트워크에서 직접적으로 인식되지 않는 형태이다.
flannel이나 weave-net 와 같은 CNI에서는 overlay 네트워크 사용하며 그 구성을 간략하게 설명한다. overlay에서 사용하는 VXLAN의 개요는 아래와 같다.
출처: 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에 대해서 접근제어를 허용시켜줘야 하는 문제가 있다.
퍼블릭 클라우드의 Managed Kubernetes 의 네트워크 아키텍처는 이를 간소화한다.
퍼블릭 클라우드는 쿠버네티스와 네트워크를 한번에 컨트롤 하는 방식으로 오버레이 네트워크가 가지는 layer를 줄여서 latency를 줄이고, Pod-to-External 통신을 간소화 시킨다.
아래는 Azure의 AKS(Azrue Kubernetes Service)에서 제공하는 Azure CNI의 예시이다.
출처: https://docs.microsoft.com/en-us/azure/aks/concepts-network
그림만 봐서는 큰 차이가 없지만 Azure CNI는 파드가 Azure의 Virtual Network와 동일한 IP 대역을 가질 수 있다. 여기서 가질 수 있는 장점이 앞서 파드가 직접 외부 자원을 호출할 때 발생하는 NAT 문제를 회피 할 수 있다는 것이다.
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
<생략>