Kubernetes 实战 —— 03. pod: 运行于 Kubernetes 中的容器

介绍 pod P53

pod 是 Kubernetes 中最为重要的核心概念,而其他对象仅仅用于 pod 管理、 pod 暴露或被 pod 使用。 pod 是一组并置的容器,代表了 Kubernetes 中的基本构建模块。 P53

当一个 pod 包含多个容器时,这些容器总是运行于同一个工作节点上——一个 pod 绝不会跨越多个工作节点。 P54

图 3.1 一个 pod 的所有容器都运行在同一个节点上

为何需要 pod P54

为何多个容器比单个容器中包含多个进程要好 P54

假设一个由多个进程组成的应用程序,无论是通过 IPC (进程间通信)还是本地存储文件进行通信,都要求它们运行于同一台机器上。如果在单个容器中运行多个不相关的进程,那么保持所有进程运行、管理它们的日志等将会是我们的责任。例如,我们需要包含一种在进程奔溃时能够自动重启的机制;同时这些进程都将记录到相同的标准输出中,而此时我们将很难确定每个进程分别记录了什么。 P54

了解 pod P55

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

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

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

Kubernetes 通过配置 Docker 来让一个 pod 内的所有容器共享相同的 Linux 命名空间,而不是每个容器都有自己的一组命名空间。 P55

由于一个 pod 中的所有容器都在相同的 Network 和 UTS 命名空间下运行(01. Kubernetes 介绍),所以它们都共享相同的主机名和网络接口。同样地,这些容器也都在相同的 IPC 命名空间下运行,因此能够通过 IPC 进行通信。在最新的 Kubernetes 和 Docker 版本中,它们也能够共享相同的 PID 命名空间,但是该特性默认未开启。 P55

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

由于大多数容器的文件系统来自容器镜像,因此默认情况下,每个容器的文件系统与其他容器完全隔离。但我们可以使用名为 Volume 的 Kubernetes 资源来共享文件目录。 P55

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

由于同一个 pod 中的容器运行于相同的 Network 命名空间中,因此在同一个 pod 中的容器运行的多个进程需要注意不能绑定到相同的端口号,否则会导致端口冲突。同一个 pod 中的所有容器具有相同的 loopback 网络接口,因此容器可以通过 localhost 与同一 pod 中的其他容器进行通信。 P55

由于每个 pod 都有独立的端口空间,对于不同 pod 中的容器来说则永远不会遇到端口冲突。 P55

介绍扁平化 pod 间网络 P56

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

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

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

通过 pod 合理管理容器 P56
  • 将多层应用分散到多个 pod 中 P57
  • 基于扩缩容考虑而分割到多个 pod 中: Kubernetes 不能横向扩缩单个容器,只能扩缩整个 pod P57
  • 紧密耦合到容器组可以添加到同一个 pod 中:通常为一个主容器和若干个支持容器 P57
    • 主容器:可以是 Web 服务器
    • 支持容器:可以是日志轮转器、收集器、数据处理器、通信适配器等

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

  • 除非有特定的原因要求容器是同一个 pod 中的一部分,否则应该在单独的 pod 中运行容器 P58
    • 它们需要一起运行还是可以在不同的主机上运行?
    • 它们代表的是一个整体还是相互独立的组件?
    • 它们必须一起进行扩缩容还是可以分别进行?

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

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

通过 YAML 文件定义所有的 Kubernetes 对象之后,还可以将它们存储在版本控制系统中,充分利用版本控制所带来的便利性。 P59

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

kubectl get pod -o yam <pod-name> 命令可以查看指定 pod 的完整 YAML 定义。 P59

