Kubernetes in Action 3 pod:运行于Kubernetes中的容器

Posted 沛沛老爹

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kubernetes in Action 3 pod:运行于Kubernetes中的容器相关的知识,希望对你有一定的参考价值。

目录

(1)Kubernetes In Action 1:Kubernetes介绍

(2)Kubernetes In Action 2:开始使用Kubernetes和Docker

3 pod:运行于Kubernetes中的容器

本章内容涵盖

  1. 创建、启动和停止pod
  2. 使用标签组织pod和其他资源
  3. 使用特定标签对所有pod执行操作
  4. 使用命名空间将多个pod分到不重叠的组中
  5. 调度pod到指定类型的工作节点

上一章已经大致介绍了在Kubernetes中创建的基本组件,包括它们的基本功能概述。那么接下来我们将更加详细地介绍所有类型的Kubernetes对象(或资源),以便你理解在何时、如何及为何要使用每一个对象。其中pod是Kubernetes中最为重要的核心概念,而其他对象仅仅是在管理、暴露pod或被pod使用,所以我们将首先介绍pod这一核心概念。

3.1 介绍pod

我们已经了解到,pod是一组并置的容器,代表了Kubernetes中的基本构建模块。在实际应用中我们并不会单独部署容器,更多的是针对一组pod的容器进行部署和操作。然而这并不意味着一个pod总是要包含多个容器——实际上只包含一个单独容器的pod也是非常常见的。值得注意的是,当一个pod包含多个容器时,这些容器总是运行于同一个工作节点上——一个pod绝不会跨越多个工作节点,如图3.1所示。

图3.1 一个pod的所有容器都运行在同一个节点上;一个pod绝不跨越两个节点

 3.1.1 为何需要pod

关于为何需要pod这种容器?为何不直接使用容器?为何甚至需要同时运行多个容器?难道不能简单地把所有进程都放在一个单独的容器中吗?接下来我们将一一回答上述问题。

  1. 为何多个容器比单个容器中包含多个进程要好?

想象一个由多个进程组成的应用程序,无论是通过ipc(进程间通信)还是本地存储文件进行通信,都要求它们运行于同一台机器上。在Kubernetes中,我们经常在容器中运行进程,由于每一个容器都非常像一台独立的机器,此时你可能认为在单个容器中运行多个进程是合乎逻辑的,然而在实践中这种做法并不合理。

容器被设计为每个容器只运行一个进程(除非进程本身产生子进程)。如果在单个容器中运行多个不相关的进程,那么保持所有进程运行、管理它们的日志等将会是我们的责任。例如,我们需要包含一种在进程崩溃时能够自动重启的机制。同时这些进程都将记录到相同的标准输出中,而此时我们将很难确定每个进程分别记录了什么。

综上所述,我们需要让每个进程运行于自己的容器中,而这就是Docker和Kubernetes期望使用的方式。

3.1.2 了解pod

由于不能将多个进程聚集在一个单独的容器中,我们需要另一种更高级的结构来将容器绑定在一起,并将它们作为一个单元进行管理,这就是pod背后的根本原理。

在包含容器的pod下,我们可以同时运行一些密切相关的进程,并为它们提供(几乎)相同的环境,此时这些进程就好像全部运行于单个容器中一样,同时又保持着一定的隔离。这样一来,我们便能全面地利用容器所提供的特性,同时对这些进程来说它们就像运行在一起一样,实现两全其美。

同一pod中容器之间的部分隔离

在上一章中,我们已经了解到容器之间彼此是完全隔离的,但此时我们期望的是隔离容器组,而不是单个容器,并让每个容器组内的容器共享一些资源,而不是全部(换句话说,没有完全隔离)。Kubernetes通过配置Docker来让一个pod内的所有容器共享相同的Linux命名空间,而不是每个容器都有自己的一组命名空间。

由于一个pod中的所有容器都在相同的network和UTS命名空间下运行(在这里我们讨论的是Linux命名空间),所以它们都共享相同的主机名和网络接口。同样地,这些容器也都在相同的IPC命名空间下运行,因此能够通过IPC进行通信。在最新的Kubernetes和Docker版本中,它们也能够共享相同的PID命名空间,但是该特征默认是未激活的。

