您的容器现在正在运行。在本节中,您将学习如何与应用程序通信、检查其日志以及在容器中执行命令以探索应用程序的环境。让我们确认在容器中运行的应用程序响应了您的请求。

向 pod 中的应用程序发送请求

在第 2 章中,您使用 kubectl expose 命令创建了一个配置负载均衡器的服务,以便您可以与在您的 pod 中运行的应用程序通信。您现在将采用不同的方法。出于开发、测试和调试目的,您可能希望直接与特定 pod 通信,而不是使用将连接转发到随机选择的 pod 的服务。

您已经了解到,每个 pod 都分配有自己的 IP 地址,集群中的每个其他 pod 都可以访问它。此 IP 地址通常位于集群内部。您无法从本地计算机访问它,除非 Kubernetes 以特定方式部署——例如,在没有 VM 的情况下使用 kind 或 Minikube 创建集群时。

通常,要访问 pod,您必须使用以下部分中描述的方法之一。首先,让我们确定 pod 的 IP 地址。

获取 pod 的 IP 地址

您可以通过检索 pod 的完整 YAML 并在状态部分中搜索 podIP 字段来获取 pod 的 IP 地址。或者,您可以使用 kubectl describe 显示 IP,但最简单的方法是使用带有宽输出选项的 kubectl get:

  1. $ kubectl get pod kubia -o wide
  2. NAME READY STATUS RESTARTS AGE IP NODE ...
  3. kubia 1/1 Running 0 35m 10.244.2.4 worker2 ...

如 IP 列所示,我的 pod 的 IP 是 10.244.2.4。现在我需要确定应用程序正在侦听的端口号。

获取应用程序绑定的端口

如果我不是应用程序的作者,我将很难找出应用程序侦听的端口。我可以检查它的源代码或容器镜像的 Dockerfile,因为通常在那里指定端口,但我可能无权访问任何一个。如果其他人创建了 pod,我怎么知道它正在侦听哪个端口?

幸运的是,您可以在 pod 定义本身中指定端口列表。没有必要指定任何端口,但总是这样做是个好主意。有关详细信息,请参阅侧边栏。

为什么要在 pod 定义中指定容器端口

在 pod 定义中指定端口纯粹是为了提供信息。它们的省略对客户端是否可以连接到 pod 的端口没有影响。如果容器通过绑定到其 IP 地址的端口接受连接,则任何人都可以连接到它,即使该端口未在 pod 规范中明确指定,或者您指定了错误的端口号。

尽管如此,始终指定端口是一个好主意,这样任何有权访问您的集群的人都可以看到每个 pod 公开了哪些端口。通过显式定义端口,您还可以为每个端口分配一个名称,这在您通过服务公开 pod 时非常有用。

从工作节点连接到 pod

Kubernetes 网络模型规定每个 pod 都可以从任何其他 pod 访问,并且每个节点都可以访问集群中任何节点上的任何 pod。

因此,与您的 pod 通信的一种方法是登录到您的一个工作节点并从那里与 pod 对话。您已经了解到登录节点的方式取决于您用于部署集群的方式。在 Minikube 中,您可以运行 minikube ssh 来登录到您的单个节点。在 GKE 上使用 gcloud compute ssh 命令。在 Kind 中,请使用以下命令登录到工作节点:

  1. docker exec -it kind-worker bash

登录到节点后,使用带有 pod 的 IP 和端口的 curl 命令来访问您的应用程序。我的 pod 的 IP 是 10.244.2.4,端口是 8080,所以我运行以下命令:

  1. $ curl 10.244.2.4:8080
  2. Hey there, this is kubia. Your IP is ::ffff:10.244.2.1.

通常你不会使用这种方法与你的 pod 通信,但是如果出现通信问题并且你想通过首先尝试最短的通信路径来查找原因,则可能需要使用它。在这种情况下,最好登录到 pod 所在的节点并从那里运行 curl。它与 pod 之间的通信发生在本地,因此这种方法始终具有最高的成功机会。

从一次性客户端 pod 连接

测试应用程序连接性的第二种方法是在您专门为此任务创建的另一个 pod 中运行 curl。使用此方法测试其他 pod 是否能够访问您的 pod。即使网络完美运行,情况也可能并非如此。在第 24 章中,您将学习如何通过将 Pod 彼此隔离来锁定网络。在这样的系统中,一个 pod 只能与它被允许的 pod 对话。

