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주석 처리)
설치 전 체크리스트
아래 항목은 단일 구성과 HA 구성 모두 설치 전에 확정합니다. 확정한 값은 /etc/hosts, 로드밸런서 설정, kubeadm init 인자에 동일하게 반영해야 합니다.
1. 구성 및 작업 범위
- [ ] 구성 유형 확정: 단일 컨트롤 플레인, HA 3중화, 물리 서버, 가상 서버 여부
- [ ] 노드별 역할, hostname, 관리 IP, Kubernetes 통신 NIC, gateway, DNS, NTP 서버 확정
- [ ] 작업 가능 시간과 금지 작업 확인: reboot, 네트워크 서비스 재시작, 방화벽 reload, Docker/containerd 재시작, OS 전체 업데이트
- [ ] 기존 서비스, 기존 컨테이너, 6443 포트 사용 서비스, NodePort 사용 서비스, HAProxy/keepalived 사용 여부 확인
2. 네트워크 대역 및 엔드포인트
- [ ] Pod CIDR 확정:
/20권장, 최소/22수준으로 환경 규모에 맞게 조정 - [ ] Service CIDR 확정:
/24가능, 여유가 필요하면/22권장 - [ ] Pod CIDR과 Service CIDR이 서버 실제 IP, 사내망, VPN, DB망, 관리망, 백업망, 스토리지망과 겹치지 않음 확인
- [ ] API endpoint 확정: 단일 노드 IP, VIP, FQDN 중 선택하고 인증서 SAN, DNS 또는
/etc/hosts반영 방식 확정 - [ ] VIP 사용 시 미사용 IP 여부, 로드밸런서 방식, keepalived VRRP protocol 112 허용 여부, HAProxy 6443 bind 충돌 여부 확인
3. 방화벽 및 포트
- [ ] Control Plane 포트 허용: 6443/TCP, 2379-2380/TCP, 10250/TCP, 10257/TCP, 10259/TCP
- [ ] Worker 포트 허용: 10250/TCP, 10256/TCP, NodePort 기본 범위 30000-32767/TCP,UDP
- [ ] CNI, Ingress, 스토리지, Harbor, DNS, NTP 등 부가 구성에서 필요한 추가 포트 확인
- [ ] 방화벽 flush, iptables/nftables 초기화, firewalld reload가 금지된 환경이면 허용 규칙 방식으로 사전 합의
4. 런타임, OS, 폐쇄망 자산
- [ ] Docker/containerd 설치 상태, 기존 컨테이너 사용 여부, containerd 또는 Docker 재시작 가능 여부 확인
- [ ] kubelet과 containerd의 cgroup driver를
systemd로 맞출 수 있는지 확인 - [ ] OS 버전, 커널 버전, Kubernetes 버전, containerd 버전, CNI 버전의 호환성 확인
- [ ] swap 비활성화 가능 여부, 시간 동기화 상태, 디스크 여유 공간, inode 여유, hostname/MAC address/product UUID 중복 없음 확인
- [ ] 폐쇄망 반입 자산 확인: RPM, 바이너리, 이미지, 매니페스트, Helm chart, Harbor 인증서와 레지스트리 접속 정보
5. 설치 진행 판단
- [ ] 미확정 항목이 없고, 운영 영향이 있는 조치의 승인 범위가 명확한지 확인
- [ ] 장애 발생 시 되돌릴 범위와 담당자 연락 경로 확인
- [ ] API Server 설정, etcd TLS, 인증서 취약점은
[Kubernetes 취약점 점검 및 보완 가이드](../../../security/005-vulnerability-check-remediation.md)기준으로 점검
디렉토리 구조
| 경로 | 설명 |
|---|---|
common/rpms/ |
공통 의존성 RPM (모든 노드) |
k8s/rpms/ |
kubeadm, kubelet, kubectl, containerd RPM |
k8s/binaries/ |
helm, cri-dockerd, nerdctl 등 바이너리 |
k8s/images/ |
kubeadm, Calico 등 컨테이너 이미지 .tar |
k8s/charts/ |
Helm 차트 |
k8s/utils/ |
calico.yaml 등 매니페스트 |
[Kubernetes 취약점 점검 및 보완 가이드](../../../security/005-vulnerability-check-remediation.md) |
Kubernetes kubeadm API Server 설정, etcd TLS, 인증서 취약점 점검 및 보완 절차 |
Phase -1: 인터넷 연결 호스트에서 에셋 다운로드
폐쇄망에 반입할 오프라인 패키지(RPM/DEB, 외부 바이너리, 컨테이너 이미지)를 다운로드하려면 인터넷에 연결된 호스트에서 다음 스크립트를 먼저 실행합니다.
- Rocky Linux/RHEL 환경에서 실행 시
k8s/rpms/및common/rpms/에 RPM이 다운로드됩니다. - Ubuntu/Debian 환경에서 실행 시
k8s/debs/및common/debs/에 DEB이 다운로드됩니다. - 단, 본 v1.30.0 설치 가이드의 본문 절차는 아직 Rocky Linux 9.6 기준입니다. Ubuntu 설치 절차는 별도 검증 후 반영해야 합니다.
- 감지된 실행 호스트의 OS 버전에 맞춰 패키지가 다운로드되므로, 실제 타겟 노드와 동일한 OS 메이저 버전을 갖춘 외부망 호스트에서 구동하는 것을 권장합니다.
다운로드가 완료되면 컴포넌트 디렉토리를 압축하여 폐쇄망용 Bastion 또는 마스터 노드로 이관합니다.
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 0.5: 시간 동기화 설정 (Chrony) — 전체 노드 필수
Kubernetes 클러스터는 노드 간 시간 동기화가 필수적입니다. 시간이 틀어지면 인증서 유효기간 오류, 클러스터 합류 실패 등이 발생하므로, 설치 전에 모든 노드의 시간을 동기화해야 합니다.
1. Chrony 설정 변경 (내부망 NTP 서버 지정)
에어갭(폐쇄망) 환경의 경우, 내부망에 구축된 NTP 서버 주소로 설정해야 합니다. /etc/chrony.conf 파일을 수정합니다.
2. 동기화 상태 확인
모든 노드에서 시스템 클럭 동기화 상태를 최종 검증합니다.
출력 결과 중System clock synchronized: yes 상태를 확인합니다. chronyc sources 또는 chronyc tracking을 실행하여 연동이 성공적으로 이루어졌는지 상세히 확인할 수 있습니다.
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. swap 비활성화 (영구 박멸 및 LVM 정리)
sudo swapoff -a
# /etc/fstab 내 3번째 필드가 swap인 라인을 안전하게 주석 처리 (.bak 백업 생성)
if [ -f /etc/fstab ]; then
sudo sed -i.bak -E '/^[[:space:]]*[^#[:space:]]+[[:space:]]+[^#[:space:]]+[[:space:]]+swap[[:space:]]+/ s/^/#/' /etc/fstab
fi
# systemd swap 유닛 목록 및 파일 검색 후 마스킹 (부팅 시 부활 방지)
# 1) systemctl list-units에 잡히는 swap 장치들 마스킹
for unit in $(sudo systemctl list-units --type=swap --all --no-legend --no-pager | grep -oE '\S+\.swap'); do
if [ -n "$unit" ]; then
sudo systemctl mask "$unit"
fi
done
# 2) systemctl list-unit-files에 잡히는 swap 파일들 마스킹
for unit_file in $(sudo systemctl list-unit-files --type=swap --no-legend --no-pager | grep -oE '\S+\.swap'); do
if [ -n "$unit_file" ]; then
if [ "$(sudo systemctl is-enabled "$unit_file" 2>/dev/null)" != "masked" ]; then
sudo systemctl mask "$unit_file"
fi
fi
done
# zram (Compressed swap) 비활성화 (사용 중일 경우)
if sudo systemctl is-active zram-generator >/dev/null 2>&1 || sudo systemctl list-unit-files | grep -q zram; then
sudo systemctl disable --now zram-generator 2>/dev/null || true
sudo systemctl disable --now zram-config 2>/dev/null || true
fi
sudo systemctl daemon-reload
# [Rocky/RHEL 전용] LVM 기반 Swap 볼륨 영구 비활성화 및 커널 파라미터(resume=) 정리
# LVM swap 볼륨이 남아 있으면 부팅 시 systemd-fstab-generator 가 감지하여 에러를 일으키거나 부팅 지연이 발생합니다.
# ⚠️ 주의: 반드시 데이터 백업 후 숙련된 관리자만 수행하십시오.
# 1) LVM Swap 볼륨 정보 확인
# sudo lvs (예: /dev/mapper/rl-swap 또는 /dev/rl/swap 존재 여부 확인)
# 2) LVM Swap 볼륨 비활성화 및 영구 삭제
# sudo lvremove /dev/rl/swap -y
# 3) GRUB 커널 명령행에서 'resume=/dev/mapper/rl-swap' 항목 정리
# - /etc/default/grub 파일을 열어 GRUB_CMDLINE_LINUX 줄에서 resume=... 부분을 제거합니다.
# - 예시: GRUB_CMDLINE_LINUX="crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M resume=/dev/mapper/rl-swap rd.lvm.lv=rl/root..."
# → 'resume=/dev/mapper/rl-swap'을 삭제
# 4) GRUB 설정 재빌드 (부팅 시 90초 타임아웃/행 걸림 원천 차단)
# - UEFI 부팅인 경우:
# sudo grub2-mkconfig -o /boot/efi/EFI/rocky/grub.cfg
# - BIOS(Legacy) 부팅인 경우:
# sudo grub2-mkconfig -o /boot/grub2/grub.cfg
# 5. 파일 디스크립터(FD) 및 시스템 Limits 상향
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-limits.conf
fs.file-max = 2097152
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 8192
EOF
sudo sysctl --system
cat <<EOF | sudo tee /etc/security/limits.d/99-kubernetes-limits.conf
* soft nofile 1048576
* hard nofile 1048576
* soft nproc 1048576
* hard nproc 1048576
root soft nofile 1048576
root hard nofile 1048576
EOF
sudo mkdir -p /etc/systemd/system/kubelet.service.d
cat <<EOF | sudo tee /etc/systemd/system/kubelet.service.d/limits.conf
[Service]
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
EOF
sudo systemctl daemon-reload
# 6. 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
# pause 이미지 버전 변경 (준비된 설치 파일의 pause 버전에 맞게 수정)
sudo sed -i 's/pause:3.10.1/pause:3.9/g' /etc/containerd/config.toml
# Harbor(또는 사설 레지스트리) 인증서 디렉토리 인식 경로 보장
cat <<'EOF' | sudo tee /tmp/ensure-containerd-registry-config-path.sh >/dev/null
#!/usr/bin/env bash
set -euo pipefail
config="/etc/containerd/config.toml"
plugin="io.containerd.grpc.v1.cri"
if containerd --version 2>/dev/null | grep -qE 'containerd .* 2\.'; then
plugin="io.containerd.cri.v1.images"
fi
mkdir -p /etc/containerd/certs.d
if grep -qE '^[[:space:]]*config_path[[:space:]]*=' "$config"; then
sed -i 's|^[[:space:]]*config_path[[:space:]]*=.*| config_path = "/etc/containerd/certs.d"|' "$config"
elif grep -qF "[plugins.\"${plugin}\".registry]" "$config" || grep -qF "[plugins.'${plugin}'.registry]" "$config"; then
awk -v plugin="$plugin" '
BEGIN { sq = sprintf("%c", 39); dq = "\""; done = 0 }
{
print
if (!done && ($0 == "[plugins." dq plugin dq ".registry]" || $0 == "[plugins." sq plugin sq ".registry]")) {
print " config_path = \"/etc/containerd/certs.d\""
done = 1
}
}
' "$config" > "${config}.tmp" && mv "${config}.tmp" "$config"
else
cat >> "$config" <<CONFIG
[plugins."${plugin}".registry]
config_path = "/etc/containerd/certs.d"
CONFIG
fi
EOF
sudo bash /tmp/ensure-containerd-registry-config-path.sh
sudo rm -f /tmp/ensure-containerd-registry-config-path.sh
# containerd 서비스 Limits 설정 (systemd override)
sudo mkdir -p /etc/systemd/system/containerd.service.d
cat <<EOF | sudo tee /etc/systemd/system/containerd.service.d/limits.conf
[Service]
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
EOF
sudo systemctl daemon-reload
# containerd 시작 및 활성화
sudo systemctl enable --now containerd
sudo systemctl status containerd
/etc/security/limits.d는 주로 로그인 세션에 적용됩니다.kubelet과containerd처럼 systemd가 직접 띄우는 서비스는 위 systemd override까지 적용해야 FD/프로세스 limits가 일관되게 반영됩니다.
(선택) Harbor TLS 인증서 등록
skip_verify는 사용하지 않습니다. Harbor 서버 인증서가 중간 CA를 포함한 체인으로
검증되어야 하므로, Harbor 접속 FQDN과 포트별 디렉토리에 전체 체인 인증서를 배치한 뒤
hosts.toml의 ca에 명시합니다.
# Harbor 레지스트리 주소와 체인 인증서 파일
HARBOR_HOST="harbor-product.strato.co.kr:8443"
HARBOR_SCHEME="https"
CHAIN_CERT="./strato.co.kr_chain.crt"
sudo mkdir -p "/etc/containerd/certs.d/${HARBOR_HOST}"
sudo cp "${CHAIN_CERT}" "/etc/containerd/certs.d/${HARBOR_HOST}/strato.co.kr_chain.crt"
sudo tee "/etc/containerd/certs.d/${HARBOR_HOST}/hosts.toml" <<EOF
server = "${HARBOR_SCHEME}://${HARBOR_HOST}"
[host."${HARBOR_SCHEME}://${HARBOR_HOST}"]
capabilities = ["pull", "resolve", "push"]
ca = ["strato.co.kr_chain.crt"]
EOF
sudo systemctl restart containerd
# TLS 검증을 유지한 상태로 이미지 pull 확인
sudo ctr -n k8s.io image pull \
harbor-product.strato.co.kr:8443/lgcns/strato-landing-frontend:10.0.0
(선택) 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 변경 시 인증서 재발급 필수
옵션 A: 물리 로드밸런서 (Physical LB) 방식 (권장)
기업용 L4/L7 스위치나 클라우드 제공업체의 로드밸런서를 사용하는 경우입니다.
5-B-1. 물리 LB 동작 모드 확인 (관리자 확인 필수)
물리 LB가 트래픽을 백엔드 노드로 전달할 때의 방식을 먼저 확인해야 합니다.
- DNAT (NAT) 방식: LB가 패킷의 목적지 IP를 VIP에서 노드 IP로 변환하여 전달합니다. 별도의 노드 설정이 필요 없습니다.
- DSR (Direct Server Return) 또는 Transparent 방식: LB가 목적지 IP를 VIP 그대로 둔 채 MAC 주소만 바꿔서 전달합니다. 이 경우 5-A-3 단계의 루프백 설정이 필수입니다.
5-B-2. FQDN 등록 및 Hairpin NAT 방지 (전체 노드)
마스터 노드들이 자기 자신을 호출할 때 외부 LB를 거쳐 나갔다 들어오는 현상(Hairpin)을 방지하기 위해 노드별로 /etc/hosts를 다르게 설정합니다.
- 마스터 노드 (1, 2, 3):
k8s-api.internal을 자기 자신의 실제 IP로 매핑합니다.
- 워커 노드 및 외부 클라이언트:
k8s-api.internal을 물리 LB VIP로 매핑합니다.
5-B-3. (DSR/Transparent 모드인 경우만) VIP 루프백 설정
물리 LB가 목적지 IP를 VIP로 유지하여 패킷을 던질 때, 커널이 이를 "내 것"으로 인식하게 하기 위해 루프백(lo)에 VIP를 할당하고 ARP 응답을 끕니다.
# 전체 마스터 노드 실행
# 1. 루프백에 VIP 할당
sudo ip addr add <물리_LB_VIP>/32 dev lo
# 2. ARP 응답 방지 (물리 LB와 IP 충돌 방지)
cat <<EOF | sudo tee /etc/sysctl.d/k8s-dsr.conf
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.lo.arp_ignore = 1
net.ipv4.conf.lo.arp_announce = 2
EOF
sudo sysctl --system
옵션 B: 소프트웨어 VIP 방식 (keepalived + haproxy)
Master 3대와 가상 IP(VIP) 환경을 가정합니다. VIP를 K8s API Server(6443) 앞단에 두어 마스터 노드 장애 시에도 API 통신이 끊기지 않게 합니다.
5-B-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-B-2. 커널 파라미터 설정 (전체 마스터 노드)
VIP가 자신의 인터페이스에 없어도 바인딩할 수 있도록 설정합니다.
cat <<EOF | sudo tee /etc/sysctl.d/haproxy.conf
net.ipv4.ip_nonlocal_bind = 1
EOF
sudo sysctl --system
5-B-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-B-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-B-5. 서비스 시작 및 VIP 확인
sudo systemctl enable --now haproxy
sudo systemctl enable --now keepalived
# VIP 활성화 확인 (Master-1에서 VIP가 보여야 함)
ip addr show eth0 | grep VIP
옵션 C: 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중화) — 물리 LB 방식 (Phase 5 옵션 A 에서 진행한 경우)
물리 LB가 외부에서 6443 포트를 중계하고 있으므로, 로컬 HAProxy 중지/시작이나 bind-address 수정 단계가 전혀 필요 없습니다.
# kubeadm init — FQDN 사용 시 (권장)
sudo kubeadm init \
--control-plane-endpoint "k8s-api.internal:6443" \
--upload-certs \
--apiserver-cert-extra-sans="k8s-api.internal,<물리_LB_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
# 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: HA(3중화) 구성 (VIP 사용)
--apiserver-cert-extra-sans에 VIP와 전체 마스터 IP를 포함해야 RHEL/Rocky 9계열의 엄격한 SAN 검증을 통과할 수 있습니다.
pod-network-cidr 와 service-cidr 는 현재 기본값으로 되어있습니다. 환경에 따라 해당 네트워크 대역 사용이 불가하다면 변경해야 합니다.
FQDN을 사용하는 경우(5-B-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
옵션 C: HA(3중화) — Localhost LB 방식 (Phase 5 옵션 C 에서 진행한 경우)
각 노드의 HAProxy 가 127.0.0.1:8443 만 점유하고, 백엔드는 마스터들의 6443 으로 포워딩합니다.
kube-apiserver 의 6443 과 포트가 겹치지 않으므로 HAProxy 중지·재시작 단계가 불필요하고,
bind-address 수정도 필요 없습니다(기본 0.0.0.0 사용).
인증서 SAN 에 반드시
127.0.0.1을 포함해야 모든 노드의 kubeconfig(https://127.0.0.1:8443)가 동일 인증서로 검증됩니다.
sudo kubeadm init \
--control-plane-endpoint "127.0.0.1:8443" \
--upload-certs \
--apiserver-cert-extra-sans="127.0.0.1,<MASTER1_IP>,<MASTER2_IP>,<MASTER3_IP>" \
--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
# HAProxy 백엔드 헬스체크 확인 — Master-1 만 UP 으로 보여야 정상
ss -tlnp | grep 8443
옵션 D: 단일 구성
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 조인 명령을 복사하여 실행합니다.
Phase 5 에서 선택한 LB 방식에 따라 절차가 달라집니다.
물리 LB 방식 (Phase 5 옵션 A)
물리 LB가 외부에서 트래픽을 중계하므로, HAProxy 중지나 bind-address 수정 단계가 전혀 필요 없습니다.
# 1. 컨트롤 플레인 조인 (endpoint = FQDN)
sudo kubeadm join k8s-api.internal:6443 --token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH> \
--control-plane --certificate-key <CERT_KEY>
# 2. kubeconfig
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
소프트웨어 VIP 방식 (Phase 5 옵션 B)
# 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
# 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
Localhost LB 방식 (Phase 5 옵션 C)
각 마스터의 HAProxy 가 127.0.0.1:8443 만 점유하므로 HAProxy 중지 / bind-address 수정 단계 모두 불필요합니다.
Master-1 의 kubeadm init 출력에 표시된 join 명령은 endpoint 가 127.0.0.1:8443 으로 이미 지정되어 있습니다.
# 1. 컨트롤 플레인 조인 (endpoint = 127.0.0.1:8443)
sudo kubeadm join 127.0.0.1:8443 --token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH> \
--control-plane --certificate-key <CERT_KEY>
# 2. kubeconfig
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# 3. (선택) HAProxy 백엔드 상태 — 모든 마스터가 합류하면 3대 모두 UP
sudo journalctl -u haproxy -n 20 --no-pager
Phase 7: Calico CNI 설치 (Master-1)
--pod-network-cidr을 기본값(192.168.0.0/16)에서 변경한 경우,calico.yaml에서CALICO_IPV4POOL_CIDR항목을 찾아 주석을 해제하고 값을 수정한 뒤 적용합니다.--service-cidr는 Calico가 관리하지 않으므로 변경 불필요합니다.해당 라인으로 이동해 주석(
#)을 제거하고 값을 수정합니다.
멀티 홈 IP 환경 대응 (BGP 피어링 오동작 방지)
노드에 네트워크 카드가 여러 개 장착되어 있거나 가상 인터페이스가 많아 IP가 여러 개 할당된 경우, Calico가 BGP 통신에 적합하지 않은 IP를 자동 감지하여 노드 간 Pod 통신이 단절될 수 있습니다. 이를 방지하기 위해 다음 조치가 적극 권장됩니다.
k8s/utils/calico.yaml 파일 내 calico-node DaemonSet 환경 변수 설정에 IP_AUTODETECTION_METHOD를 추가하고 주 대역을 필터링하도록 구성합니다.
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 8-1: nerdctl 설치 (선택, 전체 노드)
컨테이너 이미지 조회·조작이 필요한 노드에 설치합니다. containerd와 직접 통신하며 docker CLI와 유사한 UX를 제공합니다. Rootless 모드 사용 시 rootlesskit, slirp4netns 등 여러 의존 도구가 필요하므로 Full 패키지 준비를 권장합니다.
cd k8s/binaries
# 1. nerdctl 및 필수 의존성 배포
# (사전에 nerdctl-full 패키지를 압축 해제하여 bin/ 아래의 모든 파일을 준비해야 함)
sudo cp bin/* /usr/local/bin/
sudo chmod +x /usr/local/bin/nerdctl /usr/local/bin/rootlesskit /usr/local/bin/slirp4netns /usr/local/bin/containerd-rootless*
# 2. 설치 확인
nerdctl --version
Rocky/RHEL
sudoPATH 주의:nerdctl은/usr/local/bin/nerdctl에 설치되며, Rocky/RHEL 기본sudo secure_path에는/usr/local/bin이 없어sudo nerdctl호출 시 "command not found" 가 발생합니다. 셋 중 하나로 해결:
- 전체 경로:
sudo /usr/local/bin/nerdctl ...sudo visudo의Defaults secure_path끝에:/usr/local/bin추가- 심볼릭 링크:
sudo ln -sf /usr/local/bin/nerdctl /usr/bin/nerdctl
Rootless 모드 활성화 절차 (일반 사용자 계정)
바이너리 설치 후, sudo 없이 nerdctl을 사용하려면 아래 절차를 일반 사용자 계정으로 진행해야 합니다.
- 필수 패키지 확인:
shadow-utils패키지가 설치되어 있어야 합니다.
- 사용자 환경 설정 도구 실행: (root가 아닌 일반 계정으로 실행)
성공 시 ~/.config/systemd/user/containerd.service가 등록됩니다.
- 환경 변수 등록:
~/.bashrc또는~/.zshrc하단에 아래 내용을 추가하고 적용(source)합니다.
- Linger 설정 (권장): 로그아웃 후에도 서비스가 유지되도록 설정합니다.
주요 사용 예시:
# containerd k8s.io 네임스페이스 이미지 목록 확인
sudo nerdctl -n k8s.io images
# Harbor에서 이미지 pull (TLS 검증)
sudo nerdctl -n k8s.io pull harbor-product.strato.co.kr:8443/library/myapp:1.0.0
Phase 8-2: skopeo 설치 (선택, 전체 노드)
레지스트리 간 이미지 복사, 이미지 메타데이터 검사 등에 사용합니다. 데몬 없이 동작하며 Harbor push/pull 검증에 유용합니다.
cd k8s/rpms
# skopeo 및 의존성 RPM 일괄 설치
sudo dnf localinstall -y --disablerepo='*' \
skopeo-1.20.0-2.el9_7.x86_64.rpm \
containers-common-1-135.el9_7.x86_64.rpm \
device-mapper-libs-1.02.206-2.el9_7.2.x86_64.rpm \
gpgmepp-1.15.1-6.el9.x86_64.rpm \
libassuan-2.5.5-3.el9.x86_64.rpm
# 설치 확인
skopeo --version
주요 사용 예시:
# Harbor 이미지 목록 조회 (TLS 검증)
skopeo list-tags docker://harbor-product.strato.co.kr:8443/library/myapp
# 레지스트리 간 이미지 복사
skopeo copy \
docker://<SRC_REGISTRY>/myapp:1.0.0 \
docker://harbor-product.strato.co.kr:8443/library/myapp:1.0.0
# 이미지 메타데이터 확인
skopeo inspect docker://harbor-product.strato.co.kr:8443/library/myapp:1.0.0
Phase 9: 워커 노드 조인
Master-1의 kubeadm init 출력에서 워커 조인 명령을 복사하여 실행합니다.
Phase 5(또는 6)에서 선택한 구성 방식에 맞춰 아래 옵션을 선택하세요.
위 출력의 <ENDPOINT> 는 Phase 5 에서 선택한 LB 방식에 따라 달라집니다:
| Phase 5 옵션 | 워커가 사용할 endpoint | 사전 작업 |
|---|---|---|
| A (HA — 물리 LB) | k8s-api.internal:6443 |
워커 노드 /etc/hosts 에 FQDN 등록 필요 (Phase 5-A-2) |
| B (HA — 소프트웨어 VIP) | k8s-api.internal:6443 |
워커 노드 /etc/hosts 에 FQDN 등록 필요 (Phase 5-B-1) |
| C (HA — Localhost LB) | 127.0.0.1:8443 |
워커 노드에도 HAProxy 설치·설정 완료되어 있어야 함 (Phase 5 옵션 C) |
| D (단일 구성) | <MASTER_IP>:6443 |
추가 작업 불필요 |
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이면 완료입니다.
참고: 취약점 점검 및 보완
Kubernetes kubeadm API Server 설정, etcd TLS, 인증서 취약점 점검 및 보완 절차는 [Kubernetes 취약점 점검 및 보완 가이드](../../../security/005-vulnerability-check-remediation.md)를 참조하세요.