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中容器的部分,但是它们的主要区别在于:
-
生命周期不同:initContainers是在Pod中所有容器之前启动的,并且只有在initContainers完成后才会启动其他容器。而Containers则是同时启动的。
-
用途不同:initContainers主要用于在启动Pod之前完成一些初始化操作,例如配置环境变量、检查依赖等。而Containers则是用于运行应用程序或服务。
-
状态不同: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。