问题定位流程图
TL;DR:这里有一个流程图可以帮助你调试 Kubernetes 中的部署
当你希望在 Kubernetes 中部署一个应用程序时,通常需要定义三个组件:
- Deployment(部署) — 用于创建应用程序副本的配置。
- Service(服务) — 内部负载均衡器,将流量路由到 Pods。
- Ingress(入口) — 描述流量从集群外部到达 Service 的方式。
这里是一个快速预览。
部署简单的 Hello World 应用程序的 YAML 配置示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
labels:
track: canary
spec:
selector:
matchLabels:
any-name: my-app
template:
metadata:
labels:
any-name: my-app
spec:
containers:
- name: cont1
image: ghcr.io/learnk8s/app:1.0.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- port: 80
targetPort: 8080
selector:
name: app
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- http:
paths:
- backend:
service:
name: my-service
port:
number: 80
path: /
pathType: Prefix
这个定义比较长,很容易忽视组件之间的关系。
例如:
- 什么时候使用端口80,什么时候使用端口8080?
- 是否应该为每个 Service 创建新的端口以避免冲突?
- 标签名称是否重要?是否应该在各处保持相同?
在专注于调试之前,让我们回顾一下这三个组件之间的关系。
Deployment和Service的连接
令人惊讶的是,Service 和 Deployment 并没有直接连接。
相反,Service 直接指向 Pods,跳过了 Deployment。
因此,你应该注意的是 Pods 和 Services 之间的关系。
你应该记住三件事:
- Service 的选择器(selector)应该至少匹配一个 Pod 的标签。
- Service 的
targetPort
应该与 Pod 的containerPort
匹配。 - Service 的
port
可以是任何数字。多个 Service 可以使用相同的端口,因为它们具有不同的 IP 地址。
下图总结了如何连接端口:
如果查看 YAML,标签和 ports/targetPort
应该匹配。
yamlCopy code
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
labels:
track: canary
spec:
selector:
matchLabels:
any-name: my-app
template:
metadata:
labels:
any-name: my-app
spec:
containers:
- name: cont1
image: ghcr.io/learnk8s/app:1.0.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- port: 80
targetPort: 8080
selector:
any-name: my-app
关于 Deployment 顶部的 track: canary
标签呢?是否也应该匹配?
该标签属于 Deployment,Service 的选择器不使用它来路由流量。
换句话说,你可以安全地删除它或为其分配不同的值。
那么 matchLabels
选择器呢?
它始终必须匹配 Pod 的标签,并且它由 Deployment 用于跟踪 Pods。
假设你进行了正确的更改,该如何测试呢?
你可以使用以下命令检查 Pods 是否具有正确的标签:
kubectl get pods --show-labels
NAME READY STATUS LABELS
my-deployment-pv6pd 1/1 Running any-name=my-app,pod-template-hash=7d6979fb54
my-deployment-f36rt 1/1 Running any-name=my-app,pod-template-hash=7d6979fb54
并且可以查看pod的端口
kubectl get pod <pod name> --template='{{(index (index .spec.containers 0).ports 0).containerPort}}{{"\n"}}'
或者如果你有属于多个应用程序的 Pods:
kubectl get pods --selector any-name=my-app --show-labels
其中 any-name=my-app
是标签 any-name: my-app
。
仍然遇到问题?
你还可以连接到 Pod!
你可以使用 kubectl
中的 port-forward
命令连接到 Service 并测试连接:
kubectl port-forward service/<service name> 3000:80
Forwarding from 127.0.0.1:3000 -> 80
Forwarding from [::1]:3000 -> 80
其中:
service/<service name>
是 Service 的名称 — 在当前的 YAML 中是 "my-service"。3000
是你希望在计算机上打开的端口。80
是 Service 在端口字段中暴露的端口。
如果你可以连接,那么设置是正确的。
如果不能,你很可能放错了标签或端口不匹配。
连接 Service 和 Ingress
将应用程序暴露给外部的下一步是配置 Ingress。
Ingress 必须知道如何找到 Service,然后连接 Pods 并路由流量。
Ingress 通过名称和暴露的端口检索正确的Service。
Ingress 和 Service 中应该匹配两个要素:
- Ingress 的
service.port
应该与 Service 的端口匹配。 - Ingress 的
service.name
应该与 Service 的名称匹配。
以下图表总结了如何连接端口:
在实践中,你应该查看以下几行:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- port: 80
targetPort: 8080
selector:
any-name: my-app
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- http:
paths:
- backend:
service:
name: my-service
port:
number: 80
path: /
pathType: Prefix
如何测试 Ingress 是否正常工作呢?
你可以使用与之前相同的策略,使用 kubectl port-forward
,但是连接到 Ingress controller 而不是连接到 Service。
首先,使用以下命令检索 Ingress controller 的 Pod 名称:
kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS
kube-system coredns-5644d7b6d9-jn7cq 1/1 Running
kube-system etcd-minikube 1/1 Running
kube-system kube-apiserver-minikube 1/1 Running
kube-system kube-controller-manager-minikube 1/1 Running
kube-system kube-proxy-zvf2h 1/1 Running
kube-system kube-scheduler-minikube 1/1 Running
kube-system nginx-ingress-controller-6fc5bcc 1/1 Running
识别 Ingress Pod(可能位于不同的 Namespace 中),并使用描述命令检索其端口:
kubectl describe pod nginx-ingress-controller-6fc5bcc --namespace kube-system | grep Ports
Ports: 80/TCP, 443/TCP, 18080/TCP
最后,连接到 Pod:
kubectl port-forward nginx-ingress-controller-6fc5bcc 3000:80 --namespace kube-system
Forwarding from 127.0.0.1:3000 -> 80
Forwarding from [::1]:3000 -> 80
其中:
nginx-ingress-controller-6fc5bcc
是 Ingress controller 的名称。3000
是你希望在计算机上打开的端口。80
是 Ingress controller 在端口字段中暴露的端口。
此时,每当你访问计算机上的端口3000时,请求都会转发到 Pod 上的端口80。
如果现在可以工作,问题就在基础设施中。你应该调查流量如何路由到你的集群。
如果仍然不起作用,问题就在 Ingress controller 中。你应该调试 Ingress。
如果仍然无法使 Ingress controller 正常工作,你应该开始调试它。
有许多不同版本的 Ingress controllers。
常见的选项包括 Nginx、HAProxy、Traefik 等。
你应该查阅 Ingress controller 的文档,找到调试指南。
由于 Ingress Nginx 是最流行的 Ingress controller,我们在下一节中包含了一些关于它的提示。
关于端口和标签应匹配的简要回顾:
- Service Selector 应与 Pod 的标签匹配。
- 服务
targetPort
应与 Pod 内容器中的containerPort
匹配。 - Service 端口可以是任意数字。多个服务可以使用相同的端口,因为它们具有不同的分配的 IP 地址。
- Ingress 的
service.port
应与服务中的port
匹配。 - Service的名称应与 Ingress 中的
service.name
字段匹配。
了解如何构建你的 YAML 定义只是故事的一部分。
当出现问题时会发生什么?
也许 Pod 没有启动,或者它正在崩溃。
调试 Kubernetes 部署的三个步骤
在深入调试损坏的部署之前,了解 Kubernetes 如何工作的心理模型至关重要。
由于每个部署中有三个组件,你应该按顺序调试它们,从底层开始。
-
确保你的 Pods 正在运行
-
关注将流量路由到 Pods 的 Service
-
检查 Ingress 是否正确配置。
1.调试 Pods
大多数情况下,问题出现在 Pod 本身。
你应该确保你的 Pods 处于运行状态并准备就绪。
如何检查呢?
kubectl get pods
NAME READY STATUS RESTARTS AGE
app1 0/1 ImagePullBackOff 0 47h
app2 0/1 Error 0 47h
app3-76f9fcd46b-xbv4k 1/1 Running 1 47h
在上面的输出中,最后一个 Pod 正在运行并准备就绪 。然而,前两个 Pod 既不在运行状态也不准备就绪。
如何调查出错原因?
有四个有用的命令用于调试 Pods:
kubectl logs <pod name>
有助于检索 Pod 中容器的日志。kubectl describe pod <pod name>
有助于检索与 Pod 关联的事件列表。kubectl get pod <pod name>
有助于提取存储在 Kubernetes 中的 Pod 的 YAML 定义。kubectl exec -ti <pod name> -- bash
有助于在 Pod 的容器中运行交互式命令。
应该使用哪一个?
并没有一种适用于所有情况的方法。相反,你应该组合使用它们。
常见的 Pods 错误
Pods 可能会出现启动和运行时错误。
启动错误包括:
- ImagePullBackoff
- ImageInspectError
- ErrImagePull
- ErrImageNeverPull
- RegistryUnavailable
- InvalidImageName
运行时错误包括:
- CrashLoopBackOff
- RunContainerError
- KillContainerError
- VerifyNonRootError
- RunInitContainerError
- CreatePodSandboxError
- ConfigPodSandboxError
- KillPodSandboxError
- SetupNetworkError
- TeardownNetworkError
有些错误比其他错误更常见。
以下是最常见错误的列表以及如何修复它们。
ImagePullBackOff
当 Kubernetes 无法检索 Pod 中一个容器的镜像时,会出现此错误。
有三个常见的原因:
- 镜像名称无效 。 例如,你拼写错误了名称,或者镜像不存在。
- 为镜像指定了不存在的标签。
- 你尝试检索的镜像属于私有注册表,而 Kubernetes 没有访问它的权限凭据。
前两种情况可以通过纠正镜像名称和标签来解决。
对于最后一种情况,你应该将凭据添加到私有注册表中的一个 Secret,并在 Pods 中引用它。官方文档中有一个关于如何做到这一点的例子。
CrashLoopBackOff
如果容器无法启动,Kubernetes 会显示 CrashLoopBackOff 消息作为状态。
通常,容器无法启动有以下的情况:
- 应用程序中有阻止启动的错误。
- 你配置容器时出现错误。
- 存活性探测失败了太多次。
你应该尝试从该容器检索日志以查明失败的原因。
如果由于容器重新启动太快而看不到日志,你可以使用以下命令:
kubectl logs <pod-name> --previous
它会打印前一个容器的错误消息。
RunContainerError
当容器无法启动时,就会出现这个错误。
这甚至发生在容器内部的应用程序启动之前。
问题通常是由于错误配置,例如:
- 挂载不存在的卷,如 ConfigMap 或 Secrets。
- 将只读卷挂载为可读写。
你应该使用 kubectl describe pod <pod-name>
来检查和分析错误。
处于 Pending 状态的 Pods
当创建一个 Pod 时,Pod 保持在 Pending 状态。
为什么呢?
假设你的调度器组件运行正常,原因可能有:
- 集群没有足够的资源,如 CPU 和内存,来运行 Pod。
- 当创建 Pod 会使命名空间超出配额时,当前命名空间有一个 ResourceQuota 对象。
- Pod 绑定到一个处于 Pending 状态的 PersistentVolumeClaim。
你最好的选择是检查 kubectl describe
命令中的事件部分:
kubectl describe pod <pod name>
对于由于 ResourceQuotas 导致的错误,你可以使用以下命令检查集群的日志:
kubectl get events --sort-by=.metadata.creationTimestamp
查看节点剩余的资源配额
kubectl describe nodes
处于未就绪状态的 Pods
如果一个 Pod 正在运行但未就绪,这意味着 Readiness 探测失败。
当 Readiness 探测失败时,Pod 不会附加到 Service,并且不会将任何流量转发到该实例。
失败的 Readiness 探测是特定于应用程序的错误,因此你应该检查 kubectl describe
中的事件部分以识别错误。
2.调试Service
如果你的 Pods 正在运行并准备就绪,但仍然无法从你的应用程序收到响应,你应该检查Service是否配置正确。
Service 被设计为根据它们的标签将流量路由到 Pods。
因此,你应该首先检查的是Service针对多少个 Pods 进行了配置。
你可以通过检查服务中的 Endpoints 来实现:
kubectl describe service my-service
Name: my-service
Namespace: default
Selector: app=my-app
IP: 10.100.194.137
Port: <unset> 80/TCP
TargetPort: 8080/TCP
Endpoints: 172.17.0.5:8080
一个 Endpoint 是一对 <ip address:port>
,并且至少应该有一个,当服务至少针对一个 Pod 时。
如果 "Endpoints" 部分为空,有两种解释:
- 你没有任何以正确标签运行的 Pod(提示:你应该检查你是否在正确的命名空间中)。
- 服务的
selector
标签中有拼写错误。
如果你看到一列 Endpoints,但仍然无法访问你的应用程序,那么服务中的 targetPort 可能是问题所在。
如何测试Service?
无论服务的类型如何,你都可以使用 kubectl port-forward
来连接它:
kubectl port-forward service/<service-name> 3000:80
# 或者不想指定端口想随机分配,可以使用以下命令
kubectl port-forward service/airec-server :8080
其中:
-
<service-name>
是Service的名称。 -
3000 是你希望在计算机上打开的端口。
-
80 是服务暴露的端口。
3.调试 Ingress
如果你已经阅读到这一部分,则:
- Pods 正在运行并准备就绪。
- Service 将流量分发到 Pod。
但你仍然无法看到应用程序的响应。
这很可能意味着 Ingress 的配置存在问题。
由于 Ingress 控制器是集群中的第三方组件,根据 Ingress 控制器的类型,有不同的调试技术。
但在深入研究 Ingress 特定工具之前,有一些直接的检查可以进行。
Ingress 使用 service.name
和 service.port
来连接到 Service。
你应该检查这些是否正确配置。
你可以使用以下命令检查 Ingress 是否正确配置:
kubectl describe ingress my-ingress
Name: my-ingress
Namespace: default
Rules:
Host Path Backends
---- ---- --------
*
/ my-service:80 (<error: endpoints "my-service" not found>)
如果 Backend 列为空,那么配置中可能存在错误。
如果在 Backend 列中看到端点,但仍然无法访问应用程序,则问题可能是:
- 你是如何将 Ingress 公开到公共互联网的。
- 你是如何将集群公开到公共互联网的。
你可以通过直接连接到 Ingress Pod 来隔离与 Ingress 相关的基础设施问题。
首先,检索 Ingress 控制器的 Pod(可能位于不同的命名空间中):
kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS
kube-system coredns-5644d7b6d9-jn7cq 1/1 Running
kube-system etcd-minikube 1/1 Running
kube-system kube-apiserver-minikube 1/1 Running
kube-system kube-controller-manager-minikube 1/1 Running
kube-system kube-proxy-zvf2h 1/1 Running
kube-system kube-scheduler-minikube 1/1 Running
kube-system nginx-ingress-controller-6fc5bcc 1/1 Running
通过 descirbe
它以检索端口:
kubectl describe pod nginx-ingress-controller-6fc5bcc --namespace kube-system | grep Ports
Ports: 80/TCP, 443/TCP, 8443/TCP
Host Ports: 80/TCP, 443/TCP, 0/TCP
最后,连接到该 Pod:
kubectl port-forward nginx-ingress-controller-6fc5bcc 3000:80 --namespace kube-system
Forwarding from 127.0.0.1:3000 -> 80
Forwarding from [::1]:3000 -> 80
在这一点上,每当你在计算机上访问端口 3000 时,请求都会被转发到 Pod 上的端口 80。
现在它能工作吗?
- 如果可以,问题就在基础设施中。你应该调查流量是如何路由到你的集群的。
- 如果不能工作,问题就在 Ingress 控制器中。你应该调试 Ingress。
- 如果仍然无法使 Ingress 控制器工作,你应该开始调试它。
有许多不同版本的 Ingress 控制器。
流行的选项包括 Nginx、HAProxy、Traefik 等。
你应该查阅你的 Ingress 控制器的文档以找到调试指南。
由于 Ingress Nginx 是最受欢迎的 Ingress 控制器,我们在下一节中包含了一些建议。
调试 Ingress Nginx
Ingress-nginx 项目有一个官方的 Kubectl 插件。
你可以使用 kubectl ingress-nginx
来:
- 检查日志、后端、证书等。
- 连接到 Ingress。
- 检查当前的配置。
你应该尝试的三个命令是:
kubectl ingress-nginx lint
,用于检查nginx.conf
。kubectl ingress-nginx backend
,检查后端(类似于kubectl describe ingress <ingress-name>
)。kubectl ingress-nginx logs
,检查日志。
请注意,你可能需要使用
--namespace <name>
为 Ingress 控制器指定正确的命名空间。
总结
在 Kubernetes 中进行故障排除可能是一项艰巨的任务,如果你不知道从哪里开始。
你应该始终记住从底层开始解决问题:从 Pods 开始,逐步向上移动到 Service 和 Ingress。
你在本文中学到的相同的调试技术可以应用于其他对象,如:
- 失败的 Jobs 和 CronJobs。
- StatefulSets 和 DaemonSets。
实际例子
Case-1: NodeHasDiskPressure
表现:节点有磁盘压力导致Pod全部被驱逐
# 查看错误事件
$ kubectl get events|grep Warning| less
# 看到磁盘有压力
The node had condition: [DiskPressure].
# 查看Pod 被驱逐的列表,发现都在一台机器上。
$ kubectl get pods -o=wide|grep Evicted
# 查看pod 的描述,发现请求的存储空间不足
$ kubectl describe pod <pod name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Evicted 32m kubelet The node was low on resource: ephemeral-storage. Container airec-server was using 13620Ki, which exceeds its request of 0.
# 因为都集中在同一个node上并且节点申请临时资源存储不足,所以猜测是node存在问题
$ kubectl describe node <node name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FreeDiskSpaceFailed 22m kubelet failed to garbage collect required amount of images. Wanted to free 9421824819 bytes, but freed 0 bytes
Warning EvictionThresholdMet 15m (x29 over 15d) kubelet Attempting to reclaim ephemeral-storage
Case-2 : Postgres DB 部署报错
报错信息
chown: changing ownership of '/var/lib/postgresql/data/pgdata': Operation not permitted
通过搜索错误找到 github issue中的解决方法 :github.com/docker-libr…
原因是 postgres DB 使用NFS 作为挂在卷的时候需要 no_root_squash 这个权限
# 查看导出权限
$ cat /etc/exports
/data/nfs/k8s *(rw,sync,no_subtree_check)
# 编辑配置文件 ,加入 no_root_squash 权限
$ vi /etc/exports
/data/nfs/k8s *(rw,sync,no_subtree_check,**no_root_squash**)
# 重启 nfs 服务
$ /etc/init.d/nfs-kernel-server restart