注意 当同一个pod中的容器使用单独的PID命名空间时,在容器中执行ps aux就只会看到容器自己的进程。

但当涉及文件系统时,情况就有所不同。由于大多数容器的文件系统来自容器镜像,因此默认情况下,每个容器的文件系统与其他容器完全隔离。但我们可以使用名为Volume的Kubernetes资源来共享文件目录,关于这一概念将在第6章进行讨论。

容器如何共享相同的IP和端口空间 

这里需强调的一点是,由于一个pod中的容器运行于相同的Network命名空间中,因此它们共享相同的IP地址和端口空间。这意味着在同一pod中的容器运行的多个进程需要注意不能绑定到相同的端口号,否则会导致端口冲突,但这只涉及同一pod中的容器。由于每个pod都有独立的端口空间,对于不同pod中的容器来说则永远不会遇到端口冲突。此外,一个pod中的所有容器也都具有相同的loopback网络接口,因此容器可以通过localhost与同一pod中的其他容器进行通信。

介绍平坦pod间网络

Kubernetes集群中的所有pod都在同一个共享网络地址空间中(如图3.2所示),这意味着每个pod都可以通过其他pod的IP地址来实现相互访问。换句话说,这也表示它们之间没有NAT(网络地址转换)网关。当两个pod彼此之间发送网络数据包时,它们都会将对方的实际IP地址看作数据包中的源IP。

图3.2 每个pod获取可路由的IP地址,其他pod都可以在该IP地址下看到该pod。

因此,pod之间的通信其实是非常简单的。不论是将两个pod安排在单一的还是不同的工作节点上,同时不管实际节点间的网络拓扑结构如何,这些pod内的容器都能够像在无NAT的平坦网络中一样相互通信,就像局域网(LAN)上的计算机一样。此时,每个pod都有自己的IP地址,并且可以通过这个专门的网络实现pod之间互相访问。这个专门的网络通常是由额外的软件基于真实链路实现的。

总结本节涵盖的内容:pod是逻辑主机,其行为与非容器世界中的物理主机或虚拟机非常相似。此外,运行在同一个pod中的进程与运行在同一物理机或虚拟机上的进程相似,只是每个进程都封装在一个容器之中。

3.1.3 通过pod合理管理容器

将pod视为独立的机器,其中每个机器只托管一个特定的应用。过去我们习惯于将各种应用程序塞进同一台主机,但是pod不是这么干的。由于pod比较轻量,我们可以在几乎不导致任何额外开销的前提下拥有尽可能多的pod。与将所有内容填充到一个pod中不同,我们应该将应用程序组织到多个pod中,而每个pod只包含紧密相关的组件或进程。

说到这里,对于一个由前端应用服务器和后端数据库组成的多层应用程序,你认为应该将其配置为单个pod还是两个pod呢?下面我们将对该问题做进一步探讨。

将多层应用分散到多个pod中

虽然我们可以在单个pod中同时运行前端服务器和数据库这两个容器,但这种方式并不值得推荐。前面我们已经讨论过,同一pod的所有容器总是运行在一起,但对于Web服务器和数据库来说,它们真的需要在同一台计算机上运行吗?答案显然是否定的,它们不应该被放到同一个pod中。那假如你非要把它们放在一起,有错吗?某种程度上来说,是的。

如果前端和后端都在同一个容器中,那么两者将始终在同一台计算机上运行。如果你有一个双节点Kubernetes集群,而只有一个单独的pod,那么你将始终只会用一个工作节点,而不会充分利用第二个节点上的计算资源(CPU和内存)。因此更合理的做法是将pod拆分到两个工作节点上,允许Kubernetes将前端安排到一个节点,将后端安排到另一个节点,从而提高基础架构的利用率。

基于扩缩容考虑而分割到多个pod中

