tk_ch’s blog

インフラエンジニアのブログ

Kubernetesクラスタ(kubeadmで構築)の証明書期限切れ

1年以上前に作成したKubernetesクラスタを久しぶりに起動したら、「an error on the server ("") has prevented the request from succeeding」というエラーメッセージが出て使用できなくなっていた。

# kubectl get pod
Error from server (InternalError): an error on the server ("") has prevented the request from succeeding

結果としては、クラスタ内の証明書有効期限切れが原因だった。
調査・対処結果を残しておく。

環境

Kubernetes:1.18.9
・kubeadm:1.18.9
・Docker:19.03.8
・Calico:v3.16.1
・OS:CentOS7.7

Kubernetes構成

kubeadmで構築した高可用性クラスタ
マスターノード3台、ワーカーノード3台の構成。

k8s構成
Kubernetes構成図

原因調査

状態の確認

kubectlを実行するとエラーが出る。

# kubectl get pod
Error from server (InternalError): an error on the server ("") has prevented the request from succeeding

# kubectl version
Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.9", GitCommit:"94f372e501c973a7fa9eb40ec9ebd2fe7ca69848", GitTreeState:"clean", BuildDate:"2020-09-16T13:56:40Z", GoVersion:"go1.13.15", Compiler:"gc", Platform:"linux/amd64"}
Error from server (InternalError): an error on the server ("") has prevented the request from succeeding

kubeletの状態を確認すると、起動はしているがエラーメッセージ「Unable to write event: 'Post https://dev-klb-001:6443/api/v1/namespaces/default/events: EOF' (may retry after sleeping)」が出ている。
kube-apiserverに接続出来てなさそう。
※このクラスタは、Load Balancer(dev-klb-001)経由で各マスターノード上で稼働するkube-apiserverに接続する構成。

# systemctl status kubelet -l
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; vendor preset: disabled)
  Drop-In: /usr/lib/systemd/system/kubelet.service.d
           mq10-kubeadm.conf
   Active: active (running) since 火 2022-03-29 23:20:04 JST; 2min 31s ago
     Docs: https://kubernetes.io/docs/
 Main PID: 1393 (kubelet)
    Tasks: 28
   Memory: 165.0M
   CGroup: /system.slice/kubelet.service
           tq1393 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --cgroup-driver=cgroupfs --network-plugin=cni --pod-infra-container-image=k8s.gcr.io/pause:3.2
           mq4838 /opt/cni/bin/calico

 3月 29 23:22:34 dev-k8s-001 kubelet[1393]: E0329 23:22:34.726943    1393 event.go:269] Unable to write event: 'Post https://dev-klb-001:6443/api/v1/namespaces/default/events: EOF' (may retry after sleeping)
 3月 29 23:22:34 dev-k8s-001 kubelet[1393]: E0329 23:22:34.780032    1393 kubelet.go:2270] node "dev-k8s-001" not found
 3月 29 23:22:34 dev-k8s-001 kubelet[1393]: E0329 23:22:34.880300    1393 kubelet.go:2270] node "dev-k8s-001" not found
 3月 29 23:22:34 dev-k8s-001 kubelet[1393]: E0329 23:22:34.980527    1393 kubelet.go:2270] node "dev-k8s-001" not found

kube-apiserverのコンテナ(k8s_kube-apiserver_kube-apiserver-dev-k8s-001_kube-system~)がダウンしている。

