Container Orchestration
Container Orchestration 이란 복잡한 컨테이너 환경을 효과적으로 관리하기 위한 도구로 쿠버네티스가 주로 쓰인다.
- Cluster = Master Node 가 중앙제어, 다수의 Worker Node 가 클러스터를 이루어 서로 통신하며 작동
- State = Desired State 선언 시 관리자의 개입 없이 자동으로 상태를 유지
- Scheduling = Container 를 배치할 적합한 Worker Node 를 찾아 배포
- Rollout Rollback = 배포 버전관리
- Service Discovery = 서비스 등록 및 조회
- Volume = NFS, EBS 등 다양한 스토리지 마운팅
Kubernetes Architecture
K8s 는 Master Node 와 Worker Node 의 집합으로 이루어진다.
Master Node 는 Worker Node 들을 Manage, Plan, Schedule, Monitor 하는 역할을 수행하기 위해 아래와 같은 Component 들을 가진다.
Worker Node 는 Containerized Application 이 실행되는 Node 로, 이를 위해 아래와 같은 Component 들을 가진다.
- Container Runtime
- `kubelet`
- `kube-proxy`
Docker vs containerd
- 2013년 Docker 출현으로 인한 컨테이너 기술 부상 이후 타 경쟁사 역시 각자 Container Runtime 을 개발하기 시작했고 이로 인해 표준화가 필요해졌다. 2016년 Docker 와 CoreOS 가 주축으로
imagespec과runtimespec등을 포함한 Open Container Initiative, OCI 를 출범하여 Container Runtime 의 표준화를 재정했다. 이후 OCI 스펙에 맞춰 각 기업들이 Container Runtime 을 발전시켰고, Docker 의 경우 OCI 표준에 맞춰 개발한 Container Runtime 이runc이다. - K8s 의 경우 초창기엔 Docker 만을 유일한 Container Runtime 으로 지원했었는데, OCI 등장 이후 다양한 Container Runtime 들이 등장하면서 각 Container Runtime 에 따라
kubelet이 관리해야 하는 방법을 따로 유지보수해야하는 어려움이 발생하기 시작했다. 이런kubelet유지보수 문제를 해결하면서 동시에 다양한 Container Runtime 들을 지원하기 위해kubelet에서 Container Runtime 을 관리할 수 있는 인터페이스인 Container Runtime Interface, CRI 가 등장하게된다. - 초창기 Docker 는 monolithic 한 구조로 Docker Daemon 하나에서 Docker Client, Docker API, Container Runtime, Image Build 등을 포함하고 있었고, 이는
kubelet이 Docker 의 Container Runtime 을 사용할 때 CRI 표준에 부합하는dockershim이라는 컴포넌트를 추가적으로 사용해야했다. - 이후 K8s 의
dockershim지원이 중지되고,containerd가 Docker 에서 분리되면서, CRI 스펙에 맞추기 위한cri plugin등을containerd내부적으로 추가하여containerd도kubelet에 의해 관리될 수 있게되었다. runc가 컨테이너를 실행하는 실질적인 주체라면,containerd는 한 단계 위에서 이미지 저장, 네트워킹, 스냅샷 및 기타 관리 작업 등의 기능들을 포함한 고수준의 Container Runtime 으로, 내부적으로runc를 사용하기에 OCI 와 CRI 표준을 모두 준수할 수 있는 Container Runtime 이 된 것이다.- We can use the 2 CLIs below instead of Docker to work with K8s
nerdctl= for general purpose from thecontainerdcommunitycrictl= for debugging from the K8s community (works with all CRI-compatible container runtimes)
etcd
etcdis a distributed reliable key-value store that is Simple, Secure & Fast- K8s 의 모든 상태와 데이터를 저장
- Key-Value 형태로 데이터를 저장
- 분산 시스템으로 구성하여 고가용성 확보
- TTL, watch 등 부가 기능 제공
kube-apiserver
- 쿠버네티스의 상태를 바꾸거나 조회
etcd와 유일하게 통신하는 모듈- REST API 형태로 제공
- 요청에 대한 권한 체크
- 수평적 확장 가능
Installing kube-apiserver
wget https://storage.googleapis.com/kubernetes-release/release/v1.xx.0/bin/linux/amd64/kube-apiserverkubeadm으로 설치할 경우 Pod 의 형태로 실행된다.
kube-controller-manager
- 다양한 Controller 가 존재
- A
controlleris aprocessthat continuously monitors the state of the components within the system and works towards bringing the whole system to the desired functioning state - Replication Controller, Node Controller, Endpoint Controller, …
- 끊임 없이 상태를 체크하고 원하는 상태를 유지
- 복잡성을 낮추기 위해 하나의 프로세스로 실행
- A
Installing kube-controller-manager
wget https://storage.googleapis.com/kubernetes-release/release/v1.xx.0/bin/linux/amd64/kube-controller-managerkubeadm으로 설치할 경우 Pod 의 형태로 실행된다.
kube-scheduler
kube-scheduler는 생성 요청된 Pod 가 어느 Node 에 배포되어야 하는지 확인하는 역할을 수행한다. Node 의 현재 상태와 Pod 의 요구사항을 체크하여 적절한 Node 를 찾는 작업만 수행할 뿐 Pod 를 생성하는 역할은kubelet이 수행한다.
How does it work?
- Pod 에 필요한 리소스를 여분으로 가지고 있는 Node 를 확인하고 Pod 가 배치된 후 남은 리소스량을 기준으로 순위를 매겨 스케쥴링을 수행한다.
Installing kube-scheduler
wget https://storage.googleapis.com/kubernetes-release/release/v1.xx.0/bin/linux/amd64/kube-schedulerkubeadm으로 설치할 경우 Pod 의 형태로 실행된다.
kubelet
kubelet 은 Master Node 의 kube-apiserver 로 부터 Container 생성 요청을 받아 Worker Node 에 설치된 Container Runtime 을 이용해 Container 를 생성하는 역할을 한다.
kubelet 은 자신이 위치한 Node 와 생성한 Pod 들을 모니터링하여 주기적으로 kube-apiserver 와 소통한다.
Installing kubelet
wget https://storage.googleapis.com/kubernetes-release/release/v1.xx.0/bin/linux/amd64/kubeletkubeadm으로 K8s Cluster 를 구축할 때kubelet은 설치되지 않으니 수동으로 Worker Node 에 kubelet 을 설치해주어야 한다. 때문에 kubelet 은 다른 Component 들과 다르게 Pod 이 아닌 Node 의 프로세스로서 실행된다.
kube-proxy
- 네트워크 프록시와 부하 분산 역할을 하며 K8s Cluster 에 배포된 모든 Pod 간의 통신을 담당한다.
Service와EndpointSlice의 변경을 감시한다.- 성능상의 이유로 별도의 프록시 프로그램 대신
iptables/IPVS/eBPF등을 사용하여 트래픽 라우팅 설정만 관리한다.
Installing kube-proxy
wget https://storage.googleapis.com/kubernetes-release/release/v1.xx.0/bin/linux/amd64/kube-proxykubeadm으로 설치할 경우 Pod 의 형태로 실행된다.- 각 Node 마다 Pod 형태로 실행하기 위해
DaemonSet으로 실행한다.
Kubernetes Cluster Networking
# kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
networking:
podSubnet: "10.244.0.0/16" # = --pod-network-cidr
serviceSubnet: "10.96.0.0/12" # = --service-cidr
dnsDomain: "cluster.local"
# sudo kubeadm init --config kubeadm-config.yaml파드 네트워크 설정을 위해 --cluster-cidr 와 --service-cluster-ip-range 가 존재한다.
| 네트워크 | kubeadm CLI 플래그 | kubeadm config 필드 | 실제 컴포넌트 플래그 |
|---|---|---|---|
| Pod 네트워크 | --pod-network-cidr | networking.podSubnet | kube-controller-manager --cluster-cidr |
| Service 네트워크 | --service-cidr | networking.serviceSubnet | kube-apiserver --service-cluster-ip-range |
Pod Network
kubeadm init --pod-network-cidr=10.244.0.0/16을 주면kubeadm이controller-manager에--cluster-cidr=10.244.0.0/16과--allocate-node-cidrs=true를 세팅한다.- controller-manager가 노드마다 그 풀에서 한 조각씩 떼어
node.spec.podCIDR에 박는다. 조각 크기는--node-cidr-mask-size-ipv4(IPv4 기본/24).
- kube-controller-manager가 node IPAM 컨트롤러를 돌린다. 노드가 새로 붙으면 전체 Pod 풀에서 조각을 떼어
node.spec.podCIDR에 박는 게 이 컴포넌트다. 그래서 전체 풀을 알아야 한다(--cluster-cidr+--allocate-node-cidrs=true). - kube-apiserver가 ClusterIP를 배정한다. Service를 만들면 apiserver가 Service 풀에서 빈 IP를 골라
spec.clusterIP에 써넣는다. 그래서--service-cluster-ip-range를 안다./16에서/24씩 떼니 최대 256개 노드, 노드당 254개 Pod가 가능하다. (실제로는 kubelet 기본max-pods=110이 먼저 걸린다.describe node의pods: 110이 그것이다.) - Pod IP는 실재한다. Pod 안 인터페이스(veth)에 실제로 붙어 있고, CNI가 노드 간 라우팅을 깔아준다. ping도 되고 패킷이 진짜 그 주소로 간다.
- Service ClusterIP는 실재하지 않는다. 앞서 얘기한 대로 iptables 규칙의 매칭 키일 뿐이다.
podCIDR 는 기본값이 없다. CNI는 이 podCIDR 을 읽어 Pod에 IP를 준다. 그래서 CNI가 기대하는 대역과 어긋나면 앞서 겪은 것처럼 깨진다. flannel 기본 매니페스트가 10.244.0.0/16 을 전제하는 이유이고, 네가 그 값을 준 게 맞았던 이유다.
Service Network
ClusterIP를 배정할 가상 IP 풀이다. 기본값은 10.96.0.0/12 이고, kubeadm init에 아무것도 안 주면 이게 쓰인다.
관례상 배치도 정해져 있다:
10.96.0.1=kubernetes서비스 (apiserver)10.96.0.10= CoreDNS (kube-dns서비스)
앞서 얘기했듯 이 IP들은 어디에도 실재하지 않는다. iptables DNAT 규칙의 매칭 키일 뿐이다.
Kubernetes Cluster Upgrade
Kubernetes Software Versions
K8s 는 일반적인 Software 처럼 Release Version 을 가지고 있으며, 기본적으로 최근 3개의 마이너 버전을 지원한다. kube-apiserver, controller-manager, kube-scheduler, kubelet, kube-proxy, kubectl 은 모두 동일한 버전으로 출시되며, etcd 와 core-dns 는 각각 다른 프로젝트이기에 독립적인 버전을 가지고 있다.
kube-apiserver 를 기준으로 다른 Component 들의 버전이 호환될 수 있는데 이는 아래와 같다.
- 예를 들어 kube-apiserver 가 v1.10 일 경우,
- controller-manager 와 kube-scheduler 는 v1.9 와 v1.10 이 호환되고,
- kubelet 과 kube-proxy 는 v1.8 과 v1.9 와 v1.10 이 호환되고,
- kubectl 은 v1.9 와 v1.10 과 v1.11 이 호환된다.
kubectl drain, cordon, uncordon
Security Patch 나 Software Upgrade 등의 유지보수 사유로 Node 를 제거해야 하는 경우, 해당 Node 에서 실행되고 있는 Pod 을 다른 Node 에 옮겨두는 방법을 택할 수 있다.
kube-controller-manager --pod-eviction-timeout=5m0sReplicaSet 으로 배포된 Pod 의 경우, 기본적으로 5분 뒤 다른 Node 에 재배포된다.
kubectl drain node-1Node 내 Pod 를 다른 Node 로 옮기고 싶은 경우 drain 명령어를 통해 Pod 를 옮길 수 있다. 만약 ReplicaSet 으로 배포되지 않은 Pod 가 존재한다면 Error 가 발생한다.
kubectl uncordon node-1Node 가 재시작 된 이후 Pod 가 해당 Node 에 Scheduling 될 수 있도록 uncordon 을 사용할 수 있고,
kubectl cordon node-2반대로 cordon 명령어를 통해 Node 에 Pod 가 Scheduling 되는 것을 제한할 수 있다.
Kubernetes Cluster Upgrade Process
K8s Cluster 를 업그레이드할 때 EKS 나 AKS 같은 Managed-Service 를 사용한다면 간단하게 몇 번의 클릭으로 업그레이드가 가능하지만, 그렇지 않은 경우엔 kubeadm 툴을 활용해 Cluster 를 업그레이드할 수 있다.
# k8s apt repository 리스트 파일에서
vim /etc/apt/sources.list.d/kubernetes.list
# 아래로 변경
echo -e "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /" > /etc/apt/sources.list.d/kubernetes.list
# apt 업데이트 후 kubeadm 최신 버전 확인
apt update
apt-cache madison kubeadm
apt-get install kubeadm=1.32.0-1.1
kubeadm upgrade plan v1.32.0
kubeadm upgrade apply v1.32.0
kubectl get nodes
apt-get install kubelet=1.32.0-1.1
systemctl restart kubelet
kubectl get nodes먼저 Master Node 를 위 명령어들을 통해 업그레이드하자. kubeadm 툴 역시 업그레이드하고자 하는 버전으로 새로 설치해주어야한다. 또한, kubectl get nodes 는 기본적으로 kubelet 의 버전을 보여주기 때문에 kubelet 역시 새로운 버전으로 설치해준 뒤 확인해보자.
# 먼저 node01 에 실행중인 Pod 을 drain 하고
kubectl drain node01
# node01 으로 접속한 뒤
ssh node01
# k8s apt repository 리스트 파일에서
vim /etc/apt/sources.list.d/kubernetes.list
# 아래로 변경
deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /
# apt 업데이트 후 kubeadm 최신 버전 확인
apt update
apt-cache madison kubeadm
apt-get install kubeadm=1.32.0-1.1
kubeadm upgrade node config --kubelet-version v1.32.0
apt-get install kubelet=1.32.0-1.1
systemctl restart kubelet
exit
kubectl uncordon node-1이제 Worker Node 로 넘어간 뒤 마찬가지로 업그레이드해주면 된다.
Kubernetes Backup and Restore Methods
Kubernetes Object Backup
kubectl get all --all-namespaces -o yaml > all-deploy-services.yamlResource Configuration 자체를 YAML 파일로 저장하여 백업하는 방법이 있고,
ETCD Backup & Restore
# etcd 스냅샷 생성
export ETCDCTL_API=3
etcdctl snapshot save /tmp/snapshot.db \
--endpoints=https://127.0.0.1:2379 \ # 로컬은 127.0.0.1:2379, 외부 etcd 는 kubeadm-config.yaml 내 etcd.external.endpoints
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/etcd-server.crt \
--key=/etc/kubernetes/pki/etcd/etcd-server.key
etcdctl snapshot status snapshot.db
# etcd 복원
etcdctl snapshot restore /opt/snapshot-pre-boot.db \
--data-dir /var/lib/etcd-from-backup
# kube-apiserver 정지
service kube-apiserver stop
# etcd 재시작
systemctl daemon-reload
service etcd restart
# kube-apiserver 재시작
service kube-apiserver start또는 etcd 스냅샷을 이용해 백업하는 방법이 있다.
vi /etc/kubernetes/manifests/etcd.yaml
# 복원 이후 volume path 수정
volumes:
- hostPath:
path: /var/lib/etcd-from-backup
type: DirectoryOrCreate
name: etcd-data복원 이후 etcd volume path 를 수정해주자.
CRD, Custom Resource Definition
apiVersion: flights.com/v1
kind: FlightTicket
metadata:
name: my-flight-ticket
spec:
from: Mumbai
to: London
number: 2K8s 에선 ReplicaSet, Deployment 등 기본적으로 제공되는 Resource 외에 사용자가 커스텀으로 Resource 를 만들 수 있다. 위와 같은 Resource 를 만들고 싶을 때 Custom Resource Definition 을 생성해 Object 를 생성하고 관리할 수 있다.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: flighttickets.flights.com
spec:
scope: Namespaced
group: flights.com
names:
kind: FlightTicket
singular: flightticket
plural: flighttickets
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
from:
type: string
to:
type: string
number:
type: integer
minimum: 1
maximum: 10위와 같이 CustomResourceDefinition 을 생성하면 정의된 Custom Resource 를 생성하고 관리할 수 있다. 다만 ETCD 에 저장만 될 뿐 어떤 기능이 있는 것은 아니다. 기능을 추가하기 위해 Custom Controller 를 작성할 수 있다.
Custom Controllers
Custom Resource 를 제어하려면 Custom Controller 를 작성해야 하는데, 파이썬 등 다양한 언어로 작성할 수 있지만 API 콜을 기반으로 통신하기 때문에 자체적으로 큐 및 캐싱 시스템을 포함해야 하는 수고가 있을 수 있다. 때문에 K8s Go 클라이언트로 Custom Controller 를 작성하면 좀 더 쉽게 Controller 를 구축할 수 있다.
Operator Framework
Operator Framework 는 CustomResourceDefinition 과 CustomController 를 패키지화하여 K8s 에 배포할 수 있도록 도와준다.