{"id":"IL-0184","org":"dbhang","date":"2021-03-31","docs":[],"title":{"en":"JupyterHub's KubeSpawner could not create containers and kubectl was refused because the API server and other internal certificates of a kubeadm cluster had expired","ko":"kubeadm 클러스터의 API 서버와 내부 인증서가 만료돼 JupyterHub KubeSpawner 가 컨테이너를 만들지 못하고 kubectl 연결이 거부됐다"},"domain":["kubernetes"],"impact":{"scope":"JupyterHub KubeSpawner container creation and all access to the Kubernetes API of the cluster","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"kubeadm clusters run on certificates that expire, and when the API server certificate expires kubectl cannot be used to fix it, so renewal has to happen on the control plane node with kubeadm. After renewal, the kubeconfig files and the running control plane containers must also pick up the new certificates, or kubectl returns Unauthorized.","ko":"kubeadm 클러스터의 인증서가 만료되면 kubectl 이 API 서버에 연결하지 못하므로 kubectl 로는 갱신할 수 없고, 노드에서 kubeadm 으로 직접 갱신해야 한다. 갱신 뒤에는 인증서만이 아니라 kubeconfig 파일도 새로 만들어야 하고, 예전 인증 정보를 들고 있는 kube-system 파드도 재시작해야 한다.\n\n글쓴이는 클러스터를 처음부터 직접 구성해 봤다면 인증서를 쓴다는 것을 바로 알았을 것이고, 명령 한 줄로 막을 수 있는 문제였다고 돌아봤다."},"related":[],"sources":[{"url":"https://my-grope-log.tistory.com/3","kind":"blog","license":"unknown","archived":null}],"summary":{"en":"A kubeadm-built cluster that runs JupyterHub stopped accepting requests because its internal certificates, including the API server certificate, had expired. The author had not used kubeadm since building the cluster and had not known that the cluster relies on certificates that expire.\n\nWith kubectl unusable, the certificates were renewed with kubeadm on the control plane node. The author checked the remaining validity with kubeadm alpha certs check-expiration, moved the old certificates and the /etc/kubernetes/*.conf files to a backup directory, regenerated all certificates with kubeadm init phase certs all and the kubeconfig files with kubeadm init phase kubeconfig all. kubeconfig files such as admin.conf have to be regenerated separately to match the new certificates; the output shown came from a kubeadm client at v1.16.3.\n\nCopying the new admin.conf to ~/.kube/config and restarting kubelet was not enough: the kube-system pods still held the old credentials and kubectl answered Unauthorized. Restarting all of those containers with docker restart made kubectl work again and pods could be created. Containers without an automatic restart policy have to be started by hand. The author notes that anyone who had built a cluster from scratch would know about these certificates, and that a single command would have prevented the outage.","ko":"글쓴이는 kubeadm 으로 구성한 쿠버네티스 클러스터에서 JupyterHub KubeSpawner 로 컨테이너를 띄우고 있었다. 클러스터를 만든 뒤로 kubeadm 으로 인증서를 관리한 적이 없었고, 쿠버네티스 API 서버를 포함한 클러스터 내부 인증서가 만료되면서 KubeSpawner 의 API 호출이 인증서 검증에서 실패했다. kubectl 도 API 서버에 연결하지 못해 kubectl 로는 아무것도 할 수 없었다.\n\n글쓴이는 마스터 노드에서 kubeadm 으로 인증서를 갱신했다. `kubeadm alpha certs check-expiration` 으로 남은 기간을 확인하고, 기존 인증서와 `/etc/kubernetes/*.conf` 파일을 백업 디렉터리로 옮긴 뒤 `kubeadm init phase certs all` 로 인증서를 모두 새로 만들었다. 다시 확인하니 apiserver 를 비롯한 인증서의 남은 기간이 364d 로 늘었다. admin.conf, controller-manager.conf, scheduler.conf 같은 kubeconfig 파일은 새 인증서에 맞춰 따로 갱신해야 해서 `kubeadm init phase kubeconfig all` 로 다시 만들었다.\n\n새 admin.conf 를 `~/.kube/config` 로 복사하고 kubelet 을 재시작했지만 kubectl 은 Unauthorized 오류를 냈다. kube-system 파드들이 예전 인증 정보를 들고 있었기 때문이다. `docker restart` 로 이 컨테이너들을 모두 재시작하자 kubectl 이 다시 동작했고 파드 생성도 정상으로 돌아왔다. 자동 재시작 설정이 없는 파드는 하나하나 다시 띄워야 한다."},"trigger":null,"severity":"unknown","symptoms":["control-plane-unresponsive","deploys-failing"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":{"en":"KubeSpawner failed to create a container and logged an SSL certificate verification error.","ko":"KubeSpawner 가 컨테이너를 만들다가 처음 보는 SSL 인증서 오류를 냈다. 글쓴이는 오류 로그를 검색해 클러스터 내부 인증서 만료가 원인임을 알았다."},"components":["kube-apiserver","kubelet"],"root_cause":"certificate-expiry","remediation":[{"text":{"en":"checked certificate expiry with kubeadm alpha certs check-expiration","ko":"`kubeadm alpha certs check-expiration` 으로 인증서별 남은 기간을 확인했다."}},{"text":{"en":"moved the old certificates and /etc/kubernetes/*.conf files to a backup directory","ko":"기존 인증서 파일과 `/etc/kubernetes/*.conf` 파일을 백업 디렉터리로 옮겼다."}},{"text":{"en":"regenerated all certificates with kubeadm init phase certs all","ko":"`kubeadm init phase certs all` 로 모든 인증서를 새로 만들었다."}},{"text":{"en":"regenerated the kubeconfig files with kubeadm init phase kubeconfig all","ko":"`kubeadm init phase kubeconfig all` 로 admin.conf 를 비롯한 kubeconfig 파일을 새로 만들었다."}},{"text":{"en":"copied /etc/kubernetes/admin.conf to ~/.kube/config and restarted kubelet","ko":"새 admin.conf 를 `~/.kube/config` 로 복사하고 kubelet 을 재시작했다."}},{"text":{"en":"restarted the kube-system containers with docker restart so they picked up the new credentials","ko":"예전 인증 정보를 들고 있던 kube-system 컨테이너들을 `docker restart` 로 모두 재시작했다."}}],"action_items":[],"contributing":["limit-unknown","no-alert-on-limit"],"recurrence_of":null,"symptoms_text":{"en":"KubeSpawner, which starts JupyterHub user containers on Kubernetes, failed to create containers with an error the author had never seen, saying an SSL certificate had expired.\n\nThe log read: HTTPSConnectionPool(host=hostVM, port=6443): Max retries exceeded with url: /api/v1/namespaces (Caused by SSLError(SSLError(\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')])\"))). Searching for it showed that the certificates used for communication inside the Kubernetes cluster had expired.\n\nEvery kubectl command, including the ones meant to renew certificates, failed with \"The connection to the server hostvm:6443 was refused - did you specify the right host or port?\", because the Kubernetes API server certificate had expired too. After new certificates were generated and admin.conf was copied into place, kubectl returned \"error: You must be logged in to the server (Unauthorized)\" until the kube-system containers were restarted.","ko":"JupyterHub 의 KubeSpawner 로 컨테이너를 만들 때 처음 보는 오류가 나며 생성이 실패했다. 오류는 SSL 인증서가 만료됐다는 내용이었다.\n\nKubeSpawner 의 오류 로그에는 다음 메시지가 찍혔다. \"HTTPSConnectionPool(host=hostVM, port=6443): Max retries exceeded with url: /api/v1/namespaces (Caused by SSLError(SSLError(\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')])\")))\"\n\n검색해 보니 쿠버네티스 클러스터 내부 통신에 쓰는 인증서가 만료돼 생긴 문제였다. kubectl 명령으로 인증서를 갱신하려 했지만 어떤 kubectl 명령을 써도 \"The connection to the server hostvm:6443 was refused - did you specify the right host or port?\" 오류가 났다. 쿠버네티스 API 서버의 인증서도 만료된 상태였다.\n\n새 인증서와 kubeconfig 를 적용한 뒤에도 kubectl 은 \"error: You must be logged in to the server (Unauthorized)\" 를 냈다. kube-system 의 파드들이 예전 인증 정보를 계속 들고 있었기 때문이다."},"updated_at":"2026-11-09T11:55:57.261101Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0184","org_slug":"dbhang"}