Cloud Tech

쿠버네티스 네트워크이해하기(Kind, Pause, Ephemeral Container, FlannelCNI)

Hanhorang31 2024. 9. 6. 17:27
 

Overview 

지난 글에서 도커 네트워크를 확인한 것을 바탕으로 쿠버네티스 네트워크 구성을 살펴보겠습니다. 블로그 내용은 CloudNet@ KANS3 스터디를 참고하였습니다.

Kind(Kubernetes in Docker)

Kind는 도커 컨테이너 위에 로컬 kubernetes 클러스터를 실행하기 위한 도구 입니다. Kind는 Kubernetes 클러스터를 로컬 환경에서 손쉽게 시뮬레이션하고, 개발, 테스트, 학습 목적으로 사용됩니다.

Docker(Host) 안에 또 다른 Docker를 실행하여 쿠버네티스 Control Plane과 Work Node를 구성합니다.

Mac에서의 환경 구성

M1 MAX 에서 구성
  1. docker 설치
 

Install Docker Desktop on Mac

Install Docker for Mac to get started. This guide covers system requirements, where to download, and instructions on how to install and update.

docs.docker.com

 

  1. kind 설치
 

kind – Quick Start

Quick Start This guide covers getting started with the kind command. If you are having problems please see the known issues guide. NOTE: kind does not require kubectl, but you will not be able to perform some of the examples in our docs without it. To inst

kind.sigs.k8s.io


# kind 설치
brew install kind

# kubectl 설치  
brew install kubernetes-cli 
kubectl version --client=true

# helm(패키지도구) 설치
brew install helm

# 클러스터 구성
kind create cluster 

# Cluster 정보 확인
kind get clusters
kind get nodes
kubectl cluster-info
kubectl get nodes -o wide 
kubectl get pods -A  

클러스터 구성을 확인하면 기본 쿠버네티스 파드들이 확인됩니다.

아키텍처 구성과 같이 하나의 이미지(컨테이너)에서 위 파드들이 구동됨을 확인할 수 있습니다.

컨테이너 정보를 확인하면 다음과 같습니다.


 docker inspect kind-control-plane 
 ...
 # 이미지
  "Image": "kindest/node:v1.31.0@sha256:53df588e04085fd41ae12de0c3fe4c72f7013bba32a20e7325357a1ac94ba865", 

# 네트워크 설정 
"Networks": {
    "kind": {
        "IPAMConfig": null,
        "Links": null,
        "Aliases": [
            "0dc12e56a0d0",
            "kind-control-plane"
        ],
        "MacAddress": "02:42:ac:12:00:02",
        "NetworkID": "c05a1e707c761b687f897d4aa5e32d24d583d7a4fc33018af75a5b95f13af12b",
        "EndpointID": "e35faf647a245584561ffa6ae68791c4b706987bb02e24e869ab1679513b9459",
        "Gateway": "172.18.0.1",
        "IPAddress": "172.18.0.2",
        "IPPrefixLen": 16,
        "IPv6Gateway": "fc00:f853:ccd:e793::1",
        "GlobalIPv6Address": "fc00:f853:ccd:e793::2",
        "GlobalIPv6PrefixLen": 64,
        "DriverOpts": null,
        "DNSNames": [
            "kind-control-plane",
            "0dc12e56a0d0"
        ]
    }
} 
# 볼륨 설정 
"Mounts": [
    {
        "Type": "bind",
        "Source": "/lib/modules",
        "Destination": "/lib/modules",
        "Mode": "ro",
        "RW": false,
        "Propagation": "rprivate"
    },
    {
        "Type": "volume",
        "Name": "ee52c26f3eee17cb4d7bd3817d0ea7e9f7010c94d67299912805d8ff70140c66",
        "Source": "/var/lib/docker/volumes/ee52c26f3eee17cb4d7bd3817d0ea7e9f7010c94d67299912805d8ff70140c66/_data",
        "Destination": "/var",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    }
],
 
# 실행 환경 확인
"Entrypoint": [
    "/usr/local/bin/entrypoint",
    "/sbin/init"
],
"Env": [
  "KUBECONFIG=/etc/kubernetes/admin.conf", # 클러스터 접근 키 
  "KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER",
  "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  "container=docker",
  "HTTP_PROXY=",
  "HTTPS_PROXY=",
  "NO_PROXY="
]
.. 
  • 네트워크 대역이 172.18.0.1, 172.18.0.2 로 설정
  • 볼륨은 로컬 /var/lib 에 마운트
  • 쿠버네티스 실행 변수는 /etc/kubernetes/admin.conf 에서 확인이 가능합니다.

쿠버네티스 구동 정보도 확인하겠습니다.

kubectl get node -o wide
kubectl get pods -A -o wide 
kubectl get pods -v 6
kubectl get deploy -n local-path-storage 
  • CRI는 containerd, CNI는 kindnet 을 사용하며, 컨트롤 플레인은 로컬 포트 52645를 통해 운영 중임을 확인할 수 있습니다.
  • 볼륨은 local-path-provisioner를 이용하여 로컬 볼륨과 마운트됩니다.

kind 컨테이너 안에서 쿠버네티스 정보를 확인하겠습니다.

# 컨테이너 접근
docker exec -it myk8s-control-plane /bin/bash

grep staticPodPath /var/lib/kubelet/config.yaml
tree /etc/kubernetes

# 프로세스 확인
ps -ef 

# CRI(containerd) cli - crictl 사용
crictl ps 
crictl image
  • 1번(초기화 프로세스)에 systemd 프로세스 확인
  • crictl 은 CRI 표준을 따르는 런타임(예: containerd, CRI-O) 의 커맨드라인 툴입니다. 컨테이너의 CRI가 containerd로 호환성을 가진 crictl를 사용해야 확인이 가능합니다.

이어서 쿠버네티스 모니터링 도구인 kube_ops_view 와 nginx 파드를 배포하여 구성을 확인하겠습니다.

다만 그전에 워커노드 호스트 포트와 쿠버네티스 포트 매핑을 위해 클러스터 수정 작업이 필요합니다.

kind에서는 클러스터 수정 작업이 없는 관계로 클러스터를 삭제하고 포트 매핑 옵션을 통해 다시 생성해주세요.


#kind 클러스터 삭제 
kind delete clusters kind 


# 포트 매핑
cat <<EOT> kind-2node.yaml
# two node (one workers) cluster config
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
  extraPortMappings:
  - containerPort: 31000
    hostPort: 31000
    listenAddress: "0.0.0.0" # Optional, defaults to "0.0.0.0"
    protocol: tcp # Optional, defaults to tcp
  - containerPort: 31001
    hostPort: 31001
EOT

# 클러스터 구성
CLUSTERNAME=horang
kind create cluster --config kind-2node.yaml --name $CLUSTERNAME

 

kube_ops_view 설치

# kube_ops_view 설치 
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=31000 --set env.TZ="Asia/Seoul" --namespace kube-system

# 설치 확인 
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view 

# 도메인 접근 
localhost:31000 

세부 항목 확인시 로컬 포트에서 31000 포트를 허용하는 룰이 추가되었습니다.

Chain KUBE-NODEPORTS (1 references)
target     prot opt source               destination         
KUBE-EXT-7EJNTS7AENER2WX5  tcp  --  anywhere             127.0.0.0/8          /* kube-system/kube-ops-view:http */ tcp dpt:31000 nfacct-name  localhost_nps_accepted_pkts
KUBE-EXT-7EJNTS7AENER2WX5  tcp  --  anywhere             anywhere             /* kube-system/kube-ops-view:http */ tcp dpt:31000

 

Nginx 배포

이어서 nginx 파드를 배포하겠습니다.

# 파드 생성
# 디플로이먼트와 서비스 배포
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-websrv
spec:
  replicas: 2
  selector:
    matchLabels:
      app: deploy-websrv
  template:
    metadata:
      labels:
        app: deploy-websrv
    spec:
      terminationGracePeriodSeconds: 0
      containers:
      - name: deploy-websrv
        image: nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: deploy-websrv
spec:
  ports:
    - name: svc-webport
      port: 80
      targetPort: 80
      nodePort: 31001
  selector:
    app: deploy-websrv
  type: NodePort
EOF

kubectl get pods,svc 

맥에서 kind를 통해 테스트 쿠버네티스 클러스터를 구성하였습니다.

무엇보다도 매력적인 점은 무료인점과 클러스터 구성에 30초 이내 할 수 있다는 점이 있어 추후 테스트 과정에 있어 자주 사용할 것 같습니다.


파드 & PAUSE 컨테이너

위에서 예제 파드(kube-ops-view, nginx) 를 배포하였습니다.

예제 파드의 프로세스를 확인하면 Pause라는 프로세스가 확인됩니다.

# 사전 툴 설치
docker exec -it horang-control-plane sh -c 'apt update && apt install tree jq psmisc lsof wget bridge-utils tcpdump htop git nano -y'
docker exec -it horang-worker sh -c 'apt update && apt install tree jq psmisc lsof wget bridge-utils tcpdump htop git nano -y'