要在一次性 pod 中运行 curl,请使用以下命令:

  1. $ k run --image=rancher/curl -it --restart=Never --rm client-pod http://10.244.2.4:8080
  2. Hey there, this is kubia. Your IP is ::ffff:10.244.2.6.
  3. pod "client-pod" deleted

此命令使用从 rancher/curl 镜像创建的单个容器运行一个 pod。您还可以使用任何其他提供 curl 二进制可执行文件的镜像。 -it 选项将您的控制台附加到容器的标准输入和输出,—restart=Never 选项确保当 curl 命令及其容器终止时 Pod 被视为已完成,—rm 选项在结束时删除 Pod . pod 的名称是 client-pod,在容器中执行的命令是 curl 10.244.2.4:8080。

您还可以修改命令以在客户端 pod 中运行 bash shell,然后从 shell 运行 curl。

当您专门测试 pod 到 pod 的连接性时,创建一个 pod 以查看它是否可以访问另一个 pod 很有用。如果你只想知道你的 pod 是否在响应请求,你也可以使用下一节介绍的方法。

通过 kubectl 端口转发连接到 pod

在开发过程中,与运行在您的 pod 中的应用程序通信的最简单方法是使用 kubectl port-forward 命令,该命令允许您通过绑定到本地计算机上的网络端口的代理与特定的 pod 通信,如下图。
image.png
要打开与 pod 的通信路径,您甚至不需要查找 pod 的 IP,因为您只需要指定其名称和端口即可。以下命令启动一个代理,将您计算机的本地端口 8080 转发到 kubia pod 的端口 8080:

  1. $ kubectl port-forward kubia 8080
  2. ... Forwarding from 127.0.0.1:8080 -> 8080
  3. ... Forwarding from [::1]:8080 -> 8080

代理现在等待接受连接。在另一个终端中运行以下 curl 命令:

  1. $ curl localhost:8080
  2. Hey there, this is kubia. Your IP is ::ffff:127.0.0.1.

如您所见,curl 已连接到本地代理并收到来自 pod 的响应。虽然 port-forward 命令是在开发和故障排除期间与特定 pod 通信的最简单方法,但就底层发生的情况而言,它也是最复杂的方法。通信通过几个组件,因此如果通信路径中出现任何问题,您将无法与 pod 通信,即使 pod 本身可以通过常规通信通道访问。

kubectl port-forward 命令还可以将连接转发到服务而不是 pod,并且还有其他一些有用的功能。运行 kubectl port-forward —help 以了解更多信息。

下图显示了网络数据包如何从 curl 进程流向您的应用程序并返回。
image.png
如图,curl进程连接代理,代理连接API服务器,API服务器再连接到承载Pod的节点上的Kubelet,Kubelet再通过Pod的回环设备连接到容器(在换句话说,通过 localhost 地址)。我相信你会同意请求路径非常长。

容器中的应用程序必须绑定到环回设备上的端口,以便 Kubelet 访问它。如果它只侦听 pod 的 eth0 网络接口,您将无法使用 kubectl port-forward 命令访问它。

查看应用程序日志

您的 Node.js 应用程序将其日志写入标准输出流。容器化应用程序通常不会将日志写入文件,而是将日志记录到标准输出 (stdout) 和标准错误流 (stderr)。这允许容器运行时拦截输出,将其存储在一致的位置(通常是 /var/log/containers)并提供对日志的访问,而无需知道每个应用程序存储其日志文件的位置。

当您使用 Docker 在容器中运行应用程序时,您可以使用 docker logs 显示其日志。当您在 Kubernetes 中运行应用程序时,您可以登录到托管 pod 的节点并使用 docker logs 显示其日志,但 Kubernetes 使用 kubectl logs 命令提供了一种更简单的方法来执行此操作。

使用 kubectl logs 检索 pod 的日志

要查看 pod 的日志(更具体地说,容器的日志),请在本地计算机上运行以下列表中显示的命令:

  1. $ kubectl logs kubia
  2. Kubia server starting...
  3. Local hostname is kubia
  4. Listening on port 8080
  5. Received request for / from ::ffff:10.244.2.1
  6. Received request for / from ::ffff:10.244.2.5
  7. Received request for / from ::ffff:127.0.0.1

