Overview
지난 글에서 도커 네트워크를 확인한 것을 바탕으로 쿠버네티스 네트워크 구성을 살펴보겠습니다. 블로그 내용은 CloudNet@ KANS3 스터디를 참고하였습니다.
Kind(Kubernetes in Docker)
Kind는 도커 컨테이너 위에 로컬 kubernetes 클러스터를 실행하기 위한 도구 입니다. Kind는 Kubernetes 클러스터를 로컬 환경에서 손쉽게 시뮬레이션하고, 개발, 테스트, 학습 목적으로 사용됩니다.
Docker(Host) 안에 또 다른 Docker를 실행하여 쿠버네티스 Control Plane과 Work Node를 구성합니다.
Mac에서의 환경 구성
M1 MAX 에서 구성
- docker 설치
- kind 설치
# 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 코드를 확인하면 신호를 처리하고 무한 대기 상태를 가집니다.
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
# 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 컨테이너들은 언어별로 다르게 설정되어 제공됩니다.
- Java Application : async-profiler
- Golang : ebpf profiling
- Python : py-spy
- Ruby : rbspy
- NodeJs: perf
설치는 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가지 요구사항이 있습니다.
- 파드들은 각자 고유한 IP를 가진다. * 클러스터 IP대역과 파드 사용 IP대역이 중복되지 말아야함
- 클러스터 내 모든 파드들은 NAT(Network Address Translation)없이 통신이 가능해야 한다.
- 호스트 네트워크를 사용하는 파드는 NAT없이 파드와 통신해야 한다.
- 노드의 에이전트는 파드와 통신이 가능해야 한다.
위 과정을 거친다면 파드 내 컨테이너는 아래 4가지의 동작(루프백, 파드 내부, 클러스터 내부, 클러스터 외부)에 대해 통신이 가능해야 합니다.
쿠버네티스에서는 이러한 네트워크 통신을 위해 CNI(Container Network Interface)를 정의했습니다.
CNI를 통해 파드가 생성,삭제될 때 네트워크 연결을 구현하며, IPAM를 통해 파드IP 할당 관리를 수행할 수 있게됩니다.
쿠버네티스 CNI 로 AWS VPC CNI, Flannel, Calico, Cilium 등이 있으나 이번 글에서는 Flannel CNI를 확인하겠습니다.
Flannel CNI
Flannel은 단일 바이너리 에이전트(flanneId)가 각 노드에서 동작하여 쿠버네티스 클러스터 내 파드 통신 환경을 구성해주는 CNI입니다. 대표 네트워크 모드인 VXLAN 모드는 네트워크 오버레이 기술인 VXLAN을 통해 패킷을 캡슐화하여 노드 간 통신 환경을 구성합니다.
통신 구성 동작을 확인하면 cni0(bridge) 를 통해 파드간 내부 통신을 하며, 외부 통신은 flannel.1 을 통한 캡슐화를 통해 통신합니다.
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 |