k8s 深入篇———— k8s 的pod[五]

前言

简单整理一下pod的相关知识。

正文

为什么我们需要pod。

前面我们知道了k8s一个最重要的作用是解决容器的编排功能,那么为什么有一个pod的东西。

这就是实际中遇到的问题。

那就是容器和容器之间,那就是如何表达容器和容器之间的关系。

就是有些场景下,容器与容器之间是存在关系的。

如果把k8s 比作操作系统,容器比作进程,那么进程组就是pod。

之所以有这个pod,就是因为一些容器他们之间需要在公共的namespace、cgroup 下面运行。

也可以理解他们原本就应该在一台虚拟机下面执行。

像这样容器间的紧密协作,我们可以称为“超亲密关系”。

这些具有“超亲密关系”容器的典型特征包括但不限于:互相之间会发生直接的文件交换、使用 localhost 或者 Socket 文件进行本地通信、

会发生非常频繁的远程调用、需要共享某些 Linux Namespace(比如,一个容器要加入另一个容器的 Network Namespace)等等。

不过,Pod 在 Kubernetes 项目里还有更重要的意义,那就是:容器设计模式。

首先,关于 Pod 最重要的一个事实是:它只是一个逻辑概念。

也就是说,Kubernetes 真正处理的,还是宿主机操作系统上 Linux 容器的 Namespace
和 Cgroups,而并不存在一个所谓的 Pod 的边界或者隔离环境。
那么,Pod 又是怎么被“创建”出来的呢?
答案是:Pod,其实是一组共享了某些资源的容器。

具体的说:Pod 里的所有容器,共享的是同一个 Network Namespace,并且可以声明
共享同一个 Volume。

所以,在 Kubernetes 项目里,Pod 的实现需要使用一个中间容器,这个容器叫作 Infra 容器。

在这个 Pod 中,Infra 容器永远都是第一个被创建的容器,而其他用户定义的容器,则通过 Join Network Namespace 的方式,与 Infra 容器关联在一起。

这样的组织关系,可以用下面这样一个示意图来表达:

这个 Pod 里有两个用户容器 A 和 B,还有一个 Infra 容器。很容易理解,在Kubernetes 项目里,Infra 容器一定要占用极少的资源,所以它使用的是一个非常特殊的镜像,叫作:k8s.gcr.io/pause。

这个镜像是一个用汇编语言编写的、永远处于“暂停”状态的容器,解压后的大小也只有 100~200 KB 左右。

而在 Infra 容器“Hold 住”Network Namespace 后,用户容器就可以加入到 Infra 容器的 Network Namespace 当中了。

所以,如果你查看这些容器在宿主机上的 Namespace文件(这个 Namespace 文件的路径,我已经在前面的内容中介绍过),它们指向的值一定是完全一样的。

当然,其他的所有网络资源,都是一个 Pod 一份,并且被该 Pod 中的所有容器共享;Pod 的生命周期只跟 Infra 容器一致,而与容器 A 和 B 无关。

比如:

apiVersion: "v1"
kind: "Pod"
metadata:
  name: two-contrains
  namespace: name01
spec:
  restartPolicy: "Always"
  volumes:
  - name: shared-data
    hostPath:
      path: /data
  containers:
  - name: nginx-controller
    image: nginx
    volumeMounts:
    - name: shared-data
      mountPath: /usr/share/nginx/html
  - name: debian-container
    image: debian
    volumeMounts:
    - name: shared-data
      mountPath: /pod-data
    command: ["/bin/sh"]
    args: ["-c","echo Hello from the debian container ! > /pod-data/index.html"]

pod 声明了一个volume,然后容器nginx-controller、debian-container 使用。

来看下怎么实现的。

来看下docker 现象。

对于volumeMounts 而言,其实就是绑定/pod-data 到 /data 中。

对于网络而言:

其实就是pod 内的几个容器共享网络。

对上面而言用的是infra的网络。

再举一个例子, 介绍一个initContainers:

k8s中的initContainers和Containers都是用于定义Pod中容器的部分,但是它们的主要区别在于:

  1. 生命周期不同:initContainers是在Pod中所有容器之前启动的,并且只有在initContainers完成后才会启动其他容器。而Containers则是同时启动的。

  2. 用途不同:initContainers主要用于在启动Pod之前完成一些初始化操作,例如配置环境变量、检查依赖等。而Containers则是用于运行应用程序或服务。

  3. 状态不同:initContainers完成后会退出,而Containers会继续运行。

总之,initContainers主要用于在Pod启动时完成一些初始化工作,而Containers则是用于运行应用程序或服务。

是的,initContainers是按照它们在Pod中的顺序依次运行的,每个initContainer必须在前一个initContainer完成后才能开始运行。只有所有的initContainers都成功完成后,Pod中的其他容器才会启动。

在定义initContainers时,可以使用spec.initContainers字段来指定它们的顺序。例如,下面的示例定义了两个initContainers,分别用于执行初始化操作:

