kubeadm 문제 해결

kubeadm 문제 해결 (Troubleshooting kubeadm)

어떤 프로그램과 마찬가지로 kubeadm을 설치하거나 실행할 때 오류가 발생할 수 있어요. 이 페이지는 몇 가지 일반적인 실패 시나리오를 나열하고 문제를 이해하고 고치는 데 도움이 되는 단계를 제공해요.

문제가 아래에 나열되어 있지 않다면 다음 단계를 따라요:

출처: 문서

본문

RBAC 누락으로 v1.18 노드를 v1.17 클러스터에 조인할 수 없음 (Not possible to join a v1.18 Node to a v1.17 cluster due to missing RBAC)

v1.18에서 kubeadm은 같은 이름의 노드가 이미 존재하면 클러스터에 노드가 조인되는 것을 방지하는 기능을 추가했어요. 이것은 bootstrap-token 사용자가 Node 객체를 GET할 수 있도록 RBAC를 추가해야 했어요.

그러나 이것은 v1.18의 kubeadm join이 kubeadm v1.17로 만든 클러스터에 조인할 수 없는 문제를 일으켜요.

이 문제를 우회하려면 두 가지 옵션이 있어요:

kubeadm v1.18을 사용해 컨트롤 플레인 노드에서 kubeadm init phase bootstrap-token을 실행해요. 이것이 나머지 bootstrap-token 권한도 활성화한다는 점을 주의해요.

또는

다음 RBAC를 kubectl apply -f ...로 수동으로 적용해요:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: kubeadm:get-nodes
rules:
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kubeadm:get-nodes
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: kubeadm:get-nodes
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: Group
    name: system:bootstrappers:kubeadm:default-node-token

