KANS 스터디 2주차 - K8S Flannel & PAUSE

개요

2주차 스터디는 가장 쉽고 간편한 CNI중 하나인 Flannel CNI와 세상에서 가장 많이 사용되는 컨테이너인 PAUSE 컨테이너를 주제로 진행했다.
Flannel CNI는 아무래도 기능이 다양하지는 않기 때문에 프로덕션에서 쓰기에는 조금 무리가 있지만 처음 CNI를 공부하는 입장에서는 굉장히 좋은 구조인 것 같다.

실제로 온프레미스에서 가장 많이 사용되는 CNI는 아마도 Calico인 것 같고, 내가 속한 맨텍의 아코디언 팀에서도 기본적으로 제공하는 CNI는 Calico를 채택하고 있다.

향후 학습 버전

향후 스터디에서는 쿠버네티스 1.22버전을 사용 할 계획이다. 릴리즈 히스토리
EKS도 곧 1.22 버전 제공한다고 한다. 일반적으로 매니지드 쿠버네티스는 최신 버전의 2버전 내외로 유지하는 것 같다.

스터디 전 스몰토크📢

Vagrantfile 분석

금주 실습에서 사용할 Vagrantfile 세팅과 관련해서 정리해보았다.
기본적으로 M1, W2 구성으로 설치 되지만 W 개수 조정이나 CPU, Memory 스펙은 개인 PC 사양에 맞춰서 적절히 조정이 필요하다. 또한, 쿠버네티스 버전등도 1.22.5로 명시해두었는데, 필요시 변경해서 사용하면 된다.

각종 스크립트 및 설정파일 목록

  • init_cfg.sh
  • master.sh
  • worker.sh
  • kube-flannel.yaml : 플라넬 파일 기본 설정 파일에서 변경 사항(POD CIDR 변경)
# 아래 파드의 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

kubectl 발음은 어떻게 하나요?

큐브시티엘, 큐베시티엘, 큐브컨트롤, 큐베컨트롤 등등 참고

etcd 발음은 어떻게 하나요?

엣시디, 이티시디

쿠버네티스 구조

Control Plane

  • kube-apiserver : 마스터로 전달되는 모든 요청을 받아 드리는 API 서버 - (심화) 링크1 링크2 링크3
  • etcd : 클러스터내 모든 메타 정보를 저장하는 서비스
  • kube-scheduler : 사용자의 요청에 따라 적절하게 컨테이너를 워커 노드에 배치하는 스케줄러
  • kube-controller-manager : 현재 상태와 바라는 상태를 지속적으로 확인하며 특정 이벤트에 따라 특정 동작을 수행하는 컨트롤러 - 링크
  • cloud-controller-manager : 클라우드 플랫폼(AWS, GCP, Azure 등)에 특화된 리소스를 제어하는 클라우드 컨트롤러 - 링크

Worker Node

  • kubelet : 마스터의 명령에 따라 컨테이너의 라이프 사이클을 관리하는 노드 관리자
  • kube-proxy : 컨테이너의 네트워킹을 책임지는 프록시, 네트워크 규칙을 유지 관리
  • Container Runtime : 실제 컨테이너를 실행하는 컨테이너 실행 환경, (Docker & containerD & CRI-O)

1. Flannel CNI

들어가기 전 스몰토크📢

CNI와 관련된 궁금한 점

  • CNI는 왜 Addons인가요?(feat. CSI는 왜?)
  • CNI가 왜 필요한가요?

K8S 가 해결해야될 4가지 네트워크 문제

  1. Highly-coupled container-to-container communications: this is solved by Pods and localhost communications.
    • 파드에 여러개의 컨테이너가 동작 시, pause 컨테이너의 netns 를 공유하여 컨테이너들간 통신은 localhost 로 통신 가능!
  2. Pod-to-Pod communications: this is the primary focus of this document.
    • 파드 ↔ 파드 간 NAT 없이 통신할 수 있다.
  3. Pod-to-Service communications: this is covered by services.
    • 파드 ↔ 서비스 간 통신 가능!
  4. External-to-Service communications: this is covered by services.
    • 외부 ↔ 서비스 간 통신 가능

K8S 네트워크 동작 기준

  1. pods on a node can communicate with all pods on all nodes without NAT
    • 파드 ↔ 파드 간 NAT 없이 통신할 수 있다.
  2. agents on a node (e.g. system daemons, kubelet) can communicate with all pods on that node
    • 노드의 에이전트(예: 시스템 데몬, kubelet)는 모든 파드와 통신할 수 있다.
  3. pods in the host network of a node can communicate with all pods on all nodes without NAT
    • 노드의 호스트 네트워크에 있는 파드는 NAT 없이 모든 노드에 있는 모든 파드와 통신할 수 있다.
  4. IP range from which to assign service cluster IPs. This must not overlap with any IP ranges assigned to nodes for pods
    • 서비스 클러스터 IP 대역은 각 노드의 파드 IP 대역과 겹치지 않아야 한다.

Highly Available 구성 링크

Control Plane(마스터 노드) HA 설정 : 마스터 노드 3대(5대...) 구성 및 외부 LB(혹은 keepalived)를 통한 API 단일 진입점 구성

1. kubeadm HA topology - stacked etcd

2. kubeadm HA topology - external etcd

3. 노드에 keepalived 활용

(토론)업그레이드는 어떻게 하나요?


(본문) Flannel CNI란?

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 가 동작

Flannel CNI 특징 및 구조

  • VXLAN (권장) : Port(UDP 8472), DirecRouting 지원(같은 서브넷 노드와는 host-gw 처럼 동작)
  • 노드마다 flannel.1 생성 : VXLAN VTEP 역할
  • 노드마다 cni0 생성 : bridge 역할


출처 : https://ikcoo.tistory.com/101

flannel 1의 역할은?

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이다. 그 이유는 오버헤드(?) 때문에 => 뒤에서 패킷캡처해서 확인해볼 예정

Flannel CNI 설치확인

  • kube-flannel-cfg
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>
  • kube-system kube-flannel-ds
kubectl describe ds -n kube-system kube-flannel-ds
  • Flannel 정보 확인 : 대역, MTU 사이즈
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
  • 워커 노드에 할당된 podCIDR 확인
# 중복되지 않는 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 등을 확인 가능

  • 라우팅 정보 확인 : M -> M, W1, W2의 Flannel1으로 라우팅 테이블 설정이 되어있음
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

Flannel CNI Ping 테스트

  • ping 테스트 : 상대방 Flannel VTEP 인터페이스와 통신 가능
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

  • 각 노드의 VtepMAC 주소 정보 확인
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
  • iptalbes 룰 확인 : 다음과 같이 모든 노드의 podCIDR 대역간에는 통신 가능하도록 filter Chanin에 의해서 FORWARD 설정
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 대역은 멀티캐스트 대역

  • flannel 파드 이름 확인
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
  • flannel 파드에서 ps 정보 확인
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
  • flannel 파드들의 로그 확인
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

파드 생성시 CNI 모니터링

# 워커 노드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
  • Worker1, Worker2에 pod가 생성되었을 때 ip link 정보 확인 및 brctl로 브릿지 정보 확인

파드 Shell 접속 후 확인

# 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와 통신이 되는것을 알 수 있었다.👏

내용정리

  1. CNI는 POD간의 통신을 위한 네트워크 환경을 만들어 준다.
  2. 좀 더 상세하게 말하자면 워커노드 간(다른 네트워크 대역)에 Overay Network 구성을 제공하고 POD간에 NAT없이 통신할 수 있도록 한다.

2. 파드 & PAUSE 컨테이너

이번에는 파드와 Pause 컨테이너에 대해서 알아보려 한다. 사실 지금까지 나는 파드를 잘 안다고 생각했지만(파잘알)... 이번 스터디를 통해서 나는 정말 아무고토 몰랐구나.. 라는 생각을 하게 되었다.😢

이번 계기로 정말 파잘알이 되어야겠다.🔥

파드(Pod)란?

K8s 에서는 컨테이너 애플리케이션의 기본 단위를 파드(Pod)라고 부르며, 파드는 1개 이상의 컨테이너로 구성된 컨테이너의 집합이다. 공식문서 정의

파드의 구조

일반적으로 1파드 1컨테이너가 기본으로 알고 있지만, 정확히 말하면 기본적으로 pause 컨테이너가 숨겨져 있기 때문에 +1을 해줘야 할 것 같다.

이러한 파드내에 생성되는 컨테이터가 멀티 컨테이너(사이드카 등) 형태로 생성될 때에는 net nsipc ns를 공유한다. 그래서 pod ip 정보등이 동일하다.

여기서 ipc ns경우에는.. 조금 어려운 내용이기 때문에 추후 istio 스터디 때 좀 더 딥다이브하게 다뤄볼 예정이다.

Pause 컨테이너란?

세상에서 가장 많이 사용되는 또는 실행되는 컨테이너이다. 그 이유는 파드가 최초 생성될 때 기본적으로 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 확인 >> 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는 각각 다른 것을 알 수 있었다.

  • 컨테이너 IP 정보 확인
# 컨테이너의 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를 공유 함으로써 일어날 수 있는 신기한 일을 체험해보려 한다.

각 콘솔에 대한 부가적인 설명

  • 첫번째 터미널 : web server 로그확인
  • 두번째 터미널 : netshoot 컨테이너에서 localhost에 curl 명령
  • 세번째 터미널 : web server Listen Port 및 IP 정보
  • 네번째 터미널 : netshoot Listen Port 및 IP 정보

# 터미널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도 공유한다면 어떤일이 발생할까?

docker를 사용해서 직접 pause 컨테이너를 만들어 보자

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
  • pause 컨테이너의 정보 확인
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
  • busybox컨테이너의 정보 확인
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. 심화 실습내용도 추후에 별도로 올릴 예정이다

profile
DevOps를 꿈꾸는 엔지니어 입니다.

1개의 댓글

comment-user-thumbnail
2022년 1월 23일

내용이 정말 방대하네요! Flannel/Pause 에 대해서 상세 분해해서 설명한 좋은 글입니다. 잘 봤습니다!

답글 달기