# 워크 노드 접속 
docker exec -it horang-woker /binbash 

# 워크 노드 프로세스 확인
pstree -aclnpsS

pause 컨테이너는 파드 내 모든 컨테이너의 네임스페이스(NET, MNT, UTS) 를 공유시켜줍니다. 부모프로세스로 동작하여 자식 컨테이너를 구성시키며 컨테이너간 네트워크 통신, 볼륨, 일관성을 가질 수 있습니다.

  • Pod 내 특정 컨테이너가 비정상 종료되어, namespace에 문제가 발생하는 것을 방지하는 역할도 대신함
  • 부모 프로세스(1) 로 구동되어 자식프로세스 관리함
pause 구동(KANS 3기)

pause 코드를 확인하면 신호를 처리하고 무한 대기 상태를 가집니다.

SIGINT(인터럽트), SIGTERM(종료요청), SIGCHLD(자식프로세스 종료) 에따라 반환 값을 전달하며 그 외 상황에는 대기 상태를 가집니다.

pstree -aclnpsS # 프로세스 번호 확인
lsns -p 1 
lsns -p 1589 # pause 프로세스 번호 

 

Ephemeral Container

운영 중인 파드에 임시 컨테이너를 주입하여 디버깅 및 분석에 활용할 수 있습니다. 운영 파드 이미지들은 경량화로 기타 디버깅 툴 설치가 필요할 수 있으며 서비스 영향이 있을 수 있어 컨테이너를 주입하여 디버깅할 수 있습니다.

쿠버네티스 1.25 로 편입된 기능으로 공식 문서에도 참고 가능합니다.

배포한 kube-ops-view 파드를 기반으로 확인하겠습니다.

kube-ops-view 파드 내부에서 디버깅 명령어(ps, curl, top) 확인 시 명령어가 없음을 확인할 수 있습니다.

kubectl exec -it kube-ops-view-657dbc6cd8-bs8kj -n kube-system -- bin/bash 
Copy
# kubectl debug --it 파드 이름 --image 디버깅이미지  --target 대상 컨테이너 
kubectl debug -it kube-ops-view-657dbc6cd8-bs8kj --image nicolaka/netshoot --target kube-ops-view  -n kube-system

접근하여 디버깅 명령어 시 정상적으로 작동됨을 확인할 수 있습니다.

파드 내 확인시 Ephermeral Container 도 표시됩니다.

  • 임시 파드에서 대상 컨테이너를 확인할 수 있는 이유는 pause 컨테이너를 통해 Ns, mnt, uts 네임스페이스를 공유받기 때문입니다.

디버깅 기능이 매력적이지만, 서비스 운영 파드에 붙이는 것은 리스크가 있을 수 있습니다. 이럴 경우 대상 파드의 복사본을 만들어서 확인이 가능합니다.

kubectl debug -it kube-ops-view-657dbc6cd8-bs8kj --image nicolaka/netshoot -c  kube-ops-view  -n kube-system --copy-to debug-copy-demo \
--share-processes

다만,, ps 확인시 복제본임에도 실제 운영 컨테이너가 올라가있진 않습니다.

덤프를 뜨거나 일부 테스트에 유용할 것 같습니다.


kubectl-flame

Ephemeral Container 와 비슷한 성격으로 운영 파드를 프로파일링해주는 툴이 있어 소개합니다.

해당 툴을 이용하면 운영 파드의 대한 수정없이 CPU FlameGraph 확인할 수 있습니다.

동작 원리는 Ephemeral Container 와 똑같습니다. FlameGrpah 컨테이너를 삽입하여 프로세스 공유를 통해 그래프를 생성합니다. FlameGrpah 컨테이너들은 언어별로 다르게 설정되어 제공됩니다.

설치는 krew 을 통해 할 수 있습니다. 다만 Mac의 경우 flame 설치가 불가능합니다.

# krew 플러그인 설치
# macOS/Linux 
(
  set -x; cd "$(mktemp -d)" &&
  OS="$(uname | tr '[:upper:]' '[:lower:]')" &&
  ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')" &&
  KREW="krew-${OS}_${ARCH}" &&
  curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/${KREW}.tar.gz" &&
  tar zxvf "${KREW}.tar.gz" &&
  ./"${KREW}" install krew
)

export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"


#kubectl-flame 설치
kubectl krew install flame

필자의 환경은 Mac이라 flame이 지원하지 않습니다…

 

 

 

 

쿠버네티스 네트워크 CNI

이전 블로그 글에서 도커 없이 컨테이너 네트워킹 구성 방법을 확인했습니다.

