learning/k8s-advanced/ts/deployment.md
原文链接: A visual guide on troubleshooting Kubernetes deployments
下图可帮助你调试Kubernetes Deployment。点击此处 获取该图的PDF版本
当你希望在Kubernetes中部署应用程序时,你通常会定义三个组件:
一个Deployment - 这是一份用于创建你的应用程序的Pod副本的"食谱";
一个Service - 一个内部负载均衡器,用于将流量路由到内部的Pod上;
一个Ingress - 描述如何流量应该如何从集群外部流入到集群内部的你的服务上。
下面让我们用示意图快速总结一下要点。
<b-carousel class="ts-deployment" id="carousel-1" controls :interval="5000" indicators background="#fff" img-height="920" img-width="690" style="text-shadow: 1px 1px 2px #333;"
<!-- Text slides with image -->
<b-carousel-slide
img-src="/statics/learning/ts/deployment/5c4ee7aa22c0d5d7b61746cae882aedc.svg">
在Kubernetes中,你的应用程序通过两层负载均衡器暴露服务:内部的和外部的
</b-carousel-slide>
<b-carousel-slide
img-src="/statics/learning/ts/deployment/616def92e99bcaa69bb2e7bb7768a595.svg">
内部的负载均衡器称为Service,而外部的负载均衡器称为Ingress
</b-carousel-slide>
<b-carousel-slide img-src="/statics/learning/ts/deployment/aa7dc4e26be246133054a6603aa07a77.svg"> Pod不会直接部署。Deployment会负责创建Pod并管理它们 </b-carousel-slide>
</b-carousel>假设你要部署一个简单的"HelloWorld"应用,该应用的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/v1beta1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- http:
paths:
- backend:
serviceName: app
servicePort: 80
path: /
这个定义很长,组件之间的相互关系并不容易看出来。
例如:
在进行调试之前,让我们回顾一下这三个组件是如何相互关联的。
让我们从Deployment和Service开始。
令人惊讶的消息是,Service和Deployment之间根本没有连接。
事实是:Service直接指向Pod,并完全跳过了Deployment。
因此,你应该注意的是Pod和Service之间的相互关系。
你应该记住三件事:
下面的图总结了如何连接端口:
<b-carousel class="ts-deployment" id="carousel-2" controls :interval="5000" indicators background="#fff" img-height="920" img-width="690" style="text-shadow: 1px 1px 2px #333;"
<!-- Text slides with image -->
<b-carousel-slide
img-src="/statics/learning/ts/deployment/244048d9100f0468f0fb2cc5d24908b9.svg">
考虑上面被一个服务暴露的Pod
</b-carousel-slide>
<b-carousel-slide
img-src="/statics/learning/ts/deployment/ba0a5f40770add57a39f48fc1fa68ea9.svg">
创建Pod时,应为Pod中的每个容器定义containerPort端口
</b-carousel-slide>
<b-carousel-slide img-src="/statics/learning/ts/deployment/198396281daf41d2b64bf92e39afe5d5.svg"> 当创建一个Service时,你可以定义port和targetPort,但是哪个用来连接容器呢? </b-carousel-slide>
<b-carousel-slide img-src="/statics/learning/ts/deployment/56f461d209901a168ba9b6aabd56f8a7.svg"> targetPort和containerPort应该始终保持匹配 </b-carousel-slide>
<b-carousel-slide img-src="/statics/learning/ts/deployment/3e6865f66e7f366feeffaf79a636b397.svg"> 如果容器暴露3000端口(containerPort),那么targetPort应该匹配这一个端口号 </b-carousel-slide>
</b-carousel>再来看看YAML,标签和ports/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的选择器未使用它来路由流量。
换句话说,你可以安全地删除它或为其分配其他值。
那matchLabels选择器呢?
它必须始终与Pod的标签匹配,并且被Deployment用来跟踪Pod。
假设你已经进行了所有正确的设置,该如何测试它呢?
你可以使用以下命令检查Pod是否具有正确的标签:
$ kubectl get pods --show-labels
或者,如果你拥有属于多个应用程序的Pod:
$ 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
service/<service name> 是服务的名称- 在上面的YAML中是“my-service”port 字段暴露的端口如果可以连接,则说明设置正确。
如果不行,则很可能是你填写了错误的标签或端口不匹配。
接下来是配置Ingress以将你的应用暴露到集群外部。
Ingress必须知道如何检索服务,然后检索Pod并将流量路由给它们。
Ingress按名字和暴露的端口检索正确的服务。
在Ingress和Service中应该匹配两件事:
servicePort 应该匹配service的 portserviceName 应该匹配服务的 name下面的图总结了如何连接端口:
<b-carousel class="ts-deployment" id="carousel-3" controls :interval="5000" indicators background="#fff" img-height="920" img-width="690" style="text-shadow: 1px 1px 2px #333;"
<!-- Text slides with image -->
<b-carousel-slide
img-src="/statics/learning/ts/deployment/3d0bb4d60860ca4bf934cd8e0f6296bb.svg">
你已经知道servive暴露一个port
</b-carousel-slide>
<b-carousel-slide img-src="/statics/learning/ts/deployment/c11fe067be3b340235842f9e43b021b0.svg"> Ingress有一个字段叫servicePort </b-carousel-slide>
<b-carousel-slide img-src="/statics/learning/ts/deployment/bc44ba06abef481a6dd6db4562092e52.svg"> service的port和Ingress的service应该始终保持匹配 </b-carousel-slide>
<b-carousel-slide
img-src="/statics/learning/ts/deployment/f198bbe14b7a3f9faed51fa2834557df.svg">
如果你为service指定的port是80,那么你也应该将ingress的 servicePort 改为80
</b-carousel-slide>
实践中,你应该查看以下几行(下面代码中的 my-service 和80):
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- port: 80
targetPort: 8080
selector:
any-name: my-app
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- http:
paths:
- backend:
serviceName: my-service
servicePort: 80
path: /
你如何测试Ingress是否正常工作呢?
你可以使用与以前相同的策略kubectl port-forward,但是这次你应该连接到Ingress控制器,而不是连接到Service。
首先,使用以下命令检索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
标识Ingress Pod(可能在其他命名空间中)并描述它以检索端口:
$ 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
此时,每次你访问计算机上的端口3000时,请求都会转发到Ingress控制器Pod上的端口80。
如果访问 http://localhost:3000,则应找到提供网页服务的应用程序。
快速回顾一下哪些端口和标签应该匹配:
targetPort 应与Pod中容器的 containerPort 匹配servicePort 应该匹配 service 的 portserviceName 字段匹配知道如何构造YAML定义只是故事的一部分。
出了问题后该怎么办?
Pod可能无法启动,或者正在崩溃。
在深入研究失败的deployment之前,我们必须对Kubernetes的工作原理有一个明确定义的思维模型。
由于每个deployment中都有三个组件,因此你应该自下而上依次调试所有组件。
<b-carousel class="ts-deployment" id="carousel-4" controls :interval="5000" indicators background="#fff" img-height="920" img-width="690" style="text-shadow: 1px 1px 2px #333;"
<!-- Text slides with image -->
<b-carousel-slide img-src="/statics/learning/ts/deployment/f1bcc6166f088371a58d7b0b04661908.svg"> 你应该从底部开始对deployment进行故障排除。首先,检查Pod是否已就绪并正在运行。 </b-carousel-slide>
<b-carousel-slide img-src="/statics/learning/ts/deployment/ef0791df2a69439059143c7f54c2a249.svg"> 如果Pod已就绪,则应调查service是否可以将流量分配给Pod。 </b-carousel-slide>
<b-carousel-slide img-src="/statics/learning/ts/deployment/39a656b37862d1bbd310e175a8a5de47.svg"> 最后,你应该检查service与ingress之间的连接。 </b-carousel-slide>
</b-carousel>在大多数情况下,问题出在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
在上述会话中,最后一个Pod处于就绪并正常运行的状态;但是,前两个Pod既不处于Running也不是Ready。
你如何调查出了什么问题?
有四个有用的命令可以对Pod进行故障排除:
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的一个容器中运行交互式命令应该使用哪一个呢?
没有一种万能的。
相反,我们应该结合着使用它们。
Pod可能会出现启动和运行时错误。
启动错误包括:
运行时错误包括:
有些错误比其他错误更常见。
以下是最常见的错误列表以及如何修复它们的方法。
当Kubernetes无法获取到Pod中某个容器的镜像时,将出现此错误。
共有三个可能的原因:
前两种情况可以通过更正image名称和标记来解决。
针对第三种情况,你应该将私有registry的访问凭证通过Secret添加到k8s中并在Pod中引用它。
如果容器无法启动,则Kubernetes将显示错误状态为:CrashLoopBackOff。
通常,在以下情况下容器无法启动:
你应该尝试从该容器中检索日志以调查其失败的原因。
如果由于容器重新启动太快而看不到日志,则可以使用以下命令:
$ kubectl logs <pod-name> --previous
这个命令打印前一个容器的错误消息。
当容器无法启动时,出现此错误。
甚至在容器内的应用程序启动之前。
该问题通常是由于配置错误,例如:
你应该使用 kubectl describe pod <pod-name>命令收集和分析错误。
当创建Pod时,该Pod保持Pending状态。
为什么?
假设你的调度程序组件运行良好,可能的原因如下:
最好的选择是检查kubectl describe命令输出的“事件”部分内容:
$ kubectl describe pod <pod name>
对于因ResourceQuotas而导致的错误,可以使用以下方法检查集群的日志:
$ kubectl get events --sort-by=.metadata.creationTimestamp
如果Pod正在运行但未就绪(not ready),则表示readiness就绪探针失败。
当“就绪”探针失败时,Pod未连接到服务,并且没有流量转发到该实例。
就绪探针失败是应用程序的特定错误,因此你应检查 kubectl describe 中的 Events 部分以识别错误。
如果你的Pod正在运行并处于就绪状态,但仍无法收到应用程序的响应,则应检查服务的配置是否正确。
service旨在根据流量的标签将流量路由到Pod。
因此,你应该检查的第一件事是服务关联了多少个Pod。
你可以通过检查服务中的端点(endpoint)来做到这一点:
$ kubectl describe service <service-name> | grep Endpoints
端点是一对,并且在服务(至少)以Pod为目标时,应该至少有一个端点。
如果“端点”部分为空,则有两种解释:
selector 标签上有拼写错误如果你看到端点列表,但仍然无法访问你的应用程序,则 targetPort 可能是你服务中的罪魁祸首。
你如何测试服务?
无论服务类型如何,你都可以使用 kubectl port-forward 来连接它:
$ kubectl port-forward service/<service-name> 3000:80
这里:
<service-name> 是服务的名称3000 是你希望在计算机上打开的端口80 是服务公开的端口如果你已到达本节,则:
但是你仍然看不到应用程序的响应。
这意味着最有可能是Ingress配置错误。
由于正在使用的Ingress控制器是集群中的第三方组件,因此有不同的调试技术,具体取决于Ingress控制器的类型。
但是在深入研究Ingress专用工具之前,你可以用一些简单的方法进行检查。
Ingress使用 serviceName 和 servicePort 连接到服务。
你应该检查这些配置是否正确。
你可以通过下面命令检查Ingress配置是否正确:
$ kubectl describe ingress <ingress-name>
如果backend一列为空,则配置中必然有一个错误。
如果你可以在“backend”列中看到端点,但是仍然无法访问该应用程序,则可能是以下问题:
你可以通过直接连接到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
执行 kubectl describe pod 以检索端口:
$ kubectl describe pod nginx-ingress-controller-6fc5bcc --namespace kube-system \ | grep Ports
最后,连接到Pod:
$ kubectl port-forward nginx-ingress-controller-6fc5bcc 3000:80 --namespace kube-system
此时,每次你访问计算机上的端口3000时,请求都会转发到Pod上的端口80。
现在可以用吗?
如果仍然无法使Ingress控制器正常工作,则应开始对其进行调试。
目前有许多不同版本的Ingress控制器。
热门选项包括Nginx,HAProxy,Traefik等。
你应该查阅Ingress控制器的文档以查找故障排除指南。
由于 Ingress Nginx 是使用比较多的的Ingress控制器之一,因此在下一部分中我们将介绍一些有关调试ingress-nginx的技巧。
如果参考 kuboard.cn 上的 Kubernetes 安装文档,您安装的 Ingress 控制器是 nginx ingress,和 Ingress Nginx 并非是同一个东西
Ingress-nginx 项目有一个 kubectl 的官方插件。
你可以用kubectl ingress-nginx来:
你应该尝试的三个命令是:
kubectl ingress-nginx lint,它会检查 nginx.confkubectl ingress-nginx backend,以检查后端(类似于 kubectl describe ingress <ingress-name> )kubectl ingress-nginx logs,查看日志请注意,你可能需要为Ingress控制器指定正确的名称空间
--namespace <name>。
如果你不知道从哪里开始,那么在Kubernetes中进行故障排除可能是一项艰巨的任务。
你应该始终牢记从下至上解决问题:从Pod开始,然后通过Service和Ingress向上移动堆栈。
你在本文中了解到的调试技术也可以应用于其他对象,例如: