[KANS3] K8S Flannel CNI & PAUSE

xgro·2024년 9월 6일
0

KANS3

목록 보기
2/9
post-thumbnail

📌 Notice

Kubernetes Advanced Networking Study (=KANS)
k8s 네트워크 장애 시, 네트워크 상세 동작 원리를 기반으로 원인을 찾고 해결하는 내용을 정리한 블로그입니다.

CloudNetaStudy 그룹에서 스터디를 진행하고 있습니다.

Gasida님께 다시한번 🙇 감사드립니다.

EKS 관련 이전 스터디 내용은 아래 링크를 통해 확인할 수 있습니다.

1주차의 도커/리눅스 기본기능을 이용하여 자원을 격리하고, 네트워크를 구성하는 방법에 대해서 자세하게 학습하였다면 이번주 내용을 비교적 수월하게 학습할 수 있습니다.

특히 이번 과정은 기초를 잘 다져야 그 다음 단계를 이해해 나갈 수 있으므로 최대한 기본 지식의 실습에 중점을 두어 실습을 진행하였습니다.



📌 Summary

  • Docker는 리눅스 네임스페이스와 Cgroups를 활용해 프로세스 격리 및 자원 관리를 수행합니다.

  • Pause 컨테이너는 파드의 네트워크 네임스페이스를 생성하고, 파드 내 컨테이너는 이를 공유해 통신합니다.

  • Flannel CNI는 VxLAN 등을 사용해 파드 간 네트워크 통신을 지원하며, 클러스터 내 일관된 네트워크 환경을 제공합니다.



📌 Study

👉 Step 01. 쿠버네티스 소개

쿠버네티스가 무엇인지 간략하게 정리합니다.


✅ 쿠버네티스 소개

쿠버네티스(Kubernetes)는 컨테이너화된 애플리케이션의 배포, 확장 및 관리를 자동화하는 오픈 소스 플랫폼입니다.

이를 통해 개발자는 애플리케이션을 손쉽게 확장하고, 여러 클러스터에서 일관된 환경을 유지할 수 있습니다. 또한, 자가 치유(Self-healing) 메커니즘을 통해 장애가 발생한 파드를 자동으로 복구하여 안정적인 운영을 지원합니다.

쿠버네티스 클러스터는 크게 다음과 같은 두가지 요소로 구성되어 있습니다.

Control Plane Components

클러스터 구성/운영에 필요한 필수 컴포넌트가 관리되는 영역

  • kube-apiserver
    쿠버네티스 API를 노출하는 쿠버네티스 컨트롤 플레인 컴포넌트
  • etcd
    모든 클러스터 데이터를 담는 쿠버네티스 저장소로 사용되는 고가용성 키-값 저장소.
  • kube-controller-manager
    컨트롤러 프로세스를 실행하는 컴포넌트
  • kube-scheduler
    노드가 배정되지 않은 새로 생성된 파드 를 감지하고, 실행할 노드를 선택
  • etc...

Node Components

실행 중인 Pod를 유지 관리하며 Kubernetes 런타임 환경을 제공

  • kubelet
    각 노드에서 실행되는 에이전트. Kubelet은 파드에서 컨테이너가 확실하게 동작하도록 관리
  • kube-proxy
    클러스터의 각 노드에서 실행되는 네트워크 프록시로, 쿠버네티스의 서비스 개념의 구현부
  • Container runtime
    컨테이너 실행을 담당하는 소프트웨어

👉 Step 02. kind 소개 및 설치

Kind는 Kubernetes 클러스터를 로컬에서 실행하기 위해 도커(Docker)를 사용하는 도구로, 개발 및 테스트 목적으로 주로 사용됩니다.

Kind는 Docker 컨테이너 안에 쿠버네티스 클러스터를 쉽게 생성할 수 있어, 쿠버네티스 환경을 빠르게 구축하고 실험할 수 있는 편리한 방법을 제공합니다.

로컬 환경에서 여러 노드 클러스터를 생성하거나, CI(Continuous Integration) 파이프라인에서 쿠버네티스를 테스트할 때 매우 유용합니다.

출처 - https://kind.sigs.k8s.io/

✅ MacOS 설치

M1 Mac을 사용중이므로 환경 설치는 MacOS 기준으로 정리하겠습니다.

실습 환경
Kind 사용하는 도커 엔진 리소스에 최소 vCPU 4, Memory 8GB 할당을 권고 - Link

kind 및 툴 설치

Kind 및 쿠버네티스 관리를 위한 도구를 설치합니다.

# Install Kind
brew install kind
kind --version

# Install kubectl
brew install kubernetes-cli
kubectl version --client=true

# Install Helm
brew install helm
helm version

# Install Wireshark : 캡처된 패킷 확인
brew install --cask wireshark

kind 기본 사용 - 클러스터 배포 및 확인

회사에서 사용 중인 kubeconfig 가 있다면, 미리 kubeconfig 백업 후 kind 실습을 진행하시기 바랍니다.

# 클러스터 배포 전 확인
docker ps

# Create a cluster with kind
kind create cluster

# 클러스터 배포 확인
kind get clusters
kind get nodes
kubectl cluster-info

