这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

应用故障排除

调试常见的容器应用问题.

该文档包含一组用于解决容器化应用程序问题的资源。 它涵盖了诸如 Kubernetes 资源(如 Pod、Service 或 StatefulSets)的常见问题、 关于理解容器终止消息的建议以及调试正在运行的容器的方法。

1 - 调试 Pod

本指南帮助用户调试那些部署到 Kubernetes 上后没有正常运行的应用。 本指南 并非 指导用户如何调试集群。 如果想调试集群的话,请参阅这里

诊断问题

故障排查的第一步是先给问题分类。问题是什么?是关于 Pod、Replication Controller 还是 Service?

调试 Pod

调试 Pod 的第一步是查看 Pod 信息。用如下命令查看 Pod 的当前状态和最近的事件:

kubectl describe pods ${POD_NAME}

查看一下 Pod 中的容器所处的状态。这些容器的状态都是 Running 吗?最近有没有重启过?

后面的调试都是要依靠 Pod 的状态的。

Pod 停滞在 Pending 状态

如果一个 Pod 停滞在 Pending 状态,表示 Pod 没有被调度到节点上。 通常这是因为某种类型的资源不足导致无法调度。 查看上面的 kubectl describe ... 命令的输出,其中应该显示了为什么没被调度的原因。 常见原因如下:

  • 资源不足: 你可能耗尽了集群上所有的 CPU 或内存。此时,你需要删除 Pod、调整资源请求或者为集群添加节点。 更多信息请参阅计算资源文档

  • 使用了 hostPort: 如果绑定 Pod 到 hostPort,那么能够运行该 Pod 的节点就有限了。 多数情况下,hostPort 是非必要的,而应该采用 Service 对象来暴露 Pod。 如果确实需要使用 hostPort,那么集群中节点的个数就是所能创建的 Pod 的数量上限。

Pod 停滞在 Waiting 状态

如果 Pod 停滞在 Waiting 状态,则表示 Pod 已经被调度到某工作节点,但是无法在该节点上运行。 同样,kubectl describe ... 命令的输出可能很有用。 Waiting 状态的最常见原因是拉取镜像失败。要检查的有三个方面:

  • 确保镜像名字拼写正确。
  • 确保镜像已被推送到镜像仓库。
  • 尝试手动是否能拉取镜像。例如,如果你在你的 PC 上使用 Docker,请运行 docker pull <镜像>

Pod 停滞在 terminating 状态

如果 Pod 停滞在 Terminating 状态,表示已发出删除 Pod 的请求, 但控制平面无法删除该 Pod 对象。

如果 Pod 拥有 Finalizer 并且集群中安装了准入 Webhook, 可能会导致控制平面无法移除 Finalizer,从而导致 Pod 出现此问题。

要确认这种情况,请检查你的集群中是否有 ValidatingWebhookConfiguration 或 MutatingWebhookConfiguration 处理 pods 资源的 UPDATE 操作。

如果 Webhook 是由第三方提供的:

  • 确保你使用的是最新版。
  • 禁用处理 UPDATE 操作的 Webhook。
  • 向相关供应商报告问题。

如果你是 Webhook 的作者:

  • 对于变更性质的 Webhook,请确保在处理 UPDATE 操作时不要更改不可变字段。 例如,一般不允许更改 containers
  • 对于验证性质的 Webhook,请确保你的验证策略仅被应用于新的更改之上。换句话说, 你应该允许存在违规的现有 Pod 通过验证。这样可以确保在安装验证性质的 Webhook 之前创建的 Pod 可以继续运行。

Pod 处于 Crashing 或别的不健康状态

一旦 Pod 被调度, 就可以采用调试运行中的 Pod 中的方法来进一步调试。

Pod 处于 Running 态但是没有正常工作

如果 Pod 行为不符合预期,很可能 Pod 描述(例如你本地机器上的 mypod.yaml)中有问题, 并且该错误在创建 Pod 时被忽略掉,没有报错。 通常,Pod 的定义中节区嵌套关系错误、字段名字拼错的情况都会引起对应内容被忽略掉。 例如,如果你误将 command 写成 commnd,Pod 虽然可以创建, 但它不会执行你期望它执行的命令行。

可以做的第一件事是删除你的 Pod,并尝试带有 --validate 选项重新创建。 例如,运行 kubectl apply --validate -f mypod.yaml。 如果 command 被误拼成 commnd,你将会看到下面的错误信息:

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

接下来就要检查的是 API 服务器上的 Pod 与你所期望创建的是否匹配 (例如,你原本使用本机上的一个 YAML 文件来创建 Pod)。 例如,运行 kubectl get pods/mypod -o yaml > mypod-on-apiserver.yaml, 之后手动比较 mypod.yaml 与从 API 服务器取回的 Pod 描述。 从 API 服务器处获得的 YAML 通常包含一些创建 Pod 所用的 YAML 中不存在的行,这是正常的。 不过,如果如果源文件中有些行在 API 服务器版本中不存在,则意味着 Pod 规约是有问题的。

调试副本控制器

副本控制器相对比较简单直接。它们要么能创建 Pod,要么不能。 如果不能创建 Pod,请参阅上述说明调试 Pod。

你也可以使用 kubectl describe rc ${CONTROLLER_NAME} 命令来检视副本控制器相关的事件。

调试 Service

服务支持在多个 Pod 间负载均衡。 有一些常见的问题可以造成服务无法正常工作。 以下说明将有助于调试服务的问题。

首先,验证服务是否有端点。对于每一个 Service 对象,API 服务器为其提供对应的 endpoints 资源。

通过如下命令可以查看 endpoints 资源:

kubectl get endpoints ${SERVICE_NAME}

确保 Endpoints 与服务成员 Pod 个数一致。 例如,如果你的 Service 用来运行 3 个副本的 nginx 容器,你应该会在 Service 的 Endpoints 中看到 3 个不同的 IP 地址。

服务缺少 Endpoints

如果没有 Endpoints,请尝试使用 Service 所使用的标签列出 Pod。 假定你的服务包含如下标签选择算符:

...
spec:
  - selector:
     name: nginx
     type: frontend

你可以使用如下命令列出与选择算符相匹配的 Pod,并验证这些 Pod 是否归属于创建的服务:

kubectl get pods --selector=name=nginx,type=frontend

验证 Pod 的 containerPort 与服务的 targetPort 是否匹配。

网络流量未被转发

请参阅调试 Service 了解更多信息。

接下来

如果上述方法都不能解决你的问题, 请按照调试 Service 文档中的介绍, 确保你的 Service 处于 Running 状态,有 Endpoints 被创建,Pod 真的在提供服务; DNS 服务已配置并正常工作,iptables 规则也已安装并且 kube-proxy 也没有异常行为。

你也可以访问故障排查文档来获取更多信息。

2 - 调试 Service

对于新安装的 Kubernetes,经常出现的问题是 Service 无法正常运行。你已经通过 Deployment(或其他工作负载控制器)运行了 Pod,并创建 Service , 但是当你尝试访问它时,没有任何响应。此文档有望对你有所帮助并找出问题所在。

在 Pod 中运行命令

对于这里的许多步骤,你可能希望知道运行在集群中的 Pod 看起来是什么样的。 最简单的方法是运行一个交互式的 busybox Pod:

kubectl run -it --rm --restart=Never busybox --image=gcr.io/google-containers/busybox sh

