kube-apiserver 복구 — Calico 순환 의존

Kubernetes5분조회

쿠버네티스를 직접 구축해보면서 그 위에 이런저런 앱도 올려 테스트해볼까 해서, 클러스터를 하나 구축했습니다. AWS EC2 두 대에 Kubespray 로 올렸고, 노드 2개는 마스터 1대와 워커 1대(worker1)로 나눴습니다. Kubespray 는 Ansible 로 서버에 쿠버네티스를 깔아주는 도구입니다.

설치는 끝났는데 거의 모든 파드가 비정상이었습니다. CoreDNS, Calico(파드 간 네트워킹을 맡는 CNI) 같은 시스템 파드부터 FluxCD, Ingress Nginx 까지 ContainerCreating·Pending·Terminating·Unknown 이 뒤섞여 있었습니다.

시작점은 워커 노드 하나였습니다.

kubelet의 잘못된 API 서버 주소 — localhost

worker1NotReady 였습니다. 노드에 들어가 kubelet 로그를 봤습니다.

$ sudo journalctl -u kubelet -f

Unable to register node with API server
Post "https://localhost:6443/api/v1/nodes":
  dial tcp 127.0.0.1:6443: connect: connection refused

워커 노드에는 API 서버가 없으니 localhost:6443 에는 아무것도 안 떠 있고, 그래서 연결이 거부됩니다. Kubespray 설치 과정에서 kubeconfig 의 server 주소가 잘못 잡힌 상태였습니다.

/etc/kubernetes/kubelet.confclusters 섹션에서 server 를 마스터 노드의 실제 내부 IP 로 바꿨습니다.

# 전
server: https://localhost:6443
# 후
server: https://<master-node-ip>:6443
sudo systemctl daemon-reload && sudo systemctl restart kubelet

노드는 Ready 로 넘어왔는데, 여기서 끝날 줄 알았더니 아니었습니다.

Calico ↔ API 서버 순환 의존

calico-node 파드들이 Init:ErrorInit:CrashLoopBackOff 를 오갔습니다.

$ kubectl logs calico-node-xxxxx -c install-cni

Unable to create token for CNI kubeconfig
error=Post "https://<service-cluster-ip>:443/...":
  dial tcp <service-cluster-ip>:443: i/o timeout

여기에 순환 구조가 있습니다.

Calico (install-cni)
   │  API 서버에 접근해야 시작됨

클러스터 IP 경로 (= API 서버로 가는 길)
   │  이 경로가 뚫리려면 파드 네트워크가 있어야 함

파드 네트워크
   │  그런데 이걸 만드는 게…
   └────────▶ 다시 Calico  (한 바퀴 도는 순환)

Calico 가 API 서버에 닿으려면 파드 네트워크가 필요한데, 그 파드 네트워크를 만드는 게 Calico 입니다. 서로가 서로를 기다리는 셈입니다.

install-cni 는 API 서버에 접근해야 하는데, 그 주소로 Kubernetes 서비스의 클러스터 IP 를 씁니다. 클러스터 IP 로 가는 경로는 파드 네트워크가 있어야 열립니다. 그 파드 네트워크를 만드는 게 Calico 입니다.

파드 네트워크가 없는 상태에서 그 파드 네트워크를 타고 API 서버에 가려니 타임아웃이 납니다.

클러스터 IP 를 거치지 않고 API 서버로 직접 가게 했습니다. 환경변수 두 개면 됩니다.

env:
  - name: KUBERNETES_SERVICE_HOST
    value: "<master-node-ip>"
  - name: KUBERNETES_SERVICE_PORT
    value: "6443"

install-cni init 컨테이너와 calico-node 컨테이너 양쪽에 넣어야 합니다.

kubectl apply — immutable field 충돌

DaemonSet 을 YAML 로 뽑아 고치고 다시 적용하려는데 거부당합니다.

kubectl get daemonset calico-node -n kube-system -o yaml > calico-node-fix.yaml

kubectl get -o yaml 은 서버가 관리하는 필드까지 전부 뱉습니다. 이걸 그대로 apply 하면 바꿀 수 없는 값을 바꾸려 한다고 막힙니다.

지워야 하는 것들입니다.

metadata.creationTimestamp
metadata.generation
metadata.resourceVersion
metadata.uid
status:            (섹션 전체)

지운 뒤 적용합니다.

kubectl apply -f calico-node-fix.yaml --force

Calico 파드가 전부 1/1 Running 이 됐습니다.

CoreDNS — 루프와 API 접근 두 문제

Calico 를 고치자 CoreDNS 가 다른 에러를 냈습니다.

[FATAL] plugin/loop: Loop detected

Corefileforward . /etc/resolv.conf 가 원인입니다. 노드의 /etc/resolv.conf 가 클러스터 DNS 를 가리키고 있으면, CoreDNS 가 자기한테 쿼리를 되던지는 루프가 됩니다.

/etc/resolv.conf 를 따라가지 말고 외부 DNS 를 직접 적습니다.

# 전
forward . /etc/resolv.conf
# 후
forward . 8.8.8.8 8.8.4.4

그러자 아까 Calico 에서 본 것과 같은 에러가 나왔습니다.

failed to list *v1.Namespace: ... dial tcp <service-cluster-ip>:443: i/o timeout

CoreDNS 도 inClusterConfig 로 클러스터 IP 를 잡아 API 서버에 붙습니다. 같은 환경변수를 Deployment 에 넣어 직접 연결로 바꿨습니다.

Ingress·FluxCD도 같은 원인

ingress-nginx 컨트롤러 로그입니다.

Creating API client" host="https://<service-cluster-ip>:443"
connect: connection refused

FluxCD 컨트롤러들도 CrashLoopBackOff 였습니다. 둘 다 API 서버에 붙어야 동작하는 컴포넌트고, 붙는 방식이 같습니다. KUBERNETES_SERVICE_HOSTKUBERNETES_SERVICE_PORT 를 각 Deployment 에 넣자 올라왔습니다.

증상은 여럿, 원인은 API 서버로 가는 경로 하나

증상은 “앱이 전부 죽는다”였지만 원인은 하나였습니다. API 서버로 가는 경로.

파드가 API 서버에 붙는 기본 방식은 inClusterConfig 이고, 이건 Kubernetes 서비스의 클러스터 IP 를 씁니다. 그 IP 로 가는 경로가 깨지면 API 서버에 의존하는 컴포넌트가 전부 같이 죽습니다. CNI, DNS, Ingress 컨트롤러, GitOps 컨트롤러가 전부 여기 해당합니다.

증상이 컴포넌트마다 다르게 보여서 각각 다른 문제처럼 느껴지지만, 에러 메시지에 같은 IP 와 같은 포트가 반복해서 나오면 그게 신호입니다.

  1. 불러오는 중