그렇다면 쿠버네티스 네트워크는 어떻게 구성해야 할까요?

도커 컨테이너와의 차이점이라면 쿠버네티스는 여러 노드로 구성해야 한다는 점입니다.

파드를 구성한다면 다음의 문제점이 발생할 수 있습니다. (참고 : 전효준님의 네트워크 이해하기)

위 문제를 해결하기 위해서 쿠버네티스 네트워크 모델은 4가지 요구사항이 있습니다.

  1. 파드들은 각자 고유한 IP를 가진다. * 클러스터 IP대역과 파드 사용 IP대역이 중복되지 말아야함
  2. 클러스터 내 모든 파드들은 NAT(Network Address Translation)없이 통신이 가능해야 한다.
  3. 호스트 네트워크를 사용하는 파드는 NAT없이 파드와 통신해야 한다.
  4. 노드의 에이전트는 파드와 통신이 가능해야 한다.

위 과정을 거친다면 파드 내 컨테이너는 아래 4가지의 동작(루프백, 파드 내부, 클러스터 내부, 클러스터 외부)에 대해 통신이 가능해야 합니다.

KANS3

쿠버네티스에서는 이러한 네트워크 통신을 위해 CNI(Container Network Interface)를 정의했습니다.

CNI를 통해 파드가 생성,삭제될 때 네트워크 연결을 구현하며, IPAM를 통해 파드IP 할당 관리를 수행할 수 있게됩니다.

KANS3

쿠버네티스 CNI 로 AWS VPC CNI, Flannel, Calico, Cilium 등이 있으나 이번 글에서는 Flannel CNI를 확인하겠습니다.

 

Flannel CNI

Flannel은 단일 바이너리 에이전트(flanneId)가 각 노드에서 동작하여 쿠버네티스 클러스터 내 파드 통신 환경을 구성해주는 CNI입니다. 대표 네트워크 모드인 VXLAN 모드는 네트워크 오버레이 기술인 VXLAN을 통해 패킷을 캡슐화하여 노드 간 통신 환경을 구성합니다.

통신 구성 동작을 확인하면 cni0(bridge) 를 통해 파드간 내부 통신을 하며, 외부 통신은 flannel.1 을 통한 캡슐화를 통해 통신합니다.

다른 노드간 파드 통신(출처 : KANS3기)
동일 노드 내 파드 간 내부 통신(출처 KANS3기)

Kind에서 Flannel VXLAN모드를 구성하여 확인하겠습니다.

# 기존 k8s 클러스터 확인
kind get clusters 

# 기존 클러스터 삭제
kind delete clusters horang 


# Flannel 클러스터 구성 
cat <<EOF> kind-cni.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  labels:
    mynode: control-plane
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
  - containerPort: 30002
    hostPort: 30002
  kubeadmConfigPatches:
  - |
    kind: ClusterConfiguration
    controllerManager:
      extraArgs:
        bind-address: 0.0.0.0
    etcd:
      local:
        extraArgs:
          listen-metrics-urls: http://0.0.0.0:2381
    scheduler:
      extraArgs:
        bind-address: 0.0.0.0
  - |
    kind: KubeProxyConfiguration
    metricsBindAddress: 0.0.0.0
- role: worker
  labels:
    mynode: worker
- role: worker
  labels:
    mynode: worker2
networking: 
  disableDefaultCNI: true
EOF
kind create cluster --config kind-cni.yaml --name myk8s --image kindest/node:v1.30.4 


# core-dns 파드 확인 
kubectl describe pod -n kube-system -l k8s-app=kube-dns 
  • 기본 네트워크 (kindnet) 비활성화
# flannel CNI 설치 
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
 
# 구성 확인 
kubectl get ds,pod,cm -n kube-flannel 

# Bridge 설정 필요 (/opt/cni/bin 경로 파일 존재하지 않음)
kubectl describe pod -n kube-system -l k8s-app=kube-dns 

Bridge 파일은 스터디를 통해 전달받았습니다.

# bridge 복사  
docker cp bridge myk8s-control-plane:/opt/cni/bin/bridge
docker cp bridge myk8s-worker:/opt/cni/bin/bridge
docker cp bridge myk8s-worker2:/opt/cni/bin/bridge

# bridge 권한 부여 
docker exec -it myk8s-control-plane  chmod 755 /opt/cni/bin/bridge
docker exec -it myk8s-worker         chmod 755 /opt/cni/bin/bridge
docker exec -it myk8s-worker2        chmod 755 /opt/cni/bin/bridge