如果你已经有了你想使用的正在运行的 Pod,则可以运行以下命令去进入:

kubectl exec <POD-NAME> -c <CONTAINER-NAME> -- <COMMAND>

设置

为了完成本次实践的任务,我们先运行几个 Pod。 由于你可能正在调试自己的 Service,所以,你可以使用自己的信息进行替换, 或者你也可以跟着教程并开始下面的步骤来获得第二个数据点。

kubectl create deployment hostnames --image=registry.k8s.io/serve_hostname
deployment.apps/hostnames created

kubectl 命令将打印创建或变更的资源的类型和名称,它们可以在后续命令中使用。 让我们将这个 deployment 的副本数扩至 3。

kubectl scale deployment hostnames --replicas=3
deployment.apps/hostnames scaled

请注意这与你使用以下 YAML 方式启动 Deployment 类似:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: hostnames
  name: hostnames
spec:
  selector:
    matchLabels:
      app: hostnames
  replicas: 3
  template:
    metadata:
      labels:
        app: hostnames
    spec:
      containers:
      - name: hostnames
        image: registry.k8s.io/serve_hostname

"app" 标签是 kubectl create deployment 根据 Deployment 名称自动设置的。

确认你的 Pod 是运行状态:

kubectl get pods -l app=hostnames
NAME                        READY     STATUS    RESTARTS   AGE
hostnames-632524106-bbpiw   1/1       Running   0          2m
hostnames-632524106-ly40y   1/1       Running   0          2m
hostnames-632524106-tlaok   1/1       Running   0          2m

你还可以确认你的 Pod 是否正在提供服务。你可以获取 Pod IP 地址列表并直接对其进行测试。

kubectl get pods -l app=hostnames \
    -o go-template='{{range .items}}{{.status.podIP}}{{"\n"}}{{end}}'
10.244.0.5
10.244.0.6
10.244.0.7

用于本教程的示例容器通过 HTTP 在端口 9376 上提供其自己的主机名, 但是如果要调试自己的应用程序,则需要使用你的 Pod 正在侦听的端口号。

在 Pod 内运行:

for ep in 10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376; do
    wget -qO- $ep
done

输出类似这样:

hostnames-632524106-bbpiw
hostnames-632524106-ly40y
hostnames-632524106-tlaok

如果此时你没有收到期望的响应,则你的 Pod 状态可能不健康,或者可能没有在你认为正确的端口上进行监听。 你可能会发现 kubectl logs 命令对于查看正在发生的事情很有用, 或者你可能需要通过kubectl exec 直接进入 Pod 中并从那里进行调试。

假设到目前为止一切都已按计划进行,那么你可以开始调查为何你的 Service 无法正常工作。

Service 是否存在?

细心的读者会注意到我们实际上尚未创建 Service —— 这是有意而为之。 这一步有时会被遗忘,这是首先要检查的步骤。

那么,如果我尝试访问不存在的 Service 会怎样? 假设你有另一个 Pod 通过名称匹配到 Service,你将得到类似结果:

wget -O- hostnames
Resolving hostnames (hostnames)... failed: Name or service not known.
wget: unable to resolve host address 'hostnames'

首先要检查的是该 Service 是否真实存在:

kubectl get svc hostnames
No resources found.
Error from server (NotFound): services "hostnames" not found

让我们创建 Service。 和以前一样,在这次实践中 —— 你可以在此处使用自己的 Service 的内容。

kubectl expose deployment hostnames --port=80 --target-port=9376
service/hostnames exposed

重新运行查询命令:

kubectl get svc hostnames
NAME        TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
hostnames   ClusterIP   10.0.1.175   <none>        80/TCP    5s

现在你知道了 Service 确实存在。

同前,此步骤效果与通过 YAML 方式启动 Service 一样:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: hostnames
  name: hostnames
spec:
  selector:
    app: hostnames
  ports:
  - name: default
    protocol: TCP
    port: 80
    targetPort: 9376

为了突出配置范围的完整性,你在此处创建的 Service 使用的端口号与 Pods 不同。 对于许多真实的 Service,这些值可以是相同的。

是否存在影响目标 Pod 的网络策略入站规则?

如果你部署了任何可能影响到 hostnames-* Pod 的传入流量的网络策略入站规则, 则需要对其进行检查。

详细信息,请参阅网络策略

Service 是否可通过 DNS 名字访问?

通常客户端通过 DNS 名称来匹配到 Service。

从相同命名空间下的 Pod 中运行以下命令:

nslookup hostnames
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

如果失败,那么你的 Pod 和 Service 可能位于不同的命名空间中, 请尝试使用限定命名空间的名称(同样在 Pod 内运行):

nslookup hostnames.default
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames.default
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

如果成功,那么需要调整你的应用,使用跨命名空间的名称去访问它, 或者在相同的命名空间中运行应用和 Service。如果仍然失败,请尝试一个完全限定的名称:

nslookup hostnames.default.svc.cluster.local
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames.default.svc.cluster.local
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

注意这里的后缀:"default.svc.cluster.local"。"default" 是我们正在操作的命名空间。 "svc" 表示这是一个 Service。"cluster.local" 是你的集群域,在你自己的集群中可能会有所不同。

你也可以在集群中的节点上尝试此操作:

nslookup hostnames.default.svc.cluster.local 10.0.0.10
Server:         10.0.0.10
Address:        10.0.0.10#53

Name:   hostnames.default.svc.cluster.local
Address: 10.0.1.175

如果你能够使用完全限定的名称查找,但不能使用相对名称,则需要检查你 Pod 中的 /etc/resolv.conf 文件是否正确。在 Pod 中运行以下命令:

cat /etc/resolv.conf

你应该可以看到类似这样的输出:

nameserver 10.0.0.10
search default.svc.cluster.local svc.cluster.local cluster.local example.com
options ndots:5

nameserver 行必须指示你的集群的 DNS Service, 它是通过 --cluster-dns 标志传递到 kubelet 的。

search 行必须包含一个适当的后缀,以便查找 Service 名称。 在本例中,它查找本地命名空间(default.svc.cluster.local)中的服务和所有命名空间 (svc.cluster.local)中的服务,最后在集群(cluster.local)中查找服务的名称。 根据你自己的安装情况,可能会有额外的记录(最多 6 条)。 集群后缀是通过 --cluster-domain 标志传递给 kubelet 的。 本文中,我们假定后缀是 “cluster.local”。 你的集群配置可能不同,这种情况下,你应该在上面的所有命令中更改它。

options 行必须设置足够高的 ndots,以便 DNS 客户端库考虑搜索路径。 在默认情况下,Kubernetes 将这个值设置为 5,这个值足够高,足以覆盖它生成的所有 DNS 名称。

是否存在 Service 能通过 DNS 名称访问?

如果上面的方式仍然失败,DNS 查找不到你需要的 Service ,你可以后退一步, 看看还有什么其它东西没有正常工作。 Kubernetes 主 Service 应该一直是工作的。在 Pod 中运行如下命令:

nslookup kubernetes.default
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes.default
Address 1: 10.0.0.1 kubernetes.default.svc.cluster.local

如果失败,你可能需要转到本文的 kube-proxy 节, 或者甚至回到文档的顶部重新开始,但不是调试你自己的 Service ,而是调试 DNS Service。

Service 能够通过 IP 访问么?