# docker ps -a
CONTAINER ID        IMAGE                  COMMAND                  CREATED                  STATUS                          PORTS               NAMES
f368632b3aea        e9585e7d0849           "kube-apiserver --ad…"   43 seconds ago           Exited (2) 22 seconds ago                           k8s_kube-apiserver_kube-apiserver-dev-k8s-001_kube-system_6b98960f9130958088f75ebb9d60bf0b_43220
184d8db8a792        c53af2e3d068           "kube-scheduler --au…"   About a minute ago       Up About a minute                                   k8s_kube-scheduler_kube-scheduler-dev-k8s-001_kube-system_0b616cf167c119acab298fb3401e5b0d_17
d7180e8f9c79        k8s.gcr.io/pause:3.2   "/pause"                 About a minute ago       Up About a minute                                   k8s_POD_kube-scheduler-dev-k8s-001_kube-system_0b616cf167c119acab298fb3401e5b0d_14
e4bc371d116f        dacf3f247065           "kube-controller-man…"   About a minute ago       Up About a minute                                   k8s_kube-controller-manager_kube-controller-manager-dev-k8s-001_kube-system_c3d93a3b59f1ca8301ff46a4830eeaa7_18
298d73338ee9        303ce5db0e90           "etcd --advertise-cl…"   About a minute ago       Up About a minute                                   k8s_etcd_etcd-dev-k8s-001_kube-system_0eadebe3593196dc3f27d3e05cc6c3a9_34724
ea2d8a47539b        k8s.gcr.io/pause:3.2   "/pause"                 About a minute ago       Up About a minute                                   k8s_POD_kube-apiserver-dev-k8s-001_kube-system_6b98960f9130958088f75ebb9d60bf0b_12
adc2f556a505        k8s.gcr.io/pause:3.2   "/pause"                 About a minute ago       Up About a minute                                   k8s_POD_etcd-dev-k8s-001_kube-system_0eadebe3593196dc3f27d3e05cc6c3a9_13
127994367f83        k8s.gcr.io/pause:3.2   "/pause"                 About a minute ago       Up About a minute                                   k8s_POD_kube-controller-manager-dev-k8s-001_kube-system_c3d93a3b59f1ca8301ff46a4830eeaa7_13

ダウンしていたkube-apiserverコンテナのログを確認するが、よく分からず。

# docker logs f368632b3aea 
W0329 12:01:55.559292       1 clientconn.go:1208] grpc: addrConn.createTransport failed to connect to {https://127.0.0.1:2379  <nil> 0 <nil>}. Err :connection error: desc = "transport: authentication handshake failed: context deadline exceeded". Reconnecting...
panic: context deadline exceeded

goroutine 1 [running]:
k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition.NewREST(0xc000700a10, 0x50ebdc0, 0xc000157d40, 0xc000157f68)
    /workspace/anago-v1.18.9-rc.0.79+8147d851af540a/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go:56 +0x3e7
k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/apiserver.completedConfig.New(0xc000a53ca0, 0xc00044d408, 0x51aa780, 0x774b858, 0x10, 0x0, 0x0)
    /workspace/anago-v1.18.9-rc.0.79+8147d851af540a/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go:145 +0x14ef
k8s.io/kubernetes/cmd/kube-apiserver/app.createAPIExtensionsServer(0xc00044d400, 0x51aa780, 0x774b858, 0x0, 0x50eb980, 0xc0009767a0)
    /workspace/anago-v1.18.9-rc.0.79+8147d851af540a/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/cmd/kube-apiserver/app/apiextensions.go:102 +0x59
k8s.io/kubernetes/cmd/kube-apiserver/app.CreateServerChain(0xc00087f080, 0xc000030360, 0x455f9f4, 0xc, 0xc0006d3c48)
    /workspace/anago-v1.18.9-rc.0.79+8147d851af540a/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/cmd/kube-apiserver/app/server.go:181 +0x2b8
k8s.io/kubernetes/cmd/kube-apiserver/app.Run(0xc00087f080, 0xc000030360, 0x0, 0x0)
    /workspace/anago-v1.18.9-rc.0.79+8147d851af540a/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/cmd/kube-apiserver/app/server.go:150 +0x101
k8s.io/kubernetes/cmd/kube-apiserver/app.NewAPIServerCommand.func1(0xc00089a000, 0xc000875380, 0x0, 0x1a, 0x0, 0x0)
    /workspace/anago-v1.18.9-rc.0.79+8147d851af540a/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/cmd/kube-apiserver/app/server.go:117 +0x104
k8s.io/kubernetes/vendor/github.com/spf13/cobra.(*Command).execute(0xc00089a000, 0xc00004c1d0, 0x1a, 0x1b, 0xc00089a000, 0xc00004c1d0)
    /workspace/anago-v1.18.9-rc.0.79+8147d851af540a/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/vendor/github.com/spf13/cobra/command.go:826 +0x460
k8s.io/kubernetes/vendor/github.com/spf13/cobra.(*Command).ExecuteC(0xc00089a000, 0x16e0d84e6630895f, 0x772d680, 0xc000068750)
    /workspace/anago-v1.18.9-rc.0.79+8147d851af540a/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/vendor/github.com/spf13/cobra/command.go:914 +0x2fb
k8s.io/kubernetes/vendor/github.com/spf13/cobra.(*Command).Execute(...)
    /workspace/anago-v1.18.9-rc.0.79+8147d851af540a/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/vendor/github.com/spf13/cobra/command.go:864
main.main()
    _output/dockerized/go/src/k8s.io/kubernetes/cmd/kube-apiserver/apiserver.go:43 +0xcd

原因

この後、ノードのOS再起動や、kubelet、Dockerの再起動を試したが、状況は変わらなかった。
以前は問題なく動作していたクラスタが時間経過で動作しなくなったので、証明書まわりが怪しそうだが、ググった感じ証明書期限切れのときは「Unable to connect to the server: x509: certificate has expired or is not yet valid」とストレートなメッセージが出そう。

念のため、証明書の状態を確認したところ、とっくに期限が切れていた。

# date
2022年  3月 29日 火曜日 23:26:51 JST

# kubeadm alpha certs check-expiration
[check-expiration] Reading configuration from the cluster...
[check-expiration] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[check-expiration] Error reading configuration from the Cluster. Falling back to default configuration

W0329 23:24:42.384030    6270 configset.go:202] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io]
CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                 Sep 26, 2021 08:27 UTC   <invalid>                               no
apiserver                  Sep 26, 2021 08:27 UTC   <invalid>       ca                      no
apiserver-etcd-client      Sep 26, 2021 08:27 UTC   <invalid>       etcd-ca                 no
apiserver-kubelet-client   Sep 26, 2021 08:27 UTC   <invalid>       ca                      no
controller-manager.conf    Sep 26, 2021 08:27 UTC   <invalid>                               no
etcd-healthcheck-client    Sep 26, 2021 08:27 UTC   <invalid>       etcd-ca                 no
etcd-peer                  Sep 26, 2021 08:27 UTC   <invalid>       etcd-ca                 no
etcd-server                Sep 26, 2021 08:27 UTC   <invalid>       etcd-ca                 no
front-proxy-client         Sep 26, 2021 08:27 UTC   <invalid>       front-proxy-ca          no
scheduler.conf             Sep 26, 2021 08:27 UTC   <invalid>                               no

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Sep 24, 2030 08:27 UTC   8y              no
etcd-ca                 Sep 24, 2030 08:27 UTC   8y              no
front-proxy-ca          Sep 24, 2030 08:27 UTC   8y              no