# 노드 정보 확인
kubectl get node -o wide

# 파드 정보 확인
kubectl get pod -A
kubectl get componentstatuses

# 컨트롤플레인 (컨테이너) 노드 1대가 실행
docker ps
docker images

# kube config 파일 확인
cat ~/.kube/config
혹은
cat $KUBECONFIG # KUBECONFIG 변수 지정 사용 시

# nginx 파드 배포 및 확인 : 컨트롤플레인 노드인데 파드가 배포 될까요?
kubectl run nginx --image=nginx:alpine
kubectl get pod -owide

# 노드에 Taints 정보 확인
kubectl describe node | grep Taints
Taints:             <none>

# 클러스터 삭제
kind delete cluster

# kube config 삭제 확인
cat ~/.kube/config
혹은
cat $KUBECONFIG # KUBECONFIG 변수 지정 사용 시

✅ kind 기본 사용

Kind는 로컬에서 간단하게 Kubernetes 클러스터를 실행할 수 있어, 클라우드 환경을 사용하지 않고도 개발자들이 빠르게 테스트 환경을 구축할 수 있습니다.

특히, 클러스터 구성을 yaml 형태로 선언하여 프로비저닝 할 수 있고 다중 노드 클러스터를 지원하여, 복잡한 네트워크 토폴로지를 테스트하거나 실습할 수 있습니다. 이를 통해 실제와 유사한 환경을 로컬에서도 손쉽게 구성할 수 있습니다.

최신 버전의 쿠버네티스나 특정 버전에서 애플리케이션의 호환성을 빠르게 확인할 수 있습니다.

Kind 클러스터 배포 전 Docker 확인

# 클러스터 배포 전 확인
docker ps

# kind 는 별도 도커 네트워크 생성 후 사용 : 기본값 172.18.0.0/16
docker network ls
docker inspect kind | jq

yaml 형식의 config 파일을 생성하여 쿠버네티스 프로비저닝

# Create a cluster with 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
EOT

# 클러스터 생성
kind create cluster --config kind-2node.yaml --name myk8s

# 확인
kind get nodes --name myk8s

# k8s api 주소 확인 : 어떻게 로컬에서 접속이 되는 걸까?
kubectl cluster-info

# docker 프로세스 확인
docker ps 

# 포트 포워딩 정보 확인
docker exec -it myk8s-control-plane ss -tnlp | grep 6443

# 파드 IP 확인
kubectl get pod -n kube-system -l component=kube-apiserver -owide 

# component=kube-apiserver 라벨을 가진 파드의 정보를 출력
kubectl describe  pod -n kube-system -l component=kube-apiserver

# control-plane 파드에서 6443 포트의 livez 엔드포인트 호출
docker exec -it myk8s-control-plane curl -k https://localhost:6443/livez ;echo

# control-plane 파드에서 6443 포트의 readyz 엔드포인트 호출
docker exec -it myk8s-control-plane curl -k https://localhost:6443/readyz ;echo

# 노드 정보 확인 : CRI 는 containerd 사용
kubectl get node -o wide

# 파드 정보 확인 : CNI 는 kindnet 사용
kubectl get pod -A -owide

# 네임스페이스 확인 >> 도커 컨테이너에서 배운 네임스페이스와 다릅니다!
kubectl get namespaces

# 컨트롤플레인, 워커 컨테이너 각각 1대씩 실행 : 도커 컨테이너 이름은 myk8s-control-plane , myk8s-worker 임을 확인
docker ps
docker images

# 디버그용 내용 출력에 ~/.kube/config 권한 인증 로드
kubectl get pod -v6

# kube config 파일 확인
cat ~/.kube/config
혹은
cat $KUBECONFIG

# local-path 라는 StorageClass 가 설치, local-path 는 노드의 로컬 저장소를 활용함
# 로컬 호스트의 path 를 지정할 필요 없이 local-path provisioner 이 볼륨을 관리
kubectl get sc
kubectl get deploy -n local-path-storage

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


✅ 파드 생성 및 확인

파드(Pod)는 쿠버네티스에서 배포 가능한 가장 작은 단위입니다.
하나의 파드는 하나 이상의 컨테이너를 포함할 수 있으며, 이들 컨테이너는 동일한 네트워크 네임스페이스를 공유합니다. 파드는 컨테이너들이 서로 통신할 수 있도록 지원하고, 필요한 리소스(볼륨, 환경 변수 등)를 공유할 수 있습니다.

이번 예시에서는 두 개의 파드를 생성합니다:

  • netpod: nicolaka/netshoot 이미지를 사용한 파드로, 네트워크 디버깅 도구를 포함하고 있습니다. 이 파드를 사용해 다른 파드와의 네트워크 연결을 테스트할 수 있습니다.
  • nginx: nginx:alpine 이미지를 사용한 웹 서버 파드로, 간단한 웹 페이지를 제공하는 역할을 합니다.

파드가 정상적으로 생성되면 kubectl get pod 명령어로 파드의 상태 및 IP 주소를 확인할 수 있습니다.

각 파드는 클러스터 내에서 고유한 IP를 가지며, 이를 통해 서로 통신할 수 있습니다.