假设你已经确认 DNS 工作正常,那么接下来要测试的是你的 Service 能否通过它的 IP 正常访问。 从集群中的一个 Pod,尝试访问 Service 的 IP(从上面的 kubectl get 命令获取)。

for i in $(seq 1 3); do 
    wget -qO- 10.0.1.175:80
done

输出应该类似这样:

hostnames-632524106-bbpiw
hostnames-632524106-ly40y
hostnames-632524106-tlaok

如果 Service 状态是正常的,你应该得到正确的响应。 如果没有,有很多可能出错的地方,请继续阅读。

Service 的配置是否正确?

这听起来可能很愚蠢,但你应该两次甚至三次检查你的 Service 配置是否正确,并且与你的 Pod 匹配。 查看你的 Service 配置并验证它:

kubectl get service hostnames -o json
{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "hostnames",
        "namespace": "default",
        "uid": "428c8b6c-24bc-11e5-936d-42010af0a9bc",
        "resourceVersion": "347189",
        "creationTimestamp": "2015-07-07T15:24:29Z",
        "labels": {
            "app": "hostnames"
        }
    },
    "spec": {
        "ports": [
            {
                "name": "default",
                "protocol": "TCP",
                "port": 80,
                "targetPort": 9376,
                "nodePort": 0
            }
        ],
        "selector": {
            "app": "hostnames"
        },
        "clusterIP": "10.0.1.175",
        "type": "ClusterIP",
        "sessionAffinity": "None"
    },
    "status": {
        "loadBalancer": {}
    }
}
  • 你想要访问的 Service 端口是否在 spec.ports[] 中列出?
  • targetPort 对你的 Pod 来说正确吗(许多 Pod 使用与 Service 不同的端口)?
  • 如果你想使用数值型端口,那么它的类型是一个数值(9376)还是字符串 “9376”?
  • 如果你想使用名称型端口,那么你的 Pod 是否暴露了一个同名端口?
  • 端口的 protocol 和 Pod 的是否对应?

Service 有 Endpoints 吗?

如果你已经走到了这一步,你已经确认你的 Service 被正确定义,并能通过 DNS 解析。 现在,让我们检查一下,你运行的 Pod 确实是被 Service 选中的。

早些时候,我们已经看到 Pod 是运行状态。我们可以再检查一下:

kubectl get pods -l app=hostnames
NAME                        READY     STATUS    RESTARTS   AGE
hostnames-632524106-bbpiw   1/1       Running   0          1h
hostnames-632524106-ly40y   1/1       Running   0          1h
hostnames-632524106-tlaok   1/1       Running   0          1h

-l app=hostnames 参数是在 Service 上配置的标签选择器。

“AGE” 列表明这些 Pod 已经启动一个小时了,这意味着它们运行良好,而未崩溃。

"RESTARTS" 列表明 Pod 没有经常崩溃或重启。经常性崩溃可能导致间歇性连接问题。 如果重启次数过大,通过调试 Pod 了解相关技术。

在 Kubernetes 系统中有一个控制回路,它评估每个 Service 的选择算符,并将结果保存到 Endpoints 对象中。

kubectl get endpoints hostnames
NAME        ENDPOINTS
hostnames   10.244.0.5:9376,10.244.0.6:9376,10.244.0.7:9376

这证实 Endpoints 控制器已经为你的 Service 找到了正确的 Pods。 如果 ENDPOINTS 列的值为 <none>,则应检查 Service 的 spec.selector 字段, 以及你实际想选择的 Pod 的 metadata.labels 的值。 常见的错误是输入错误或其他错误,例如 Service 想选择 app=hostnames,但是 Deployment 指定的是 run=hostnames。在 1.18之前的版本中 kubectl run 也可以被用来创建 Deployment。

Pod 工作正常吗?

至此,你知道你的 Service 已存在,并且已匹配到你的Pod。在本实验的开始,你已经检查了 Pod 本身。 让我们再次检查 Pod 是否确实在工作 - 你可以绕过 Service 机制并直接转到 Pod, 如上面的 Endpoints 所示。

在 Pod 中运行:

for ep in 10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376; do
    wget -qO- $ep
done

输出应该类似这样:

hostnames-632524106-bbpiw
hostnames-632524106-ly40y
hostnames-632524106-tlaok

你希望 Endpoint 列表中的每个 Pod 都返回自己的主机名。 如果情况并非如此(或你自己的 Pod 的正确行为是什么),你应调查发生了什么事情。

kube-proxy 正常工作吗?

如果你到达这里,则说明你的 Service 正在运行,拥有 Endpoints,Pod 真正在提供服务。 此时,整个 Service 代理机制是可疑的。让我们一步一步地确认它没问题。

Service 的默认实现(在大多数集群上应用的)是 kube-proxy。 这是一个在每个节点上运行的程序,负责配置用于提供 Service 抽象的机制之一。 如果你的集群不使用 kube-proxy,则以下各节将不适用,你将必须检查你正在使用的 Service 的实现方式。

kube-proxy 正常运行吗? {#is-kube-proxy working}

确认 kube-proxy 正在节点上运行。在节点上直接运行,你将会得到类似以下的输出:

ps auxw | grep kube-proxy
root  4194  0.4  0.1 101864 17696 ?    Sl Jul04  25:43 /usr/local/bin/kube-proxy --master=https://kubernetes-master --kubeconfig=/var/lib/kube-proxy/kubeconfig --v=2

下一步,确认它并没有出现明显的失败,比如连接主节点失败。要做到这一点,你必须查看日志。 访问日志的方式取决于你节点的操作系统。 在某些操作系统上日志是一个文件,如 /var/log/messages kube-proxy.log, 而其他操作系统使用 journalctl 访问日志。你应该看到输出类似于:

I1027 22:14:53.995134    5063 server.go:200] Running in resource-only container "/kube-proxy"
I1027 22:14:53.998163    5063 server.go:247] Using iptables Proxier.
I1027 22:14:54.038140    5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns-tcp" to [10.244.1.3:53]
I1027 22:14:54.038164    5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns" to [10.244.1.3:53]
I1027 22:14:54.038209    5063 proxier.go:352] Setting endpoints for "default/kubernetes:https" to [10.240.0.2:443]
I1027 22:14:54.038238    5063 proxier.go:429] Not syncing iptables until Services and Endpoints have been received from master
I1027 22:14:54.040048    5063 proxier.go:294] Adding new service "default/kubernetes:https" at 10.0.0.1:443/TCP
I1027 22:14:54.040154    5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns" at 10.0.0.10:53/UDP
I1027 22:14:54.040223    5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns-tcp" at 10.0.0.10:53/TCP

如果你看到有关无法连接主节点的错误消息,则应再次检查节点配置和安装步骤。

kube-proxy 无法正确运行的可能原因之一是找不到所需的 conntrack 二进制文件。 在一些 Linux 系统上,这也是可能发生的,这取决于你如何安装集群, 例如,你是手动开始一步步安装 Kubernetes。如果是这样的话,你需要手动安装 conntrack 包(例如,在 Ubuntu 上使用 sudo apt install conntrack),然后重试。

Kube-proxy 可以以若干模式之一运行。在上述日志中,Using iptables Proxier 行表示 kube-proxy 在 "iptables" 模式下运行。 最常见的另一种模式是 "ipvs"。

Iptables 模式

在 "iptables" 模式中, 你应该可以在节点上看到如下输出:

iptables-save | grep hostnames
-A KUBE-SEP-57KPRZ3JQVENLNBR -s 10.244.3.6/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-57KPRZ3JQVENLNBR -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.3.6:9376
-A KUBE-SEP-WNBA2IHDGP2BOBGZ -s 10.244.1.7/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-WNBA2IHDGP2BOBGZ -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.1.7:9376
-A KUBE-SEP-X3P2623AGDH6CDF3 -s 10.244.2.3/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-X3P2623AGDH6CDF3 -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.2.3:9376
-A KUBE-SERVICES -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames: cluster IP" -m tcp --dport 80 -j KUBE-SVC-NWV5X2332I4OT4T3
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-WNBA2IHDGP2BOBGZ
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-X3P2623AGDH6CDF3
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -j KUBE-SEP-57KPRZ3JQVENLNBR

对于每个 Service 的每个端口,应有 1 条 KUBE-SERVICES 规则、一个 KUBE-SVC-<hash> 链。 对于每个 Pod 末端,在那个 KUBE-SVC-<hash> 链中应该有一些规则与之对应,还应该 有一个 KUBE-SEP-<hash> 链与之对应,其中包含为数不多的几条规则。 实际的规则数量可能会根据你实际的配置(包括 NodePort 和 LoadBalancer 服务)有所不同。

IPVS 模式

在 "ipvs" 模式中, 你应该在节点下看到如下输出:

ipvsadm -ln
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
...
TCP  10.0.1.175:80 rr
  -> 10.244.0.5:9376               Masq    1      0          0
  -> 10.244.0.6:9376               Masq    1      0          0
  -> 10.244.0.7:9376               Masq    1      0          0
...

对于每个 Service 的每个端口,还有 NodePort,External IP 和 LoadBalancer 类型服务 的 IP,kube-proxy 将创建一个虚拟服务器。 对于每个 Pod 末端,它将创建相应的真实服务器。 在此示例中,服务主机名(10.0.1.175:80)拥有 3 个末端(10.244.0.5:937610.244.0.6:937610.244.0.7:9376)。

kube-proxy 是否在执行代理操作?

假设你确实遇到上述情况之一,请重试从节点上通过 IP 访问你的 Service :

curl 10.0.1.175:80
hostnames-632524106-bbpiw

如果这步操作仍然失败,请查看 kube-proxy 日志中的特定行,如:

Setting endpoints for default/hostnames:default to [10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376]

如果你没有看到这些,请尝试将 -v 标志设置为 4 并重新启动 kube-proxy,然后再查看日志。

边缘案例: Pod 无法通过 Service IP 连接到它本身

这听起来似乎不太可能,但是确实可能发生,并且应该可以工作。

如果网络没有为“发夹模式(Hairpin)”流量生成正确配置, 通常当 kube-proxyiptables 模式运行,并且 Pod 与桥接网络连接时,就会发生这种情况。 kubelet 提供了 hairpin-mode 标志。 如果 Service 的末端尝试访问自己的 Service VIP,则该端点可以把流量负载均衡回来到它们自身。 hairpin-mode 标志必须被设置为 hairpin-veth 或者 promiscuous-bridge