另一个不应该将应用程序都放到单一pod中的原因就是扩缩容。pod也是扩缩容的基本单位,对于Kubernetes来说,它不能横向扩缩单个容器,只能扩缩整个pod。这意味着如果你的pod由一个前端和一个后端容器组成,那么当你扩大pod的实例数量时,比如扩大为两个,最终会得到两个前端容器和两个后端容器。

通常来说,前端组件与后端组件具有完全不同的扩缩容需求,所以我们倾向于分别独立地扩缩它们。更不用说,像数据库这样的后端服务器,通常比无状态的前端web服务器更难扩展。因此,如果你需要单独扩缩容器,那么这个容器很明确地应该被部署在单独的pod中。

何时在pod中使用多个容器

将多个容器添加到单个pod的主要原因是应用可能由一个主进程和一个或多个辅助进程组成,如图3.3所示。

图3.3 pod应该包含紧密耦合的容器组(通常是一个主容器和支持主容器的其他容器)

例如,pod中的主容器可以是一个仅仅服务于某个目录中的文件的Web服务器,而另一个容器(所谓的sidecar容器)则定期从外部源下载内容并将其存储在Web服务器目录中。在第6章中,我们将看到在这种情况下需要使用Kubernetes Volume,并将其挂载到两个容器中。

sidecar容器的其他例子包括日志轮转器和收集器、数据处理器、通信适配器等。

决定何时在pod中使用多个容器

回顾一下容器应该如何分组到pod中:当决定是将两个容器放入一个pod还是两个单独的pod时,我们需要问自己以下问题:

它们需要一起运行还是可以在不同的主机上运行?

它们代表的是一个整体还是相互独立的组件?

它们必须一起进行扩缩容还是可以分别进行?

基本上,我们总是应该倾向于在单独的pod中运行容器,除非有特定的原因要求它们是同一pod的一部分。图3.4将有助于我们记忆这一点。

图3.4 容器不应该包含多个进程,pod也不应该包含多个并不需要运行在同一主机上的容器

 管pod可以包含多个容器,但为了保持现在的简单性,本章将仅讨论单容器pod有关的问题。稍后我们将在第6章看到如何在一个pod中使用多个容器。

3.2 以YAML或JSON描述文件创建pod

pod和其他Kubernetes资源通常是通过向Kubernetes REST API提供JSON或YAML描述文件来创建的。此外还有其他更简单的创建资源的方法,比如在前一章中使用的 kubectl run 命令,但这些方法通常只允许你配置一组有限的属性。另外,通过YAML文件定义所有的Kubernetes对象之后,还可以将它们存储在版本控制系统中,充分利用版本控制所带来的便利性。

因此,为了配置每种类型资源的各种属性,我们需要了解并理解Kubernetes API对象定义。通过本书学习各种资源类型时,我们将会了解其中的大部分内容。需要注意的是,我们并不会解释每一个独立属性,因此在创建对象时还应参考 http://kubernetes.io/docs/reference/ 中的Kubernetes API参考文档。

3.2.1 检查现有pod的YAML描述文件

假设我们已经在上一章中创建了一些pod,接下来就来看看这些pod的YAML文件是如何定义的。我们将使用带有 -o yaml 选项的 kubectl get 命令来获取pod的整个YAML定义,正如下面的代码清单所示。

代码清单3.1 已部署pod的完整YAML

$ kubectl get po kubia -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2022-09-13T02:47:12Z"
  labels:
    run: kubia
  name: kubia
  namespace: default
  resourceVersion: "33327"
  uid: 8be3cd68-3610-423f-855c-da3afdbc3738