파드를 생성하여 어플리케이션이 배포되는것을 확인합니다.

# 파드 생성
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
  name: netpod
spec:
  containers:
  - name: netshoot-pod
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx-pod
    image: nginx:alpine
  terminationGracePeriodSeconds: 0
EOF

# 파드 확인
kubectl get pod -owide

netpod를 이용해서 nginx 웹에 접속하면 다음과 같이 응답이 오는것을 확인합니다.

# netpod 파드에서 nginx 웹 접속
kubectl exec -it netpod -- curl -s $(kubectl get pod nginx -o jsonpath={.status.podIP}) | grep -o "<title>.*</title>"
<title>Welcome to nginx!</title>



👉 Step 03. 파드 & PAUSE 컨테이너

✅ k8s CRI

Kubernetes CRI(Container Runtime Interface)는 쿠버네티스에서 컨테이너 런타임과의 통신을 위한 표준 인터페이스입니다.

CRI는 쿠버네티스가 다양한 컨테이너 런타임을 지원할 수 있게 하는 핵심 요소로, 이를 통해 쿠버네티스가 특정 런타임에 종속되지 않고 유연하게 동작할 수 있습니다.

CRI의 핵심 역할

  • 컨테이너 런타임의 추상화
    CRI는 쿠버네티스가 다양한 컨테이너 런타임(Docker, containerd, CRI-O 등)과 상호 작용할 수 있도록 추상화를 제공합니다. 쿠버네티스는 CRI를 통해 런타임과 상호작용하여 파드를 생성하고 컨테이너를 관리합니다. 이는 런타임 변경 시에도 쿠버네티스의 동작 방식을 유지할 수 있게 합니다.

  • 플러그인 방식으로 다양한 런타임 지원
    CRI는 플러그인 구조를 채택하여 쿠버네티스가 다양한 런타임을 지원할 수 있도록 합니다. 예를 들어, 초기에는 Docker가 기본 컨테이너 런타임이었지만, 현재는 containerd나 CRI-O와 같은 보다 경량화되고 쿠버네티스 친화적인 런타임이 널리 사용되고 있습니다.

  • Kubelet과의 통신
    쿠버네티스의 Kubelet은 노드에서 실행되는 주요 에이전트로, CRI를 통해 컨테이너 런타임과 통신하며 파드를 생성, 관리합니다. 이를 통해 쿠버네티스가 컨테이너를 직접 관리하지 않고 런타임과의 상호작용에 집중할 수 있게 됩니다.


✅ Pod

파드(Pod)는 쿠버네티스에서 배포할 수 있는 가장 작은 단위로, 하나 이상의 컨테이너를 그룹화한 객체입니다.

파드는 쿠버네티스 클러스터 내에서 실행되는 모든 컨테이너의 실행 환경을 정의하고, 이들 컨테이너가 하나의 애플리케이션처럼 동작하도록 돕습니다.

Pod 내에 실행되는 컨테이너들은 반드시 동일한 노드에 할당되며 동일한 생명 주기를 갖습니다
→ Pod 삭제 시, Pod 내 모든 컨테이너가 삭제 됩니다.

Pod 는 노드 IP 와 별개로 클러스터 내에서 접근 가능한 IP를 할당 받으며, 다른 노드에 위치한 Pod 도 NAT 없이 Pod IP로 접근 가능합니다. → 요걸 CNI 해줌!

Pod 내에 있는 컨테이너들은 서로 IP를 공유, 컨테이너끼리는 localhost 통해 서로 접근하며 포트를 이용해 구분합니다.

pause 컨테이너parent 처럼 network ns 를 만들어 주고, 내부의 컨테이너들은 해당 net ns 를 공유하게 됩니다.

pause 컨테이너에는 두 가지 핵심 책임이 있습니다.

  • 포드에서 Linux 네임스페이스 공유의 기반 역할을 합니다.

  • PID(프로세스 ID) 네임스페이스 공유가 활성화되면 각 포드에 대한 PID 1 역할을 하며 좀비 프로세스를 거둡니다.

Pod는 리소스 제약이 있는 격리된 환경의 애플리케이션 컨테이너 그룹으로 구성됩니다. CRI에서 이 환경을 PodSandbox라고 합니다.

Kubelet은 RPC를 통해 컨테이너의 수명 주기를 관리하고, 컨테이너 수명 주기 후크와 활성/준비 확인을 실행하며, Pod의 재시작 정책을 준수합니다

📚 실습 환경

실습 환경을 위한 클러스터를 생성하고, kube-ops-view를 설치합니다.

# '컨트롤플레인, 워커 노드 1대' 클러스터 배포 : 파드에 접속하기 위한 포트 맵핑 설정
cat <<EOT> kind-2node.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
EOT
kind create cluster --config kind-2node.yaml --name myk8s

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

# 확인
kubectl get nodes -o wide
docker ps
docker port myk8s-worker
docker exec -it myk8s-control-plane ip -br -c -4 addr
docker exec -it myk8s-worker  ip -br -c -4 addr

# 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=30000 --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

