📌Index
- Kubernetes의 HA
- Vagrant로 VM 생성하기
- 로드밸런서 구성하기
- 호스트 파일 수정 및 Swap off
- Docker 및 Kubernetes 설치하기
- shell script로 간편화하기
- Kubernetes 초기화 및 노드 조인
- CNI(Container Network Interface) 설치
✔️ Kubernetes의 HA
고가용성(High Availability)이란, 서버와 네트워크, 프로그램 등의 정보 시스템이 상당히 오랜 기간 동안 지속적으로 정상 운영이 가능한 성질을 말한다.
고가용성(HA) 쿠버네티스의 토플로지를 구성하는 데는 두 가지 방법이 있다.
stacked etcd
: etcd 노드와 컨트롤 플레인 노드를 함께 위치 시키는 중첩된(stacked) 컨트롤 플레인 노드 방식external etcd
: etcd와 컨트롤 플레인이 분리된 노드에서 운영되는 외부 etcd 노드 방식
중첩된 etcd 토플로지
중첩된 HA 클러스터는 etcd에서 제공하는 분산 데이터 스토리지 클러스터를, 컨트롤 플레인 구성 요소를 실행하는 kubeadm
으로 관리되는 노드에 의해서 형성된 클러스터 상단에 중첩하는 토플로지이다.
지역 etcd 맴버는 kubeadm init
와 kubeadm join --control-plane
을 이용할 때에 컨트롤 플레인 노드에 자동으로 생성된다.
외부 etcd 토플로지
외부 etcd를 이용하는 HA 클러스터는 etcd로 제공한 분산된 데이터 스토리지 클러스터가 컨트롤 플레인 구성 요소를 운영하는 노드로 형성하는 클러스터의 외부에 있는 토플로지이다.
이 토플로지는 컨트롤 플레인과 etcd 멤버를 분리하므로, 컨트롤 플레인 인스턴스나 etcd 멤버를 잃는 충격이 덜하고, 클러스터 중복성에 있어 중첩된 HA 토플로지만큼 영향을 미치지 않는다. 그러나 이 토플로지는 중첩된 토플로지에 비해 호스트 개수가 두배나 필요하며, 이를 구성하기 위해서는 최소한 3개의 컨트롤 플레인과 3개의 etcd노드가 필요하다.
🔎 목표
본 실습에서는 중첩된 etcd 토플로지를 구성한다.
VM Config : Using Vagrant to Automate the VM installtion for HA Cluster
- 마스터 노드 수 : 2 (2CPUs, 2GB RAM and running ubuntu)
- 워커 노드 수 : 2 (2CPUs, 2GB RAM and running ubuntu)
- 로드 밸런서 노드 수 : 1 (1CPUs, 2GB RAM and running ubuntu)
일반적으로 노드를 홀수개(3+3)로 생성하지만, 노트북이 여러개의 가상 머신을 실행하는 데 한계가 있어 위와 같이 클러스터를 구성한다.
✔️ Vagrant로 VM 생성하기
Vagrant 설치/사용 방법은 Vagrant 설치 및 기본 사용법에서 확인할 수 있다.
다음과 같이 Vagrantfile을 작성하여 k8s-lb
, k8s-master1
, k8s-master2
, k8s-worker1
, k8s-worker2
VM 생성을 자동화할 수 있도록한다. 이때 Vagrant Cloud의 ubuntu/focal64
이미지를 사용한다.
Vagrantfile
# 노드 수 정의
NO_MASTER_NODE = 2 # 마스터 노드 수 지정
NO_WORKER_NODE = 2 # 작업자 노드 수 지정
#IP 주소 정의
IP_ADDR = "192.168.100.20"
Vagrant.configure("2") do |config|
### LoadBalancer Node ####
config.vm.define "k8s-lb" do |ubuntu|
ubuntu.vm.box = "ubuntu/focal64"
ubuntu.vm.hostname = "k8s-lb"
ubuntu.vm.network "private_network", ip: "192.168.100.200"
ubuntu.vm.provider "virtualbox" do |vb|
vb.name = "k8s-lb"
vb.cpus = 1
vb.memory = 2048
end
end
### Master Node ####
(1..NO_MASTER_NODE).each do |i|
config.vm.define "k8s-master#{i}" do |master|
master.vm.box = "ubuntu/focal64"
master.vm.hostname = "k8s-master#{i}"
master.vm.network "private_network", ip: IP_ADDR + "#{i}"
master.vm.provider "virtualbox" do |vb|
vb.name = "k8s-master#{i}"
vb.cpus = 2
vb.memory = 2048
end
end
### Worker node ###
(1..NO_WORKER_NODE).each do |i|
config.vm.define "k8s-worker#{i}" do |worker|
worker.vm.box = "ubuntu/focal64"
worker.vm.hostname = "k8s-worker#{i}"
worker.vm.network "private_network", ip: IP_ADDR + "#{i+NO_MASTER_NODE}"
worker.vm.provider "virtualbox" do |vb|
vb.name = "k8s-worker#{i}"
vb.cpus = 2
vb.memory = 2048
end
end
end
end
end
다음 명령을 실행하여 VM을 생성한다.
vagrant up
✔️ 로드밸런서 구성하기
고가용성(HA)을 위해 필요한 것이 로드밸런서(load balancer)이다. 로드 밸런서 뒤에 있는 apiserver 중 한개의 apiserver에 장애가 발생해도, 나머지 apiserver로 정상적인 서비스를 하도록 로드를 분배한다.
로드 밸런서는 HAproxy
라는 application을 설치한다.
sudo apt update && sudo apt install haproxy -y
HAproxy
설정 파일을 다음과 같이 입력한다.
cat << EOF | sudo tee -a /etc/haproxy/haproxy.cfg
frontend kubernetes-master-lb
bind 0.0.0.0:6443
option tcplog
mode tcp
default_backend kubernetes-master-nodes
backend kubernetes-master-nodes
mode tcp
balance roundrobin
option tcp-check
option tcplog
server k8s-master1 192.168.100.201:6443 check
server k8s-master2 192.168.100.202:6443 check
EOF
다음으로 변경 사항이 적용될 수 있도록 HAproxy
시스템을 재시작한다.
sudo systemctl restart haproxy
sudo systemctl enable haproxy
마지막으로 SELinux
를 permissive mod(disable)로 세팅한다.
sudo apt install selinux-utils
setenforce 0
✔️ 호스트 파일 수정 및 Swap off
모든 노드에서 공통적으로 작업해야하는 항목들이 있다.
hosts 파일 설정
sudo vi /etc/hosts
hosts 파일에 아래 내용을 추가한다.
192.168.100.200 k8s-lb
192.168.100.201 k8s-master1
192.168.100.202 k8s-master2
192.168.100.203 k8s-worker1
192.168.100.204 k8s-worker2
이렇게 /etc/hosts
파일을 수정함으로써 ip 대신 사용자가 정의한 이름으로 사용할 수 있다.
Swap off
kubelet
이 제대로 작동하려면 반드시 swap off(disable)해야한다.
다음 명령을 실행한다.
sudo -i
swapoff -a
echo 0 > /proc/sys/vm/swappiness
sed -e '/swap/ s/^#*/#/' -i /etc/fstab
✔️ Docker 및 Kubernetes 설치
모든 노드에 Docker, Kubernetes를 설치해야한다.
docker 설치
sudo apt update
sudo apt install -y docker.io
sudo systemctl start docker
sudo systemctl enable docker
docker 데몬 교체
docker 1.22
버전부터 cgroups
를 지원하지 않는다.
따라서 Cgroup Driver
를 systemd
로 설정해줘야한다.
cat << EOF | sudo tee –a /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
변경 사항을 적용하기 위해 시스템을 다시 시작한다.
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo systemctl daemon-reload
sudo systemctl restart docker
Kubernetes 설치
kubelet
, kubeadm
, kubectl
을 설치한다.
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
sudo 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
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
✔️ shell script로 간편화하기
로드밸런서 구성을 제외하고, 지금까지의 작업을 모든 노드에서 진행해야한다. 이는 굉장히 번거로운 작업이므로, shell script로 모든 과정을 작성해보았다.
install_docker_and_k8s.sh
#!/bin/bash
sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config
sed -i 's/archive.ubuntu.com/mirror.kakao.com/g' /etc/apt/sources.list
sed -i 's/security.ubuntu.com/mirror.kakao.com/g' /etc/apt/sources.list
systemctl restart ssh
#------호스트 파일 수정-----#
echo "Hostname set"
echo "192.168.100.200 k8s-lb" >> /etc/hosts
echo "192.168.100.201 k8s-master1" >> /etc/hosts
echo "192.168.100.202 k8s-master2" >> /etc/hosts
echo "192.168.100.203 k8s-worker1" >> /etc/hosts
echo "192.168.100.204 k8s-worker2" >> /etc/hosts
#------Swap Disable-----#
echo "Swap off"
sudo -i
swapoff -a
echo 0 > /proc/sys/vm/swappiness
sed -e '/swap/ s/^#*/#/' -i /etc/fstab
#------Install Docker-----#
echo "Install docker"
sudo apt update
sudo apt install -y docker.io
sudo systemctl start docker
sudo systemctl enable docker
cat << EOF | sudo tee –a /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo systemctl daemon-reload
sudo systemctl restart docker
#------Install k8s-----#
echo "Install kubernetes"
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
sudo 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
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
Vagrantfile
에서 shell
프로비저너를 이용하여, VM 생성 시 shell script를 실행하도록 설정할 수 있다.
### shell script 실행 ###
config.vm.provision "shell", path: "scripts/install_docker_and_k8s.sh"
Vagrantfile
# 노드 수 정의
NO_MASTER_NODE = 2 # 마스터 노드 수 지정
NO_WORKER_NODE = 2 # 작업자 노드 수 지정
#IP 주소 정의
IP_ADDR = "192.168.100.20"
Vagrant.configure("2") do |config|
### LoadBalancer Node ####
config.vm.define "k8s-lb" do |ubuntu|
ubuntu.vm.box = "ubuntu/focal64"
ubuntu.vm.hostname = "k8s-lb"
ubuntu.vm.network "private_network", ip: "192.168.100.200"
ubuntu.vm.provider "virtualbox" do |vb|
vb.name = "k8s-lb"
vb.cpus = 1
vb.memory = 2048
end
end
### Master Node ####
(1..NO_MASTER_NODE).each do |i|
config.vm.define "k8s-master#{i}" do |master|
master.vm.box = "ubuntu/focal64"
master.vm.hostname = "k8s-master#{i}"
master.vm.network "private_network", ip: IP_ADDR + "#{i}"
master.vm.provider "virtualbox" do |vb|
vb.name = "k8s-master#{i}"
vb.cpus = 2
vb.memory = 2048
end
end
### Worker node ###
(1..NO_WORKER_NODE).each do |i|
config.vm.define "k8s-worker#{i}" do |worker|
worker.vm.box = "ubuntu/focal64"
worker.vm.hostname = "k8s-worker#{i}"
worker.vm.network "private_network", ip: IP_ADDR + "#{i+NO_MASTER_NODE}"
worker.vm.provider "virtualbox" do |vb|
vb.name = "k8s-worker#{i}"
vb.cpus = 2
vb.memory = 2048
end
end
end
end
### shell script 실행 ###
config.vm.provision "shell", path: "scripts/install_docker_and_k8s.sh"
end
위와 같이 작성 후 vagrant up
하면, 로드밸런서 구성만 하고 바로 Kubernetes 초기화 작업으로 들어가면 된다.
✔️ Kubernetes 초기화 및 노드 조인
Kubernetes 초기화
컨트롤 플레인 노드 중 하나(k8s-master1
노드)에서 kubeadm init
명령을 통해 kubernetes 초기화 작업을 진행한다.
sudo kubeadm init --control-plane-endpoint k8s-lb:6443 --pod-network-cidr 10.244.0.0/16 --apiserver-advertise-address=192.168.100.201 --upload-certs
--control-plane-endpoint
: 로드밸런서 서버와6443
포트를 입력--pod-network-cidr
: 쿠버네티스에서 사용할 컨테이너의 네트워크 대역을 지정--apiserver-advertise-address
: 쿠버네티스가 생성한 TLS 인증서에 적용할 IP 또는 도메인을 명시 => 현재 마스터 노드의 주소(k8s-master1
의 IP) 입력--upload-certs
: 노드 join에 필요한 cert key값을 받기 위해 필요
성공하면 다음과 같은 결과가 출력된다.
- 작업하는 환경마다 token, hash, key 값은 다르다.
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Alternatively, if you are the root user, you can run:
export KUBECONFIG=/etc/kubernetes/admin.conf
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
You can now join any number of the control-plane node running the following command on each as root:
kubeadm join k8s-lb:6443 --token sueyw4.e88pafmhx5f4gte5 \
--discovery-token-ca-cert-hash sha256:fff4c031326be2092f7eb41f674170cf0929bddf899ac3919fc4c55b429ca837 \
--control-plane --certificate-key 1a796bbf67694cb02e22b5d50c973875847f9693afe6b2cb7275aa01cf5d8b88
Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join k8s-lb:6443 --token sueyw4.e88pafmhx5f4gte5 \
--discovery-token-ca-cert-hash sha256:fff4c031326be2092f7eb41f674170cf0929bddf899ac3919fc4c55b429ca837
현재 작업 중인 노드(k8s-master1
)에서 kubectl
명령어 사용을 위해 인증파일을 생성해야한다.
kubectl
은 항상 $HOME/.kube
를 참조한다. root가 아닌 노멀 사용자는 반드시 인증이 필요하다.
이 파일은 쿠버네티스에 인증하기 위한 파일로, 절대 노출되면 안된다.
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
노드 조인
다른 마스터 노드(k8s-master2
)에서 아래와 같은 명령을 실행하여 k8s-master1
으로 조인한다.
이때, 루트로 실행해야하기 때문에 sudo
를 붙이거나, sudo -i
명령으로 루트 사용자로 전환한다.
sudo kubeadm join k8s-lb:6443 --token sueyw4.e88pafmhx5f4gte5 \
--discovery-token-ca-cert-hash sha256:fff4c031326be2092f7eb41f674170cf0929bddf899ac3919fc4c55b429ca837 \
--control-plane --certificate-key 1a796bbf67694cb02e22b5d50c973875847f9693afe6b2cb7275aa01cf5d8b88
워커 노드들(k8s-worker1
, k8s-worker2
)에서는 컨트롤 플레인(마스터) 노드로 조인하기 위해 다음 명령을 실행한다.
sudo kubeadm join k8s-lb:6443 --token sueyw4.e88pafmhx5f4gte5 \
--discovery-token-ca-cert-hash sha256:fff4c031326be2092f7eb41f674170cf0929bddf899ac3919fc4c55b429ca837
🔎 만약 토큰, 해쉬를 잊었다면?
- 토큰을 모를 경우
kubeadm token list
- 토큰이 없는 경우 (기본 24시간 후 삭제)
kubeadm token create
- --discovery-token-ca-cert-hash를 모를 경우
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | \
openssl dgst -sha256 -hex | sed 's/^.* //'
모든 노드에서 조인이 성공하면 결과는 다음과 같다.
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master1 NotReady control-plane 7m10s v1.25.0
k8s-master2 NotReady control-plane 5m19s v1.25.0
k8s-worker1 NotReady <none> 102s v1.25.0
k8s-worker2 NotReady <none> 65s v1.25.0
아직 상태가 NotReady
인 것을 확인할 수 있다.
kubeadm init
명령 시 add-on
을 완성하지 않았었기 때문에(CNI(container network interface) 설정), 구성을 마무리 해줘야한다 : Cluster Networking | Kubernetes
✔️ CNI(Container Network Interface) 설치
여러가지 CNI 플러그인이 있지만 여기서는 Calico를 사용한다.
curl
명령어로 yaml
파일을 다운 받는다.
curl https://docs.projectcalico.org/manifests/calico-typha.yaml -o calico.yaml
vi calico.yaml
calico.yaml
파일을 다음과 같디 수정한다. 주석 #
을 해제하고, kubeadm init
명령 시 설정했던 pod의 cidr와 동일하게 맞춰줘야한다.
- name: CALICO_IPV4POOL_CIDR
value: "10.244.0.0/16"
다음으로 calico를 설치한다.
kubectl apply -f calico.yaml
시간이 조금 지나면 모두 Ready
상태가 되는 것을 확인할 수 있다.
kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master1 Ready control-plane 27m v1.25.0
k8s-master2 Ready control-plane 25m v1.25.0
k8s-worker1 Ready <none> 26m v1.25.0
k8s-worker2 Ready <none> 23m v1.25.0
이때, pod들도 모두 Running
상태가 되어야한다.
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system calico-kube-controllers-58dbc876ff-vjvt5 1/1 Running 0 4m32s
kube-system calico-node-db6w2 1/1 Running 0 4m32s
kube-system calico-node-hgxd6 1/1 Running 0 4m32s
kube-system calico-node-p2wt8 1/1 Running 0 4m32s
kube-system calico-node-zc4xs 1/1 Running 1 (71s ago) 4m32s
kube-system calico-typha-644446b795-7jnhz 1/1 Running 0 4m32s
kube-system coredns-565d847f94-7gnlz 1/1 Running 0 16m
kube-system coredns-565d847f94-pb8nj 1/1 Running 0 16m
kube-system etcd-k8s-master1 1/1 Running 0 16m
kube-system etcd-k8s-master2 1/1 Running 0 9m25s
kube-system kube-apiserver-k8s-master1 1/1 Running 0 16m
kube-system kube-apiserver-k8s-master2 1/1 Running 1 (119s ago) 9m17s
kube-system kube-controller-manager-k8s-master1 1/1 Running 2 (2m18s ago) 16m
kube-system kube-controller-manager-k8s-master2 1/1 Running 0 9m27s
kube-system kube-proxy-8h4dm 1/1 Running 0 9m36s
kube-system kube-proxy-9dssz 1/1 Running 0 7m36s
kube-system kube-proxy-j58ks 1/1 Running 0 7m58s
kube-system kube-proxy-p49hc 1/1 Running 0 16m
kube-system kube-scheduler-k8s-master1 1/1 Running 1 (3m25s ago) 16m
kube-system kube-scheduler-k8s-master2 1/1 Running 1 (2m51s ago) 9m24s
watch
명령어로 실시간으로 확인해볼 수 있고, 시간이 조금 지나면 모두 Running
상태가 되는 것을 확인할 수 있다.
watch kubectl get pods -A
본 실습은 공식 문서에서도 확인해볼 수 있으며, 다음에는 실습을 NCP에서 진행해볼 예정이다.
Reference