spec:
  containers:
  - image: jliudong/kubia
    imagePullPolicy: Always
    name: kubia
    ports:
    - containerPort: 8080
      protocol: TCP
    resources: 
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-ztllb
      readOnly: true
  dnsPolicy: ClusterFirst
  enableServiceLinks: true
  nodeName: minikube
  preemptionPolicy: PreemptLowerPriority
  priority: 0
  restartPolicy: Always
  schedulerName: default-scheduler
  securityContext: 
  serviceAccount: default
  serviceAccountName: default
  terminationGracePeriodSeconds: 30
  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
  volumes:
  - name: kube-api-access-ztllb
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2022-09-13T02:47:12Z"
    status: "True"
    type: Initialized
  - lastProbeTime: null
    lastTransitionTime: "2022-09-13T02:47:12Z"
    message: 'containers with unready status: [kubia]'
    reason: ContainersNotReady
    status: "False"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2022-09-13T02:47:12Z"
    message: 'containers with unready status: [kubia]'
    reason: ContainersNotReady
    status: "False"
    type: ContainersReady
  - lastProbeTime: null
    lastTransitionTime: "2022-09-13T02:47:12Z"
    status: "True"
    type: PodScheduled
  containerStatuses:
  - image: jliudong/kubia
    imageID: ""
    lastState: 
    name: kubia
    ready: false
    restartCount: 0
    started: false
    state:
      waiting:
        message: Back-off pulling image "jliudong/kubia"
        reason: ImagePullBackOff
  hostIP: 192.168.49.2
  phase: Pending
  podIP: 172.17.0.10
  podIPs:
  - ip: 172.17.0.10
  qosClass: BestEffort
  startTime: "2022-09-13T02:47:12Z"

上述代码清单的内容看上去较为复杂,但一旦我们理解了基础知识并知道如何区分重要部分和细枝末节时,它就变得非常简单。此外,稍后我们将看到,当创建一个新的pod时,需要写的YAML相对来说则要短得多。

介绍pod定义的主要部分

pod定义由这么几个部分组成:首先是YAML中使用的Kubernetes API版本和YAML描述的资源类型;其次是几乎在所有Kubernetes资源中都可以找到的三大重要部分:

  • metadata 包括名称、命名空间、标签和关于该容器的其他信息。
  • spec包含pod内容的实际说明,例如pod的容器、卷和其他数据。
  • status 包含运行中的pod的当前信息,例如pod所处的条件、每个容器的描述和状态,以及内部IP和其他基本信息。

代码清单3.1展示了一个正在运行的pod的完整描述,其中包含了它的状态。status部分包含只读的运行时数据,该数据展示了给定时刻的资源状态。而在创建新的pod时,永远不需要提供status部分。

上述三部分展示了Kubernetes API对象的典型结构。正如你将在整本书中看到的那样,其他对象也都具有相同的结构,这使得理解新对象相对来说更加容易。

对上述YAML中的每个属性进行深究的意义并不大,因此接下来我们将关注如何创建pod的最基本的YAML。

3.2.2 为pod创建一个简单的YAML描述文件

我们将创建一个名为kubia-manual.yaml的文件(可以在任意目录下创建该文件),或者下载本书的代码档案文件,然后在Chapter03文件夹中找到该文件。下面的清单展示了该文件的全部内容。

代码清单3.2 一个基本的pod manifest:kubia-manual.yaml

apiVersion: v1
 
  kind: Pod

metadata:

  name: kubia-manual

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://kubernetes.io/docs/api上的Kubernetes参考文档查看每个API对象支持哪些属性,也可以使用kubectl explain命令。

例如,当从头创建一个pod manifest时,可以从请求kubectl 来解释pod开始:

$ kubectl explain pods
KIND:     Pod
VERSION:  v1

DESCRIPTION:
     Pod is a collection of containers that can run on a host. This resource is
     created by clients and scheduled onto hosts.

FIELDS:
   apiVersion   <string>
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

   kind <string>
     Kind is a string value representing the REST resource this object
     represents. Servers may infer this from the endpoint the client submits
     requests to. Cannot be updated. In CamelCase. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

   metadata <Object>
     Standard object's metadata. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

   spec <Object>
     Specification of the desired behavior of the pod. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

   status   <Object>
     Most recently observed status of the pod. This data may not be up to date.
     Populated by the system. Read-only. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

 Kubectl打印出对象的解释并列出对象可以包含的属性,接下来就可以深入了解各个属性的更多信息。例如,可以这样查看spec属性:

$ kubectl explain pod.spec
KIND:     Pod
VERSION:  v1