# kube-ops-view 접속 URL 확인 (1.5 , 2 배율) : macOS 사용자
echo -e "KUBE-OPS-VIEW URL = http://localhost:30000/#scale=1.5"
echo -e "KUBE-OPS-VIEW URL = http://localhost:30000/#scale=2"

Pod를 배포하고, 격리 상태를 확인합니다.

# [터미널1] myk8s-worker bash 진입 후 실행 및 확인
docker exec -it myk8s-worker bash
----------------------------------
systemctl list-unit-files | grep 'enabled         enabled'
containerd.service                                                                    enabled         enabled
kubelet.service                                                                       enabled         enabled
...

# 
crictl ps

# 확인 : kubelet에 --container-runtime-endpoint=unix:///run/containerd/containerd.sock
pstree -aln
systemd
  |-systemd-journal
  |-containerd
  |   \-12*[{containerd}]
  |-kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime-endpoint=unix:///run/containerd/containerd.sock --node-ip=172.18.0.4 --node-labels=mynode=worker2 --pod-infra-container-image=registry.k8s.io/pause:3.9 --provider-id=kind://docker/myk8s/myk8s-worker2 --runtime-cgroups=/system.slice/containerd.service
  |   \-14*[{kubelet}]
  |-containerd-shim -namespace k8s.io -id e41d2d62c1bb44a955fe13ddef0dbb006c44352fda493e8d76c489138756d2fa -address /run/containerd/containerd.sock
  |   |-12*[{containerd-shim}]
  |   |-pause
  |   \-kube-proxy --config=/var/lib/kube-proxy/config.conf --hostname-override=myk8s-worker2
  |       \-9*[{kube-proxy}]
  |-containerd-shim -namespace k8s.io -id 9768cd57beeee9b0d1dc38e46dce44697113c3e3924d098e7b8c776909852f63 -address /run/containerd/containerd.sock
  |   |-11*[{containerd-shim}]
  |   |-pause
  |   \-flanneld --ip-masq --kube-subnet-mgr
  |       \-10*[{flanneld}]
  \-containerd-shim -namespace k8s.io -id 6bd147995b3a6c17384459eb4d3ceab4369329e6b57c009bdc6257b72254e1fb -address /run/containerd/containerd.sock
      |-11*[{containerd-shim}]
      |-pause
      \-metrics-server --cert-dir=/tmp --secure-port=10250 --kubelet-insecure-tls --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname --kubelet-use-node-status-port --metric-resolution=15s
          \-12*[{metrics-server}]


# 확인 : 파드내에 pause 컨테이너와 metrics-server 컨테이너, 네임스페이스 정보
pstree -aclnpsS
...
	\-containerd-shim,1776 -namespace k8s.io -id 6bd147995b3a6c17384459eb4d3ceab4369329e6b57c009bdc6257b72254e1fb -address /run/containerd/containerd.sock
	|-{containerd-shim},1777
  ...
	|-pause,1102,ipc,mnt,net,pid,uts
  ...


# 네임스페이스 확인 : lsns - List system namespaces
lsns -p 1
lsns -p $$
        NS TYPE   NPROCS PID USER COMMAND
4026531834 time       15   1 root /sbin/init
4026531837 user       15   1 root /sbin/init
4026532525 mnt         9   1 root /sbin/init
4026532550 uts        13   1 root /sbin/init
4026532551 ipc         9   1 root /sbin/init
4026532577 pid         9   1 root /sbin/init
4026532579 net        13   1 root /sbin/init
4026532891 cgroup     13   1 root /sbin/init

# 해당 파드에 pause 컨테이너는 호스트NS와 다른 5개의 NS를 가짐 : mnt/pid 는 pasue 자신만 사용, net/uts/ipc는 app 컨테이너를 위해서 먼저 생성해둠
lsns -p 1102
        NS TYPE   NPROCS   PID USER  COMMAND
4026531834 time       15     1 root  /sbin/init
4026531837 user       15     1 root  /sbin/init
4026532891 cgroup     13     1 root  /sbin/init
4026533496 net         2  1797 65535 /pause
4026533625 uts         2  1797 65535 /pause
4026533626 ipc         2  1797 65535 /pause
4026533624 mnt         1  1797 65535 /pause
4026533627 pid         1  1797 65535 /pause

# app 컨테이너(kube-ops-view)는 호스트NS와 다른 6개의 NS를 가짐 : mnt/pid/cgroup 는 자신만 사용, net/uts/ipc는 pause 컨테이너가 생성한 것을 공유 사용함
pgrep python3
lsns -p $(pgrep python3)
pgrep metrics-server
1896
lsns -p $(pgrep metrics-server)
        NS TYPE   NPROCS   PID USER  COMMAND
4026531834 time       15     1 root  /sbin/init
4026531837 user       15     1 root  /sbin/init
4026533273 net         2  1102 65535 /pause
4026533404 uts         2  1102 65535 /pause
4026533405 ipc         2  1102 65535 /pause
4026533407 mnt         1  1166 1000  /run/rosetta/rosetta /usr/local/bin/python3 python3 -m kube_ops_view
4026533408 pid         1  1166 1000  /run/rosetta/rosetta /usr/local/bin/python3 python3 -m kube_ops_view
4026533409 cgroup      1  1166 1000  /run/rosetta/rosetta /usr/local/bin/python3 python3 -m kube_ops_view

