容器云中使用了docker来部署应用,k8s可以根据负载自动增减docker,那在上面部署的应用出了问题后,如何排查?
收起一般来说,应用程序的故障可以分为下面几个方面:
Services的故障
检查Pod的问题首先应该了解Pod所处的状况。下面这个命令能够获得Pod当前的状态和近期的事件列表:
$ kubectl describe pods ${POD_NAME}
确认清楚在Pod以及其中每一个容器的状态,是否都处于Runing?通过观察容器的已运行时间判断它是否刚刚才重新启动过?
根据不同的运行状态,用户应该采取不同的调查措施。
如果Pod保持在Pending的状态,这意味着它无法被正常的调度到一个节点上。通常来说,这是由于某种系统资源无法满足Pod运行的需求。观察刚才kubectl describe命令的输出内容,其中应该包括了能够判断错误原因的消息。常见的原因有以下这些:
Pod处在Waiting的状态,说明它已经被调度分配到了一个工作节点,然而它无法在那个节点上运行。同样的,在刚才kubectl describe命令的输出内容中,应该包含有更详细的错误信息。最经常导致Pod始终Waiting的原因是无法下载所需的镜像(译者注:由于国内特殊的网络环境,这类问题出现得特别普遍)。用户可以从下面三个方面进行排查:
手工的在节点上运行一次docker pull <镜像名称>检测镜像能否被正确的拉取下来
首先,查看正在运行容器的日志。
$ kubectl logs <Pod名称> <Pod中的容器名称>
如果容器之前已经崩溃过,通过以下命令可以获得容器前一次运行的日志内容。
$ kubectl logs --previous <Pod名称> <Pod中的容器名称>
此外,还可以使用exec命令在指定的容器中运行任意的调试命令。
$ kubectl exec -c -- <任意命令> <命令参数列表...>
值得指出的是,对于只有一个容器的Pod情况,-c 这个参数是可以省略的。
例如查看一个运行中的Cassandra日志文件内容,可以参考下面这个命令:
$ kubectl exec cassandra -- cat /var/log/cassandra/system.log
要是上面的这些办法都不奏效,你也可以找到正在运行该Pod的主机地址,然后使用SSH登陆进去检测。但这是在确实迫不得已的情况下才会采用的措施,通常使用Kubernetes暴露的API应该足够获得所需的环境信息。因此,如果当你发现自己不得不登陆到主机上去获取必要的信息数据时,不妨到Kubernetes的GitHub页面中给我们提一个功能需求(Feature Request),在需求中附上详细的使用场景以及为什么当前Kubernetes所提供的工具不能够满足需要。
如果Pod没有按照预期的功能运行,有可能是由于在Pod描述文件中(例如你本地机器的mypod.yaml文件)存在一些错误,这些配置中的错误在Pod时创建并没有引起致命的故障。这些错误通常包括Pod描述的某些元素嵌套层级不正确,或是属性的名称书写有误(这些错误属性在运行时会被忽略掉)。举例来说,如果你把command属性误写为了commnd,Pod仍然会启动,但用户所期待运行的命令则不会被执行。
对于这种情况,首先应该尝试删掉正在运行的Pod,然后使用--validate参数重新运行一次。继续之前的例子,当执行kubectl create --validate -f mypod.yaml命令时,被误写为commnd的command指令会导致下面这样的错误:
I0805 10:43:25.129850 46757 schema.go:126] unknown field: commnd
I0805 10:43:25.129973 46757 schema.go:129] this may be a false alarm, see https://github.com/kubernetes/kubernetes/issues/6842
pods/mypod
下一件事是检查当前apiserver运行Pod所使用的Pod描述文件内容是否与你想要创建的Pod内容(用户本地主机的那个yaml文件)一致。比如,执行kubectl get pods/mypod -o yaml > mypod-on-apiserver.yaml命令将正在运行的Pod描述文件内容导出来保存为mypod-on-apiserver.yaml,并将它和用户自己的Pod描述文件mypod.yaml进行对比。由于apiserver会尝试自动补全一些缺失的Pod属性,在apiserver导出的Pod描述文件中有可能比本地的Pod描述文件多出若干行,这是正常的,但反之如果本地的Pod描述文件比apiserver导出的Pod描述文件多出了新的内容,则很可能暗示着当前运行的Pod使用了不正确的内容。
Replication Controllers的逻辑十分直白,它的作用只是创建新的Pod副本,仅仅可能出现的错误便是它无法创建正确的Pod,对于这种情况,应该参考上面的『排查Pods的故障』部分进行检查。
也可以使用kubectl describe rc <控制器名称>来显示与指定Replication Controllers相关的事件信息。
Service为一系列后端Pod提供负载均衡的功能。有些十分常见的故障都可能导致Service无法正常工作,以下将提供对调试Service相关问题的参考。
首先,检查Service连接的服务提供端点(endpoint),对于任意一个Service对象,apiserver都会为其创建一个端点资源(译者注:即提供服务的IP地址和端口号)。
这个命令可以查看到Service的端口资源:
$ kubectl get endpoints
请检查这个命令输出端点信息中的端口号与实际容器提供服务的端口号是否一致。例如,如果你的Service使用了三个Nginx容器的副本(replicas),这个命令应该输出三个不同的IP地址的端点信息。
如果刚刚的命令显示Service没有端点信息,请尝试通过Service的选择器找到具有相应标签的所有Pod。假设你的Service描述选择器内容如下:
...
spec:
- selector:
name: nginx
type: frontend
可以使用以下命令找出相应的Pod:
$ kubectl get pods --selector=name=nginx,type=frontend
找到了符合标签的Pod后,首先确认这些被选中的Pod是正确,有无错选、漏选的情况。
如果被选中的Pod没有问题,则问题很可能出在这些Pod暴露的端口没有被正确的配置好。要是一个Service指定了containerPort,但被选中的Pod并没有在配置中列出相应的端口,它们就不会出现在端点列表中。
请确保所用Pod的containerPort与Service的containerPort配置信息是一致的。
如果你能够连接到Service,但每次连接上就立即被断开,同时Service的端点列表内容是正确的,很可能是因为Kubernetes的kube-proxy服务无法连接到相应的Pod。
请检查以下几个方面:
如果上述的这些步骤还不足以解答你所遇到的问题,也就是说你已经确认了相应的Service正在运行,并且具有恰当的端点资源,相应的Pod能够提供正确的服务,集群的DNS域名服务没有问题,IPtable的防火墙配置没有问题,kube-proxy服务也都运转正常。
仅仅依靠nsenter,docker-cli及容器日志往往是不够的,对于Kubernetes环境而言,kubectl工具能够部分代替docker-cli且在容器层面之上可展示详细的资源和事件信息。
docker本身是支持通过命令行参数实现容器之间各命名空间的共享,因此只要启动一个工具齐全的容器并让该容器共享业务容器的命名空间,就能够利用工具容器中丰富的工具对业务容器进行调试诊断。命令如下:
# busybox容器共享了目标容器的网络命名空间,PID命名空间和IPC命名空间
docker run -it --network=container:$TARGET_ID --pid=container:$TARGET_ID --ipc=container:$TARGET_ID busybox
将介绍几种可行的容器调试与排障解决方案,这些解决方案都是基于Kubernetes环境(纯docker环境使用上文的docker命令即可),均需使用工具镜像(可选netshoot[200MB])。
Kubernetes中的业务单元是pod,pod中可以包含多个容器,多个容器共享网络,PID和IPC命名空间,PID命名空间共享需要显式启用。sidecar方式即是在业务pod中同时包含业务容器和工具容器(当然sidecar的应用不止如此),此方式使用简单,但工具容器注入时pod内的容器将重新部署,即容器无法在运行时注入。
示例:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
shareProcessNamespace: true
containers:
- name: nginx
image: nginx
- name: tool
image: xo
securityContext:
capabilities:
add:
- SYS_PTRACE
stdin: true
tty: true
#### kubectl-debug简介
kubectl-debug是由PingCAP员工开发的Kubernetes容器调试诊断插件 。本地执行kubectl-debug会在目标容器所在节点部署Debug Agent,通过Debug Agent部署共享目标容器命名空间的工具(debug)容器(docker原生特性)。kubectl-debug使用的工具默认是netshoot,但可以配置为其他容器。此方案是非侵入式的,不会对目标容器产生影响。本插件支持CrashLoopBackoff诊断。
示例 :
# kubectl 1.12.0 或更高的版本,可以直接使用:
kubectl debug -h
# 假如安装了debug-agent 的 daemonset,可以使用--agentless=false 来加快启动速度
# 之后的命令里会使用默认的agentless模式
kubectl debug POD_NAME
# 假如 Pod 处于CrashLookBackoff 状态无法连接,可以复制一个完全相同的 Pod来进行诊断
kubectl debug POD_NAME --fork
# 当使用fork mode时,如果需要复制出来的pod保留原pod的labels,可以使用 --fork-pod-retain-labels 参数进行设置(注意逗号分隔,且不允许空格)
# 示例如下
# 若不设置,该参数默认为空(既不保留原pod的任何labels,fork出来的新pod的labels为空)
kubectl debug POD_NAME --fork --fork-pod-retain-labels=<labelKeyA>,<labelKeyB>,<labelKeyC>
# 为了使 没有公网 IP 或无法直接访问(防火墙等原因)的 NODE 能够访问,默认开启 port-forward 模式
# 如果不需要开启port-forward模式,可以使用 --port-forward=false 来关闭
kubectl debug POD_NAME --port-forward=false --agentless=false --daemonset-ns=kube-system --daemonset-name=debug-agent
# 老版本的 kubectl 无法自动发现插件,需要直接调用 binary
kubectl-debug POD_NAME
# 使用私有仓库镜像,并设置私有仓库使用的kubernetes secret
# secret data原文请设置为 {Username: <username>, Password: <password>}
# 默认secret_name为kubectl-debug-registry-secret,默认namspace为default
kubectl-debug POD_NAME --image calmkart/netshoot:latest --registry-secret-name <k8s_secret_name> --registry-secret-namespace <namespace>
# 在默认的agentless模式中,你可以设置agent pod的resource资源限制,如下示例
# 若不设置,默认为空
kubectl-debug POD_NAME --agent-pod-cpu-requests=250m --agent-pod-cpu-limits=500m --agent-pod-memory-requests=200Mi --agent-pod-memory-limits=500Mi
#### Ephemeral Container介绍
Ephemeral Container是Kubernetes v1.16以后加入的新资源类型,旨在解决pod内容器调试排障问题,目前处于Alpha阶段。Kubernetes的pod一旦处于运行时状态就无法再向其中添加容器,而Ephemeral Container则可以直接插入到处于运行时状态的pod中并共享该pod中容器的各Namespace,从而实现业务容器的便捷调试与诊断 。