RESOURCE: spec <Object>

DESCRIPTION:
     Specification of the desired behavior of the pod. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

     PodSpec is a description of a pod.

FIELDS:
   activeDeadlineSeconds    <integer>
     Optional duration in seconds the pod may be active on the node relative to
     StartTime before the system will actively try to mark it failed and kill
     associated containers. Value must be a positive integer.

   affinity <Object>
     If specified, the pod's scheduling constraints

   automountServiceAccountToken <boolean>
     AutomountServiceAccountToken indicates whether a service account token
     should be automatically mounted.
......

3.2.3 使用kubectl create来创建pod

我们使用kubectl create命令从YAML文件创建pod:

$ kubectl create -f kubia-manual.yaml

kubectl create -f 命令用于从YAML或JSON文件创建任何资源(不只是pod)。

得到运行中pod的完整定义

pod创建完成后,可以请求Kubernetes来获得完整的YAML,可以看到它与我们之前看到的YAML文件非常相似。在下一节中我们将了解返回定义中出现的其他字段,接下来就直接使用以下命令来查看该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 get pods

这里可以看到kubia-manual这个pod,状态显示它正在运行。有可能你像笔者一样想要通过与pod的实际通信来确认其正在运行,但该方法将在之后进行讨论。现在我们先查看应用的日志来检查是否存在错误。

3.2.4 查看应用程序日志

小型Node.js应用将日志记录到进程的标准输出。容器化的应用程序通常会将日志记录到标准输出和标准错误流,而不是将其写入文件,这就允许用户可以通过简单、标准的方式查看不同应用程序的日志。

容器运行时(在我们的例子中为Docker)将这些流重定向到文件,并允许我们运行以下命令来获取容器的日志:

$ docker logs <container id>

使用ssh命令登录到pod正在运行的节点,并使用docker logs命令查看其日志,但Kubernetes提供了一种更为简单的方法。

使用kubectl logs命令获取pod日志

为了查看pod的日志(更准确地说是容器的日志),只需要在本地机器上运行以下命令(不需要ssh到任何地方):

$ kubectl logs kubia-manual

在我们向Node.js应用程序发送任何Web请求之前,日志只显示一条关于服务器启动的语句。正如我们所见,如果该pod只包含一个容器,那么查看这种在Kubernetes中运行的应用程序的日志则非常简单。

注意 每天或者每次日志文件达到10MB大小时,容器日志都会自动轮替。 kubectl logs 命令仅显示最后一次轮替后的日志条目。

获取多容器pod的日志时指定容器名称

如果我们的pod包含多个容器,在运行 kubectl logs 命令时则必须通过包含-c <容器名称>选项来显式指定容器名称。在kubia-manual pod中,我们将容器的名称设置为kubia,所以如果该pod中有其他容器,可以通过如下命令获取其日志:

$ kubectl logs kubia-manual -c kubia

这里需要注意的是,我们只能获取仍然存在的pod的日志。当一个pod被删除时,它的日志也会被删除。如果希望在pod删除之后仍然可以获取其日志,我们需要设置中心化的、集群范围的日志系统,将所有日志存储到中心存储中。在第17章中我们将会解释如何设置集中的日志系统。

3.2.5 向pod发送请求

kubectl get 命令和我们的应用日志显示该pod正在运行,但我们如何在实际操作中看到该状态呢?在前一章中,我们使用kubectl expose 命令创建了一个service,以便在外部访问该pod。由于有一整章专门介绍service,因此本章并不打算使用该方法。此外,还有其他连接到pod以进行测试和调试的方法,其中之一便是通过端口转发。

将本地网络端口转发到pod中的端口

如果想要在不通过service的情况下与某个特定的pod进行通信(出于调试或其他原因),Kubernetes将允许我们配置端口转发到该pod。可以通过 kubectl port-forward 命令完成上述操作。例如以下命令会将机器的本地端口8888转发到我们的kubia-manual pod的端口8080:

$ kubectl port-forward kubia-manual 8888:8080

此时端口转发正在运行,可以通过本地端口连接到我们的pod。

