1 - 使用 crictl 对 Kubernetes 节点进行调试

FEATURE STATE: Kubernetes v1.11 [stable]

crictl 是 CRI 兼容的容器运行时命令行接口。 你可以使用它来检查和调试 Kubernetes 节点上的容器运行时和应用程序。 crictl 和它的源代码在 cri-tools 代码库。

准备开始

crictl 需要带有 CRI 运行时的 Linux 操作系统。

安装 crictl

你可以从 cri-tools 发布页面 下载一个压缩的 crictl 归档文件,用于几种不同的架构。 下载与你的 kubernetes 版本相对应的版本。 提取它并将其移动到系统路径上的某个位置,例如/usr/local/bin/

一般用法

crictl 命令有几个子命令和运行时参数。 有关详细信息,请使用 crictl helpcrictl <subcommand> help 获取帮助信息。

crictl 默认连接到 unix:///var/run/dockershim.sock。 对于其他的运行时,你可以用多种不同的方法设置端点:

  • 通过设置参数 --runtime-endpoint--image-endpoint
  • 通过设置环境变量 CONTAINER_RUNTIME_ENDPOINTIMAGE_SERVICE_ENDPOINT
  • 通过在配置文件中设置端点 --config=/etc/crictl.yaml

你还可以在连接到服务器并启用或禁用调试时指定超时值,方法是在配置文件中指定 timeoutdebug 值,或者使用 --timeout--debug 命令行参数。

要查看或编辑当前配置,请查看或编辑 /etc/crictl.yaml 的内容。

cat /etc/crictl.yaml
runtime-endpoint: unix:///var/run/dockershim.sock
image-endpoint: unix:///var/run/dockershim.sock
timeout: 10
debug: true

crictl 命令示例

警告:

如果使用 crictl 在正在运行的 Kubernetes 集群上创建 Pod 沙盒或容器, kubelet 最终将删除它们。 crictl 不是一个通用的工作流工具,而是一个对调试有用的工具。

打印 Pod 清单

打印所有 Pod 的清单:

crictl pods
POD ID              CREATED              STATE               NAME                         NAMESPACE           ATTEMPT
926f1b5a1d33a       About a minute ago   Ready               sh-84d7dcf559-4r2gq          default             0
4dccb216c4adb       About a minute ago   Ready               nginx-65899c769f-wv2gp       default             0
a86316e96fa89       17 hours ago         Ready               kube-proxy-gblk4             kube-system         0
919630b8f81f1       17 hours ago         Ready               nvidia-device-plugin-zgbbv   kube-system         0

根据名称打印 Pod 清单:

crictl pods --name nginx-65899c769f-wv2gp
POD ID              CREATED             STATE               NAME                     NAMESPACE           ATTEMPT
4dccb216c4adb       2 minutes ago       Ready               nginx-65899c769f-wv2gp   default             0

根据标签打印 Pod 清单:

crictl pods --label run=nginx
POD ID              CREATED             STATE               NAME                     NAMESPACE           ATTEMPT
4dccb216c4adb       2 minutes ago       Ready               nginx-65899c769f-wv2gp   default             0

打印镜像清单

打印所有镜像清单:

crictl images
IMAGE                                     TAG                 IMAGE ID            SIZE
busybox                                   latest              8c811b4aec35f       1.15MB
k8s-gcrio.azureedge.net/hyperkube-amd64   v1.10.3             e179bbfe5d238       665MB
k8s-gcrio.azureedge.net/pause-amd64       3.1                 da86e6ba6ca19       742kB
nginx                                     latest              cd5239a0906a6       109MB

根据仓库打印镜像清单:

crictl images nginx
IMAGE               TAG                 IMAGE ID            SIZE
nginx               latest              cd5239a0906a6       109MB

只打印镜像 ID:

crictl images -q
sha256:8c811b4aec35f259572d0f79207bc0678df4c736eeec50bc9fec37ed936a472a
sha256:e179bbfe5d238de6069f3b03fccbecc3fb4f2019af741bfff1233c4d7b2970c5
sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e
sha256:cd5239a0906a6ccf0562354852fae04bc5b52d72a2aff9a871ddb6bd57553569

打印容器清单

打印所有容器清单:

crictl ps -a
CONTAINER ID        IMAGE                                                                                                             CREATED             STATE               NAME                       ATTEMPT
1f73f2d81bf98       busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47                                   7 minutes ago       Running             sh                         1
9c5951df22c78       busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47                                   8 minutes ago       Exited              sh                         0
87d3992f84f74       nginx@sha256:d0a8828cccb73397acb0073bf34f4d7d8aa315263f1e7806bf8c55d8ac139d5f                                     8 minutes ago       Running             nginx                      0
1941fb4da154f       k8s-gcrio.azureedge.net/hyperkube-amd64@sha256:00d814b1f7763f4ab5be80c58e98140dfc69df107f253d7fdd714b30a714260a   18 hours ago        Running             kube-proxy                 0

打印正在运行的容器清单:

crictl ps
CONTAINER ID        IMAGE                                                                                                             CREATED             STATE               NAME                       ATTEMPT
1f73f2d81bf98       busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47                                   6 minutes ago       Running             sh                         1
87d3992f84f74       nginx@sha256:d0a8828cccb73397acb0073bf34f4d7d8aa315263f1e7806bf8c55d8ac139d5f                                     7 minutes ago       Running             nginx                      0
1941fb4da154f       k8s-gcrio.azureedge.net/hyperkube-amd64@sha256:00d814b1f7763f4ab5be80c58e98140dfc69df107f253d7fdd714b30a714260a   17 hours ago        Running             kube-proxy                 0

在正在运行的容器上执行命令

crictl exec -i -t 1f73f2d81bf98 ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var

获取容器日志

获取容器的所有日志:

