2주차 스터디는 가장 쉽고 간편한 CNI중 하나인 Flannel CNI와 세상에서 가장 많이 사용되는 컨테이너인 PAUSE 컨테이너를 주제로 진행했다.
Flannel CNI는 아무래도 기능이 다양하지는 않기 때문에 프로덕션에서 쓰기에는 조금 무리가 있지만 처음 CNI를 공부하는 입장에서는 굉장히 좋은 구조인 것 같다.
실제로 온프레미스에서 가장 많이 사용되는 CNI는 아마도 Calico인 것 같고, 내가 속한 맨텍의 아코디언 팀에서도 기본적으로 제공하는 CNI는 Calico를 채택하고 있다.
향후 스터디에서는 쿠버네티스 1.22버전을 사용 할 계획이다. 릴리즈 히스토리
EKS도 곧 1.22 버전 제공한다고 한다. 일반적으로 매니지드 쿠버네티스는 최신 버전의 2버전 내외로 유지하는 것 같다.
금주 실습에서 사용할 Vagrantfile 세팅과 관련해서 정리해보았다.
기본적으로 M1, W2 구성으로 설치 되지만 W 개수 조정이나 CPU, Memory 스펙은 개인 PC 사양에 맞춰서 적절히 조정이 필요하다. 또한, 쿠버네티스 버전등도 1.22.5로 명시해두었는데, 필요시 변경해서 사용하면 된다.
# 아래 파드의 CIDR 대역을 172.16.0.0/16 으로 변경
net-conf.json: |
{
"Network": "172.16.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
...
# 플라넬 파드에 args 에 --iface=enp0s8 추가 (enp0s3 로 flannel 동작하기 않게 하기 위해)
containers:
- name: kube-flannel
image: rancher/mirrored-flannelcni-flannel:v0.16.1
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
- --iface=enp0s8
큐브시티엘, 큐베시티엘, 큐브컨트롤, 큐베컨트롤 등등 참고
엣시디, 이티시디
Control Plane(마스터 노드) HA 설정 : 마스터 노드 3대(5대...) 구성 및 외부 LB(혹은 keepalived)를 통한 API 단일 진입점 구성
Flannel runs a small, single binary agent called flanneld on each host, and is responsible for allocating a subnet lease to each host out of a larger, preconfigured address space. → 모든 노드에 flanneld 가 동작
출처 : https://ikcoo.tistory.com/101
cni0(brdige)와 물리 CNI사이에 flannel 1이 들어감으로서 별도 마스커레이딩(NAT)역할 대신 VTEP이라고 하는 터널링 기능을 통해 VXLAN Overay Network을 구성해준다.
Flannel1 인터페이스 간에 통신을 위해서 사용되는 것이라고 생각하면 될 것 같다.
(굉장히 어렵지만 재미있는 구조이다.😅)
ip -c -d addr show flannel.1
5: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
link/ether 4e:ea:c3:c3:86:e4 brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65535
vxlan id 1 local 192.168.100.10 dev enp0s8 srcport 0 0 dstport 8472 nolearning ttl auto ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 172.16.0.0/32 brd 172.16.0.0 scope global flannel.1
valid_lft forever preferred_lft forever
inet6 fe80::4cea:c3ff:fec3:86e4/64 scope link
valid_lft forever preferred_lft forever
flannel1의 MTU 사이즈는 1450이다. 그 이유는 오버헤드(?) 때문에 => 뒤에서 패킷캡처해서 확인해볼 예정
kubectl describe cm -n kube-system kube-flannel-cfg
Name: kube-flannel-cfg
Namespace: kube-system
Labels: app=flannel
tier=node
Annotations: <none>
Data
====
cni-conf.json:
----
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
net-conf.json:
----
{
"Network": "172.16.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
BinaryData
====
Events: <none>
kubectl describe ds -n kube-system kube-flannel-ds
cat /run/flannel/subnet.env
FLANNEL_NETWORK=172.16.0.0/16
FLANNEL_SUBNET=172.16.0.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true
# 중복되지 않는 24비트대역 사용
kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}' ;echo
172.16.0.0/24 172.16.1.0/24 172.16.2.0/24
ip -c -br addr
lo UNKNOWN 127.0.0.1/8 ::1/128
enp0s3 UP 10.0.2.15/24 fe80::d2:caff:fe0f:a612/64
enp0s8 UP 192.168.100.10/24 fe80::a00:27ff:fe06:2b86/64
docker0 DOWN 172.17.0.1/16
flannel.1 UNKNOWN 172.16.0.0/32 fe80::a012:b0ff:feb8:257c/64
cni0 UP 172.16.0.1/24 fe80::e896:66ff:febe:6211/64
vethece9b30d@if3 UP fe80::14af:b5ff:feb4:427e/64
vetha260904c@if3 UP fe80::cce1:49ff:feb1:b226/64
📌
/32
대역의 경우 x.x.x.0 IP 주소로 실제 사용이 가능함.
# 각 인터페이스 확인
ip -c link | grep -E 'flannel|cni|veth' -A1
5: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
link/ether a2:12:b0:b8:25:7c brd ff:ff:ff:ff:ff:ff
6: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether ea:96:66:be:62:11 brd ff:ff:ff:ff:ff:ff
7: vethece9b30d@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default
link/ether 16:af:b5:b4:42:7e brd ff:ff:ff:ff:ff:ff link-netnsid 0
8: vetha260904c@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default
link/ether ce:e1:49:b1:b2:26 brd ff:ff:ff:ff:ff:ff link-netnsid 1
ip -c -d addr show cni0
6: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
link/ether ea:96:66:be:62:11 brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65535
bridge forward_delay 1500 hello_time 200 max_age 2000 ageing_time 30000 stp_state 0 priority 32768 vlan_filtering 0 vlan_protocol 802.1Q bridge_id 8000.ea:96:66:be:62:11 designated_root 8000.ea:96:66:be:62:11 root_port 0 root_path_cost 0 topology_change 0 topology_change_detected 0 hello_timer 0.00 tcn_timer 0.00 topology_change_timer 0.00 gc_timer 217.23 vlan_default_pvid 1 vlan_stats_enabled 0 vlan_stats_per_port 0 group_fwd_mask 0 group_address 01:80:c2:00:00:00 mcast_snooping 1 mcast_router 1 mcast_query_use_ifaddr 0 mcast_querier 0 mcast_hash_elasticity 16 mcast_hash_max 4096 mcast_last_member_count 2 mcast_startup_query_count 2 mcast_last_member_interval 100 mcast_membership_interval 26000 mcast_querier_interval 25500 mcast_query_interval 12500 mcast_query_response_interval 1000 mcast_startup_query_interval 3124 mcast_stats_enabled 0 mcast_igmp_version 2 mcast_mld_version 1 nf_call_iptables 0 nf_call_ip6tables 0 nf_call_arptables 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 172.16.0.1/24 brd 172.16.0.255 scope global cni0
valid_lft forever preferred_lft forever
inet6 fe80::e896:66ff:febe:6211/64 scope link
valid_lft forever preferred_lft forever
ip -c -d addr show flannel.1
5: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
link/ether a2:12:b0:b8:25:7c brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65535
vxlan id 1 local 192.168.100.10 dev enp0s8 srcport 0 0 dstport 8472 nolearning ttl auto ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 172.16.0.0/32 brd 172.16.0.0 scope global flannel.1
valid_lft forever preferred_lft forever
inet6 fe80::a012:b0ff:feb8:257c/64 scope link
valid_lft forever preferred_lft forever
참고 :
-d
옵션은 디테일 옵션으로 vxlan 등을 확인 가능
ip -c route | grep 172.16.
172.16.0.0/24 dev cni0 proto kernel scope link src 172.16.0.1
172.16.1.0/24 via 172.16.1.0 dev flannel.1 onlink
172.16.2.0/24 via 172.16.2.0 dev flannel.1 onlink
ping -c 1 172.16.1.0
PING 172.16.1.0 (172.16.1.0) 56(84) bytes of data.
64 bytes from 172.16.1.0: icmp_seq=1 ttl=64 time=0.709 ms
--- 172.16.1.0 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.709/0.709/0.709/0.000 ms
(⎈ |admin-k8s:default) root@k8s-m:~# ping -c 1 172.16.2.0
PING 172.16.2.0 (172.16.2.0) 56(84) bytes of data.
64 bytes from 172.16.2.0: icmp_seq=1 ttl=64 time=0.742 ms
--- 172.16.2.0 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.742/0.742/0.742/0.000 ms
kubectl describe node | grep VtepMAC -A4
Annotations: flannel.alpha.coreos.com/backend-data: {"VNI":1,"VtepMAC":"a2:12:b0:b8:25:7c"}
flannel.alpha.coreos.com/backend-type: vxlan
flannel.alpha.coreos.com/kube-subnet-manager: true
flannel.alpha.coreos.com/public-ip: 192.168.100.10
kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
--
Annotations: flannel.alpha.coreos.com/backend-data: {"VNI":1,"VtepMAC":"42:7b:d8:d5:f7:8c"}
flannel.alpha.coreos.com/backend-type: vxlan
flannel.alpha.coreos.com/kube-subnet-manager: true
flannel.alpha.coreos.com/public-ip: 192.168.100.101
kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
--
Annotations: flannel.alpha.coreos.com/backend-data: {"VNI":1,"VtepMAC":"62:a9:f6:cb:35:22"}
flannel.alpha.coreos.com/backend-type: vxlan
flannel.alpha.coreos.com/kube-subnet-manager: true
flannel.alpha.coreos.com/public-ip: 192.168.100.102
kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
iptables -t filter -S | grep 172.16.0.0
-A FORWARD -s 172.16.0.0/16 -j ACCEPT
-A FORWARD -d 172.16.0.0/16 -j ACCEPT
iptables -t nat -S | grep 'A POSTROUTING'
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.16.0.0/16 -d 172.16.0.0/16 -j RETURN
-A POSTROUTING -s 172.16.0.0/16 ! -d 224.0.0.0/4 -j MASQUERADE --random-fully
-A POSTROUTING ! -s 172.16.0.0/16 -d 172.16.0.0/24 -j RETURN
-A POSTROUTING ! -s 172.16.0.0/16 -d 172.16.0.0/16 -j MASQUERADE --random-fully
참고 :
224.0.0.0/4
대역은 멀티캐스트 대역
kubectl get pod -n kube-system -l app=flannel -o name
pod/kube-flannel-ds-4c8km
pod/kube-flannel-ds-bcr6r
pod/kube-flannel-ds-snkt7
kubectl exec -it -n kube-system pod/kube-flannel-ds-snkt7 -- ps | head -2
Defaulted container "kube-flannel" out of: kube-flannel, install-cni-plugin (init), install-cni (init)
PID USER TIME COMMAND
1 root 0:02 /opt/bin/flanneld --ip-masq --kube-subnet-mgr --iface=enp0
kubens kube-system && kubetail -l app=flannel --since 1h
kubens -
잘은 모르겠지만... flannel(CNI)이 iptalbes rule을 조작하고 있다는 log들을 확인할 수 있었다.
lsns -t net
NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
4026531992 net 135 1 root unassigned /run/docker/netns/default /sbin/init
4026532233 net 2 5146 65535 0 /run/docker/netns/90b6db59b16b /pause
4026532307 net 2 5160 65535 1 /run/docker/netns/40489d8b2409 /pause
# 워커 노드1,2 - 모니터링
watch -d "ip link | egrep 'cni|veth' ;echo; brctl show cni0"
# cat & here document 명령 조합으로 즉석(?) 리소스 생성
# 마스터 노드 - 파드 2개 생성
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: pod-1
labels:
app: pod
spec:
containers:
- name: netshoot-pod
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
name: pod-2
labels:
app: pod
spec:
containers:
- name: netshoot-pod
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 파드 확인
kubectl get pod -o wide
ip link
정보 확인 및 brctl
로 브릿지 정보 확인# pod-1에 zsh로 접속
kubectl exec -it pod-1 -- zsh
dP dP dP
88 88 88
88d888b. .d8888b. d8888P .d8888b. 88d888b. .d8888b. .d8888b. d8888P
88' `88 88ooood8 88 Y8ooooo. 88' `88 88' `88 88' `88 88
88 88 88. ... 88 88 88 88 88. .88 88. .88 88
dP dP `88888P' dP `88888P' dP dP `88888P' `88888P' dP
Welcome to Netshoot! (github.com/nicolaka/netshoot)
# eth0 정보 확인
pod-1 ~ ip -c addr show eth0
3: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether f2:c2:2b:bd:68:11 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.16.1.7/24 brd 172.16.1.255 scope global eth0
valid_lft forever preferred_lft forever
# GW IP는 어떤 인터페이스인가? (1)flannel.1 (2)cni0(v)
route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.16.1.1 0.0.0.0 UG 0 0 0 eth0
172.16.0.0 172.16.1.1 255.255.0.0 UG 0 0 0 eth0
172.16.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
# ping 테스트
pod-1 ~ ping -c 1 172.16.1.1
PING 172.16.1.1 (172.16.1.1) 56(84) bytes of data.
64 bytes from 172.16.1.1: icmp_seq=1 ttl=64 time=0.036 ms
--- 172.16.1.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.036/0.036/0.036/0.000 ms
pod-1 ~ ping -c 1 172.16.2.8
PING 172.16.2.8 (172.16.2.8) 56(84) bytes of data.
64 bytes from 172.16.2.8: icmp_seq=1 ttl=62 time=0.814 ms
--- 172.16.2.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.814/0.814/0.814/0.000 ms
# ARP Table 갱신정보
pod-1 ~ ip -c neigh
172.16.1.1 dev eth0 lladdr 5a:68:17:52:37:55 REACHABLE
실제 파드가 생성되는 각 노드의 cni0(gateway)과 연결되어 외부로 통신이 된다. 이때 VXLAN으로 Overay해서 다른 노드로 통신
Pod-1에서 Pod-2로 통신할 때 패킷캡처를 떠보니 Source -> Dest에 대한 정보가 보이는 것을 확인할 수 있다.
좀 더 확실하게 확인하기 위해서 WireShark에서 패킷 캡처 정보를 보려고한다.
📌 참고: 윈도우에서 기본적으로 Wireshark를 깔면 VXLAN이 안보여서 프로토콜 4789에서 8462로 바꾸면 정상적으로 볼 수 있다.
패킷캡처 결과를 보니 POD끼리 통신할 때 VXLAN이 감싸서 노드간에 통신하는 형태이다.
(Overay Network의 특징)
결론적으로 다른 노드간에 통신을 할 경우 다음 그림에서 나온 것 처럼 VXLAN 헤더를 감싸서 Overay 통신이 이루어 진다. 목적지 노드에 도착한 뒤 VXLAN 헤더를 벗겨내고, Inner 헤더의 네트워크 정보를 통해서 실제 POD와 통신이 되는것을 알 수 있었다.👏
이번에는 파드와 Pause 컨테이너에 대해서 알아보려 한다. 사실 지금까지 나는 파드를 잘 안다고 생각했지만(파잘알)... 이번 스터디를 통해서 나는 정말 아무고토 몰랐구나.. 라는 생각을 하게 되었다.😢
이번 계기로 정말 파잘알이 되어야겠다.🔥
K8s 에서는 컨테이너 애플리케이션의 기본 단위를 파드(Pod)라고 부르며, 파드는 1개 이상의 컨테이너로 구성된 컨테이너의 집합이다. 공식문서 정의
일반적으로 1파드 1컨테이너가 기본으로 알고 있지만, 정확히 말하면 기본적으로 pause 컨테이너가 숨겨져 있기 때문에 +1을 해줘야 할 것 같다.
이러한 파드내에 생성되는 컨테이터가 멀티 컨테이너(사이드카 등) 형태로 생성될 때에는 net ns
와 ipc ns
를 공유한다. 그래서 pod ip 정보등이 동일하다.
여기서 ipc ns
경우에는.. 조금 어려운 내용이기 때문에 추후 istio 스터디 때 좀 더 딥다이브하게 다뤄볼 예정이다.
세상에서 가장 많이 사용되는 또는 실행되는 컨테이너이다. 그 이유는 파드가 최초 생성될 때 기본적으로 pause 컨테이기 때문이다. 그래서 다른 이름으로 인프라 컨테이너로 불리기도 한다.
위에서 첨부한 그림과 같은 형태로 파드를 생성해보자.
# 파드 생성
kubectl apply -f https://raw.githubusercontent.com/gasida/NDKS/main/3/myweb2.yaml
# 확인
kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myweb2 2/2 Running 0 25s 172.16.2.9 k8s-w2 <none> <none>
다음으로 파드가 생성된 w2에서 docker 컨테이너 실행 결과를 확인해보니, 내가 실행하지 않은 pause 컨테이너가 실행된 것을 볼 수 있었다.
docker ps --format "table {{.Image}}\t{{.Status}}\t{{.Names}}" | grep myweb2
nicolaka/netshoot Up 49 seconds k8s_myweb2-netshoot_myweb2_default_3ede4f4a-f0e9-4ac9-9105-bc8ac46b4a33_0
nginx Up 52 seconds k8s_myweb2-nginx_myweb2_default_3ede4f4a-f0e9-4ac9-9105-bc8ac46b4a33_0
k8s.gcr.io/pause:3.5 Up About a minute k8s_POD_myweb2_default_3ede4f4a-f0e9-4ac9-9105-bc8ac46b4a33_0
pause 컨테이너...너는 누구니?🤔
describe로 파드 상세 정보를 확인해보니 nginx
컨테이너와 netshoot
컨테이너 두 개는 보이지만, pause 컨테이너는 확인되지 않는다.
# Pod 상세 정보 확인 결과 : 컨테이너 2개
kubectl describe pod myweb2
root@k8s-m:~# kubectl describe pod myweb2
Name: myweb2
...
Containers:
myweb2-nginx:
Container ID: docker://4686811509c647da65a5d127583118fbecaa53dafa5e24707318f4be2976dff2
Image: nginx
...
myweb2-netshoot:
Container ID: docker://30be1eb17c071ba3d64c8b2f3af37baa67d87203dc6960119e1a8d9ba557339a
Image: nicolaka/netshoot
...
# 파드의 각각 컨테이너 IP 확인 >> IP가 같다!
kubectl exec myweb2 -c myweb2-netshoot -- ip addr
kubectl exec myweb2 -c myweb2-nginx -- apt update
kubectl exec myweb2 -c myweb2-nginx -- apt install -y net-tools
kubectl exec myweb2 -c myweb2-nginx -- ifconfig
확인 결과 두 컨테이너의 IP 정보가 동일한 것을 확인할 수 있다.
그 이유는 앞서 설명한 것 처럼 net ns를 공유하기 때문에 동일한 IP정보를 가지고 있다.
또한 워커노드에서 프로세스를 확인해보니 각 컨테이너의 동작을 확인할 수 있었다.
ps -ef
명령으로 확인# nginx 컨테이너
ps -ef | grep 'nginx -g' | grep -v grep
root 93092 93073 0 09:11 ? 00:00:00 nginx: master process nginx -g daemon off;
# netshoot 컨테이너
ps -ef | grep 'curl' | grep -v grep
root 93186 93165 0 09:11 ? 00:00:00 /bin/bash -c while true; do sleep 5; curl localhost; done
docke top
명령으로 확인# nginx 컨테이너
docker top k8s_myweb2-nginx_myweb2_default_ff755d5c-8ea7-4b8c-a65b-b2be9b077492_0
UID PID PPID C STIME TTY TIME CMD
root 93092 93073 0 09:11 ? 00:00:00 nginx: master process nginx -g daemon off;
systemd+ 93132 93092 0 09:11 ? 00:00:00 nginx: worker process
systemd+ 93133 93092 0 09:11 ? 00:00:00 nginx: worker process
# netshoot 컨테이너
docker top k8s_myweb2-netshoot_myweb2_default_ff755d5c-8ea7-4b8c-a65b-b2be9b077492_0
UID PID PPID C STIME TTY TIME CMD
root 93186 93165 0 09:11 ? 00:00:00 /bin/bash -c while true; do sleep 5; curl localhost; done
root 125676 93186 0 10:23 ? 00:00:00 sleep 5
이번에는 각 프로세스를 변수로 지정해서 각 네임스페이스에 대한 상세 정보를 확인해보자.
# 각각 프로세스를 변수에 지정
NGINXPID=$(ps -ef | grep 'nginx -g' | grep -v grep | awk '{print $2}'); echo $NGINXPID
93092
NETSHPID=$(ps -ef | grep 'curl' | grep -v grep | awk '{print $2}'); echo $NETSHPID
93186
lsns
명령으로 각 컨테이너의 NS를 확인lsns -p $NGINXPID
NS TYPE NPROCS PID USER COMMAND
4026531835 cgroup 129 1 root /sbin/init
4026531837 user 129 1 root /sbin/init
4026532213 ipc 6 92982 65535 /pause
4026532216 net 6 92982 65535 /pause
4026532287 mnt 3 93092 root nginx: master process nginx -g daemon off;
4026532288 uts 3 93092 root nginx: master process nginx -g daemon off;
4026532289 pid 3 93092 root nginx: master process nginx -g daemon off;
lsns -p $NETSHPID
NS TYPE NPROCS PID USER COMMAND
4026531835 cgroup 129 1 root /sbin/init
4026531837 user 129 1 root /sbin/init
4026532213 ipc 6 92982 65535 /pause
4026532216 net 6 92982 65535 /pause
4026532290 mnt 2 93186 root /bin/bash -c while true; do sleep 5; curl localhost; done
4026532291 uts 2 93186 root /bin/bash -c while true; do sleep 5; curl localhost; done
4026532292 pid 2 93186 root /bin/bash -c while true; do sleep 5; curl localhost; done
확인해본 결과 ipc
, net
네임스페이스는 동일하지만 mnt
, uts
, pid
는 각각 다른 것을 알 수 있었다.
# 컨테이너의 ID 변수 지정
NGINXCID=$(docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Names}}" | grep nginx | awk '{print $1}')
NETSHCID=$(docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Names}}" | grep netshoot | awk '{print $1}')
PAUSECID=$(docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Names}}" | grep POD_myweb2 | awk '{print $1}')
echo $NGINXCID
echo $NETSHCID
echo $PAUSECID
# 컨테이너의 IP 확인 >> IP가 같다!
docker exec $NGINXCID ifconfig
docker exec $NETSHCID ip addr
이번에는 docker inspect
명령으로 각 정보를 비교해본다.
# 컨테이너의 NetworkMode 정보와 PAUSE 컨테이너의 Id,Pid 정보 확인
# 아래 처럼 위 pause 컨네이너를 nginx, netshoot 컨테이너가 공유하고 있음을 확인
# 즉, 내부 컨테이너가 pause 컨테이너의 network namespace 를 공유
docker inspect $NGINXCID | grep NetworkMode
"NetworkMode": "container:34cc69b1641ae598a8b1602a9f1ade6b7a22f05ade32dbd5a10feec09a1d2440",
docker inspect $NETSHCID | grep NetworkMode
"NetworkMode": "container:34cc69b1641ae598a8b1602a9f1ade6b7a22f05ade32dbd5a10feec09a1d2440",
docker inspect $PAUSECID | egrep 'Id|Pid\"'
"Id": "34cc69b1641ae598a8b1602a9f1ade6b7a22f05ade32dbd5a10feec09a1d2440",
"Pid": 92982,
nginx, netshoot, pause 3개의 컨테이너가 모두 동일한 NetworkMode(Net NS)를 공유해서 사용한다는 것을 확인할 수 있다.
결국 앞서 언급했던 그림을 참고하면 다음과 같다.
lsns
명령으로 PAUSE 의 NET NS PID 확인lsns -t net
NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
4026531992 net 123 1 root unassigned /run/docker/netns/default /sbin/init
4026532216 net 6 92982 65535 0 /run/docker/netns/a5fd245a192c /pause
PAUSEPID=$(docker inspect $PAUSECID | grep 'Pid\"' | awk '{print $2}' | cut -d "," -f 1)
echo $PAUSEPID
92982
nsenter
명령으로 각 NS에 접속하여 IP정보 확인nsenter -t $PAUSEPID -n ip -c addr
nsenter -t $NGINXPID -n ip -c addr
nsenter -t $NETSHPID -n ip -c addr
그림에서 볼 수있듯이 3가지 컨테이너는 모두 동일한 IP를 가지고 있다.
다음으로 각 컨테이너에서 NET NS를 공유 함으로써 일어날 수 있는 신기한 일을 체험해보려 한다.
# 터미널1 : nginx 컨테이너 웹 접속 로그 출력 : 접속자(myweb2-netshoot)의 IP 가 127.0.0.1 이다?
kubectl logs -f myweb2 -c myweb2-nginx
root@k8s-m:~# kubectl logs -f myweb2 -c myweb2-nginx
127.0.0.1 - - [16/Jun/2021:06:22:24 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.77.0" "-"
127.0.0.1 - - [16/Jun/2021:06:23:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.77.0" "-"
...
# 터미널2
# myweb2-netshoot 컨테이너 내부에서 확인, -c 옵션으로 포드의 어떤 컨테이너에 대해 명령어를 수행할지 명시할 수 있음
kubectl exec -it myweb2 -c myweb2-netshoot -- zsh
------------------------------
# 웹 접속 확인
curl -s localhost |grep -o "<title>.*</title>"
curl -s localhost/login.php
# IP/MAC 확인
ip -c a
ip -c route
# TCP Listen 정보 확인 : TCP 80 리슨 상태이다!
ss -tln
bash-5.1# ss -tln
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 **0.0.0.0:80** 0.0.0.0:*
LISTEN 0 511 [::]:80 [::]:*
# 프로세스 정보 확인 : localhost curl 날리는 중
ps axf
PID USER TIME COMMAND
1 root 0:01 /bin/bash -c while true; do sleep 5; curl localhost; done
6659 root 0:01 zsh
6767 root 0:00 zsh
7247 root 0:00 sleep 5
7250 root 0:00 ps axf
ps -ef
# 빠져나오기
exit
------------------------------
움짤을 통해서 다시한번 확인해 보자!
이로써 NET NS를 공유하면 어떤 현상이 발생할 수 있는지 확인할 수 있었다. 그렇다면 PID NS도 공유한다면 어떤일이 발생할까?
pause 컨테이너 이미지와 busybox 이미지를 통해 컨테이너를 생성하고 ID와 PID를 변수로 지정한다.
root@k8s-w1:~# docker run -d --rm --ipc="shareable" --name pause k8s.gcr.io/pause:3.2
Unable to find image 'k8s.gcr.io/pause:3.2' locally
3.2: Pulling from pause
c74f8866df09: Pull complete
Digest: sha256:927d98197ec1141a368550822d18fa1c60bdae27b78b0c004f705f548c07814f
Status: Downloaded newer image for k8s.gcr.io/pause:3.2
8ce3641d05eb088d4af0fbb2de57cfdf9781b469f5f31ffc4d8ba093a7efcb3c
root@k8s-w1:~# docker run -ti --rm -d --name sleep-busybox --net=container:pause --ipc=container:pause --pid=container:pause busybox sleep 3600
Unable to find image 'busybox:latest' locally
latest: Pulling from library/busybox
5cc84ad355aa: Pull complete
Digest: sha256:5acba83a746c7608ed544dc1533b87c737a0b0fb730301639a0179f9344b1678
Status: Downloaded newer image for busybox:latest
8e1221fd3e89774a03775445c649ef8b9a477cb94d56c0ef41f1bf90de24c2d5
# 확인
root@k8s-w1:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8e1221fd3e89 busybox "sleep 3600" About a minute ago Up About a minute sleep-busybox
8ce3641d05eb k8s.gcr.io/pause:3.2 "/pause" About a minute ago Up About a minute pause
...
root@k8s-w1:~# ps -ef | grep -v grep | grep sleep
root 225812 225781 0 14:11 pts/0 00:00:00 sleep 3600
# 컨테이너의 ID 와 PID 변수 지정
PAUSECID=$(docker ps --format "table {{.ID}}\t{{.Names}}" | grep pause | awk '{print $1}')
echo $PAUSECID
BUSYBOXCID=$(docker ps --format "table {{.ID}}\t{{.Names}}" | grep sleep-busybox | awk '{print $1}')
echo $BUSYBOXCID
PAUSEPID=$(docker inspect $PAUSECID | grep 'Pid\"' | awk '{print $2}' | cut -d "," -f 1)
echo $PAUSEPID
BUSYBOXPID=$(docker inspect $BUSYBOXCID | grep 'Pid\"' | awk '{print $2}' | cut -d "," -f 1)
echo $BUSYBOXPID
root@k8s-w1:~# nsenter -t $PAUSEPID -n ip -c addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
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
8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
root@k8s-w1:~# lsns -t net
NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
4026531992 net 126 1 root unassigned /run/docker/netns/default /sbin/init
4026532218 net 2 225628 root 0 /run/docker/netns/9b7ef5774798 /pause
root@k8s-w1:~# lsns -p $PAUSEPID
NS TYPE NPROCS PID USER COMMAND
4026531835 cgroup 128 1 root /sbin/init
4026531837 user 128 1 root /sbin/init
4026532213 mnt 1 225628 root /pause
4026532214 uts 1 225628 root /pause
4026532215 ipc 2 225628 root /pause
4026532216 pid 2 225628 root /pause
4026532218 net 2 225628 root /pause
## PID 네임스페이스를 공유해서 상대방 컨테이너(busybox)의 프로세스 정보가 보임
root@k8s-w1:~# nsenter -t $PAUSEPID -n ps
PID TTY TIME CMD
10052 pts/0 00:00:00 sudo
10053 pts/0 00:00:00 su
10055 pts/0 00:00:00 bash
225812 pts/0 00:00:00 sleep
231784 pts/0 00:00:00 ps
root@k8s-w1:~# nsenter -t $BUSYBOXPID -n ip -c addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
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
8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
# sleep-busybox 컨테이너 정보 확인 >> ipc, pid, net 은 pause 컨테이너 네임스페이스를 사용
root@k8s-w1:~# lsns -p $BUSYBOXPID
NS TYPE NPROCS PID USER COMMAND
4026531835 cgroup 125 1 root /sbin/init
4026531837 user 125 1 root /sbin/init
4026532215 ipc 2 225628 root /pause
4026532216 pid 2 225628 root /pause
4026532218 net 2 225628 root /pause
4026532288 mnt 1 225812 root sleep 3600
4026532289 uts 1 225812 root sleep 3600
## PID 네임스페이스를 공유해서 상대방 컨테이너(pause)의 프로세스 정보가 보임
root@k8s-w1:~# nsenter -t $BUSYBOXPID -n ps
PID TTY TIME CMD
10052 pts/0 00:00:00 sudo
10053 pts/0 00:00:00 su
10055 pts/0 00:00:00 bash
225812 pts/0 00:00:00 sleep
231901 pts/0 00:00:00 ps
이렇게 PID를 공유하는 컨테이너까지 직접 만들어 보았다. 이러한 특징을 잘 활용하면 유용하게 사용할 수도 있겠다는 생각도 들었지만, 한편으로 관리가 잘 안된다면 보안상 정말 위험할 수도 있겠다는 생각이 들었다.
쿠버네티스에서는 기본적으로 이러한 PID NS를 기본적으로 격리하도록 제공하지만 shareProcessNamespace: true
옵션등을 통해서 공유할 수 있도록 설정할 수도 있다.참고
또한, 이러한 특징을 사용해서 debug
할 수도 있겠다는 생각이 들었지만, 이미 최신 버전의 쿠버네티스 환경에서는 debug
파드 기능을 제공하고 있기 때문에 참고해보면 좋을 것 같다.참고
📌 참고 : 컨테이너 이미지를 제작할 때에는 보안 및 경량화를 위해 각종 패키지 설치를 최소화 하는 것을 권장한다. 그렇기 때문에 파드에 대한 이슈를 분석하거나 디버깅 해야할 때에는 디버깅 파드와 같은 기능을 사용해 보는 것을 추천한다.
이렇게 2주차 스터디 내용도 끝이났다. 사실 "스터디 끝나고 바로 복습을 해야지"라고 마음 먹었으나... 이런저런 핑계로 3주차 스터디 전날에 부랴부랴 실습을 해보았다.
다음주는 꼭 바로바로 실습하고 지난번에 스터디 한 내용도 복습하는걸로...😂
PS. 심화 실습내용도 추후에 별도로 올릴 예정이다
내용이 정말 방대하네요! Flannel/Pause 에 대해서 상세 분해해서 설명한 좋은 글입니다. 잘 봤습니다!