通过端口转发连接到pod

在另一个终端中,通过运行在localhost:8888上的kubectl port-forward代理,可以使用curl 命令向pod发送一个HTTP请求:

$ curl localhost:8888

3.3 使用标签组织pod

此时我们的集群中只有两个正在运行的pod。但部署实际应用程序时,大多数用户最终将运行更多的pod。随着pod数量的增加,将它们分类到子集的需求也就变得越来越明显了。

例如,对于微服务架构,部署的微服务数量可以轻松超过20个甚至更多。这些组件可能是副本(部署同一组件的多个副本)和多个不同的发布版本(stable、beta、canary等)同时运行。这样一来可能会导致我们在系统中拥有数百个pod,如果没有可以有效组织这些组件的机制,将会导致产生巨大的混乱,如图3.6所示。该图展示了多个微服务的pod,包括一些运行多副本集,以及其他运行于同一微服务中的不同版本。

图3.6 微服务架构中未分类的pod

很明显,我们需要一种能够基于任意标准将上述pod组织成更小群体的方式,这样一来处理系统的每个开发人员和系统管理员都可以轻松地看到哪个pod是什么。此外,我们希望通过一次操作对属于某个组的所有pod进行操作,而不必单独为每个pod执行操作。

通过标签来组织pod和所有其他Kubernetes对象。

3.3.1 介绍标签

标签是一种简单却功能强大的Kubernetes特性,不仅可以组织pod,也可以组织所有其他的Kubernetes资源。详细来讲,标签是可以附加到资源的任意键值对,用以选择具有该确切标签的资源(这是通过标签选择器完成的)。只要标签的key在资源内是唯一的,一个资源便可以拥有多个标签。通常在我们创建资源时就会将标签附加到资源上,但之后我们也可以再添加其他标签,或者修改现有标签的值,而无须重新创建资源。

接下来回到图3.6中的微服务示例。通过给这些pod添加标签,可以得到一个更组织化的系统,以便我们理解。此时每个pod都标有两个标签:

  • app,它指定pod属于哪个应用、组件或微服务。
  • rel,它显示在pod中运行的应用程序版本是stable、beta还是canary。

定义 金丝雀发布是指在部署新版本时,先只让一小部分用户体验新版本以观察新版本的表现,然后再向所有用户进行推广,这样可以防止暴露有问题的版本给过多的用户。

如图3.7所示,通过添加这两个标签基本上可以将pod组织为两个维度(基于应用的横向维度和基于版本的纵向维度)。

图3.7 使用pod标签组织微服务架构中的pod

每个可以访问集群的开发或运维人员都可以通过查看pod标签轻松看到系统的结构,以及每个pod的角色。

3.3.2 创建pod时指定标签

现在,可以通过创建一个带有两个标签的新pod来查看标签的实际应用。使用以下代码清单中的内容创建一个名为kubia-manual-with-labels.yaml的新文件。

代码清单3.3 带标签的pod: kubia-manual-with-labels.yaml

metadata.labels 部分已经包含了creation_method=manual 和env=prod标签。现在来创建该pod:

$ kubectl create -f kubia-manual-with-labels.yaml

kubectl get pods命令默认不会列出任何标签,但我们可以使用--showlabels选项来查看:

$ kubectl get po --show-labels

如果你只对某些标签感兴趣,可以使用-L选项指定它们并将它们分别显示在自己的列中,而不是列出所有标签。接下来我们再次列出所有pod,并将附加到pod kubia-manual-v2上的两个标签的列展示如下:

$ kubectl get po -L creation_method.env

NAME    READY   STATUS    RESTARTS   AGE     CREATION_METHOD.ENV

kubia   0/1     Pending   0          6h39m

3.3.3 修改现有pod的标签

标签也可以在现有pod上进行添加和修改。由于pod kubia-manual也是手动创建的,所以为其添加 creation_method=manual 标签:

$ kubectl label po kubia-manual creation_method=manual

现在,将kubia-manual-v2 pod上的env=prod标签更改为env=debug,以演示现有标签也可以被更改。