crictl logs 87d3992f84f74
10.240.0.96 - - [06/Jun/2018:02:45:49 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"
10.240.0.96 - - [06/Jun/2018:02:45:50 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"
10.240.0.96 - - [06/Jun/2018:02:45:51 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"

获取最近的 N 行日志:

crictl logs --tail=1 87d3992f84f74
10.240.0.96 - - [06/Jun/2018:02:45:51 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"

运行 Pod 沙盒

crictl 运行 Pod 沙盒对容器运行时排错很有帮助。 在运行的 Kubernetes 集群中,沙盒会随机地被 kubelet 停止和删除。

  1. 编写下面的 JSON 文件:

    {
        "metadata": {
            "name": "nginx-sandbox",
            "namespace": "default",
            "attempt": 1,
            "uid": "hdishd83djaidwnduwk28bcsb"
        },
        "logDirectory": "/tmp",
        "linux": {
        }
    }
    
  2. 使用 crictl runp 命令应用 JSON 文件并运行沙盒。

    crictl runp pod-config.json
    

    返回了沙盒的 ID。

创建容器

crictl 创建容器对容器运行时排错很有帮助。 在运行的 Kubernetes 集群中,沙盒会随机的被 kubelet 停止和删除。

  1. 拉取 busybox 镜像

    crictl pull busybox
    Image is up to date for busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47
    
  2. 创建 Pod 和容器的配置:

    Pod 配置

    {
        "metadata": {
            "name": "nginx-sandbox",
            "namespace": "default",
            "attempt": 1,
            "uid": "hdishd83djaidwnduwk28bcsb"
        },
        "log_directory": "/tmp",
        "linux": {
        }
    }
    

    容器配置

    {
      "metadata": {
          "name": "busybox"
      },
      "image":{
          "image": "busybox"
      },
      "command": [
          "top"
      ],
      "log_path":"busybox.log",
      "linux": {
      }
    }
    
  3. 创建容器,传递先前创建的 Pod 的 ID、容器配置文件和 Pod 配置文件。返回容器的 ID。

    crictl create f84dd361f8dc51518ed291fbadd6db537b0496536c1d2d6c05ff943ce8c9a54f container-config.json pod-config.json
    
  4. 查询所有容器并确认新创建的容器状态为 Created

    crictl ps -a
    
    CONTAINER ID        IMAGE               CREATED             STATE               NAME                ATTEMPT
    3e025dd50a72d       busybox             32 seconds ago      Created             busybox             0
    

启动容器

要启动容器,要将容器 ID 传给 crictl start

crictl start 3e025dd50a72d956c4f14881fbb5b1080c9275674e95fb67f965f6478a957d60
3e025dd50a72d956c4f14881fbb5b1080c9275674e95fb67f965f6478a957d60

确认容器的状态为 Running

crictl ps
CONTAINER ID        IMAGE               CREATED              STATE               NAME                ATTEMPT
3e025dd50a72d       busybox             About a minute ago   Running             busybox             0

更多信息请参考 kubernetes-sigs/cri-tools

Docker CLI 和 crictl 的映射

以下的映射表格只适用于 Docker CLI v1.40 和 crictl v1.19.0 版本。 请注意该表格并不详尽。例如,其中不包含 Docker CLI 的实验性命令。

说明: 尽管有些命令的输出缺少了一些数据列,CRICTL 的输出格式与 Docker CLI 是类似的。 如果你的脚本程序需要解析命令的输出,请确认检查该特定命令的输出。
mapping from docker cli to crictl - retrieve debugging information
docker clicrictl描述不支持的功能
attachattach连接到一个运行中的容器--detach-keys, --sig-proxy
execexec在运行中的容器里运行一个命令--privileged, --user, --detach-keys
imagesimages列举镜像 
infoinfo显示系统级的信息 
inspectinspect, inspecti返回容器、镜像或者任务的详细信息 
logslogs获取容器的日志--details
psps列举容器 
statsstats实时显示容器的资源使用统计信息列:NET/BLOCK I/O, PIDs
versionversion显示运行时(Docker、ContainerD、或者其他) 的版本信息 
mapping from docker cli to crictl - perform changes
docker clicrictl描述不支持的功能
createcreate创建一个新的容器 
killstop (timeout=0)杀死一个或多个正在运行的容器--signal
pullpull从镜像仓库拉取镜像或者代码仓库--all-tags, --disable-content-trust
rmrm移除一个或多个容器 
rmirmi移除一个或多个镜像 
runrun在新容器里运行一个命令 
startstart启动一个或多个停止的容器--detach-keys
stopstop停止一个或多个正运行的容器 
updateupdate更新一个或多个容器的配置CRI 不支持 --restart--blkio-weight 以及一些其他的资源限制选项。
mapping from docker cli to crictl - supported only in crictl
crictl描述
imagefsinfo返回镜像的文件系统信息
inspectp显示一个或多个 Pod 的状态
port-forward转发本地端口到 Pod
pods列举 Pod
runp运行一个新的 Pod
rmp移除一个或多个 Pod
stopp停止一个或多个正运行的 Pod

2 - 使用 Stackdriver 生成日志

在阅读这篇文档之前,强烈建议你先熟悉一下 Kubernetes 日志概况

说明: 默认情况下,Stackdriver 日志机制仅收集容器的标准输出和标准错误流。 如果要收集你的应用程序写入一个文件(例如)的任何日志,请参见 Kubernetes 日志概述中的 sidecar 方式

部署

为了接收日志,你必须将 Stackdriver 日志代理部署到集群中的每个节点。 此代理是一个已配置的 fluentd,其配置存在一个 ConfigMap 中,且实例使用 Kubernetes 的 DaemonSet 进行管理。 ConfigMapDaemonSet 的实际部署,取决你的集群设置。

部署到一个新的集群

Google Kubernetes Engine

对于部署在 Google Kubernetes Engine 上的集群,Stackdriver 是默认的日志解决方案。 Stackdriver 日志机制会默认部署到你的新集群上,除非你明确地不选择。

其他平台

为了将 Stackdriver 日志机制部署到你正在使用 kube-up.sh 创建的集群上,执行如下操作:

  1. 设置环境变量 KUBE_LOGGING_DESTINATIONgcp
  2. 如果不是跑在 GCE 上,在 KUBE_NODE_LABELS 变量中包含 beta.kubernetes.io/fluentd-ds-ready=true

集群启动后,每个节点都应该运行 Stackdriver 日志代理。 DaemonSetConfigMap 作为附加组件进行配置。 如果你不是使用 kube-up.sh,可以考虑不使用预先配置的日志方案启动集群,然后部署 Stackdriver 日志代理到正在运行的集群。

警告: 除了 Google Kubernetes Engine,Stackdriver 日志守护进程在其他的平台有已知的问题。 请自行承担风险。

部署到一个已知集群

  1. 在每个节点上打标签(如果尚未存在)

    Stackdriver 日志代理部署使用节点标签来确定应该将其分配到给哪些节点。 引入这些标签是为了区分 Kubernetes 1.6 或更高版本的节点。 如果集群是在配置了 Stackdriver 日志机制的情况下创建的,并且节点的版本为 1.5.X 或更低版本,则它将使用 fluentd 用作静态容器。 节点最多只能有一个 fluentd 实例,因此只能将标签打在未分配过 fluentd pod 的节点上。 你可以通过运行 kubectl describe 来确保你的节点被正确标记,如下所示:

    kubectl describe node $NODE_NAME
    

    输出应类似于如下内容:

    Name:           NODE_NAME
    Role:
    Labels:         beta.kubernetes.io/fluentd-ds-ready=true
    ...
    

    确保输出内容包含 beta.kubernetes.io/fluentd-ds-ready=true 标签。 如果不存在,则可以使用 kubectl label 命令添加,如下所示:

    kubectl label node $NODE_NAME beta.kubernetes.io/fluentd-ds-ready=true
    
    说明: 如果节点发生故障并且必须重新创建,则必须将标签重新打在重新创建了的节点。 为了让此操作更便捷,你可以在节点启动脚本中使用 Kubelet 的命令行参数给节点添加标签。
  1. 通过运行以下命令,部署一个带有日志代理配置的 ConfigMap

    kubectl apply -f https://k8s.io/examples/debug/fluentd-gcp-configmap.yaml
    

    该命令在 default 命名空间中创建 ConfigMap。你可以在创建 ConfigMap 对象之前手动下载文件并进行更改。

  1. 通过运行以下命令,部署日志代理的 DaemonSet

    kubectl apply -f https://k8s.io/examples/debug/fluentd-gcp-ds.yaml
    

    你也可以在使用前下载和编辑此文件。

验证日志代理部署

部署 Stackdriver DaemonSet 之后,你可以通过运行以下命令来查看日志代理的部署状态:

kubectl get ds --all-namespaces

如果你的集群中有 3 个节点,则输出应类似于如下:

NAMESPACE     NAME               DESIRED   CURRENT   READY     NODE-SELECTOR                              AGE
...
default       fluentd-gcp-v2.0   3         3         3         beta.kubernetes.io/fluentd-ds-ready=true   5m
...

要了解使用 Stackdriver 进行日志记录的工作方式,请考虑以下具有日志生成的 pod 定义 counter-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox
    args: [/bin/sh, -c,
            'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']

这个 pod 定义里有一个容器,该容器运行一个 bash 脚本,脚本每秒写一次计数器的值和日期时间,并无限期地运行。 让我们在默认命名空间中创建此 pod。

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

你可以观察到正在运行的 pod:

kubectl get pods
NAME                                           READY     STATUS    RESTARTS   AGE
counter                                        1/1       Running   0          5m

在短时间内,你可以观察到 "pending" 的 pod 的状态,因为 kubelet 必须先下载容器镜像。 当 pod 状态变为 Running 时,你可以使用 kubectl logs 命令查看此 counter pod 的输出。

kubectl logs counter
0: Mon Jan  1 00:00:00 UTC 2001
1: Mon Jan  1 00:00:01 UTC 2001
2: Mon Jan  1 00:00:02 UTC 2001
...

正如日志概览所述,此命令从容器日志文件中获取日志项。 如果该容器被 Kubernetes 杀死然后重新启动,你仍然可以访问前一个容器的日志。 但是,如果将 Pod 从节点中驱逐,则日志文件会丢失。让我们通过删除当前运行的 counter 容器来演示这一点:

kubectl delete pod counter
pod "counter" deleted

然后重建它:

kubectl create -f https://k8s.io/examples/debug/counter-pod.yaml
pod/counter created

一段时间后,你可以再次从 counter pod 访问日志:

kubectl logs counter
0: Mon Jan  1 00:01:00 UTC 2001
1: Mon Jan  1 00:01:01 UTC 2001
2: Mon Jan  1 00:01:02 UTC 2001
...

如预期的那样,日志中仅出现最近的日志记录。 但是,对于实际应用程序,你可能希望能够访问所有容器的日志,特别是出于调试的目的。 这就是先前启用的 Stackdriver 日志机制可以提供帮助的地方。

查看日志

Stackdriver 日志代理为每个日志项关联元数据,供你在后续的查询中只选择感兴趣的消息: 例如,来自某个特定 Pod 的消息。

元数据最重要的部分是资源类型和日志名称。 容器日志的资源类型为 container,在用户界面中名为 GKE Containers(即使 Kubernetes 集群不在 Google Kubernetes Engine 上)。 日志名称是容器的名称,因此,如果你有一个包含两个容器的 pod,在 spec 中名称定义为 container_1container_2,则它们的日志的名称分别为 container_1container_2

系统组件的资源类型为 compute,在接口中名为 GCE VM Instance。 系统组件的日志名称是固定的。 对于 Google Kubernetes Engine 节点,系统组件中的每个日志项都具有以下日志名称之一:

  • docker
  • kubelet
  • kube-proxy

你可以在Stackdriver 专用页面 上了解有关查看日志的更多信息。

查看日志的一种可能方法是使用 Google Cloud SDK 中的 gcloud logging 命令行接口。 它使用 Stackdriver 日志机制的 过滤语法查询特定日志。 例如,你可以运行以下命令:

gcloud beta logging read 'logName="projects/$YOUR_PROJECT_ID/logs/count"' --format json | jq '.[].textPayload'
...
"2: Mon Jan  1 00:01:02 UTC 2001\n"
"1: Mon Jan  1 00:01:01 UTC 2001\n"
"0: Mon Jan  1 00:01:00 UTC 2001\n"
...
"2: Mon Jan  1 00:00:02 UTC 2001\n"
"1: Mon Jan  1 00:00:01 UTC 2001\n"
"0: Mon Jan  1 00:00:00 UTC 2001\n"

如你所见,尽管 kubelet 已经删除了第一个容器的日志,日志中仍会包含 counter 容器第一次和第二次运行时输出的消息。

导出日志

你可以将日志导出到 Google Cloud StorageBigQuery 进行进一步的分析。 Stackdriver 日志机制提供了接收器(Sink)的概念,你可以在其中指定日志项的存放地。 可在 Stackdriver 导出日志页面 上获得更多信息。

配置 Stackdriver 日志代理

有时默认的 Stackdriver 日志机制安装可能无法满足你的需求,例如:

  • 你可能需要添加更多资源,因为默认的行为表现无法满足你的需求。
  • 你可能需要引入额外的解析机制以便从日志消息中提取更多元数据,例如严重性或源代码引用。
  • 你可能想要将日志不仅仅发送到 Stackdriver 或仅将部分日志发送到 Stackdriver。

在这种情况下,你需要更改 DaemonSetConfigMap 的参数。

先决条件

如果使用的是 GKE,并且集群中启用了 Stackdriver 日志机制,则无法更改其配置, 因为它是由 GKE 管理和支持的。 但是,你可以禁用默认集成的日志机制并部署自己的。

说明: 你将需要自己支持和维护新部署的配置了:更新映像和配置、调整资源等等。

若要禁用默认的日志记录集成,请使用以下命令:

gcloud beta container clusters update --logging-service=none CLUSTER

你可以在部署部分中找到有关如何将 Stackdriver 日志代理安装到 正在运行的集群中的说明。

更改 DaemonSet 参数

当集群中有 Stackdriver 日志机制的 DaemonSet 时,你只需修改其 spec 中的 template 字段,DaemonSet 控制器将为你管理 Pod。 例如,假设你按照上面的描述已经安装了 Stackdriver 日志机制。 现在,你想更改内存限制,来给 fluentd 提供的更多内存,从而安全地处理更多日志。

获取集群中运行的 DaemonSet 的 spec:

kubectl get ds fluentd-gcp-v2.0 --namespace kube-system -o yaml > fluentd-gcp-ds.yaml

然后在 spec 文件中编辑资源需求,并使用以下命令更新 apiserver 中的 DaemonSet 对象:

kubectl replace -f fluentd-gcp-ds.yaml

一段时间后,Stackdriver 日志代理的 pod 将使用新配置重新启动。

更改 fluentd 参数

Fluentd 的配置存在 ConfigMap 对象中。 它实际上是一组合并在一起的配置文件。 你可以在官方网站上了解 fluentd 的配置。

假设你要向配置添加新的解析逻辑,以便 fluentd 可以理解默认的 Python 日志记录格式。 一个合适的 fluentd 过滤器类似如下:

<filter reform.**>
  type parser
  format /^(?<severity>\w):(?<logger_name>\w):(?<log>.*)/
  reserve_data true
  suppress_parse_error_log true
  key_name log
</filter>

现在,你需要将其放入配置中,并使 Stackdriver 日志代理感知它。 通过运行以下命令,获取集群中当前版本的 Stackdriver 日志机制的 ConfigMap

kubectl get cm fluentd-gcp-config --namespace kube-system -o yaml > fluentd-gcp-configmap.yaml

然后在 containers.input.conf 键的值中,在 source 部分之后插入一个新的过滤器。

说明: 顺序很重要。

在 apiserver 中更新 ConfigMap 比更新 DaemonSet 更复杂。 最好考虑 ConfigMap 是不可变的。 如果是这样,要更新配置,你应该使用新名称创建 ConfigMap,然后使用 上面的指南DaemonSet 更改为指向它。

添加 fluentd 插件

Fluentd 用 Ruby 编写,并允许使用 plugins 扩展其功能。 如果要使用默认的 Stackdriver 日志机制容器镜像中未包含的插件,则必须构建自定义镜像。 假设你要为来自特定容器添加 Kafka 信息接收器,以进行其他处理。 你可以复用默认的容器镜像源,并仅添加少量更改:

  • 将 Makefile 更改为指向你的容器仓库,例如 PREFIX=gcr.io/<your-project-id>
  • 将你的依赖项添加到 Gemfile 中,例如 gem 'fluent-plugin-kafka'

然后在该目录运行 make build push。 在更新 DaemonSet 以使用新镜像后,你就可以使用在 fluentd 配置中安装的插件了。

3 - 在本地开发和调试服务

Kubernetes 应用程序通常由多个独立的服务组成,每个服务都在自己的容器中运行。 在远端的 Kubernetes 集群上开发和调试这些服务可能很麻烦,需要 在运行的容器上打开 Shell, 然后在远端 Shell 中运行你所需的工具。

telepresence 是一种工具,用于在本地轻松开发和调试服务,同时将服务代理到远程 Kubernetes 集群。 使用 telepresence 可以为本地服务使用自定义工具(如调试器和 IDE), 并提供对 Configmap、Secret 和远程集群上运行的服务的完全访问。

本文档描述如何在本地使用 telepresence 开发和调试远程集群上运行的服务。

准备开始

  • Kubernetes 集群安装完毕
  • 配置好 kubectl 与集群交互
  • Telepresence 安装完毕

打开终端,不带参数运行 telepresence,以打开 telepresence Shell。 这个 Shell 在本地运行,使你可以完全访问本地文件系统。

telepresence Shell 的使用方式多种多样。 例如,在你的笔记本电脑上写一个 Shell 脚本,然后直接在 Shell 中实时运行它。 你也可以在远端 Shell 上执行此操作,但这样可能无法使用首选的代码编辑器,并且在容器终止时脚本将被删除。

开发和调试现有的服务

在 Kubernetes 上开发应用程序时,通常对单个服务进行编程或调试。 服务可能需要访问其他服务以进行测试和调试。 一种选择是使用连续部署流水线,但即使最快的部署流水线也会在程序或调试周期中引入延迟。

使用 --swap-deployment 选项将现有部署与 Telepresence 代理交换。 交换允许你在本地运行服务并能够连接到远端的 Kubernetes 集群。 远端集群中的服务现在就可以访问本地运行的实例。

要运行 telepresence 并带有 --swap-deployment 选项,请输入:

telepresence --swap-deployment $DEPLOYMENT_NAME

这里的 $DEPLOYMENT_NAME 是你现有的部署名称。

运行此命令将生成 Shell。在该 Shell 中,启动你的服务。 然后,你就可以在本地对源代码进行编辑、保存并能看到更改立即生效。 你还可以在调试器或任何其他本地开发工具中运行服务。

接下来

如果你对实践教程感兴趣,请查看本教程,其中介绍了在 Google Kubernetes Engine 上本地开发 Guestbook 应用程序。

Telepresence 有多种代理选项,以满足你的各种情况。

要了解更多信息,请访问 Telepresence 网站

4 - 审计

FEATURE STATE: Kubernetes v1.21 [beta]

Kubernetes 审计(Auditing) 功能提供了与安全相关的、按时间顺序排列的记录集, 记录每个用户、使用 Kubernetes API 的应用以及控制面自身引发的活动。

审计功能使得集群管理员能够回答以下问题:

  • 发生了什么?
  • 什么时候发生的?
  • 谁触发的?
  • 活动发生在哪个(些)对象上?
  • 在哪观察到的?
  • 它从哪触发的?
  • 活动的后续处理行为是什么?

审计记录最初产生于 kube-apiserver 内部。每个请求在不同执行阶段都会生成审计事件;这些审计事件会根据特定策略 被预处理并写入后端。策略确定要记录的内容和用来存储记录的后端。 当前的后端支持日志文件和 webhook。

每个请求都可被记录其相关的 阶段(stage)。已定义的阶段有:

  • RequestReceived - 此阶段对应审计处理器接收到请求后,并且在委托给 其余处理器之前生成的事件。
  • ResponseStarted - 在响应消息的头部发送后,响应消息体发送前生成的事件。 只有长时间运行的请求(例如 watch)才会生成这个阶段。
  • ResponseComplete - 当响应消息体完成并且没有更多数据需要传输的时候。
  • Panic - 当 panic 发生时生成。
说明: 审计事件配置 的配置与 Event API 对象不同。

审计日志记录功能会增加 API server 的内存消耗,因为需要为每个请求存储审计所需的某些上下文。 此外,内存消耗取决于审计日志记录的配置。

审计策略

审计政策定义了关于应记录哪些事件以及应包含哪些数据的规则。 审计策略对象结构定义在 audit.k8s.io API 组 处理事件时,将按顺序与规则列表进行比较。第一个匹配规则设置事件的 审计级别(Audit Level)。已定义的审计级别有:

  • None - 符合这条规则的日志将不会记录。
  • Metadata - 记录请求的元数据(请求的用户、时间戳、资源、动词等等), 但是不记录请求或者响应的消息体。
  • Request - 记录事件的元数据和请求的消息体,但是不记录响应的消息体。 这不适用于非资源类型的请求。
  • RequestResponse - 记录事件的元数据,请求和响应的消息体。这不适用于非资源类型的请求。

你可以使用 --audit-policy-file 标志将包含策略的文件传递给 kube-apiserver。 如果不设置该标志,则不记录事件。 注意 rules 字段 必须 在审计策略文件中提供。没有(0)规则的策略将被视为非法配置。

以下是一个审计策略文件的示例:

apiVersion: audit.k8s.io/v1 # This is required.
kind: Policy
# Don't generate audit events for all requests in RequestReceived stage.
omitStages:
  - "RequestReceived"
rules:
  # Log pod changes at RequestResponse level
  - level: RequestResponse
    resources:
    - group: ""
      # Resource "pods" doesn't match requests to any subresource of pods,
      # which is consistent with the RBAC policy.
      resources: ["pods"]
  # Log "pods/log", "pods/status" at Metadata level
  - level: Metadata
    resources:
    - group: ""
      resources: ["pods/log", "pods/status"]

  # Don't log requests to a configmap called "controller-leader"
  - level: None
    resources:
    - group: ""
      resources: ["configmaps"]
      resourceNames: ["controller-leader"]

  # Don't log watch requests by the "system:kube-proxy" on endpoints or services
  - level: None
    users: ["system:kube-proxy"]
    verbs: ["watch"]
    resources:
    - group: "" # core API group
      resources: ["endpoints", "services"]

  # Don't log authenticated requests to certain non-resource URL paths.
  - level: None
    userGroups: ["system:authenticated"]
    nonResourceURLs:
    - "/api*" # Wildcard matching.
    - "/version"

  # Log the request body of configmap changes in kube-system.
  - level: Request
    resources:
    - group: "" # core API group
      resources: ["configmaps"]
    # This rule only applies to resources in the "kube-system" namespace.
    # The empty string "" can be used to select non-namespaced resources.
    namespaces: ["kube-system"]

  # Log configmap and secret changes in all other namespaces at the Metadata level.
  - level: Metadata
    resources:
    - group: "" # core API group
      resources: ["secrets", "configmaps"]

  # Log all other resources in core and extensions at the Request level.
  - level: Request
    resources:
    - group: "" # core API group
    - group: "extensions" # Version of group should NOT be included.

  # A catch-all rule to log all other requests at the Metadata level.
  - level: Metadata
    # Long-running requests like watches that fall under this rule will not
    # generate an audit event in RequestReceived.
    omitStages:
      - "RequestReceived"

你可以使用最低限度的审计策略文件在 Metadata 级别记录所有请求:

# 在 Metadata 级别为所有请求生成日志
apiVersion: audit.k8s.io/v1beta1
kind: Policy
rules:
- level: Metadata

如果你在打磨自己的审计配置文件,你可以使用为 Google Container-Optimized OS 设计的审计配置作为出发点。你可以参考 configure-helper.sh 脚本,该脚本能够生成审计策略文件。你可以直接在脚本中看到审计策略的绝大部份内容。

你也可以参考 Policy 配置参考 以获取有关已定义字段的详细信息。

审计后端

审计后端实现将审计事件导出到外部存储。Kube-apiserver 默认提供两个后端:

  • Log 后端,将事件写入到文件系统
  • Webhook 后端,将事件发送到外部 HTTP API

在这所有情况下,审计事件均遵循 Kubernetes API 在 audit.k8s.io API 组 中定义的结构。

说明:

对于 patch 请求,请求的消息体需要是设定 patch 操作的 JSON 所构成的一个串, 而不是一个完整的 Kubernetes API 对象 JSON 串。 例如,以下的示例是一个合法的 patch 请求消息体,该请求对应 /apis/batch/v1/namespaces/some-namespace/jobs/some-job-name

[
  {
    "op": "replace",
    "path": "/spec/parallelism",
    "value": 0
  },
  {
    "op": "remove",
    "path": "/spec/template/spec/containers/0/terminationMessagePolicy"
  }
]

Log 后端

Log 后端将审计事件写入 JSONlines 格式的文件。 你可以使用以下 kube-apiserver 标志配置 Log 审计后端:

  • --audit-log-path 指定用来写入审计事件的日志文件路径。不指定此标志会禁用日志后端。- 意味着标准化
  • --audit-log-maxage 定义保留旧审计日志文件的最大天数
  • --audit-log-maxbackup 定义要保留的审计日志文件的最大数量
  • --audit-log-maxsize 定义审计日志文件的最大大小(兆字节)

如果你的集群控制面以 Pod 的形式运行 kube-apiserver,记得要通过 hostPath 卷来访问策略文件和日志文件所在的目录,这样审计记录才会持久保存下来。例如:

  --audit-policy-file=/etc/kubernetes/audit-policy.yaml
  --audit-log-path=/var/log/audit.log

接下来挂载数据卷:

volumeMounts:
  - mountPath: /etc/kubernetes/audit-policy.yaml
    name: audit
    readOnly: true
  - mountPath: /var/log/audit.log
    name: audit-log
    readOnly: false

最后配置 hostPath

- name: audit
  hostPath:
    path: /etc/kubernetes/audit-policy.yaml
    type: File

- name: audit-log
  hostPath:
    path: /var/log/audit.log
    type: FileOrCreate

Webhook 后端

Webhook 后端将审计事件发送到远程 Web API,该远程 API 应该暴露与 kube-apiserver 形式相同的 API,包括其身份认证机制。你可以使用如下 kube-apiserver 标志来配置 Webhook 审计后端:

  • --audit-webhook-config-file 设置 Webhook 配置文件的路径。Webhook 配置文件实际上是一个 kubeconfig 文件
  • --audit-webhook-initial-backoff 指定在第一次失败后重发请求等待的时间。随后的请求将以指数退避重试。

Webhook 配置文件使用 kubeconfig 格式指定服务的远程地址和用于连接它的凭据。

事件批处理

日志和 Webhook 后端都支持批处理。以 Webhook 为例,以下是可用参数列表。要获取日志 后端的同样参数,请在参数名称中将 webhook 替换为 log。 默认情况下,在 webhook 中批处理是被启用的,在 log 中批处理是被禁用的。 同样,默认情况下,在 webhook 中启用带宽限制,在 log 中禁用带宽限制。

  • --audit-webhook-mode 定义缓存策略,可选值如下:
    • batch - 以批处理缓存事件和异步的过程。这是默认值。
    • blocking - 在 API 服务器处理每个单独事件时,阻塞其响应。
    • blocking-strict - 与 blocking 相同,不过当审计日志在 RequestReceived 阶段 失败时,整个 API 服务请求会失效。

以下参数仅用于 batch 模式。

  • --audit-webhook-batch-buffer-size 定义 batch 之前要缓存的事件数。 如果传入事件的速率溢出缓存区,则会丢弃事件。
  • --audit-webhook-batch-max-size 定义一个 batch 中的最大事件数。
  • --audit-webhook-batch-max-wait 无条件 batch 队列中的事件前等待的最大事件。
  • --audit-webhook-batch-throttle-qps 每秒生成的最大批次数。
  • --audit-webhook-batch-throttle-burst 在达到允许的 QPS 前,同一时刻允许存在的最大 batch 生成数。

参数调整

需要设置参数以适应 API 服务器上的负载。

例如,如果 kube-apiserver 每秒收到 100 个请求,并且每个请求仅在 ResponseStartedResponseComplete 阶段进行审计,则应该考虑每秒生成约 200 个审计事件。 假设批处理中最多有 100 个事件,则应将限制级别设置为每秒至少 2 个查询。 假设后端最多需要 5 秒钟来写入事件,你应该设置缓冲区大小以容纳最多 5 秒的事件, 即 10 个 batch,即 1000 个事件。

但是,在大多数情况下,默认参数应该足够了,你不必手动设置它们。 你可以查看 kube-apiserver 公开的以下 Prometheus 指标,并在日志中监控审计子系统的状态。

  • apiserver_audit_event_total 包含所有暴露的审计事件数量的指标。
  • apiserver_audit_error_total 在暴露时由于发生错误而被丢弃的事件的数量。

日志条目截断

日志后端和 Webhook 后端都支持限制所输出的事件的尺寸。 例如,下面是可以为日志后端配置的标志列表:

  • audit-log-truncate-enabled:是否弃用事件和批次的截断处理。
  • audit-log-truncate-max-batch-size:向下层后端发送的各批次的最大尺寸字节数。
  • audit-log-truncate-max-event-size:向下层后端发送的审计事件的最大尺寸字节数。

默认情况下,截断操作在 webhooklog 后端都是被禁用的,集群管理员需要设置 audit-log-truncate-enabledaudit-webhook-truncate-enabled 标志来启用此操作。

接下来

5 - 应用故障排查

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

诊断问题

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

调试 Pods

调试 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 状态的最常见原因是拉取镜像失败。要检查的有三个方面:

  • 确保镜像名字拼写正确
  • 确保镜像已被推送到镜像仓库
  • 用手动命令 docker pull <镜像> 试试看镜像是否可拉取

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} 命令来检视副本控制器相关的事件。

调试服务

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

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

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

kubectl get endpoints ${SERVICE_NAME}

确保 Endpoints 与服务成员 Pod 个数一致。 例如,如果你的 Service 用来运行 3 个副本的 nginx 容器,你应该会在服务的 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 处于 Running 态,有 Endpoints 被创建,Pod 真的在提供服务; DNS 服务已配置并正常工作,iptables 规则也以安装并且 kube-proxy 也没有异常行为。

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

6 - 应用自测与调试

运行应用时,不可避免的需要定位问题。 前面我们介绍了如何使用 kubectl get pods 来查询 pod 的简单信息。 除此之外,还有一系列的方法来获取应用的更详细信息。

使用 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-1006230814-6winp   1/1       Running   0          11s
nginx-deployment-1006230814-fmgu3   1/1       Running   0          11s

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

kubectl describe pod nginx-deployment-1006230814-6winp
Name:		nginx-deployment-1006230814-6winp
Namespace:	default
Node:		kubernetes-node-wul5/10.240.0.9
Start Time:	Thu, 24 Mar 2016 01:39:49 +0000
Labels:		app=nginx,pod-template-hash=1006230814
Annotations:    kubernetes.io/created-by={"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"ReplicaSet","namespace":"default","name":"nginx-deployment-1956810328","uid":"14e607e7-8ba1-11e7-b5cb-fa16" ...
Status:		Running
IP:		10.244.0.6
Controllers:	ReplicaSet/nginx-deployment-1006230814
Containers:
  nginx:
    Container ID:	docker://90315cc9f513c724e9957a4788d3e625a078de84750f244a40f97ae355eb1149
    Image:		nginx
    Image ID:		docker://6f62f48c4e55d700cf3eb1b5e33fa051802986b77b874cc351cce539e5163707
    Port:		80/TCP
    QoS Tier:
      cpu:	Guaranteed
      memory:	Guaranteed
    Limits:
      cpu:	500m
      memory:	128Mi
    Requests:
      memory:		128Mi
      cpu:		500m
    State:		Running
      Started:		Thu, 24 Mar 2016 01:39:51 +0000
    Ready:		True
    Restart Count:	0
    Environment:        <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-5kdvl (ro)
Conditions:
  Type          Status
  Initialized   True
  Ready         True
  PodScheduled  True
Volumes:
  default-token-4bcbi:
    Type:	Secret (a volume populated by a Secret)
    SecretName:	default-token-4bcbi
    Optional:   false
QoS Class:      Guaranteed
Node-Selectors: <none>
Tolerations:    <none>
Events:
  FirstSeen	LastSeen	Count	From					SubobjectPath		Type		Reason		Message
  ---------	--------	-----	----					-------------		--------	------		-------
  54s		54s		1	{default-scheduler }						Normal		Scheduled	Successfully assigned nginx-deployment-1006230814-6winp to kubernetes-node-wul5
  54s		54s		1	{kubelet kubernetes-node-wul5}	spec.containers{nginx}	Normal		Pulling		pulling image "nginx"
  53s		53s		1	{kubelet kubernetes-node-wul5}	spec.containers{nginx}	Normal		Pulled		Successfully pulled image "nginx"
  53s		53s		1	{kubelet kubernetes-node-wul5}	spec.containers{nginx}	Normal		Created		Created container with docker id 90315cc9f513
  53s		53s		1	{kubelet kubernetes-node-wul5}	spec.containers{nginx}	Normal		Started		Started container with docker id 90315cc9f513

这里可以看到容器和 Pod 的标签、资源需求等配置信息,还可以看到状态、就绪态、 重启次数、事件等状态信息。

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

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

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

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

最后,你还可以看到与 Pod 相关的近期事件。 系统通过指示第一次和最后一次看到事件以及看到该事件的次数来压缩多个相同的事件。 “From” 标明记录事件的组件, “SubobjectPath” 告诉你引用了哪个对象(例如 Pod 中的容器), “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:
  annotations:
    kubernetes.io/created-by: |
            {"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"ReplicaSet","namespace":"default","name":"nginx-deployment-1006230814","uid":"4c84c175-f161-11e5-9a78-42010af00005","apiVersion":"extensions","resourceVersion":"133434"}}
  creationTimestamp: 2016-03-24T01:39:50Z
  generateName: nginx-deployment-1006230814-
  labels:
    app: nginx
    pod-template-hash: "1006230814"
  name: nginx-deployment-1006230814-6winp
  namespace: default
  resourceVersion: "133447"
  uid: 4c879808-f161-11e5-9a78-42010af00005
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
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-4bcbi
      readOnly: true
  dnsPolicy: ClusterFirst
  nodeName: kubernetes-node-wul5
  restartPolicy: Always
  securityContext: {}
  serviceAccount: default
  serviceAccountName: default
  terminationGracePeriodSeconds: 30
  volumes:
  - name: default-token-4bcbi
    secret:
      secretName: default-token-4bcbi
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: 2016-03-24T01:39:51Z
    status: "True"
    type: Ready
  containerStatuses:
  - containerID: docker://90315cc9f513c724e9957a4788d3e625a078de84750f244a40f97ae355eb1149
    image: nginx
    imageID: docker://6f62f48c4e55d700cf3eb1b5e33fa051802986b77b874cc351cce539e5163707
    lastState: {}
    name: nginx
    ready: true
    restartCount: 0
    state:
      running:
        startedAt: 2016-03-24T01:39:51Z
  hostIP: 10.240.0.9
  phase: Running
  podIP: 10.244.0.6
  startTime: 2016-03-24T01:39:49Z

示例:调试宕机或无法联系的节点

有时候,在调试时,查看节点的状态是很有用的 —— 例如,因为你已经注意到节点上运行的 Pod 的奇怪行为, 或者想了解为什么 Pod 不会调度到节点上。 与 Pod 一样,你可以使用 kubectl describe nodekubectl get node -o yaml 来查询节点的详细信息。 例如,如果某个节点宕机(与网络断开连接,或者 kubelet 挂掉无法重新启动等等),你将看到以下情况。 请注意显示节点未就绪的事件,也请注意 Pod 不再运行(它们在5分钟未就绪状态后被驱逐)。

kubectl get nodes
NAME                     STATUS       ROLES     AGE     VERSION
kubernetes-node-861h     NotReady     <none>    1h      v1.13.0
kubernetes-node-bols     Ready        <none>    1h      v1.13.0
kubernetes-node-st6x     Ready        <none>    1h      v1.13.0
kubernetes-node-unaj     Ready        <none>    1h      v1.13.0
kubectl describe node kubernetes-node-861h
Name:			kubernetes-node-861h
Role
Labels:		 kubernetes.io/arch=amd64
           kubernetes.io/os=linux
           kubernetes.io/hostname=kubernetes-node-861h
Annotations:        node.alpha.kubernetes.io/ttl=0
                    volumes.kubernetes.io/controller-managed-attach-detach=true
Taints:             <none>
CreationTimestamp:	Mon, 04 Sep 2017 17:13:23 +0800
Phase:
Conditions:
  Type		Status		LastHeartbeatTime			LastTransitionTime			Reason					Message
  ----    ------    -----------------     ------------------      ------          -------
  OutOfDisk             Unknown         Fri, 08 Sep 2017 16:04:28 +0800         Fri, 08 Sep 2017 16:20:58 +0800         NodeStatusUnknown       Kubelet stopped posting node status.
  MemoryPressure        Unknown         Fri, 08 Sep 2017 16:04:28 +0800         Fri, 08 Sep 2017 16:20:58 +0800         NodeStatusUnknown       Kubelet stopped posting node status.
  DiskPressure          Unknown         Fri, 08 Sep 2017 16:04:28 +0800         Fri, 08 Sep 2017 16:20:58 +0800         NodeStatusUnknown       Kubelet stopped posting node status.
  Ready                 Unknown         Fri, 08 Sep 2017 16:04:28 +0800         Fri, 08 Sep 2017 16:20:58 +0800         NodeStatusUnknown       Kubelet stopped posting node status.
Addresses:	10.240.115.55,104.197.0.26
Capacity:
 cpu:           2
 hugePages:     0
 memory:        4046788Ki
 pods:          110
Allocatable:
 cpu:           1500m
 hugePages:     0
 memory:        1479263Ki
 pods:          110
System Info:
 Machine ID:                    8e025a21a4254e11b028584d9d8b12c4
 System UUID:                   349075D1-D169-4F25-9F2A-E886850C47E3
 Boot ID:                       5cd18b37-c5bd-4658-94e0-e436d3f110e0
 Kernel Version:                4.4.0-31-generic
 OS Image:                      Debian GNU/Linux 8 (jessie)
 Operating System:              linux
 Architecture:                  amd64
 Container Runtime Version:     docker://1.12.5
 Kubelet Version:               v1.6.9+a3d1dfa6f4335
 Kube-Proxy Version:            v1.6.9+a3d1dfa6f4335
ExternalID:                     15233045891481496305
Non-terminated Pods:            (9 in total)
  Namespace                     Name                                            CPU Requests    CPU Limits      Memory Requests Memory Limits
  ---------                     ----                                            ------------    ----------      --------------- -------------
......
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  CPU Requests  CPU Limits      Memory Requests         Memory Limits
  ------------  ----------      ---------------         -------------
  900m (60%)    2200m (146%)    1009286400 (66%)        5681286400 (375%)
Events:         <none>
kubectl get node kubernetes-node-861h -o yaml
apiVersion: v1
kind: Node
metadata:
  creationTimestamp: 2015-07-10T21:32:29Z
  labels:
    kubernetes.io/hostname: kubernetes-node-861h
  name: kubernetes-node-861h
  resourceVersion: "757"
  selfLink: /api/v1/nodes/kubernetes-node-861h
  uid: 2a69374e-274b-11e5-a234-42010af0d969
spec:
  externalID: "15233045891481496305"
  podCIDR: 10.244.0.0/24
  providerID: gce://striped-torus-760/us-central1-b/kubernetes-node-861h
status:
  addresses:
  - address: 10.240.115.55
    type: InternalIP
  - address: 104.197.0.26
    type: ExternalIP
  capacity:
    cpu: "1"
    memory: 3800808Ki
    pods: "100"
  conditions:
  - lastHeartbeatTime: 2015-07-10T21:34:32Z
    lastTransitionTime: 2015-07-10T21:35:15Z
    reason: Kubelet stopped posting node status.
    status: Unknown
    type: Ready
  nodeInfo:
    bootID: 4e316776-b40d-4f78-a4ea-ab0d73390897
    containerRuntimeVersion: docker://Unknown
    kernelVersion: 3.16.0-0.bpo.4-amd64
    kubeProxyVersion: v0.21.1-185-gffc5a86098dc01
    kubeletVersion: v0.21.1-185-gffc5a86098dc01
    machineID: ""
    osImage: Debian GNU/Linux 7 (wheezy)
    systemUUID: ABE5F6B4-D44B-108B-C46A-24CCE16C8B6E

接下来

了解更多的调试工具:

7 - 故障诊断

有时候事情会出错。本指南旨在解决这些问题。它包含两个部分:

  • 应用排错 - 针对部署代码到 Kubernetes 并想知道代码为什么不能正常运行的用户。
  • 集群排错 - 针对集群管理员以及 Kubernetes 集群表现异常的用户。

你也应该查看所用发行版本的已知问题。

获取帮助

如果你的问题在上述指南中没有得到答案,你还有另外几种方式从 Kubernetes 团队获得帮助。

问题

本网站上的文档针对回答各类问题进行了结构化组织和分类。 概念部分解释 Kubernetes 体系结构以及每个组件的工作方式, 安装部分提供了安装的实用说明。 任务部分展示了如何完成常用任务, 教程部分则提供对现实世界、特定行业或端到端开发场景的更全面的演练。 参考部分提供了详细的 Kubernetes API 文档 和命令行 (CLI) 接口的文档,例如kubectl

求救!我的问题还没有解决!我现在需要帮助!

Stack Overflow

社区中的其他人可能已经问过和你类似的问题,也可能能够帮助解决你的问题。 Kubernetes 团队还会监视带有 Kubernetes 标签的帖子。 如果现有的问题对你没有帮助,请问一个新问题!

Slack

Kubernetes 社区中有很多人在 #kubernetes-users 这一 Slack 频道聚集。 Slack 需要注册;你可以请求一份邀请, 并且注册是对所有人开放的。欢迎你随时来问任何问题。 一旦注册了,就可以访问通过 Web 浏览器或者 Slack 专用的应用访问 Slack 上的 Kubernetes 组织

一旦你完成了注册,就可以浏览各种感兴趣主题的频道列表(一直在增长)。 例如,Kubernetes 新人可能还想加入 #kubernetes-novice 频道。又比如,开发人员应该加入 #kubernetes-dev 频道。

还有许多国家/地区语言频道。请随时加入这些频道以获得本地化支持和信息:

Country / language specific Slack channels
国家频道
中国#cn-users, #cn-events
芬兰#fi-users
法国#fr-users, #fr-events
德国#de-users, #de-events
印度#in-users, #in-events
意大利#it-users, #it-events
日本#jp-users, #jp-events
韩国#kr-users
荷兰#nl-users
挪威#norw-users
波兰#pl-users
俄罗斯#ru-users
西班牙#es-users
瑞典#se-users
土耳其#tr-users, #tr-events

论坛

欢迎你加入 Kubernetes 官方论坛 discuss.kubernetes.io

Bugs 和功能请求

如果你发现一个看起来像 Bug 的问题,或者你想提出一个功能请求,请使用 Github 问题跟踪系统

在提交问题之前,请搜索现有问题列表以查看是否其中已涵盖你的问题。

如果提交 Bug,请提供如何重现问题的详细信息,例如:

  • Kubernetes 版本:kubectl version
  • 云平台、OS 发行版、网络配置和 Docker 版本
  • 重现问题的步骤

8 - 确定 Pod 失败的原因

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

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

准备开始

你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。 如果你还没有集群,你可以通过 Minikube 构建一 个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:

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

读写终止消息

在本练习中,您将创建运行一个容器的 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 create -f https://k8s.io/examples/debug/termination.yaml
    

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

  2. 显示 Pod 的信息:
    kubectl get pod termination-demo
    

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

  3. 显示 Pod 的详细信息:

    kubectl get pod --output=yaml
    
    输出结果包含 "Sleep expired" 消息:
    apiVersion: v1
    kind: Pod
    ...
        lastState:
          terminated:
            containerID: ...
            exitCode: 0
            finishedAt: ...
            message: |
                        Sleep expired
            ...
    
  4. 使用 Go 模板过滤输出结果,使其只含有终止消息:

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

定制终止消息

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

在下例中,容器将终止消息写入 /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 行,以较小者为准。

接下来

9 - 节点健康监测

节点问题检测器(Node Problem Detector) 是一个守护程序,用于监视和报告节点的健康状况。 你可以将节点问题探测器以 DaemonSet 或独立守护程序运行。 节点问题检测器从各种守护进程收集节点问题,并以 NodeConditionEvent 的形式报告给 API 服务器。

要了解如何安装和使用节点问题检测器,请参阅 节点问题探测器项目文档

准备开始

你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。 如果你还没有集群,你可以通过 Minikube 构建一 个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:

局限性

  • 节点问题检测器只支持基于文件类型的内核日志。 它不支持像 journald 这样的命令行日志工具。
  • 节点问题检测器使用内核日志格式来报告内核问题。 要了解如何扩展内核日志格式,请参阅添加对另一个日志格式的支持

启用节点问题检测器

一些云供应商将节点问题检测器以插件形式启用。 你还可以使用 kubectl 或创建插件 Pod 来启用节点问题探测器。

使用 kubectl 启用节点问题检测器

kubectl 提供了节点问题探测器最灵活的管理。 你可以覆盖默认配置使其适合你的环境或检测自定义节点问题。例如:

  1. 创建类似于 node-strought-detector.yaml 的节点问题检测器配置:

    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      name: node-problem-detector-v0.1
      namespace: kube-system
      labels:
        k8s-app: node-problem-detector
        version: v0.1
        kubernetes.io/cluster-service: "true"
    spec:
      selector:
        matchLabels:
          k8s-app: node-problem-detector  
          version: v0.1
          kubernetes.io/cluster-service: "true"
      template:
        metadata:
          labels:
            k8s-app: node-problem-detector
            version: v0.1
            kubernetes.io/cluster-service: "true"
        spec:
          hostNetwork: true
          containers:
          - name: node-problem-detector
            image: k8s.gcr.io/node-problem-detector:v0.1
            securityContext:
              privileged: true
            resources:
              limits:
                cpu: "200m"
                memory: "100Mi"
              requests:
                cpu: "20m"
                memory: "20Mi"
            volumeMounts:
            - name: log
              mountPath: /log
              readOnly: true
          volumes:
          - name: log
            hostPath:
              path: /var/log/

    说明: 你应该检查系统日志目录是否适用于操作系统发行版本。
  2. 使用 kubectl 启动节点问题检测器:

    kubectl apply -f https://k8s.io/examples/debug/node-problem-detector.yaml
    

使用插件 pod 启用节点问题检测器

如果你使用的是自定义集群引导解决方案,不需要覆盖默认配置, 可以利用插件 Pod 进一步自动化部署。

创建 node-strick-detector.yaml,并在控制平面节点上保存配置到插件 Pod 的目录 /etc/kubernetes/addons/node-problem-detector

覆盖配置文件

构建节点问题检测器的 docker 镜像时,会嵌入 默认配置

不过,你可以像下面这样使用 ConfigMap 将其覆盖:

  1. 更改 config/ 中的配置文件

  2. 创建 ConfigMap node-strick-detector-config

    kubectl create configmap node-problem-detector-config --from-file=config/
    
  3. 更改 node-problem-detector.yaml 以使用 ConfigMap:

    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      name: node-problem-detector-v0.1
      namespace: kube-system
      labels:
        k8s-app: node-problem-detector
        version: v0.1
        kubernetes.io/cluster-service: "true"
    spec:
      selector:
        matchLabels:
          k8s-app: node-problem-detector  
          version: v0.1
          kubernetes.io/cluster-service: "true"
      template:
        metadata:
          labels:
            k8s-app: node-problem-detector
            version: v0.1
            kubernetes.io/cluster-service: "true"
        spec:
          hostNetwork: true
          containers:
          - name: node-problem-detector
            image: k8s.gcr.io/node-problem-detector:v0.1
            securityContext:
              privileged: true
            resources:
              limits:
                cpu: "200m"
                memory: "100Mi"
              requests:
                cpu: "20m"
                memory: "20Mi"
            volumeMounts:
            - name: log
              mountPath: /log
              readOnly: true
            - name: config # Overwrite the config/ directory with ConfigMap volume
              mountPath: /config
              readOnly: true
          volumes:
          - name: log
            hostPath:
              path: /var/log/
          - name: config # Define ConfigMap volume
            configMap:
              name: node-problem-detector-config
  4. 使用新的配置文件重新创建节点问题检测器:

    # 如果你正在运行节点问题检测器,请先删除,然后再重新创建
    kubectl delete -f https://k8s.io/examples/debug/node-problem-detector.yaml
    kubectl apply -f https://k8s.io/examples/debug/node-problem-detector-configmap.yaml
    
说明: 此方法仅适用于通过 kubectl 启动的节点问题检测器。

如果节点问题检测器作为集群插件运行,则不支持覆盖配置。 插件管理器不支持 ConfigMap

内核监视器

内核监视器(Kernel Monitor) 是节点问题检测器中支持的系统日志监视器守护进程。 内核监视器观察内核日志并根据预定义规则检测已知的内核问题。

内核监视器根据 config/kernel-monitor.json 中的一组预定义规则列表匹配内核问题。 规则列表是可扩展的,你始终可以通过覆盖配置来扩展它。

添加新的 NodeCondition

要支持新的 NodeCondition,请在 config/kernel-monitor.json 中的 conditions 字段中创建一个条件定义:

{
  "type": "NodeConditionType",
  "reason": "CamelCaseDefaultNodeConditionReason",
  "message": "arbitrary default node condition message"
}

检测新的问题

你可以使用新的规则描述来扩展 config/kernel-monitor.json 中的 rules 字段以检测新问题:

{
  "type": "temporary/permanent",
  "condition": "NodeConditionOfPermanentIssue",
  "reason": "CamelCaseShortReason",
  "message": "regexp matching the issue in the kernel log"
}

配置内核日志设备的路径

检查你的操作系统(OS)发行版本中的内核日志路径位置。 Linux 内核日志设备 通常呈现为 /dev/kmsg。 但是,日志路径位置因 OS 发行版本而异。 config/kernel-monitor.json 中的 log 字段表示容器内的日志路径。 你可以配置 log 字段以匹配节点问题检测器所示的设备路径。

添加对其它日志格式的支持

内核监视器使用 Translator 插件转换内核日志的内部数据结构。 你可以为新的日志格式实现新的转换器。

建议和限制

建议在集群中运行节点问题检测器以监控节点运行状况。 运行节点问题检测器时,你可以预期每个节点上的额外资源开销。 通常这是可接受的,因为:

  • 内核日志增长相对缓慢。
  • 已经为节点问题检测器设置了资源限制。
  • 即使在高负载下,资源使用也是可接受的。有关更多信息,请参阅节点问题检测器 基准结果

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

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

准备开始

你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。 如果你还没有集群,你可以通过 Minikube 构建一 个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:

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

获取容器的 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 create -f https://k8s.io/examples/application/shell-demo.yaml

检查容器是否运行正常:

kubectl get pod shell-demo

获取正在运行容器的 Shell:

kubectl exec -it shell-demo -- /bin/bash
说明:

双破折号 "--" 用于将要传递给命令的参数与 kubectl 的参数分开。

在 shell 中,打印根目录:

root@shell-demo:/# ls /

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

root@shell-demo:/# ls /
root@shell-demo:/# cat /proc/mounts
root@shell-demo:/# cat /proc/1/maps
root@shell-demo:/# apt-get update
root@shell-demo:/# apt-get install -y tcpdump
root@shell-demo:/# tcpdump
root@shell-demo:/# apt-get install -y lsof
root@shell-demo:/# lsof
root@shell-demo:/# apt-get install -y procps
root@shell-demo:/# ps aux
root@shell-demo:/# ps aux | grep nginx

编写 nginx 的 根页面

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

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

root@shell-demo:/# echo Hello shell demo > /usr/share/nginx/html/index.html

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

root@shell-demo:/# apt-get update
root@shell-demo:/# apt-get install curl
root@shell-demo:/# curl localhost

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

Hello shell demo

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

在容器中运行单个命令

在普通的命令窗口(而不是 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 有两个容器分别为 main-app 和 healper-app。 下面的命令将会打开一个 shell 访问 main-app 容器。

kubectl exec -it my-pod --container main-app -- /bin/bash

接下来

11 - 调试 Init 容器

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

准备开始

你必须拥有一个 Kubernetes 的集群,同时你的 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 容器。

12 - 调试 Pods 和 ReplicationControllers

此页面展示如何调试 Pod 和 ReplicationController。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。 如果你还没有集群,你可以通过 Minikube 构建一 个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:

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

调试 Pod

调试一个 pod 的第一步是观察它。使用下面的命令检查 Pod 的当前状态和最近事件:

kubectl describe pods ${POD_NAME}

看看 Pod 中的容器的状态。它们都是 Running 吗?最近有重启吗?

根据 Pod 的状态继续调试。

我的 Pod 停滞在 Pending 状态

如果 Pod 被卡在 Pending 状态,就意味着它不能调度在某个节点上。一般来说,这是因为某种类型的资源不足而 导致无法调度。 查看上面的命令 kubectl describe ... 的输出。调度器的消息中应该会包含无法调度 Pod 的原因。 原因包括:

资源不足

你可能已经耗尽了集群中供应的 CPU 或内存。在这个情况下你可以尝试几件事情:

  • 向集群中添加节点。

  • 终止不需要的 Pod 为 Pending 状态的 Pod 提供空间。

  • 检查该 Pod 是否不大于你的节点。例如,如果全部节点具有 cpu:1 容量,那么具有 请求为 cpu: 1.1 的 Pod 永远不会被调度。

    你可以使用 kubectl get nodes -o <format> 命令来检查节点容量。 下面是一些能够提取必要信息的命令示例:

    kubectl get nodes -o yaml | egrep '\sname:|cpu:|memory:'
    kubectl get nodes -o json | jq '.items[] | {name: .metadata.name, cap: .status.capacity}'
    

可以考虑配置资源配额 来限制可耗用的资源总量。 如果与命名空间一起使用,它可以防止一个团队吞噬所有的资源。

使用hostPort

当你将一个 Pod 绑定到某 hostPort 时,这个 Pod 能被调度的位置数量有限。 在大多数情况下,hostPort 是不必要的; 尝试使用服务对象来暴露你的 Pod。 如果你需要 hostPort,那么你可以调度的 Pod 数量不能超过集群的节点个数。

我的 Pod 一直在 Waiting

如果 Pod 一直停滞在 Waiting 状态,那么它已被调度在某个工作节点,但它不能在该机器上运行。 再次,来自 kubectl describe ... 的内容应该是可以是很有用的。 最常见的原因 Waiting 的 Pod 是无法拉取镜像。有三件事要检查:

  • 确保你的镜像的名称正确。
  • 你是否将镜像推送到存储库?
  • 在你的机器上手动运行 docker pull <image>,看看是否可以拉取镜像。

我的 Pod 一直 Crashing 或者其他不健康状态

一旦 Pod 已经被调度,就可以依据 调试运行中的 Pod 展开进一步的调试工作。

调试 Replication Controller

Replication Controller 相当简单。它们或者能或者不能创建 Pod。如果它们无法创建 Pod, 请参考上面的说明 来调试你的 Pod。

你也可以使用 kubectl describe rc ${CONTROLLER_NAME} 来检查和副本控制器有关的事件。

13 - 调试 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=k8s.gcr.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: k8s.gcr.io/serve_hostname

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

确认你的 Pods 是运行状态:

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:
  name: hostnames
spec:
  selector:
    app: hostnames
  ports:
  - name: default
    protocol: TCP
    port: 80
    targetPort: 9376

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

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" 是你的集群域,在你自己的集群中可能会有所不同。

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

说明: 10.0.0.10 是集群的 DNS 服务 IP,你的可能有所不同。
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,如上面的 Endpoint 所示。

说明: 这些命令使用的是 Pod 端口(9376),而不是 Service 端口(80)。

在 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 正常运行吗?

确认 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:53.999055    5063 server.go:255] Tearing down userspace rules. Errors here are acceptable.
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"。先前的 "userspace" 模式已经被这些所代替。

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)。

Userspace 模式

在极少数情况下,你可能会用到 "userspace" 模式。在你的节点上运行:

iptables-save | grep hostnames
-A KUBE-PORTALS-CONTAINER -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames:default" -m tcp --dport 80 -j REDIRECT --to-ports 48577
-A KUBE-PORTALS-HOST -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames:default" -m tcp --dport 80 -j DNAT --to-destination 10.240.115.247:48577

对于 Service (本例中只有一个)的每个端口,应当有 2 条规则: 一条 "KUBE-PORTALS-CONTAINER" 和一条 "KUBE-PORTALS-HOST" 规则。

几乎没有人应该再使用 "userspace" 模式,因此你在这里不会花更多的时间。

kube-proxy 是否在运行?

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

curl 10.0.1.175:80
hostnames-632524106-bbpiw

如果失败,并且你正在使用用户空间代理,则可以尝试直接访问代理。 如果你使用的是 iptables 代理,请跳过本节。

回顾上面的 iptables-save 输出,并提取 kube-proxy 为你的 Service 所使用的端口号。 在上面的例子中,端口号是 “48577”。现在试着连接它:

curl localhost:48577
hostnames-632524106-tlaok

如果这步操作仍然失败,请查看 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 联系我们。

接下来

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

14 - 调试StatefulSet

此任务展示如何调试 StatefulSet。

准备开始

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

调试 StatefulSet

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

kubectl get pods -l app=myapp

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

接下来

15 - 调试运行中的 Pod

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

准备开始

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

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

检查 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}
说明: -c ${CONTAINER_NAME} 是可选择的。如果Pod中仅包含一个容器,就可以忽略它。

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

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

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

kubectl exec -it cassandra -- sh

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

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

FEATURE STATE: Kubernetes v1.18 [alpha]

当由于容器崩溃或容器镜像不包含调试程序(例如无发行版镜像等) 而导致 kubectl exec 无法运行时,临时容器对于排除交互式故障很有用。 从 'v1.18' 版本开始,'kubectl' 有一个可以创建用于调试的临时容器的 alpha 命令。

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

说明: 本示例需要你的集群已经开启 EphemeralContainers 特性门控kubectl 版本为 v1.18 或者更高。

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

kubectl run ephemeral-demo --image=k8s.gcr.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 --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中启用 共享进程命名空间

说明: 容器运行时必须支持--target参数。 如果不支持,则临时容器可能不会启动,或者可能使用隔离的进程命名空间启动。

你可以使用 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 --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:/#
说明:
  • 如果你没有使用 --container 指定新的容器名,kubectl debug 会自动生成的。
  • 默认情况下,-i 标志使 kubectl debug 附加到新容器上。 你可以通过指定 --attach=false 来防止这种情况。 如果你的会话断开连接,你可以使用 kubectl attach 重新连接。
  • --share-processes 允许在此 Pod 中的其他容器中查看该容器的进程。 参阅在 Pod 中的容器之间共享进程命名空间 获取更多信息。

不要忘了清理调试 Pod:

kubectl delete pod myapp myapp-debug

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

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

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

kubectl run --image=busybox 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。

说明:
  • 要更改指定容器的命令,你必须用 --container 命令指定容器的名字, 否则 kubectl debug 将建立一个新的容器运行你指定的命令。
  • 默认情况下,标志 -i 使 kubectl debug 附加到容器。 你可通过指定 --attach=false 来防止这种情况。 如果你的断开连接,可以使用 kubectl attach 重新连接。

不要忘了清理调试 Pod:

kubectl delete pod myapp myapp-debug

在更改容器镜像时创建 Pod 副本

在某些情况下,你可能想从正常生产容器镜像中 把行为异常的 Pod 改变为包含调试版本或者附加应用的镜像。

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

kubectl run myapp --image=busybox --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 的主机并通过 SSH 进入该主机, 但是如果使用 Kubernetes API 中的工具,则通常不需要这样做。 因此,如果你发现自己需要使用 ssh 进入主机,请在GitHub 上提交功能请求, 以描述你的用例以及这些工具不足的原因。

16 - 资源指标管道

资源使用指标,例如容器 CPU 和内存使用率,可通过 Metrics API 在 Kubernetes 中获得。 这些指标可以直接被用户访问,比如使用 kubectl top 命令行,或者被集群中的控制器 (例如 Horizontal Pod Autoscalers) 使用来做决策。

Metrics API

通过 Metrics API,你可以获得指定节点或 Pod 当前使用的资源量。 此 API 不存储指标值,因此想要获取某个指定节点 10 分钟前的 资源使用量是不可能的。

此 API 与其他 API 没有区别:

  • 此 API 和其它 Kubernetes API 一起位于同一端点(endpoint)之下且可发现, 路径为 /apis/metrics.k8s.io/
  • 它具有相同的安全性、可扩展性和可靠性保证

Metrics API 在 k8s.io/metrics 仓库中定义。你可以在那里找到有关 Metrics API 的更多信息。

说明: Metrics API 需要在集群中部署 Metrics Server。否则它将不可用。

度量资源用量

CPU

CPU 用量按其一段时间内的平均值统计,单位为 CPU 核。 此度量值通过在内核(包括 Linux 和 Windows)提供的累积 CPU 计数器乘以一个系数得到。 kubelet 组件负责选择计算系数所使用的窗口大小。

内存

内存用量按工作集(Working Set)的大小字节数统计,其数值为收集度量值的那一刻的内存用量。 如果一切都很理想化,“工作集” 是任务在使用的内存总量,该内存是不可以在内存压力较大 的情况下被释放的。 不过,具体的工作集计算方式取决于宿主 OS,有很大不同,且通常都大量使用启发式 规则来给出一个估计值。 其中包含所有匿名内存使用(没有后台文件提供存储者),因为 Kubernetes 不支持交换分区。 度量值通常包含一些高速缓存(有后台文件提供存储)内存,因为宿主操作系统并不是总能 回收这些页面。

Metrics 服务器

Metrics 服务器 是集群范围资源用量数据的聚合器。 默认情况下,在由 kube-up.sh 脚本创建的集群中会以 Deployment 的形式被部署。 如果你使用其他 Kubernetes 安装方法,则可以使用提供的 部署组件 components.yaml 来部署。

Metric 服务器从每个节点上的 kubelet 公开的 Summary API 中采集指标信息。 该 API 通过 Kubernetes 聚合器 注册到主 API 服务器上。

设计文档 中可以了解到有关 Metrics 服务器的更多信息。

17 - 资源监控工具

要扩展应用程序并提供可靠的服务,你需要了解应用程序在部署时的行为。 你可以通过检测容器检查 Kubernetes 集群中的应用程序性能, Pods, 服务 和整个集群的特征。 Kubernetes 在每个级别上提供有关应用程序资源使用情况的详细信息。 此信息使你可以评估应用程序的性能,以及在何处可以消除瓶颈以提高整体性能。

在 Kubernetes 中,应用程序监控不依赖单个监控解决方案。 在新集群上,你可以使用资源度量完整度量管道来收集监视统计信息。

资源度量管道

资源指标管道提供了一组与集群组件,例如 Horizontal Pod Autoscaler 控制器以及 kubectl top 实用程序相关的有限度量。 这些指标是由轻量级的、短期、内存存储的 metrics-server 收集的, 通过 metrics.k8s.io 公开。

度量服务器发现集群中的所有节点,并且查询每个节点的 kubelet 以获取 CPU 和内存使用情况。 Kubelet 充当 Kubernetes 主节点与节点之间的桥梁,管理机器上运行的 Pod 和容器。 kubelet 将每个 Pod 转换为其组成的容器,并在容器运行时通过容器运行时接口 获取各个容器使用情况统计信息。 kubelet 从集成的 cAdvisor 获取此信息,以进行旧式 Docker 集成。 然后,它通过 metrics-server Resource Metrics API 公开聚合的 pod 资源使用情况统计信息。 该 API 在 kubelet 的经过身份验证和只读的端口上的 /metrics/resource/v1beta1 中提供。

完整度量管道

一个完整度量管道可以让你访问更丰富的度量。 Kubernetes 还可以根据集群的当前状态,使用 Pod 水平自动扩缩器等机制, 通过自动调用扩展或调整集群来响应这些度量。 监控管道从 kubelet 获取度量值,然后通过适配器将它们公开给 Kubernetes, 方法是实现 custom.metrics.k8s.ioexternal.metrics.k8s.io API。

Prometheus 是一个 CNCF 项目,可以原生监控 Kubernetes、 节点和 Prometheus 本身。 完整度量管道项目不属于 CNCF 的一部分,不在 Kubernetes 文档的范围之内。

18 - 集群故障排查

本篇文档是介绍集群故障排查的;我们假设对于你碰到的问题,你已经排除了是由应用程序造成的。 对于应用的调试,请参阅 应用故障排查指南。 你也可以访问故障排查 来获取更多的信息。

列举集群节点

调试的第一步是查看所有的节点是否都已正确注册。

运行

kubectl get nodes

验证你所希望看见的所有节点都能够显示出来,并且都处于 Ready 状态。

为了了解你的集群的总体健康状况详情,你可以运行:

kubectl cluster-info dump

查看日志

到这里,挖掘出集群更深层的信息就需要登录到相关的机器上。下面是相关日志文件所在的位置。 (注意,对于基于 systemd 的系统,你可能需要使用journalctl)。

主控节点

  • /var/log/kube-apiserver.log - API 服务器, 提供API服务
  • /var/log/kube-scheduler.log - 调度器, 负责产生调度决策
  • /var/log/kube-controller-manager.log - 管理副本控制器的控制器

工作节点

  • /var/log/kubelet.log - kubelet,负责在节点运行容器
  • /var/log/kube-proxy.log - kube-proxy, 负责服务的负载均衡

集群故障模式的一般性概述

下面是一个不完整的列表,列举了一些可能的出错场景,以及通过调整集群配置来解决相关问题的方法。

根本原因

  • VM(s) 关机
  • 集群之间,或者集群和用户之间网络分裂
  • Kubernetes 软件本身崩溃
  • 数据丢失或者持久化存储不可用(如:GCE PD 或 AWS EBS 卷)
  • 操作错误,如:Kubernetes 或者应用程序配置错误

具体情况:

  • API 服务器所在的 VM 关机或者 API 服务器崩溃
    • 结果
      • 不能停止、更新或者启动新的 Pod、服务或副本控制器
      • 现有的 Pod 和服务在不依赖 Kubernetes API 的情况下应该能继续正常工作
  • API 服务器的后端存储丢失
    • 结果
      • API 服务器应该不能启动
      • kubelet 将不能访问 API 服务器,但是能够继续运行之前的 Pod 和提供相同的服务代理
      • 在 API 服务器重启之前,需要手动恢复或者重建 API 服务器的状态
  • Kubernetes 服务组件(节点控制器、副本控制器管理器、调度器等)所在的 VM 关机或者崩溃
    • 当前,这些控制器是和 API 服务器在一起运行的,它们不可用的现象是与 API 服务器类似的
    • 将来,这些控制器也会复制为多份,并且可能不在运行于同一节点上
    • 它们没有自己的持久状态
  • 单个节点(VM 或者物理机)关机
    • 结果
      • 此节点上的所有 Pod 都停止运行
  • 网络分裂
    • 结果
      • 分区 A 认为分区 B 中所有的节点都已宕机;分区 B 认为 API 服务器宕机 (假定主控节点所在的 VM 位于分区 A 内)。
  • kubelet 软件故障
    • 结果
      • 崩溃的 kubelet 就不能在其所在的节点上启动新的 Pod
      • kubelet 可能删掉 Pod 或者不删
      • 节点被标识为非健康态
      • 副本控制器会在其它的节点上启动新的 Pod
  • 集群操作错误
    • 结果
      • 丢失 Pod 或服务等等
      • 丢失 API 服务器的后端存储
      • 用户无法读取API
      • 等等

缓解措施:

  • 措施:对于 IaaS 上的 VMs,使用 IaaS 的自动 VM 重启功能

    • 缓解:API 服务器 VM 关机或 API 服务器崩溃
    • 缓解:Kubernetes 服务组件所在的 VM 关机或崩溃
  • 措施: 对于运行 API 服务器和 etcd 的 VM,使用 IaaS 提供的可靠的存储(例如 GCE PD 或者 AWS EBS 卷)

    • 缓解:API 服务器后端存储的丢失
  • 措施:使用高可用性的配置

    • 缓解:主控节点 VM 关机或者主控节点组件(调度器、API 服务器、控制器管理器)崩馈
      • 将容许一个或多个节点或组件同时出现故障
    • 缓解:API 服务器后端存储(例如 etcd 的数据目录)丢失
      • 假定你使用了高可用的 etcd 配置
  • 措施:定期对 API 服务器的 PDs/EBS 卷执行快照操作

    • 缓解:API 服务器后端存储丢失
    • 缓解:一些操作错误的场景
    • 缓解:一些 Kubernetes 软件本身故障的场景
  • 措施:在 Pod 的前面使用副本控制器或服务

    • 缓解:节点关机
    • 缓解:kubelet 软件故障
  • 措施:应用(容器)设计成容许异常重启

    • 缓解:节点关机
    • 缓解:kubelet 软件故障