使用 kubectl logs -f 流式传输日志

如果您想实时流式传输应用程序日志以查看每个请求,您可以使用 —follow 选项(或更短的版本 -f)运行命令:

  1. $ kubectl logs kubia -f

现在向应用程序发送一些额外的请求并查看日志。完成后按 ctrl-C 停止流式传输日志。

显示每个记录行的时间戳

您可能已经注意到我们忘记在日志语句中包含时间戳。没有时间戳的日志可用性有限。幸运的是,容器运行时会将当前时间戳附加到应用程序生成的每一行。您可以使用 —timestamps=true 选项显示这些时间戳,如下面的清单所示。

  1. $ kubectl logs kubia –-timestamps=true
  2. 2020-02-01T09:44:40.954641934Z Kubia server starting...
  3. 2020-02-01T09:44:40.955123432Z Local hostname is kubia
  4. 2020-02-01T09:44:40.956435431Z Listening on port 8080
  5. 2020-02-01T09:50:04.978043089Z Received request for / from ...
  6. 2020-02-01T09:50:33.640897378Z Received request for / from ...
  7. 2020-02-01T09:50:44.781473256Z Received request for / from ...

您可以通过仅键入不带值的 —timestamps 来显示时间戳。对于布尔选项,仅指定选项名称即可将选项设置为 true。这适用于所有采用布尔值并默认为 false 的 kubectl 选项。

显示最近的日志

如果您运行的第三方应用程序在其日志输出中不包含时间戳,则前面的功能非常有用,但是每一行都带有时间戳的事实给我们带来了另一个好处:按时间过滤日志行。 Kubectl 提供了两种按时间过滤日志的方法。

第一个选项是当您只想显示过去几秒、几分钟或几小时的日志时。例如,要查看最近两分钟产生的日志,请运行:

  1. $ kubectl logs kubia --since=2m

另一个选项是使用 —since-time 选项显示在特定日期和时间之后生成的日志。要使用的时间格式是 RFC3339。例如,以下命令用于打印 2020 年 2 月 1 日上午 9:50 之后生成的日志:

  1. $ kubectl logs kubia –-since-time=2020-02-01T09:50:00Z

显示日志的最后几行

您还可以指定要显示的日志末尾的行数,而不是使用时间来限制输出。要显示最后十行,请尝试:

  1. $ kubectl logs kubia –-tail=10

接受值的 Kubectl 选项可以用等号或空格指定。除了 —tail=10,您还可以键入 —tail 10。

了解 pod 日志的可用性

Kubernetes 为每个容器保留一个单独的日志文件。它们通常存储在运行容器的节点上的 /var/log/containers 中。为每个容器创建一个单独的文件。如果容器重新启动,它的日志将被写入一个新文件。因此,如果在您使用 kubectl logs -f 跟踪其日志时重新启动容器,该命令将终止,您需要再次运行它以流式传输新容器的日志。

kubectl logs 命令只显示当前容器的日志。要查看前一个容器的日志,请使用 —previous(或 -p)选项。

根据您的集群配置,当日志文件达到一定大小时,它们也可能会被轮换。在这种情况下,kubectl 日志将只显示新的日志文件。流式传输日志时,您必须重新启动命令以切换到新文件。

当你删除一个 pod 时,它的所有日志文件也会被删除。要使 pod 的日志永久可用,您需要设置一个中央的、集群范围的日志系统。第 23 章解释了如何。

将日志写入文件的应用程序呢?

如果您的应用程序将其日志写入文件而不是标准输出,您可能想知道如何访问该文件。理想情况下,您应该配置集中式日志记录系统来收集日志,以便您可以在中央位置查看它们,但有时您只想保持简单,不介意手动访问日志。在接下来的两节中,您将学习如何将日志和其他文件从容器复制到您的计算机或从计算机复制到容器,以及如何在正在运行的容器中运行命令。您可以使用任何一种方法来显示日志文件或容器内的任何其他文件。

将文件复制到容器或从容器复制文件

有时您可能希望将文件添加到正在运行的容器或从中检索文件。在运行的容器中修改文件不是你通常会做的事情——至少在生产中不会——但它在开发过程中很有用。