注意 在更改现有标签时,需要使用 --overwrite 选项。

$ kubectl label po kubia-manual-v2 env=debug --overwrite

再次列出pod以查看更新后的标签:

$ kubectl get po -L creation_method.env

正如我们所看到,目前将标签附加到资源上看起来并没有什么价值,在现有资源上更改标签也是如此。但在下一章中我们将证实,这会是一项令人难以置信的强大功能。而首先我们需要看看这些标签除了在列出pod时用以简单显示外,还可以用来做什么。

3.4 通过标签选择器列出pod子集

在上一节中我们将标签附加到资源上,以便在列出资源时可以看到每个资源旁边的标签,这看起来并没有什么有趣的地方。但值得注意的是,标签要与标签选择器结合在一起。标签选择器允许我们选择标记有特定标签的pod子集,并对这些pod执行操作。可以说标签选择器是一种能够根据是否包含具有特定值的特定标签来过滤资源的准则。

标签选择器根据资源的以下条件来选择资源:

  • 包含(或不包含)使用特定键的标签
  • 包含具有特定键和值的标签
  • 包含具有特定键的标签,但其值与我们指定的不同

3.4.1 使用标签选择器列出pod

接下来我们使用标签选择器在之前创建的pod上进行操作,以观察我们手动创建的所有pod(用creation_method=manual标记了它们),并执行以下操作:

$ kubectl get po -l creation_method=manual

NAME              READY   STATUS    RESTARTS   AGE

kubia-manual-v2   1/1     Running   0          13h

列出包含env标签的所有pod,无论其值如何:

$ kubectl get po -l env

NAME              READY   STATUS    RESTARTS   AGE

kubia-manual-v2   1/1     Running   0          13h

同样列出没有env标签的pod:

$ kubectl get po -l '!env'

NAME          READY   STATUS    RESTARTS   AGE

kubia-n7fvq   1/1     Running   0          3d23h

kubia-qrb4q   1/1     Running   0          3d20h

kubia-ww77s   1/1     Running   0          3d23h

注意 确保使用单引号来圈引!env,这样bash shell才不会解释感叹号(译者注:感叹号在bash中有特殊含义,表示事件指示器)。

同理,我们也可以将pod与以下标签选择器进行匹配:

  • creation_method!=manual 选择带有 creation_method 标签,并且值不等于manual的pod
  • env in (prod,devel) 选择带有env标签且值为prod或devel的pod
  • env not in (prod,devel) 选择带有env标签,但其值不是prod或devel的pod

接下来回到我们面向微服务的架构示例中的pod,可以使用标签选择器app=pc(如图3.8所示)选择属于product catalog微服务的所有pod。

图3.8 使用标签选择器“app=pc”选择product catalog微服务的pod

3.4.2 在标签选择器中使用多个条件

在包含多个逗号分隔的情况下,可以在标签选择器中同时使用多个条件,此时资源需要全部匹配才算成功匹配了选择器。例如,如果我们只想选择product catalog微服务的beta版本pod,可以使用以下选择器:app=pc,rel=beta(如图3.9所示)。

图3.9 通过多个标签选择器选择pod

标签选择器不仅帮助我们列出pod,在对一个子集中的所有pod都执行操作时也具有重要意义。例如,在本章的后面我们将看到如何使用标签选择器来实现一次删除多个pod。此外标签选择器不只是被kubectl使用,在后续内容中我们也将看到它们在内部也被使用过。

3.5 使用标签和选择器来约束pod调度

迄今为止,我们创建的所有pod都是近乎随机地调度到工作节点上的。正如前一章我们所提到的,这恰恰是在Kubernetes集群中工作的正确方式。由于Kubernetes将集群中的所有节点抽象为一个整体的大型部署平台,因此对于你的pod实际调度到哪个节点而言是无关紧要的。对于每个pod而言,它获得所请求的确切数量的计算资源(CPU、内存等)及其从其他pod的可访问性,完全不受该pod所调度到的节点的影响,所以通常来说没有任何需要指定Kubernetes把pod调度到哪里的需求。

