├── .gitignore ├── README.md ├── images ├── tcpdump.png ├── nf_conntrack.png ├── telnet_hang.png └── telnet_hang2.png ├── 排错指南-网络和服务.md └── 排错指南-Pod.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [WIP] kubernetes-troubleshooting-book 2 | 3 | Kubernetes 排错指南 4 | -------------------------------------------------------------------------------- /images/tcpdump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opsnull/kubernetes-troubleshooting-book/HEAD/images/tcpdump.png -------------------------------------------------------------------------------- /images/nf_conntrack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opsnull/kubernetes-troubleshooting-book/HEAD/images/nf_conntrack.png -------------------------------------------------------------------------------- /images/telnet_hang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opsnull/kubernetes-troubleshooting-book/HEAD/images/telnet_hang.png -------------------------------------------------------------------------------- /images/telnet_hang2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opsnull/kubernetes-troubleshooting-book/HEAD/images/telnet_hang2.png -------------------------------------------------------------------------------- /排错指南-网络和服务.md: -------------------------------------------------------------------------------- 1 | # 排错指南 - 网络 2 | 3 | 本文档介绍排查网络异常的步骤和方法。 4 | 5 | 网络异常的范围: 6 | 7 | 1. 节点间 Pod IP 不通; 8 | 1. 节点间 Server 服务不通; 9 | 1. NodePort 不通; 10 | 1. 以及由此导致的 域名解析失败; 11 | 12 | ## mysql service 和 pod 均不通,在两个节点的 flannel.1 接口抓包,结果显示调用方收到了回包 13 | 14 | 原因: 15 | 16 | 1. mysql server 所在节点 ARP 缓存的调用方节点 flannel.1 接口 MAC 地址不对,导致调用方虽然收到了回包(三层发送),但是发现接口MAC 地址不对,便丢弃了。 17 | 1. 初步怀疑与删除了 flanneld daemonset,但是没有清理 flanneld 网络接口,后续重新创建 daemonset 时,flanneld 缓存了旧的 MAC 地址有关; 18 | 19 | 解决方案:删除错误的 ARP 缓存记录 20 | 21 | # 访问 Pod 服务,第一次通,后续时通时不通 22 | 23 | 现象: 24 | 25 | 1. 节点(prophet1)上的 Pod 对外通过 NodePort 提供服务,但从其它节点和本节点 telnet NodeIP:NodePort ,第一次通,后续都不通(卡住): 26 | 27 | ![telnet_hang](images/telnet_hang.png) 28 | 29 | 1. 从其它节点(prophet5) telnet 异常节点 prophet1 未监听的端口,被正常 refused,但 telnet NodePort 时卡住: 30 | 31 | ![telnet_hang2](images/telnet_hang2.png) 32 | 33 | 1. nf_conntrack 连接追踪结论显示:prophet5 没有收到异常节点发送的 SYN_ACK 响应: 34 | 35 | ![nf_conntrack](images/nf_conntrack.png) 36 | 37 | 1. prophet1 上的各 K8S 组件、iptables 规则都正常、ip_forward 功能也开启; 38 | 1. 查找 NodePort 对应的 Pod IP,使用 ip route get Pod_IP 命令查找对应 Pod 容器的 calico 接口名称,然后在该接口上抓包。 39 | 1. 抓包结果显示: 40 | 41 | + 第一次请求 TCP Timestamp value 是 621677338,被正确响应; 42 | + 后续几次请求的 TCP Timestamp value 是 6137607xx,没有响应; 43 | + 紧接着请求的 TCP Timestamp value 是 621697338,被正确响应; 44 | + 后续的几次请求的 TCP Timestamp value 是 6137607xx,没有响应; 45 | 46 | ![tcpdump](images/tcpdump.png) 47 | 48 | 原因: 49 | 50 | 1. 一个 NAT 和 net.ipv4.tcp_tw_recycle 引起的血案! 51 | 1. K8S 节点使用 NAT 机制支持访问 Pod IP、Service IP、NodePort; 52 | 1. 节点开启了内核参数 net.ipv4.tcp_tw_recycle,开启了该参数后,无法保证经过 NAT 转换后的客户端 TCP 请求 Header 中的 Timestamp 值严格递增;(因为各客户端时间可能不同步,很难保证他们的 TCP 请求的 timestamp 严格递增) 53 | 1. 而 kernel 的 PASW 机制要求所有来自同一个 Host IP 的 TCP 包 timestamp 必须是递增的,当收到的 timestamp 变小时,会认为这是一个过期的数据包,将其丢弃; 54 | 55 | 解决方案: 56 | 57 | 1. 使用命令 sysctl -w net.ipv4.tcp_tw_recycle=0 关闭 K8S 节点的 tcp_tw_recycle。 58 | 59 | # Pod 服务收到的请求来源 IP 非 Pod IP,而是所在节点 flannel.1 的 IP 60 | 61 | 现象: 62 | 63 | 1. keystone 收到的请求来源 IP 大量为.0 结尾的各节点 flannnel.1 IP 地址,而非实际发起请求的 Pod IP; 64 | 1. 只有和 keystone 在一个节点的 Pod 请求, 才会正确显示来源 IP; 65 | 66 | 原因: 67 | 68 | 1. flannel 启动后生成的 docker 环境变量中包含 --ip-masq=true 参数: 69 | 70 | ``` bash 71 | [root@m7-devops-128071 ~]# cat /var/run/flannel/docker 72 | DOCKER_OPT_BIP="--bip=172.30.24.1/24" 73 | DOCKER_OPT_IPMASQ="--ip-masq=true" 74 | DOCKER_OPT_MTU="--mtu=1450" 75 | DOCKER_NETWORK_OPTIONS=" --bip=172.30.24.1/24 --ip-masq=true --mtu=1450" 76 | ``` 77 | 78 | 1. dockerd 启动后读取该环境变量,设置如下 POSTROUTING 规则: 79 | 80 | ``` bash 81 | [root@m7-devops-128071 ~]# iptables-save |grep POSTROUTING|grep -v '^:' 82 | -A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING 83 | -A POSTROUTING -s 172.30.24.0/24 ! -o docker0 -j MASQUERADE 84 | -A POSTROUTING -s 172.30.24.4/32 -d 172.30.24.4/32 -p tcp -m tcp --dport 3306 -j MASQUERADE 85 | -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE 86 | ``` 87 | 88 | 其中第二条规则:`-A POSTROUTING -s 172.30.24.0/24 ! -o docker0 -j MASQUERADE`,将来自本节点 POD 访问其他节点 POD 的请求做了 SNAT,即来源 IP 设置为节点的 flannel.1 地址;访问本节点 POD 的请求,因不匹配 ! -o docker0 规则,所以不会做 SNAT,来源 IP 为 POD IP; 89 | 90 | 解决办法: 91 | 92 | 1. 不能直接关闭 dockerd 的 `--ip-masq=true` 参数,否则 docker 容器无法访问除 Cluster 和 POD 外的 IP,如其他节点 IP、公网 IP 等; 93 | 1. 如果关闭 dockerd 的 `--ip-masq=true` 参数(通过修改 mk-docker-opts.sh 参数),则必须手动添加如下 SNAT 规则 (注意将 172.30.24.0/24 替换为节点对应的 POD 网段 CIDR): 94 | 95 | ``` bash 96 | -A POSTROUTING -s 172.30.24.0/24 ! -o docker0 -j my-chain1 97 | -A POSTROUTING -s 172.30.24.0/24 ! -o flannel.1 -j my-chain2 98 | -A my-chain1 ! -o flannel.1 -j MASQUERADE 99 | -A my-chain2 ! -o docker0 -j MASQUERADE 100 | ``` -------------------------------------------------------------------------------- /排错指南-Pod.md: -------------------------------------------------------------------------------- 1 | # 排错指南 - Pod 2 | 3 | 本文档介绍 Pod 的异常状态,可能原因和解决办法。 4 | 5 | 排查 Pod 异常的常用命令如下: 6 | 7 | + 查看 Pod 状态:`kubectl get pods -n -o wide` 8 | + 查看 Pod 的 yaml 配置:`kubectl get pods -n -o yaml` 9 | + 查看 Pod 的事件:`kubectl describe pods -n ` 10 | + 查看 Pod 容器日志:`kubectl logs -n [-c ]` 11 | 12 | ## Pod 一直处于 Pending 状态 13 | 14 | Pending 状态说明 Pod 还没有调度到某个 Node 上面。可以通过 `kubectl describe pods -n ` 命令查看到 Pod 的事件,进而判断为什么没有调度。如: 15 | 16 | ``` bash 17 | $ kubectl describe pod mypod 18 | ... 19 | Events: 20 | Type Reason Age From Message 21 | ---- ------ ---- ---- ------- 22 | Warning FailedScheduling 12s (x6 over 27s) default-scheduler 0/4 nodes are available: 2 Insufficient cpu. 23 | ``` 24 | 25 | 可能的原因和解决方案: 26 | 27 | 1. 集群的 kube-scheduler 服务都挂掉了: 28 | 29 | 可以在各节点运行 `ps -elf | grep kube-scheduler` 命令来验证。如果挂掉,可以使用 `systectl start kube-scheduler` 命令启动服务; 30 | 31 | 1. 节点没有打标签或打的标签值不匹配: 32 | 33 | 如 Pod 指定了 nodeSelector、nodeAffinity、podAffinity 或 AntiAffinity 等标签选择器,但没有节点打对应的标签或打的标值不匹配。 34 | 35 | 可以使用 `kubectl describe pods` 查看 Pod 的定义,使用 `kubectl get node --show-labels` 查看各 Node 的 lables 列表,使用 `kubectl label node lable_name=lable_value` 给 Node 打标签; 36 | 37 | 1. 节点资源不足: 38 | 39 | 集群内所有的 Node 都不满足该 Pod request 的 CPU、Memory、GPU 或者临时存储空间等资源,或者 Node 上的 Pod 数量达到了上限(kubelet 的 --max-pods 参数指定)。 40 | 41 | 可以通过 `kubectl describe node` 来查看各节点资源分配情况。然后通过删除不用的 Pod 来释放资源,或增加新的 Node 来增加资源。 42 | 43 | **案例**: 44 | 45 | 1. 创建的 Pod 一直处于 Pending 状态,kubectl describe pods 显示 `No nodes are available that match all of the predicates: Insufficient pods (3).` 46 | 47 | 原因: 48 | 49 | 1. kubelet 默认最多能运行 110 个 Pods: 50 | 51 | ``` txt 52 | $ kubectl describe node m7-devops-128071|grep -A 7 'Capacity' 53 | Capacity: 54 | cpu: 40 55 | memory: 264040352Ki 56 | pods: 110 57 | Allocatable: 58 | cpu: 40 59 | memory: 263937952Ki 60 | pods: 110 61 | ``` 62 | 63 | 当所有 kubelet 运行的 Pods 数达到 110 后,新创建的 Pod 就会因为资源不足而无法调度,提示 Insufficient pods; 64 | 65 | 解决方案: 66 | 67 | 1. kubelet 的 --max-pods 选项可以指定运行的最大 Pod 数目,通过调大该参数,如 110 → 240, 可以解决该问题。 68 | 1. 注意:因为 flanneld 配置的本节点 Pod 网段是 /24,所以一个 Node 最多运行 254 个 Pod(flannel、docker0 占用 2 个),--max-pods 不能超过该值; 69 | 70 | 1. 创建 Pod 基础容器 (sanbox) 失败: 71 | 72 | 现象: 73 | 74 | 1. 创建的 Pod 一直处于 Pending 状态; 75 | 76 | ``` bash 77 | [root@scriptk8c ~]# k get pods 78 | NAME READY STATUS RESTARTS AGE 79 | dnsuutils-ds-7q6dr 0/1 Pending 0 3s 80 | ``` 81 | 82 | 1. kubectl describe pod 显示创建 sandbox pod 失败: 83 | 84 | ``` bash 85 | [root@scriptk8c ~]# k describe pods dnsuutils-ds-7q6dr|tail 86 | Tolerations: node.alpha.kubernetes.io/notReady:NoExecute 87 | node.alpha.kubernetes.io/unreachable:NoExecute 88 | node.kubernetes.io/disk-pressure:NoSchedule 89 | node.kubernetes.io/memory-pressure:NoSchedule 90 | Events: 91 | Type Reason Age From Message 92 | ---- ------ ---- ---- ------- 93 | Normal SuccessfulMountVolume 2m kubelet, 172.27.129.237 MountVolume.SetUp succeeded for volume "default-token-jhdrm" 94 | Warning FailedCreatePodSandBox 25s (x8 over 2m) kubelet, 172.27.129.237 Failed create pod sandbox. 95 | Warning FailedSync 25s (x8 over 2m) kubelet, 172.27.129.237 Error syncing pod 96 | ``` 97 | 98 | 原因: 99 | 100 | 1. kubelet 基础容器配置参数:--pod-infra-container-image=docker02:35000/rhel7/pod-infrastructure:latest 101 | 1. 节点本地不存在这个 image,所以 kubelet 去 registry docker02:35000 拉取该镜像; 102 | 1. registry 需要登录认证,而 kubelet 没有提供认证信息,所以获取 pod-infra-container-image 失败,进而导致创建 sandbox 失败; 103 | 104 | 解决方案: 105 | 106 | 1. 从其他节点导出 pod-infra-container-image ,再导入到该节点,这样 kubelet 就不会去 registry 拉取镜像了; 107 | 108 | ``` bash 109 | $ docker save registry.access.redhat.com/rhel7/pod-infrastructure -o rhel.tar 110 | $ docker load -i rhel.tar 111 | ``` 112 | 113 | 1. 或者让 kublet 使用节点的 docker 认证信息去 registry 拉取镜像: 114 | 1. 在节点 root 账户下,执行 docker login docker02:35000 命令,输入账号、密码登录; 115 | 1. 修改 kubelet 配置文件,添加环境变量: HOME=root,重启 kubelet: 116 | 117 | ``` bash 118 | [root@scriptk8c ~]# grep HOME /mnt/disk01/opt/k8s/etc/kubernetes/kubelet 119 | HOME=/root 120 | 121 | [root@scriptk8c ~]# systemctl restart kubelet 122 | [root@scriptk8c ~]# 123 | ``` 124 | 125 | 注意:方案 2 适用于 k8s 1.8 版本,1.11 支持更多的认证方式; 126 | 127 | 参考: 128 | 129 | 1. https://v1-8.docs.kubernetes.io/docs/concepts/containers/images/ 130 | 1. https://kubernetes.io/docs/concepts/containers/images/#configuring-nodes-to-authenticate-to-a-private-repository 131 | 132 | 1. Pod 使用 HostNetwork,对应的端口(HostPort)在节点上已被占用: 133 | 134 | **案例**: 135 | 136 | 1. 集群中已经部署一个 node_exporter 的 Daemonset,它使用了 hostNetwork 和 hostPID ,端口为 9100。则再部署一个使用相同端口的 daemonset 时,一直不创建 pod(describe 时所有 field 均为 0): 137 | 138 | ``` bash 139 | [root@m7-devops-128123 log]# kubectl get ds |grep -E "NAME|node-exporter" 140 | NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE 141 | my-prom-prometheus-node-exporter 0 0 0 0 0 17m 142 | ``` 143 | 144 | 1. 查看所有 kube-xxx 组件的日志,均没有相关的异常日志。 145 | 146 | 原因: 147 | 148 | 1. Daemonset 由 DaemonSet Controller (非 scheduler )负责调度。如果 DS Controller 发现没有 Node 满足调度条件,如 Node 资源不足、** HostPort 端口冲突**等,就不会为 Node 创建 POD; 149 | 1. 本问题原因在于 Node_Exporter 使用了 hostNewtork,部署监听相同端口的 Daemonset 时出现 HostPort 端口冲突,故 K8S 不再创建 POD; 150 | 151 | 解决方案: 152 | 153 | 1. 换个 HostPort (Service 端口也需要更新) 或通常使用 Service 对外开放服务端口; 154 | 155 | ## Pod 一直处于 Waiting 或 ContainerCreating 状态 156 | 157 | 可能的原因和解决方案: 158 | 159 | 1. 创建 Pod 的基础容器 (sandbox) 失败,例如节点本地没有 pod-infrastructure 镜像,但是从 kubelet 拉取失败(如认证问题): 160 | 161 | 使用命令 `docker images |grep pod-infra` 确认节点 kubelet 参数 `--pod-infra-container-image` 对应的镜像,如 `registry.access.redhat.com/rhel7/pod-infrastructure:latest` 是否存在。如果不存在则手动拉取到节点(可能需要先登录 registry)。 162 | 163 | 1. Pod yaml 定义中请求的 CPU、Memory 太小或者**单位不对**,不足以成功运行 Sandbox。 164 | 165 | 常见的错误是: 166 | 1. Pod yaml 定义 request 或 limit Memory 值是**不带单位**(如 Gi, Mi, Ki 等)的数值,如 4,这时只分配和限制使用 4 bytes。 167 | 2. 内存单位 M 写成了小写 m,如 1024m,表示 1.024 Byte; 168 | 169 | `kubectl descirbe pod` 显示: 170 | 171 | Pod sandbox changed, it will be killed and re-created。 172 | 173 | kubelet 日志报错: 174 | 175 | to start sandbox container for pod ... Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused "process_linux.go:301: running exec setns process for init caused \"signal: killed\"": unknown 176 | 177 | 1. 拉取镜像失败: 178 | + 配置了错误的镜像; 179 | + Kubelet 无法访问镜像仓库(国内环境访问 gcr.io 需要特殊处理); 180 | + 拉取私有镜像的 imagePullSecret 没有配置或配置有误; 181 | + 镜像太大,拉取超时(可以适当调整 kubelet 的 --image-pull-progress-deadline 和 --runtime-request-timeout 选项); 182 | 183 | ## Pod 一直处于 ImagePullBackOff 状态 184 | 185 | 可能的原因和解决方案: 186 | 187 | 1. http 类型的 registry,但是没有添加到 dockerd 的 insecure-registry=172.27.129.211:35000 配置参数中: 188 | 189 | dockerd 默认从 https 类型的 registry 拉取镜像,如果使用 https 类型的 registry,则必须将它添加到 insecure-registry 参数中,然后重启或 reload dockerd 生效。 190 | 191 | 1. https 类型的 registry,但是使用自签名的 ca 证书,dockerd 不识别: 192 | 193 | 将 registry 加入到 insecure-registry 参数中,然后重启或 reload dockerd 生效。或者将它的 ca 证书放置到 `/etc/docker/certs.d//ca.crt` 位置; 194 | 195 | 1. registry 需要认证,但是 Pod 没有配置 imagePullSecret,配置的 Secret 不存在或有误: 196 | 197 | 首先创建一个 docker-registry 类型的 Secret: 198 | 199 | ``` bash 200 | $ kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL 201 | ``` 202 | 203 | 然后在容器中引用这个 Secret: 204 | 205 | ``` bash 206 | spec: 207 | containers: 208 | - name: private-reg-container 209 | image: 210 | imagePullSecrets: 211 | - name: my-secret 212 | ``` 213 | 214 | 1. 镜像文件损坏,需要重新 push 镜像文件 215 | 216 | kubectl describe pod 报错: 217 | 218 | Failed to pull image "docker02:35000/env/release/3.2.0/prophet/app/executor-service-kube.tar:release-3.2.0-8": rpc error: code = Unknown desc = error pulling image configuration: unknown blob 219 | 220 | 1. kubelet 的 --registry-qps、--registry-burst 值太小(默认分别为 5,10),并发拉取镜像时被限制,kubectl describe pod 报错: 221 | 222 | Failed to pull image "172.27.129.211:35000/metricbeat-prophet:6.0.1": pull QPS exceeded,这时,可以调大这两个参数,然后重启 kubelet 解决。 223 | 224 | --registry-qps 太小导致的并发限制案例: 225 | 226 | ``` bash 227 | [root@m7-power-k8s01 ~]# kubectl get pods -n env30 -o wide alert-engine-alert-engine-54fd454f64-clbd7 228 | NAME READY STATUS RESTARTS AGE IP NODE 229 | alert-engine-alert-engine-54fd454f64-clbd7 0/3 ImagePullBackOff 0 59m 172.30.208.111 m7-power-k8s03 230 | 231 | [root@m7-power-k8s01 ~]# kubectl describe pods -n env30 alert-engine-alert-engine-54fd454f64-clbd7 | tail -3 232 | Warning Failed 44m (x2 over 46m) kubelet, m7-power-k8s03 Failed to pull image "172.27.129.211:35000/filebeat-with-module:6.0.0": pull QPS exceeded. 233 | Normal BackOff 5m (x83 over 51m) kubelet, m7-power-k8s03 Back-off pulling image "172.27.129.211:35000/filebeat-with-module:6.0.0" 234 | Normal Pulling 36s (x13 over 51m) kubelet, m7-power-k8s03 pulling image "172.27.129.211:35000/filebeat-with-module:6.0.0" 235 | ``` 236 | 237 | ## Pod 一直处于 ImageInspectError 状态 238 | 239 | 现象: 240 | 241 | 1. 启动 Pod 失败,kubectl get pods 显示的 STATUS 为 "ImageInspectError" 242 | 243 | ``` bash 244 | [root@m7-devops-128071 gitlab]# kubectl get pods --all-namespaces|grep Inspect 245 | fangrong pms-558b58dfbd-fpjzl 0/1 ImageInspectError 0 13h 246 | prophet-resource-automl pas-08a7e677-5c4e-4e2d-992f-7d93cd0f05d5-automl-544ff774b7xgh88 0/1 ImageInspectError 0 13h 247 | prophet-resource-automl pas-1ccab6a2-0415-4542-8ccf-348dd28451c4-automl-6c6bd6f4644sqpw 0/1 ImageInspectError 0 13h 248 | qatest312 pms-b97bc97fc-5djfl 0/1 ImageInspectError 0 6h 249 | ``` 250 | 251 | 1. kubectl describe pod 显示,ImageInspectError 的原因为 readlink /mnt/disk0/docker/data/overlay2: invalid argument: 252 | 253 | ``` bash 254 | [root@m7-devops-128071 gitlab]# kubectl describe pods -n fangrong pms-558b58dfbd-fpjzl|tail -2 255 | Warning FailedSync 1h (x522 over 13h) kubelet, m7-devops-128107 Error syncing pod 256 | Warning InspectFailed 3m (x590 over 13h) kubelet, m7-devops-128107 Failed to inspect image "docker02:35000/env/develop/prophet/app/pms.tar:develop-175": rpc error: code = Unknown desc = Error response from daemon: readlink /mnt/disk0/docker/data/overlay2: invalid argument 257 | ``` 258 | 259 | 1. 打开 dockerd 的 debug 日志级别,查看对应的日志显示 在 readlink /mnt/disk0/docker/data/overlay2 出现了 os.PathError 错误: 260 | 261 | ``` bash 262 | 13 06:00:04 m7-devops-128107 dockerd[34157]: time="2018-08-13T06:00:04.061634901+08:00" level=debug msg="FIXME: Got an API for which error does not match any expected type!!!: readlink /mnt/disk0/docker/data/overlay2: invalid argument" error_type="*os.PathError" module=api 263 | 8月 13 06:00:04 m7-devops-128107 dockerd[34157]: time="2018-08-13T06:00:04.061670830+08:00" level=error msg="Handler for GET /v1.31/images/docker02:35000/grafana/grafana-enhanced:5.2.0/json returned error: readlink /mnt/disk0/docker/data/overlay2: invalid argument" 264 | 8月 13 06:00:04 m7-devops-128107 dockerd[34157]: time="2018-08-13T06:00:04.061704408+08:00" level=debug msg="FIXME: Got an API for which error does not match any expected type!!!: readlink /mnt/disk0/docker/data/overlay2: invalid argument" error_type="*os.PathError" module=api 265 | ``` 266 | 267 | 原因: 268 | 269 | 1. 节点上 docker 镜像文件损坏,当使用它启动容器后,容器文件系统错误,进而导致系统调用 readlink() 返回 os.PathError 错误; 270 | 1. dockerd 不能正确处理这个 Error https://github.com/allencloud/docker/blob/master/api/server/httputils/errors.go#L65,所以提示 FIXME: Got an API for which error does not match any expected type!!! 271 | 1. image 文件损坏可能与重启服务器导致的文件系统不完整有关,可以使用 fsck 命令修复文件系统; 272 | 273 | 验证节点上镜像文件损坏的步骤: 274 | 275 | 1. 在该节点上使用 image 起容器,结果启动失败: 276 | 277 | ``` bash 278 | [root@m7-devops-128107 ~]# docker run -it docker02:35000/env/develop/prophet/app/pms.tar:develop-175 sh 279 | docker: Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused "exec: \"/bin/bash\": stat /bin/bash: no such file or directory": unknown. 280 | [root@m7-devops-128107 ~]# 281 | ``` 282 | 283 | 1. 或者,导出 image 镜像失败,提示文件完整性校验出错: 284 | 285 | ``` bash 286 | [root@m7-devops-128107 ~]# docker save docker02:35000/env/develop/prophet/app/pms.tar:develop-175 -o pms.img 287 | Error response from daemon: file integrity checksum failed for "etc/anacrontab" 288 | ``` 289 | 290 | 解决方案: 291 | 292 | 1. 删除节点上所有使用损坏 image 的容器,否则不能删除 image: 293 | 294 | ``` bash 295 | [root@m7-devops-128107 ~]# docker ps -a|grep pms.tar 296 | ecdad07d835b docker02:35000/env/develop/prophet/app/pms.tar:develop-175 "/bin/bash /opt/work…" About a minute ago Created competent_meninsky 297 | f9c805e91ac7 docker02:35000/env/develop/prophet/app/pms.tar:develop-175 "/bin/bash /opt/work…" 2 minutes ago Created hopeful_stonebraker 298 | 63b6a12efc3e docker02:35000/env/develop/prophet/app/pms.tar:develop-175 "/bin/bash /opt/work…" 8 minutes ago Created confident_wright 299 | [root@m7-devops-128107 ~]# docker rm ecdad07d835b # 删除所有使用损坏的 pms.tar 镜像的容器 300 | ecdad07d835b 301 | [root@m7-devops-128107 ~]# docker rm f9c805e91ac7 302 | f9c805e91ac7 303 | [root@m7-devops-128107 ~]# docker rm 63b6a12efc3e 304 | 63b6a12efc3 305 | ``` 306 | 307 | 1. 删除节点上损坏的 image (必须要有 Deleted: sha256 开头的输出结果,才表明实际删除了 image layer 文件,否则需要删除使用它的容器后再删除 image): 308 | 309 | ``` bash 310 | [root@m7-devops-128107 ~]# docker rmi docker02:35000/env/develop/prophet/app/pms.tar:develop-175 311 | ``` 312 | 313 | 1. 重新拉取 image 文件: 314 | 315 | ``` bash 316 | [root@m7-devops-128107 ~]# docker pull docker02:35000/env/develop/prophet/app/pms.tar:develop-175 317 | ``` 318 | 319 | 参考: 320 | 321 | + https://github.com/allencloud/docker/blob/master/api/server/httputils/errors.go#L65 322 | + https://github.com/kubernetes/kubernetes/issues/63612 323 | 324 | ## Pod 一直处于 CrashLoopBackOff 状态 325 | 326 | CrashLoopBackOff 状态说明容器曾经启动了,但又异常退出了。此时 Pod 的 RestartCounts 通常是大于 0 的,可以先查看一下容器的日志: 327 | 328 | ``` bash 329 | kubectl describe pod -n 330 | kubectl logs -n [-c container_name] 331 | kubectl logs --previous -n [-c container_name] 332 | ``` 333 | 334 | 从 describe pod 的 State、Last State 里,以及容器日志可以发现一些容器退出的原因,比如: 335 | 336 | + 容器进程退出,如域名解析失败、连接数据库失败; 337 | + 健康检查失败退出 338 | + OOMKilled 339 | + 镜像文件损坏 340 | 341 | ``` bash 342 | $ kubectl describe pod mypod 343 | ... 344 | Containers: 345 | sh: 346 | Container ID: docker://3f7a2ee0e7e0e16c22090a25f9b6e42b5c06ec049405bc34d3aa183060eb4906 347 | Image: alpine 348 | Image ID: docker-pullable://alpine@sha256:7b848083f93822dd21b0a2f14a110bd99f6efb4b838d499df6d04a49d0debf8b 349 | Port: 350 | Host Port: 351 | State: Terminated 352 | Reason: OOMKilled 353 | Exit Code: 2 354 | Last State: Terminated 355 | Reason: OOMKilled 356 | Exit Code: 2 357 | Ready: False 358 | Restart Count: 3 359 | Limits: 360 | cpu: 1 361 | memory: 1G 362 | Requests: 363 | cpu: 100m 364 | memory: 500M 365 | ... 366 | ``` 367 | 368 | 如果此时如果还未发现线索,还可以到容器内执行命令来进一步查看退出原因: 369 | 370 | ``` bash 371 | $ kubectl exec cassandra -- cat /var/log/cassandra/system.log 372 | ``` 373 | 374 | 如果还是没有线索,那就需要 SSH 登录该 Pod 所在的 Node 上,查看 Kubelet 或者 Docker 的日志进一步排查了: 375 | 376 | ``` bash 377 | # Query Node 378 | kubectl get pod -o wide 379 | 380 | # SSH to Node 381 | ssh @ 382 | 383 | # Query kubelet log 384 | journalctl -u kubelet 385 | 386 | # Query docker log 387 | journalctl -u docker 388 | ``` 389 | 390 | **案例一**: 391 | 392 | 1. Pod 启动失败,kubectl get pods 显示状态为 CrashLoopBackOff,kubectl describe pods 显示错误信息:read-only file system error 393 | 394 | 现象: 395 | 396 | 1. Pod 启动失败,kubectl get pods 显示状态为 CrashLoopBackOff: 397 | 398 | ``` bash 399 | [root@m7-devops-128071 gitlab]# kubectl get pods --all-namespaces -o wide|grep 128123|grep -v Running|grep pas 400 | prophet-resource-qa312 pas-7e9f06b0-6f42-4c40-b9a9-eb3471ee824b-predictor-7789d7bjqzdw 0/2 CrashLoopBackOff 5 3m 172.30.37.27 m7-devops-128123 401 | prophet-resource-qa312 pas-c7917262-fd06-4f98-86bf-96bf3813e5ec-predictor-765c577zp6jl 0/2 CrashLoopBackOff 4 3m 172.30.37.16 m7-devops-128123 402 | prophet-resource-qa312 pas-d12af6cb-3659-48b3-b2d7-f34fc9536c23-online-dataload-7455d7 0/1 ImagePullBackOff 0 14m 172.30.37.29 m7-devops-128123 403 | ``` 404 | 405 | 1. kubectl describe pods 显示 mkdir 时返回 read-only file system error: 406 | 407 | ``` bash 408 | E0813 14:55:53.024865 3777 remote_runtime.go:209] StartContainer "d41d314a85af6eb8c7e5e" from runtime service failed: rpc error: 409 | code = Unknown desc = failed to start container "d41d314a85af6eb8c7e5ec38ff89": Error response from daemon: OCI runtime create failed: 410 | container_linux.go:348: starting container process caused 411 | "process_linux.go:402: container init caused \"rootfs_linux.go:58: mounting \\\"/mnt/disk0/k8s/kubelet/pods/ea9035f3-9ec5-11e8-af9c-0cc47adb93d8/volumes/kubernetes.io~empty-dir/log-entry--predictor--1\\\" to rootfs \\\"/mnt/disk0/docker/data/overlay2/962a78785442dc45bdf95e/merged\\\" 412 | at \\\"/mnt/disk0/docker/data/overlay2/962a78785442dc45b0f5/merged/collect/predictor/root/predictor/logs/predictor\\\" caused 413 | \\\"mkdir /mnt/disk0/docker/data/overlay2/962a78785442dc495e/merged/collect/predictor/root/predictor/logs/predictor: 414 | read-only file system\\\"\"": unknown 415 | ``` 416 | 417 | 原因: 418 | 419 | 1. 节点本地 docker image 文件损坏,当使用它启动容器后,容器文件系统错误,进而导致被只读挂载(ro); 420 | 1. 或者,docker pull image 时出错(提示 error pulling image configuration: unknown blob),导致挂载时文件系统错误; 421 | 422 | 解决方案: 423 | 424 | 1. 删除节点上所有使用损坏 image 的容器,然后删除 image,再重新 pull image; 425 | 1. 确认 registry 中的 image 文件是否完整,不完整时重新 push image; 426 | 427 | **案例二**: 428 | 429 | 现象: 430 | 431 | 1. Pod 启动失败,kubectl get pods 显示状态为 CrashLoopBackOff: 432 | 433 | ``` bash 434 | $ kubectl get pods -n metricbeat -o wide |grep m7-power-128050 435 | metricbeat-995dcffbd-6rppf 0/1 CrashLoopBackOff 1142 9d 172.30.168.11 m7-power-128050 436 | ``` 437 | 1. kubectl describe pods 显示 docker 将 pod 的 kubelet 目录 mount 到容器目录中时提示 no such file or directory: 438 | 439 | ``` bash 440 | $ kubectl describe pods -n metricbeat metricbeat-995dcffbd-6rppf | tail -10 441 | Warning Failed 4d kubelet, m7-power-128050 Error: failed to start container "metricbeat": Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused "process_linux.go:402: container init caused \"rootfs_linux.go:58: mounting \\\"/mnt/disk2/k8s/kubelet/pods/54d426d3-cacd-11e8-971a-5e384b278319/volume-subpaths/config/metricbeat/0\\\" to rootfs \\\"/mnt/disk1/docker/data/overlay2/5753fb64509f490968802fe00a6a6e000b7f17f4839d62ba5ca1dc484c86ba22/merged\\\" at \\\"/mnt/disk1/docker/data/overlay2/5753fb64509f490968802fe00a6a6e000b7f17f4839d62ba5ca1dc484c86ba22/merged/etc/metricbeat.yml\\\" caused \\\"no such file or directory\\\"\"": unknown 442 | Warning Failed 4d kubelet, m7-power-128050 Error: failed to start container "metricbeat": Error response from daemon: OCI runtime create failed: container_linux.go:348: 443 | Normal Pulled 43m (x1135 over 9d) kubelet, m7-power-128050 Container image "docker.elastic.co/beats/metricbeat:6.4.1" already present on machine 444 | Warning FailedSync 23m (x26664 over 4d) kubelet, m7-power-128050 Error syncing pod 445 | Warning BackOff 3m (x25616 over 4d) kubelet, m7-power-128050 Back-off restarting failed container 446 | ``` 447 | 448 | 原因:容器已经挂了,但是 kubelet 还不知晓,导致 kubelet 将 pod 目录挂载到容器目录时,容器目录不存在。 449 | 450 | 解决办法:删除 pod,然后自动重建。 451 | 452 | ## Pod 一直处于 Error 状态 453 | 454 | 通常处于 Error 状态说明 Pod 启动过程中发生了错误。常见的原因包括: 455 | 456 | + 依赖的 ConfigMap、Secret 或者 PV 等不存在 457 | + 请求的资源超过了管理员设置的限制,比如超过了 LimitRange 等 458 | + 违反集群的安全策略,比如违反了 PodSecurityPolicy 等 459 | + 容器无权操作集群内的资源,比如开启 RBAC 后,需要为 ServiceAccount 配置角色绑定 460 | 461 | ## Pod 一直处于 Terminating 或 Unknown 状态 462 | 463 | 正常情况下,如果删除了 Pod,经过一个 grace period(默认 30s)后,如果 Pod 还在 Running,则 kublet 会向 docker 发送 kill 命令,进而 docker 向 Pod 中的所有进程发送 SIGKILL 信号,强行删除 Pod。所以,如果节点工作正常,一般一个 grace period 后,Pod 会被清除。 464 | 465 | 如果节点失联 NotReady,默认 5min 后,node controller 开始驱逐它上面的 Pods,即将该 Node 上的 Pod 标记为 Terminating 状态,然后在其它节点上再起 Pod。从 v1.5 开始,node controller 不再从 etcd 中强行删除(force delete)失联 Node 上的 Pod 信息,而是等待节点恢复连接后,确认驱逐的 Pod 都已经 Terminating 后才删除这些 Pods。所以,这一段时间内,Pod 可能有多副本运行的情况。想要删除 NotReady 节点上的 Terminating 或 Unknown 状态 Pod 的方法: 466 | 467 | + 从集群中删除该 Node:kubectl delete node 468 | + Node 恢复正常。Kubelet 会重新跟 kube-apiserver 通信确认这些 Pod 的期待状态,进而再删除这些 Pod。 469 | + 用户强制删除。用户可以执行 kubectl delete pods --grace-period=0 --force 强制删除 Pod。除非明确知道 Pod 的确处于停止状态(比如 Node 所在 VM 或物理机已经关机),否则不建议使用该方法。特别是 StatefulSet 管理的 Pod,强制删除容易导致脑裂或者数据丢失等问题。 470 | 471 | 处于 Terminating 状态的 Pod 在 Kubelet 恢复正常运行后一般会自动删除。但有时也会出现无法删除的情况,并且通过 kubectl delete pods --grace-period=0 --force 也无法强制删除。此时一般是由于 finalizers 导致的,通过 kubectl edit 将 finalizers 删除即可解决。 472 | 473 | ``` bash 474 | "finalizers": [ 475 | "foregroundDeletion" 476 | ] 477 | ``` 478 | 479 | 另一种导致删除的 Pod 一直处于 Terminating 状态的原因:Pod 业务容器和 Sandbox 都被正常删除,但是 kubelet 在 umount Pod 挂载的目录时一直失败,提示 device or resource busy,进而导致 Pod 的状态一直是 Terminating: 480 | 481 | ``` bash 482 | 9月 22 20:04:07 ee-test kubelet[3583]: E0922 20:04:07.711666 3583 nestedpendingoperations.go:264] Operation for "\"kubernetes.io/configmap/c74b89dd-be53-11e8-a1f4-525400f721a0-filebeat-config\" (\"c74b89dd-be53-11e8-a1f4-525400f721a0\")" failed. No retries permitted until 2018-09-22 20:04:08.711632504 +0800 CST (durationBeforeRetry 1s). Error: error cleaning subPath mounts for volume "filebeat-config" (UniqueName: "kubernetes.io/configmap/c74b89dd-be53-11e8-a1f4-525400f721a0-filebeat-config") pod "c74b89dd-be53-11e8-a1f4-525400f721a0" (UID: "c74b89dd-be53-11e8-a1f4-525400f721a0") : error deleting /mnt/disk01/k8s/lib/kubelet/pods/c74b89dd-be53-11e8-a1f4-525400f721a0/volume-subpaths/filebeat-config/filebeat/1: remove /mnt/disk01/k8s/lib/kubelet/pods/c74b89dd-be53-11e8-a1f4-525400f721a0/volume-subpaths/filebeat-config/filebeat/1: device or resource busy 483 | ``` 484 | 485 | 原因: 486 | 1. CentOS 3.10 内核以及 17.12.1 以前版本的 docker 在 umount 时可能会出现 device busy 情况; 487 | 488 | 解决方案: 489 | 490 | 1. 升级 docker-ce 到 17.12.1 及以上版本; 491 | 1. 升级操作系统内核到 4.4.x; 492 | 493 | 参考: 494 | 495 | + https://github.com/moby/moby/issues/22260 496 | + https://github.com/kubernetes/kubernetes/issues/65110 497 | 498 | ## Pod 状态长时间(小时级别)不更新,一直处于 Creating、Terminating 状态 499 | 500 | 现象: 501 | 502 | 1. 创建、删除或重建 Pod 后,等待很长时间(小时级别),kubectl get pods 显示 Pod 的 Status 一直是 Creating、Terminating 状态,进而导致 Service 长时间不可用; 503 | 1. 在 Pod 调度到的节点上,执行 docker ps |grep xxx 命令,可以看到 Creating 的 Pod 容器已实际 Running 一段时间了; 504 | 505 | 原因: 506 | 507 | 1. kubelet 周期(1s)向 docker 查询各容器的状态、配置参数、image 是否存在、image 版本等信息; 508 | 1. kubelet 将查询的结果发给 kube-apiserver,以更新 Pod 的状态,这个发送过程受多个 QPS 参数控制; 509 | 1. kubelet 的 QPS 配置参数: --event-qps 、--kube-api-qps、--event-burst、--kube-api-burst 默认值分别为 5、5、10、10 ; 510 | 1. 当节点 Pod 数目过多(200+),Pod 状态更新时间非常频繁时,kubelet 的默认 QPS 值成为瓶颈,导致 Pod 状态不能及时更新到 kube-apiserver。查看 Pod 的 Status时, 一直处于 Creating、Terminating 状态,进而导致 Service 长时间不可用; 511 | 512 | 解决办法: 513 | 514 | 1. 调大 kubelet 的 QPS 配置参数: 515 | 516 | ``` bash 517 | --event-qps=0 518 | --kube-api-qps=2000 519 | --kube-api-burst=4000 520 | --registry-qps=0 521 | ``` 522 | 523 | 参考: 524 | 525 | 1. https://github.com/kubernetes/kubernetes/issues/39113#issuecomment-305919878 526 | 527 | ## Pod 行为异常 528 | 529 | 这里所说的行为异常是指 Pod 没有按预期的行为执行,比如没有运行 podSpec 里面设置的命令行参数。这一般是 podSpec yaml 文件内容有误,可以尝试使用 --validate 参数重建容器,比如: 530 | 531 | ``` bash 532 | kubectl delete pod mypod 533 | kubectl create --validate -f mypod.yaml 534 | ``` 535 | 536 | 也可以查看创建后的 podSpec 是否是对的,比如 537 | 538 | ```bash 539 | kubectl get pod mypod -o yaml 540 | ``` 541 | 542 | ## 修改静态 Pod 的 Manifest 后未自动重建 543 | 544 | Kubelet 使用 inotify 机制检测 /etc/kubernetes/manifests 目录(可通过 Kubelet 的 --pod-manifest-path 选项指定)中静态 Pod 的变化,并在文件发生变化后重新创建相应的 Pod。但有时也会发生修改静态 Pod 的 Manifest 后未自动创建新 Pod 的情景,此时一个简单的修复方法是重启 Kubelet。 545 | 546 | ## 参考文档 547 | 548 | + [Troubleshoot Applications](https://kubernetes.io/docs/tasks/debug-application-cluster/debug-application/) 549 | + [Pod 异常排错](https://feisky.gitbooks.io/kubernetes/zh/troubleshooting/pod.html) 550 | --------------------------------------------------------------------------------