update on 2022-05-21
今天在 homelab 的 k8s 集群上发生了同样的情况,我想删除一个 namespace,再确认已经把 namespace 里面所有其他资源都删除的情况下,namespace 始终是 Terminating, 找了很多资料,方法也众说纷纭 。
最后通过看 api-server
log 发现原来又是 Unable to authenticate the request due to an error: x509: certificate has expired or is not yet valid
root cause 还是我更新 cert 的时候又漏了某些步骤。
事情的起因是 k8s 的 cert 过期了,在目录 /etc/kubernetes/pki/
下面的这些 cert 都与 k8s 的核心服务息息相关,因此 cert 过期了,整个 k8s 集群就停止服务了。
这个集群是 kubernetes 1.14, 因此需要运行几个命令完成更新,而 1.15 版本以上这个过程简化了不少。 由于之前已经 renew cert 两次了,因此正常按部就班几个操作就完事了,但是这个因为一点小疏忽,加上系统死机重启了一次,花了很多时间去恢复各种服务。
本文记录 debug 的过程中遇到的一些症状,以及后来发现的解决方法,为以后遇到类似问题提供思路。
Node 重启后 kubelet 没运行
前面提到,可能是因为 cert 过期后触发某些 bug 导致 Master Node 不能 ssh(之前 renew cert 没有类似问题),所以只能去机房按电源开关重启了。
重启之后,发现 kubelet 没有运行,于是尝试 systemctl restart kubelet
, 发现没有效果,那就看看 status
$ systemctl status kubelet.service
● kubelet.service - kubelet: The Kubernetes Node Agent
Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset: enabled)
Drop-In: /etc/systemd/system/kubelet.service.d
└─10-kubeadm.conf
Active: activating (auto-restart) (Result: exit-code) since Tue 2022-04-26 22:09:42 PDT; 4s ago
Docs: https://kubernetes.io/docs/home/
Process: 18121 ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS (code=exited, status=255)
Main PID: 18121 (code=exited, status=255)
一搜索,发现结果都是告诉你 cert 过期了,一切似乎很简单,我已经 renew 过几次 cert 了,操作起来轻车熟路。 正因为轻车熟路,所以忽略了要删除原来的旧文件,导致更新 cert 后 kubelet.service 仍然无法启动。
这就要说到下面这个坑了。
更新 cert 需要删除原来的文件
k8s cert 过期后如何 renew? 网上有很多文章不再赘述,针对 1.14 版本,简而言之是运行下面几个命令
1. kubeadm alpha certs renew all
2. kubeadm init phase kubeconfig all --apiserver-advertise-address=192.168.31.50
3. cp /etc/kubernetes/admin.conf ./kube/config
4. systemctl restart kubelet
其中第一步运行完之后,/etc/kubernetes/pki
目录下除了 CA 以外,所有的 cert 都被更新了,这一步很简单不需要特殊注意什么。
/etc/kubernetes/pki$ ls -al
total 72
drwxr-xr-x 3 root root 4096 Aug 3 2020 .
drwxr-xr-x 4 root root 4096 Apr 27 01:26 ..
-rw-r--r-- 1 root root 1216 Apr 26 22:47 apiserver.crt
-rw-r--r-- 1 root root 1090 Apr 26 22:47 apiserver-etcd-client.crt
-rw------- 1 root root 1679 Apr 26 22:47 apiserver-etcd-client.key
-rw------- 1 root root 1679 Apr 26 22:47 apiserver.key
-rw-r--r-- 1 root root 1099 Apr 26 22:47 apiserver-kubelet-client.crt
-rw------- 1 root root 1679 Apr 26 22:47 apiserver-kubelet-client.key
-rw------- 1 root root 38 Aug 3 2020 basic_auth_file
-rw-r--r-- 1 root root 1025 Apr 23 2019 ca.crt
-rw------- 1 root root 1675 Apr 23 2019 ca.key
drwxr-xr-x 2 root root 4096 Apr 22 2020 etcd
-rw-r--r-- 1 root root 1038 Apr 23 2019 front-proxy-ca.crt
-rw------- 1 root root 1675 Apr 23 2019 front-proxy-ca.key
-rw-r--r-- 1 root root 1058 Apr 26 22:47 front-proxy-client.crt
-rw------- 1 root root 1679 Apr 26 22:47 front-proxy-client.key
-rw------- 1 root root 1679 Apr 23 2019 sa.key
-rw------- 1 root root 451 Apr 23 2019 sa.pub
第二步是用新的 cert 生成 /etc/kubernetes
目录下的配置文件。
/etc/kubernetes$ ls -al
drwxr-xr-x 4 root root 4096 Apr 27 01:26 .
drwxr-xr-x 97 root root 4096 Aug 20 2021 ..
-rw------- 1 root root 5451 Apr 26 23:00 admin.conf
-rw------- 1 root root 5451 Apr 23 2021 admin.conf.old
-rw------- 1 root root 5487 Apr 26 23:00 controller-manager.conf
-rw------- 1 root root 5487 Apr 23 2021 controller-manager.conf.old
-rw------- 1 root root 5459 Apr 26 23:00 kubelet.conf
-rw------- 1 root root 5459 Apr 26 22:47 kubelet.conf.old
drwxr-xr-x 2 root root 4096 Aug 3 2020 manifests
drwxr-xr-x 3 root root 4096 Aug 3 2020 pki
-rw------- 1 root root 5431 Apr 26 23:00 scheduler.conf
-rw------- 1 root root 5435 Apr 23 2021 scheduler.conf.old
注意,在运行第二条命令前,需要把所有 .config
文件删除或者是 rename,如果保留 .config 文件的话,这些文件不会被更新,所以导致 kubelet 启动读取配置文件后,仍然是过期的 cert,因此 systemctl 无法启动这个服务,错误内容在第一节里面已经展示了。
这一点如果错了,很难发现错在哪。 如果上网搜索 kubelet error code 255
,结果都是告诉你需要更新 cert,但是明明已经更新了cert 了啊,为什么还不行呢? 然后你再更新一遍 cert,会发现还是没用,因为 config file 没有更新。
重启 Docker
完成上面 2 步后,kubelet 还是无法启动,这就很奇怪了,琢磨了半天,我想到重启下 docker 试试? 在重启之前,我还用 docker container prune 命令把旧的 container 都删了
$ systemctl restart docker
果然,之后用 docker ps 查看,各种 container 都运行起来了。
不知道这个问题有没有共性,还是只是我遇到的特例,总之,kubelet 服务有问题的时候,也可以尝试重启下 docker。
Pod 没有正常运行
到这里,用 kubectl 查看各个 namespace,pod 基本都显示 running 状态,我以为集群基本正常了。
这时,我看到有一个 mysql 的 pod 处于 CrashBackoff 的状态,按照以往的经验,mysql 属于有状态应用,可能因为 node 重启产生了一些错误,之前我的解决方法就是删除这个 pod,然后 k8s 会自动起一个新的。
于是故技重施,我删除了这个 pod,等待 k8s 新建一个,可是等了又等,迟迟不见 k8s 创建新的 Pod。
用 kubectl 查看 deployment 或者 ReplicaSet 的状态,看起来都正常,
$ kubectl -n kubeflow describe deployment katib-mysql
#..... 省略
Replicas: 1 desired | 1 updated | 1 total | 0 available | 0 unavailable
#..... 省略
Conditions:
Type Status Reason
---- ------ ------
Available True 某某原因(我没有记下来)
Progressing False 某某原因(我没有记下来)
上面这个内容是经过我编辑的,原始的状态信息我没有记下来,总之,此时的情况是:
- desired/updated 这些都是 1,唯独 available 是 0,也就是说 k8s 迟迟不创建一个新的 pod
- Available/Progressing 也显示了一些 reason,但是还是没告诉我哪里有问题
对于这个问题,我也是琢磨了好久,始终不明白: cert 也更新了,集群状态大部分也都在 running 了,为啥唯独个别 pod 无法创建呢?
然后,我有删除了一个 running 的 pod,看看正常的 pod 能否自动重建,发现也不可以。
最终,我想到看看 k8s apiserver 和 scheduler 的 log, 一看,发现好像还是有很多 Unauthorized
, connection error
, cert error
之类的错,但是明明 cert 已经更新了啊 ?
这就回到了第二小节的问题,不单单是 kubelet.conf 的 config 要删除才能重建,其他所有的 config 都要删除。 于是问题解决。
所以,这个问题给我的思路就是,如果发现 k8s 在创建 Pod 方面有问题,首先要想到的是 API server,scheduler,kubelet 是否正常。 因为这几个组件与 pod 的生命周期息息相关。
Image 不是原来的版本
这个问题很奇怪,至今没想明白是谁动了我的 image tag :-)
这次的问题还是某个 mysql pod 无法启动,查看它的 log
$ kubectl -n kubeflow logs -f katib-mysql-57884cb488-qjdct
2022-04-27 08:45:03+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.25-1debian10 started.
2022-04-27 08:45:03+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
2022-04-27 08:45:03+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.25-1debian10 started.
2022-04-27T08:45:03.559461Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.25) starting as process 1
2022-04-27T08:45:03.580431Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2022-04-27T08:45:04.193878Z 1 [ERROR] [MY-013171] [InnoDB] Cannot boot server version 80025 on data directory built by version 80028. Downgrade is not supported
mysqld: Can't open file: 'mysql.ibd' (errno: 0 - )
2022-04-27T08:45:09.194257Z 1 [ERROR] [MY-010334] [Server] Failed to initialize DD Storage Engine
2022-04-27T08:45:09.194591Z 0 [ERROR] [MY-010020] [Server] Data Dictionary initialization failed.
2022-04-27T08:45:09.195342Z 0 [ERROR] [MY-010119] [Server] Aborting
2022-04-27T08:45:09.196992Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.0.25) MySQL Community Server - GPL.
问题似乎很清楚: “Cannot boot server version 80025 on data directory built by version 80028”。
但是我明明运行的是 docker container 啊,同样的 image 为什么会存在版本不同的问题呢? 要知道,docker 本质上解决的就是版本不一致的问题。
不过既然错误这么说了,我就看了下 deployment 信息,image 版本是这样指定的 mysql:8
。
注意,此处是 mysql:8
, 我又去 dockerhub 上看了下 mysql:8
发现最近有更新,同时,也有 error 里面提到的 mysql:8.0.28
。
于是,我把 deployment 中 image 替换成了 mysql:8.0.28
,问题解决。
但是,始终没有理解为什么会出现这样的问题,按理说一个 image:tag
组合就唯一定义了一个版本,不可能出现一个是 8.0.25, 另一个是 8.0.28 的问题。
$ docker image ls | grep mysql
mysql 8.0.28 f2ad9f23df82 6 days ago 521MB
mysql 5.6 dd3b2a5dcb48 4 months ago 303MB
mysql 8 5c62e459e087 10 months ago 556MB
mysql 8.0.3 00400babc1b7 4 years ago 343M
好吧,这个问题始终没想明白。
全文完。