Kubernetes实战(第二版)--第五章 在Pods中运行应用程序
本章涵盖了
- 理解如何以及何时对容器进行分组
- 通过从YAML文件创建Pod对象来运行应用程序
- 与应用程序通信,查看它的日志,探索它的环境
- 添加一个sidecar容器来扩展pod的主容器
- 通过在pod启动时运行初始化容器命令来初始化pods
让我用一个图表来刷新您的记忆,它显示了您在第3章中创建的用于在Kubernetes上部署最小应用程序的三种类型的对象。图5.1显示了它们之间的关系以及它们在系统中具有哪些功能。
图5.1组成已部署应用程序的三种基本对象类型
现在您对如何通过Kubernetes API公开这些对象有了基本的了解。在本章和接下来的章节中,您将了解每一种方法的具体细节,以及通常用于部署完整应用程序的许多其他方法。让我们从Pod对象开始,因为它代表了Kubernetes中最重要的核心概念——应用程序的运行实例。
5.1 理解pods
您已经了解到,pod是一组位于同一位置的容器,是Kubernetes中的基本构建块。
不是单独部署容器,而是将一组容器作为单个单元(即pod)部署和管理。虽然pod可能包含几个容器,但一个pod只包含一个容器并不罕见。当一个pod有多个容器时,它们都在同一个工作节点上运行——单个pod实例永远不会跨越多个节点。图5.2将帮助您可视化此信息。
图5.2 一个pod的所有容器都运行在同一个节点上。一个pod从不跨越多个节点。
5.1.1 理解为什么我们需要pods
让我们讨论一下为什么需要同时运行多个容器,而不是在同一个容器中运行多个进程。
理解为什么一个容器不应该包含多个进程
设想一个由几个进程组成的应用程序,这些进程通过IPC(进程间通信)或共享文件相互通信,这要求它们在同一台计算机上运行。在第2章中,您了解到每个容器就像一个独立的计算机或虚拟机。计算机通常运行多个进程;容器也可以做到这一点。您可以在一个容器中运行组成应用程序的所有进程,但这使得该容器非常难以管理。
容器被设计为只运行单个进程,不计算它产生的任何子进程。容器工具和Kubernetes都是围绕这一事实开发的。例如,在容器中运行的进程应该将其日志写入标准输出。用于显示日志的Docker和Kubernetes命令只显示从输出中捕获的内容。如果一个进程在容器中运行,它是唯一的写入器,但是如果在容器中运行多个进程,它们都写入相同的输出。因此,它们的日志是相互交织的,很难区分每一行日志属于哪个进程。
容器应该只运行单个进程的另一个指示是,容器运行时仅在容器的根进程死亡时重新启动容器。它不关心这个根进程创建的任何子进程。如果它产生子进程,则它单独负责保持所有这些进程的运行。
要充分利用容器运行时提供的特性,应该考虑在每个容器中只运行一个进程。
理解一个pod如何组合多个容器
由于不应该在单个容器中运行多个进程,因此显然需要另一种更高级的构造,即使在划分为多个容器时也可以一起运行相关的进程。这些进程必须能够像普通计算机中的进程一样彼此通信。这就是pods被引入的原因。
使用pod,您可以一起运行紧密相关的进程,为它们(几乎)提供相同的环境,就像它们都运行在一个容器中一样。这些进程在某种程度上是孤立的,但不是完全的——它们共享一些资源。这给了你两全其美。您可以使用容器提供的所有特性,但也允许进程一起工作。pod使这些相互连接的容器作为一个单元进行管理。
在第二章中,您了解到容器使用自己的一组Linux名称空间,但是它也可以与其他容器共享一些名称空间。这种名称空间的共享正是Kubernetes和容器运行时将容器组合到pods中的方式。
如图5.3所示,一个pod中的所有容器共享相同的网络名称空间,从而共享属于它的网络接口、IP地址和端口空间。
图5.3 pod中的容器共享相同的网络接口
由于共享端口空间,在相同pod的容器中运行的进程不能绑定到相同的端口号,而其他pod中的进程有自己的网络接口和端口空间,从而消除了不同pod之间的端口冲突。
pod中的所有容器也看到相同的系统主机名,因为它们共享UTS名称空间,并且可以通过通常的IPC机制进行通信,因为它们共享IPC名称空间。pod还可以配置为对其所有容器使用单个PID名称空间,这使得它们共享单个进程树,但是必须为每个pod分别显式地启用此功能。
请注意
当相同pod的容器使用单独的PID名称空间时,它们不能相互看到,也不能在它们之间发送SIGTERM或SIGINT之类的进程信号。
正是这种名称空间的共享让在pod中运行的进程感觉像是在一起运行,尽管它们运行在不同的容器中。
相反,每个容器总是有自己的挂载名称空间,为其提供自己的文件系统,但是当两个容器必须共享文件系统的一部分时,可以向pod添加一个volume(卷),并将其挂载到两个容器中。这两个容器仍然使用两个独立的挂载名称空间,但是共享卷被挂载到这两个名称空间中。你将在第7章了解更多关于卷的知识。
5.1.2 将容器组织到pods
你可以把每个pod看作是一台独立的计算机。与虚拟机不同,虚拟机通常承载多个应用程序,您通常在每个pod中只运行一个应用程序。您不需要在单个pod中组合多个应用程序,因为pod几乎没有资源开销。您可以有您需要的任意数量的pod,所以您应该将应用程序划分为单个pod,以便每个pod只运行密切相关的应用程序进程。
让我用一个具体的例子来说明。
将多层应用程序栈拆分为多个pods
设想一个由前端web服务器和后端数据库组成的简单系统。我已经解释过前端服务器和数据库不应该运行在同一个容器中,因为容器中内置的所有特性都是按照容器中运行的进程不超过一个的预期设计的。如果不是在单个容器中,那么是否应该在所有在同一pod中的独立容器中运行它们?
尽管没有什么可以阻止您在单个pod中同时运行前端服务器和数据库,但这不是最好的方法。我已经解释了pod的所有容器总是在同一位置运行,但是web服务器和数据库必须在同一台计算机上运行吗?答案显然是否定的,因为它们可以很容易地通过网络进行通信。因此,您不应该在同一个pod中运行它们。
如果前端和后端都在同一个pod中,那么它们都运行在同一个集群节点上。如果您有一个两节点集群,并且只创建这个pod,那么您只使用了一个工作节点,并且没有利用第二个节点上可用的计算资源。这意味着浪费了CPU、内存、磁盘存储和带宽。将容器分成两个pod允许Kubernetes将前端pod放在一个节点上,后端pod放在另一个节点上,从而提高硬件的利用率。
分解成多个pods,以支持独立扩展
不使用单个pod的另一个原因与水平伸缩有关。pod不仅是部署的基本单元,也是扩展的基本单元。在第2章中,您扩展了Deployment对象,Kubernetes创建了额外的pods——应用程序的额外副本。Kubernetes不会在pod中复制容器。它复制整个pod。
前端组件通常与后端组件有不同的伸缩需求,因此我们通常分别对它们进行伸缩。当您的pod同时包含前端和后端容器并且Kubernetes复制它时,您将得到前端和后端容器的多个实例,这并不总是您想要的。有状态后端(如数据库)通常无法伸缩。至少没有无状态前端那么容易。如果容器必须与其他组件分开进行伸缩,这就清楚地表明它必须部署在单独的pod中。
下图说明了刚才的解释。
图5.4 将应用程序栈拆分为pods
5.2.2 从YAML文件中创建Pod对象
在为pod准备好清单文件之后,现在可以通过将文件发布到Kubernetes API来创建对象。
通过将清单文件应用到集群来创建对象
当您将清单发布到API时,您将引导Kubernetes将清单应用到集群。这就是为什么执行此操作的kubectl子命令称为apply。让我们使用它来创建pod:
pod “kubia” created
通过修改清单文件来更新对象并重新应用
kubectl apply命令用于创建对象以及对现有对象进行更改。如果您决定更改pod对象,可以简单地编辑kubia.yaml文件,并再次运行apply命令。pod的一些字段是不可变的,因此更新可能会失败,但您总是可以删除pod,然后再次创建它。在本章的最后,你将学习如何删除pods和其他对象。
检索运行POD的完整清单
pod对象现在是集群配置的一部分。可以从API中读取它,使用以下命令查看完整的对象清单:
$ kubectl get po kubia -o yaml
如果运行这个命令,您将注意到清单与kubia.yaml中的清单相比增加了很多内容。您将看到metadata 部分现在要大得多,对象现在有一个status部分。相关字段中的spec 部分也增加了内容。您可以使用kubectl explain来查看更多关于这些新字段的内容,但它们中的大部分将在本章和后续章节中进行解释。
5.2.3 检查新创建的pod
在开始与内部运行的应用程序交互之前,让我们使用基本的kubectl命令来看看pod是如何运行的。
快速检查pod状态
Pod对象已经创建,但是如何知道Pod中的容器是否正在实际运行呢?您可以使用kubectl get命令查看pod的摘要:
NAME READY STATUS RESTARTS AGE
kubia 1/1 Running 0 32s
你可以看到pod在运行中,但除此之外就没什么了。要了解更多信息,您可以尝试在前一章中学习的kubectl get pod -o wide或kubectl describe命令。
使用 kubectl describe 查看pod细节
要显示pod的更详细视图,使用kubectl describe命令:
#清单5.2使用 kubectl describe pod 来检查pod
$ kubectl describe pod kubia
Name: kubia
Namespace: default
Priority: 0
Node: worker2/172.18.0.4
Start Time: Mon, 27 Jan 2020 12:53:28 +0100
...
清单没有显示整个输出,但是如果您自己运行这个命令,您将看到使用kubectl get -o yaml命令打印完整对象清单时所看到的几乎所有信息。
观察事件,看看底层发生了什么
正如在前一章中使用description node命令检查节点对象一样,description pod命令应该在输出的底部显示几个与pod相关的事件。
如果你还记得,这些事件不是对象本身的一部分,而是独立的对象。让我们打印它们,以了解创建pod对象时发生的更多事情。下面的清单显示了创建pod后记录的所有事件。
#清单5.3部署Pod对象后记录的事件
$ kubectl get events
LAST SEEN TYPE REASON OBJECT MESSAGE
<unknown> Normal Scheduled pod/kubia Successfully assigned default/
kubia to worker2
5m Normal Pulling pod/kubia Pulling image luksa/kubia:1.0
5m Normal Pulled pod/kubia Successfully pulled image
5m Normal Created pod/kubia Created container kubia
5m Normal Started pod/kubia Started container kubia
这些事件是按时间顺序打印的。最近的事件在底部。您可以看到,首先将pod分配给一个工作节点,然后提取容器镜像,然后创建容器并最终启动。
没有显示任何警告事件,所以似乎一切正常。如果在您的集群中不是这样,那么您应该阅读第5.4节来学习如何对pod故障进行故障排除。
5.3 与应用和pod交互
您的容器现在正在运行。在本节中,您将学习如何与应用程序通信,检查其日志,并在容器中执行命令以探索应用程序的环境。让我们确认在容器中运行的应用程序是否响应您的请求。
5.3.1 向pod中的应用发送请求
在第2章中,您使用kubectl expose命令创建了一个服务,该服务提供了一个负载均衡器,这样您就可以与在pod中运行的应用程序进行通信。现在您将采用一种不同的方法。为了开发、测试和调试目的,您可能希望直接与特定的pod通信,而不是使用将连接转发到随机选择的pod的服务。
您已经了解到,每个pod都被分配了自己的IP地址,集群中的每个其他pod都可以访问它。这个IP地址通常是集群内部的。您不能从本地计算机访问它,除非Kubernetes以一种特定的方式部署——例如,在使用kind或Minikube而没有VM来创建集群时。
通常,要访问pods,您必须使用以下部分中描述的方法之一。首先,让我们确定pod的IP地址。
获取pod的IP地址
通过检索pod的完整YAML并在status部分中搜索podIP字段,可以获得pod的IP地址。或者,您可以使用kubectl description来显示IP,但最简单的方法是使用kubectl get命令,搭配wide 输出选项:
NAME READY STATUS RESTARTS AGE IP NODE ...
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暴露了哪些端口。通过显式定义端口,还可以为每个端口分配一个名称,这在通过服务公开pods时非常有用。
pod清单表明,容器使用端口8080,因此您现在拥有了与应用程序对话所需的一切。
从工作节点连接到pod
Kubernetes网络模型规定,每个pod都可以从任何其他pod访问,每个节点都可以到达集群中任何节点上的任何pod。
因此,与pod通信的一种方法是登录到一个工作节点并从那里与pod通信。您已经了解到,登录到节点的方式取决于部署集群时使用的工具。如果你用的是kind,运行docker exec -it kind-worker bash,如果你用的是minikube ssh。在GKE上使用gcloud compute ssh命令。对于其他集群,请参考它们的文档。
登录到节点后,使用curl命令和pod的IP和端口访问应用程序。我的pod的IP是10.244.2.4,端口是8080,所以我运行以下命令:
$ curl 10.244.2.4:8080
Hey there, this is kubia. Your IP is ::ffff:10.244.2.1.
通常不使用此方法与pods通信,但如果存在通信问题,并且希望首先尝试尽可能短的通信路径来查找原因,则可能需要使用此方法。在这种情况下,最好登录到pod所在的节点并从那里运行curl。它和pod之间的通信发生在本地,因此这种方法总是有最大的成功机会。
从临时(一次性)客户端pod连接
测试应用程序连接性的第二种方法是在另一个专门为此任务创建的pod中运行curl。使用此方法测试其他pod是否能够访问您的pod。即使网络运行良好,情况也可能并非如此。在第24章中,您将学习如何通过隔离pods来锁定网络。在这样的系统中,pod只能与它允许的pod通信。
要在一个临时pod中运行curl,使用以下命令:
$ kubectl run --image=tutum/curl -it --restart=Never --rm client-pod
[CA] curl 10.244.2.4:8080
Hey there, this is kubia. Your IP is ::ffff:10.244.2.5.
pod "client-pod" deleted
这个命令运行一个pod,这个pod中包含一个从tutum/curl镜像创建的容器。也可以使用提供curl二进制可执行文件的任何其他镜像。-it选项将控制台附加到容器的标准输入和输出,--restart=Never选项确保当curl命令及其容器终止时pod被认为已经完成,--rm选项在结束时删除pod。pod的名称为client-pod,在其容器中执行的命令为curl 10.244.24:8080。
请注意
您还可以修改该命令,以便在客户机pod中运行bash shell,然后在shell中运行curl。
当您专门测试pod到pod的连接时,创建一个pod只是为了看看它是否可以访问另一个pod是很有用的。如果您只想知道您的pod是否正在响应请求,可以使用下一节中介绍的方法。
通过kubectl port-forward连接到pods
在开发过程中,与运行在pod中的应用程序通信的最简单方法是使用kubectl port-forward命令,它允许您通过绑定到本地计算机上的网络端口的代理与特定的pod通信,如下图所示。
图5.8 通过kubectl port-forward代理连接pod
要打开与pod的通信路径,您甚至不需要查找pod的IP,因为您只需要指定它的名称和端口。下面的命令启动一个代理,将您计算机的本地端口8080转发到kubia pod的端口8080:
$ kubectl port-forward kubia 8080
... Forwarding from 127.0.0.1:8080 -> 8080
... Forwarding from [::1]:8080 -> 8080
代理现在等待传入的连接。在另一个终端上运行curl命令:
$ curl localhost:8080
Hey there, this is kubia. Your IP is ::ffff:127.0.0.1.
如您所见,curl已经连接到本地代理并从pod接收响应。虽然port-forward命令是在开发和故障排除过程中与特定pod通信的最简单方法,但就底层发生的事情而言,它也是最复杂的方法。通信通过几个组件,因此,如果通信路径中出现任何问题,您将无法与pod进行通信,即使pod本身可以通过常规通信通道访问。
请注意
kubectl port-forward命令还可以将连接转发到服务,而不是pods,它还有其他一些有用的特性。运行kubectl port-forward --help了解更多信息。
图5.9显示了网络数据包如何从curl进程流向您的应用程序并返回。
图5.9使用端口转发时curl和container之间的长通信路径
如图,curl进程连接到代理,即连接到API服务器,然后连接到pod所在节点的Kubelet,然后Kubelet通过pod的回送装置(换句话说,通过本地主机地址)连接到容器。我相信您会同意通信路径非常长。
请注意
容器中的应用程序必须绑定到环回设备(loopback device)上的端口,以便Kubelet到达它。如果它只监听pod的eth0网络接口,那么您将无法使用kubectl port-forward命令访问它。
5.3.2 查看应用日志
Node.js应用程序将其日志写入标准输出流。容器化的应用程序通常不会将日志写入文件,而是记录到标准输出(stdout)和标准错误流(stderr)。这允许容器运行时环境拦截输出,将其存储在一致的位置(通常是/var/log/containers),并提供对日志的访问,而不必知道每个应用程序将其日志文件存储在何处。
当你使用Docker在容器中运行一个应用程序时,你可以通过Docker logs <container-id>来显示它的日志。当您在Kubernetes中运行应用程序时,您可以登录到承载pod的节点,并使用docker logs显示它的日志,但是Kubernetes使用kubectl logs命令提供了一种更简单的方法来实现这一点。
使用kubectl logs检索pod的日志
要查看pod的日志(更确切地说,是容器的日志),在本地计算机上运行如下清单所示的命令:
#清单5.4显示一个pod的日志
$ kubectl logs kubia
Kubia server starting...
Local hostname is kubia
Listening on port 8080
Received request for / from ::ffff:10.244.2.1#一个从节点内部发送的请求
Received request for / from ::ffff:10.244.2.5#来自一次性客户端pod的请求
Received request for / from ::ffff:127.0.0.1#通过端口转发发送的请求
使用kubectl logs -f查看日志流
如果您想要实时地查看应用程序日志,以查看每个传入的请求,您可以使用--follow选项(或更短的版本-f)运行命令:
现在向应用程序发送一些额外的请求,并查看日志。完成后,按ctrl-C停止传输日志。
显示每个记录行的时间戳
您可能已经注意到,我们忘记在日志语句中包含时间戳。没有时间戳的日志可用性有限。幸运的是,容器运行时将当前时间戳附加到应用程序生成的每一行。可以使用--timestamps=true选项来显示这些时间戳,如下面的清单所示。
Listing 5.5 Displaying the timestamp of each log line
$ kubectl logs kubia --timestamps=true
2020-02-01T09:44:40.954641934Z Kubia server starting...
2020-02-01T09:44:40.955123432Z Local hostname is kubia
2020-02-01T09:44:40.956435431Z Listening on port 8080
2020-02-01T09:50:04.978043089Z Received request for / from ...
2020-02-01T09:50:33.640897378Z Received request for / from ...
2020-02-01T09:50:44.781473256Z Received request for / from ...
提示
您可以通过只输入--timestamp而不输入值来显示时间戳。对于布尔型选项,仅仅指定选项名就会将该选项设置为true。只输入--timestamp适用于所有接受布尔值并默认为false的kubectl选项。
显示最近的日志
如果您运行的第三方应用程序在其日志输出中不包含时间戳,则前面的特性非常好,但是每一行都有时间戳的事实给我们带来了另一个好处:按时间过滤日志行。Kubectl提供了两种按时间过滤日志的方法。
第一个选项是当您只想显示过去几秒、几分钟或几小时的日志时。例如,要查看最近两分钟内产生的日志,可以执行以下命令:
另一个选项是使用--since-time选项显示特定日期和时间之后生成的日志。使用的时间格式为RFC3339。例如,打印2020年2月1日上午9:50以后的日志:
显示日志的最后几行
不需要使用时间来限制输出,您还可以指定从日志末尾开始要显示多少行。要显示最后十行,请尝试:
请注意
接受值的Kubectl选项可以用等号或空格来指定。您也可以输入--tail 10,或者--tail=10。
了解pod日志的可用性
Kubernetes为每个容器保留一个单独的日志文件。它们通常存储在运行容器的节点的/var/log/containers中。为每个容器创建一个单独的文件。如果重新启动容器,则将其日志写入新文件。因此,如果在您使用kubectl logs -f跟踪其日志时重新启动容器,则该命令将终止,您需要再次运行该命令来流化新容器的日志。
kubectl logs命令只显示当前容器的日志。要查看前一个容器中的日志,请使用--previous(或-p)选项。
请注意
根据您的集群配置,当日志文件达到一定大小时,也可以旋转它们。在这种情况下,kubectl logs将只显示当前的日志文件。流化日志时,必须重新启动命令,以便在日志旋转时切换到新文件。
当您删除一个pod时,它的所有日志文件也会被删除。要使pods日志永久可用,您需要设置一个集中的、集群范围的日志系统。第23章对此作了解释。
那么将日志写入文件的应用程序呢?
如果应用程序将其日志写入文件而不是标准输出,您可能想知道如何访问该文件。理想情况下,您应该配置集中式日志记录系统来收集日志,以便可以在集中位置查看它们,但有时您只是想保持简单,不介意手动访问日志。在接下来的两个小节中,您将学习如何将日志和其他文件从容器复制到您的计算机(以相反的方向),以及如何在运行容器中运行命令。您可以使用任何一种方法来显示日志文件或容器中的任何其他文件。
5.3.3 在容器之间复制文件
有时,您可能希望向运行的容器添加文件或从中检索文件。在运行容器中修改文件不是您通常会做的事情——至少不是在生产环境中——但在开发过程中它可能很有用。
Kubectl提供cp命令,将文件或目录从本地计算机复制到任意pod的容器中,或者从容器复制到您的计算机中。例如,将kubia pod容器中的/etc/hosts文件拷贝到本地文件系统的/tmp目录下,执行如下命令:
$ kubectl cp kubia:/etc/hosts /tmp/kubia-hosts
要从本地文件系统复制文件到容器中,运行以下命令:
请注意
kubectl cp命令要求在容器中存在tar二进制文件,但是这个要求将来可能会改变。
5.3.4 在运行容器中执行命令
当调试在容器中运行的应用程序时,可能需要从内部检查容器及其环境。Kubectl也提供了这个功能。可以使用kubectl exec命令执行容器文件系统中存在的任何二进制文件。
在容器中调用单个命令
例如,kubia pod中的容器中运行的进程如下所示:
#清单5.6在pod容器中运行的进程
$ kubectl exec kubia -- ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 1.3 812860 27356 ? Ssl 11:54 0:00 node app.js#node.js服务器
root 120 0.0 0.1 17500 2128 ? Rs 12:22 0:00 ps aux#刚才调用的命令
你在第2章中使用Docker命令来研究正在运行的容器中的进程,这是Kubernetes的等价命令。它允许您在任何pod中远程运行命令,而不必登录到承载该pod的节点。如果使用ssh在远程系统上执行命令,那么将看到kubectl exec与ssh没有太大不同。
在5.3.1节中,你在一个一次性的客户端pod中执行curl命令来发送请求到你的应用程序,但是你也可以在kubia pod中运行这个命令:
$ kubectl exec kubia -- curl -s localhost:8080
Hey there, this is kubia. Your IP is ::1.
为什么在KUBECTL EXEC命令中使用双破折号?
命令中的双破折号(--)将kubectl参数与要在容器中执行的命令分隔开。如果命令没有以破折号开头的参数,则没有必要使用双破折号。如果在前面的例子中忽略了双破折号,那么-s选项将被解释为kubectl exec的一个选项,并导致以下误导性错误:
The connection to the server localhost:8080 was refused – did you specify the right host or port?
这看起来像是Node.js服务器拒绝接受连接,但问题出在别处。curl命令永远不会执行。当kubectl试图在localhost:8080与Kubernetes API服务器通信时,这个错误是由kubectl自己报告的,而localhost:8080不是服务器所在的位置。如果运行kubectl options命令,您将看到-s选项可用于指定Kubernetes API服务器的地址和端口。kubectl没有将该选项传递给curl,而是将其作为自己的选项。加上双破折号可以防止这种情况发生。
幸运的是,为了防止出现这种情况,如果忘记使用双破折号,更新版本的kubectl会返回一个错误。
在容器中运行交互式shell
前面的两个示例展示了如何在容器中执行单个命令。当命令完成时,您将返回到shell。如果要在容器中运行多个命令,可以在容器中运行shell,如下所示:
$ kubectl exec -it kubia -- bash
root@kubia:/# #在容器中运行的shell的命令提示符
-it是两个选项的缩写:-i和-t,这表示您希望通过将标准输入传递给容器并将其标记为终端(TTY)来交互式地执行bash命令。
现在可以通过在shell中执行命令来探索容器的内部。例如,可以使用ls -la命令查看容器中的文件,使用ip link命令查看容器中的网络接口,使用ping命令测试容器的连通性。您可以运行容器中可用的任何工具。
并不是所有的容器都允许运行shell
应用程序的容器镜像包含许多重要的调试工具,但并非每个容器镜像都是如此。为了保持镜像较小并提高容器中的安全性,生产环境中使用的大多数容器不包含任何二进制文件,只包含容器主进程所需的二进制文件。这大大减少了攻击面,但也意味着您不能在生产容器中运行shell或其他工具。幸运的是,Kubernetes的一个新特性称为临时容器,它允许您通过将调试容器附加到正在运行的容器上来调试这些容器。
MEAP读者请注意
临时容器目前是alpha特性,这意味着它们可以随时更改甚至删除。这也是为什么它们目前没有在本书中解释。如果在本书投入生产之前,它们已经进入测试阶段,那么将增加一个解释它们的部分。
5.3.5 attach 运行中的容器
kubectl attach命令是与正在运行的容器交互的另一种方式。它将自己附加到在容器中运行的主进程的标准输入、输出和错误流。通常,您只使用它与读取标准输入的应用程序交互。
使用kubectl attach查看应用程序打印到标准输出的内容
如果应用程序不从标准输入读取数据,那么kubectl attach命令不过是将应用程序日志写入流的一种替代方法,因为这些日志通常被写入标准输出和错误流,而attach命令将它们写入流,就像kubectl logs -f命令那样。
运行以下命令连接到你的kubia pod:
$ kubectl attach kubia
Defaulting container name to kubia.
Use 'kubectl describe pod/kubia -n default' to see all of the containers in this pod.
If you don't see a command prompt, try pressing enter.
现在,当您在另一个终端中使用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的pod清单kubia.yaml只有一点点不同。下面的清单突出显示了它们之间的区别。
Listing 5.7 Enabling standard input for a container
apiVersion: v1
kind: Pod
metadata:
name: kubia-stdin #这个pod名为kubia-stdin
spec:
containers:
image: luksa/kubia:1.0-stdin #它使用了kubia应用程序的特殊版本
stdin: true #应用程序需要从标准输入流中读取
ports:
正如您在清单中看到的,如果在pod中运行的应用程序想要从标准输入中读取数据,您必须在pod清单中通过将容器定义中的stdin字段设置为true来实现这一点。这告诉Kubernetes为标准输入流分配一个缓冲区,否则当应用程序试图读取该流时,它将始终接收到一个EOF。
使用kubectl apply命令从这个清单文件创建pod:
$ kubectl apply -f kubia-stdin.yaml
pod/kubia-stdin created
要启用与应用程序的通信,再次使用kubectl port-forward命令,但是由于前面执行的port-forward命令仍然在使用本地端口8080,所以必须终止它或选择一个不同的本地端口转发到新的pod。你可以这样做:
$ kubectl port-forward kubia-stdin 8888:8080
Forwarding from 127.0.0.1:8888 -> 8080
Forwarding from [::1]:8888 -> 8080
命令行参数8888:8080指示命令将本地端口8888转发到pod的端口8080。
您现在可以通过http://localhost:8888:访问该应用程序
$ curl localhost:8888
Hey there, this is kubia-stdin. Your IP is ::ffff:127.0.0.1.
让我们使用kubectl attach将问候从“Hey there”更改为“Howdy”,以写入应用程序的标准输入流。执行如下命令:
注意命令中附加选项-i的使用。它指示kubectl将其标准输入传递给容器。
请注意
与kubectl exec命令一样,kubectl attach也支持--tty或-t选项,这表明标准输入是一个终端(tty),但是必须将容器配置为通过容器定义中的tty字段分配一个终端。
现在,您可以在终端中输入新的问候语并按下enter键。然后应用程序应该回复新的问候:
Howdy #输入欢迎语并按<ENTER>
Greeting set to: Howdy #这是应用程序的响应
要查看应用程序现在是否用新的问候语响应HTTP请求,请重新执行curl命令或在web浏览器中刷新页面:
$ curl localhost:8888
Howdy, this is kubia-stdin. Your IP is ::ffff:127.0.0.1.
这是新的问候方式。您可以通过在终端中使用kubectl attach命令输入另一行来再次更改它。要退出attach命令,请按Control-C或相应的键。
请注意
容器定义中的另一个字段stdinOnce确定在attach会话结束时标准输入通道是否关闭。默认情况下,它被设置为false,这允许您在每个kubectl attach 会话中使用标准输入。如果将其设置为true,则标准输入仅在第一个会话期间保持打开状态。
5.4 在pod中运行多个容器
5.2节中部署的kubia应用程序只支持HTTP。让我们添加TLS支持,这样它也可以通过HTTPS为客户端服务。您可以通过编写额外的代码来实现这一点,但是有一个更简单的选项,您根本不需要修改代码。
您可以在一个sidecar容器中与Node.js应用程序一起运行一个反向代理(见5.1.2节),并让它代表应用程序处理HTTPS请求。一个非常流行的能够提供这种功能的软件包叫做Envoy。Envoy proxy是一款高性能的开源服务代理,最初由Lyft开发,后来为本地云计算基金会(Cloud Native Computing Foundation)做了贡献。把它加到你的pod里。
5.4.1 使用特使代理扩展kubia Node.js应用
让我简要解释一下应用程序的新体系结构将是什么样子的。如下图所示,pod将有两个容器——Node.js和新的Envoy容器。Node.js容器将继续直接处理HTTP请求,但HTTPS请求将由Envoy处理。对于每个传入的HTTPS请求,Envoy将创建一个新的HTTP请求,然后通过本地环回设备(通过本地主机IP地址)将该请求发送给Node.js应用程序。
图5.10 pod容器和网络接口详细视图
Envoy还提供了一个基于web的管理界面,在下一章的一些练习中证明它很方便使用。
很明显,如果您在Node.js应用程序本身内实现TLS支持,应用程序将消耗更少的计算资源和更低的延迟,因为不需要额外的网络跳动,但添加Envoy代理可能是一个更快更容易的解决方案。它还提供了一个很好的起点,您可以在此基础上添加Envoy提供的许多其他特性,这些特性在应用程序代码本身中可能永远不会实现。请参阅envoyproxy.io上的Envoy代理文档了解更多。
5.4.2 添加Envoy代理
您将创建一个带有两个容器的新pod。您已经获得了Node.js容器,但还需要一个将运行Envoy的容器。
创建Envoy容器镜像
该代理的作者在Docker Hub上发布了官方代理容器镜像。您可以直接使用此镜像,但需要以某种方式向容器中的Envoy进程提供配置、证书和私钥文件。你将在第七章学习如何做到这一点。现在,您将使用一个已经包含所有三个文件的镜像。
我已经创建了镜像,并在docker.io/luksa/kubia-ssl-proxy:1.0上提供了它,但是如果你想自己构建它,你可以在书的代码归档中找到kubia-ssl-proxy-image目录下的文件。
该目录包含Dockerfile,以及代理将用于服务HTTPS的私钥和证书。它还包含了配置文件envoy.conf。在其中,您将看到代理被配置为侦听端口8443,终止TLS,并将请求转发到本地主机上的端口8080,Node.js应用程序正在侦听端口8080。代理也被配置为在端口9901上提供管理接口,如前所述。
创建pod清单
构建镜像之后,您必须为新的pod创建清单,如下面的清单所示。
#清单5.8 pod kubia-ssl (kubia-ssl.yaml)清单
apiVersion: v1
kind: Pod
metadata:
name: kubia-ssl #A
spec:
containers:
- name: kubia
image: luksa/kubia:1.0
ports:
- name: http #B
containerPort: 8080 #B
- name: envoy #C
image: luksa/kubia-ssl-proxy:1.0
ports:
- name: https #D
containerPort: 8443 #D
- name: admin #E
containerPort: 9901 #E
#A运行Node.js应用的容器
#B Node.js监听端口8080
#C运行特使代理的容器
#D代理的HTTPS端口
#E代理管理接口端口
这个pod的名称是kubia-ssl。它有两个容器:kubia和envoy。清单只比第5.2.1节中的清单稍微复杂一点。唯一的新字段是端口名称,包含这些字段是为了让任何读取清单的人都能理解每个端口号代表什么。
创建一个pod
使用命令kubectl apply -f kubia-ssl.yaml从清单中创建pod。然后使用kubectl get和kubectl description命令确认pod的容器已成功运行。
5.4.3 与双pod交互
当pod启动时,您可以在pod中开始使用应用程序,检查它的日志并从内部探索容器。
与应用程序通信
与前面一样,您可以使用kubectl port-forward来启用与pod中的应用程序的通信。因为它公开了三个不同的端口,所以您可以像下面这样向所有三个端口转发:
$ kubectl port-forward kubia-ssl 8080 8443 9901
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Forwarding from 127.0.0.1:8443 -> 8443
Forwarding from [::1]:8443 -> 8443
Forwarding from 127.0.0.1:9901 -> 9901
Forwarding from [::1]:9901 -> 9901
首先,通过在浏览器中打开URL http://localhost:8080或使用curl,确认你可以通过HTTP与应用程序通信:
$ curl localhost:8080
Hey there, this is kubia-ssl. Your IP is ::ffff:127.0.0.1.
如果可以,您还可以在https://localhost:8443上尝试通过HTTPS访问应用程序。使用curl,你可以这样做:
$ curl https://localhost:8443 --insecure
Hey there, this is kubia-ssl. Your IP is ::ffff:127.0.0.1.
成功!这位Envoy代理人完美地处理了这项任务。您的应用程序现在使用一个sidecar容器支持HTTPS。
为什么必须使用--INSECURE选项
在访问服务时必须使用--insecure选项有两个原因。说明Envoy代理使用的证书为自签名证书,且为域名example.com签发。您通过本地kubectl proxy访问该服务,并使用localhost作为URL中的域名,这意味着它与服务器证书中的名称不匹配。要使它匹配,你必须使用以下命令:
$ curl https://example.com:8443 --resolve example.com:8443:127.0.0.1
这确保了证书与请求的URL匹配,但是由于证书是自签名的,curl仍然不能验证服务器的合法性。您必须将服务器的证书替换为由受信任的机构签名的证书,或者使用--insecure标志;在本例中,您也不需要费心使用--resolve标志。
显示多个容器的pod日志
kubia-ssl pod包含两个容器,因此如果要显示日志,必须使用--container或-c选项指定容器的名称。以查看kubia容器日志为例,执行如下命令:
$ kubectl logs kubia-ssl -c kubia
Envoy代理运行在名为envoy的容器中,因此显示其日志如下:
或者,您可以使用--all-containers选项来显示这两个容器的日志:
您还可以将这些命令与第5.3.2节中介绍的其他选项组合使用。
在多容器pod的容器中运行命令
如果希望使用kubectl exec命令在pod的容器中运行shell或另一个命令,还可以使用--container或-c选项指定容器名。例如,在envoy容器中运行shell,执行如下命令:
请注意
如果不提供名称,kubectl exec默认使用pod清单中指定的第一个容器。
5.5 在pod启动时运行额外的容器
当一个pod包含多个容器时,所有的容器都是并行启动的。Kubernetes还没有提供一种机制来指定一个容器是否依赖于另一个容器,这将允许您确保一个容器在另一个容器之前启动。但是,Kubernetes允许您在pod的主容器启动之前运行一系列容器来初始化pod。本节将解释这种特殊类型的容器。
5.5.1 init容器(init containers)介绍
pod清单可以指定一个要在pod启动时和pod的正常容器启动之前运行的容器列表。这些容器用于初始化pod,并被适当地称为init容器。如下图所示,它们一个接一个地运行,并且必须在pod的主容器启动之前全部成功完成。
5.5.1 init容器(init containers)介绍
pod清单可以指定一个要在pod启动时和pod的正常容器启动之前运行的容器列表。这些容器用于初始化pod,并被适当地称为init容器。如下图所示,它们一个接一个地运行,并且必须在pod的主容器启动之前全部成功完成。
图5.11显示pod的init容器和常规容器如何启动的时间序列
Init容器与pod的常规容器类似,但它们不是并行运行的——一次只运行一个Init容器。
理解init容器可以做什么
init容器通常被添加到pods中以实现以下目的:
- 初始化pod主容器使用的卷中的文件。这包括从安全证书存储中检索主容器使用的证书和私钥、生成配置文件、下载数据等等。
- 初始化pod的网络系统。由于pod的所有容器共享相同的网络名称空间,因此网络接口和配置也相同,因此init容器对它所做的任何更改也会影响主容器。
- 延迟pod的主容器的启动,直到满足先决条件。例如,如果主容器在容器启动之前依赖于另一个可用的服务,则init容器可能会阻塞,直到该服务就绪。
- 通知外部服务pod即将开始运行。在某些特殊情况下,当应用程序的新实例启动时,必须通知外部系统,可以使用init容器来传递此通知。
您可以在主容器本身中执行这些操作,但使用init容器有时是更好的选择,并具有其他优点。让我们看看这是为什么。
理解什么时候将初始化代码移动到init容器是有意义的
使用init容器来执行初始化任务不需要重新构建主容器镜像,并且允许在许多不同的应用程序中重用单个init容器镜像。如果希望将相同的特定于基础设施的初始化代码注入到所有pod中,这一点特别有用。使用init容器还可以确保在任何(可能是多个)主容器启动之前完成初始化。
另一个重要原因是安全性。通过将攻击者可能使用的工具或数据从主容器转移到init容器,可以减少pod的攻击面。
例如,假设pod必须注册到外部系统。pod需要某种秘密令牌来针对这个系统进行身份验证。如果注册过程是由主容器执行的,那么这个秘密令牌必须存在于其文件系统中。如果在主容器中运行的应用程序存在漏洞,允许攻击者读取文件系统上的任意文件,那么攻击者可能会获得这个令牌。通过从init容器执行注册,令牌必须仅在init容器的文件系统中可用,这是攻击者无法轻易破坏的。
5.5.2 向pod中添加init容器
在pod清单中,init容器在spec部分的initContainers字段中定义,就像常规容器在其containers字段中定义一样。
在kubia-ssl pod中添加两个容器
让我们看一个向kubia pod添加两个init容器的例子。第一个init容器模拟初始化过程。它运行5秒,同时将几行文本打印到标准输出。
第二个init容器使用ping命令执行网络连通性测试,检查从pod中是否可以到达特定的IP地址。如果不指定IP地址,则使用地址1.1.1.1。如果你想自己构建它们,你可以在书的代码归档中找到Dockerfiles和其他工件。或者,您可以使用下面清单中指定的预构建镜像。
包含这两个init容器的pod清单在kubia-init.yaml文件中。下面的清单显示了如何定义init容器。
#清单5.9在pod清单中定义init容器
Listing 5.9 Defining init containers in a pod manifest: kubia-init.yaml
apiVersion: v1
kind: Pod
metadata:
name: kubia-init
spec:
initContainers: #A
- name: init-demo #B
image: luksa/init-demo:1.0
- name: network-check #C
image: luksa/network-connectivity-checker:1.0
containers: #D
- name: kubia
image: luksa/kubia:1.0
ports:
- name: http
containerPort: 8080
- name: envoy
image: luksa/kubia-ssl-proxy:1.0
ports:
- name: https
containerPort: 8443
- name: admin
containerPort: 9901
#A init容器在initContainers字段中指定
#B 首先运行这个容器
#C 在第一个容器完成后运行
#D 这些是pod里的常规容器。他们同时运行。
如您所见,init容器的定义非常简单。只指定每个容器的名称和镜像就足够了。
请注意
容器名在所有init容器和常规容器的联合中必须是唯一的。
部署带有init容器的pod
在您从清单文件中创建pod之前,在一个单独的终端中运行以下命令,这样您就可以看到在init和常规容器启动时pod的状态是如何变化的:
$ kubectl get pods -w
您还需要使用以下命令在另一个终端中查看事件:
$ kubectl get events -w
准备好后,运行apply命令创建pod:
$ kubectl apply -f kubia-init.yaml
使用init容器检查pod的启动
当pod启动时,检查kubectl get events -w命令打印的事件。下面的清单显示了您应该看到的内容。
#清单5.10 Pod事件显示了如何执行init容器
TYPE REASON MESSAGE
Normal Scheduled Successfully assigned pod to worker2
Normal Pulling Pulling image "luksa/init-demo:1.0" #A
Normal Pulled Successfully pulled image
Normal Created Created container init-demo
Normal Started Started container init-demo
Normal Pulling Pulling image "luksa/network-connec... #B
Normal Pulled Successfully pulled image
Normal Created Created container network-check
Normal Started Started container network-check
Normal Pulled Container image "luksa/kubia:1.0" #C
already present on machine
Normal Created Created container kubia
Normal Started Started container kubia
Normal Pulled Container image "luksa/kubia-ssl-
proxy:1.0" already present on machine
Normal Created Created container envoy
Normal Started Started container envoy
#A 第一个init容器的镜像被拉出,容器被启动
#B 在第一个init容器完成后,启动第二个init容器
#C pod的两个主要容器然后并行启动
清单显示了容器启动的顺序。首先启动init-demo容器。当它完成时,网络检查容器就会启动,当它完成时,两个主要容器kubia和envoy就会启动。
现在检查另一个终端中pod状态的转换。它们显示在下一个清单中。
#清单5.11在启动过程中涉及init容器的Pod状态变化
NAME READY STATUS RESTARTS AGE
kubia-init 0/2 Pending 0 0s
kubia-init 0/2 Pending 0 0s
kubia-init 0/2 Init:0/2 0 0s #A
kubia-init 0/2 Init:0/2 0 1s #A
kubia-init 0/2 Init:1/2 0 6s #B
kubia-init 0/2 PodInitializing 0 7s #C
kubia-init 2/2 Running 0 8s #D
#A 第一个init容器正在运行
#B 第一个init容器已经完成,第二个正在运行
#C 所有初始化容器已经成功完成
pod的主要容器正在运行
如清单所示,当init容器运行时,pod的状态显示已经完成的init容器的数量和总数。当所有初始化容器都完成时,pod的状态显示为PodInitializing。此时,主容器的镜像被拉出。当容器启动时,状态变为Running。
5.5.3 检查init容器
与普通容器一样,您可以使用kubectl exec在正在运行的init容器中运行额外的命令,并使用kubectl logs显示日志。
显示init容器日志
标准输出和错误输出(每个init容器都可以写入其中)的捕获方式与常规容器完全相同。使用kubectl logs命令可以显示init容器的日志,可以在容器运行时或完成后使用-c选项指定容器名称。使用如下命令查看kubia-init pod中的network-check容器日志。
#清单5.12显示init容器的日志
$ kubectl logs kubia-init -c network-check
Checking network connectivity to 1.1.1.1 ...
Host appears to be reachable
日志显示network-check init容器运行时没有错误。在下一章中,您将看到如果init容器失败会发生什么。
进入正在运行的init容器
您可以使用kubectl exec命令在init容器中运行shell或其他命令,就像使用普通容器一样,但是只能在init容器终止之前执行。如果您想自己尝试一下,可以从kubia-init-slow.yaml文件创建一个pod,它使init-demo容器运行60秒。当pod启动时,用以下命令在容器中运行shell:
$ kubectl exec -it kubia-init-slow -c init-demo -- sh
您可以使用shell从内部探索容器,但时间很短。当容器的主进程在60秒后退出时,shell进程也会终止。
通常情况下,只有当init容器未能及时完成时,才需要进入正在运行的init容器,并且需要查找原因。正常操作时,在执行kubectl exec命令之前,init容器会终止。
5.6 删除pods和其他对象
如果您已经尝试了本章和第2章中的练习,那么您的集群中现在存在几个pods和其他对象。为了结束这一章,您将学习各种删除它们的方法。删除一个pod将终止其容器并从节点中删除它们。删除Deployment对象会导致其pods的删除,而删除负载均衡器类型的服务则会解除负载均衡器(如果已经配置了负载均衡器)。
5.6.1 按名称删除pod
删除对象最简单的方法是通过名称删除对象。
删除单个pod
使用以下命令从集群中移除kubia pod:
$ kubectl delete po kubia
pod "kubia" deleted
通过删除一个pod,表示您不再需要pod或其容器存在。Kubelet关闭pod的容器,删除所有相关的资源,如日志文件,并在此过程完成后通知API服务器。然后移除Pod对象。
提示
默认情况下,kubectl delete命令会等待对象不再存在。要跳过等待,可以运行带有--wait=false选项的命令。
当pod处于关闭状态时,状态变为Terminating:
$ kubectl get po kubia
NAME READY STATUS RESTARTS AGE
kubia 1/1 Terminating 0 35m
如果您希望应用程序为其客户机提供良好的体验,那么准确地了解容器是如何关闭的是很重要的。这将在下一章中解释,我们将深入到pod及其容器的生命周期中。
请注意
如果您熟悉Docker,您可能想知道是否可以停止pod,然后再重新启动它,就像您可以使用Docker容器一样。答案是否定的。使用Kubernetes,您只能完全删除一个pod,然后稍后再创建它。
用一个命令删除多个pod
您还可以用一个命令删除多个pod。如果你运行kubia-init和kubia-init-slow pod,你可以通过指定它们的名字用空格隔开来删除它们,如下所示:
$ kubectl delete po kubia-init kubia-init-slow
pod "kubia-init" deleted
pod "kubia-init-slow" deleted
5.6.2 删除manifest文件中定义的对象
每当您从一个文件中创建对象时,您也可以通过将文件传递给delete命令来删除它们,而不是指定pod的名称。
通过指定清单文件删除对象
您可以删除从kubia-ssl.yaml文件创建的kubia-ssl pod,使用以下命令:
$ kubectl delete -f kubia-ssl.yaml
pod "kubia-ssl" deleted
在您的示例中,该文件只包含一个pod对象,但是您通常会遇到包含多个不同类型的对象的文件,这些对象代表一个完整的应用程序。这使得部署和删除应用程序就像分别执行kubectl apply -f app.yaml和kubectl delete -f app.yaml一样简单。
从多个清单文件中删除对象
有时,应用程序是在几个清单文件中定义的。您可以指定多个文件,用逗号分隔。例如:
$ kubectl delete -f kubia.yaml,kubia-ssl.yaml
请注意
您还可以使用此语法同时应用几个文件(例如:kubectl apply -f kubia.yaml,kubia-ssl.yaml)。
在使用Kubernetes的多年中,我实际上从未使用过这种方法,但我经常通过指定目录名(而不是单个文件的名称)来部署文件目录中的所有清单文件。例如,你可以在本书代码存档的基目录下运行以下命令来部署你在本章中创建的所有pod:
$ kubectl apply -f Chapter05/
这适用于目录中所有具有正确文件扩展名(.yaml, .json,和类似的)。然后您可以使用相同的方法删除pods:
$ kubectl delete -f Chapter05/
请注意
使用--recursive标志也可以扫描子目录。
5.6.3 删除所有pod
现在,您已经删除了除kubia-stdin和在第3章中使用kubectl create deployment命令创建的pod之外的所有pod。根据您扩展部署的方式,其中一些pods应该仍然在运行:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
kubia-stdin 1/1 Running 0 10m
kubia-9d785b578-58vhc 1/1 Running 0 1d
kubia-9d785b578-jmnj8 1/1 Running 0 1d
不用通过名字删除这些pod,我们可以使用--all选项将它们全部删除:
$ kubectl delete po --all
pod "kubia-stdin" deleted
pod "kubia-9d785b578-58vhc" deleted
pod "kubia-9d785b578-jmnj8" deleted
现在再次执行kubectl get pods命令确认没有pod存在:
$ kubectl get po
NAME READY STATUS RESTARTS AGE
kubia-9d785b578-cc6tk 1/1 Running 0 13s
kubia-9d785b578-h4gml 1/1 Running 0 13s
这是意想不到的!两个pod仍在运行。如果你仔细看他们的名字,你会发现这两个不是你刚刚删除的。age栏也表明这些是新的pod。您也可以尝试删除它们,但是您将看到,无论您如何频繁地删除它们,都会创建新的pods来取代它们。
这些pods不断弹出的原因是Deployment对象的存在。负责启用Deployment对象的控制器必须确保pods的数量总是与对象中指定的副本的数量相匹配。当您删除与部署相关联的pod时,控制器将立即创建一个替代pod。
要删除这些pods,您必须将Deployment扩展到0,或者完全删除Deployment对象。这表明您不再希望这个部署或它的pod存在于集群中。
5.6.4 删除大多数对象
您可以使用下一个清单中显示的命令删除到目前为止所创建的所有内容——包括部署、它的pods和服务。
#清单5.13删除所有类型的对象
$ kubectl delete all --all
pod "kubia-9d785b578-cc6tk" deleted
pod "kubia-9d785b578-h4gml" deleted
service "kubernetes" deleted
service "kubia" deleted
deployment.apps "kubia" deleted
replicaset.apps "kubia-9d785b578" deleted
命令中的第一个all表示要删除所有类型的对象。--all选项表示您想要删除每个对象类型的所有实例。在上一节中尝试删除所有pod时使用了这个选项。
当删除对象时,kubectl打印每个被删除对象的类型和名称。在前面的清单中,您应该看到它删除了pods、部署和服务,还删除了所谓的复制集对象。您将在第11章中了解这是什么,在该章中,我们将更详细地了解部署。
您将注意到,delete命令还删除了内置的kubernetes服务。不要担心这个问题,因为服务会在几分钟后自动重新创建。
使用此方法时,某些对象不会被删除,因为关键字all不包括所有对象类型。这是一种预防措施,以防止您不小心删除包含重要信息的对象。事件对象类型就是一个例子。
请注意
在delete命令中可以指定多个对象类型。例如,您可以使用kubectl delete events,all --all来删除事件以及all中包含的所有对象类型。
5.7 总结
在本章中,你学到了:
-
pod作为一个位于同一位置的组运行一个或多个容器。它们是部署和水平扩展的单元。一个典型的容器只运行一个进程。Sidecar容器补充pod中的主容器。
-
如果容器必须一起运行,那么它们只能是同一个pod的一部分。前端和后端进程应该在单独的pod中运行。这允许它们单独缩放。
-
当pod启动时,它的init容器一个接一个地运行。当最后一个init容器完成时,pod的主容器就会启动。您可以使用init容器从内部配置pod,延迟其主容器的启动,直到满足先决条件,或通知外部服务pod即将开始运行。
-
kubectl工具用于创建pods、查看它们的日志、在容器中复制文件、在这些容器中执行命令以及在开发过程中与单个pods通信。