spec:
  initContainers:
  - name: init-container-1
    image: busybox
    command: ['sh', '-c', 'echo "init container 1"']
  - name: init-container-2
    image: busybox
    command: ['sh', '-c', 'echo "init container 2"']
  containers:
  - name: my-app
    image: my-image
    command: ['sh', '-c', 'echo "my app"']

在这个示例中,init-container-1将在init-container-2之前执行。当Pod启动时,先运行init-container-1,然后等待它完成后再运行init-container-2,最后才启动my-app容器。

那么举一个实际中用到的例子,现在这个例子没什么用了,因为现在java 打包就集成了tomcat,而不是在外面包裹一层:

apiVersion: v1
kind: Pod
metadata:
  name: javaweb-2
spec:
  initContainers:
  - image: geektime/sample:v2
    name: war
    command: ["cp", "/sample.war", "/app"]
    volumeMounts:
    - mountPath: /app
      name: app-volume
  containers:
  - image: geektime/tomcat:7.0
    name: tomcat
    command: ["sh","-c","/root/apache-tomcat-7.0.42-v2/bin/start.sh"]
    volumeMounts:
    - mountPath: /root/apache-tomcat-7.0.42-v2/webapps
      name: app-volume
    ports:
    - containerPort: 8080
      hostPort: 8001
  volumes:
  - name: app-volume
    emptyDir: {}

上面大体意思是将sample.war拷贝到app下面,然后将挂载出来,然后geektime/tomcat:7.0 就可以使用这个war 包了。

实际上,这个所谓的“组合”操作,正是容器设计模式里最常用的一种模式,它的名字叫:sidecar

顾名思义,sidecar 指的就是我们可以在一个 Pod 中,启动一个辅助容器,来完成一些独立于主进程(主容器)之外的工作。

比如,在我们的这个应用 Pod 中,Tomcat 容器是我们要使用的主容器,而 WAR 包容器的存在,只是为了给它提供一个 WAR 包而已。

所以,我们用 Init Container 的方式优先运行 WAR 包容器,扮演了一个 sidecar 的角色。

这样做就有用给好处,就是每次都更新的是war包,而不需要关注tomcat运行环境,减少包的大小。

比如,我现在有一个应用,需要不断地把日志文件输出到容器的 /var/log 目录中。

这时,我就可以把一个 Pod 里的 Volume 挂载到应用容器的 /var/log 目录上。

然后,我在这个 Pod 里同时运行一个 sidecar 容器,它也声明挂载同一个 Volume 到自己的 /var/log 目录上。

这样,接下来 sidecar 容器就只需要做一件事儿,那就是不断地从自己的 /var/log 目录里读取日志文件,转发到 MongoDB 或者 Elasticsearch 中存储起来。这样,一个最基本的日志收集工作就完成了。

跟第一个例子一样,这个例子中的 sidecar 的主要工作也是使用共享的 Volume 来完成对文件的操作。

但不要忘记,Pod 的另一个重要特性是,它的所有容器都共享同一个 NetworkNamespace。

这就使得很多与 Pod 网络相关的配置和管理,也都可以交给 sidecar 完成,而完全无须干涉用户容器。

这里最典型的例子莫过于 Istio 这个微服务治理项目了。

Istio 项目使用 sidecar 容器完成微服务治理的原理,我在后面很快会讲解到。

下面一点需要区分:

在 Kubernetes 中,Infra Container 和 Init Container 都是容器,但它们有不同的用途和生命周期。

Infra Container 是一个在 Pod 中运行的辅助容器,用于提供一些共享资源或服务,例如网络命名空间、存储卷、日志收集、监控等。Infra Container 在 Pod 启动后一直运行直到 Pod 终止。

Init Container 是一种特殊类型的容器,它是在 Pod 中其他容器启动之前运行的,用于初始化或准备一些资源。例如,可以使用 Init Container 下载应用程序代码、初始化数据库、生成配置文件等。Init Container 在它的工作完成后立即退出,然后 Pod 中的其他容器才开始启动。

因此,Infra Container 和 Init Container 的主要区别在于它们的用途和生命周期。Infra Container 是一个持久的辅助容器,为 Pod 提供一些共享资源或服务;而 Init Container 是一个短暂的容器,用于在其他容器启动之前初始化或准备一些资源。

然后:

如果在 Kubernetes 中未定义 Infra Container,则 Kubernetes 会自动添加一个名为 `pause` 的 Infra Container。这是一个非常轻量级的容器,它的作用是为 Pod 中的其他容器创建 Linux 命名空间和网络 namespace,并为网络 namespace 分配 IP 地址。

在 Pod 中自动添加的 `pause` 容器是一个 Infra Container,它会在其他容器之前启动,并在其他容器退出之后保持运行状态。因此,即使在 Pod 定义文件中未定义 Infra Container,Kubernetes 仍然会确保 Infra Container 在 Pod 启动时运行。

需要注意的是,如果在 Pod 定义文件中显式定义 Infra Container,则 Kubernetes 不会自动添加 `pause` 容器。在这种情况下,Infra Container 的定义顺序决定了它的启动顺序。

一般情况下,我们是不填这个Infra Container。

posted @ 2023-06-24 16:03  敖毛毛  阅读(149)  评论(0编辑  收藏  举报