오늘은 쿠버네티스의 고가용성(HA) 구성부터 시작해서 노드 스케줄링 전략, 그리고 쿠버네티스의 패키지 매니저인 Helm까지 다뤘다. 내용 자체는 많았지만 각 개념이 실제 운영 환경에서 어떻게 쓰이는지 연결되니까 이해하기 좋았다.
지금까지는 마스터노드가 1개였는데, 오늘은 마스터노드를 3개로 구성하는 고가용성(HA) 클러스터를 직접 만들어봤다.
핵심 개념은 이렇다. 마스터노드가 여러 개 있어도 한 번에 하나만 active 상태로 동작하고, 나머지는 standby 상태로 대기한다. active 노드에 장애가 생기면 standby 중 하나가 새로운 active로 선출된다. 이때 새 마스터를 선출하려면 과반수 투표가 필요하기 때문에 마스터노드 수는 반드시 홀수(3, 5, 7...)로 가져가야 한다.
클라이언트
│
HAProxy (211.183.3.50:6443) ← 단일 접속점
│
├── m1 (211.183.3.101) ← active
├── m2 (211.183.3.102) ← standby
└── m3 (211.183.3.103) ← standby
1. worker2를 클론해서 m1, m2, m3 구성 (IP: 101, 102, 103)
각 노드에서 기존 클러스터 설정을 초기화한다.
kubeadm reset
# 각 노드의 클러스터를 리셋
2. HAProxy 서버 구성 (hostname: haproxy, IP: 211.183.3.50)
apt update -y
apt install -y haproxy
vi /etc/haproxy/haproxy.cfg
# 기존 내용 전부 삭제(ggdG) 후 아래 내용으로 교체
HAProxy 설정 핵심:
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 211.183.3.101:6443 check
server k8s-master2 211.183.3.102:6443 check
server k8s-master3 211.183.3.103:6443 check
systemctl restart haproxy
systemctl enable haproxy
3. m1에서 클러스터 초기화 (반드시 m1에서만!)
m1에서 클러스터를 초기화할 때 기존 명령과 다른 점은 --control-plane-endpoint를 HAProxy IP로 지정하는 것이다.
kubeadm init --pod-network-cidr=10.244.0.0/16 \
--upload-certs \
--kubernetes-version=v1.30.3 \
--ignore-preflight-errors=all \
--control-plane-endpoint=211.183.3.50
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
HAProxy 서버에서도 kubectl 명령을 사용하려면 두 가지가 필요하다.
1. kubectl 설치
apt-get install -y apt-transport-https ca-certificates curl
mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
apt-get update
apt-get install -y kubectl
apt-mark hold kubectl
2. m1의 kubeconfig 파일 복사
# <m1>에서 내용 복사
cat /etc/kubernetes/admin.conf
# <haproxy>에서 붙여넣기
mkdir ~/.kube
vi ~/.kube/config
m1을 서스펜드해보면 잠시 후 NotReady 상태가 되고, HAProxy가 m2나 m3로 트래픽을 자동으로 넘기는 걸 확인할 수 있었다.
쿠버네티스 스케줄러가 Pod를 어느 노드에 배치할지 결정하는 방법이 여러 가지 있다.
노드에 key:value 형태의 라벨을 붙이고, Pod 스펙에서 해당 라벨을 가진 노드에만 배치되도록 지정하는 가장 단순한 방법이다.
kubectl label node worker2 gpu=true
spec:
nodeSelector:
gpu: "true"
NodeSelector보다 세밀한 조건을 걸 수 있다. 두 가지 모드가 있다.
| 모드 | 설명 |
|---|---|
requiredDuringScheduling... | 반드시 해당 노드에만 배치 (조건 불충족 시 Pending) |
preferredDuringScheduling... | 가능하면 해당 노드에 배치 (불가능하면 다른 노드에 배치) |
required는 조건을 만족하는 노드가 없으면 Pod가 영원히 Pending 상태에 머문다. 실제로 worker1을 셧다운하고 required로 worker1을 지정한 Pod를 띄워보니 Pending 상태가 유지됐고, worker1을 다시 켜자 바로 Running으로 전환됐다.
앞의 두 방식이 Pod 입장에서 노드를 선택하는 개념이라면, Taint는 노드 입장에서 Pod를 기피하는 개념이다.
# worker1에 taint 설정 - team=dev toleration이 없는 Pod는 배치 거부
kubectl taint node worker1 team=dev:NoSchedule
Taint가 걸린 노드에 Pod를 배치하려면 Pod 스펙에 Toleration을 명시해야 한다.
tolerations:
- key: "team"
operator: "Equal"
value: "dev"
effect: "NoSchedule"
실습에서 모든 워커노드에 Taint를 걸고 Toleration 없는 Deployment를 만들어보니 Pod가 전부 Pending 상태가 됐다. Affinity + Tolerations를 조합하면 특정 노드에만 특정 Pod를 배치하는 세밀한 제어가 가능하다.
apt가 우분투의 패키지 매니저라면, Helm은 쿠버네티스의 패키지 매니저다.
앱 하나를 배포하려면 Deployment, Service, PV, PVC, ConfigMap, Secret 등 수많은 매니페스트가 필요하다. Helm을 쓰면 이 묶음을 차트(Chart)로 패키징해서 변수만 조절해 여러 환경(dev, staging, production)에 손쉽게 배포할 수 있다.
| 개념 | 설명 |
|---|---|
| 레포지토리 | 차트들을 모아놓은 공간 (Docker Hub와 비슷) |
| 차트 | 앱 배포에 필요한 매니페스트들의 묶음 |
| 릴리스 | 차트로 찍어낸 실제 배포 인스턴스 |
차트 허브는 artifacthub.io 에서 찾을 수 있다.
# Helm 설치 (kubectl 명령을 치는 곳에 설치)
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod +x get_helm.sh
./get_helm.sh
# 레포지토리 추가
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
# 차트 검색
helm search repo nginx
# 릴리스 설치 (기본값으로)
helm install mynginx bitnami/nginx
# values 파일로 커스터마이징해서 설치
helm install node-mynginx bitnami/nginx -f my-values.yml
# 릴리스 목록 확인
helm list
# 릴리스 삭제
helm delete mynginx
Helm의 진짜 강점은 values 파일로 변수를 조절해서 같은 차트로 다른 설정의 릴리스를 찍어낼 수 있다는 점이다.
# my-values.yml
service:
type: NodePort
replicaCount: 3
helm install my3nginx bitnami/nginx -f my-values.yml
수정하지 않은 나머지 값들은 전부 차트의 기본값(default values)이 적용된다. 변경하고 싶은 것만 오버라이드하면 되니까 관리가 훨씬 편하다.
오늘 실습에서는 bitnami 레포의 ingress-controller 차트가 이미지 지원 종료로 설치가 안 되는 상황도 겪었다. 이럴 때는 다른 레포를 찾아서 쓰면 된다.
helm repo add nginx https://helm.nginx.com/stable
helm install ingress-controller nginx/nginx-ingress -n ingress-nginx
차트가 가진 모든 변수를 파일로 내보내서 분석할 수 있다.
# 차트의 기본 values를 파일로 저장
helm inspect values bitnami/nginx > nginx-values.yml
# 내가 변경하고 싶은 값만 별도 파일로 작성
vi my-values.yml
# my-values.yml - 변경하고 싶은 값만 작성 (나머지는 기본값 적용)
service:
type: NodePort
replicaCount: 3
helm repo add metallb https://metallb.github.io/metallb
helm install metallb metallb/metallb -n metallb-system
# prometheus-community 레포 추가
helm repo add pro https://prometheus-community.github.io/helm-charts
helm repo update
# values 파일 작성 (Grafana 서비스 타입 및 계정 설정)
vi pro-values.yml
grafana:
adminUser: admin
adminPassword: admin
service:
type: LoadBalancer
helm install pro pro/kube-prometheus-stack -f pro-values.yml
# StorageClass를 지정해서 WordPress 설치
vi wp-myvalues.yml
global:
defaultStorageClass: "fast-sc"
helm install wp bitnami/wordpress -f wp-myvalues.yml
# 릴리스 삭제 (uninstall이 공식 명령어)
helm uninstall wp
# 릴리스 삭제 후에도 PVC가 남아있을 수 있으니 확인 후 정리
kubectl delete pvc --all
주의:
helm uninstall로 릴리스를 삭제해도 PVC는 자동으로 삭제되지 않는다. 재설치 전에 반드시kubectl delete pvc --all로 정리해야 한다.
오늘은 운영 환경에서 실제로 많이 쓰이는 개념들을 집중적으로 다뤘다. 마스터노드 삼중화는 EKS 같은 관리형 서비스에서는 AWS가 알아서 해주는 부분인데, 직접 구성해보니 내부 동작이 훨씬 명확하게 이해됐다. Helm은 앞으로 EKS에서 애드온 설치할 때 계속 쓰게 될 것 같아서 확실히 익혀두길 잘했다.
이번 velog는 AWS Kiro를 통해 작성하였다.