📌 Notice
Kubernetes Advanced Networking Study (=KANS)
k8s 네트워크 장애 시, 네트워크 상세 동작 원리를 기반으로 원인을 찾고 해결하는 내용을 정리한 블로그입니다.
CloudNetaStudy
그룹에서 스터디를 진행하고 있습니다.
Gasida
님께 다시한번 🙇 감사드립니다.
EKS 관련 이전 스터디 내용은 아래 링크를 통해 확인할 수 있습니다.
1주차의 도커/리눅스 기본기능을 이용하여 자원을 격리하고, 네트워크를 구성하는 방법에 대해서 자세하게 학습하였다면 이번주 내용을 비교적 수월하게 학습할 수 있습니다.
특히 이번 과정은 기초를 잘 다져야 그 다음 단계를 이해해 나갈 수 있으므로 최대한 기본 지식의 실습에 중점을 두어 실습을 진행하였습니다.
Docker는 리눅스 네임스페이스와 Cgroups를 활용해 프로세스 격리 및 자원 관리를 수행합니다.
Pause 컨테이너는 파드의 네트워크 네임스페이스를 생성하고, 파드 내 컨테이너는 이를 공유해 통신합니다.
Flannel CNI는 VxLAN 등을 사용해 파드 간 네트워크 통신을 지원하며, 클러스터 내 일관된 네트워크 환경을 제공합니다.
쿠버네티스가 무엇인지 간략하게 정리합니다.
쿠버네티스(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
컨테이너 실행을 담당하는 소프트웨어
Kind는 Kubernetes 클러스터를 로컬에서 실행하기 위해 도커(Docker)를 사용하는 도구로, 개발 및 테스트 목적으로 주로 사용됩니다.
Kind는 Docker 컨테이너 안에 쿠버네티스 클러스터를 쉽게 생성할 수 있어, 쿠버네티스 환경을 빠르게 구축하고 실험할 수 있는 편리한 방법을 제공합니다.
로컬 환경에서 여러 노드 클러스터를 생성하거나, CI(Continuous Integration) 파이프라인에서 쿠버네티스를 테스트할 때 매우 유용합니다.
출처 - https://kind.sigs.k8s.io/
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는 로컬에서 간단하게 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>
Kubernetes CRI(Container Runtime Interface)는 쿠버네티스에서 컨테이너 런타임과의 통신을 위한 표준 인터페이스입니다.
CRI는 쿠버네티스가 다양한 컨테이너 런타임을 지원할 수 있게 하는 핵심 요소로, 이를 통해 쿠버네티스가 특정 런타임에 종속되지 않고 유연하게 동작할 수 있습니다.
컨테이너 런타임의 추상화
CRI는 쿠버네티스가 다양한 컨테이너 런타임(Docker, containerd, CRI-O 등)과 상호 작용할 수 있도록 추상화를 제공합니다. 쿠버네티스는 CRI를 통해 런타임과 상호작용하여 파드를 생성하고 컨테이너를 관리합니다. 이는 런타임 변경 시에도 쿠버네티스의 동작 방식을 유지할 수 있게 합니다.
플러그인 방식으로 다양한 런타임 지원
CRI는 플러그인 구조를 채택하여 쿠버네티스가 다양한 런타임을 지원할 수 있도록 합니다. 예를 들어, 초기에는 Docker가 기본 컨테이너 런타임이었지만, 현재는 containerd나 CRI-O와 같은 보다 경량화되고 쿠버네티스 친화적인 런타임이 널리 사용되고 있습니다.
Kubelet과의 통신
쿠버네티스의 Kubelet은 노드에서 실행되는 주요 에이전트로, CRI를 통해 컨테이너 런타임과 통신하며 파드를 생성, 관리합니다. 이를 통해 쿠버네티스가 컨테이너를 직접 관리하지 않고 런타임과의 상호작용에 집중할 수 있게 됩니다.
파드(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" } ], ...
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
#
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'
#
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'
----------------------------------------
이번 스터디를 통해 쿠버네티스 네트워크 구조와 동작 원리에 대해 깊이 있게 이해할 수 있었습니다.
특히 Docker와 리눅스의 네임스페이스, Cgroups 기술을 바탕으로 한 컨테이너의 자원 격리와 네트워크 구성 방식을 살펴보며, 쿠버네티스의 CNI(컨테이너 네트워크 인터페이스) 플러그인이 어떻게 파드 간 통신을 지원하는지 학습했습니다.
Docker의 네트워크 모델(브리지, 호스트, 논 네트워크 모델)을 기반으로, 쿠버네티스의 네트워크가 가상 이더넷 장치(veth)와 브리지를 통해 어떻게 동작하는지 알 수 있었습니다.
Pause 컨테이너가 각 파드의 네트워크 네임스페이스를 생성 및 관리하고, 파드 내부의 다른 컨테이너들이 이를 공유하여 하나의 애플리케이션처럼 동작하는 원리를 파악했습니다.
Flannel과 같은 CNI 플러그인이 어떻게 네트워크 오버레이 기술을 활용해 클러스터 내 파드 간 통신을 구현하는지, 특히 VXLAN 방식을 통해 네트워크 패킷이 전달되는 과정을 실습했습니다.
쿠버네티스 네트워크는 매우 복잡하면서도 잘 설계된 구조로, 각 컴포넌트가 유기적으로 결합하여 안정적인 클러스터 환경을 제공합니다.
이번 스터디를 통해 이러한 네트워크의 기본 원리와 실제 동작 방식을 이해하게 되었으며, 향후 네트워크 문제 발생 시 보다 신속하게 원인을 분석하고 해결할 수 있는 기반 지식을 쌓을 수 있었습니다.