介绍 pod 定义的主要部分 P59

  • YAML 中使用的 Kubernetes API 版本
  • YAML 描述的资源类型
  • metadata: 包括名称、命名空间、标签和关于该容器的其他信息
  • spec: 包含 pod 内容的实际说明,例如 pod 的容器、卷和其他数据
  • status: 包含运行中的 pod 的当前信息,例如 pod 所处的条件、每个容器的描述和状态,以及内部 IP 和其他基本信息
    • status 包含只读的运行时数据,该数据展示了给定时刻的资源状态。在创建新的 pod 时, status 部分不需要提供
创建一个简单的 YAML 描述文件 P61
# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 资源类型为 Pod
kind: Pod
metadata:
  # pod 的名称 
  name: kubia-manual
spec:
  containers:
  # 创建容器所使用的镜像
  - image: idealism/kubia
    # 容器的名称
    name: kubia
    ports:
    # 应用监听的端口
    - containerPort: 8080
      protocol: TCP

指定容器端口 P61

在 pod 定义中的端口仅仅是展示性的 (informational) ,忽略它们不影响客户端通过端口连接到 pod 。如果容器通过绑定到地址 0.0.0.0 的端口接受连接,那么即使端口未明确列出在 pod spec 中,其他 pod 也依旧能够连接到该端口。 P61

明确定义端口的意义: P62

  • 每个使用集群的人都可以快速查看每个 pod 对外暴露的端口
  • 允许为每个端口指定一个名称

可以使用 kubectl explain 发现可用的 API 对象字段, kubectl explain pod 可以查看 pod 的 各个属性,然后通过选择对应的属性 (kubectl explain pod.spec) 深入了解每个属性的更多信息。 P62

使用 kubectl create 来创建 pod P63
# kubectl create -f 可以从 YAML 或 JSON 文件创建任何资源,不仅仅是 pod
kubectl create -f kubia-manual.yaml
# 查看刚刚创建的 kubia-manual 的 完整描述文件
kubectl get pod kubia-manual -o yaml
查看应用程序日志 P64

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

  • docker logs <container> 允许我们查看主机上指定容器的日志
  • kubectl logs <pod-name> -c <container> 允许我们查看指定 pod 中指定容器的日志,如果该 pod 只包含一个容器,那么 -c <container> 可以省略

当一个 pod 被删除时,它的日志也会被删除。如果希望在 pod 删除之后仍然可以获取其日志,我们需要设置中心化的、集群范围的日志系统,将所有日志存储到中央存储中。 P64

向 pod 发送请求 P65

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

kubectl port-forward kubia-manual 8888:8080 可以将本地端口 8888 转发到 kubia-manual pod 到端口 8080 ,这样我们就可以在本地使用 curl localhost:8888 向 pod 发送一个 HTTP 请求。 P65

图 3.5 使用 kubectl port-forward 和 curl 时的简单视图

使用标签组织 pod P66

微服务架构下可能会部署很多组件,这些组件可能是副本(部署同一组件的多个副本)和多个不同的发布版本(stable, beta, canary 等)同时运行,系统中可能拥有数百个 pod ,如果没有可以有效组织这些组件的机制,将会导致巨大的混乱。 P66

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

介绍标签 P66

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

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

  • app: 基于应用的横向纬度,指定 pod 属于哪一个应用、组件或微服务
  • rel: 基于版本的纵向纬度,显示在 pod 中运行的应用程序版本 (stable, beta, canary)
    • 金丝雀版 (canary) :部署新版本时,只让一小部分用户体验新版本以观察新版本的表现,然后再向所有用户进行推广,这样可以防止暴露有问题的版本给过多的用户

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

创建 pod 时指定标签 P67

基于 kubia-manual.yaml 创建一个新的描述文件 kubia-manual-with-labels.yaml ,并添加 metadata.labels 属性,指定 creation_method=manualenv=prod 标签。

# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 资源类型为 Pod
kind: Pod
metadata:
  # pod 的名称
  name: kubia-manual-v2
  # pod 的标签
  labels:
    creation_method: manual
    env: prod
