kubernetes之pod
1.介绍pod
pod是一组并置的容器,代表了Kubernetes中的基本构建模 块。在实际应用中我们并不会单独部署容器,更多的是针对组pod 的容器进行部署和操作。然而这并不意味着一个pod总是要包含多个容器---实际上只包含一个单独容器的pod也是非常常见的。值得注意的是,当一个pod 包含多个容器时 这些容器总是运行于同一个工作节点上----- pod绝不会跨越多个工作节点。(能跨越多个节点是多个pod,由控制器控制,概念不要搞混)
1.1 为何需要pod
关于为何需要pod这种容器?为何不直接使用容器?为何甚至需要同时运行多个容器?难道不能简单地把所有进程都放在一个单独的容器中吗?接下来将回答上述问题。
为何多个容器比单个容器中包含多个进程要好
想象一个由多个进程组成的应用程序,无论是通过ipc (进程间通信)还是本地存储文件进行通信,都要求它们运行于同一台机器上。在 Kubernetes中,我们经常在容器中运行进程,由于每一个容器都非常像一台独立的机器,此时你可能认为单个容器中运行多个进程是合乎逻辑的,然而在实践中这种做法并不合理。
容器被设计为每个容器只运行一个进程除非进程本身产生子进程)。如果在单个容器中运行多个不相关进程,那么保持所有进程运行、管理它们的日志等将会是我们的责任。例如,我们需要包含一种在进程崩溃时能够自动重启的机制。同时这些进程都将记录到相同的标准输出中,而此时我们将难确定每个进程分别记录了什么。
综上所述,我们需要让每个进程运行于自己的容器中,而这就是Docker和 Kuberneters期望使用的方式。
1.2 了解pod
由于不能将多个进程聚集在一个单独的容器中,我们需要另一种更高级结构来将容器绑定在一起,并将它们作为一个单元进行管理,这就是pod背后的根本原理。
在包含容器的pod 下,我们可以同时运行一些密切相关进程,并为它们提供(几乎相同的环境,此时这些进程就好像全部运行于单个容器中一样,同时又保持着一定的隔离。这样一来,我们便能全面地利用容器所提供的特性,同时对这些进程来说它们就像运行在一起一样,实现两全其美。
同一pod中容器之间的部分隔离
在上一章中,我们己经了解到容器之间彼此是完全隔离的,但此时我们期望的是隔离容器组,而不是单个容器,并让每个容器组内的容器共享一些资源,而不是全部(换句话说没有完全隔离)。Kubernetes 过配置Docker来让一个pod 内的所有容器共享相同的Linux命名空间,而不是每个容器都有自己的一组命名空间。
由于一个pod 中的所有容器都在相同的network和UTS 命名空间下运行(这里我们讨论的是Linux命名空间),所以它们都共享相同的主机名和网络接口。同样地,这些容器也都在相同的IPC 命名空间下运行,因此能够通过IPC 进行通信。在最新的Kubernetes和D ocker版本中,它们也能够共享相同的PID命名空间,但是该特征默认是未激活的。
注意: 当同一个pod中的容器使用单独 PID 命名空间时,在容器中执 ps aux 就只会看到容器自己的进程。
当涉及文件系统时,情况就有所不同。由于大多数容器的文件系统来自容器镜像,因此默认情况下,每个容器的文件系统与其他容器完全隔离。但我们可以使用名为VolumeKube rnetes资源来共享文件目录。这个会单独讲解。
容器如何共享相同的IP 和端口空间
这里需强调的一点是,由于一个pod 中的容器运行于相同的Network命名空间中,因此它们共享相同的IP 地址和端口空间。这意味着在同一 pod 中的容器运行的多个进程需要注意不能绑定到相同的端口号,否则导致端口冲突,但这只涉及同一pod 中的容器。由于每个pod 都有独立的端口空间,对于不同pod 中的容器来说则永远不会遇到端口冲突。此外,一个pod 中的所有容器也都具有相同的loopback 络接口,因此容器可以通过localhost与同一 pod 中的其他容器进行通信。
介绍平坦pod间网络
Kubernetes集群中的所有pod 都在同一个共享网络地址空间中( 图3.2所示),这意味着每个pod 都可以通过其他podIP地址来实现相互访问。换句话说,这也表示它们之间没 NAT( 网络地址转换)网关。当两个pod 彼此之间发送网络数据时,它们都会将对方的实际IP 地址看作数据包中的源IP。
因此,pod之间的通信其实是非常简单的。不论是将两个pod安排在单一的还是不同的工作节点上,同时不管实际节点间的网络拓扑结构如何,这些pod内的容器都能够像在无NAT的平坦网络中一样相互通信,就像局域网(LAN)上的计算机一样。此时,每个pod都有自己的IP地址,并且可以通过这个专门的网络实现pod之间互相访问。这个专门的网络通常是由额外的软件基于真实链路实现的。
总结本节涵盖的内容:pod是逻辑主机,其行为与非容器世界中的物理主机或虚拟机非常相似。此外,运行在同一个pod中的进程与运行在同一物理机或虚拟机上的进程相似,只是每个进程都封装在一个容器之中。
1.3 通过pod合理管理容器
将pod视为独立的机器,其中每个机器只托管一个特定的应用。过去我们习惯于将各种应用程序塞进同一台主机,但是pod不是这么干的。由于pod比较轻量,我们可以在几乎不导致任何额外开销的前提下拥有尽可能多的pod。与将所有内容填充到一个pod中不同,我们应该将应用程序组织到多个pod中,而每个pod只包含紧密相关的组件或进程。
说到这里,对于一个由前端应用服务器和后端数据库组成的多层应用程序,你认为应该将其配置为单个pod还是两个pod呢?下面我们将对该问题做进一步探讨。
将多层应用分散到多个pod中
虽然我们可以在单个pod中同时运行前端服务器和数据库这两个容器,但这种方式并不值得推荐。前面我们己经讨论过,同一pod的所有容器总是运行在一起,但对于Web服务器和数据库来说,它们真的需要在同一台计算机上运行吗?答案显然是否定的,它们不应该被放到同一个pod中。那假如你非要把它们放在一起,有错吗?某种程度上来说,是的。
如果前端和后端都在同一个容器中,那么两者将始终在同一台计算机上运行。 如果你有一个双节点Kubemetes集群,而只有一个单独的pod,那么你将始终只会用一个工作节点,而不会充分利用第二个节点上的计算资源(CPU和内存)。因此更合理的做法是将pod拆分到两个工作节点上,允许Kubemetes将前端安排到一个节点,将后端安排到另一个节点,从而提高基础架构的利用率。
基于扩缩容考虑而分割到多个pod
另一个不应该将应用程序都放到单一pod中的原因就是扩缩容。pod也是扩缩容的基本单位,对于Kubemetes来说,它不能横向扩缩单个容器,只能扩缩整个pod。这意味着如果你的pod由一个前端和一个后端容器组成,那么当你扩大pod的实例数量时,比如扩大为两个,最终会得到两个前端容器和两个后端容器。
通常来说,前端组件与后端组件具有完全不同的扩缩容需求,所以我们倾向于分别独立地扩缩它们。更不用说,像数据库这样的后端服务器,通常比无状态的前端web服务器更难扩展。因此,如果你需要单独扩缩容器,那么这个容器很明确地应该被部署在单独的pod中。
何时在pod中使用多个容器
将多个容器添加到单个pod的主要原因是应用可能由一个主进程和一个或多个 辅助进程组成,如图
例如,pod中的主容器可以是一个仅仅服务于某个目录中的文件的Web服务器,而另一个容器(所谓的sidecar容器)则定期从外部源下载内容并将其存储在Web服务器目录中。也有需要使用Kubemetes Volume,并将其挂载到两个容器中。
sidecar容器的其他例子包括日志轮转器和收集器、数据处理器、通信适配器等。
决定何时pod 中使用多个容器
回顾一下容器应该如何分组到pod中:当决定是将两个容器放入一个pod还是两个单独的pod时,我们需要问自己以下问题:
-
- 它们需要一起运行还是可以在不同的主机上运行?
- 它们代表的是一个整体还是相互独立的组件?
- 它们必须一起进行扩缩容还是可以分别进行?
基本上,我们总是应该倾向于在单独 pod 中运行容器,除非有特定的原因要求它们是同一pod的一部分。图3.4将有助于我们记忆这一点。
尽管pod可以包含多个容器,但为了保持现在的简单性,这里仅讨论单容器pod有关的问题。其它文档讨论如何在一个pod中使用多个容器。
2.以YAML或JSON描述文件创建pod
pod和其他Kubernetes资源通常是通过向Kubernetes REST API提供JSON或YAML描述文件来创建的。此外还有其他更简单的创建资源的方法。使用的kubectl run命令,但这些方法通常只允许你配置一组有限的属性。另外, 通过YAML文件定义所有的Kubernetes对象之后,还可以将它们存储在版本控制系统中,充分利用版本控制所带来的便利性。
因此,为了配置每种类型资源的各种属性,我们需要了解并理解Kubernetes API对象定义。这里将会了解其中的大部分内容。需要注意的是,并不会解释每一个独立属性,且每个版本的api有些可能会不太相同。因此创建对象时还应参考 http://kubemetes.io/docs/reference/中的Kubemetes API 参考文档。
2.1 检查现有pod的YAML描述文件
现在就来看看这些pod的YAML 文件是如何定义的。我们将使用带有yaml选项的kubectl get命令来获取 pod的整个YAML定义,正如下面的代码清单所示。
上述代码清单的内容看上去较为复杂,但一旦我们理解了基础知识并知道如何区分重要部分和细枝末节时,它就变得非常简单。此外,稍后我们将看到,当创建一个新的pod时,需要写的YAML相对来说则要短得多。
介绍pod定义的主要部分
pod定义由这么几个部分组成:首先是YAML中使用的Kubernetes API版本和YAML描述的资源类型;其次是几乎在所有Kubernetes资源中都可以找到的三大重要部分:
-
- 包括名称、命名空间、标签和关于该容器的其他信息。
- spec包含pod内容的实际说明,例如pod的容器、卷和其他数据。
- status包含运行中的pod的当前信息,例如pod所处的条件、每个容器的描述和状态,以及内部IP和其他基本信息。
上面的代码清单展示了一个正在运行的pod的完整描述,其中包含了它的状态。status部分包含只读的运行时数据,该数据展示了给定时刻的资源状态。而在创建新的pod时,永远不需要提供status部分。
上述三部分展示了Kubemetes API对象的典型结构。正如你看到的那样,其他对象也都具有相同的结构,这使得理解新对象相对来说更加容易。
对上述YAML中的每个属性进行深究的意义并不大,因此接下来我们将关注如何创建pod的最基本的YAML。
2.2 为pod创建一个简单 YAML描述文件
#代码3.2 一个基本的pod manifest apiVersion: v1 #描述文件遵循v1版本的Kubernetes API kind: Pod #在描述一个pod metadata: name: kubia-manual #pod的名称 spec: containers: - image: luksa/kubia #创建容器所用的镜像 name: kubia #容器的名称 ports: - containerPort: 8080 #应用监听端口 protocol: TCP
很明显,我们能够感受到该代码清单比代码清单3.1中的定义要简单得多。接下来我们就对整个描述文件进行深入探讨,该文件遵循Kubernetes API的v1版本。我们描述的资源类型是Pod,名称为kubia-manual;该pod由基于luksa/kubia 镜像的单个容器组成。此外我们还给该容器命名,并表示它正在监听8080端口。
指定容器端口
在pod 定义中指定端口纯粹是展示性的(informational)。忽略它们对于客户端是否可以通过端口连接到pod 不会带来任何影响。如果容器通过绑定到地址0.0.0.0的端口接收连接,那么即使端口未明确列出在pod spec中,其他pod也依旧能够连接该端口。但明确定义端口仍是有意义的,在端口定义下,每个使用集群的人都可以快速查看每个pod对外暴露的端口。此外,明确端口可以给端口指定名称,这样更加方便。
使用kubectl explain来发现可能的API对象字段 在准备 manifest 时,可以转到 http://kubemetes.io/docs/api 上的 Kubernetes 参考文档查看每个API对象支持哪些属性,也可以使用kubectl explain命令。 例如,当从头创建一个pod manifest时,可以从请 kubectl来解释pod 开始:
$ kubectl explain pods
DESCRIPTION:
Pod is a collection of containers that can run on a host. This resource
is created by clients and scheduled onto hosts.
FIELDS:
kind <string>
Kind is a string value representing the REST resource this object
represents...
metadata <Object>
Standard object's metadata...
spec <Object>
Specification of the desired behavior of the pod...
status <Object>
Most recently observed status of the pod. This data may not be up to
date...
Kubectl打印出对象的解释并列出对象可以包含的属性,接下来就可以深入了解各个属性的更多信息。例如,可以这样查看spec属性:
$ kubectl explain pod.spec
RESOURCE: spec <Object>
DESCRIPTION:
Specification of the desired behavior of the pod...
podSpec is a description of a pod.
FIELDS:
hostPID <boolean>
Use the host's pid namespace. Optional: Default to false.
...
volumes <[]Object>
List of volumes that can be mounted by containers belonging to the
pod.
Containers <[]Object> -required-
List of containers belonging to the pod. Containers cannot currently
Be added or removed. There must be at least one container in a pod.
Cannot be updated. More info:
http://releases.k8s.io/release-1.4/docs/user-guide/containers.md
2.3 使用kuberctl create来创建pod
我们使用kubectl create命令从YAML文件创建pod:
$ kubectl create -f kubia-manual.yaml pod "kubia-manual" created
kubectl create -f命令用于从YAML或JSON文件创建任何资源(不只是pod)。
得到运行中pod的完整定义
pod 创建完成后,可以请 Kubernetes来获得完整的YAML,可以看到它与之前看到的Y AML文件非常相似。在下一节中我们将了解返回定义中出现的其他字段,接下来就直接使用以下命令来查看该pod 的完整描述文件:
$ kubectl get po kubia-manual -o yaml
也可以让kubectl返回JSON格式而不是YAML格式(显然,即使你使用YAML创建pod,同样也可以获取JSON格式的描述文件):
$ kubectl get po kubia-manual -o json
在pod列表中查看新创建的pod
创建好pod之后,如何知道它是否正在运行?此时可以列出pod来查看它们的状态:
$ kubectl getpods NAME READY STATUS RESTARTS AGE kubia-manual 1/1 Running 0 32s kubia-zxzij 1/1 Running 0 1d
2.4 查看应用程序日志
小型Node.js应将日志记录进程的标准输出。容器化的应用程序通常会将日志记录标准输出和标准错误流,而不是将其写入文件,这就允许用户可以通过简单、标准的方式查看不同应用程序的日志。
容器运行时(Docker)将这些流重定向到文件,并允许运行以下命令来获取容器的日志:
$ docker logs <container id>
使用ssh命令登录到pod正在运行的节点,并使用docker logs命令查看其日志,但Kubernetes提供了一种更为简单的方法。
使用kubectllogs命令获取pod日志
为了查看pod的日志(更准确地说是容器的日志),只需要在本地机器上运行以下命令(不需要ssh到任何地方):
$ kubecti logs kubia-manual
Kubia server starting...
在我们 Node.js应用程序发送任何Web请求之前,日志只显示一条关于服务器启动的语句。正如我们所见,如果该pod 只包含一个容器,那么查看这种在 Kubernetes中运行的应用程序的日志则非常简单。
注意每天或者每次日志文件达到10MB大小时,容器日志都会自动轮替。 kubectl logs 命令仅显示最后一次轮替后的日志条目。
获取多容器pod的日志时指定容器名称
如果我们的pod包含多个容器,在运行kubectl logs命令时则必须通过包含-c〈容器名称〉选项来显式指定容器名称。在kubia-manual pod中,容器的名称设置为kubia,所以如果该pod中有其他容器,可以通过如下命令获取日志:
$ kubectl logs kubia-manual -c kubia
Kubia server starting...
这里需要注意的是,我们只能获取仍然存在的pod的日志。当一个pod被删除时,它的日志也会被删除。如果希望在pod删除之后仍然可以获取其日志,我们需要设置中心化的、集群范围的日志系统,将所有日志存储到中心存储中。
2.5 向pod发送请求
kubectl get命令和我们的应用日志显示该pod正在运行,但如何在实际操作中看到该状态呢?方式有很多种,比如service等,但是这里就介绍端口转发方式。
将本地网络端口转发到pod 中的端口
如果想要在不通过service的情况下与某个特定的pod进行通信(出于调试或其他原因),kubernetes 将允许我们配置端口转发到该pod。可以通过kubectl port-forward命令完成上述操作。例如以下命令会将机器的本地端口8888转发到我们的kubia-manual pod的端口8080:
$ kubectl port-forward kubia-manual 8888:8080 ... Forwarding from 127.0.0.1:8888 -> 8080 ... Forwarding from [::1]:8888 - > 8080
此时端口转发正在运行,可以通过本地端口连接到我们的pod。
通过端口转发连接到pod
在另一个终端中,通过运行在localhost:8888上的kubectl port-forward代理,可以使用curl命令向pod发送一个HTTP请求:
$ curl localhost:8888 You've hit kubia-manual
实际上,kubectl进程和pod之间还有 一些额外的组件,但现在暂时不关注它们。
像这样使用端口转发是一种测试特定pod的有效方法,还有很多办法。
3.使用标签组织pod
此时我们的集群中只有两个正在运行的pod。但部署实际应用程序时,大多数用户最终将运行更多的pod。随着pod数量的增加,将它们分类到子集的需求也就变得越来越明显了。
例如,对于微服务架构,部署的微服务数量可以轻松超过20个甚至更多。这些组件可能是副本(部署同一组件的多个副本)和多个不同的发布版本(stable、beta、canary等)同时运行。这样一来可能会导致我们在系统中拥有数百pod,如果没有可以有效组织这些组件的机制,将会导致产生巨大的混乱,如图3.6所示。该图展示了多个微服务的pod,包括一些运行多副本集,以及其他运行于同一微服务中的不同版本。
很明显,我们需要一种能够基于任意标准将上述pod组织成更小群体的方式, 这样一来处理系统的每个开发人员和系统管理员都可以轻松地看到哪个pod是什么。此外,我们希望通过一次操作对属于某个组的所有pod进行操作,而不必单独为每个pod执行操作。
通过标签来组织pod和所有其他Kubernetes对象。
3.1 介绍标签
标签是一种简单却功能强大的Kubernetes特性,不仅可以组织pod,也可以组织所有其他的Kubernetes资源。详细来讲,标签是可以附加到资源的任意键值对,用以选择具有该确切标签的资源(这是通过标签选择器完成的)。只要标签的key在资源内是唯一的,一个资源便可以拥有多个标签。通常在我们创建资源时就会将标签附加到资源上,但之后我们也可以再添加其他标签,或者修改现有标签的值,而无须重新创建资源。
接下来回到图3.6中的微服务示例。通过给这些pod添加标签,可以得到一个更组织化的系统,以便我们理解。此时每个pod都标有两个标签:
-
- app,它指定pod属于哪个应用、组件或微服务。
- rel,它显示在pod中运行的应用程序版本是stable、beta还是canary。
解释: 金丝雀发布是指在部署新版本时,先只让一小部分用户体验新版本以观察新版本的表现,然后再向所有用户进行推广,这样可以防止暴露有问题的版本给过多的用户。
如图3.7所示,通过添加这两个标签基本上可以将pod组织为两个维度(基于应用的横向维度和基于版本的纵向维度)。
每个可以访问集群的开发或运维人员都可以通过查看pod标签轻松看到系统的 结构,以及每个pod的角色。
3.2 创建pod时指定标签
现在,可以通过创建一个带有两个标签的新pod来查看标签的实际应用。使用以下代码清单中的内容创建一个名为kubia-manual-with-labels.yaml的新文件。
#代码3.3带标签的 pod: kubia-manual-with-labels.yaml apiVersion: v1 kind: Pod metadata: name: kubia-manual-v2 labels: creation_method: manual #两个标签被附加到pod上 env: prod spec: containers: - image: luksa/kubia name: kubia ports: - containerPort: 8080 protocol: TCP
metadata.labels部分己经包含了和creation_method: manual和env=prod标签。现在来创建该pod:
$ kubectl create -f kubia-manual-with-labels.yaml pod "kubia-manual-v2" created
kubectl get pods命令默认不会列出任何标签,但我们可以使用--show-labels选项来查看:
$ kubectl get po --show-labels NAME READY STATUS RESTARTS AGE LABELS kubia-manual 1/1 Running 0 16m <none> kubia-manual-v2 1/1 Running 0 2m creat_method=manual,env=prod kubia-zxzij 1/1 Running 0 1d run=kubia
如果你只对某些标签感兴趣,可以使用-L选项指定它们并将它们分别显示在自己的列中,而不是列出所有标签。接下来我们再次列出所有pod,并将附加到pod kubia-manual-v2上的两个标签的列展示如下:
$ kubectl get po -L creation_method,env NAME READY STATUS RESTARTS AGE CREATION_METHOD ENV kubia-manual 1/1 Running 0 16m <none> <none> kubia-manual-v2 1/1 Running 0 2m manual prod kubia-zxzij 1/1 Running 0 1d <none> <none>
3.3 修改现有pod的标签
标签也可以在现有pod上进行添加和修改。由于pod kubia-manual也是手动创建的,所以为其添加creation_method=manual标签:
$ kubectl label po kubia-manual creation_method=manual pod "kubia-manual" labeled
现在,将kubia-manual-v2 pod上的env=prod标签更改为env=debug,以演示现有标签也可以被更改。
注意在更改现有标签时,需要使用--overwrite选项。
$ kubectl label po kubia-manual-v2 env=debug --overwrite pod "kubia-manual-v2" labeled
再次列出pod以查看更新后的标签:
$ kubectl get po -L creation_method,env NAME READY STATUS RESTARTS AGE CREATION_METHOD ENV kubia-manual 1/1 Running 0 16m manual <none> kubia-manual-v2 1/1 Running 0 2m manual debug kubia-zxzij 1/1 Running 0 1d <none> <none>
正如我们所看到,目前将标签附加到资源上看起来并没有什么价值,在现有资源上更改标签也是如此。那么下面看看标签的强大之处。
4.通过标签选择器列出pod子集
在上一节中我们将标签附加到资源上,以便在列出资源时可以看到每个资源旁边的标签,这看起来并没有什么有趣的地方。但值得注意的是,标签要与标签选择器结合在一起。标签选择器允许我们选择标记有特定标签的pod子集,并对这些pod执行操作。可以说标签选择器是一种能够根据是否包含具有特定值的特定标签来过滤资源的准则。
标签选择器根据资源的以下条件来选择资源:
-
- 包含(或不包含)使用特定键的标签
- 包含具有特定键和值的标签
- 包含具有特定键的标签,但其值与我们指定的不同
4.1 使用标签选择器列出pod
接下来使用标签选择器在之前创建的pod上进行操作,以观察手动创建的所有pod (用creation_method=manual标记了它们),并执行以下操作:
$ kubectl get po -l creation_method=manual NAME READY STATUS RESTARTS AGE kubia-manual 1/1 Running 0 51m kubia-manual-v2 1/1 Running 0 37m
列出包含env标签的所有pod,无论其值如何:
$ kubectl get po -l env NAME READY STATUS RESTARTS AGE kubia-manual-v2 1/1 Running 0 37m
同样列出没env标签 pod:
$ kubectl get po -l '!env' NAME READY STATUS RESTARTS AGE kubia-manual 1/1 Running 0 51m kubia-zxzij 1/1 Running 0 10d
注意确保使用单引号来圈引!env,这样bash shell才不会解释感叹号(感叹号在bash中有特殊含义,表示事件指示器)。
同理,我们也可以将pod与以下标签选择器进行匹配:
-
- creation_method!=manual选择带有creation_method标签,并且值不等于manual的pod
- env in (prod,devel)选择带有env标签且值为prod或devel的pod
- env notin (prod,devel)选择带有env标签,但其值不是prod或devel的pod
接下来回到我们面向微服务的架构示例中的pod,可以使用标签选择器app=pc(如图3.8)选择属于product catalog微服务的所有pod
4.2 标签选择器中使用多个条件
在包含多个逗号分隔的情况下,可以在标签选择器中同时使用多个条件,此时资源需要全部匹配才算成功匹配了选择器。例如,如果我们只想选择product catalog微服务的beta版本pod,可以使用以下选择器:app=pc,rel=beta
标签选择器不进帮助我们列出pod,在对一个子集中的所有pod都执行操作时也具有重要意义。例如,在本章的后面我们将看到如何使用标签选择 来现一次删除多个pod。此外标签选择器不只是被kubectl使用,在后续内容中我们将看到它们内部也被使用过。
5.使用标签和选择器来约束pod调度
迄今为止,我们创建的所有pod都是近乎随机地调度到工作节点上的。正如前一章我们所提到的,这恰恰是在Kubernetes集群中工作的正确方式。由于Kubernetes将集群中的所有节点抽象为一个整体的大型部署平台,因此对于你的pod实际调度到哪个节点而言是无关紧要的。对于每个pod而言,它获得所请求的确切数量的计算资源(CPU、内存等)及其从其他pod的可访问性,完全不受该pod所调度到的节点的影响,所以通常来说没有任何需要指定Kubernetes把pod调度到哪里的需求。
当然,某些情况下,我们希望对将pod调度到何处持一定发言权,你的硬件基础设施并不是同质便是一个很好的例子。如果你的某些工作节点使用机械硬盘,而其他节点使用固态硬盘,那么你可能想将一些pod调度到一组节点,同时将其他pod调度到另一组节点。另外,当需要将执行GPU密集型运算的pod调度到实际提供GPU加速的节点上时,也需要pod调度。
这里不会特别说明pod应该调度到哪个节点上,因为这将会使应用程序与基础架构强耦合,从而违背了Kubernetes对运行在其上的应用程序隐藏实际的基础架构的整个构想。但如果你想对一个pod应该调度到哪里拥有发言权,那就不应该直接指定一个确切的节点,而应该用某种方式描述对节点的需求,使Kubernetes选择一个符合这些需求的节点。这恰恰可以通过节点标签和节点标签选择器完成。
5.1 使用标签分类工作节点
如前所述,pod并不是唯一可以附加标签的Kubernetes资源。标签可以附加到任何Kubernetes对象上,包括节点。通常来说,当运维团队向集群添加新节点时,他们将通过附加标签来对节点进行分类,这些标签指定节点提供的硬件类型,或者任何在调度pod时能提供便利的其他信息。
假设我们集群中的一个节点刚添加完成,它包含一个用于通用GPU计算的GPU。我们希望向节点添加标签来展示这个功能特性,可以通过将标签gpu=true添加到其中一个节点来实现(只需从kubectl get nodes返回的列表中选择一个):
$ kubectl label node gke-kubia-85f6-node-0rrx gpu=true node "gke-kubia-85f6-node-0rrx" labeled
现在我们可以在列出节点时使用标签选择器,就像之前操作pod—样,列出只包含标签gpu=true节点。
$ kubectl get nodes -l gpu=true NAME STATUS AGE gke-kubia-85f6-node-0rrx Ready 1d
与预期相符,此时只有一个节点具有此标签。当然还可以尝试列出所有节点,并告知kubectl展示一个显示每个节点的gpu标签值附加列(kubectl get nodes -L gpu)。
5.2 将pod调度到特定节点
现在,假设我们想部署一个需要GPU来执行其工作的新pod。为了让调度器只在提供适当GPU的节点中进行选择,我们需要在pod的YAML文件中添加一个节点选择器。使用以下代码清单中的内容创建一个名为kubia-gpu.yaml的文件,然后使用 kkbectl create -f kubia-gpu.yaml命令创建该pod。
#代码3.4 使用标签选择器将pod调度到特定节点 apiVersion: v1 kind: Pod metadata: name: kubia-gpu spec: #节点选择器要求k8s只将pod部署到包含标签gpu=true的节点上 nodeSelector: gpu: "true" containers: - image: luksa/kubia name: kubia
只需要在spec部分添加了一个nodeSelector字段。当我们创建该pod时, 调度器将只在包含标签gpu=true的节点中选择(在目前的例子中,只有一个这样的节点)。
5.3 调度到一个特定节点
同样地,我们也可以将pod调度到某个确定的节点,由于每个节点都有一个唯一标签,其中键为kubernetes.io/hostname,值为该节点的实际主机名,因此我们也可以将pod调度到某个确定的节点。但如果节点处于离线状态,通过hostname标签将nodeSelector设置为特定节点可能会导致pod不可调度。我们绝不应该考虑单个节点,而是应该通过标签选择器考虑符合特定标准的逻辑节点组。
这是一个关于标签和标签选择器是如何工作,以及如何使用它们影响 Kubernetes操作的快速演示。其它文章中也会介绍Replication-Controllers 和Service时,标签选择器的重要性和实用性也将变得更加明显。
6.注解pod
除标签外,pod和其他对象还可以包含注解。注解也是键值对,所以它们本质上与标签非常相似。但与标签不同,注解并不是为了保存标识信息而存在的,它们不能像标签一样用于对对象进行分组。当我们可以通过标签选择器选择对象时,就不存在注解选择器这样的东西。
另一方面,注解可以容纳更多的信息,并且主要用于工具使用。Kubernetes也会将一些注解自动添加到对象,但其他的注解则需要由用户手动添加。
向Kubernetes引入新特性时,通常也会使用注解。一般来说,新功能的alpha 和beta版本不会向API对象引入任何新字段,因此使用的是注解而不是字段,一旦所需的API更改变得清晰并得到所有相关人员的认可,就会引入新的字段并废弃相关注解。
大量使用注解可以为每个pod或其他API对象添加说明,以便每个使用该集群的人都可以快速查找有关每个单独对象的信息。例如,指定创建对象的人员姓名的注解可以使在集群中工作的人员之间的协作更加便利。
6.1 查找对象的注解
看一个Kubernetes自动添加注解到前一章中创建的pod的注解示例。为了查看注解,需要获取pod的完整YAML文件或使用kubectl describe命令。在下述代码清单中使用第一个方法。
$ kubectl get po kubia-zxzij -o yaml apiVersion: v1 kind: Pod metadata: annotations: kubernetes.io/created-by: | {"kind":"SerializedReference", "apiVersion":"v1", "reference":{"kind":"ReplicationController", "namespace":"default", ...
如上kubernetes.io/created-by注解保存了创建该pod的对象的一些JSON数据,而没有涉及太多细节,因此注解并不会是我们想要放入标签的东西。相对而言,标签应该简短一些,而注解则可以包含相对更多的数据(总共不超过 256KB)。
注意kubernetes.io/created-by注解在版本1.8中已经废弃,将会在版本1.9中删除,所以在YAML文件中不会再看到该注解。
6.2 添加和修改注解
显然,和标签一样,注解可以在创建时就添加到pod中,也可以在之后再对现有的pod进行添加或修改。其中将注解添加到现有对象的最简单的方法是通过 kubectl annotate命令。
我们现在可以尝试添加注解到kubia-manual pod中:
$ kubectl annotate pod kubia-manual mycompany.com/someannotation="foo bar" pod "kubia-manual" annotated
上面将注解mycompany.com/someannotation添加为foo bar。使用这种格式的注解键来避免键冲突是一个好方法。当不同的工具或库向对象添加注解时,如果它们不像刚刚那样使用唯一的前缀,可能会意外地覆盖对方的注解。使用kubectl describe命令查看刚刚添加的注解:
$ kubectl describe pod kubia-manual Annotations: mycompany.com/someannotation="foo bar"
7.使用命名空间对资源进行分组
首先回到标签的概念,可以看到标签是如何将pod和其他对象组织成组的。由于每个对象都可以有多个标签,因此这些对象组可以重叠。另外,当在集群中工作(例如通过kubectl)时,如果没有明确指定标签选择器,我们总能看到所有对象。
但是,当你想将对象分割成完全独立且不重叠的组时,又该如何呢?可能你每次只想在一个小组内进行操作,因此Kubernetes也能将对象分组到命名空间中。这个和用于相互隔离进程的Linux命名空间不一样,Kubernetes命名空间简单地为对象名称提供了一个作用域。此时并不会将所有资源都放在同一个命名空间中,而是将它们组织到多个命名空间中,这样可以允许多次使用相同的资源名称(跨不同的命名空间)。
7.1 了解对命名空间的需求
在使用多个namespace的前提下,可以将包含大量组件的复杂系统拆分为更小的不同组,这些不同组也可以用于在多租户环境中分配资源,将资源分配为生产、开发和QA环境,或者以其他任何你需要的方式分配资源。资源名称只需在命名空间内保持唯一即可,因此两个不同的命名空间可以包含同名的资源。虽然大多数类型的资源都与命名空间相关,但仍有一些与它无关,其中之一便是全局且未被约束于单一命名空间的节点资源。
7.2 现其他命名空间及其pod
首先,来列出集群中的所有命名空间:
$ kubectl get ns NAME LABELS STATUS AGE default <none> Active 1h kube-public <none> Active 1h kube-system <none> Active 1h
到目前为止,我们只在default命名空间中进行操作。当使用kubectl get命令列出资源时,我们从未明确指定命名空间,因此kubectl总是默认为default命名空间,只显示该命名空间下的对象。但从列表中我们可以看到还存在kube-public和kube-system命名空间。接下来可以使用kubectl命令指定命名空间来列出只属于该命名空间的pod,如下所示为属于kube-system命名空间的pod:
$ kubectl get po --namespace kube-system #也可以使用-n来代替--namespace NAME READY STATUS RESTARTS AGE fluentd-cloud-kubia-e8fe-node-txje 1/1 Running 0 1h heapster-vll-fzlge 1/1 Running 0 1h kube-dns-v9-p8a4t 0/4 Pending 0 1h kube-ui-v4-kdlai 1/1 Running 0 1h l7-lb-controller-v0.5.2-bue96 2/2 Running 92 1h
namespace能够将不属于一组的资源分到不重叠的组中。如果有多个用户或用户组正在使用同一个Kubernetes集群,并且它们都各自管理自己独特的资源集合,那么它们就应该分别使用各自的命名空间。这样一来,它们就不用特别担心无意中修改或删除其他用户的资源,也无须关心名称冲突。如前所述,命名空间为资源名称提供了一个作用域。
除了隔离资源,命名空间还可用于仅允许某些用户访问某些特定资源,甚至限制单个用户可用的计算资源数量。其它篇幅讲解。
7.3 创建一个命名空间
命名空间是一种和其他资源一样的Kubernetes资源,因此可以通过将YAML文件提交到Kubernetes API服务器来创建该资源。
从YAML文件创建命名空间
apiVersion: v1
kind: Namespace
metadata:
name: custom-namespace
或者也可以使用kubectl create namespace命令创建命名空间
$ kubectl create namespace custom-namespace namespace "custom-namespace" created
7.4 管理其他命名空间中的对象
如果想要在刚创建的命名空间中创建资源,可以选择在metadata字段中添加一个namespace:custom-namespace属性,也可以在使用kubectl create命令创建资源时指定命名空间:
$ kubectl create -f kubia-manual.yaml -n custom-namespace pod "kubia-manual" created
在列出、描述、修改或删除其他命名空间中的对象时,需要给kubectl命令传递namespace(或-n)选项。如果不指定命名空间,kubectl将在当前上下文中配置的默认命名空间中执行操作。而当前上下文的命名空间和当前上下文本身都可以通过kubectl config命令进行更改。
7.5 命名空间提供的隔离
在结束命名空间这一部分之前,需要解释一下命名空间不提供什么--至少不是开箱即用的。尽管命名空间将对象分隔到不同的组,只允许你对属于特定命名空间的对象进行操作,但实际上命名空间之间并不提供对正在运行的对象的任何隔离。
例如,你可能会认为当不同的用户在不同的命名空间中部署pod时,这些pod应该彼此隔离,并且无法通信,但事实却并非如此。命名空间之间是否提供网络隔离取决于Kubernetes所使用的网络解决方案。当该解决方案不提供命名空间间的网络隔离时,如果命名空间foo中的某个pod知道命名空间bar中pod的IP地址,那它就可以将流量(例如HTTP请求)发送到另一个pod。
8.停止和移除pod
8.1 按名称删除pod
首先,将按名称删除kubia-gpu pod:
$ kubectl delete po kubia-gpu pod "kubia-gpu" deleted
在删除pod的过程中,实际上我们在指示Kubernetes终止该pod中的所有谷器。 Kubernetes向进程发送一个SIGTERM信号并等待一定的秒数(默认为30),使其正常关闭。如果它没有及时关闭,则通过SIGKILL终止该进程。因此,为了确保你的进程总是正常关闭,进程需要正确处理SIGTERM信号。
还可以通过指定多个空格分隔的名称来删除多个pod (例如:kubectl delete po pod1 pod2)。
8.2 使用标签选择器删除pod
与根据名称指定pod进行删除不同,此时将使用了解过的关于标签选择器来kubia-manual和kubia-manual-v2 pod。这两个pod都包含标签creation_method=manual,因此可以通过使用一个标签选择器来删除它们:
$ kubectl delete po -l creation_method=manual pod nkubia-manual" deleted pod Mkubia-manual-v2" deleted
在之前的微服务示例中,可能有几十个(或可能有几百个)Pod。例如,通过指定rel=canary标签选择器,可以一次删除所有金丝雀pod:
kubectl delete po -l rel=canary
8.3 通过删除整个命名空间来删除pod
再回过头看看custom-namespace中的pod。此时不再需要该命名空间中的pod,也不需要命名空间本身。这意味着,可以简单地删除整个命名空间(pod将会伴随命名空间自动删除)。现在使用以下命令删除custom-namespace :
$ kubectl delete ns custom-namespace namespace "custom-namespace" deleted
8.4 删除命名空间中的所有pod,但保留命名空间
这一次不再删除一个特定pod,而是通过使用--all选项告诉Kubernetes 删除当前命名空间中的所有pod。
$ kubectl delete po --all pod "kubia-zxzij" deleted
8.5 删除命名空间中的(几乎)所有资源
通过使用单个命令删除当前命名空间中的所有资源,可以删除ReplicationCcontroller和pod,以及我们创建的所有service:
$ kubectl delete all --all pod "kubia-09as0" deleted replicationcontroller "kubia" deleted service "kubernetes" deleted service "kubia-http" deleted
命令中的第一个all指定正在删除所有资源类型,而--all选项指定将删除所有资源实例,而不是按名称指定它们(我们在运行前一个删除命令时已经使用过 此选项)。
注意使用all关键字删除所有内容并不是真的完全删除所有内容。一些资源(比如Secret)会被保留下来,并且需要被明确指定删除。
删除资源时,kubectl将打印它删除的每个资源的名称。
注意:kubectl delete all --all命令也会删除名为kubernetes的Service,但它应该会在几分钟后自动重新创建。