Kubectl 提供 cp 命令将文件或目录从本地计算机复制到任何 pod 的容器或从容器复制到您的计算机。例如,要将 /etc/hosts 文件从 kubia pod 的容器复制到本地文件系统的 /tmp 目录,请运行以下命令:

  1. $ kubectl cp kubia:/etc/hosts /tmp/kubia-hosts

要将文件从本地文件系统复制到容器,请运行以下命令:

  1. $ kubectl cp /path/to/local/file kubia:path/in/container

kubectl cp 命令要求容器中存在 tar 二进制文件,但此要求将来可能会发生变化。

在运行的容器中执行命令

在调试容器中运行的应用程序时,可能需要从内部检查容器及其环境。 Kubectl 也提供了这个功能。您可以使用 kubectl exec 命令执行容器文件系统中存在的任何二进制文件。

在容器中调用单个命令

例如,您可以通过运行以下命令列出 kubia pod 中容器中运行的进程:

  1. $ kubectl exec kubia -- ps aux
  2. USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
  3. root 1 0.0 1.3 812860 27356 ? Ssl 11:54 0:00 node app.js
  4. root 120 0.0 0.1 17500 2128 ? Rs 12:22 0:00 ps aux

这是 Kubernetes 等效的 Docker 命令,您在第 2 章中用于探索正在运行的容器中的进程。它允许您在任何 pod 中远程运行命令,而无需登录到托管 pod 的节点。如果您使用 ssh 在远程系统上执行命令,您会发现 kubectl exec 并没有太大的不同。

之前您在一次性客户端 pod 中执行 curl 命令以向您的应用程序发送请求,但您也可以在 kubia pod 本身内运行该命令:

  1. $ kubectl exec kubia -- curl -s localhost:8080
  2. Hey there, this is kubia. Your IP is ::1.

为什么在 kubectl exec 命令中使用双破折号?

命令中的双破折号 (—) 将 kubectl 参数与要在容器中执行的命令分隔开。如果命令没有以破折号开头的参数,则不需要使用双破折号。如果在前面的示例中省略双破折号, -s 选项将被解释为 kubectl exec 的选项,并导致以下误导性错误:

  1. $ kubectl exec kubia curl -s localhost:8080

与服务器 localhost:8080 的连接被拒绝 - 您是否指定了正确的主机或端口?

这可能看起来像 Node.js 服务器拒绝接受连接,但问题出在其他地方。 curl 命令永远不会执行。当 kubectl 尝试与位于 localhost:8080 的 Kubernetes API 服务器(不是服务器所在的位置)通信时,会报告该错误。如果您运行 kubectl options 命令,您会看到 -s 选项可用于指定 Kubernetes API 服务器的地址和端口。 kubectl 没有将该选项传递给 curl,而是将其作为自己的。添加双破折号可以防止这种情况。

幸运的是,为了防止出现这种情况,如果您忘记了双破折号,较新版本的 kubectl 设置为返回错误。

在容器中运行交互式 shell

前面的两个示例展示了如何在容器中执行单个命令。命令完成后,您将返回到您的 shell。如果要在容器中运行多个命令,可以在容器中运行一个shell,如下所示:

  1. $ kubectl exec -it kubia -- bash
  2. root@kubia:/#

-it 是两个选项的缩写:-i 和 -t,它们表示您希望通过将标准输入传递给容器并将其标记为终端 (TTY) 以交互方式执行 bash 命令。

您现在可以通过在 shell 中执行命令来探索容器的内部。例如,您可以通过运行 ls -la 查看容器中的文件,使用 ip link 查看其网络接口,或使用 ping 测试其连接性。您可以运行容器中可用的任何工具。

并非所有容器都允许您运行 shell

应用程序的容器镜像包含许多重要的调试工具,但并非每个容器镜像都如此。为了使镜像更小并提高容器中的安全性,生产中使用的大多数容器不包含除容器主进程所需的二进制文件之外的任何二进制文件。这显着减少了攻击面,但也意味着您无法在生产容器中运行 shell 或其他工具。幸运的是,一个名为临时容器的新 Kubernetes 功能允许您通过将调试容器附加到它们来调试正在运行的容器。

临时容器目前是 alpha 功能,这意味着它们可能随时更改甚至被删除。这也是本书目前未对它们进行解释的原因。如果他们在本书投入生产之前完成测试版,将添加一个解释它们的部分。