# 소켓 파일 확인
ls -l /run/containerd/containerd.sock

# 특정 소켓 파일을 사용하는 프로세스 확인
lsof /run/containerd/containerd.sock

#
ss -xl | egrep 'Netid|containerd'

#
findmnt -A
TARGET                                                  SOURCE                 FSTYPE    OPTIONS
/                                                       overlay                overlay   rw,relatime,lowerdir=/var/lib/docker/overlay2/l/HW4BGGJ4LV6M5
...
|-/sys                                                  sysfs                  sysfs     ro,nosuid,nodev,noexec,relatime
| |-/sys/kernel/debug                                   debugfs                debugfs   rw,nosuid,nodev,noexec,relatime
| |-/sys/kernel/tracing                                 tracefs                tracefs   rw,nosuid,nodev,noexec,relatime
| |-/sys/fs/fuse/connections                            fusectl                fusectl   rw,nosuid,nodev,noexec,relatime
| |-/sys/kernel/config                                  configfs               configfs  rw,nosuid,nodev,noexec,relatime
| \-/sys/fs/cgroup                                      cgroup                 cgroup2   rw,nosuid,nodev,noexec,relatime

findmnt -t cgroup2

grep cgroup /proc/filesystems

stat -fc %T /sys/fs/cgroup/

----------------------------------

신규파드를 배포하고 확인해봅니다.

# [터미널2] kubectl 명령 실행 및 확인

# Pod 생성 : YAML 파일에 컨테이너가 사용할 포트(TCP 80)을 설정
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: myweb
spec:
  containers:
  - image: nginx:alpine
    name: myweb-container
    ports:
    - containerPort: 80
      protocol: TCP
  terminationGracePeriodSeconds: 0
EOF

# Pod 정보 확인 : pause 컨테이너 정보가 보이는가?
kubectl get pod -o wide
kubectl describe pod myweb
kubectl get pod myweb -o json # status.conditions 에 Type 정보 확인 : 시간 정렬은 안되어 있음..

---

# [터미널1] myk8s-worker bash 진입 후 실행 및 확인
docker exec -it myk8s-worker bash
----------------------------------
crictl ps
pstree -aln
pstree -aclnpsS # 파드내에 pause 컨테이너와 app 컨테이너, 네임스페이스 정보

# 네임스페이스 확인 : lsns - List system namespaces
lsns -p 1
lsns -p $$
lsns -p <pstree -aclnpsS에서 출력된 pause 컨테이너 PID> 
lsns -p $(pgrep nginx) # app 컨테이너(metrics-server)

----------------------------------

# [터미널2] kubectl 명령 실행 및 확인
kubectl delete pod myweb

파드 내부에 2개의 컨테이너를 배포합니다

myweb2.yaml : containers 가 복수형인 것에 주목합니다.

apiVersion: v1
kind: Pod
metadata:
  name: myweb2
spec:
  containers:
  - name: myweb2-nginx
    image: nginx
    ports:
    - containerPort: 80
      protocol: TCP
  - name: myweb2-netshoot
    image: nicolaka/netshoot
    command: ["/bin/bash"]
    args: ["-c", "while true; do sleep 5; curl localhost; done"] # 포드가 종료되지 않도록 유지합니다
  terminationGracePeriodSeconds: 0

파드를 배포하고, 두개의 컨테이너가 어떻게 운영되는지 확인합니다.

# [터미널1] 파드 생성
kubectl apply -f myweb2.yaml

# kind POD 정보 확인 : 컨테이너의 집합
kubectl explain pod
KIND:       Pod
VERSION:    v1
DESCRIPTION:
    Pod is a collection of containers that can run on a host. This resource is created by clients and scheduled onto hosts.
...

# 확인
# pod 정보 READY 에 2/2 를 확인 : pod 내 모든 컨테이너가 정상이여야지 status 가 Running 가 됨
kubectl get pod -owide

# Pod 상세 정보에 컨테이너 2개 정보가 보인다
kubectl describe pod myweb2

Name:         myweb2
...
Containers:
  myweb2-nginx:
    Container ID:   docker://2717dd093ee5c69a918c6c52461f47cf5f0c0330378730ce717d1fcabb0fc748
    Image:          nginx
...
  myweb2-netshoot:
    Container ID:  docker://e3e3aef9ee53ef805336d4b6e0986f63e23c767b1648d18ff09948815c5f06a9
    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

# myweb2-netshoot 컨테이너 zsh 진입
kubectl exec myweb2 -c myweb2-netshoot -it -- zsh
----------------------------------
ifconfig
ss -tnlp
curl localhost # nginx 컨테이너가 아닌데, 로컬 접속 되고 tcp 80 listen 이다. 왜그럴까?
ps -ef # nginx 프로세스 정보가 안보이는데... 
exit
----------------------------------

netshoot 컨테이너 내부에서는 nginx 관련 프로세스 정보가 확인되지 않지만, localhost 80포트로 요청시 응답을 받는것을 확인할 수 있습니다.

새로운 터미널을 생성하고 nginx 파드를 상세히 확인합니다.

