본문 바로가기
사이드 프로젝트

[CI/CD 프로젝트] K8S - Containerd 내부 구조 이해

by 간장공장공차장 2024. 11. 19.
반응형

1. 컨테이너를 위한 리눅스 네트워크

※ 1.2이상 버전에서 K8S는 컨테이너 런타임으로 Docker 의 사용 중단한다.

컨테이너는 애플리케이션이 한 컴퓨팅 환경에서 다른 컴퓨팅 환경으로 빠르고 안정적으로 실행될 수 있도록 코드와 모든 종속성을 패키지화하는 소프트웨어의 표준 단위이다.

컨테이너를 수행하게 되면 네트워크 네임스페이스 별로 격리화되어, 고유의 네트워크 구성을 가지게 된다.

서로 다른 네트워크 네임스페이스는 서로 “고립된” 환경으로 존재하게 된다.

echo "# Network devices"
ip link list

echo -e "\n# Route table"
ip route list

echo -e "\n# iptables rules"
iptables --list-rules

리눅스 네트워크 시스템은 기본적으로 이와 같은 구성을 가지고 있다.

iptables와 netfilter

iptables : 리눅스 상에서 방화벽을 설정하는 도구, 커널 상에서 netfilter 패킷필터링 기능을 사용자 공간에서 제어한다.

netfilter : 네트워크 필터링 훅으로, 이 훅을 통해 source → dest 로 연결되도록 동작한다.

# 특정 규칙 추가
sudo iptables -A <CHAIN_NAME> -p <PROTOCOL> -s <SOURCE> -d <DESTINATION> -j <TARGET>

# 특정 규칙 삭제
sudo iptables -D <CHAIN_NAME> <RULE_SPECIFICATION>

# 설정을 저장하여 부팅 시 적용하도록 하기
sudo iptables-save > /etc/iptables/rules.v4

따라서, 컨테이너를 생성하게되면, 컨테이너는 각각의 독립된 네트워크 컨텍스트를 가지게 된다.

MSA 환경에서 container끼리 통신하는 것은 필수적이다. 그렇다면, 서로 격리된 네트워크에 있는 Pod는 어떻게 통신을 할까?

Container ↔ Host

컨테이너가 생성되게 되면, container에도 가상의 이더넷이 생기게 된다.(ceth0)

또한, linux에도 가상의 이더넷이 생긴다(veth0) 각각의 route table에 이더넷 정보를 매핑하게 되면, linux host 와 container 사이에 패킷이 소통할 수 있게 된다.

Container ↔ Container

container ↔ container끼리는 이터넷 끼리 연결되어있지 않다. (Layer2) 그렇다면, Layer3(IP)를 이용해서 어떤 이더넷으로 가야할 지 설정이 필요하다.

라우팅 테이블을 연결하게 되면, 호스트의 라우팅 테이블에서 host로 라우팅되도록 연결하는 이더넷이 두개가 된다. (veth0, veth2)

같은 dest로의 호출에 대한 라우팅 규칙이 2개가 됨에 따라 container는 host로 연결할 수 없게 된다. (커널은 어떤 경로가 우선해야 하는지 구별하는 메커니즘 없이 동일한 인터페이스에 대한 동일한 목적지로 가는 여러 동일한 경로를 허용하지 않는다)
따라서, 우리는 브릿지 네트워크가 필요하다. cni가 브릿지 네트워크를 설정해준다.

브릿지는 layer2이기 때문에 연결된 인터페이스 간에 패킷을 전달한다. 따라서 container간, 여러개의 container가 host에 접근이 가능해진다.

Container ↔ Internet

리눅스에서는 기본적으로 패킷 전달 기능은 꺼져있다. 즉, container에서 보내는 패킷을 host가 라우트하지 않는다. 따라서 몇가지 설정이 필요하다. 패킷 포워딩 기능을 활성화하고, 이 패킷이 iptables를 볼 수 있도록 설정을 한다. 이 설정이 완료되면, 호스트 머신을 라우터로 바꾸었고, 브리지 인터페이스는 컨테이너의 기본 게이트웨이가 된다.

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# 필요한 sysctl 파라미터를 설정하면, 재부팅 후에도 값이 유지된다.
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

# 재부팅하지 않고 sysctl 파라미터 적용하기
sudo sysctl --system
  • overlay : 컨테이너 런타임(Docker 및 containerd 등)에서 일반적으로 사용되어 계층화된 파일 시스템을 효율적으로 관리하는 오버레이 파일 시스템에 대한 지원을 제공
  • br_netfilter : iptables가 브릿지된 네트워크 트래픽과 상호 작용하여 브릿지된 인터페이스에 대한 패킷 필터링을 수행, netfilter는 네트워크 필터링 훅으로, 이 훅을 통해 source → dest 로 연결되도록 동작한다.
  • modprobe : 위의 k8s.conf 파일이 system boot 시 적용되는 파일이므로, 현 상태에서 리눅스 커널에 네트워크 설정이 바로 추가될 수 있도록 수행
  • net.bridge.bridge-nf-call-iptables : 브릿지 네트워크 사이로 ipv4 패킷이 전달될 수 있도록 방화벽 설정
  • net.ipv4.ip_forward = 1 : 네트워크 인터페이스 사이로, ip패킷이 포워딩 될 수 있도록 설정

2. K8S의 네트워크

K8S는 아래 그림에 있는 요소들을 통해 container를 관리한다.

그림출처: https://kubernetes.io/

Containerd

  • 고수준의 컨테이너 런타임으로, 컨테이너 이미지를 관리하고 컨테이너의 생명 주기를 관리하는 데 사용된다.
  • 컨테이너 이미지의 다운로드, 저장, 캐싱.
  • 컨테이너의 생성, 시작, 정지, 삭제와 같은 생명 주기 관리.
  • runc와 같은 하위 런타임과 통신하여 실제 컨테이너 실행을 수행.