附加(Attaching)到正在运行的容器

kubectl attach 命令是与正在运行的容器交互的另一种方式。它将自己附加到容器中运行的主进程的标准输入、输出和错误流。通常,您只使用它与从标准输入读取的应用程序进行交互。

使用 kubectl attach 查看应用程序打印到标准输出的内容

如果应用程序不从标准输入读取,kubectl attach 命令只是流式传输应用程序日志的另一种方式,因为这些通常写入标准输出和错误流,并且 attach 命令就像 kubectl 日志一样流式传输它们-f 命令可以。

  1. $ kubectl attach kubia

现在,当您在另一个终端中使用 curl 向应用程序发送新的 HTTP 请求时,您将看到应用程序记录到标准输出的行也打印在执行 kubectl attach 命令的终端中。

使用 kubectl attach 写入应用程序的标准输入

kubia 应用程序不会从标准输入流中读取,但您会在本书的代码存档中找到执行此操作的另一个版本的应用程序。您可以通过将其写入应用程序的标准输入流来更改应用程序响应 HTTP 请求的问候语。让我们将这个版本的应用程序部署在一个新的 pod 中,并使用 kubectl attach 命令更改问候语。

您可以在 kubia-stdin-image/ 目录中找到构建镜像所需的工件,也可以使用预构建的镜像 docker.io/luksa/kubia:1.0-stdin。 pod 清单位于文件 kubia-stdin.yaml 中。它与您之前部署的 kubia.yaml 中的 pod 清单仅略有不同。以下列表中突出显示了这些差异。

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: kubia-stdin
  5. spec:
  6. containers:
  7. - name: kubia
  8. image: luksa/kubia:1.0-stdin
  9. stdin: true
  10. ports:
  11. - containerPort: 8080

正如您在清单中看到的,如果在 pod 中运行的应用程序想要从标准输入中读取,您必须在 pod 清单中通过将容器定义中的 stdin 字段设置为 true 来指明这一点。这告诉 Kubernetes 为标准输入流分配一个缓冲区,否则应用程序在尝试从中读取时总是会收到一个 EOF。

使用 kubectl apply 命令从此清单文件创建 pod:

  1. $ kubectl apply -f kubia-stdin.yaml
  2. pod/kubia-stdin created

要启用与应用程序的通信,请再次使用 kubectl port-forward 命令,但由于之前执行的 port-forward 命令仍在使用本地端口 8080,您必须终止它或选择其他本地端口转发到新的 pod。您可以按如下方式执行此操作:

  1. $ kubectl port-forward kubia-stdin 8888:8080
  2. Forwarding from 127.0.0.1:8888 -> 8080
  3. Forwarding from [::1]:8888 -> 8080

命令行参数 8888:8080 指示命令将本地端口 8888 转发到 pod 的端口 8080。

  1. $ curl localhost:8888
  2. Hey there, this is kubia-stdin. Your IP is ::ffff:127.0.0.1.

让我们通过使用 kubectl attach 将问候语从“Hey there”更改为“Howdy”,以写入应用程序的标准输入流。运行以下命令:

  1. $ kubectl attach -i kubia-stdin

请注意在命令中使用了附加选项 -i。它指示 kubectl 将其标准输入传递给容器。

与 kubectl exec 命令一样,kubectl attach 也支持 —tty 或 -t 选项,表示标准输入为终端(TTY),但容器必须配置为通过容器定义中的 tty 字段分配终端.

您现在可以在终端中输入新的问候语,然后按 ENTER 键。然后应用程序应以新的问候语进行响应:

  1. Howdy
  2. Greeting set to: Howdy

要查看应用程序现在是否使用新问候语响应 HTTP 请求,请重新执行 curl 命令或在 Web 浏览器中刷新页面:

  1. $ curl localhost:8888
  2. Howdy, this is kubia-stdin. Your IP is ::ffff:127.0.0.1.

有新的问候。您可以使用 kubectl attach 命令在终端中键入另一行来再次更改它。要退出附加命令,请按 Control-C 或等效键。

容器定义中的附加字段 stdinOnce 确定在附加会话结束时是否关闭标准输入通道。默认设置为 false,这允许您在每个 kubectl attach 会话中使用标准输入。如果将其设置为 true,则标准输入仅在第一个会话期间保持打开状态。