如果你不知道从何下手,那么在 Kubernetes 中排查故障可能会是一项艰难的任务。文本以超详细的图解说明了如何对 Kubernetes Deployment 进行故障排查,相信会对你有启发。
下面这张图可以帮助你调试 Kubernetes 中的 Deployment(关注本公众号,回复『20210305』可获取高清图片下载链接)。
当你想要在 Kubernetes 中部署应用程序时,通常需要定义 3 个组件:
-
Deployment
:创建 Pod 副本的方法;
-
Service
:内部负载均衡器,将流量路由到 Pod;
-
Ingress
:描述流量如何从外部集群流向 Service。
Kubernetes 中应用程序通过内部和外部两层负载均衡器暴露
内部负载均衡器叫 Service,外部负载均衡器叫 Ingress
Pod 不是直接部署的。相反,Deployment 会创建 Pod,并监控 Pod
假设你希望部署一个简单的 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: 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
在进行调试之前,让我们回顾一下这三个组件之间的关系。我们从 Deployment 和 Service 开始。
令人惊讶的是,
Deployment 和 Service 之间根本没有连接
。
相反,Service 直接指向 Pod,完全跳过了 Deployment
。因此,你应该关注的是 Pod 和 Service 之间是如何相互关联的。
-
Service selector 应至少匹配 Pod 的一个标签;
-
Service 的 targetPort 应该与 Pod 的 containerPort 匹配;
-
Service 端口可以是任何数字。多个 Service 可以使用同一个端口,因为每个 Service 分配到的 IP 地址不同。
在创建 Pod 时,需要为 Pod 中的每个容器定义端口 containerPort
创建 Service 时,可以定义 port 和 targetPort。但是哪一个应该和容器连接呢
targetPort 和 conatinerPort 需始终匹配
如果容器暴露的是端口 3000,那么 targetPort 应该也是 3000
如果查看 YAML 文件,标签和 port/targetPort 应该是匹配的。
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: 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 selector 不使用它来路由流量。换句话说,你可以放心地删除它或为它分配别的值。
那 matchLables selector 呢?它必须始终与 Pod 的标签匹配,Deployment 用它来跟踪 Pod。
假设你做了正确的更改,你应该如何测试它呢?可以使用以下命令检查 Pod 是否具有正确的标签:
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
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/ 3000:80
Forwarding from 127.0.0.1:3000 -> 8080
Forwarding from [::1]:3000 -> 8080
如果可以连接,说明设置正确。如果不能连接,很可能是标签弄错了或端口不匹配。
暴露应用程序的下一步是配置 Ingress。Ingress 必须知道如何检索 Service,然后连接 Pod 并将流量路由到它们
。Ingress 按名称和暴露的端口检索正确的 Service。
Ingress 和 Service 中必须匹配的有:
你已经知道了 Service 会暴露一个 port
Ingress 有一个字段叫做 ServicePort
Service 的 port 和 Ingress 的 ServicePort 必须始终匹配
如果你要把端口 80 分配给一个 Service,必须把 ServicePort 也改成 80
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
你可以使用之前的策略,即 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(可能在另一个命名空间中),描述它来检索端口:
kubectl describe pod nginx-ingress-controller-6fc5bcc \
--namespace kube-system \
| grep Ports
Ports: 80/TCP, 443/TCP, 18080/TCP
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 时,请求都会转发到 Ingress controller Pod 的端口 80。
如果访问 http://localhost:3000,则应找到提供网页的应用程序。
-
Service selector 应该和 Pod 的标签匹配;
-
Service 的 targetPort 应该和 Pod 里面容器的 containerPort 匹配;
-
Service 端口可以是任意数字。多个 Service 可以使用同一个端口,因为不同的 Service 分配的 IP 地址不同;
-
Ingress 的 service.port 应该和 Service 的 port 匹配;
-
Service 的名称应该和 Ingress 中 service.name 字段匹配。
了解如何构造 YAML 文件中的定义只是开始。出问题了怎么办?可能 Pod 无法启动了,或崩溃了。
3个步骤排查 kubernetes Deployment 故障
在深入探究有故障的 Deploymen 时,必须明确 Kubernetes 是如何工作的。
由于每个 Deployment 中都有三个组件,因此你应该从下往上依次调试所有组件。
应该从最底层开始为 Deployment 做故障排查。首先,检查 Pod 是否已就绪并在运行中
如果 Pod 已就绪,应该检查 Service 是否能将流量路由到 Pod
最后,检查 Service 和 Ingress 之间的连接
大多数情况下,问题出在 Pod 本身。你应该确保 Pod 已就绪并且在运行中。
那么如何检查呢?
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 既没有就绪,也没有运行。你怎么检查哪里出了问题呢?
-
kubectl logs
有助于检索 Pod 中容器的日志;
-
kubectl describe pod
对检索与 Pod 相关的事件列表很有用;
-
kubectl get pod
可提取 Kubernetes 中存储的 Pod 的 YAML 定义;
-
kubectl exec -ti
bash 可在 Pod 中的一个容器运行一个交互式命令。
那么你该用哪个命令呢?具体情况具体分析,通常这些命令要结合起来用。
-
ImagePullBackoff
-
ImageInspectError
-
ErrImagePull
-
ErrImageNeverPull
-
RegistryUnavailable
-
InvalidImageName
-
CrashLoopBackOff
-
RunContainerError
-
KillContainerError
-
VerifyNonRootError
-
RunInitContainerError
-
CreatePodSandboxError
-
ConfigPodSandboxError
-
KillPodSandboxError
-
SetupNetworkError
-
TeardownNetworkError
有些错误的频率很高。下面是最常见的错误以及解决方法。
当 Kubernetes 无法检索 Pod 中某一个容器的镜像时会报这个错。常见的原因如下:
前两种情况可以通过改正镜像名称/标签解决。对于最后一种情况,应该将私有 registry 的访问凭证通过 Secret 添加到 Kubernetes 中,并在 Pod 中引用它。
官方文档中有解决方法示例:https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
如果容器无法启动,Kubernetes 将显示 CrashloopBackOff 的信息。通常,在如下情况下容器无法启动:
你应该尝试检索容器日志,查看为什么容器无法启动。如果你无法查看日志是因为容器重启得太快了,可以用如下命令:
kubectl logs --previous
当容器无法启动时会出现这个错误。它甚至会在容器里的应用程序启动之前出现。这个问题通常是由于如下错误配置造成的:
可以使用 kubectl describe pod
命令检查和分析这个错误。
当你创建了一个 Pod,这个 Pod 处于 Pending 的状态。为什么会这样?
-
集群没有足够的资源(例如 CPU 和内存)来运行 Pod。
-
当前的命名空间具有 ResourceQuota 对象,创建 Pod 将使命名空间超过配额。
-
该 Pod 绑定了一个处于 Pending 状态的 PersistentVolumeClaim。
最好的选择是在 kubectl describe 命令中检查事件。
kubectl describe pod
对于因 ResourceQuota 造成的错误,可以使用以下方法检查群集日志:
kubectl get events --sort-by=.metadata.creationTimestamp
如果 Pod 正在运行但未就绪,则表示“就绪”探针失败。
当“就绪”探针失败时,则 Pod 未连接到服务,并且没有流量转发到该实例。
就绪探针故障是应用程序相关的错误,因此应该检查 kubectl describe 中的“事件”以验证错误。
如果 Pod 在运行中且已就绪,但仍无法收到应用程序的响应,就应检查 Service 的配置是否正确。
Service 会根据 Pod 的标签将流量路由到 Pod。因此,应该先检查 Service 定位了多少个 Pod。
可以通过检查 Service 中的 Endpoint 来做到这一点:
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 是一对
,当 Service 定位到一个 Pod 后,至少应该有一个 Endpoint。