spec:
  containers:
    # 创建容器所使用的镜像
    - image: idealism/kubia
      # 容器的名称
      name: kubia
      ports:
        # 应用监听的端口
        - containerPort: 8080
          protocol: TCP
  • kubectl create -f kubia-manual-with-labels.yaml: 创建一个新 pod
  • kubectl get pods --show-labels: 查看所有 pod 并列出全部标签
    NAME              READY   STATUS    RESTARTS   AGE     LABELS
    kubia-manual      1/1     Running   0          79m     <none>
    kubia-manual-v2   1/1     Running   0          3m11s   creation_method=manual,env=prod
    
  • kubectl get pods -L creation_method -L env: 查看所有 pod 并列出指定标签,每个标签自成一列
    NAME              READY   STATUS    RESTARTS   AGE     CREATION_METHOD   ENV
    kubia-manual      1/1     Running   0          84m                       
    kubia-manual-v2   1/1     Running   0          7m35s   manual            prod
    
修改现有 pod 的标签 P68
  • kubectl label pod kubia-manual creation_method=manual: 对 kubia-manual pod 添加 creation_method=manual 标签
  • kubectl label pod kubia-manual-v2 env=debug --overwrite: 将 kubia-manual-v2 pod 的 env=prod 标签更改为 env=debug
    • 更改现有标签时,需要使用 --overwrite 选项
  • kubectl get pods -L env,creation_method: 查看所有 pod 并列出指定标签,每个标签自成一列
    NAME              READY   STATUS    RESTARTS   AGE   ENV     CREATION_METHOD
    kubia-manual      1/1     Running   0          90m           manual
    kubia-manual-v2   1/1     Running   0          13m   debug   manual
    

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

标签选择器允许我们选择标记有特定标签的 pod 子集,并对这些 pod 执行操作,它可以根据资源的以下条件来选择资源: P68

  • 包含(或不包含)使用特定键的标签
  • 包含具有特定键和值的标签
  • 包含具有特定键的标签,但其值与我们指定但不同
使用标签选择器列出 pod P69
  • kubectl get pods -l creation_method=manual: 列出包含 creation_method=manual 标签的所有 pod
  • kubectl get pods -l env: 列出有 env 标签的所有 pod ,无论其值为如何
  • kubectl get pods -l '!env': 列出没有 env 标签的所有 pod
  • kubectl get pods -l creation_method!=manual: 列出有 creation_method 标签但其值不等于 manual 的所有 pod
  • kubectl get pods -l 'env in (debug, prod)': 列出有 env 标签并且其值为 debugprod 的所有 pod
  • kubectl get pods -l 'env notin (debug, prod)': 列出没有 env 标签,或者有 env 标签并且其值不为 debugprod 的所有 pod

使用标签选择器 app=pc 选择前面所述示例中属于 product catalog 微服务的所有 pod 。 P70

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

在标签选择器中使用多个条件 P71
  • kubectl get pods -l creation_method=manual -l env=debug: 列出包含 creation_method=manual 标签和 env=debug 标签的所有 pod
  • kubectl get pods -l 'creation_method=manual, env in (debug, prod)': 列出包含 creation_method=manual 标签,且含有 env 标签并且其值为 debugprod 的所有 pod

使用标签选择器 app=pc,rel=beta 选择前面所述示例中属于 product catalog 微服务的 beta 版本所有 pod 。 P71

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

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

某些情况下,我们希望对将 pod 调度到何处持有一定发言权,例如:硬件基础设施不同质。 P71

  • 某些工作节点使用机械硬盘,其他节点使用固态硬盘。可能想将一些 pod 调度到一组节点,同时将其他 pod 调度到另一组节点
  • 将执行 GPU 密集型运算的 pod 调度到实际提供 GPU 加速到节点上

这种情况下,我们应该用某种方式描述对节点的需求,使 Kubernetes 选择一个符合这些需求的节点,这恰好可以通过节点标签和节点标签选择器完成。 P72

