대상: Kubespray로 구축/운영 중인 워커 노드(control-plane, etcd 제외)
전제: 운영 중 클러스터, 다운타임 최소화 목표
체크리스트
- 사전 점검: 노드 명/파드/스토리지 확인 → 백업/변경창 확보
- 드레인 & 제거:
kubectl cordon/drain→kubectl delete node→ (필요 시) CNI 잔여 리소스 정리 - Kubespray 정리:
remove-node.yml(온라인/오프라인 분기) - 인벤토리 정리: 제거 노드 정보 삭제
- 노드 재추가: 신규 VM 준비 → 인벤토리에 추가 →
facts.yml→scale.yml --limit=<새노드> - 검증 & 레이블/테인트 복원
0. 변경 전 점검
# 노드/파드 분포
kubectl get nodes -o wide
kubectl get pods -A -o wide --field-selector spec.nodeName=<노드명>
# 로컬 PV / emptyDir 존재 여부와 PDB 확인
kubectl get pv,pvc -A
kubectl get pdb -A
kubectl drain은 PDB를 존중하여 안전하게 파드를 축출. PDB 때문에 드레인이 막히면, 일시적으로 스케일 아웃/인, PDB 완화 등으로 처리.
PDB (PodDisruptionBudget) 는 쿠버네티스에서 파드 중단 예산이라고 부르는 리소스
📌 정의
- PDB는 자발적인 중단(eviction) 상황에서 동시에 내려갈 수 있는 파드의 수를 제한하는 정책.
- 예를 들어
kubectl drain, 노드 업그레이드, 수동 롤링 업데이트 같은 관리자/운영자의 작업이 여기에 해당됨.
📌 왜 필요할까?
어떤 워크로드(특히 ReplicaSet/Deployment, StatefulSet)는 항상 일정 개수 이상의 파드가 살아 있어야 서비스가 끊기지 않음이 보장.
- 예: DB 프록시, 웹 API 서버, 인그레스 컨트롤러 등.
PDB를 설정해 두면 운영자가 노드를 내릴 때 쿠버네티스가:
- PDB 조건을 확인하고,
- 동시에 너무 많은 파드가 내려가지 않도록 제어.
📌 사용 예시
apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: api-pdb spec: minAvailable: 2 # 항상 최소 2개는 살아 있어야 함 selector: matchLabels: app: my-api위 설정의 의미:
app=my-api라벨을 가진 파드가 총 3개라면,kubectl drain같은 작업 시 동시에 1개까지만 내려갈 수 있고 최소 2개는 살아 있어야 함.
📌 핵심 요약
- 대상: 자발적 중단(노드 드레인, 롤링 업데이트 등).
- 보장: 워크로드의 가용성을 유지하면서 안전하게 파드를 재배치.
- 설정 단위:
minAvailable(최소 유지 수) 또는maxUnavailable(최대 허용 중단 수).
👉 정리하면,
PDB는 “내 서비스 파드가 최소 몇 개는 항상 살아 있어야 한다” 라는 업타임 안전장치
1. 워커 노드 드레인 & K8s 오브젝트 제거
# 신규 스케줄 방지
kubectl cordon <노드명>
# 안전 축출(DS 무시, emptyDir 데이터 삭제 동의, 고아 파드 강제 시 --force)
kubectl drain <노드명> --ignore-daemonsets --delete-emptydir-data
# 노드 오브젝트 제거
kubectl delete node <노드명>
기본 절차: 드레인 → 노드 삭제.
CNI(예: Calico) 잔여 리소스 정리 (선택)
드물게 노드 오브젝트 삭제 후에도 CNI의 노드/아이피 할당이 남는 경우가 있습니다. Calico 사용 시:
# calicoctl이 구성되어 있다면
calicoctl get nodes -o wide
calicoctl delete node <노드ID or 노드명>
⚠️ 주의: 워커 노드에는 보통
:6443(apiserver)이 없습니다. 프로세스 수동kill -9로 정리하지 마세요. 꼭 필요하면systemctl stop kubelet정도만 일시 사용하고, 정리는 Kubespray로 일원화하는 게 안전합니다.
2. Kubespray로 노드 정리(remove-node.yml)
온라인(SSH 가능) 노드: 원격 접속으로 패키지/네트워크를 깨끗하게 정리.
cd kubespray/
# (권장) facts 캐시 갱신
ansible-playbook -i inventory/<cluster>/hosts.yaml --become --become-user=root playbooks/facts.yml
# 노드 정리(해당 노드로 한정)
ansible-playbook -i inventory/<cluster>/hosts.yaml --become --become-user=root \
remove-node.yml -e node=<노드명>
오프라인(SSH 불가) 노드: Kubespray가 접근할 수 없으므로 강제 제거 플래그를 사용.
ansible-playbook -i inventory/<cluster>/hosts.yaml --become --become-user=root \
remove-node.yml -e node=<노드명> -e reset_nodes=false -e allow_ungraceful_removal=true
오프라인 노드 제거 시
reset_nodes=false+allow_ungraceful_removal=true를 추가해야 실패를 피합니다.
3. 인벤토리에서 항목 삭제
노드가 정상 제거되었으면 Ansible 인벤토리에서 해당 노드 항목을 지움. (예: inventory/<cluster>/hosts.yaml 또는 inventory.ini)
4. 대체/신규 워커 노드 준비
신규 VM(또는 베어메탈)을 준비하고 다음을 만족시켜야 함.
- (권장) SSH 무비번 접속 가능(컨트롤 노드 → 신규 노드)
sudo권한 부여, 시간 동기화, 네트워크/방화벽 포트 허용- (재사용 IP일 때) known_hosts 충돌 제거:
ssh-keygen -R <노드IP> ssh-keygen -R <노드호스트명> - swap off(Kubespray가 처리하지만, 미리 꺼두면 더 깔끔)
sudo swapoff -a sudo sed -ri 's/^([^#].*\sswap\s)/#\1/' /etc/fstab
5. 인벤토리에 신규 노드 추가 (예시)
YAML(권장)
all:
hosts:
worker-new:
ansible_host: 10.10.10.25
ip: 10.10.10.25
access_ip: 10.10.10.25
ansible_user: ubuntu
children:
kube_control_plane:
hosts:
cp-1: {}
cp-2: {}
kube_node:
hosts:
worker-1: {}
worker-2: {}
worker-new: {}
etcd:
hosts:
cp-1: {}
cp-2: {}
k8s_cluster:
children:
kube_control_plane: {}
kube_node: {}
INI(사용 중이라면)
[all]
worker-new ansible_host=10.10.10.25 ip=10.10.10.25 access_ip=10.10.10.25 ansible_user=ubuntu
[kube_control_plane]
cp-1
cp-2
[kube_node]
worker-1
worker-2
worker-new
[etcd]
cp-1
cp-2
[k8s_cluster:children]
kube_control_plane
kube_node
6. facts 갱신 후, 스케일 인(노드 추가)
Kubespray는 --limit로 특정 호스트만 처리할 수 있는데, 그 전에 facts 캐시를 전체 갱신하는 것이 안전.
# 1) facts 캐시 갱신(전체)
ansible-playbook -i inventory/<cluster>/hosts.yaml --become --become-user=root playbooks/facts.yml
# 2) 새 노드에 한정하여 설치/조인
ansible-playbook -i inventory/<cluster>/hosts.yaml --become --become-user=root \
scale.yml --limit=worker-new
7. 사후 검증 & 라벨/테인트 복원
# 노드 조인/상태
kubectl get nodes -o wide
kubectl describe node worker-new
# (필요 시) 과거 라벨/테인트 복원
kubectl label node worker-new node-role.kubernetes.io/worker='' env=prod
kubectl taint nodes worker-new node.kubernetes.io/purpose=ingress:NoSchedule
8. 장애/에러 트러블슈팅 팁
- PDB 때문에 drain 실패: 일시 스케일업·PDB 일시 완화·
--disable-eviction은 최후의 수단. - 노드가 오프라인:
remove-node.yml -e reset_nodes=false -e allow_ungraceful_removal=true로 강제 정리. - Calico 잔여 엔트리:
calicoctl delete node <ID>로 정리. - facts 캐시 꼬임/부분 작업 불안:
playbooks/facts.yml→--limit작업 재시도. - 추가 실패 시 롤백: 해당 노드만
reset.yml --limit=<노드>로 초기화 후 재시도(전체 리셋 아님, 타겟만 한정).
9. 왜 “프로세스 강제 종료”를 권하지 않나?
- 워커에는 보통
kube-apiserver(:6443)가 없습니다. 임의kill -9는 더러운 상태를 남김. - Kubespray의
remove-node.yml은 kubelet/런타임/네트워크를 정상 순서로 정리.
오프라인 노드처럼 접근 불가한 상황만 특수 플래그로 처리.
10. 명령 모음 (복붙용)
# A) 드레인 & 삭제
kubectl cordon <node>
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
kubectl delete node <node>
# B) (선택) Calico 잔여 정리
calicoctl get nodes -o wide
calicoctl delete node <node>
# C) Kubespray 정리 (온라인)
ansible-playbook -i inventory/<cluster>/hosts.yaml -b playbooks/facts.yml
ansible-playbook -i inventory/<cluster>/hosts.yaml -b remove-node.yml -e node=<node>
# Kubespray 정리 (오프라인)
ansible-playbook -i inventory/<cluster>/hosts.yaml -b remove-node.yml \
-e node=<node> -e reset_nodes=false -e allow_ungraceful_removal=true
# D) 신규 노드 추가
ansible-playbook -i inventory/<cluster>/hosts.yaml -b playbooks/facts.yml
ansible-playbook -i inventory/<cluster>/hosts.yaml -b scale.yml --limit=<new-node>
# E) 검증 & 복원
kubectl get nodes -o wide
kubectl label node <new-node> env=prod
kubectl taint nodes <new-node> node.kubernetes.io/purpose=ingress:NoSchedule
ⓒ 2025 엉뚱한 녀석의 블로그 [quirky guy's Blog]. All rights reserved. Unauthorized copying or redistribution of the text and images is prohibited. When sharing, please include the original source link.
🛠 마지막 수정일: 2025.09.18
답글 남기기
댓글을 달기 위해서는 로그인해야합니다.