Kubernetesの公式ドキュメントによると、以下の通り。

・kubeadmで生成される証明書の期限は1年。
・コントロールプレーンのアップグレード時にkubeadmは証明書を更新する。
kubeadm certs renewコマンドで手動で証明書更新が可能。

Kubernetesを随時バージョンアップすれば問題ない。
とはいえ何らかの理由で1年以上バージョンアップできないケースもあると思う。
せっかく期限切れしたので、そのようなケースを想定して手動更新をやってみる。

証明書を手動更新する

クラスタの証明書を更新(マスターノード#1での作業)

公式の手順に従い、kubeadm certs renewコマンドで証明書を手動更新する。
まずは、マスターノード全台で作業が必要とのことなので、まずはマスターノード#1で作業をする。

# kubeadm alpha certs renew all
[renew] Reading configuration from the cluster...
[renew] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[renew] Error reading configuration from the Cluster. Falling back to default configuration

W0329 23:40:55.707534   19090 configset.go:202] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io]
certificate embedded in the kubeconfig file for the admin to use and for kubeadm itself renewed
certificate for serving the Kubernetes API renewed
certificate the apiserver uses to access etcd renewed
certificate for the API server to connect to kubelet renewed
certificate embedded in the kubeconfig file for the controller manager to use renewed
certificate for liveness probes to healthcheck etcd renewed
certificate for etcd nodes to communicate with each other renewed
certificate for serving etcd renewed
certificate for the front proxy client renewed
certificate embedded in the kubeconfig file for the scheduler manager to use renewed

証明書の期限を確認すると、更新されている。

