OpenShift 4 버전에서 Multus를 사용하여 CNI 네트워크를 생성하는 방법에 대해서 정리한다.
Multus의 Bridge CNI는 Linux Bridge를 사용하여 Pod 간 네트워크를 연결하는 방식으로,
Pod들이 공유된 브릿지 인터페이스를 통해 L2 네트워크에서 직접 통신할 수 있도록 지원한다.
즉, Pod to Pod 간 네트워크가 동일한 브릿지를 통해 연결되며, 추가적인 L3 라우팅 없이 직접 통신이 가능하다.
Pod 간 통신을 위해 가상의 브릿지 인터페이스를 추가하여 사용하는 형태이며, 별도의 인터페이스가 필요하지 않다.
Pod는 브릿지 인터페이스(br0)를 통해 연결되며, 해당 인터페이스를 통해 트래픽이 전달된다.
각 Pod는 개별적인 MAC Address를 가지며, DHCP 또는 Static 방식으로 IP를 할당받을 수 있다.
Bridge CNI는 Linux Bridge 기반이므로, 각 Pod의 네트워크 인터페이스는 독립적인 MAC 주소를 유지한다.
-
설명
Pod 간 통신은 공유된 브릿지 인터페이스를 통해 이루어지며, 브로드캐스트를 통해 ARP(Address Resolution Protocol) 기반으로 동작한다.
Pod to Pod 간 직접 통신이 가능하며, 동일한 서브넷 내에서 네트워크 Hop 없이 연결된다. -
장점
Pod 간 직접 MAC 기반 통신 가능하며, 네트워크 Hop 없이 저지연(Low Latency) 통신 가능하다.
브로드캐스트(ARP, DHCP) 트래픽을 지원하며, 기존 L2 네트워크와 유사한 방식으로 동작하여 호환성이 높다.
구현이 간단하며, 기존 Linux Bridge를 활용 가능하고 네트워크 구성을 변경하지 않고 적용 가능하다.
Low latency 환경 및 소규모 환경에 적합하며, 추가적인 L3 라우팅 없이 빠른 통신이 필요할 때 유용하다. -
단점
Pod 개수가 증가하면 브로드캐스트 트래픽(ARP 요청 등)이 많아져 네트워크 대역폭이 불필요하게 소비될 수 있다.
MAC 테이블 크기가 증가할 수 있으며, 브릿지가 학습해야 하는 MAC 주소 개수가 많아지면 성능 저하 가능성 있다.
대규모 환경에서는 브로드캐스트 트래픽이 많아져 네트워크 병목이 발생할 수 있어 적합하지 않다.
- Static IP(Single)
[root@bastion ~]# oc apply -f - <<EOF
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: bridge-single-static-ip
spec:
config: |-
{
"cniVersion": "0.4.0",
"name": "bridge-net",
"type": "bridge",
"bridge": "br0",
"ipam": {
"type": "static",
"addresses": [
{
"address": "172.16.10.2/24",
"gateway": "172.16.0.1"
}
]
}
}
EOF
- Static IP(Multiple)
[root@bastion ~]# oc apply -f - <<EOF
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: bridge-multiple-static-ip
spec:
config: |-
{
"cniVersion": "0.4.0",
"name": "bridge-net",
"type": "bridge",
"bridge": "br0",
"ipam": {
"type": "static",
"addresses": [
{
"address": "172.16.10.2/24",
"gateway": "172.16.0.1"
}
],
"addresses": [
{
"address": "172.16.10.3/24",
"gateway": "172.16.0.1"
}
]
}
}
EOF
Deployment에 적용한다.
[root@bastion ~]# oc scale deployment nginx --replicas=1 -n sample
[root@bastion ~]# oc patch deployment nginx --type='merge' \
-p '{"spec":{"template":{"metadata":{"annotations":{"k8s.v1.cni.cncf.io/networks":"[{\"name\": \"bridge-single-static-ip\"}]"}}}}}'
[root@bastion ~]# oc scale deployment nginx --replicas=2 -n sample
[root@bastion ~]# oc patch deployment nginx --type='merge' \
-p '{"spec":{"template":{"metadata":{"annotations":{"k8s.v1.cni.cncf.io/networks":"[{\"name\": \"bridge-multiple-static-ip\"}]"}}}}}'
- MAC Address 확인
- Host
[root@bastion ~]# cat /sys/class/net/ens36/address
00:50:56:be:6f:9a
- Pod
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 1 | cut -d '/' -f '2')"
[root@bastion ~]# oc exec -n sample -it $POD -- cat /sys/class/net/net1/address
62:5a:87:a8:01:2c
- Pod IP 확인
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 1 | cut -d '/' -f '2')"
[root@bastion ~]# oc exec -n sample -it $POD -- cat /proc/net/fib_trie | grep '172.16.10.2' | sort -u
|-- 172.16.10.2
1.6.2. Bridge 모드(Multiple IP)
- MAC Address 확인
- Host
[root@bastion ~]# cat /sys/class/net/ens36/address
00:50:56:be:6f:9a
- Pod
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | cut -d '/' -f '2')"
[root@bastion ~]# for pods in ${POD}; do oc exec -n sample -it $pods -- cat /sys/class/net/net1/address; done
62:5a:87:a8:01:2c
52:bc:a2:08:09:2f
- Pod IP 확인
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | cut -d '/' -f '2')"
[root@bastion ~]# for pods in ${POD}; do oc exec -n sample -it $pods -- cat /proc/net/fib_trie | grep -E "172\.16\.10\.(2|3)"; done | sort -u
|-- 172.16.10.2
|-- 172.16.10.3
Pod to Pod로 호출한다.
- Pod01
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 2 | cut -d '/' -f '2' | sed -n '1p')"
[root@bastion ~]# oc rsh -n sample $POD
# bash
root@nginx-77d9dc8445-cr7fm:/# for requests in {1..5}; do sleep 1 && curl -I -XGET http://172.16.10.3/; done
- 로그 확인(bastion)
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 2 | cut -d '/' -f '2' | sed -n '2p')"
[root@bastion ~]# oc logs -n sample -f $POD
172.16.10.2 - - [23/Feb/2025:12:36:36 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
172.16.10.2 - - [23/Feb/2025:12:36:37 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
172.16.10.2 - - [23/Feb/2025:12:36:38 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
172.16.10.2 - - [23/Feb/2025:12:36:39 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
172.16.10.2 - - [23/Feb/2025:12:36:40 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
tcpdump를 통해 패킷 덤프를 확인하여 MAC Address가 같은지 확인 한다.
Pod안에서 패킷 덤프를 캡쳐하기 위해서는 특수 권한인 NET_RAW, NET_ADMIN, privileged 권한을 부여한다.
[root@bastion ~]# oc adm policy add-scc-to-user privileged -z default -n sample
[root@bastion ~]# oc patch deployment nginx \
--type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/securityContext", "value": {"capabilities": {"add": ["NET_RAW", "NET_ADMIN"]}, "privileged": true}}]'
Pod to Pod로 호출한다.
- Pod01
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 2 | cut -d '/' -f '2' | sed -n '1p')"
[root@bastion ~]# oc rsh -n sample $POD
# bash
root@nginx-77d9dc8445-cr7fm:/# tcpdump -i net1 -w /tmp/multus-bridge-nginx01-172-16-10-2-to-nginx02-172-16-10-3.pcap
- 서비스 Pod 단
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 2 | cut -d '/' -f '2' | sed -n '2p')"
[root@bastion ~]# oc rsh -n sample -it $POD
# bash
root@nginx-77d9dc8445-22n4p:/# tcpdump -i net1 -w /tmp/multus-bridge-nginx02-172-16-10-3.pcap
Pod to Pod로 호출한다.
- Pod01
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 2 | cut -d '/' -f '2' | sed -n '1p')"
[root@bastion ~]# oc rsh -n sample $POD
# bash
root@nginx-77d9dc8445-cr7fm:/# for requests in {1..5}; do sleep 1 && curl -I -XGET http://172.16.10.3/; done
- Bridge 모드
SRC: 172.16.10.2(62:5a:87:a8:01:2c)
DST: 172.16.10.3(52:bc:a2:08:09:2f)
TCPDUMP - multus-bridge-nginx01-172-16-10-2-to-nginx02-172-16-10-3.pcap
TCPDUMP - multus-bridge-nginx02-172-16-10-3.pcap
물리 인터페이스를 Pod에 직접 연결하여 네트워크 성능을 최적화하는 방식이다.
즉, Pod가 호스트의 특정 네트워크 인터페이스를 직접 사용하여 트래픽을 처리하며,
SR-IOV(Single Root I/O Virtualization)와 같은 기술을 활용하여 네트워크 성능을 극대화할 수 있다.
Pod는 호스트의 물리 네트워크 인터페이스(ens33, ens36 등)를 직접 사용하며, 별도의 가상 인터페이스를 생성하지 않는다.
Linux Bridge, OVN-Kubernetes 등의 가상 네트워크 계층 없이, Pod가 직접 물리 NIC를 사용하므로 네트워크 오버헤드가 줄어든다.
호스트에서 사용되지 않는 NIC(Secondary NIC)를 Pod에 직접 할당하는 방식으로 동작한다.
Host Device CNI는 IP 주소 할당을 직접 수행하지 않으며, 물리 네트워크에서 관리해야 한다.
IP는 DHCP 또는 Static 방식으로 할당 가능하며, IPAM(Whereabouts 등)을 사용할 수도 있다.
Pod는 호스트의 물리 인터페이스에 직접 연결되므로, L2/L3 네트워크를 그대로 활용할 수 있다.
-
설명
Pod가 호스트의 물리 네트워크 인터페이스를 직접 사용하여 네트워크를 처리한다.
Pod to Pod 간 통신 시, 호스트의 네트워크 정책과 물리 네트워크 설정을 따르므로, 기존의 네트워크와 쉽게 통합 가능하다.
Pod 간 통신이 L2 브로드캐스트 기반으로 이루어지지 않으며, 네트워크 스위치에서 MAC 주소를 학습해야 한다. -
장점
고성능 네트워크를 지원하며, Pod가 호스트의 물리 네트워크 인터페이스를 직접 사용하므로, 성능 최적화가 가능하다.
네트워크 오버헤드가 최소화 되어 가상 네트워크 계층(Bridge, OVS 등)이 필요 없으므로, 네트워크 지연(latency)이 감소한다.
L3 라우팅을 통해 네트워크 확장 가능하기 때문에, 기존의 물리 네트워크와 직접 연동할 수 있다.
SR-IOV, DPDK 등과 함께 사용 가능하며, 고속 패킷 처리 환경(High-Performance Computing, NFV 등)에서 활용 가능하다. -
단점
Pod to Pod 간 직접 통신이 어려울 수 있으며, Pod 간 직접 L2 브로드캐스트(ARP, DHCP 등)가 불가능하고, 네트워크 스위치를 통해서 처리 되어야 한다.
NIC 리소스가 제한으로 하나의 물리 NIC를 여러 Pod가 공유할 수 없으며, Pod 수에 따라 NIC 개수가 필요할 수 있다.
Cloud 환경에서 사용 제한으로 AWS, GCP, Azure 등의 Cloud 환경에서는 물리 NIC를 Pod에 직접 할당하는 것이 어렵다.
네트워크 정책 적용 어렵기 때문에, Kubernetes 네이티브 네트워크 정책을 적용할 수 없으며, 기존 네트워크 ACL을 사용해야 한다.
Cloud 환경에서는 Host Device CNI보다 IPVLAN L3 모드가 더 적합하다.
[root@bastion ~]# oc apply -f - <<EOF
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: host-device-static-ip
spec:
config: |-
{
"cniVersion": "0.4.0",
"name": "host-device-net",
"type": "host-device",
"device": "ens36",
"ipam": {
"type": "static",
"addresses": [
{
"address": "172.16.10.2/16",
"gateway": "172.16.0.1"
}
]
}
}
EOF
[root@bastion ~]# oc apply -f - <<EOF
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: host-device02-static-ip
spec:
config: |-
{
"cniVersion": "0.4.0",
"name": "host-device02-net",
"type": "host-device",
"device": "ens37",
"ipam": {
"type": "static",
"addresses": [
{
"address": "172.16.10.3/16",
"gateway": "172.16.0.1"
}
]
}
}
EOF
Deployment에 적용한다.
[root@bastion ~]# oc scale deployment nginx --replicas=1 -n sample
[root@bastion ~]# oc patch deployment nginx --type='merge' \
-p '{"spec":{"template":{"metadata":{"annotations":{"k8s.v1.cni.cncf.io/networks":"[{\"name\": \"host-device-static-ip\"}]"}}}}}'
2.5.2. Host Device02 - Static IP
[root@bastion ~]# oc scale deployment nginx02 --replicas=1 -n sample
[root@bastion ~]# oc patch deployment nginx02 --type='merge' \
-p '{"spec":{"template":{"metadata":{"annotations":{"k8s.v1.cni.cncf.io/networks":"[{\"name\": \"host-device02-static-ip\"}]"}}}}}'
- MAC Address 확인
- Host NetworkAttachmentDefinition가 생성 되기 전에 확인 해야한다.
[root@bastion ~]# cat /sys/class/net/ens36/address
00:50:56:be:6f:9a
[root@bastion ~]# cat /sys/class/net/ens37/address
00:50:56:be:8d:b4
- Pod
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | cut -d '/' -f '2')"
[root@bastion ~]# for pods in ${POD}; do oc exec -n sample -it $pods -- cat /sys/class/net/net1/address; done
00:50:56:be:6f:9a
00:50:56:be:8d:b4
- Pod IP 확인
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | cut -d '/' -f '2')"
[root@bastion ~]# for pods in ${POD}; do oc exec -n sample -it $pods -- cat /proc/net/fib_trie | grep -E "172\.16\.10\.(2|3)"; done | sort -u
|-- 172.16.10.2
|-- 172.16.10.3
Pod to Pod로 호출한다.
- Pod01
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 2 | cut -d '/' -f '2' | sed -n '1p')"
[root@bastion ~]# oc rsh -n sample $POD
# bash
root@nginx-69fb4db9-8j7b6:/# for requests in {1..5}; do sleep 1 && curl -I -XGET http://172.16.10.3/; done
- 로그 확인(bastion)
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 2 | cut -d '/' -f '2' | sed -n '2p')"
[root@bastion ~]# oc logs -n sample -f $POD
172.16.10.2 - - [23/Feb/2025:13:36:56 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
172.16.10.2 - - [23/Feb/2025:13:36:57 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
172.16.10.2 - - [23/Feb/2025:13:36:58 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
172.16.10.2 - - [23/Feb/2025:13:36:59 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
172.16.10.2 - - [23/Feb/2025:13:37:00 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
tcpdump를 통해 패킷 덤프를 확인하여 MAC Address가 같은지 확인 한다.
Pod안에서 패킷 덤프를 캡쳐하기 위해서는 특수 권한인 NET_RAW, NET_ADMIN, privileged 권한을 부여한다.
[root@bastion ~]# oc adm policy add-scc-to-user privileged -z default -n sample
[root@bastion ~]# oc patch deployment nginx \
--type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/securityContext", "value": {"capabilities": {"add": ["NET_RAW", "NET_ADMIN"]}, "privileged": true}}]'
[root@bastion ~]# oc patch deployment nginx02 \
--type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/securityContext", "value": {"capabilities": {"add": ["NET_RAW", "NET_ADMIN"]}, "privileged": true}}]'
Pod to Pod로 호출한다.
- Pod01
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 2 | cut -d '/' -f '2' | sed -n '1p')"
[root@bastion ~]# oc rsh -n sample $POD
# bash
root@nginx-69fb4db9-8j7b6:/# tcpdump -i net1 -w /tmp/multus-host-device-nginx01-172-16-10-2-to-nginx02-172-16-10-3.pcap
- 서비스 Pod 단
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 2 | cut -d '/' -f '2' | sed -n '2p')"
[root@bastion ~]# oc rsh -n sample -it $POD
# bash
root@nginx02-6456bc5b99-9tb2k:/# tcpdump -i net1 -w /tmp/multus-host-device-nginx02-172-16-10-3.pcap
Pod to Pod로 호출한다.
- Pod01
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 2 | cut -d '/' -f '2' | sed -n '1p')"
[root@bastion ~]# oc rsh -n sample $POD
# bash
root@nginx-69fb4db9-8j7b6:/# for requests in {1..5}; do sleep 1 && curl -I -XGET http://172.16.10.3/; done
2.8.5. 패킷 덤프 분석
- Bridge 모드
SRC: 172.16.10.2(00:50:56:be:6f:9a)
DST: 172.16.10.3(00:50:56:be:8d:b4)
TCPDUMP - multus-host-device-nginx01-172-16-10-2-to-nginx02-172-16-10-3.pcap
TCPDUMP - multus-host-device-nginx02-172-16-10-3.pcap
기존 Worker 노드에서 사용하고 있는 네트워크 대역과 충돌하지 않는 별도의 인터페이스를 사용해야 한다.
Pod는 master로 지정한 인터페이스의 MAC Address를 공유하며 개별적으로 할당된 IP를 사용한다.
할당 된 IP는 개별의 External IP 형태로 존재하기 때문에, Application 서비스의 포트를 직접적으로 사용한다.
whereabouts를 사용하여 IP 범위(Range) 지정 방식으로 할당 가능하다.
-
설명
Broadcast를 통해 ARP(Address Resolution Protocol) 기반 통신을 한다. -
장점
Node 또는 Pod 간의 통신은 동일한 대역내에서 브릿지 형태로 통신 된다.
Low latency 환경 및 소규모 환경에 적합하다. -
단점
Pod 개수가 증가 되면, 그에 따른 브로드캐스트 패킷과 ARP 요청이 많아지며, 네트워크 대역폭이 불필요하게 소비될 수 있다.
즉, MAC Address가 같기 때문에, 네트워크 스위치 입장에서는 MAC Address Table에 대한 변동이 잦으므로 포트가 구분이 안될 수 있다.
대규모 환경에는 부적합하다.
-
설명
master 인터페이스가 게이트웨이 역할을 하며, 라우팅을 통해 통신된다. -
장점
ARP 통신과 브로드캐스트 패킷을 전송하지 않아 관련 트래픽이 발생하지 않는다.
Pod 개수가 증가 되어도 네트워크 장비가 MAC 주소 혼란 없이 정상적으로 트래픽을 처리할 수 있다.
대규모 환경에 적합하다. -
단점
Master 인터페이스를 기준으로 라우팅 되므로 Pod to Pod, Node to Node 간의 통신은 반드시 Master 인터페이스를 거치고 통신 된다.
Case 1) Pod01 to Pod02(Same Node01)
Pod01 -> Master Interface(Node01) -> Pod02
Case 2) Pod01 to Pod02(Node01 to Node02)
Pod01 -> Master Interface(Node01) -> Physical L2 Switch -> Physical L3 Switch
- Physical L2 Switch -> Master Interface(Node02) -> Pod02
Kubernetes의 기본 Service 객체(ClusterIP, NodePort, LoadBalancer)는 Multus를 통해 생성된 네트워크 인터페이스를 지원하지 않는다.
OpenShift에서 제공하는 Ingress Controller는 OVN-Kubernetes 네트워크에서만 동작된다.
따라서, Multus 네트워크로 구성된 Pod들에 대한 로드밸런싱은 물리 로드밸런서 또는 HAProxy와 같은 SW 로드밸런서를 사용하여 처리해야 한다.
- Static IP(Single)
[root@bastion ~]# oc apply -f - <<EOF
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-l2-single-static-ip
namespace: sample
spec:
config: |
{
"cniVersion": "0.4.0",
"type": "ipvlan",
"master": "ens36",
"mode": "l2",
"ipam": {
"type": "static",
"addresses": [
{
"address": "172.16.10.2/24",
"gateway": "172.16.0.1"
}
]
}
}
EOF
- Static IP(Multiple)
[root@bastion ~]# oc apply -f - <<EOF
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-l2-multiple-static-ip
namespace: sample
spec:
config: |
{
"cniVersion": "0.4.0",
"type": "ipvlan",
"master": "ens36",
"mode": "l2",
"ipam": {
"type": "static",
"addresses": [
{
"address": "172.16.10.2/24",
"gateway": "172.16.0.1"
}
],
"addresses": [
{
"address": "172.16.10.3/24",
"gateway": "172.16.0.1"
}
]
}
}
EOF
[root@bastion ~]# oc apply -f - <<EOF
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-l2-multiple-static-ip
namespace: sample
spec:
config: |
{
"cniVersion": "0.4.0",
"type": "ipvlan",
"master": "ens36",
"mode": "l2",
"ipam": {
"type": "whereabouts",
"range": "172.16.0.0/16",
"range_start": "172.16.10.2",
"range_end": "172.16.10.4",
"exclude": [
"172.16.10.4/32"
],
"gateway": "172.16.0.1"
}
}
EOF
- Static IP(Single)
[root@bastion ~]# oc apply -f - <<EOF
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-l3-single-static-ip
namespace: sample
spec:
config: |
{
"cniVersion": "0.4.0",
"type": "ipvlan",
"master": "ens36",
"mode": "l3",
"ipam": {
"type": "static",
"addresses": [
{
"address": "172.16.10.2/24",
"gateway": "172.16.0.1"
}
]
}
}
EOF
- Static IP(Multiple)
[root@bastion ~]# oc apply -f - <<EOF
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-l3-multiple-static-ip
namespace: sample
spec:
config: |
{
"cniVersion": "0.4.0",
"type": "ipvlan",
"master": "ens36",
"mode": "l3",
"ipam": {
"type": "static",
"addresses": [
{
"address": "172.16.10.2/24",
"gateway": "172.16.0.1"
}
],
"addresses": [
{
"address": "172.16.10.3/24",
"gateway": "172.16.0.1"
}
]
}
}
EOF
[root@bastion ~]# oc apply -f - <<EOF
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-l3-multiple-static-ip
namespace: sample
spec:
config: |
{
"cniVersion": "0.4.0",
"type": "ipvlan",
"master": "ens36",
"mode": "l3",
"ipam": {
"type": "whereabouts",
"range": "172.16.0.0/16",
"range_start": "172.16.10.2",
"range_end": "172.16.10.4",
"exclude": [
"172.16.10.4/32"
],
"gateway": "172.16.0.1"
}
}
EOF
Deployment에 적용한다.
[root@bastion ~]# oc scale deployment nginx --replicas=1 -n sample
[root@bastion ~]# oc patch deployment nginx --type='merge' \
-p '{"spec":{"template":{"metadata":{"annotations":{"k8s.v1.cni.cncf.io/networks":"[{\"name\": \"ipvlan-l2-single-static-ip\"}]"}}}}}'
[root@bastion ~]# oc scale deployment nginx --replicas=2 -n sample
[root@bastion ~]# oc patch deployment nginx --type='merge' \
-p '{"spec":{"template":{"metadata":{"annotations":{"k8s.v1.cni.cncf.io/networks":"[{\"name\": \"ipvlan-l2-multiple-static-ip\"}]"}}}}}'
[root@bastion ~]# oc scale deployment nginx --replicas=1 -n sample
[root@bastion ~]# oc patch deployment nginx --type='merge' \
-p '{"spec":{"template":{"metadata":{"annotations":{"k8s.v1.cni.cncf.io/networks":"[{\"name\": \"ipvlan-l3-single-static-ip\"}]"}}}}}'
#### 3.7.4. Layer 3 - Multiple Static IP
[root@bastion ~]# oc scale deployment nginx --replicas=2 -n sample
[root@bastion ~]# oc patch deployment nginx --type='merge' \
-p '{"spec":{"template":{"metadata":{"annotations":{"k8s.v1.cni.cncf.io/networks":"[{\"name\": \"ipvlan-l3-multiple-static-ip\"}]"}}}}}'
- MAC Address 확인
- Host
[root@bastion ~]# cat /sys/class/net/ens36/address
00:50:56:be:6f:9a
- Pod
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 1 | cut -d '/' -f '2')"
[root@bastion ~]# oc exec -n sample -it $POD -- cat /sys/class/net/net1/address
00:50:56:be:6f:9a
- Pod IP 확인
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 1 | cut -d '/' -f '2')"
[root@bastion ~]# oc exec -n sample -it $POD -- cat /proc/net/fib_trie | grep '172.16.10.2'
- MAC Address 확인
- Host
[root@bastion ~]# cat /sys/class/net/ens36/address
00:50:56:be:6f:9a
- Pod
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | cut -d '/' -f '2')"
[root@bastion ~]# for pods in ${POD}; do oc exec -n sample -it $pods -- cat /sys/class/net/net1/address; done
00:50:56:be:6f:9a
00:50:56:be:6f:9a
- Pod IP 확인
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | cut -d '/' -f '2')"
[root@bastion ~]# for pods in ${POD}; do oc exec -n sample -it $pods -- cat /proc/net/fib_trie | grep -E "172\.16\.10\.(2|3)"; done | sort -u
|-- 172.16.10.2
|-- 172.16.10.3
172.16.0.0/16 대역과 통신 되는 서버에서 호출한다.
[root@svc-test ~]# ip a s ens33 | grep inet | awk '{print $2}'
172.16.10.1/16
[root@svc-test ~]# for requests in {1..5}; do sleep 1 && curl -I -XGET http://172.16.10.2/; done
- 로그 확인(bastion)
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 2 | cut -d '/' -f '2' | sed -n '2p')"
[root@bastion ~]# oc logs -n sample -f $POD
172.16.10.1 - - [23/Feb/2025:06:45:02 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
172.16.10.1 - - [23/Feb/2025:06:45:03 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
172.16.10.1 - - [23/Feb/2025:06:45:04 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
172.16.10.1 - - [23/Feb/2025:06:45:05 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
172.16.10.1 - - [23/Feb/2025:06:45:06 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
tcpdump를 통해 패킷 덤프를 확인하여 MAC Address가 같은지 확인 한다.
Pod안에서 패킷 덤프를 캡쳐하기 위해서는 특수 권한인 NET_RAW, NET_ADMIN, privileged 권한을 부여한다.
[root@bastion ~]# oc adm policy add-scc-to-user privileged -z default -n sample
[root@bastion ~]# oc patch deployment nginx \
--type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/securityContext", "value": {"capabilities": {"add": ["NET_RAW", "NET_ADMIN"]}, "privileged": true}}]'
- 외부(svc-test) 172.16.0.0/16 대역과 통신 되는 서버에서 진행한다.
[root@svc-test ~]# tcpdump -i ens33 -w /tmp/svc-test-172-16-10-1-to-172-16-10-2.pcap
- 서비스 Pod 단
[root@bastion ~]# POD="$(oc get pod --no-headers -o name | head -n 2 | cut -d '/' -f '2' | sed -n '1p')"
[root@bastion ~]# oc rsh -n sample -it $POD
# tcpdump -i net1 -w /tmp/nginx-multus-ipvlan-l2-172-16-10-2.pcap
172.16.0.0/16 대역과 통신 되는 서버에서 호출한다.
[root@svc-test ~]# ip a s ens33 | grep inet | awk '{print $2}'
172.16.10.1/16
[root@svc-test ~]# for requests in {1..5}; do sleep 1 && curl -I -XGET http://172.16.10.2/; done
- IPVLAN L2 모드
SRC: 172.16.10.1(00:50:56:be:16:4e)
DST: 172.16.10.2(00:50:56:be:6f:9a)
TCPDUMP - svc-test-multus-ipvlan-l2-172-16-10-1-to-172-16-10-2.pcap
TCPDUMP - nginx-multus-ipvlan-l2-172-16-10-2.pcap
- IPVLAN L3 모드
SRC: 172.16.10.1(00:50:56:be:16:4e)
DST: 172.16.10.2(00:50:56:be:6f:9a)
TCPDUMP - svc-test-multus-ipvlan-l3-172-16-10-1-to-172-16-10-2.pcap
TCPDUMP - nginx-multus-ipvlan-l3-172-16-10-2.pcap
MACVLAN은 Pod에 개별적인 MAC 주소를 부여하여 네트워크 인터페이스를 가상화하는 기술이다.
기존 Worker 노드의 네트워크 대역과 충돌하지 않는 별도의 인터페이스를 사용해야 한다.
물리 인터페이스 또는 VLAN을 분리하여 사용해야 하며, 적절한 네트워크 정책을 적용해야 한다.
MACVLAN은 각 Pod가 개별 MAC Address를 가지며, 네트워크에서 독립적인 호스트처럼 동작한다.
할당된 IP는 외부에서 직접 접근 가능한 External IP 형태로 존재하며, 애플리케이션 서비스의 포트를 직접 사용할 수 있다.
whereabouts IPAM을 활용하여 IP 범위(Range) 지정 방식으로 할당 가능하다.
-
설명 Pod는 같은 네트워크 대역에서 브로드캐스트 기반으로 통신한다.
ARP(Address Resolution Protocol) 기반의 주소 확인 및 패킷 전송이 이루어진다. -
장점 소규모 환경에서 설정이 간단하며, 저지연(Low Latency) 환경에 적합하다.
같은 노드 또는 동일한 네트워크 대역 내에서 빠른 브로드캐스트 기반 통신이 가능하다. -
단점 Pod 개수가 증가 되면, 그에 따른 브로드캐스트 패킷과 ARP 요청이 많아지며, 네트워크 대역폭이 불필요하게 소비될 수 있다.
Pod 개별로 MAC Address를 가지므로, 네트워크 스위치에서 MAC 테이블 크기가 증가하여 부하가 발생할 수 있다.
Public Cloud 환경에서는 VM당 허용되는 MAC 주소 개수가 제한되므로 사용이 어렵다.
-
설명 Pod to Pod 간의 통신이 차단되며, 물리 L3 스위치를 통해서만 통신이 가능하다.
-
장점 기본적으로 Pod to Pod 간의 통신이 차단되어 보안성이 높아진다.
Layer 3 스위치를 경유하여 통신하는 경우 ARP, 브로드캐스트 패킷은 전송하지만, 같은 노드(Layer 2)에서는 해당 패킷은 전달은 되지 않는다.
Pod 개수가 증가 되어도 네트워크 장비가 MAC 주소 혼란 없이 정상적으로 트래픽을 처리할 수 있다.
On-Premise 환경에서 보안 강화를 위해 사용하거나, 네트워크 영역을 명확하게 구분할때 사용한다. -
단점 Pod to Pod 및 Node to Node 간 직접 통신이 불가능하며, 모든 트래픽이 반드시 L3 스위치를 경유해야 하므로 네트워크 설정이 복잡해질 수 있지만,
MAC 테이블 증가 부담은 없고, Cloud 환경에서는 MAC 개수 제한이 있어 사용이 어렵기 때문에 대신 IPVLAN L3 모드가 더 적합하다.
-
설명 Pod 간 직접 통신이 차단되며, 모든 네트워크 트래픽이 반드시 물리 Layer 3 스위치를 통해서만 전달되는 방식이다.
같은 노드 내에서는 Layer 2 브로드캐스트(ARP, DHCP)가 차단되고 스위치를 통해서만 네트워크 패킷이 전달되므로, 보안성이 강화된다. -
장점
-
보안 강화
기본적으로 Pod 간 직접 통신이 차단되어 네트워크 격리(isolation) 효과가 있어 보안성이 높아진다. -
L3 스위치를 통한 통신 가능
Pod 간 통신이 필요할 경우, L3 스위치를 경유하여 ARP 및 브로드캐스트 패킷 전송이 가능하다. -
MAC 테이블 부담 없음
Pod 개수가 증가해도 MAC 주소 충돌이나 테이블 용량 초과 없이 안정적으로 트래픽을 처리할 수 있다. -
네트워크 분리 가능
On-Premise 환경에서 보안 강화를 위해 사용하거나, 네트워크 영역을 명확하게 구분할 때 유용하다.
- 단점
-
Pod 간 직접 통신 불가능
Pod to Pod 및 Node to Node 간 직접 통신이 불가능하며, 모든 트래픽이 반드시 물리 L3 스위치를 경유해야 한다. -
네트워크 설정 복잡성 증가
L3 스위치를 통한 트래픽 라우팅이 필수적이므로, 네트워크 설정이 복잡해질 수 있다. -
Cloud 환경에서 부적합
Cloud 환경에서는 VM당 허용되는 MAC 개수가 제한되므로, VEPA 모드를 사용하기 어렵다. -
IPVLAN L3 모드가 더 적합
Cloud 환경에서는 대신 IPVLAN L3 모드를 사용하면 MAC 개수 제한 문제를 피할 수 있어 더 적합하다.
Kubernetes의 기본 Service 객체(ClusterIP, NodePort, LoadBalancer)는 Multus를 통해 생성된 네트워크 인터페이스를 지원하지 않는다.
OpenShift에서 제공하는 Ingress Controller는 OVN-Kubernetes 네트워크에서만 동작된다.
따라서, Multus 네트워크로 구성된 Pod들에 대한 로드밸런싱은 물리 로드밸런서 또는 HAProxy와 같은 SW 로드밸런서를 사용하여 처리해야 한다.