使用标签分类工作节点 P72

向集群添加新节点时,可以通过附加标签来对节点进行分类,这些标签指定节点提供对硬件类型,或者任何调度 pod 时能提供便利对其他信息。 P72

  • kubectl label node minikube-m02 gpu=true: 给节点 minikube-m02 添加 gpu=true 标签(在 02. 开始使用 Kubernetes 和 Docker 中已使用该命令给工作节点打上标签角色标签,使其 ROLES 设置为 worker

  • kubectl get nodes -l gpu=true: 列出包含 gpu=true 标签的所有节点

将 pod 调度到特定节点 P72

基于 kubia-manual-gpu.yaml 创建一个新的描述文件 kubia-manual-gpu.yaml ,并添加 spec.nodeSelector 属性,指定选择的标签为 gpu=true 。这样当我们创建该 pod 时,调度器将只在包含标签 gpu=true 的节点中选择。 P73

# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 资源类型为 Pod
kind: Pod
metadata:
  # pod 的名称
  name: kubia-manual-gpu
  # pod 的标签
  labels:
    creation_method: manual
    env: prod
spec:
  # 节点选择器
  nodeSelector:
    # 选择的标签
    gpu: "true"
  containers:
    # 创建容器所使用的镜像
    - image: idealism/kubia
      # 容器的名称
      name: kubia
      ports:
        # 应用监听的端口
        - containerPort: 8080
          protocol: TCP
调度到一个特定节点 P73

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

注解 pod P73

注解也是键值对,本质上与标签非常相似。 P73

  • 注解并不是为了保存标识信息而存在的,它们不能像标签一样用于对对象进行分组
  • 注解可以容纳更多信息,并且主要用于工具使用
  • Kubernetes 也会将一些注解自动添加到对象,但其他的注解则需要由用户手动添加
  • 向 Kubernetes 引入新特性时,通常会使用注解
    • 一般来说,新功能的 alpha 和 beta 版本不会向 API 对象引入任何新字段,因此使用的是注解而不是字段。一旦所需的 API 更改变得清晰并且得到所有相关人员的认可,就会引入新的字段并废弃相关注解
  • 大量使用注解可以为每个 pod 或其他 API 对象添加说明,以便每个使用该集群的人都可以快速查找有关每个单独对象的信息。例如,指定创建对象的人员姓名的注解可以使在集群中工作的人员之间的协作更加便利
  • 标签应该简短,注解可以包含相对更多的数据(总共不超过 256KB )
查找对象的注解 P74

我们可以通过 kubectl get pod <pod-name> -o yaml 或者 kubectl describe pod <pod-name> 查看 pod 的详细信息,在其中就会看到对应的注解。

添加和修改注解 P75

kubectl annotate pod kubia-manual-gpu idealism/annotation='gpu required': 对 pod kubia-manual-gpu 添加或修改注解 idealism/annotation 的值为 gpu requiredP74

使用前缀格式的注解键来避免键冲突是一个好方法,可以防止其他工具或库意外地覆盖自己的注解。 P75

使用命名空间对资源进行分组 P75

Kubernetes 命名空间简单地为对象名称提供了一个作用域。此时我们并不会将所有资源都放在同一个命名空间中,而是将它们组织到多个命名空间中,这样可以允许我们多次使用相同的资源名称(跨不同的命名空间)。 P75

了解对命名空间的需求 P75

在使用多个命名空间的前提下,我们可以将包含大量组件的复杂系统拆分成更小的不同组,这些不同组也可以用于在多租户环境中分配资源,将资源分配为生产、开发和 QA 环境,或者以其他任何需要的方式分配资源。资源名称只需要在命名空间内保持唯一即可,因此两个不同的命名空间可以包含同名的资源。 P75

大多数类型的资源都与命名空间相关,但仍有一些与它们无关,其中之一便是全局且未被约束于单一命名空间但节点资源。 P75

发现其他命名空间及其 pod P75
  • kubectl get namespaces: 列出集群中的所有命名空间
  • kubectl get pods -n kube-system: 列出 kube-system 命名空间下的所有 pod

命名空间的优点 P76

  • 可以隔离资源,将不属于一组的资源分到不重叠的组中,避免无意中修改或删除其他用户的资源,也无须关心名称冲突
  • 可用于仅允许某些用户访问某些特定的资源,甚至限制单个用户可用的计算资源数量
创建一个命名空间 P76

命名空间是一种和其他资源一样的 Kubernetes 资源,因此可以通过 YAML 文件提交到 Kubernetes API 服务器来创建该资源。 P76

从 YAML 文件创建命名空间 P77

kubectl create -f custom-namespace.yaml 可以通过 YAML 文件创建命名空间。

Kubernetes 中的所有内容都是一个 API 对象,可以通过向 API 服务器提交 YAML 文件来实现创建、读取、更新和删除。 P77

# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 资源类型为 Namespace
kind: Namespace
metadata:
  # 命名空间的名称
  name: custom-namespace

使用 kubectl create namespace 命令创建命名空间 P77

kubectl create namespace <namespace-name>: 创建一个指定名称的命名空间

管理其他命名空间中的对象 P77

kubectl create -n custom-namespace -f kubia-manual.yaml: 在 kubia-manual 命名空间中通过指定的 YAML 文件创建一个资源。

如果不指定命名空间, kubectl 将在当前上下文中配置的默认命名空间中执行操作。而当前上下文的命名空间和当前上下文本身都可以通过 kubectl config 命令进行更改。 P78

命名空间提供的隔离 P78

尽管命名空间将对象分隔到不同更多组,只允许你对属于特定命名空间的对象进行操作,但实际上命名空间之间并不提供对正在运行对对象对任何隔离。 P78

停止和移除 pod P78

按名称删除 pod P78

kubectl delete pod -n <namespace-name> <pod-name-1> [<pod-name-2> ...]: 删除指定命名空间下的指定 pod 。 P79

在删除 pod 的过程中,实际上我们在指示 Kubernetes 终止该 pod 中的所有容器。 Kubernetes 向进程发送一个 SIGTERM 信号并等待一定的秒数(默认为 30 秒),使其正常关闭,如果它没有即使关闭,则通过 SIGKILL 终止该进程。因此,为了确保你的进程总是正常关闭,进程需要正确处理 SIGTERM 信号。 P79

使用标签选择器删除 pod P79

kubectl delete pod -n <namespace-name> -l <label-key>=<label-value>: 删除指定命名空间下含有指定标签的所有 pod 。 P79

在微服务示例中,通过指定 rel=canary 标签选择器,可以一次删除所有金丝雀 pod 。 P79

图 3.10 通过 rel=canary 标签选择器选择并删除所有金丝雀 pod

通过删除整个命名空间来删除 pod P80

kubectl delete namespace custom-namespace: 删除整个 custom-namespace 命名空间( pod 将会随命名空间自动删除)

删除命名空间中的所有 pod ,但保留命名空间 P80

kubectl delete pod -n <namespace-name> --all: 删除指定命名空间中的所有 pod 。 P80

删除命名空间中的(几乎)所有资源 P80

kubectl delete all -n <namespace-name> --all: 删除指定命名空间中的所有资源。 all 指定删除所有资源类型, --all 选项指定删除所有资源实例。 P81

注意:使用 all 关键字并不会真的完全删除所有内容。一些资源会被保留下来,并且需要被明确指定删除。 P81

注意:该命令也会删除名为 kubernetesService ,但它会在几分钟后自动重新创建。 P81

本文首发于公众号:满赋诸机(点击查看原文) 开源在 GitHub :reading-notes/kubernetes-in-action

posted @ 2021-03-14 15:00  满赋诸机  阅读(249)  评论(0编辑  收藏  举报