诊断此类问题的常见步骤如下:

  • 确认 hairpin-mode 被设置为 hairpin-vethpromiscuous-bridge。 你应该可以看到下面这样。本例中 hairpin-mode 被设置为 promiscuous-bridge

    ps auxw | grep kubelet
    
    root      3392  1.1  0.8 186804 65208 ?        Sl   00:51  11:11 /usr/local/bin/kubelet --enable-debugging-handlers=true --config=/etc/kubernetes/manifests --allow-privileged=True --v=4 --cluster-dns=10.0.0.10 --cluster-domain=cluster.local --configure-cbr0=true --cgroup-root=/ --system-cgroups=/system --hairpin-mode=promiscuous-bridge --runtime-cgroups=/docker-daemon --kubelet-cgroups=/kubelet --babysit-daemons=true --max-pods=110 --serialize-image-pulls=false --outofdisk-transition-frequency=0
    
  • 确认有效的 hairpin-mode。要做到这一点,你必须查看 kubelet 日志。 访问日志取决于节点的操作系统。在一些操作系统上,它是一个文件,如 /var/log/kubelet.log, 而其他操作系统则使用 journalctl 访问日志。请注意,由于兼容性, 有效的 hairpin-mode 可能不匹配 --hairpin-mode 标志。在 kubelet.log 中检查是否有带有关键字 hairpin 的日志行。应该有日志行指示有效的 hairpin-mode,就像下面这样。

    I0629 00:51:43.648698    3252 kubelet.go:380] Hairpin mode set to "promiscuous-bridge"
    
  • 如果有效的发夹模式是 hairpin-veth, 要保证 Kubelet 有操作节点上 /sys 的权限。 如果一切正常,你将会看到如下输出:

    for intf in /sys/devices/virtual/net/cbr0/brif/*; do cat $intf/hairpin_mode; done
    
    1
    1
    1
    1
    
  • 如果有效的发卡模式是 promiscuous-bridge, 要保证 Kubelet 有操作节点上 Linux 网桥的权限。如果 cbr0 桥正在被使用且被正确设置,你将会看到如下输出:

    ifconfig cbr0 |grep PROMISC
    
    UP BROADCAST RUNNING PROMISC MULTICAST  MTU:1460  Metric:1
    
  • 如果以上步骤都不能解决问题,请寻求帮助。

寻求帮助

如果你走到这一步,那么就真的是奇怪的事情发生了。你的 Service 正在运行,有 Endpoints 存在, 你的 Pods 也确实在提供服务。你的 DNS 正常,iptables 规则已经安装,kube-proxy 看起来也正常。 然而 Service 还是没有正常工作。这种情况下,请告诉我们,以便我们可以帮助调查!

通过 Slack 或者 Forum 或者 GitHub 联系我们。

接下来

访问故障排查概述文档获取更多信息。

3 - 调试 StatefulSet

此任务展示如何调试 StatefulSet。

准备开始

  • 你需要有一个 Kubernetes 集群,已配置好的 kubectl 命令行工具与你的集群进行通信。
  • 你应该有一个运行中的 StatefulSet,以便用于调试。

调试 StatefulSet

StatefulSet 在创建 Pod 时为其设置了 app.kubernetes.io/name=MyApp 标签,列出仅属于某 StatefulSet 的所有 Pod 时,可以使用以下命令:

kubectl get pods -l app.kubernetes.io/name=MyApp

如果你发现列出的任何 Pod 长时间处于 UnknownTerminating 状态,请参阅 删除 StatefulSet Pod 了解如何处理它们的说明。 你可以参考调试 Pod 来调试 StatefulSet 中的各个 Pod。

接下来

进一步了解如何调试 Init 容器

4 - 确定 Pod 失败的原因

本文介绍如何编写和读取容器的终止消息。

终止消息为容器提供了一种方法,可以将有关致命事件的信息写入某个位置, 在该位置可以通过仪表板和监控软件等工具轻松检索和显示致命事件。 在大多数情况下,你放入终止消息中的信息也应该写入 常规 Kubernetes 日志

准备开始

你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

读写终止消息

在本练习中,你将创建运行一个容器的 Pod。 配置文件指定在容器启动时要运行的命令。

apiVersion: v1
kind: Pod
metadata:
  name: termination-demo
spec:
  containers:
  - name: termination-demo-container
    image: debian
    command: ["/bin/sh"]
    args: ["-c", "sleep 10 && echo Sleep expired > /dev/termination-log"]
  1. 基于 YAML 配置文件创建 Pod:

    kubectl apply -f https://k8s.io/examples/debug/termination.yaml   
    

    YAML 文件中,在 commandargs 字段,你可以看到容器休眠 10 秒然后将 "Sleep expired" 写入 /dev/termination-log 文件。 容器写完 "Sleep expired" 消息后就终止了。

  1. 显示 Pod 的信息:

    kubectl get pod termination-demo
    

    重复前面的命令直到 Pod 不再运行。

  1. 显示 Pod 的详细信息:

    kubectl get pod termination-demo --output=yaml
    

    输出结果包含 "Sleep expired" 消息:

    apiVersion: v1
    kind: Pod
    ...
        lastState:
          terminated:
            containerID: ...
            exitCode: 0
            finishedAt: ...
            message: |
              Sleep expired          
            ...
    
  1. 使用 Go 模板过滤输出结果,使其只含有终止消息:

    kubectl get pod termination-demo -o go-template="{{range .status.containerStatuses}}{{.lastState.terminated.message}}{{end}}"
    

    如果你正在运行多容器 Pod,则可以使用 Go 模板来包含容器的名称。这样,你可以发现哪些容器出现故障:

    kubectl get pod multi-container-pod -o go-template='{{range .status.containerStatuses}}{{printf "%s:\n%s\n\n" .name .lastState.terminated.message}}{{end}}'
    

定制终止消息

Kubernetes 从容器的 terminationMessagePath 字段中指定的终止消息文件中检索终止消息, 默认值为 /dev/termination-log。 通过定制这个字段,你可以告诉 Kubernetes 使用不同的文件。 Kubernetes 使用指定文件中的内容在成功和失败时填充容器的状态消息。

终止消息旨在简要说明最终状态,例如断言失败消息。 kubelet 会截断长度超过 4096 字节的消息。

所有容器的总消息长度限制为 12KiB,将会在每个容器之间平均分配。 例如,如果有 12 个容器(initContainerscontainers), 每个容器都有 1024 字节的可用终止消息空间。

默认的终止消息路径是 /dev/termination-log。 Pod 启动后不能设置终止消息路径。

在下例中,容器将终止消息写入 /tmp/my-log 给 Kubernetes 来检索:

apiVersion: v1
kind: Pod
metadata:
  name: msg-path-demo
spec:
  containers:
  - name: msg-path-demo-container
    image: debian
    terminationMessagePath: "/tmp/my-log"

此外,用户可以设置容器的 terminationMessagePolicy 字段,以便进一步自定义。 此字段默认为 "File",这意味着仅从终止消息文件中检索终止消息。 通过将 terminationMessagePolicy 设置为 "FallbackToLogsOnError",你就可以告诉 Kubernetes,在容器因错误退出时,如果终止消息文件为空,则使用容器日志输出的最后一块作为终止消息。 日志输出限制为 2048 字节或 80 行,以较小者为准。

接下来

5 - 调试 Init 容器

此页显示如何核查与 Init 容器执行相关的问题。 下面的示例命令行将 Pod 称为 <pod-name>,而 Init 容器称为 <init-container-1><init-container-2>

准备开始

你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

检查 Init 容器的状态

显示你的 Pod 的状态:

kubectl get pod <pod-name>

例如,状态 Init:1/2 表明两个 Init 容器中的一个已经成功完成:

NAME         READY     STATUS     RESTARTS   AGE
<pod-name>   0/1       Init:1/2   0          7s

更多状态值及其含义请参考理解 Pod 的状态

获取 Init 容器详情

查看 Init 容器运行的更多详情:

kubectl describe pod <pod-name>

例如,对于包含两个 Init 容器的 Pod 可能显示如下信息:

Init Containers:
  <init-container-1>:
    Container ID:    ...
    ...
    State:           Terminated
      Reason:        Completed
      Exit Code:     0
      Started:       ...
      Finished:      ...
    Ready:           True
    Restart Count:   0
    ...
  <init-container-2>:
    Container ID:    ...
    ...
    State:           Waiting
      Reason:        CrashLoopBackOff
    Last State:      Terminated
      Reason:        Error
      Exit Code:     1
      Started:       ...
      Finished:      ...
    Ready:           False
    Restart Count:   3
    ...

你还可以通过编程方式读取 Pod Spec 上的 status.initContainerStatuses 字段,了解 Init 容器的状态:

kubectl get pod nginx --template '{{.status.initContainerStatuses}}'

此命令将返回与原始 JSON 中相同的信息.

通过 Init 容器访问日志

与 Pod 名称一起传递 Init 容器名称,以访问容器的日志。

kubectl logs <pod-name> -c <init-container-2>

运行 Shell 脚本的 Init 容器在执行 Shell 脚本时输出命令本身。 例如,你可以在 Bash 中通过在脚本的开头运行 set -x 来实现。

理解 Pod 的状态

Init: 开头的 Pod 状态汇总了 Init 容器执行的状态。 下表介绍调试 Init 容器时可能看到的一些状态值示例。

状态含义
Init:N/MPod 包含 M 个 Init 容器,其中 N 个已经运行完成。
Init:ErrorInit 容器已执行失败。
Init:CrashLoopBackOffInit 容器执行总是失败。
PendingPod 还没有开始执行 Init 容器。
PodInitializing or RunningPod 已经完成执行 Init 容器。

6 - 调试运行中的 Pod

本页解释如何在节点上调试运行中(或崩溃)的 Pod。

准备开始

  • 你的 Pod 应该已经被调度并正在运行中, 如果你的 Pod 还没有运行,请参阅调试 Pod

  • 对于一些高级调试步骤,你应该知道 Pod 具体运行在哪个节点上,并具有在该节点上运行命令的 shell 访问权限。 你不需要任何访问权限就可以使用 kubectl 去运行一些标准调试步骤。

使用 kubectl describe pod 命令获取 Pod 详情

与之前的例子类似,我们使用一个 Deployment 来创建两个 Pod。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        resources:
          limits:
            memory: "128Mi"
            cpu: "500m"
        ports:
        - containerPort: 80

使用如下命令创建 Deployment:

kubectl apply -f https://k8s.io/examples/application/nginx-with-request.yaml
deployment.apps/nginx-deployment created

使用如下命令查看 Pod 状态:

kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-67d4bdd6f5-cx2nz   1/1     Running   0          13s
nginx-deployment-67d4bdd6f5-w6kd7   1/1     Running   0          13s

我们可以使用 kubectl describe pod 命令来查询每个 Pod 的更多信息,比如:

kubectl describe pod nginx-deployment-67d4bdd6f5-w6kd7
Name:         nginx-deployment-67d4bdd6f5-w6kd7
Namespace:    default
Priority:     0
Node:         kube-worker-1/192.168.0.113
Start Time:   Thu, 17 Feb 2022 16:51:01 -0500
Labels:       app=nginx
              pod-template-hash=67d4bdd6f5
Annotations:  <none>
Status:       Running
IP:           10.88.0.3
IPs:
  IP:           10.88.0.3
  IP:           2001:db8::1
Controlled By:  ReplicaSet/nginx-deployment-67d4bdd6f5
Containers:
  nginx:
    Container ID:   containerd://5403af59a2b46ee5a23fb0ae4b1e077f7ca5c5fb7af16e1ab21c00e0e616462a
    Image:          nginx
    Image ID:       docker.io/library/nginx@sha256:2834dc507516af02784808c5f48b7cbe38b8ed5d0f4837f16e78d00deb7e7767
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Thu, 17 Feb 2022 16:51:05 -0500
    Ready:          True
    Restart Count:  0
    Limits:
      cpu:     500m
      memory:  128Mi
    Requests:
      cpu:        500m
      memory:     128Mi
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-bgsgp (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  kube-api-access-bgsgp:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   Guaranteed
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  34s   default-scheduler  Successfully assigned default/nginx-deployment-67d4bdd6f5-w6kd7 to kube-worker-1
  Normal  Pulling    31s   kubelet            Pulling image "nginx"
  Normal  Pulled     30s   kubelet            Successfully pulled image "nginx" in 1.146417389s
  Normal  Created    30s   kubelet            Created container nginx
  Normal  Started    30s   kubelet            Started container nginx

在这里,你可以看到有关容器和 Pod 的配置信息(标签、资源需求等), 以及有关容器和 Pod 的状态信息(状态、就绪、重启计数、事件等)。

容器状态是 Waiting、Running 和 Terminated 之一。 根据状态的不同,还有对应的额外的信息 —— 在这里你可以看到, 对于处于运行状态的容器,系统会告诉你容器的启动时间。

Ready 指示是否通过了最后一个就绪态探测。 (在本例中,容器没有配置就绪态探测;如果没有配置就绪态探测,则假定容器已经就绪。)

Restart Count 告诉你容器已重启的次数; 这些信息对于定位配置了 “Always” 重启策略的容器持续崩溃问题非常有用。

目前,唯一与 Pod 有关的状态是 Ready 状况,该状况表明 Pod 能够为请求提供服务, 并且应该添加到相应服务的负载均衡池中。

最后,你还可以看到与 Pod 相关的近期事件。 “From” 标明记录事件的组件, “Reason” 和 “Message” 告诉你发生了什么。

例子: 调试 Pending 状态的 Pod

可以使用事件来调试的一个常见的场景是,你创建 Pod 无法被调度到任何节点。 比如,Pod 请求的资源比较多,没有任何一个节点能够满足,或者它指定了一个标签,没有节点可匹配。 假定我们创建之前的 Deployment 时指定副本数是 5(不再是 2),并且请求 600 毫核(不再是 500), 对于一个 4 个节点的集群,若每个节点只有 1 个 CPU,这时至少有一个 Pod 不能被调度。 (需要注意的是,其他集群插件 Pod,比如 fluentd、skydns 等等会在每个节点上运行, 如果我们需求 1000 毫核,将不会有 Pod 会被调度。)

kubectl get pods
NAME                                READY     STATUS    RESTARTS   AGE
nginx-deployment-1006230814-6winp   1/1       Running   0          7m
nginx-deployment-1006230814-fmgu3   1/1       Running   0          7m
nginx-deployment-1370807587-6ekbw   1/1       Running   0          1m
nginx-deployment-1370807587-fg172   0/1       Pending   0          1m
nginx-deployment-1370807587-fz9sd   0/1       Pending   0          1m

为了查找 Pod nginx-deployment-1370807587-fz9sd 没有运行的原因,我们可以使用 kubectl describe pod 命令描述 Pod,查看其事件:

kubectl describe pod nginx-deployment-1370807587-fz9sd
  Name:		nginx-deployment-1370807587-fz9sd
  Namespace:	default
  Node:		/
  Labels:		app=nginx,pod-template-hash=1370807587
  Status:		Pending
  IP:
  Controllers:	ReplicaSet/nginx-deployment-1370807587
  Containers:
    nginx:
      Image:	nginx
      Port:	80/TCP
      QoS Tier:
        memory:	Guaranteed
        cpu:	Guaranteed
      Limits:
        cpu:	1
        memory:	128Mi
      Requests:
        cpu:	1
        memory:	128Mi
      Environment Variables:
  Volumes:
    default-token-4bcbi:
      Type:	Secret (a volume populated by a Secret)
      SecretName:	default-token-4bcbi
  Events:
    FirstSeen	LastSeen	Count	From			        SubobjectPath	Type		Reason			    Message
    ---------	--------	-----	----			        -------------	--------	------			    -------
    1m		    48s		    7	    {default-scheduler }			        Warning		FailedScheduling	pod (nginx-deployment-1370807587-fz9sd) failed to fit in any node
  fit failure on node (kubernetes-node-6ta5): Node didn't have enough resource: CPU, requested: 1000, used: 1420, capacity: 2000
  fit failure on node (kubernetes-node-wul5): Node didn't have enough resource: CPU, requested: 1000, used: 1100, capacity: 2000

这里你可以看到由调度器记录的事件,它表明了 Pod 不能被调度的原因是 FailedScheduling(也可能是其他值)。 其 message 部分表明没有任何节点拥有足够多的资源。

要纠正这种情况,可以使用 kubectl scale 更新 Deployment,以指定 4 个或更少的副本。 (或者你可以让 Pod 继续保持这个状态,这是无害的。)

你在 kubectl describe pod 结尾处看到的事件都保存在 etcd 中, 并提供关于集群中正在发生的事情的高级信息。 如果需要列出所有事件,可使用命令:

kubectl get events

但是,需要注意的是,事件是区分名字空间的。 如果你对某些名字空间域的对象(比如 my-namespace 名字下的 Pod)的事件感兴趣, 你需要显式地在命令行中指定名字空间:

kubectl get events --namespace=my-namespace

查看所有 namespace 的事件,可使用 --all-namespaces 参数。

除了 kubectl describe pod 以外,另一种获取 Pod 额外信息(除了 kubectl get pod)的方法 是给 kubectl get pod 增加 -o yaml 输出格式参数。 该命令将以 YAML 格式为你提供比 kubectl describe pod 更多的信息 —— 实际上是系统拥有的关于 Pod 的所有信息。 在这里,你将看到注解(没有标签限制的键值元数据,由 Kubernetes 系统组件在内部使用)、 重启策略、端口和卷等。

kubectl get pod nginx-deployment-1006230814-6winp -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2022-02-17T21:51:01Z"
  generateName: nginx-deployment-67d4bdd6f5-
  labels:
    app: nginx
    pod-template-hash: 67d4bdd6f5
  name: nginx-deployment-67d4bdd6f5-w6kd7
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: nginx-deployment-67d4bdd6f5
    uid: 7d41dfd4-84c0-4be4-88ab-cedbe626ad82
  resourceVersion: "1364"
  uid: a6501da1-0447-4262-98eb-c03d4002222e
spec:
  containers:
  - image: nginx
    imagePullPolicy: Always
    name: nginx
    ports:
    - containerPort: 80
      protocol: TCP
    resources:
      limits:
        cpu: 500m
        memory: 128Mi
      requests:
        cpu: 500m
        memory: 128Mi
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-bgsgp
      readOnly: true
  dnsPolicy: ClusterFirst
  enableServiceLinks: true
  nodeName: kube-worker-1
  preemptionPolicy: PreemptLowerPriority
  priority: 0
  restartPolicy: Always
  schedulerName: default-scheduler
  securityContext: {}
  serviceAccount: default
  serviceAccountName: default
  terminationGracePeriodSeconds: 30
  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
  volumes:
  - name: kube-api-access-bgsgp
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2022-02-17T21:51:01Z"
    status: "True"
    type: Initialized
  - lastProbeTime: null
    lastTransitionTime: "2022-02-17T21:51:06Z"
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2022-02-17T21:51:06Z"
    status: "True"
    type: ContainersReady
  - lastProbeTime: null
    lastTransitionTime: "2022-02-17T21:51:01Z"
    status: "True"
    type: PodScheduled
  containerStatuses:
  - containerID: containerd://5403af59a2b46ee5a23fb0ae4b1e077f7ca5c5fb7af16e1ab21c00e0e616462a
    image: docker.io/library/nginx:latest
    imageID: docker.io/library/nginx@sha256:2834dc507516af02784808c5f48b7cbe38b8ed5d0f4837f16e78d00deb7e7767
    lastState: {}
    name: nginx
    ready: true
    restartCount: 0
    started: true
    state:
      running:
        startedAt: "2022-02-17T21:51:05Z"
  hostIP: 192.168.0.113
  phase: Running
  podIP: 10.88.0.3
  podIPs:
  - ip: 10.88.0.3
  - ip: 2001:db8::1
  qosClass: Guaranteed
  startTime: "2022-02-17T21:51:01Z"

检查 Pod 的日志

首先,查看受到影响的容器的日志:

kubectl logs ${POD_NAME} ${CONTAINER_NAME}

如果你的容器之前崩溃过,你可以通过下面命令访问之前容器的崩溃日志:

kubectl logs --previous ${POD_NAME} ${CONTAINER_NAME}

使用容器 exec 进行调试

如果 容器镜像 包含调试程序, 比如从 Linux 和 Windows 操作系统基础镜像构建的镜像,你可以使用 kubectl exec 命令 在特定的容器中运行一些命令:

kubectl exec ${POD_NAME} -c ${CONTAINER_NAME} -- ${CMD} ${ARG1} ${ARG2} ... ${ARGN}

例如,要查看正在运行的 Cassandra Pod 中的日志,可以运行:

kubectl exec cassandra -- cat /var/log/cassandra/system.log

你可以在 kubectl exec 命令后面加上 -i-t 来运行一个连接到你的终端的 Shell,比如:

kubectl exec -it cassandra -- sh

若要了解更多内容,可查看获取正在运行容器的 Shell

使用临时调试容器来进行调试

特性状态: Kubernetes v1.25 [stable]

当由于容器崩溃或容器镜像不包含调试程序(例如无发行版镜像等) 而导致 kubectl exec 无法运行时,临时容器对于排除交互式故障很有用。

使用临时容器来调试的例子

你可以使用 kubectl debug 命令来给正在运行中的 Pod 增加一个临时容器。 首先,像示例一样创建一个 pod:

kubectl run ephemeral-demo --image=registry.k8s.io/pause:3.1 --restart=Never

本节示例中使用 pause 容器镜像,因为它不包含调试程序,但是这个方法适用于所有容器镜像。

如果你尝试使用 kubectl exec 来创建一个 shell,你将会看到一个错误,因为这个容器镜像中没有 shell。

kubectl exec -it ephemeral-demo -- sh
OCI runtime exec failed: exec failed: container_linux.go:346: starting container process caused "exec: \"sh\": executable file not found in $PATH": unknown

你可以改为使用 kubectl debug 添加调试容器。 如果你指定 -i 或者 --interactive 参数,kubectl 将自动挂接到临时容器的控制台。

kubectl debug -it ephemeral-demo --image=busybox:1.28 --target=ephemeral-demo
Defaulting debug container name to debugger-8xzrl.
If you don't see a command prompt, try pressing enter.
/ #

此命令添加一个新的 busybox 容器并将其挂接到该容器。--target 参数指定另一个容器的进程命名空间。 这个指定进程命名空间的操作是必需的,因为 kubectl run 不能在它创建的 Pod 中启用共享进程命名空间

你可以使用 kubectl describe 查看新创建的临时容器的状态:

kubectl describe pod ephemeral-demo
...
Ephemeral Containers:
  debugger-8xzrl:
    Container ID:   docker://b888f9adfd15bd5739fefaa39e1df4dd3c617b9902082b1cfdc29c4028ffb2eb
    Image:          busybox
    Image ID:       docker-pullable://busybox@sha256:1828edd60c5efd34b2bf5dd3282ec0cc04d47b2ff9caa0b6d4f07a21d1c08084
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Wed, 12 Feb 2020 14:25:42 +0100
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:         <none>
...

使用 kubectl delete 来移除已经结束掉的 Pod:

kubectl delete pod ephemeral-demo

通过 Pod 副本调试

有些时候 Pod 的配置参数使得在某些情况下很难执行故障排查。 例如,在容器镜像中不包含 shell 或者你的应用程序在启动时崩溃的情况下, 就不能通过运行 kubectl exec 来排查容器故障。 在这些情况下,你可以使用 kubectl debug 来创建 Pod 的副本,通过更改配置帮助调试。

在添加新的容器时创建 Pod 副本

当应用程序正在运行但其表现不符合预期时,你会希望在 Pod 中添加额外的调试工具, 这时添加新容器是很有用的。

例如,应用的容器镜像是建立在 busybox 的基础上, 但是你需要 busybox 中并不包含的调试工具。 你可以使用 kubectl run 模拟这个场景:

kubectl run myapp --image=busybox:1.28 --restart=Never -- sleep 1d

通过运行以下命令,建立 myapp 的一个名为 myapp-debug 的副本, 新增了一个用于调试的 Ubuntu 容器,

kubectl debug myapp -it --image=ubuntu --share-processes --copy-to=myapp-debug
Defaulting debug container name to debugger-w7xmf.
If you don't see a command prompt, try pressing enter.
root@myapp-debug:/#

不要忘了清理调试 Pod:

kubectl delete pod myapp myapp-debug

在改变 Pod 命令时创建 Pod 副本

有时更改容器的命令很有用,例如添加调试标志或因为应用崩溃。

为了模拟应用崩溃的场景,使用 kubectl run 命令创建一个立即退出的容器:

kubectl run --image=busybox:1.28 myapp -- false

使用 kubectl describe pod myapp 命令,你可以看到容器崩溃了:

Containers:
  myapp:
    Image:         busybox
    ...
    Args:
      false
    State:          Waiting
      Reason:       CrashLoopBackOff
    Last State:     Terminated
      Reason:       Error
      Exit Code:    1

你可以使用 kubectl debug 命令创建该 Pod 的一个副本, 在该副本中命令改变为交互式 shell:

kubectl debug myapp -it --copy-to=myapp-debug --container=myapp -- sh
If you don't see a command prompt, try pressing enter.
/ #

现在你有了一个可以执行类似检查文件系统路径或者手动运行容器命令的交互式 shell。

不要忘了清理调试 Pod:

kubectl delete pod myapp myapp-debug

在更改容器镜像时拷贝 Pod

在某些情况下,你可能想要改动一个行为异常的 Pod,即从其正常的生产容器镜像更改为包含调试构建程序或其他实用程序的镜像。

下面的例子,用 kubectl run 创建一个 Pod:

kubectl run myapp --image=busybox:1.28 --restart=Never -- sleep 1d

现在可以使用 kubectl debug 创建一个拷贝并将其容器镜像更改为 ubuntu

kubectl debug myapp --copy-to=myapp-debug --set-image=*=ubuntu

--set-imagecontainer_name=image 使用相同的 kubectl set image 语法。 *=ubuntu 表示把所有容器的镜像改为 ubuntu

kubectl delete pod myapp myapp-debug

在节点上通过 shell 来进行调试

如果这些方法都不起作用,你可以找到运行 Pod 的节点,然后创建一个 Pod 运行在该节点上。 你可以通过 kubectl debug 在节点上创建一个交互式 Shell:

kubectl debug node/mynode -it --image=ubuntu
Creating debugging pod node-debugger-mynode-pdx84 with container debugger on node mynode.
If you don't see a command prompt, try pressing enter.
root@ek8s:/#

当在节点上创建调试会话,注意以下要点:

  • kubectl debug 基于节点的名字自动生成新的 Pod 的名字。
  • 节点的根文件系统会被挂载在 /host
  • 新的调试容器运行在主机 IPC 名字空间、主机网络名字空间以及主机 PID 名字空间内, Pod 没有特权,因此读取某些进程信息可能会失败,并且 chroot /host 也可能会失败。
  • 如果你需要一个特权 Pod,需要手动创建或使用 --profile=sysadmin 标志。

当你完成节点调试时,不要忘记清理调试 Pod:

kubectl delete pod node-debugger-mynode-pdx84

指定应用配置来调试 Pod 或节点

使用 kubectl debug 通过调试 Pod 来调试节点、通过临时容器来调试 Pod 或者调试复制的 Pod 时, 你可以为其应用配置。通过应用配置,可以设置特定的属性(如 securityContext), 以适应各种场景。有两种配置:静态配置和自定义配置。

应用静态配置

静态配置是预定义的属性集,你可以使用 --profile 标志来应用这些属性。 可用的配置如下:

配置描述
legacy一组与 1.22 行为向后兼容的属性
general一组对大多数调试过程而言均合理的通用属性
baseline一组与 PodSecurityStandard Baseline 策略兼容的属性
restricted一组与 PodSecurityStandard Restricted 策略兼容的属性
netadmin一组包含网络管理员特权的属性
sysadmin一组包含系统管理员(root)特权的属性

假设你要创建一个 Pod 并进行调试。 先创建一个名为 myapp 的 Pod 作为示例:

kubectl run myapp --image=busybox:1.28 --restart=Never -- sleep 1d

然后,使用临时容器调试 Pod。 如果临时容器需要具有特权,你可以使用 sysadmin 配置:

kubectl debug -it myapp --image=busybox:1.28 --target=myapp --profile=sysadmin
Targeting container "myapp". If you don't see processes from this container it may be because the container runtime doesn't support this feature.
Defaulting debug container name to debugger-6kg4x.
If you don't see a command prompt, try pressing enter.
/ #

通过在容器内运行以下命令检查临时容器进程的权能:

/ # grep Cap /proc/$$/status
...
CapPrm:	000001ffffffffff
CapEff:	000001ffffffffff
...

这意味着通过应用 sysadmin 配置,容器进程被授予了作为特权容器的全部权能。 更多细节参见权能

你还可以检查临时容器是否被创建为特权容器:

kubectl get pod myapp -o jsonpath='{.spec.ephemeralContainers[0].securityContext}'
{"privileged":true}

你在完成上述操作后,可运行以下命令清理 Pod:

kubectl delete pod myapp

应用自定义配置

特性状态: Kubernetes v1.31 [beta]

你可以以 YAML 或 JSON 格式定义部分容器规约作为自定义配置, 并使用 --custom 标志来应用自定义配置。

创建一个名为 myapp 的 Pod 作为示例:

kubectl run myapp --image=busybox:1.28 --restart=Never -- sleep 1d

以 YAML 或 JSON 格式创建自定义配置。 以下创建一个名为 custom-profile.yaml 的 YAML 格式文件:

env:
- name: ENV_VAR_1
  value: value_1
- name: ENV_VAR_2
  value: value_2
securityContext:
  capabilities:
    add:
    - NET_ADMIN
    - SYS_TIME

运行以下命令,使用带自定义配置的临时容器来调试 Pod:

kubectl debug -it myapp --image=busybox:1.28 --target=myapp --profile=general --custom=custom-profile.yaml

你可以检查临时容器和应用的自定义配置是否已被添加到目标 Pod:

kubectl get pod myapp -o jsonpath='{.spec.ephemeralContainers[0].env}'
[{"name":"ENV_VAR_1","value":"value_1"},{"name":"ENV_VAR_2","value":"value_2"}]
kubectl get pod myapp -o jsonpath='{.spec.ephemeralContainers[0].securityContext}'
{"capabilities":{"add":["NET_ADMIN","SYS_TIME"]}}

你在完成上述任务后,可以执行以下命令清理 Pod:

kubectl delete pod myapp

7 - 获取正在运行容器的 Shell

本文介绍怎样使用 kubectl exec 命令获取正在运行容器的 Shell。

准备开始

你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

获取容器的 Shell

在本练习中,你将创建包含一个容器的 Pod。容器运行 nginx 镜像。下面是 Pod 的配置文件:

apiVersion: v1
kind: Pod
metadata:
  name: shell-demo
spec:
  volumes:
  - name: shared-data
    emptyDir: {}
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: shared-data
      mountPath: /usr/share/nginx/html
  hostNetwork: true
  dnsPolicy: Default

创建 Pod:

kubectl apply -f https://k8s.io/examples/application/shell-demo.yaml

检查容器是否运行正常:

kubectl get pod shell-demo

获取正在运行容器的 Shell:

kubectl exec --stdin --tty shell-demo -- /bin/bash

在 shell 中,打印根目录:

# 在容器内运行如下命令
ls /

在 shell 中,实验其他命令。下面是一些示例:

# 你可以在容器中运行这些示例命令
ls /
cat /proc/mounts
cat /proc/1/maps
apt-get update
apt-get install -y tcpdump
tcpdump
apt-get install -y lsof
lsof
apt-get install -y procps
ps aux
ps aux | grep nginx

编写 nginx 的根页面

再看一下 Pod 的配置文件。该 Pod 有个 emptyDir 卷,容器将该卷挂载到了 /usr/share/nginx/html

在 shell 中,在 /usr/share/nginx/html 目录创建一个 index.html 文件:

# 在容器内运行如下命令
echo 'Hello shell demo' > /usr/share/nginx/html/index.html

在 shell 中,向 nginx 服务器发送 GET 请求:

# 在容器内运行如下命令
apt-get update
apt-get install curl
curl http://localhost/

输出结果显示了你在 index.html 中写入的文本。

Hello shell demo

当用完 shell 后,输入 exit 退出。

exit # 快速退出容器内的 Shell

在容器中运行单个命令

在普通的命令窗口(而不是 shell)中,打印环境运行容器中的变量:

kubectl exec shell-demo -- env

实验运行其他命令。下面是一些示例:

kubectl exec shell-demo -- ps aux
kubectl exec shell-demo -- ls /
kubectl exec shell-demo -- cat /proc/1/mounts

当 Pod 包含多个容器时打开 shell

如果 Pod 有多个容器,--container 或者 -c 可以在 kubectl exec 命令中指定容器。 例如,你有个名为 my-pod 的 Pod,该 Pod 有两个容器分别为 main-apphealper-app。 下面的命令将会打开一个 shell 访问 main-app 容器。

kubectl exec -i -t my-pod --container main-app -- /bin/bash

接下来