# kubeadm alpha certs check-expiration
[check-expiration] Reading configuration from the cluster...
[check-expiration] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[check-expiration] Error reading configuration from the Cluster. Falling back to default configuration

W0329 23:51:57.412900   29201 configset.go:202] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io]
CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                 Mar 29, 2023 14:40 UTC   364d                                    no
apiserver                  Mar 29, 2023 14:40 UTC   364d            ca                      no
apiserver-etcd-client      Mar 29, 2023 14:40 UTC   364d            etcd-ca                 no
apiserver-kubelet-client   Mar 29, 2023 14:40 UTC   364d            ca                      no
controller-manager.conf    Mar 29, 2023 14:40 UTC   364d                                    no
etcd-healthcheck-client    Mar 29, 2023 14:40 UTC   364d            etcd-ca                 no
etcd-peer                  Mar 29, 2023 14:40 UTC   364d            etcd-ca                 no
etcd-server                Mar 29, 2023 14:40 UTC   364d            etcd-ca                 no
front-proxy-client         Mar 29, 2023 14:40 UTC   364d            front-proxy-ca          no
scheduler.conf             Mar 29, 2023 14:40 UTC   364d                                    no

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Sep 24, 2030 08:27 UTC   8y              no
etcd-ca                 Sep 24, 2030 08:27 UTC   8y              no
front-proxy-ca          Sep 24, 2030 08:27 UTC   8y              no

kubectlの証明書を更新(マスターノード#1での作業)

公式ドキュメントには明記されていないが、kubectlが使用する証明書は古いままなので、差し替える必要がある。
kubectlが使用する証明書の有効期限を確認すると、古いまま。

# cat ~/.kube/config | grep "client-certificate-data:" | awk '{ print $2 }' | base64 --decode > /tmp/crt.txt
# openssl x509 -enddate -noout -in /tmp/crt.txt | cut -d= -f 2
Sep 26 08:27:31 2021 GMT

更新された「/etc/kubernetes/admin.conf」を「~/.kube/config」にコピーすることで更新する。

# cp /etc/kubernetes/admin.conf ~/.kube/config
# cat ~/.kube/config | grep "client-certificate-data:" | awk '{ print $2 }' | base64 --decode > /tmp/crt_new.txt
# openssl x509 -enddate -noout -in /tmp/crt_new.txt | cut -d= -f 2
Mar 29 14:40:56 2023 GMT

残りのマスターノードでの作業

公式ドキュメントではこの後、/etc/kubernetes/manifests/配下のマニフェストファイルを一度退避し戻すことで、Static Podを再起動する手順となっているが、実施しても再起動しなかった。

マスターノード全台での作業を完了させる必要があるのかもしれないと考え、残りのマスターノード#2、#3でもマスターノード#1と同様の作業を実行した。
マスターノード#2、#3での作業後少し待つと、全マスターノード上でkube-apiserver Pod含むstatic Podが起動した。

作業後の確認

kubectlが問題無く実行出来るようになった。
ワーカーノードについてもSTATUSがReadyになっており、クラスタへの再登録などは不要だった。

# kubectl get node
NAME          STATUS   ROLES    AGE    VERSION
dev-k8s-001   Ready    master   549d   v1.18.9
dev-k8s-002   Ready    master   549d   v1.18.9
dev-k8s-003   Ready    master   549d   v1.18.9
dev-k8s-101   Ready    <none>   549d   v1.18.9
dev-k8s-102   Ready    <none>   549d   v1.18.9
dev-k8s-103   Ready    <none>   549d   v1.18.9

まとめ

・エラーメッセージが「Unable to connect to the server: x509: certificate has expired or is not yet valid」でなくても、証明書関連の問題の場合がある。
・公式ドキュメントにはざっくりした更新手順しか書いていないが、kubectlの証明書更新や、高可用性クラスタの場合の作業など、注意が必要な点がある。
・プロダクション環境導入時には、証明書の更新についての検討が必要。

参考文献

Kubernetes公式ドキュメント kubeadmによる証明書管理
TLS証明書のエラーでkubectlが使用できなくなった
Kubernetes証明書を10年間の有効期間で更新します
Kubernetes証明書の期限切れによりクラスタ全体の通信が停止する