설치 중 ebtables 또는 비슷한 실행 파일을 찾을 수 없음 (#ebtables-or-some-similar-executable-not-found-during-installation)

kubeadm init을 실행할 때 다음 경고가 보이면:

[preflight] WARNING: ebtables not found in system path
[preflight] WARNING: ethtool not found in system path

노드에 ebtables, ethtool 또는 비슷한 실행 파일이 빠져 있을 수 있어요. 다음 명령으로 설치할 수 있어요:

  • Ubuntu/Debian 사용자는 apt install ebtables ethtool을 실행해요.
  • CentOS/Fedora 사용자는 dnf install ebtables ethtool을 실행해요.

설치 중 kubeadm이 컨트롤 플레인을 기다리며 막힘 (#kubeadm-blocks-waiting-for-control-plane-during-installation)

kubeadm init이 다음 줄을 출력한 후 멈추는 것을 발견하면:

[apiclient] Created API client, waiting for the control plane to become ready

이것은 여러 문제로 인해 발생할 수 있어요. 가장 일반적인 것은:

  • 네트워크 연결 문제. 계속하기 전에 머신에 완전한 네트워크 연결이 있는지 확인해요.
  • 컨트롤 플레인 컨테이너가 crashloop하거나 멈춤. docker ps를 실행하고 docker logs로 각 컨테이너를 조사해 확인할 수 있어요. 다른 컨테이너 런타임에 대해서는 crictl로 Kubernetes 노드 디버깅을 참고해요.

관리되는 컨테이너를 제거할 때 kubeadm이 막힘 (#kubeadm-blocks-when-removing-managed-containers)

다음은 컨테이너 런타임이 중단되어 Kubernetes가 관리하는 컨테이너를 제거하지 않을 때 발생할 수 있어요:

sudo kubeadm reset
[preflight] Running pre-flight checks
[reset] Stopping the kubelet service
[reset] Unmounting mounted directories in "/var/lib/kubelet"
[reset] Removing kubernetes-managed containers
(block)

가능한 해결책은 컨테이너 런타임을 재시작한 다음 kubeadm reset을 다시 실행하는 것이에요. crictl을 사용해 컨테이너 런타임의 상태를 디버깅할 수도 있어요. crictl로 Kubernetes 노드 디버깅을 참고해요.

RunContainerError, CrashLoopBackOff 또는 Error 상태의 파드 (#pods-in-runcontainererror-crashloopbackoff-or-error-state)

kubeadm init 직후에는 이러한 상태의 파드가 없어야 해요.

  • kubeadm init 직후 이 상태 중 하나의 파드가 있으면 kubeadm 리포지토리에 이슈를 열어주세요. 네트워크 애드온을 배포할 때까지 coredns(또는 kube-dns)는 Pending 상태여야 해요.
  • 네트워크 애드온을 배포한 후 RunContainerError, CrashLoopBackOff 또는 Error 상태의 파드가 보이고 coredns(또는 kube-dns)에 아무 일도 일어나지 않으면, 설치한 Pod Network 애드온이 어쩐지 깨졌을 가능성이 매우 높아요. 그것에 더 많은 RBAC 권한을 부여하거나 더 새로운 버전을 사용해야 할 수도 있어요. Pod Network 프로바이더의 이슈 트래커에 이슈를 제출하고 그곳에서 이슈를 triage받아요.

coredns가 Pending 상태에 막힘 (#coredns-is-stuck-in-the-pending-state)

이것은 예상된 것이며 설계의 일부예요. kubeadm은 네트워크 프로바이더에 구애받지 않으므로 관리자가 선택한 파드 네트워크 애드온을 설치해야 해요. CoreDNS가 완전히 배포되기 전에 Pod Network를 설치해야 해요. 그래서 네트워크가 설정되기 전에는 Pending 상태인 것이에요.

HostPort 서비스가 동작하지 않음 (#hostport-services-do-not-work)

HostPort와 HostIP 기능은 Pod Network 프로바이더에 따라 사용 가능해요. HostPort와 HostIP 기능이 사용 가능한지 알아보려면 Pod Network 애드온의 작성자에게 문의해요.

Calico, Canal, Flannel CNI 프로바이더는 HostPort를 지원하는 것으로 검증됐어요. 자세한 내용은 CNI portmap 문서를 참고해요.

네트워크 프로바이더가 portmap CNI 플러그인을 지원하지 않으면 서비스의 NodePort 기능을 사용하거나 HostNetwork=true를 사용해야 할 수도 있어요.

파드가 Service IP로 접근되지 않음 (#pods-are-not-accessible-via-their-service-ip)

  • 많은 네트워크 애드온이 아직 파드가 Service IP로 스스로에 접근할 수 있게 하는 hairpin 모드를 활성화하지 않아요. 이것은 CNI(https://github.com/containernetworking/cni/issues/476)와 관련된 문제예요. hairpin 모드 지원의 최신 상태를 알아보려면 네트워크 애드온 프로바이더에 문의해요.
  • VirtualBox를 사용한다면(직접 또는 Vagrant 통해) hostname -i가 라우팅 가능한 IP 주소를 반환하는지 확인해야 해요. 기본적으로 첫 인터페이스는 라우팅 불가한 host-only 네트워크에 연결돼요. 우회 방법은 /etc/hosts를 수정하는 것이고, 예시는 이 Vagrantfile을 참고해요.

TLS 인증서 오류 (#tls-certificate-errors)

다음 오류는 가능한 인증서 불일치를 나타내요.

# kubectl get pods
Unable to connect to the server: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "kubernetes")
  • $HOME/.kube/config 파일에 유효한 인증서가 있는지 확인하고, 필요하면 인증서를 재생성해요. kubeconfig 파일의 인증서는 base64로 인코딩돼 있어요. base64 --decode 명령으로 인증서를 디코드하고 openssl x509 -text -noout로 인증서 정보를 볼 수 있어요.
  • KUBECONFIG 환경 변수를 해제하거나 기본 KUBECONFIG 위치로 설정해요.
  • 또 다른 우회 방법은 "admin" 사용자에 대한 기존 kubeconfig를 덮어쓰는 것이에요.

Kubelet 클라이언트 인증서 회전 실패 (#kubelet-client-cert)

기본적으로 kubeadm은 /etc/kubernetes/kubelet.conf에 지정된 /var/lib/kubelet/pki/kubelet-client-current.pem 심링크를 사용해 클라이언트 인증서의 자동 회전으로 kubelet을 구성해요. 이 회전 과정이 실패하면 kube-apiserver 로그에서 x509: certificate has expired or is not yet valid 같은 오류를 볼 수 있어요. 문제를 고치려면 다음 단계를 따라야 해요:

  • 실패한 노드에서 /etc/kubernetes/kubelet.conf/var/lib/kubelet/pki/kubelet-client*를 백업하고 삭제해요.
  • 클러스터에서 /etc/kubernetes/pki/ca.key가 있는 동작하는 컨트롤 플레인 노드에서 kubeadm kubeconfig user --org system:nodes --client-name system:node:$NODE > kubelet.conf를 실행해요. $NODE는 클러스터의 기존 실패한 노드의 이름으로 설정해야 해요. 결과 kubelet.conf를 수동으로 수정해 클러스터 이름과 서버 엔드포인트를 조정하거나 kubeconfig user --config를 전달해요(추가 사용자용 kubeconfig 파일 생성(/docs/tasks/administer-cluster/kubeadm/kubeadm-certs/#kubeconfig-additional-users) 참고). 클러스터에 ca.key가 없으면 kubelet.conf의 포함된 인증서를 외부에서 서명해야 해요.
  • 결과 kubelet.conf를 실패한 노드의 /etc/kubernetes/kubelet.conf에 복사해요.
  • 실패한 노드에서 kubelet을 재시작하고(systemctl restart kubelet) /var/lib/kubelet/pki/kubelet-client-current.pem이 다시 생성될 때까지 기다려요.
  • client-certificate-dataclient-key-data를 다음으로 교체해 회전된 kubelet 클라이언트 인증서를 가리키도록 kubelet.conf를 수동으로 편집해요.
  • kubelet을 재시작해요.
  • 노드가 Ready가 되는지 확인해요.

Vagrant에서 flannel을 파드 네트워크로 사용할 때 기본 NIC (#default-nic-when-using-flannel-as-the-pod-network-in-vagrant)

다음 오류는 파드 네트워크에서 뭔가 잘못되었음을 나타낼 수 있어요:

Error from server (NotFound): the server could not find the requested resource
  • Vagrant 내부에서 flannel을 파드 네트워크로 사용한다면 flannel의 기본 인터페이스 이름을 지정해야 해요. Vagrant는 일반적으로 모든 VM에 두 인터페이스를 할당해요. 모든 호스트에 IP 주소 10.0.2.15가 할당되는 첫 인터페이스는 NAT되는 외부 트래픽용이에요. 이것은 호스트의 첫 인터페이스를 기본으로 하는 flannel에 문제를 일으킬 수 있어요. 이것은 모든 호스트가 같은 공개 IP 주소를 가진다고 생각하게 해요. 이것을 방지하려면 flannel에 --iface eth1 플래그를 전달해 두 번째 인터페이스가 선택되게 해요.

컨테이너에 비공개 IP가 사용됨 (#non-public-ip-used-for-containers)

어떤 상황에서는 그 외에는 기능하는 클러스터에서 kubectl logskubectl run 명령이 다음 오류를 반환할 수 있어요:

Error from server: Get https://10.19.0.41:10250/containerLogs/default/mysql-ddc65b868-glc5m/mysql: dial tcp 10.19.0.41:10250: getsockopt: no route to host
  • 이것은 Kubernetes가 겉보기에는 같은 서브넷의 다른 IP와 통신할 수 없는 IP를 사용하기 때문일 수 있으며, 아마도 머신 프로바이더의 정책 때문일 수 있어요.
  • DigitalOcean은 eth0에 공개 IP를 할당하고 내부적으로 플로팅 IP 기능의 앵커로 사용할 비공개 IP도 할당하지만, kubelet은 공개 IP 대신 후자를 노드의 InternalIP로 선택할 거예요. 이 시나리오를 확인하려면 ifconfig 대신 ip addr show를 사용해요. ifconfig는 문제의 별칭 IP 주소를 표시하지 않기 때문이에요. 대안으로 DigitalOcean 특유의 API 엔드포인트로 droplet에서 앵커 IP를 조회할 수 있어요. 우회 방법은 --node-ip를 사용해 kubelet에 사용할 IP를 알려주는 것이에요. DigitalOcean을 사용할 때 선택적 비공개 네트워크를 사용하려면 공개 IP(eth0에 할당됨) 또는 비공개 IP(eth1에 할당됨)일 수 있어요. kubeadm NodeRegistrationOptions 구조체의 kubeletExtraArgs 섹션을 이 용도로 사용할 수 있어요. 그런 다음 kubelet을 재시작해요.

coredns 파드가 CrashLoopBackOff 또는 Error 상태 (#coredns-pods-have-crashloopbackoff-or-error-state)

이전 버전의 Docker와 함께 SELinux를 실행하는 노드가 있다면 coredns 파드가 시작되지 않는 시나리오를 겪을 수 있어요. 이것을 해결하려면 다음 옵션 중 하나를 시도할 수 있어요:

kubectl -n kube-system get deployment coredns -o yaml | \
  sed 's/allowPrivilegeEscalation: false/allowPrivilegeEscalation: true/g' | \
  kubectl apply -f -

CoreDNS가 CrashLoopBackOff를 가지는 또 다른 원인은 Kubernetes에 배포된 CoreDNS 파드가 루프를 감지할 때예요. Kubernetes가 CoreDNS가 루프를 감지하고 종료할 때마다 CoreDNS 파드를 재시작하지 않도록 방지하는 여러 우회 방법이 있어요.

더 이상 CoreDNS 루프를 무시하지 않는 것이 좋아요. 일부 쿼리가 무한 루프되고 클러스터의 DNS 서비스에 과부하가 걸릴 수 있으며, 이는 클러스터 전체의 가동 중단으로 이어질 수 있어요.

etcd 파드가 계속 재시작됨 (#etcd-pods-restart-continually)

다음 오류가 발생하면:

rpc error: code = 2 desc = oci runtime error: exec failed: container_linux.go:247: starting container process caused "process_linux.go:110: decoding init error from pipe caused \"read parent: connection reset by peer\""

이 문제는 Docker 1.13.1.84와 함께 CentOS 7을 실행할 때 나타나요. 이 Docker 버전은 kubelet이 etcd 컨테이너 안으로 exec하는 것을 방지할 수 있어요.

문제를 우회하려면 다음 옵션 중 하나를 선택해요:

  • 1.13.1-75 같은 이전 버전의 Docker로 롤백해요.
  • 18.06 같은 더 최근 권장 버전 중 하나를 설치해요.

--component-extra-args 플래그 내부의 인자에 쉼표로 구분된 값 목록을 전달할 수 없음 (#not-possible-to-pass-a-comma-separated-list-of-values-to-arguments-inside-a-component-extra-args-flag)

--component-extra-args 같은 kubeadm init 플래그는 kube-apiserver 같은 컨트롤 플레인 컴포넌트에 커스텀 인자를 전달하게 해줘요. 그러나 이 메커니즘은 값을 파싱하는 데 사용되는 기본 유형(mapStringString) 때문에 제한적이에요.

--apiserver-extra-args "enable-admission-plugins=LimitRanger,NamespaceExists" 같은 여러 쉼표로 구분된 값을 지원하는 인자를 전달하기로 결정하면 이 플래그는 flag: malformed pair, expect string=string으로 실패해요. 이것은 --apiserver-extra-args의 인자 목록이 key=value 쌍을 기대하고 이 경우 NamespacesExists가 값이 빠진 키로 간주되기 때문이에요.

대안으로 key=value 쌍을 --apiserver-extra-args "enable-admission-plugins=LimitRanger,enable-admission-plugins=NamespaceExists"처럼 분리해 시도할 수 있어요. 하지만 이것은 key enable-admission-plugins가 NamespaceExists 값만 가지게 될 거예요.

알려진 우회 방법은 kubeadm 구성 파일을 사용하는 것이에요.

cloud-controller-manager가 노드를 초기화하기 전에 kube-proxy가 스케줄링됨 (#kube-proxy-scheduled-before-node-is-initialized-by-cloud-controller-manager)

클라우드 프로바이더 시나리오에서 kube-proxy는 cloud-controller-manager가 노드 주소를 초기화하기 전에 새 워커 노드에 스케줄링될 수 있어요. 이것은 kube-proxy가 노드의 IP 주소를 제대로 선택하지 못하게 하고 로드 밸런서를 관리하는 프록시 기능에 파급 효과를 가져요.

kube-proxy 파드에서 다음 오류를 볼 수 있어요:

server.go:610] Failed to retrieve node IP: host IP unknown; known addresses: []
proxier.go:340] invalid nodeIP, initializing kube-proxy with 127.0.0.1 as nodeIP

알려진 해결책은 kube-proxy DaemonSet을 패치해 조건과 관계없이 컨트롤 플레인 노드에서 스케줄링되게 하고, 초기 보호 조건이 가라앉을 때까지 다른 노드에서 실행되지 않게 유지하는 것이에요:

kubectl -n kube-system patch ds kube-proxy -p='{
  "spec": {
    "template": {
      "spec": {
        "tolerations": [
          {
            "key": "CriticalAddonsOnly",
            "operator": "Exists"
          },
          {
            "effect": "NoSchedule",
            "key": "node-role.kubernetes.io/control-plane"
          }
        ]
      }
    }
  }
}'

이 문제에 대한 추적 이슈는 여기(https://github.com/kubernetes/kubeadm/issues/1027)에 있어요.

노드에서 /usr이 읽기 전용으로 마운트됨 (#usr-mounted-read-only)

Fedora CoreOS나 Flatcar Container Linux 같은 Linux 배포에서 /usr 디렉터리는 읽기 전용 파일시스템으로 마운트돼요. flex-volume 지원을 위해 kubelet과 kube-controller-manager 같은 Kubernetes 컴포넌트는 기본 경로 /usr/libexec/kubernetes/kubelet-plugins/volume/exec/를 사용하지만, flex-volume 디렉터리는 기능이 동작하려면 쓰기 가능해야 해요.

flex-volume은 deprecated되었으며 flexvolume를 대체할 CSI와 다른 솔루션으로 마이그레이션하는 것이 권장돼요.

이 문제를 우회하려면 kubeadm 구성 파일로 flex-volume 디렉터리를 구성할 수 있어요.

kubeadm init으로 만든 기본 컨트롤 플레인 노드에서 --config로 다음 파일을 전달해요:

apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
nodeRegistration:
  kubeletExtraArgs:
  - name: "volume-plugin-dir"
    value: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
controllerManager:
  extraArgs:
  - name: "flex-volume-plugin-dir"
    value: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"

조인하는 노드에서:

apiVersion: kubeadm.k8s.io/v1beta4
kind: JoinConfiguration
nodeRegistration:
  kubeletExtraArgs:
  - name: "volume-plugin-dir"
    value: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"

대안으로 /etc/fstab을 수정해 /usr 마운트를 쓰기 가능하게 할 수 있지만, 이것은 Linux 배포의 설계 원칙을 수정하는 것임을 유의해주세요.

kubeadm upgrade plan이 context deadline exceeded 오류 메시지를 출력함 (#kubeadm-upgrade-plan-prints-out-context-deadline-exceeded-error-message)

이 오류 메시지는 외부 etcd를 실행하는 경우 kubeadm으로 Kubernetes 클러스터를 업그레이드할 때 표시돼요. 이것은 치명적인 버그가 아니며 이전 버전의 kubeadm이 외부 etcd 클러스터에 대한 버전 검사를 수행하기 때문에 발생해요. kubeadm upgrade apply ...로 계속 진행할 수 있어요.

이 문제는 버전 1.19부터 수정됐어요.

kubeadm reset이 /var/lib/kubelet을 언마운트함 (#kubeadm-reset-unmounts-var-lib-kubelet)

/var/lib/kubelet이 마운트되어 있다면 kubeadm reset을 수행하면 효과적으로 언마운트돼요. 문제를 우회하려면 kubeadm reset 작업을 수행한 후 /var/lib/kubelet 디렉터리를 다시 마운트해요. 이것은 kubeadm 1.15에서 도입된 회귀예요. 문제는 1.20에서 수정됐어요.

kubeadm 클러스터에서 metrics-server를 안전하게 사용할 수 없음 (#cannot-use-the-metrics-server-securely-in-a-kubeadm-cluster)

kubeadm 클러스터에서 metrics-server(https://github.com/kubernetes-sigs/metrics-server)는 --kubelet-insecure-tls를 전달해 안전하지 않게 사용될 수 있어요. 이것은 프로덕션 클러스터에 권장되지 않아요.

metrics-server와 kubelet 사이에 TLS를 사용하고 싶다면 문제가 있는데, kubeadm이 kubelet용 자체 서명 서빙 인증서를 배포하기 때문이에요. 이것은 metrics-server 쪽에 다음 오류를 일으킬 수 있어요:

x509: certificate signed by unknown authority
x509: certificate is valid for IP-foo not IP-bar

kubeadm 클러스터의 kubelet이 제대로 서명된 서빙 인증서를 가지도록 구성하는 방법을 이해하려면 서명된 kubelet 서빙 인증서 활성화를 참고해요. 또한 metrics-server를 안전하게 실행하는 방법도 참고해요.

etcd 해시가 변경되지 않아 업그레이드 실패 (#upgrade-fails-due-to-etcd-hash-not-changing)

kubeadm 바이너리 v1.28.3 이상으로 컨트롤 플레인 노드를 업그레이드할 때만 적용되며, 노드가 현재 kubeadm 버전 v1.28.0, v1.28.1 또는 v1.28.2로 관리되는 경우예요.

다음은 겪을 수 있는 오류 메시지예요:

[upgrade/etcd] Failed to upgrade etcd: couldn't upgrade control plane. kubeadm has tried to recover everything into the earlier state. Errors faced: static Pod hash for component etcd on Node kinder-upgrade-control-plane-1 did not change after 5m0s: timed out waiting for the condition
[upgrade/etcd] Waiting for previous etcd to become available
I0907 10:10:09.109104    3704 etcd.go:588] [etcd] attempting to see if all cluster endpoints ([https://172.17.0.6:2379/ https://172.17.0.4:2379/ https://172.17.0.3:2379/]) are available 1/10
[upgrade/etcd] Etcd was rolled back and is now available
static Pod hash for component etcd on Node kinder-upgrade-control-plane-1 did not change after 5m0s: timed out waiting for the condition
couldn't upgrade control plane. kubeadm has tried to recover everything into the earlier state. Errors faced
k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade.rollbackOldManifests
	cmd/kubeadm/app/phases/upgrade/staticpods.go:525
k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade.upgradeComponent
	cmd/kubeadm/app/phases/upgrade/staticpods.go:254
k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade.performEtcdStaticPodUpgrade
	cmd/kubeadm/app/phases/upgrade/staticpods.go:338
...

이 실패의 이유는 영향을 받는 버전이 PodSpec에서 원하지 않는 기본값으로 etcd 매니페스트 파일을 생성하기 때문이에요. 이것은 매니페스트 비교에서 차이를 초래하고 kubeadm이 파드 해시의 변경을 기대하지만 kubelet은 해시를 업데이트하지 않을 거예요.

클러스터에서 이 문제가 보인다면 우회하는 두 가지 방법이 있어요:

  • 다음을 사용해 영향을 받는 버전과 v1.28.3(또는 이후) 사이의 etcd 업그레이드를 건너뛸 수 있어요. 이후 v1.28 패치 버전이 새 etcd 버전을 도입한 경우에는 권장되지 않아요.
  • 업그레이드 전에 etcd 정적 파드의 매니페스트를 패치해 문제가 되는 기본 속성을 제거해요.

이 버그에 대한 더 많은 정보는 추적 이슈에서 찾을 수 있어요.

더 알아보기 (Learn more)