Kubernetes v1.30.0 오프라인 설치 가이드 (Rocky Linux 9.6)
폐쇄망 환경에서 kubeadm 기반 Kubernetes v1.30.0 클러스터를 구성하는 절차를 안내합니다. containerd v2.2.0을 컨테이너 런타임으로, Calico를 CNI로 사용합니다.
전제 조건
- Rocky Linux 9.6 서버
- 단일 구성: 컨트롤 플레인 1대 + 워커 노드 1대 이상
- HA(3중화) 구성: 컨트롤 플레인 3대 + 워커 노드 1대 이상 + VIP 1개
- 모든 노드에서 아래 설치 파일 접근 가능
- swap 비활성화 완료 (
swapoff -a및/etc/fstab주석 처리)
디렉토리 구조
| 경로 | 설명 |
|---|---|
common/rpms/ |
공통 의존성 RPM (모든 노드) |
k8s/rpms/ |
kubeadm, kubelet, kubectl, containerd RPM |
k8s/binaries/ |
helm, cri-dockerd 등 바이너리 |
k8s/images/ |
kubeadm, Calico 등 컨테이너 이미지 .tar |
k8s/charts/ |
Helm 차트 |
k8s/utils/ |
calico.yaml 등 매니페스트 |
Phase 0: 설치 파일 배포 (Bastion → 전체 노드)
# 배포 대상 노드 IP 목록 (환경에 맞게 수정)
NODES=("<MASTER1_IP>" "<MASTER2_IP>" "<MASTER3_IP>" "<WORKER1_IP>" "<WORKER2_IP>", "<WORKER3_IP>")
for IP in "${NODES[@]}"; do
echo "Sending to $IP..."
scp ~/k8s-1.30.tar.gz rocky@$IP:~/
done
# 모든 노드에서 압축 해제
tar -zxvf ~/k8s-1.30.tar.gz
Phase 1: 공통 RPM 설치 (전체 노드)
# 1. 공통 의존성 RPM 설치
sudo dnf localinstall -y --disablerepo='*' common/rpms/*.rpm
# 2. kubeadm, kubelet, kubectl, containerd RPM 설치
sudo dnf localinstall -y --disablerepo='*' k8s/rpms/*.rpm
# 3. kubelet 활성화 (kubeadm init 전에는 시작하지 않아도 됨)
sudo systemctl enable kubelet
Phase 2: OS 사전 설정 (전체 노드)
# 1. SELinux permissive 모드
sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
# 2. 커널 모듈 로드
sudo modprobe overlay
sudo modprobe br_netfilter
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
# 3. sysctl 설정
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
# 4. hosts 파일 등록 (환경에 맞게 수정)
sudo tee -a /etc/hosts <<EOF
<MASTER1_IP> <MASTER1_HOSTNAME>
<MASTER2_IP> <MASTER2_HOSTNAME>
<MASTER3_IP> <MASTER3_HOSTNAME>
<WORKER1_IP> <WORKER1_HOSTNAME>
<WORKER2_IP> <WORKER2_HOSTNAME>
<WORKER3_IP> <WORKER3_HOSTNAME>
EOF
Phase 3: containerd 설정 (전체 노드)
# containerd 기본 설정 생성
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
# cgroup driver를 systemd로 변경 (필수)
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# Harbor 인증서 경로 설정
sudo sed -i "s|config_path = '/etc/containerd/certs.d:/etc/docker/certs.d'|config_path = '/etc/containerd/certs.d'|g" /etc/containerd/config.toml
# containerd 시작 및 활성화
sudo systemctl enable --now containerd
sudo systemctl status containerd
(선택) containerd 데이터 경로 변경 — 소프트링크 방식
OS 루트 디스크 용량이 작고 별도 데이터 디스크(예: /app)가 마운트되어 있는 경우에 적용합니다.
containerd 시작 전 또는 서비스를 중지한 상태에서 진행해야 합니다.
# 서비스 중지 (이미 실행 중인 경우)
sudo systemctl stop kubelet
sudo systemctl stop containerd
# 1. 실제 데이터를 저장할 디렉토리 생성 (경로는 환경에 맞게 수정)
sudo mkdir -p /app/containerd_data
# 2. 기존 데이터가 있으면 이동 (처음 설치라면 /var/lib/containerd 자체가 없으므로 자동 skip)
if [ -d "/var/lib/containerd" ]; then
sudo mv /var/lib/containerd/* /app/containerd_data/
sudo rmdir /var/lib/containerd
fi
# 3. 소프트링크 생성: /var/lib/containerd -> /app/containerd_data
sudo ln -s /app/containerd_data /var/lib/containerd
# 4. 링크 확인 (화살표가 표시되어야 함)
ls -ld /var/lib/containerd
# 결과 예시: lrwxrwxrwx ... /var/lib/containerd -> /app/containerd_data
# 5. 서비스 재시작
sudo systemctl start containerd
sudo systemctl start kubelet
config.toml의root값을 직접 변경하는 방법도 있지만, 소프트링크 방식은 기존 경로를 그대로 유지하므로 다른 툴과의 호환성을 더 쉽게 확보할 수 있습니다.containerd 재시작 후에도
SystemdCgroup = true가 적용되지 않으면 아래 명령으로 확인하세요.
Phase 4: 이미지 로드 (전체 노드)
for tar_file in k8s/images/*.tar; do
echo "Loading $tar_file..."
sudo ctr -n k8s.io images import "$tar_file"
done
# 확인
sudo ctr -n k8s.io images list | grep kube-apiserver
Phase 5: 로드밸런서 구성 (HA 3중화 시에만 / 단일 구성이면 Phase 6으로)
HA 구성을 위해 로드밸런서가 필요합니다. 환경에 따라 아래 두 가지 방식 중 하나를 선택합니다.
[사전 결정] VIP 주소를 인증서에 직접 설정할지, FQDN으로 추상화할지 먼저 결정하세요.
이 선택은 이후
kubeadm init의--control-plane-endpoint및 인증서 SAN에 영향을 미치므로 설치를 시작하기 전에 결정해야 합니다.
방식 장점 단점 FQDN ( k8s-api.internal) ← 권장VIP 변경 시 /etc/hosts만 수정, 인증서 재발급 불필요/etc/hosts관리 필요IP 직접 사용 설정 단순 VIP 변경 시 인증서 재발급 필수 FQDN 방식을 선택하면 5-A-1에서 바로
/etc/hosts등록을 먼저 수행합니다. IP 직접 사용 방식이면 5-A-1을 건너뛰고 5-A-2부터 시작합니다.
옵션 A: VIP 방식 (표준, 권장)
Master 3대와 가상 IP(VIP) 환경을 가정합니다. VIP를 K8s API Server(6443) 앞단에 두어 마스터 노드 장애 시에도 API 통신이 끊기지 않게 합니다.
5-A-1. (FQDN 방식 선택 시) FQDN 등록 (전체 노드)
VIP IP를 직접 사용하는 대신 내부 FQDN(k8s-api.internal)으로 추상화합니다.
나중에 VIP가 변경되어도 인증서 재발급 없이 DNS 서버 혹은 /etc/hosts만 수정하면 됩니다.
내부 DNS 서버가 있다면 관리자에게 요청하여 아래 내용을 추가합니다.
- 레코드 이름: k8s-api.internal
- IP 주소: VIP
만약 DNS 서버가 없다면 hosts 파일에 등록합니다.
전체 노드(마스터 + 워커)에서 실행합니다.
HAProxy의
bind는 안정성을 위해 VIP IP(<VIP>:6443)를 그대로 사용합니다. FQDN은 kubeconfig의 server 주소와 인증서 SAN에만 적용됩니다.
5-A-2. 커널 파라미터 설정 (전체 마스터 노드)
VIP가 자신의 인터페이스에 없어도 바인딩할 수 있도록 설정합니다.
cat <<EOF | sudo tee /etc/sysctl.d/haproxy.conf
net.ipv4.ip_nonlocal_bind = 1
EOF
sudo sysctl --system
5-A-3. HAProxy 설정 (전체 마스터 노드)
sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.bak
cat <<EOF | sudo tee /etc/haproxy/haproxy.cfg
global
log 127.0.0.1 local2
maxconn 4000
daemon
defaults
mode tcp
log global
option tcplog
timeout connect 10s
timeout client 1m
timeout server 1m
# Kubernetes API Server LB
frontend k8s-api
bind <VIP>:6443 # TODO 실제 VIP로 변경 필요
mode tcp
option tcplog
default_backend k8s-masters
# TODO 실제 MASTER_IP, HOSTNAME 변경 필요
backend k8s-masters
mode tcp
balance roundrobin
option tcp-check
server <MASTER1_HOSTNAME> <MASTER1_IP>:6443 check fall 3 rise 2
server <MASTER2_HOSTNAME> <MASTER2_IP>:6443 check fall 3 rise 2
server <MASTER3_HOSTNAME> <MASTER3_IP>:6443 check fall 3 rise 2
EOF
5-A-4. Keepalived 설정 (전체 마스터 노드)
각 마스터 노드별로 state, priority, interface 값을 다르게 설정합니다.
| 노드 | state | priority |
|---|---|---|
| Master-1 | MASTER |
101 |
| Master-2 | BACKUP |
100 |
| Master-3 | BACKUP |
99 |
# Master-1 기준 예시 (Master-2, 3은 state/priority 값 수정)
cat <<EOF | sudo tee /etc/keepalived/keepalived.conf
global_defs {
router_id LVS_DEVEL
}
vrrp_script check_haproxy {
script "/usr/bin/killall -0 haproxy"
interval 3
weight -2
fall 10
rise 2
}
vrrp_instance VI_1 {
state MASTER # TODO Master-2, 3은 BACKUP
interface eth0 # TODO 본인 네트워크 인터페이스명으로 변경 필수
virtual_router_id 51
priority 101 # TODO M1: 101, M2: 100, M3: 99
advert_int 1
authentication {
auth_type PASS
auth_pass 42 # 모든 노드 동일하게 설정
}
virtual_ipaddress {
<VIP> # TODO VIP 주소
}
track_script {
check_haproxy
}
}
EOF
5-A-5. 서비스 시작 및 VIP 확인
sudo systemctl enable --now haproxy
sudo systemctl enable --now keepalived
# VIP 활성화 확인 (Master-1에서 VIP가 보여야 함)
ip addr show eth0 | grep VIP
옵션 B: Localhost LB 방식 (VIP 사용 불가 환경)
VIP를 사용할 수 없는 환경에서 각 노드에 HAProxy를 띄워 Loopback(127.0.0.1:8443)으로 통신합니다.
전체 마스터 및 워커 노드에 동일하게 설정합니다.
sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.bak
cat <<EOF | sudo tee /etc/haproxy/haproxy.cfg
global
maxconn 4000
daemon
defaults
mode tcp
timeout connect 10s
timeout client 1m
timeout server 1m
frontend k8s-api-proxy
bind 127.0.0.1:8443
default_backend k8s-masters
backend k8s-masters
balance roundrobin
option tcp-check
server <MASTER1_HOSTNAME> <MASTER1_IP>:6443 check
server <MASTER2_HOSTNAME> <MASTER2_IP>:6443 check
server <MASTER3_HOSTNAME> <MASTER3_IP>:6443 check
EOF
sudo systemctl enable --now haproxy
Phase 6: kubeadm init (Master-1)
옵션 A: HA(3중화) 구성 (VIP 사용)
--apiserver-cert-extra-sans에 VIP와 전체 마스터 IP를 포함해야 RHEL/Rocky 9계열의 엄격한 SAN 검증을 통과할 수 있습니다.
pod-network-cidr 와 service-cidr 는 현재 기본값으로 되어있습니다. 환경에 따라 해당 네트워크 대역 사용이 불가하다면 변경해야 합니다.
FQDN을 사용하는 경우(5-A-1 적용 시) VIP 대신 k8s-api.internal로 대체합니다.
HAProxy 포트 충돌 주의 HAProxy가 VIP:6443을 점유하고 있으면
kubeadm init이 같은 포트를 열려다 실패합니다.kubeadm init전에 HAProxy를 잠시 중지하고, API 서버 bind-address 설정 후 다시 시작합니다.
# 1. HAProxy 일시 중지
sudo systemctl stop haproxy
# 2. kubeadm init — VIP IP 직접 사용 시
sudo kubeadm init \
--control-plane-endpoint "<VIP>:6443" \
--upload-certs \
--apiserver-cert-extra-sans="<VIP>,<MASTER1_IP>,<MASTER2_IP>,<MASTER3_IP>,127.0.0.1" \
--pod-network-cidr=192.168.0.0/16 \
--service-cidr=10.96.0.0/12 \
--kubernetes-version v1.30.0
# 2. kubeadm init — FQDN 사용 시 (권장)
sudo kubeadm init \
--control-plane-endpoint "k8s-api.internal:6443" \
--upload-certs \
--apiserver-cert-extra-sans="k8s-api.internal,<VIP>,<MASTER1_IP>,<MASTER2_IP>,<MASTER3_IP>,127.0.0.1" \
--pod-network-cidr=192.168.0.0/16 \
--service-cidr=10.96.0.0/12 \
--kubernetes-version v1.30.0
# 3. API 서버 bind-address를 Master-1의 실제 IP로 수정
# (설정하지 않으면 API 서버가 0.0.0.0으로 바인딩되어 HAProxy와 다시 충돌)
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
# spec.containers[].command 섹션에 추가:
# - --bind-address=<MASTER1_IP>
# 4. API 서버가 노드 IP로 재기동된 것을 확인 후 HAProxy 시작
sudo crictl pods --namespace kube-system | grep apiserver # Running 확인
sudo systemctl start haproxy
# 5. kubeconfig 설정
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
옵션 B: 단일 구성
sudo kubeadm init \
--control-plane-endpoint "<MASTER_IP>:6443" \
--pod-network-cidr=192.168.0.0/16 \
--service-cidr=10.96.0.0/12 \
--kubernetes-version v1.30.0
# kubeconfig 설정
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Phase 6-1: 추가 마스터 노드 조인 (Master-2, 3 — HA 구성 시에만)
Master-1 초기화 출력에서 --control-plane 조인 명령을 복사하여 실행합니다.
Master-2, 3에도 HAProxy가 VIP:6443을 점유하고 있으므로 조인 전에 중지합니다.
# 1. HAProxy 일시 중지
sudo systemctl stop haproxy
# 2. 컨트롤 플레인 조인
sudo kubeadm join <VIP>:6443 --token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH> \
--control-plane --certificate-key <CERT_KEY>
# 3. bind-address를 해당 노드 실제 IP로 수정
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
# Master-2: - --bind-address=<MASTER2_IP>
# Master-3: - --bind-address=<MASTER3_IP>
# 4. API 서버 재기동 확인 후 HAProxy 시작
sudo crictl pods --namespace kube-system | grep apiserver # Running 확인
sudo systemctl start haproxy
kubeconfig도 각 노드에 설정합니다.
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Phase 7: Calico CNI 설치 (Master-1)
--pod-network-cidr을 기본값(192.168.0.0/16)에서 변경한 경우,calico.yaml에서CALICO_IPV4POOL_CIDR항목을 찾아 주석을 해제하고 값을 수정한 뒤 적용합니다.--service-cidr는 Calico가 관리하지 않으므로 변경 불필요합니다.해당 라인으로 이동해 주석(
#)을 제거하고 값을 수정합니다.
kubectl apply -f k8s/utils/calico.yaml
# Calico Pod가 Running이 될 때까지 대기
kubectl get pods -n kube-system -w
Phase 8: Helm 설치 (컨트롤 플레인 노드)
cd k8s/binaries
tar -xzvf helm-v3.14.0-linux-amd64.tar.gz
sudo mv linux-amd64/helm /usr/local/bin/helm
helm version
Phase 9: 워커 노드 조인
Master-1의 kubeadm init 출력에서 워커 조인 명령을 복사하여 실행합니다.
sudo kubeadm join <CONTROL_PLANE_ENDPOINT>:6443 \
--token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH>
Phase 10: 설치 확인
모든 노드가 Ready 상태이고 kube-system Pod들이 Running 이면 설치 완료입니다.
# HA 구성 시 CIDR 설정 확인
kubectl get pod -n kube-system -l component=kube-controller-manager -o yaml | grep cluster
# Calico IP Pool 확인
kubectl get ippools -o yaml
재설치 시 초기화
오류 발생 등으로 재설치가 필요한 경우 아래 순서로 초기화합니다.
# 1. kubeadm reset
sudo kubeadm reset -f
# 2. CNI 및 kube 설정 파일 삭제
sudo rm -rf /etc/cni/net.d
rm -rf $HOME/.kube
sudo rm -rf /root/.kube
# 3. etcd, kubelet 데이터 삭제
sudo rm -rf /var/lib/etcd
sudo rm -rf /var/lib/kubelet
# 4. iptables 규칙 초기화
sudo iptables -F && sudo iptables -t nat -F && sudo iptables -t mangle -F && sudo iptables -X
# 5. containerd 재시작
sudo systemctl restart containerd
VIP 변경 시 조치
운영 중 VIP 대역이 변경되거나 새로운 IP를 할당받아야 하는 경우의 절차입니다.
케이스 0: 운영 중인 클러스터를 IP → FQDN으로 전환
이미 VIP IP로 초기 구성한 클러스터에 FQDN을 사후 적용하는 절차입니다. 이후 VIP가 변경되면 케이스 A 절차만으로 처리할 수 있게 됩니다.
1단계: 모든 노드에 FQDN 등록 (마스터 + 워커)
DNS 서버가 운영 중이라면 DNS 서버에 등록합니다.
2단계: API 서버 인증서에 FQDN SAN 추가 (전체 마스터 노드)
# 기존 인증서 백업
sudo cp /etc/kubernetes/pki/apiserver.crt ~/apiserver.crt.bak
sudo cp /etc/kubernetes/pki/apiserver.key ~/apiserver.key.bak
# 삭제 후 FQDN 포함하여 재발급
sudo rm /etc/kubernetes/pki/apiserver.crt /etc/kubernetes/pki/apiserver.key
sudo kubeadm init phase certs apiserver \
--control-plane-endpoint "k8s-api.internal:6443" \
--apiserver-cert-extra-sans="k8s-api.internal,<OLD_VIP>,<MASTER1_IP>,<MASTER2_IP>,<MASTER3_IP>,127.0.0.1"
# FQDN이 SAN에 포함되었는지 확인
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -A1 "Subject Alternative"
3단계: kube-apiserver 재시작 (전체 마스터 노드)
sudo mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/
sleep 10
sudo mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/
watch sudo crictl pods --namespace kube-system
4단계: kubeconfig 및 kubelet.conf 업데이트 (전체 마스터 노드)
for conf in /etc/kubernetes/admin.conf \
/etc/kubernetes/controller-manager.conf \
/etc/kubernetes/scheduler.conf \
/etc/kubernetes/kubelet.conf; do
sudo sed -i "s|https://<OLD_VIP>:6443|https://k8s-api.internal:6443|g" "$conf"
done
sudo systemctl restart kubelet
# 현재 사용자 kubeconfig 갱신
cp /etc/kubernetes/admin.conf ~/.kube/config
5단계: 워커 노드 kubelet.conf 업데이트 (전체 워커 노드)
sudo sed -i 's|https://<OLD_VIP>:6443|https://k8s-api.internal:6443|g' /etc/kubernetes/kubelet.conf
sudo systemctl restart kubelet
6단계: 클러스터 내부 ConfigMap 갱신 (Master-1에서 1회 실행)
로컬 파일 수정과 별개로, 클러스터 내부 etcd에 저장된 엔드포인트도 갱신해야 합니다.
이를 누락하면 kube-proxy 파드가 재시작될 때 구 VIP로 접속을 시도하여 CrashLoopBackOff가 발생할 수 있습니다.
# kube-proxy ConfigMap 갱신
kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed 's|<OLD_VIP>:6443|k8s-api.internal:6443|g' | \
kubectl apply -f -
# 변경 사항 적용을 위해 kube-proxy 파드 롤아웃
kubectl rollout restart daemonset kube-proxy -n kube-system
# (권장) kubeadm-config ConfigMap 갱신 — 추후 kubeadm upgrade 시 필요
kubectl get configmap kubeadm-config -n kube-system -o yaml | \
sed 's|<OLD_VIP>:6443|k8s-api.internal:6443|g' | \
kubectl apply -f -
7단계: 확인
Kubernetes control plane 주소가 https://k8s-api.internal:6443으로 표시되면 완료입니다.
케이스 A: FQDN 방식으로 초기 구성한 경우 (권장 구성)
FQDN(k8s-api.internal)이 인증서 SAN에 포함되어 있으므로, 인증서 재발급 없이 아래 순서만 따르면 됩니다.
1단계: 모든 노드의 /etc/hosts 업데이트 (마스터 + 워커)
DNS 서버가 운영 중이라면 DNS 서버에 등록합니다.
# <OLD_VIP> → <NEW_VIP> 로 변경
sudo sed -i 's/<OLD_VIP> k8s-api.internal/<NEW_VIP> k8s-api.internal/' /etc/hosts
# 확인
grep k8s-api.internal /etc/hosts
2단계: Keepalived VIP 변경 (전체 마스터 노드)
sudo sed -i 's/<OLD_VIP>/<NEW_VIP>/' /etc/keepalived/keepalived.conf
sudo systemctl restart keepalived
# 새 VIP 활성화 확인 (Master-1)
ip addr show eth0 | grep <NEW_VIP>
3단계: HAProxy bind IP 변경 (전체 마스터 노드)
sudo sed -i 's/<OLD_VIP>:6443/<NEW_VIP>:6443/' /etc/haproxy/haproxy.cfg
sudo systemctl restart haproxy
backend k8s-masters의server항목(마스터 노드 IP)은 변경하지 않습니다.
4단계: API 서버 재시작 확인
케이스 B: VIP IP를 직접 사용하여 초기 구성한 경우
인증서 SAN에 기존 VIP IP가 고정되어 있으므로, 인증서 재발급이 필수입니다. 전체 마스터 노드에서 순서대로 진행합니다.
1단계: Keepalived / HAProxy VIP 변경 (전체 마스터 노드)
sudo sed -i 's/<OLD_VIP>/<NEW_VIP>/' /etc/keepalived/keepalived.conf
sudo systemctl restart keepalived
sudo sed -i 's/<OLD_VIP>:6443/<NEW_VIP>:6443/' /etc/haproxy/haproxy.cfg
sudo systemctl restart haproxy
2단계: API 서버 인증서 재발급 (전체 마스터 노드)
# 기존 인증서 백업
sudo cp /etc/kubernetes/pki/apiserver.crt ~/apiserver.crt.bak
sudo cp /etc/kubernetes/pki/apiserver.key ~/apiserver.key.bak
# 삭제 후 재발급 (새 VIP 포함)
sudo rm /etc/kubernetes/pki/apiserver.crt /etc/kubernetes/pki/apiserver.key
sudo kubeadm init phase certs apiserver \
--control-plane-endpoint "<NEW_VIP>:6443" \
--apiserver-cert-extra-sans="<NEW_VIP>,<MASTER1_IP>,<MASTER2_IP>,<MASTER3_IP>,127.0.0.1"
3단계: kube-apiserver 재시작 (전체 마스터 노드)
static pod는 manifest를 잠시 제거했다가 복원하면 자동 재시작됩니다.
sudo mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/
sleep 10
sudo mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/
# Pod가 다시 Running 상태가 될 때까지 대기
watch sudo crictl pods --namespace kube-system
4단계: kubeconfig 및 kubelet.conf 업데이트 (전체 마스터 노드)
# 마스터 노드 kubeconfig + kubelet.conf 업데이트
for conf in /etc/kubernetes/admin.conf \
/etc/kubernetes/controller-manager.conf \
/etc/kubernetes/scheduler.conf \
/etc/kubernetes/kubelet.conf; do
sudo sed -i "s|https://<OLD_VIP>:6443|https://<NEW_VIP>:6443|g" "$conf"
done
sudo systemctl restart kubelet
# 현재 사용자 kubeconfig 갱신
cp /etc/kubernetes/admin.conf ~/.kube/config
5단계: 워커 노드 kubelet.conf 업데이트 (전체 워커 노드)
sudo sed -i 's|https://<OLD_VIP>:6443|https://<NEW_VIP>:6443|g' /etc/kubernetes/kubelet.conf
sudo systemctl restart kubelet
6단계: 클러스터 내부 ConfigMap 갱신 (Master-1에서 1회 실행)
로컬 파일 수정과 별개로, 클러스터 내부 etcd에 저장된 엔드포인트도 갱신해야 합니다.
이를 누락하면 kube-proxy 파드가 재시작될 때 구 VIP로 접속을 시도하여 CrashLoopBackOff가 발생할 수 있습니다.
# kube-proxy ConfigMap 갱신
kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed 's|<OLD_VIP>:6443|<NEW_VIP>:6443|g' | \
kubectl apply -f -
# 변경 사항 적용을 위해 kube-proxy 파드 롤아웃
kubectl rollout restart daemonset kube-proxy -n kube-system
# (권장) kubeadm-config ConfigMap 갱신 — 추후 kubeadm upgrade 시 필요
kubectl get configmap kubeadm-config -n kube-system -o yaml | \
sed 's|<OLD_VIP>:6443|<NEW_VIP>:6443|g' | \
kubectl apply -f -
7단계: 정상 동작 확인
모든 노드가 Ready 상태이고 kube-system Pod가 Running이면 완료입니다.