들어가며
이번 포스팅은 CloudNet@ 커뮤니티에서 주최하는 KANS 스터디 2주 차 주제인 "K8S Flannel CNI & PAUSE"에 대해서 정리한 내용입니다.
(스터디 내용이 많아 "K8S Pause container(1)"와 "K8S Flannel CNI(2)"로 포스팅을 나누어 작성합니다.)
쿠버네티스 딥다이브
Docker의 컨테이너 실행 관련 계층 구조
주요 컴포넌트를 정리하자면 다음과 같습니다.
- Docker Client: 사용자는 Docker 클라이언트를 통해 Docker와 상호작용하며, Docker 데몬과 Socket/API를 통해 통신합니다.
- Docker daemon: Docker daemon(dockerd)은 Docker API 요청을 처리하고 이미지, 컨테이너, 네트워크, 볼륨과 같은 Docker 객체를 관리합니다. 데몬은 gRPC 호출을 통해 컨테이너 런타임과 통신합니다.
- containerd(고수준 런타임): containerd는 다음과 같은 역할을 수행합니다:
- 컨테이너 수명 주기 관리
- 컨테이너 이미지 관리
- 이미지를 OCI(Open Container Initiative) 런타임 번들로 풀어냅니다.
- runc를 호출하여 컨테이너를 실행합니다.
- containerd-shim: containerd와 runc 사이에 위치한 경량 프로세스로, containerd가 컨테이너 프로세스에 의해 차단되지 않고 컨테이너의 수명 주기를 관리할 수 있게 합니다. shim은 containerd가 재시작되거나 충돌할 경우에도 컨테이너가 계속 실행될 수 있도록 합니다.
- runc(저수준 런타임): OCI 컨테이너 런타임 사양의 참조 구현체로, 실제 컨테이너의 생성과 실행을 담당합니다.
- 애플리케이션: 컨테이너 내에서 실행되는 실제 애플리케이션을 나타냅니다.
Kubelet Architecture
출처: https://netpple.github.io/docs/deepdive-into-kubernetes/k8s-kubelet
kubelet은 Sync Loop를 기반으로 다양한 처리를 합니다. 아키텍처에 포함된 각 컴포넌트들은 다음과 같습니다.
- Kubelet API를 위한 포트
- 10250 (secure): 보안 통신을 위한 포트입니다.
- 10255 (read-only): 읽기 전용 포트입니다.
- 10248 (healthz): 헬스 체크를 위한 포트입니다.
- 주요 관리자 컴포넌트:
- StatusManager: 노드 및 포드 상태 정보를 관리합니다.
- NodeManager: 노드와 관련된 관리를 수행합니다.
- Prober/ProbeManager: 포드와 컨테이너의 상태를 체크하기 위한 프로브 기능을 제공합니다.
- cAdvisor: 컨테이너의 자원 사용량과 성능을 모니터링합니다.
- PodManager: 포드 관리를 담당하며, 포드의 상태와 생명 주기를 제어합니다.
- ImageManager: 컨테이너 이미지를 관리합니다.
- PluginManager: 플러그인 관리를 담당합니다.
- VolumeManager: 볼륨과 관련된 작업을 관리합니다.
- PodWorker Pool: 포드 작업을 병렬로 처리하는 작업자 풀입니다.
- EvictionManager: 리소스 부족 시 포드 축출을 관리합니다.
- CertificateManager: 인증서를 관리합니다.
- Garbage Collection (GC) 및 기타 관리자:
- Image GC: 사용되지 않는 이미지의 가비지 수집을 수행합니다.
- Container GC: 사용되지 않는 컨테이너의 가비지 수집을 수행합니다.
- OOMWatcher: OOM(Out-Of-Memory) 이벤트를 감시합니다.
- 기타 컴포넌트:
- Sync Loop: 포드 상태와 설정 간의 동기화를 유지하는 루프입니다.
- PLEG (Pod Lifecycle Event Generator): 포드의 라이프사이클 이벤트를 생성하는 역할을 합니다.
- CRI (Container Runtime Interface): 컨테이너 런타임 인터페이스로, Kubelet이 컨테이너 런타임과 상호 작용하는 데 사용됩니다.
- OCI (Open Container Initiative): 컨테이너 포맷 및 런타임 사양을 정의하는 표준입니다.
- CNI (Container Network Interface): 컨테이너 네트워크 인터페이스로, 네트워크 설정을 위한 플러그인을 지원합니다.
- CSI (Container Storage Interface): 컨테이너 스토리지 인터페이스로, 스토리지 설정을 위한 플러그인을 지원합니다.
kubelet은 고수준 런타임인 containerd와 앞서 설명한 CRI를 기반으로 통신합니다.
통신에는 grpc가 사용되며 kubelet이 grpc client가 되고, containerd가 grpc server가 됩니다. kubelet은 이러한 형태로 containerd를 이용하여 컨테이너 라이프사이클 관리, 이미지 관리 및 번들링, 컨테이너런타임 실행등을 담당합니다. 실제적으로 container의 처리는 contaienrd가 OCI를 통해 저수준의 런타임인 RunC로 보내게 되어 각 컨테이너들을 제어하게 됩니다..!
kind(kubernetes in docker) 소개 및 설치
링크 - https://kind.sigs.k8s.io/
kind는 Docker 컨테이너 "노드"를 사용하여 로컬 Kubernetes 클러스터를 실행하기 위한 도구입니다. 주로 Kubernetes 자체를 테스트하기 위해 설계되었지만 로컬 개발이나 CI에도 사용될 수 있습니다.
kind는 kubeadm을 통해 클러스터 노드를 관리합니다. 아래의 그림처럼 워커노드들이 host의 docker runtime 위에 docker container 형태로 구성되어 있습니다.
Kind 실습
리소스 : vCPU 4개, Memory 8GB 할당을 권고합니다.
먼저 kind와 실습에 필요한 tool들을 설치합니다.
# 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
brew install krew
brew install kube-ps1
brew install kubecolor
brew install kubectx
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 변수 지정 사용 시
여기서 보이는 kindnet은 kind용 경량 CNI입니다.
다음으로 kind로 구성된 cluster의 기본 정보를 확인해 봅니다.
# kind 는 별도 도커 네트워크 생성 후 사용 : 기본값 172.18.0.0/16
docker network ls
docker inspect kind | jq
# 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 ps # 포트 포워딩 정보 확인
docker exec -it myk8s-control-plane ss -tnlp | grep 6443
kubectl get pod -n kube-system -l component=kube-apiserver -owide # 파드 IP 확인
kubectl describe pod -n kube-system -l component=kube-apiserver
docker exec -it myk8s-control-plane curl -k https://localhost:6443/livez ;echo
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
# 디버그용 내용 출력에 ~/.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'
이어서 kind로 구축된 쿠버네티스 관련 정보를 확인해 봅니다.
# static pod manifest 위치 찾기
docker exec -it myk8s-control-plane grep staticPodPath /var/lib/kubelet/config.yaml
staticPodPath: /etc/kubernetes/manifests
# static pod 정보 확인 : kubectl 및 control plane 에서 관리되지 않고 kubelet 을 통해 지정한 컨테이너를 배포
docker exec -it myk8s-control-plane tree /etc/kubernetes/manifests/
/etc/kubernetes/manifests/
├── etcd.yaml
├── kube-apiserver.yaml
├── kube-controller-manager.yaml
└── kube-scheduler.yaml
docker exec -it myk8s-worker tree /etc/kubernetes/manifests/
...
# 워커 노드(컨테이너) bash 진입
docker exec -it myk8s-worker bash
---------------------------------
whoami
# kubelet 상태 확인
systemctl status kubelet
# 컨테이너 확인
docker ps
crictl ps
# kube-proxy 확인
pstree
pstree -p
ps afxuwww |grep proxy
iptables -t filter -S
iptables -t nat -S
iptables -t mangle -S
iptables -t raw -S
iptables -t security -S
# tcp listen 포트 정보 확인
ss -tnlp
# 빠져나오기
exit
---------------------------------
만들어진 클러스터에서 파드 생성을 해보았습니다. 파드는 netshoot과 nginx입니다.
# 파드 생성
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 웹 접속
kubectl exec -it netpod -- curl -s $(kubectl get pod nginx -o jsonpath={.status.podIP}) | grep -o "<title>.*</title>"
다음으로 컨트롤 플레인 컨테이너 정보를 확인합니다. 현재 kind기반 DinD(Docker in Docker) 형태로 서비스를 구성해 두었기 때문에
control-plane에 접속해서 정보를 확인할 때와, 호스트 docker에서 정보를 확인할 때 차이가 있습니다. 또한 앞서 살펴보았던 /sbin/init정보와 veth 정보들을 확인할 수 있습니다.
docker ps | grep myk8s
docker inspect myk8s-control-plane | jq | grep Entrypoint -A 3
...
"Entrypoint": [
"/usr/local/bin/entrypoint",
"/sbin/init"
],
...
# 컨트롤플레인 컨테이너 bash 접속 후 확인
docker exec -it myk8s-control-plane bash
-------------------------------------------
# CPU 정보 확인
arch
aarch64 # mac m1~m3
혹은
x86_64 # intel/호환 cpu
# 기본 사용자 확인
whoami
root
# 네트워크 정보 확인
ip -br -c -4 addr
ip -c route
cat /etc/resolv.conf
# Entrypoint 정보 확인
cat /usr/local/bin/entrypoint
# 프로세스 확인 : PID 1 은 /sbin/init
ps -ef
# 컨테이터 런타임 정보 확인
systemctl status containerd
# DinD 컨테이너 확인 : crictl 사용
crictl version
crictl info
crictl ps -o json | jq -r '.containers[] | {NAME: .metadata.name, POD: .labels["io.kubernetes.pod.name"]}'
crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD
ff3d3a53905fd 9d6767b714bf1 12 minutes ago Running nginx-pod 0 20328fe63d512 nginx
bebe6b14d1ab3 eead9e442471d 13 minutes ago Running netshoot-pod 0 28cd918f0561a netpod
...
# 파드 이미지 확인
crictl images
IMAGE TAG IMAGE ID SIZE
docker.io/library/nginx alpine 9d6767b714bf1 20.2MB
docker.io/nicolaka/netshoot latest eead9e442471d 178MB
...
# kubectl 확인
kubectl get node -v6
cat /etc/kubernetes/admin.conf
exit
-------------------------------------------
kubectl get node -v6
다음으로 kind로 multi-node cluster를 구축하고, nginx를 배포한 다음 kube-ops-view를 통해 모니터링을 하는 실습을 진행했습니다.
이때, kind클러스터 내부의 port를 호스트에서 접근 가능하도록 Mapping table을 이용하여 구성했습니다.
1. 컨테이너 설치
# '컨트롤플레인, 워커 노드 1대' 클러스터 배포 : 파드에 접속하기 위한 포트 맵핑 설정
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=myk8s
kind create cluster --config kind-2node.yaml --name $CLUSTERNAME
# 배포 확인
kind get clusters
kind get nodes --name $CLUSTERNAME
# 노드 확인
kubectl get nodes -o wide
# 노드에 Taints 정보 확인
kubectl describe node $CLUSTERNAME-control-plane | grep Taints
Taints: node-role.kubernetes.io/control-plane:NoSchedule
kubectl describe node $CLUSTERNAME-worker | grep Taints
Taints: <none>
# 컨테이너 확인 : 컨테이너 갯수, 컨테이너 이름 확인
# kind yaml 에 포트 맵핑 정보 처럼, 자신의 PC 호스트에 31000 포트 접속 시, 워커노드(실제로는 컨테이너)에 TCP 31000 포트로 연결
# 즉, 워커노드에 NodePort TCP 31000 설정 시 자신의 PC 호스트에서 접속 가능!
docker ps
docker port $CLUSTERNAME-worker
31000/tcp -> 0.0.0.0:31000
31001/tcp -> 0.0.0.0:31001
# 컨테이너 내부 정보 확인 : 필요 시 각각의 노드(?)들에 bash로 접속하여 사용 가능
docker exec -it $CLUSTERNAME-control-plane ip -br -c -4 addr
docker exec -it $CLUSTERNAME-worker ip -br -c -4 addr
2. kube-ops-view 설치
# kube-ops-view
# helm show values geek-cookbook/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
# kube-ops-view 접속 URL 확인 (1.5 , 2 배율)
echo -e "KUBE-OPS-VIEW URL = http://localhost:31000/#scale=1.5"
3. nginx 배포 및 테스트
nginx를 배포하면 초기 extraPortMappings를 통해 오픈한 포트로 ngninx를 접속할 수 있습니다.
# 디플로이먼트와 서비스 배포
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
# 확인
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
117a1145a676 kindest/node:v1.29.2 "/usr/local/bin/entr…" 7 minutes ago Up 7 minutes 0.0.0.0:31000-31001->31000-31001/tcp myk8s-worker
...
kubectl get deploy,svc,ep deploy-websrv
...
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/deploy-websrv NodePort 10.96.204.112 <none> 80:31001/TCP 55s
...
# 자신의 PC에 호스트 포트 31001 접속 시 쿠버네티스 서비스에 접속 확인
open http://localhost:31001
curl -s localhost:31001 | grep -o "<title>.*</title>"
<title>Welcome to nginx!</title>
# 디플로이먼트와 서비스 삭제
kubectl delete deploy,svc deploy-websrv
Pod & Pouse Container 실습
Pod와 Pouse Container에 대해 학습했습니다. 네트워크, 네임스페이스 등의 내용은 1주 차의 내용을 참고하시면 도움이 됩니다.
링크
1주 차(1/2) - https://devlos.tistory.com/91
1주 차(2/2) - https://devlos.tistory.com/92
출처: https://kubernetes.io/blog/2016/12/container-runtime-interface-cri-in-kubernetes/
CRI는 예전에 Container runtime 개발사들이 다양한 방식으로 runtime을 만들었을 때, 각 제품들의 차이에 대한 이슈가 있어 표준화되었다고 합니다.
Pod는 리소스 제약이 있는 격리된 환경의 애플리케이션을 구동하기 위한 컨테이너 그룹으로 구성되고, CRI에서는 이 환경을 PodSandbox라고 합니다.
Pod를 시작하기 전에 kubelet은 RuntimeService.RunPodSandBox를 호출하여 환경을 만드는데, 여기에는 Pod에 대한 네트워킹 설정이 포함됩니다.
Pause 컨테이너는 네트워크 인터페이스를 이용하여 Lo와 eth0, NET/MNT/UTS namespace를 생성하고 애플리케이션 컨테이너와 공유합니다. Pause container의 특징은 다음과 같습니다.
- Pod는 1개 이상의 컨테이너를 가질 수 있습니다 ⇒ sidecar 패턴 등
- Pod 내에 실행되는 컨테이너들은 반드시 동일한 노드에 할당되며 동일한 생명 주기를 갖습니다 → Pod 삭제 시, Pod 내 모든 컨테이너가 삭제 - Pod IP - Pod 는 노드 IP와 별개로 클러스터 내에서 접근 가능한 IP를 할당받으며, 다른 노드에 위치한 Pod 도 NAT 없이 Pod IP로 접근 가능
- IP 공유 - Pod 내에 있는 컨테이너들은 서로 IP를 공유, 컨테이너끼리는 localhost 통해 서로 접근하며 포트를 이용해 구분
- pause 컨테이너가 'parent'처럼 network ns를 만들어 주고, 내부의 컨테이너들은 해당 net ns 를 공유
- 쿠버네티스에서 pause 컨테이너는 포드의 모든 컨테이너에 대한 "부모 컨테이너" 역할을 함
- pause 컨테이너의 두 가지 핵심 책임
- 첫째, 포드에서 Linux 네임스페이스 공유의 기반 역할
- 둘째, PID(프로세스 ID) 네임스페이스 공유가 활성화되면 각 포드에 대한 PID 1 역할을 하며 좀비 프로세스를 제거
- volume 공유 - Pod 안의 컨테이너들은 동일한 볼륨과 연결이 가능하여 파일 시스템을 기반으로 서로 파일을 주고받을 수 있음
- Pod는 리소스 제약이 있는 격리된 환경의 애플리케이션 컨테이너 그룹으로 구성됩니다. CRI에서 이 환경을 PodSandbox라고 함
- 포드를 시작하기 전에 kubelet은 RuntimeService.RunPodSandbox를 호출하여 환경을 만듭니다. (파드 네트워킹 설정 ’IP 할당’ 포함)
- Kubelet은 RPC를 통해 컨테이너의 수명 주기를 관리하고, 컨테이너 수명 주기 훅과 활성/준비 확인을 실행하며, Pod의 재시작 정책을 준수
Pause 컨테이너의 구현체를 살펴보면, 무한 대기루프를 돌면서 신호가 발생하면 프로세스 ID 확인 및 신호 핸들러를 설정해 줍니다.
소스 출처 - https://github.com/kubernetes/kubernetes/blob/master/build/pause/linux/pause.c
다음으로 Pause 컨테이너의 동작에 대해 실습을 하기 위해 실습환경을 구성합니다.
# '컨트롤플레인, 워커 노드 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"
다음으로 현재 workernode에 떠있는 containerd-shim 내부 pause container, metrics-server의 네임스페이스들이 위에서 설명한 것처럼 공유하여 사용되고 있는지를 확인해 봅니다.
# [터미널1] myk8s-worker bash 진입 후 실행 및 확인
docker exec -it myk8s-worker bash
crictl ps
# 확인 : kubelet에 --container-runtime-endpoint=unix:///run/containerd/containerd.sock
pstree -aln
# 확인 : 파드내에 pause 컨테이너와 metrics-server 컨테이너, 네임스페이스 정보
pstree -aclnpsS
# 네임스페이스 확인 : lsns - List system namespaces
lsns -p 1
lsns -p $$
# 해당 파드에 pause 컨테이너는 호스트NS와 다른 5개의 NS를 가짐 : mnt/pid 는 pasue 자신만 사용, net/uts/ipc는 app 컨테이너를 위해서 먼저 생성해둠
lsns -p 1797
# app 컨테이너(metrics-server)는 호스트NS와 다른 6개의 NS를 가짐 : mnt/pid/cgroup 는 자신만 사용, net/uts/ipc는 pause 컨테이너가 생성한 것을 공유 사용함
pgrep python3
lsns -p $(pgrep python3)
pgrep metrics-server
lsns -p $(pgrep metrics-server)
#
ls -l /run/containerd/containerd.sock
# 특정 소켓 파일을 사용하는 프로세스 확인
lsof /run/containerd/containerd.sock
#
ss -xl | egrep 'Netid|containerd'
#
findmnt -A
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
---
# [터미널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
다음으로 nginx, netshoot으로 구성된 pod를 통해 사용자가 생성하는 애플리케이션 pod에서도 pause컨테이너가 생성한 ns를 공유해서 사용하는 것을 확인해 보도록 하겠습니다.
myweb2.yaml
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 https://raw.githubusercontent.com/gasida/NDKS/main/3/myweb2.yaml
# 확인
# pod 정보 READY 에 2/2 를 확인 : pod 내 모든 컨테이너가 정상이여야지 status 가 Running 가 됨
kubectl get pod -owide
# Pod 상세 정보에 컨테이너 2개 정보가 보인다
kubectl get pod myweb2 -o jsonpath='{range .status.containerStatuses[*]}{"Container Name: "}{.name}{"\nContainer ID: "}{.containerID}{"\nImage: "}{.image}{"\n\n"}{end}'
root@k8s-m:~# kubectl describe pod myweb2
# 파드의 각각 컨테이너 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
----------------------------------
# 터미널3 : nginx 컨테이너 웹 접속 로그 출력 : 접속자(myweb2-netshoot)의 IP 가 ::1(ipv6) 혹은 127.0.0.1(ipv4) 이닷!
kubectl logs -f myweb2 -c myweb2-nginx
# [터미널2]
docker exec -it myk8s-worker bash
----------------------------------
# 컨테이너 정보 확인 : POD 와 POD ID가 같음을 확인
crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD
f0401ef30af36 e286c635d1232 About a minute ago Running myweb2-netshoot 0 40f9a3021011a myweb2
1f83b080de52d a9dfdba8b7190 About a minute ago Running myweb2-nginx 0 40f9a3021011a myweb2
...
# 워커 노드에서 컨테이너 프로세스 정보 확인
ps -ef | grep 'nginx -g' | grep -v grep
ps -ef | grep 'curl' | grep -v grep
생성된 컨테이너들이 pause 컨테이너와 동일한 IP를 사용하고, namespace를 공유하는 것을 확인했습니다.
# 각각 프로세스를 변수에 지정
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
lsns -p $NETSHPID
# pause 정보 확인 :
PAUSEPID=2995
lsns -p $PAUSEPID
# 개별 컨테이너에 명령 실행 : IP 동일 확인
crictl ps
crictl ps -q
crictl exec -its 39132f5bc08c4 ifconfig
crictl exec -its 1c14cfe28e7c4 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개의 네임스페이스 비교 , 아래 2995 프로세스의 정제는?
crictl inspect 39132f5bc08c4 | jq '.info.runtimeSpec.linux.namespaces[] | .path'
crictl inspect 1c14cfe28e7c4 | jq '.info.runtimeSpec.linux.namespaces[] | .path'
PAUSE Container가 ns를 공유하는 특성을 이용하여,
애플리케이션과 런타임 의존성만 포함하는 최소화된 이미지기반 컨테이너인 Distroless Container를 대상으로
Ephemeral Containers와 같은 형태로 디버깅에 활용할 수 있습니다.
'클라우드 컴퓨팅 & NoSQL > [KANS] 쿠버네티스 네트워크 심화 스터디' 카테고리의 다른 글
[3주차(2/2) - k8s Calico CNI mode & 운영] KANS 스터디 (24.09.08) (0) | 2024.09.18 |
---|---|
[3주차(1/2) - k8s Calico CNI] KANS 스터디 (24.09.08) (1) | 2024.09.18 |
[2주차(2/2) - Flannel CNI] KANS 스터디 (24.09.01) (4) | 2024.09.05 |
[1주차(2/2) - 컨테이너 네트워크 & IPTables] KANS 스터디 (24.08.25) (6) | 2024.09.01 |
[1주차(1/2) - 도커 컨테이너 격리] KANS 스터디 (24.08.25) (0) | 2024.09.01 |