# 터미널3 : nginx 컨테이너 웹 접속 로그 출력 : 접속자(myweb2-netshoot)의 IP 가 ::1(ipv6) 혹은 127.0.0.1(ipv4) 이닷!
kubectl logs -f myweb2 -c myweb2-nginx
::1 - - [01/Sep/2024:06:33:26 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.7.1" "-"
혹은
127.0.0.1 - - [16/Jun/2021:06:22:24 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.77.0" "-"

# [터미널2] 
docker exec -it myk8s-worker bash
----------------------------------
# 컨테이너 정보 확인 : POD 와 POD ID가 같음을 확인
crictl ps
CONTAINER           IMAGE               CREATED              STATE               NAME                ATTEMPT             POD ID              POD
ce8f83ca25894       e286c635d1232       6 minutes ago       Running             myweb2-netshoot     0                   1dc0ca7ed2804       myweb2
f1854599cb7c0       195245f0c7927       6 minutes ago       Running             myweb2-nginx        0                   1dc0ca7ed2804       myweb2
...

# 워커 노드에서 컨테이너 프로세스 정보 확인
ps -ef | grep 'nginx -g' | grep -v grep
root       14508   14483  0 09:23 ?        00:00:00 nginx: master process nginx -g daemon off;

ps -ef | grep 'curl' | grep -v grep
root       14596   14574  0 09:23 ?        00:00:00 /bin/bash -c while true; do sleep 5; curl localhost; done

# 각각 프로세스를 변수에 지정
NGINXPID=$(ps -ef | grep 'nginx -g' | grep -v grep | awk '{print $2}')
echo $NGINXPID

NETSHPID=$(ps -ef | grep 'curl' | grep -v grep | awk '{print $2}')
echo $NETSHPID

# 한 파드 내의 각 컨테이너의 네임스페이스 정보 확인
## time, user 네임스페이스는 호스트와 같음, 격리하지 않음
## mnt, uts, pid 네임스페이스는 컨테이너별로 격리
## ipc, uts, net 네임스페이스는 파드 내의 컨테이너 간 공유 (IPC : 컨테이너 프로세스간 공유 - signal, socket, pipe 등)
## Pause 컨테이너는 IPC, Network, UTS 네임스페이스를 생성하고 유지 -> 나머지 컨테이너들은 해당 네임스페이스를 공유하여 사용
## 유저가 실행한 특정 컨테이너가 비정상 종료되어 컨터이너 전체에서 공유되는 네임스페이스에 문제가 발생하는 것을 방지

lsns -p $NGINXPID
        NS TYPE   NPROCS   PID USER  COMMAND
4026531834 time       28     1 root  /sbin/init
4026531837 user       28     1 root  /sbin/init
4026533482 net        12  2112 65535 /pause
4026533611 uts        12  2112 65535 /pause
4026533612 ipc        12  2112 65535 /pause
4026533614 mnt         9  2172 root  nginx: master process nginx -g daemon off;
4026533615 pid         9  2172 root  nginx: master process nginx -g daemon off;
4026533616 cgroup      9  2172 root  nginx: master process nginx -g daemon off;

lsns -p $NETSHPID
        NS TYPE   NPROCS   PID USER  COMMAND
4026531834 time       28     1 root  /sbin/init
4026531837 user       28     1 root  /sbin/init
4026533482 net        12  2112 65535 /pause
4026533611 uts        12  2112 65535 /pause
4026533612 ipc        12  2112 65535 /pause
4026533617 mnt         2  2296 root  /bin/bash -c while true; do sleep 5; curl localhost; done
4026533618 pid         2  2296 root  /bin/bash -c while true; do sleep 5; curl localhost; done
4026533619 cgroup      2  2296 root  /bin/bash -c while true; do sleep 5; curl localhost; done

# pause 정보 확인 : 
PAUSEPID=<각자 자신의 pause PID>
PAUSEPID=1691
lsns -p $PAUSEPID
        NS TYPE   NPROCS   PID USER  COMMAND
4026531834 time       31     1 root  /sbin/init
4026531837 user       31     1 root  /sbin/init
4026532814 cgroup     15     1 root  /sbin/init # cgroup 호스트와 같은 이유는?
4026533410 net        15  1691 65535 /pause
4026533540 mnt         1  1691 65535 /pause     # app 컨테이너와 다른 이유는?
4026533541 uts        15  1691 65535 /pause
4026533542 ipc        15  1691 65535 /pause
4026533543 pid         1  1691 65535 /pause     # app 컨테이너와 다른 이유는?

# 개별 컨테이너에 명령 실행 : IP 동일 확인
crictl ps
crictl ps -q
crictl exec -its ce8f83ca25894 ifconfig
crictl exec -its f1854599cb7c0 ifconfig

# PAUSE 의 NET 네임스페이스 PID 확인 및 IP 정보 확인
lsns -t net
nsenter -t $PAUSEPID -n ip -c addr
nsenter -t $NGINXPID -n ip -c addr
nsenter -t $NETSHPID -n ip -c addr

# 2개의 네임스페이스 비교 , 아래 1691 프로세스의 정제는?
crictl inspect <myweb2-nginx    컨테이너ID> | jq
crictl inspect <myweb2-netshoot 컨테이너ID> | jq
crictl inspect ce8f83ca25894 | jq
crictl inspect f1854599cb7c0 | jq
...
        "namespaces": [
          {
            "type": "pid"
          },
          {
            "type": "ipc",
            "path": "/proc/1691/ns/ipc"
          },
          {
            "type": "uts",
            "path": "/proc/1691/ns/uts"
          },
          {
            "type": "mount"
          },
          {
            "type": "network",
            "path": "/proc/1691/ns/net"
          },
          {
            "type": "cgroup"
          }
        ],
...



👉 Step 04. Flannel CNI

Flannel은 쿠버네티스(Kubernetes)에서 파드 간 네트워크를 구성하기 위한 CNI 플러그인 중 하나로, 파드들이 동일한 가상 네트워크 상에서 통신할 수 있도록 도와줍니다. 클러스터 내부의 파드 네트워크를 쉽게 설정하고 관리할 수 있는 단순하고 효율적인 네트워크 솔루션으로 널리 사용됩니다.

Flannel의 작동 방식
Flannel은 기본적으로 Layer 3 네트워크를 구현합니다. 이는 파드 간의 IP 패킷을 터널링하거나 네트워크를 통해 라우팅하는 방식으로 동작합니다. Flannel이 네트워크 패킷을 처리하는 주요 방식은 다음과 같습니다:

VxLAN 모드:
VxLAN(가상 확장 LAN)은 네트워크 오버레이 기술로, 파드 네트워크 트래픽을 UDP 패킷으로 캡슐화하여 클러스터 내 노드 간의 네트워크 통신을 가능하게 합니다. VxLAN은 대규모 클러스터에서 성능을 높이는 데 유리합니다.

Host-GW 모드:
이 모드에서는 Flannel이 호스트의 라우팅 테이블을 이용하여 파드 간 트래픽을 직접 전송합니다. VxLAN처럼 캡슐화 과정이 없기 때문에 성능이 좋지만, 물리 네트워크가 지원하는 범위 내에서만 사용 가능합니다.

UDP 모드:
초기 버전의 Flannel에서 기본으로 사용되던 모드로, 네트워크 패킷을 UDP로 캡슐화하여 전송합니다. 성능은 VxLAN보다 떨어지지만 설정이 간단합니다.


출처 - https://docs.openshift.com/container-platform/3.4/architecture/additional_concepts/flannel.html

✅ kind & 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

# 배포 확인
kind get clusters
kind get nodes --name myk8s
kubectl cluster-info

# 네트워크 확인
kubectl cluster-info dump | grep -m 2 -E "cluster-cidr|service-cluster-ip-range"

# 노드 확인 : CRI
kubectl get nodes -o wide

# 노드 라벨 확인
kubectl get nodes myk8s-control-plane -o jsonpath={.metadata.labels} | jq
...
"mynode": "control-plane",
...

kubectl get nodes myk8s-worker -o jsonpath={.metadata.labels} | jq
kubectl get nodes myk8s-worker2 -o jsonpath={.metadata.labels} | jq

# 컨테이너 확인 : 컨테이너 갯수, 컨테이너 이름 확인
docker ps
docker port myk8s-control-plane
docker port myk8s-worker
docker port myk8s-worker2

# 컨테이너 내부 정보 확인
docker exec -it myk8s-control-plane ip -br -c -4 addr
docker exec -it myk8s-worker  ip -br -c -4 addr
docker exec -it myk8s-worker2  ip -br -c -4 addr

#
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree jq psmisc lsof wget bridge-utils tcpdump iputils-ping htop git nano -y'
docker exec -it myk8s-worker  sh -c 'apt update && apt install tree jq psmisc lsof wget bridge-utils tcpdump iputils-ping -y'
docker exec -it myk8s-worker2 sh -c 'apt update && apt install tree jq psmisc lsof wget bridge-utils tcpdump iputils-ping -y'

✅ Flannel 정보 확인

#
kubectl get ds,pod,cm -n kube-flannel -owide
kubectl describe cm -n kube-flannel kube-flannel-cfg

# iptables 정보 확인
for i in filter nat mangle raw ; do echo ">> IPTables Type : $i <<"; docker exec -it myk8s-control-plane  iptables -t $i -S ; echo; done
for i in filter nat mangle raw ; do echo ">> IPTables Type : $i <<"; docker exec -it myk8s-worker  iptables -t $i -S ; echo; done
for i in filter nat mangle raw ; do echo ">> IPTables Type : $i <<"; docker exec -it myk8s-worker2 iptables -t $i -S ; echo; done


# flannel 정보 확인 : 대역, MTU
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
>> node myk8s-control-plane <<
FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.0.1/24 # 해당 노드에 파드가 배포 시 할당 할 수 있는 네트워크 대역
FLANNEL_MTU=65485 # MTU 지정
FLANNEL_IPMASQ=true # 파드가 외부(인터넷) 통신 시 해당 노드의 마스커레이딩을 사용
...

# 노드마다 할당된 dedicated subnet (podCIDR) 확인
kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}' ;echo

# 노드 정보 중 flannel 관련 정보 확인 : VXLAN 모드 정보와, VTEP 정보(노드 IP, VtepMac) 를 확인
kubectl describe node | grep -A3 Annotations

# 각 노드(?) 마다 bash 진입 후 아래 기본 정보 확인 : 먼저 worker 부터 bash 진입 후 확인하자
docker exec -it myk8s-worker        bash
docker exec -it myk8s-worker2       bash
docker exec -it myk8s-control-plane bash
----------------------------------------
# 호스트 네트워크 NS와 flannel, kube-proxy 컨테이너의 네트워크 NS 비교 : 파드의 IP와 호스트(서버)의 IP를 비교해보자!
lsns -p 1
lsns -p $(pgrep flanneld)
lsns -p $(pgrep kube-proxy)


# 기본 네트워크 정보 확인
ip -c -br addr
ip -c link | grep -E 'flannel|cni|veth' -A1
ip -c addr
ip -c -d addr show cni0     # 네트워크 네임스페이스 격리 파드가 1개 이상 배치 시 확인됨

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 1e:81:fc:d5:8a:42 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
    
brctl show
bridge name	bridge id		STP enabled	interfaces
cni0		8000.663bf746b6a8	no		vethbce1591c
							vethc17ba51b
							vethe6540260

# 라우팅 정보 확인 : 다른 노드의 파드 대역(podCIDR)의 라우팅 정보가 업데이트되어 있음을 확인		
ip -c route
default via 172.18.0.1 dev eth0 
10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink 
10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1 
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink 
172.18.0.0/16 dev eth0 proto kernel scope link src 172.18.0.3 

# flannel.1 인터페이스를 통한 ARP 테이블 정보 확인 : 다른 노드의 flannel.1 IP와 MAC 정보를 확인
ip -c neigh show dev flannel.1

# 브리지 fdb 정보에서 해당 MAC 주소와 통신 시 각 노드의 enp0s8 
bridge fdb show dev flannel.1

# 다른 노드의 flannel.1 인터페이스로 ping 통신 : VXLAN 오버레이를 통해서 통신
ping -c 1 10.244.0.0
ping -c 1 10.244.1.0
ping -c 1 10.244.2.0

# iptables 필터 테이블 정보 확인 : 파드의 10.244.0.0/16 대역 끼리는 모든 노드에서 전달이 가능
iptables -t filter -S | grep 10.244.0.0

# iptables NAT 테이블 정보 확인 : 10.244.0.0/16 대역 끼리 통신은 마스커레이딩 없이 통신을 하며,
# 10.244.0.0/16 대역에서 동일 대역(10.244.0.0/16)과 멀티캐스트 대역(224.0.0.0/4) 를 제외한 나머지 (외부) 통신 시에는 마스커레이딩을 수행
iptables -t nat -S | grep 'flanneld masq' | grep -v '! -s'

----------------------------------------



📌 Conclusion

이번 스터디를 통해 쿠버네티스 네트워크 구조와 동작 원리에 대해 깊이 있게 이해할 수 있었습니다.

특히 Docker와 리눅스의 네임스페이스, Cgroups 기술을 바탕으로 한 컨테이너의 자원 격리와 네트워크 구성 방식을 살펴보며, 쿠버네티스의 CNI(컨테이너 네트워크 인터페이스) 플러그인이 어떻게 파드 간 통신을 지원하는지 학습했습니다.

  • Docker의 네트워크 모델(브리지, 호스트, 논 네트워크 모델)을 기반으로, 쿠버네티스의 네트워크가 가상 이더넷 장치(veth)와 브리지를 통해 어떻게 동작하는지 알 수 있었습니다.

  • Pause 컨테이너가 각 파드의 네트워크 네임스페이스를 생성 및 관리하고, 파드 내부의 다른 컨테이너들이 이를 공유하여 하나의 애플리케이션처럼 동작하는 원리를 파악했습니다.

  • Flannel과 같은 CNI 플러그인이 어떻게 네트워크 오버레이 기술을 활용해 클러스터 내 파드 간 통신을 구현하는지, 특히 VXLAN 방식을 통해 네트워크 패킷이 전달되는 과정을 실습했습니다.

쿠버네티스 네트워크는 매우 복잡하면서도 잘 설계된 구조로, 각 컴포넌트가 유기적으로 결합하여 안정적인 클러스터 환경을 제공합니다.

이번 스터디를 통해 이러한 네트워크의 기본 원리와 실제 동작 방식을 이해하게 되었으며, 향후 네트워크 문제 발생 시 보다 신속하게 원인을 분석하고 해결할 수 있는 기반 지식을 쌓을 수 있었습니다.



🔗 Reference

profile
안녕하세요! DevOps 엔지니어 이재찬입니다. 블로그에 대한 피드백은 언제나 환영합니다! 기술, 개발, 운영에 관한 다양한 주제로 함께 나누며, 더 나은 협업과 효율적인 개발 환경을 만드는 과정에 대해 인사이트를 나누고 싶습니다. 함께 여행하는 기분으로, 즐겁게 읽어주시면 감사하겠습니다! 🚀

0개의 댓글

관련 채용 정보