# 구성 확인 
kubectl get pod -A -owide 
kubectl describe cm -n kube-flannel kube-flannel-cfg

flannel 구성 정보를 확인하겠습니다.

# 구성 정보 확인
for i in myk8s-control-plane myk8s-worker myk8s-worker2; do echo ">> node $i <<"; docker exec -it $i cat /run/flannel/subnet.env ; echo; done 
# CIDR 확인
kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}' ;echo 
# annotation 확인
kubectl describe node | grep -A3 Annotations
  • 노드별 SUBNET 할당 정보 및 MASQ 활성화 여부, CIDR 를 확인할 수 있습니다.

노드 안 네트워크 정보는 다음과 같습니다.

# 호스트 & Flannel 네임스페이스 확인 
lsns -p 1
lsns -p $(pgrep flanneld) 

# 노드 네트워크 정보 확인 
ip -c -br addr 
ip -c link 

# cni0 네트워크 인터페이스 확인
ip -c -d addr show cni0

# 브릿지 확인
brctl show

# 라우팅 정보 확인
ip -c route 

# 위 노드 통신 확인
ping -c 1 10.244.0.0 

# Iptables 필터 테이블 정보 확인 : 파드 내 모든 노드에서 전달이 가능한지 확인
iptables -t filter -S | grep 10.244.0.0

# iptabels NAT 테이블 정보 확인 : 외부 통신이세는 마스커레이딩 수행
iptables -t nat -S | grep 'flanneld masq' | grep -v '! -s' 
  • 프로세스 확인시 호스트와 flannel net 네임스페이스가 동일한 것을 알 수 있습니다.
  • 브릿지 cni0가 올라가 있으며, iptables을 통해 내부 통신 규칙이 허용되어 있고 외부는 마스커레이딩되어 있음을 확인할 수 있습니다.

 

파드 두 개를 배포하여 구성 동작을 확인하겠습니다.

cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
  name: pod-1
  labels:
    app: pod
spec:
  nodeSelector:
    kubernetes.io/hostname: myk8s-worker
  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:
  nodeSelector:
    kubernetes.io/hostname: myk8s-worker2
  containers:
  - name: netshoot-pod
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF

# 워커 노드 접속
docker exec -it myk8s-worker  bash

# 브릿지 확인
brctl show cni0 

# CNI 브릿지 내 파드 IP 확인 
tree /var/lib/cni/networks/cbr0
  • 워크 노드별 cni0 네트워크 인터페이스를 확인하면 cni0 브릿지와 veth~ 인터페이스가 생성됩니다.

파드간 통신을 확인하겠습니다.

# 터미널 3개로 테스트합니다.

# pod-1 접속
kubectl exec -it pod-1 -- zsh 

# 워커 노드 1,2 접속
docker exec -it myk8s-worker  bash
docker exec -it myk8s-worker2 bash

# Case 1. 파드 1에서 외부 통신 패킷 확인 
tcpdump -i cni0 -nn icmp 
tcpdump -i eth0 -nn icmp

ping 8.8.8.8

# Case 2. 파드 1에서 파드 2로 통신 패킷 확인
tcpdump -i flannel.1 -nn icmp 

Ping <파드2 IP> 
  • 파드에서 외부 통신시 cni0, eth0 패킷만 수집됩니다.
  • flannel.1 는 파드 간 통신에서 마스커레이드때만 사용됩니다.
# 노드 2 터미널 내 패킷 캡처 
tcpdump -i eth0 -nn udp port 8472 -w /root/vxlan.pcap 

# 파드 1에서 파드 2로 ping
ping <파드2> 

# 호스트 터미널에서 노드 2의 데이터 복사
docker cp myk8s-worker2:/root/vxlan.pcap . 

# 와이어샤크를 통한 패킷 분석 
wireshark vxlan.pcap

패킷 확인시 캡슐화되어 데이터가 전송됨을 확인할 수 있습니다.

  • 데이터 조회가 안되는 경우 와이어샤크 내 VXLAN 포트 변경이 필요합니다. wireshark > preferences > Protocols > VXLAN Port 를 8472로 변경해주세요. 필자는 기본적으로 설정되어 있었습니다.

'Cloud Tech' 카테고리의 다른 글

쿠버네티스 서비스 Iptabls 모드 정리  (0) 2024.09.28
Calico CNI 이해  (0) 2024.09.20
컨테이너 구성 이해와 도커 취약점 점검하기  (0) 2024.08.30
OpenTofu 와 Atlantis 연동하기  (0) 2024.08.03
Kafka on EKS  (0) 2024.07.27