K8S - Kubernetes
KANG 2주차 내용을 따라하면서 정리하였습니다. 매주 매우 광범위한 자료를 제공해주시고 이를 한번에 정리하기 보다는 여러편으로 나누어서 정리 하고 있습니다. 안타깝게도, 제가 한번 소화 하여 정리 하려고 노력은 하고 있지만 대부분의 내용을 그대로 옮기는 수준이네요.
2주 내용 요약
- 다수의 컨테이너를 운영해야 될 때 발생하는 도커의 부족한 점을 해결하기 위해 컨테이너 오케스트레이션이 나오게 되었습니다.
- 현재 컨테이너 오케스트레이션 중 가장 유명한 도구는 쿠버네티스 입니다.
- 쿠버네티스는 컨트롤 플레인(마스터)와 노드(워커)로 구성되어 있습니다.
- 쿠버네티스는 컨테이너 애플리케이션의 기본 단위를 파드(Pod)라고 부르며, 파드는 1개 이상의 컨테이너로 구성된 컨테이너의 집합입니다.
실습 환경 구성
3개의 VM을 생성하며, 컨트롤 플레인 1개와 워커 2개로 구성합니다. PC 사양에 따라 cpu, memory를 다르게 설정하세요
K8S 버전은 1.22.5, CNI 는 flannel(v0.16.1, 가장 이해하기 쉬운 구조)로 설정되어 있습니다.
Vagrantfile
# Base Image
BOX_IMAGE = "ubuntu/focal64"
BOX_VERSION = "20211026.0.0"
# 워커 노드 수
N = 2
Vagrant.configure("2") do |config|
#-----Manager Node
config.vm.define "k8s-m" do |subconfig|
subconfig.vm.boot_timeout = 1800
subconfig.vm.box = BOX_IMAGE
subconfig.vm.box_version = BOX_VERSION
subconfig.vm.provider "virtualbox" do |v|
v.customize ["modifyvm", :id, "--groups", "/Flannel-Lab"]
v.customize [ "modifyvm", :id, "--uartmode1", "disconnected" ]
v.name = "Flannel-k8s-m"
v.memory = 6048
v.cpus = 6
v.linked_clone = true
end
subconfig.vm.hostname = "k8s-m"
subconfig.vm.synced_folder "./", "/vagrant", disabled: true
subconfig.vm.network "private_network", ip: "192.168.100.10"
subconfig.vm.network "forwarded_port", guest: 22, host: 50010, auto_correct: true, id: "ssh"
subconfig.vm.provision "shell", path: "https://raw.githubusercontent.com/gasida/KANS/main/2/init_cfg.sh", args: N
subconfig.vm.provision "shell", path: "https://raw.githubusercontent.com/gasida/KANS/main/2/master.sh"
end
#-----Worker Node
(1..N).each do |i|
config.vm.define "k8s-w#{i}" do |subconfig|
subconfig.vm.boot_timeout = 1800
subconfig.vm.box = BOX_IMAGE
subconfig.vm.box_version = BOX_VERSION
subconfig.vm.provider "virtualbox" do |v|
v.customize ["modifyvm", :id, "--groups", "/Flannel-Lab"]
v.customize [ "modifyvm", :id, "--uartmode1", "disconnected" ]
v.name = "Flannel-k8s-w#{i}"
v.memory = 2536
v.cpus = 4
v.linked_clone = true
end
subconfig.vm.hostname = "k8s-w#{i}"
subconfig.vm.synced_folder "./", "/vagrant", disabled: true
subconfig.vm.network "private_network", ip: "192.168.100.10#{i}"
subconfig.vm.network "forwarded_port", guest: 22, host: "5001#{i}", auto_correct: true, id: "ssh"
subconfig.vm.provision "shell", path: "https://raw.githubusercontent.com/gasida/KANS/main/2/init_cfg.sh", args: N
subconfig.vm.provision "shell", path: "https://raw.githubusercontent.com/gasida/KANS/main/2/worker.sh"
end
end
end
init_cfg.sh : 공통 설정(script) 부분
#!/usr/bin/env bash
# root password
echo ">>>> root password <<<<<<"
printf "qwe123\nqwe123\n" | passwd
# config sshd
echo ">>>> ssh-config <<<<<<"
sed -i "s/^PasswordAuthentication no/PasswordAuthentication yes/g" /etc/ssh/sshd_config
sed -i "s/^#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config
systemctl restart sshd
# profile bashrc settting
echo 'alias vi=vim' >> /etc/profile
echo "sudo su -" >> .bashrc
# Letting iptables see bridged traffic
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system
# local dns setting
echo "192.168.100.10 k8s-m" >> /etc/hosts
for (( i=1; i<=$1; i++ )); do echo "192.168.100.10$i k8s-w$i" >> /etc/hosts; done
# apparmor disable
systemctl stop apparmor && systemctl disable apparmor
# package install
apt update
apt-get install bridge-utils net-tools jq tree resolvconf wireguard -y
# config dnsserver ip
echo -e "nameserver 1.1.1.1" > /etc/resolvconf/resolv.conf.d/head
resolvconf -u
# docker install
curl -fsSL https://get.docker.com | sh
# Cgroup Driver systemd
cat <<EOF | tee /etc/docker/daemon.json
{"exec-opts": ["native.cgroupdriver=systemd"]}
EOF
systemctl daemon-reload && systemctl restart docker
# swap off
swapoff -a
# Installing kubeadm kubelet and kubectl
curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
apt-get update
#apt-get install -y kubelet kubeadm kubectl
#apt-get install -y kubelet=<VERSION> kubectl=<VERSION> kubeadm=<VERSION>
apt-get install -y kubelet=1.22.5-00 kubectl=1.22.5-00 kubeadm=1.22.5-00
apt-mark hold kubelet kubeadm kubectl
systemctl enable kubelet && systemctl start kubelet
master.sh : 마스터 노드 설정(script) 파일
#!/usr/bin/env bash
# init kubernetes
kubeadm init --token 123456.1234567890123456 --token-ttl 0 --pod-network-cidr=172.16.0.0/16 --apiserver-advertise-address=192.168.100.10
# config for master node only
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
# flannel install
kubectl apply -f https://raw.githubusercontent.com/gasida/KANS/main/2/kube-flannel.yml
# etcdctl install
apt install etcd-client -y
# source bash-completion for kubectl kubeadm
source <(kubectl completion bash)
source <(kubeadm completion bash)
## Source the completion script in your ~/.bashrc file
echo 'source <(kubectl completion bash)' >> /etc/profile
echo 'source <(kubeadm completion bash)' >> /etc/profile
## alias kubectl to k
echo 'alias k=kubectl' >> /etc/profile
echo 'complete -F __start_kubectl k' >> /etc/profile
## kubectx kubens install
git clone https://github.com/ahmetb/kubectx /opt/kubectx
ln -s /opt/kubectx/kubens /usr/local/bin/kubens
ln -s /opt/kubectx/kubectx /usr/local/bin/kubectx
## kube-ps1 install
git clone https://github.com/jonmosco/kube-ps1.git /root/kube-ps1
cat <<"EOT" >> ~/.bash_profile
source /root/kube-ps1/kube-ps1.sh
KUBE_PS1_SYMBOL_ENABLE=true
function get_cluster_short() {
echo "$1" | cut -d . -f1
}
KUBE_PS1_CLUSTER_FUNCTION=get_cluster_short
KUBE_PS1_SUFFIX=') '
PS1='$(kube_ps1)'$PS1
EOT
kubectl config rename-context "kubernetes-admin@kubernetes" "admin-k8s"
## kube-tail install
curl -O https://raw.githubusercontent.com/johanhaleby/kubetail/master/kubetail
chmod 744 kubetail && mv kubetail /usr/bin
curl -o /root/kubetail.bash https://raw.githubusercontent.com/johanhaleby/kubetail/master/completion/kubetail.bash
cat <<EOT >> ~/.bash_profile
source /root/kubetail.bash
EOT
worker.sh : 워커 노드 설정(script) 파일
#!/usr/bin/env bash
# config for work_nodes only
kubeadm join --token 123456.1234567890123456 --discovery-token-unsafe-skip-ca-verification 192.168.100.10:6443
kube-flannel.yml : 플라넬 파일 기본 설정 파일에서 변경 사항 - 링크
# 아래 파드의 CIDR 대역을 **172.16.0.0/16** 으로 변경
net-conf.json: |
{
"**Network**": "**172.16.0.0/16**",
"Backend": {
"Type": "vxlan"
}
}
...
# 플라넬 파드에 args 에 **--iface=enp0s8** 추가 (enp0s3 로 flannel 동작하기 않게 하기 위해)
containers:
- name: kube-flannel
image: rancher/mirrored-flannelcni-flannel:v0.16.1
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
- **--iface=enp0s8**
설치 및 확인
K8S 설치
# 빈 디렉토리를 생성 후 사용해주세요
# vagrant 파일 다운로드
$ curl -O https://raw.githubusercontent.com/gasida/KANS/main/2/Vagrantfile
# 배포
$ vagrant up
확인
# 배포 확인
$ vagrant status
# ssh 접속 : 참고(root 계정암호: qwe123)
## 마스터노드 접속
$ vagrant ssh k8s-m
## k8s 클러스터에 노드 확인
$ kubectl get nodes
$ kubectl get pod -A
## 워커노드 접속 : 참고(root 계정암호: qwe123)
$ vagrant ssh k8s-w1
$ vagrant ssh k8s-w2
쿠버네티스 아키텍처
- Kubernetes Components : K8S 클러스터는 Controle Plane(마스터)와 Node(노드)로 구성 - 링크
Control Plane(마스터 노드) 핵심 컴포넌트
: 마스터는 단일 서버 혹은 고가용성을 위한 클러스터 마스터로 구축
kube
-apiserver : 마스터로 전달되는 모든 요청을 받아 드리는 API 서버 - (심화) 링크1 링크2 링크3etcd
: 클러스터내 모든 메타 정보를 저장하는 서비스kube-scheduler
: 사용자의 요청에 따라 적절하게 컨테이너를 워커 노드에 배치하는 스케줄러kube-controller-manager
: 현재 상태와 바라는 상태를 지속적으로 확인하며 특정 이벤트에 따라 특정 동작을 수행하는 컨트롤러 - 링크cloud-controller-manager
: 클라우드 플랫폼(AWS, GCP, Azure 등)에 특화된 리소스를 제어하는 클라우드 컨트롤러 - 링크
Worker(워커 노드)
- 링크
kubelet
: 마스터의 명령에 따라 컨테이너의 라이프 사이클을 관리하는 노드 관리자kube-proxy
: 컨테이너의 네트워킹을 책임지는 프록시, 네트워크 규칙을 유지 관리Container Runtime
: 실제 컨테이너를 실행하는 컨테이너 실행 환경, (Docker& containerD & CRI-O)
CNI
: Container Network Interface 는 k8s 네트워크 환경을 구성해준다 - 링크, 다양한 플러그인이 존재 - 링크
K8S 가 해결해야될 4가지 네트워크 문제
- Highly-coupled container-to-container communications: this is solved by Pods and localhost communications.
- 파드에 여러개의 컨테이너가 동작 시, pause 컨테이너의 netns 를 공유하여 컨테이너들간 통신은 localhost 로 통신 가능!
- Pod-to-Pod communications: this is the primary focus of this document.
- 파드 ↔ 파드 간 NAT 없이 통신할 수 있다.
- Pod-to-Service communications: this is covered by services.
- 파드 ↔ 서비스 간 통신 가능!
- External-to-Service communications: this is covered by services.
- 외부 ↔ 서비스 간 통신 가능
K8S 네트워크 동작 기준
- pods on a node can communicate with all pods on all nodes without NAT
- 파드 ↔ 파드 간 NAT 없이 통신할 수 있다.
- agents on a node (e.g. system daemons, kubelet) can communicate with all pods on that node
- 노드의 에이전트(예: 시스템 데몬, kubelet)는 모든 파드와 통신할 수 있다.
- pods in the host network of a node can communicate with all pods on all nodes without NAT
- 노드의 호스트 네트워크에 있는 파드는 NAT 없이 모든 노드에 있는 모든 파드와 통신할 수 있다.
- IP range from which to assign service cluster IPs. This must not overlap with any IP ranges assigned to nodes for pods
- 서비스 클러스터 IP 대역은 각 노드의 파드 IP 대역과 겹치지 않아야 한다.
- 리눅스 컨테이너의 네트워크 인터페이스를 설정할 수 있도록 도와주는 일련의 명세와 라이브러리로 구성
- CNI 는 오직 ‘컨테이너의 네트워크 연결성’ 과 ‘컨테이너 삭제 시 관련된 네트워크 리소스 해제’에 대해서만 관여. 그 외의 구체적인 사안에 대한 제한이 없음.
- 이를 실행하는 runtime 는 어떤 것이든 상관없음 (k8s, podman, cloud foundry 등)
DNS
: 쿠버네티스 서비스를 위해 DNS 레코드를 제공해주며, Service Discovery 기능을 제공을 한다. 대표적으로 CoreDNS 가 있다 - 링크
- Service Discovery (서비스 탐색)
- 쿠버네티스는 클러스터내에서 통신하기 위해 노드 위치와는 상관없이 어디서든 접근할 수 있는 서비스 끝점(Service Endpoint)이 필요합니다. 사용자(또는 Pod)는 서비스 끝점을 통해 다른 컨테이너(Pod)와 통신할 수 있습니다. 이를 위해 사용자는 서비스에 접근하기 위한 끝점의 접속 정보(예를 들면 IP)를 알아야 합니다. 이를 서비스 탐색(Service Discovery)이라 합니다. 쿠버네티스에서는 DNS 기반의 서비스 탐색을 지원하기에 사용자가 매번 새로운 서비스의 IP를 찾을 필요 없이 도메인 주소를 기반으로 서비스에 접근할 수 있습니다. 쿠버네티스에서는 서비스 탐색 기능을 Service 라는 리소스를 이용하여 제공합니다.
그외 대시보드, 모니터링, 로깅 등등이 있다
쿠버네티스 핵심 개념
애완동물 VS 가축
쿠버네티스는 서버를 애완동물
(Pet)보다는 가축
(cattle)에 가깝다고 비유합니다.
애완동물
은 세심한 관리가 필요하고 가축은 떼로 방목하여 키웁니다. 애완동물은 개체마다 정해진 이름이 있고, 배고프지 말라고 끼니를 챙겨주며, 아프지 말라고 예방 접종을 합니다.
반면, 가축
은 개별적으로 정해진 이름 없이 무리로 관리합니다. 때로 무리에서 낙오된 개체는 죽기도 합니다. 애완동물과는 다르게 가축 한두 마리 죽는 것에 크게 슬퍼하지 않습니다. 워낙 개체수가 많으니까요.
다음의 예로 쿠버네티스의 서버관리 관점을 확인해봅니다.
첫째, 서버마다 특별한 이름을 부여하지 않습니다.
예를 들어, 특정 서버의 역할을 빌드 서버, 웹 서버, 모니터링 서버 등으로 구분하지 않습니다. 모든 서버들은 워커 서버로 동작합니다.
둘째, 가축과 마찬가지로 한두 개의 서버(워커 노드)가 망가져도 문제없습니다.
손쉽게 다른 서버에게 그 역할을 맡길 수 있습니다. 쿠버네티스 안에서는 모든 서버들이 마스터
와 워커
로만 구분됩니다. 마스터에서는 쿠버네티스를 운용하기 위한 필수적인 핵심 컴포넌트가 존재하고, 나머지 워커 노드들은 단순히 컨테이너를 실행하는 환경으로 사용합니다. 워커마다 특별한 역할을 맡지 않기 때문에 마스터가 죽지 않은 이상 특정 워커가 수행했던 역할을 나머지 워커들에 맡겨도 문제없습니다. 이것은 컨테이너라는 고립화된 가상실행 환경을 통해 이식성을 높였기에 가능한 일입니다. 이러한 특징으로, 많은 수의 서버를 관리하기에 수월합니다.
바라는 상태(Desired State) - 컨트롤러
(특정 리소스를 지속적으로 바라보며 리소스의 생명주기에 따라 미리 정해진 작업을 수행하는 주체)
에어콘을 사용할 때, 현재 상태가 존재하고(=현재 온도), 사용자가 바라는 상태
(=희망 온도)가 있습니다.
에어컨 시스템이 사용자의 희망온도에 따라 현재 상태를 변경시키기 위해 에어컨 시스템을 작동시키듯, 쿠버네티스는 사용자의 요청에 따라 현재 상태가 바라는 상태와 동일해지도록 사전이 미리 정의된 특정 작업을 수행합니다.
쿠버네티스에서 바라는 상태
란 사용자가 생각하는 최종 애플리케이션 배포 상태
를 말합니다.
사용자는 자신이 원하는 애플리케이션 배포 상태를 쿠버네티스에 알려주면 쿠버네티스가 자동으로 현재 상태를 바라는 상태로 변경합니다.
바라는 상태가 가지는 것의 장점으로는 쿠버네티스가 장애가 발생하여 애플리케이션이 죽더라도, 바라는 상태를 알기 때문에 손쉽게 원래의 바라는 상태로 배포 상태를 되살릴 수가 있습니다(=자가 치유)
[그림 출처:책] 핵심만 콕 쿠버네티스
네임스페이스(Namespace)
도커에서 학습한 네임스페이스와는 다른 개념입니다!
클러스터를 논리적으로 분리하는 네임스페이스라는 개념이 있습니다.
네임스페이스마다 서로 다른 권한 설정을 할 수 있으며, 네트워크 정책 등을 설정할 수 있습니다.
쿠버네티스의 모든 리소스를 크게 두 가지로 구분하자면 네임스페이스 레벨 리소스
와 클러스터 레벨 리소스
로 구분이 됩니다.
[그림 출처:책] 핵심만 콕 쿠버네티스
네임스페이스 리소스는 특정 네임스페이스 안에 속하여 존재합니다. Pod
, Deployment
, Service
와 같이 대부분의 쿠버네티스 리소스가 네임스페이스 안에 포함됩니다.
반면, Node
, PersistentVolume
, StorageClass
와 같이 네임스페이스 영역에 상관없이 클러스터 레벨에서 존재하는 리소스도 있습니다.
쿠버네티스 리소스(Resource), 오브젝트
쿠버네티스는 모든 것이 리소스(Resource)
로 표현됩니다.
Pod, ReplicaSet, Deployment 등 다양한 리소스가 존재하고 각각 역할이 있습니다.
컨테이너의 집합(Pods)
, 컨테이너의 집합을 관리하는 컨트롤러(Replica Set)
, 심지어 사용자(Service Account)
, 노드(Node)
까지도 하나의 오브젝트로 표현 가능합니다.
리소스마다 세부 정의가 다르고 그에 따른 역할과 동작 방식이 다르지만 모두 리소스로 표현됩니다.
쿠버네티스의 가장 기본적인 리소스는 Pod입니다. Pod는 하나 이상의 컨테이너를 가지는 쿠버네티스의 최소 실행 단위입니다.
쿠버네티스에서 프로세스(컨테이너)를 실행한다는 의미는 Pod 리소스를 생성하는 것과 같다고 볼 수 있습니다.
- K8S 에서 사용할 수 있는 오브젝트 확인
# K8s 사용할 수 있는 오브젝트 확인
$ kubectl api-resources
# 특정 오브젝트 설명
$ kubectl explain pod
# 특정 오브젝트의 하위 필드의 정보 확인
$ kubectl explain pod.spec | more
# 특정 오브젝트의 모든 하위 필드 정보 확인
$ kubectl explain pod --recursive | more
선언형 커맨드(Declarative Command)
쿠버네티스에서는 선언형 커맨드(Declarative Command)를 지향합니다.
선언형 커맨드란 사용자가 직접 시스템의 상태를 바꾸지 않고 사용자가 바라는 상태를 선언적으로 기술하여 명령을 내리는 방법을 말합니다.
선언형 커맨드와 반대되는 개념이 명령형(Imperative) 커맨드입니다. 명령형 커맨드는 일반적으로 자주 사용하는 명령 형식입니다.
명령형 커맨드의 예로 SQL 쿼리를 들 수 있습니다. SQL 쿼리는 어떻게 테이블의 데이터를 질의할 것인가에 대한 명령입니다. 대부분 명령이 이런 명령형 커맨드에 기반합니다.
반면, 선언형 커맨드의 대표적인 예로 HTML 문서를 들 수 있습니다. HTML 문서는 어떻게 명령을 수행해야 할지에 대한 정보는 없습니다.
대신 무엇을 해야 하는지 선언되어 있습니다.
쿠버네티스는 YAML 형식을 이용하여 선언형 명령을 내립니다. 다음은 쿠버네티스에서 사용하는 YAML 형식의 리소스(Pod) 예시입니다.
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: nginx
image: nginx:latest
이러한 YAML 형식의 리소스를 ‘YAML 정의서(YAML description)’ 라고 부릅니다.
사용자는 쿠버네티스에 어떤 명령을 전달하거나 옵션값을 수정할 때 YAML 정의서의 YAML property를 추가하거나 수정합니다.
사용자는 각 리소스마다의 모든 설정값을 외울 필요가 없습니다. 최소한의 필수값을 채워서 리소스를 생성하면, 나머지는 쿠버네티스가 기본값으로 리소스를 생성합니다.
Highly Available 구성
- 링크
Control Plane(마스터 노드) HA 설정
: 마스터 노드 3대(5대…) 구성 및 외부 LB(혹은 keepalived)를 통한 API 단일 진입점 구성- kubeadm HA topology -
stacked etcd
방안1 - 외부 LB 사용
- kubeadm HA topology -
external etcd
ETCD 를 control plane node 와 분리
방안2 - 노드에 keepalived 활용
참고 자료
- K8S Docs - Components & Cluster Architecture & Pods & Learn Kubernetes Basics & 치트 시트
- K8S Blog (v1.22) - New Peaks & Nginx-Ingress & API
- CNCF - 링크 & CNCF Landscape - 링크
- 44bits(Youtube) - 초보를 위한 쿠버네티스 안내서 & Subicura(WebSite) - 실습편
- [devinjeon] Container & Pod - 링크
- [learnk8s] Tracing the path of network traffic in Kubernetes - 링크
- (심화) ETCD 기본 동작 원리의 이해 - 링크 & ETCD? - 링크