当然,某些情况下,我们希望对将pod调度到何处持一定发言权,你的硬件基础设施并不是同质便是一个很好的例子。如果你的某些工作节点使用机械硬盘,而其他节点使用固态硬盘,那么你可能想将一些pod调度到一组节点,同时将其他pod调度到另一组节点。另外,当需要将执行GPU密集型运算的pod调度到实际提供GPU加速的节点上时,也需要pod调度。

我们不会特别说明pod应该调度到哪个节点上,因为这将会使应用程序与基础架构强耦合,从而违背了Kubernetes对运行在其上的应用程序隐藏实际的基础架构的整个构想。但如果你想对一个pod应该调度到哪里拥有发言权,那就不应该直接指定一个确切的节点,而应该用某种方式描述对节点的需求,使Kubernetes选择一个符合这些需求的节点。这恰恰可以通过节点标签和节点标签选择器完成。

3.5.1 使用标签分类工作节点

如前所述,pod并不是唯一可以附加标签的Kubernetes资源。标签可以附加到任何Kubernetes对象上,包括节点。通常来说,当运维团队向集群添加新节点时,他们将通过附加标签来对节点进行分类,这些标签指定节点提供的硬件类型,或者任何在调度pod时能提供便利的其他信息。

假设我们集群中的一个节点刚添加完成,它包含一个用于通用GPU计算的GPU。我们希望向节点添加标签来展示这个功能特性,可以通过将标签gpu=true添加到其中一个节点来实现(只需从kubectl get nodes返回的列表中选择一个):

$ kubectl label node gke-kubia gpu=true

现在我们可以在列出节点时使用标签选择器,就像之前操作pod一样,列出只包含标签gpu=true的节点:

$ kubectl get nodes -l gpu=true

与预期相符,此时只有一个节点具有此标签。当然我们还可以尝试列出所有节点,并告知kubectl展示一个显示每个节点的gpu标签值附加列(kubectl get nodes -L gpu)。

3.5.2 将pod调度到特定节点

现在,假设我们想部署一个需要GPU来执行其工作的新pod。为了让调度器只在提供适当GPU的节点中进行选择,我们需要在pod的YAML文件中添加一个节点选择器。使用以下代码清单中的内容创建一个名为kubia-gpu.yaml的文件,然后使用 kubectl create -f kubia-gpu.yaml 命令创建该pod。

代码清单3.4 使用标签选择器将pod调度到特定节点:kubia-gpu.yaml

 我们只是在spec部分添加了一个nodeSelector字段。当我们创建该pod时,调度器将只在包含标签gpu=true的节点中选择(在我们的例子中,只有一个这样的节点)。

3.5.3 调度到一个特定节点

同样地,我们也可以将pod调度到某个确定的节点,由于每个节点都有一个唯一标签,其中键为 kubernetes.io/hostname,值为该节点的实际主机名,因此我们也可以将pod调度到某个确定的节点。但如果节点处于离线状态,通过hostname标签将nodeSelector设置为特定节点可能会导致pod不可调度。我们绝不应该考虑单个节点,而是应该通过标签选择器考虑符合特定标准的逻辑节点组。

这是一个关于标签和标签选择器是如何工作,以及如何使用它们影响Kubernetes操作的快速演示。当我们在接下来的两章中讨论Replication-Controllers和Service时,标签选择器的重要性和实用性也将变得更加明显。

注意 在第16章将介绍其他影响pod调度到哪个节点的方式。

以上是关于Kubernetes in Action 3 pod:运行于Kubernetes中的容器的主要内容,如果未能解决你的问题,请参考以下文章

Kubernetes in Action 3 pod:运行于Kubernetes中的容器

Kubernetes in Action 3 pod:运行于Kubernetes中的容器

Kubernetes in Action 3 pod:运行于Kubernetes中的容器

Kubernetes in Action 3 pod:运行于Kubernetes中的容器

Kubernetes in Action 5 服务:让客户端发现pod并与之通信

Kubernetes in Action 5 服务:让客户端发现pod并与之通信