컨트롤 플레인이 여럿이면 API 서버 앞에 단일 진입점이 필요하다. keepalived 가 VIP 를 들고, HAProxy 가 그 VIP 로 들어온 6443 을 각 컨트롤 플레인의 6443 으로 넘긴다.
# /etc/keepalived/keepalived.conf
vrrp_instance VI_1 {
state MASTER
interface enX0
virtual_router_id 51
priority 101
advert_int 1
authentication {
auth_type PASS
auth_pass ${VRRP_PASSWORD}
}
virtual_ipaddress {
192.168.254.1
}
}
# /etc/haproxy/haproxy.cfg
global
log /dev/log local0
maxconn 4000
daemon
defaults
log global
mode tcp
option tcplog
timeout connect 10s
timeout client 1m
timeout server 1m
frontend kubernetes
bind 192.168.254.1:6443
default_backend kubernetes-cp
backend kubernetes-cp
balance roundrobin
option tcp-check
server cp1 192.168.11.1:6443 check
server cp2 192.168.11.2:6443 check
server cp3 192.168.11.3:6443 check
프런트엔드와 백엔드 포트는 같은 6443 을 쓴다. API 서버가 그 포트에서만 듣기 때문이다. TLS 는 API 서버가 끝내야 하므로 반드시 mode tcp 다. mode http 로 두면 HAProxy 가 내용을 해석하려 들어 연결이 깨진다.
VIP 는 진입점 전용으로 별도 주소를 잡는다. 컨트롤 플레인 중 한 대의 실제 주소를 VIP 로 겸하면, 그 노드가 죽었을 때 VIP 가 다른 노드로 넘어가면서 주소가 겹쳐 꼬인다.
[ALERT] Binding [/etc/haproxy/haproxy.cfg:23] for frontend kubernetes:
cannot bind socket (Cannot assign requested address) [192.168.254.1:6443]
VIP 를 아직 들고 있지 않은 대기 노드에서 HAProxy 가 그 주소에 바인딩하려다 실패한 것이다. VRRP 는 VIP 를 한 노드에만 붙이므로, 나머지 노드에는 그 주소가 없다.
두 방법 중 하나를 쓴다. 없는 주소에도 바인딩할 수 있게 커널에 허용하거나,
echo 'net.ipv4.ip_nonlocal_bind = 1' > /etc/sysctl.d/99-haproxy.conf
sysctl --system
모든 주소에 바인딩한다.
frontend kubernetes
bind *:6443
앞쪽이 깔끔하다. 뒤쪽은 노드의 실제 주소로도 접근이 열리므로 방화벽을 함께 본다.
[ALERT] Starting frontend http: cannot bind socket (Address already in use) [0.0.0.0:80]
이미 다른 프로세스가 그 포트를 쓰고 있다. 배포판 기본 haproxy.cfg 에 남아 있는 예시 프런트엔드가 원인인 경우가 많다. 쓰지 않는 섹션은 지운다.
같은 호스트에서 웹 서버와 HAProxy 를 모두 80 으로 띄워야 한다면 자리를 나눈다. 웹 서버는 노드 실제 주소에, HAProxy 는 VIP 에 바인딩하거나, 웹 서버를 8080 으로 옮기고 HAProxy 가 80 을 받아 넘긴다.
ss -lntp | grep ':80'
[control-plane-check] kube-controller-manager is not healthy after 4m0s
A control plane component may have crashed or exited when started by the container runtime.
정적 파드가 뜨지 못한 것이다. 런타임으로 직접 들여다본다.
crictl --runtime-endpoint unix:///run/containerd/containerd.sock ps -a | grep kube | grep -v pause
crictl logs <컨테이너 ID>
journalctl -u kubelet -xe | tail -50
자주 걸리는 원인은 셋이다. containerd 의 cgroup 드라이버가 systemd 가 아니거나, 필요한 이미지가 없거나(폐쇄망), 앞선 실패의 잔재가 남은 경우다.
grep SystemdCgroup /etc/containerd/config.toml
kubeadm config images pull
잔재 정리는 되돌릴 수 없으므로 정말 새로 시작할 때만 한다.
kubeadm reset -f
rm -rf /etc/kubernetes/pki /var/lib/etcd
systemctl restart containerd kubelet
crictl 이 컨테이너를 찾지 못한다(NotFound)면 ID 를 잘못 넣었거나 이미 정리된 것이다. crictl ps -a 로 종료된 것까지 보고 실제 ID 를 쓴다. 엔드포인트 자동 탐색 경고가 거슬리면 설정 파일을 만들어 둔다.
# /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
사내 CA 를 쓰거나 클러스터를 다시 세우면서 기존 CA 를 유지해야 한다면, kubeadm init 전에 파일을 제자리에 둔다. 있으면 kubeadm 이 새로 만들지 않고 그것으로 하위 인증서를 서명한다.
mkdir -p /etc/kubernetes/pki
cp ca.crt ca.key /etc/kubernetes/pki/
chmod 600 /etc/kubernetes/pki/ca.key
front-proxy-ca 와 etcd/ca 도 유지하려면 같은 자리에 함께 둔다. 인증서 유효 기간을 길게 잡으려는 시도는 권하지 않는다. 기간이 길수록 유출 시 피해가 커지고, 정기 갱신 절차를 두는 편이 안전하다.
ip addr show enX0 | grep 192.168.254.1 # VIP 를 든 노드에서만 보인다
curl -k https://192.168.254.1:6443/healthz
systemctl stop keepalived # 다른 노드로 넘어가는지 시험