runc

  • 역할: 컨테이너를 실행하는 저수준 런타임으로, Open Container Initiative (OCI) 스펙을 기반으로 컨테이너를 생성하고 실행한다.
  • 실제 컨테이너 프로세스를 생성하고 관리.
  • 컨테이너의 리소스(메모리, CPU 등)를 제한하고, 네트워크 설정을 관리.
  • 컨테이너가 사용하는 namespaces와 cgroups를 설정하여 격리된 환경을 제공.

CRI

CRI는 Kubernetes와 컨테이너 런타임(예: containerd, Docker 등) 간의 통신을 위한 인터페이스

  • Kubernetes가 컨테이너를 생성, 시작, 중지 및 삭제할 수 있도록 컨테이너 런타임과 상호작용하는 방법을 정의한다
  • CRI를 통해 Kubernetes는 다양한 컨테이너 런타임을 지원할 수 있으며, 런타임에 따라 다양한 기능을 사용할 수 있게된다.
  • CRI는 두 가지 gRPC 서비스를 제공한다.
    • ImageService : 컨테이너 이미지 관련 작업 (Pull, Inspect, Remove)
    • RuntimeService : 컨테이너 수명주기 관리 (Create, Start, Stop, Remove, List, Exec, Port-forward)

CNI

CNI는 컨테이너의 네트워크 연결을 관리하기 위한 인터페이스

  • 컨테이너의 네트워크 설정을 관리하는 데 사용됩니다. Kubernetes와 같은 오케스트레이션 툴과 함께 작동하여 컨테이너 간의 통신을 가능하게 한다.
  • 네트워크 인터페이스의 생성 및 삭제.
  • IP 주소 할당, 라우팅 설정.
  • 컨테이너가 서로 통신할 수 있도록 네트워크 정책을 적용.

실습

설치

  • 요구사항 : 2 vCPU & 2GB RAM 이상
  • 노드에 대한 고유 hostname, MAC 주소, product_uuid
  • 설치 버전 : Kubernetes 1.27.16
  • 보안 그룹 : (inbound) allow all tcp from all
## MAC 주소
ip link
ifconfig -a

## product_uuid
sudo cat /sys/class/dmi/id/product_uuid
  • 클러스터 내 모든 머신 간에 전체 네트워크 연결이 가능

1. 서버 기본 세팅

  1. 포트 방화벽 확인
 nc 127.0.0.1 6443 -v

 ## 방화벽 해제 + 프로세스 다운
 nc: connect to 127.0.0.1 port 6443 (tcp) failed: Connection refused

## 방화벽으로 인한 연결 실패
nc: connect to 127.0.0.1 port 6443 (tcp) failed: Operation timed out
nc: connect to 127.0.0.1 port 6443 (tcp) failed: No route to host
  1. Swap 사용 금지 설정

kubelet은 swap memory를 사용할 경우 시작을 실패하므로, 안정적인 운영을 위해 swap메모리 사용을 비활성화 한다.

# 일시 swap 사용 중지
sudo swapoff -a

free -h

# 영구 사용 중지
sed -i '/swap/s/^/#/' /etc/fstab

위와 같이 swqp 메모리 total이 0이 되면 된다.

  • hostname 설정
sudo hostnamectl set-hostname <hostname>
  • etc/hosts 설정
vi /etc/hosts
# Add the following lines:
<master-node-ip> k8s-master 172.21.1.19
<worker-node1-ip> k8s-worker1 172.21.1.7
<worker-node2-ip> k8s-worker2

2. Containerd 설치

  • containerd 확인
sudo systemctl status containerd
  • IPv4 패킷 전달 활성화 & ipv4를 포워딩해서 iptabels가 브리지 된 트래픽을 볼 수 있게 변경
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# 필요한 sysctl 파라미터를 설정하면, 재부팅 후에도 값이 유지된다.
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

# 재부팅하지 않고 sysctl 파라미터 적용하기
sudo sysctl --system

  • containerd 설치
## 바이너리 파일 다운 v1.6.2
curl -LO https://github.com/containerd/containerd/releases/download/v1.6.2/containerd-1.6.2-linux-amd64.tar.gz
sudo tar Cxzvf /usr/local containerd-1.6.2-linux-amd64.tar.gz

## systemctl 이 관리할 수 있도록 하는 유닛 파일 생성
sudo mkdir -p /usr/local/lib/systemd/system/
sudo curl -L https://raw.githubusercontent.com/containerd/containerd/main/containerd.service -o /usr/local/lib/systemd/system/containerd.service
sudo systemctl daemon-reload
sudo systemctl enable --now containerd
sudo systemctl status containerd
  • systemd가 init system일 경우 아래와 같이 SystemdCgroup = true 을 설정해준다.
# /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
  ...
  [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
    SystemdCgroup = true
---- 현재 위 파일은 아래 명령어를 수행해야 생성됨
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
  • containerd 설정 적용
sudo systemctl restart containerd
sudo systemctl enable containerd
  • runc 설치
wget https://github.com/opencontainers/runc/releases/download/v1.1.12/runc.amd64
sudo install -m 755 runc.amd64 /usr/local/sbin/runc

## 설치 확인
 /usr/local/sbin/runc --version

  • CNI 플러그인 설치
wget https://github.com/containernetworking/plugins/releases/download/v1.4.0/cni-plugins-linux-amd64-v1.4.0.tgz
sudo mkdir -p /opt/cni/bin
tar Cxzvf /opt/cni/bin  cni-plugins-linux-amd64-v1.4.0
반응형