前言
刚接触k8s时,公司运维同事分享k8s相关技术,记忆最清晰的一句话就是Pod是k8s调度的最小单位,然后巴拉巴拉说了一大堆专业术语,可能他觉得大家能听懂吧,我嘴上说着“嗯嗯嗯”,心里却想着这是啥玩意啊, 完全不知道运维同事在讲啥,这也促使我翻阅k8s相关书籍,最起码搞清专业术语,不至于和技术人员出现"交流障碍"。好了,不废话了,进入正题,深入解析Pod。
为啥先理解Pod
为啥笔者先从Pod开始了解呢?一个是在运维同事分享时提到“Pod是k8s调度的最小单位”,另一个是,在k8s中所有的资源对象 几乎都是为Pod服务的。Service用来控制Pod网络,Deployment来维护Pod的运行状态,服务账户(ServiceAccount)和基于 角色的权限控制(RBAC)管理Pod的运行权限,可以看到都跟Pod有关,Pod是唯一正在容纳并运行容器的载体,所以先从Pod开始理解。
Pod架构
网络协作
在接触容器时,我们都知道,在容器里奉行“一个容器只运行一个进程”,而实际情况中许多服务很复杂,需要多个进程互相配合完成,这也就意味着 需要多个容器相互配合。Kubernetes巧妙的解决了这个问题,他为这些容器提供了场所,就是Pod对象。
Pod是Kubernetes调度的最小单位,而不是容器,Pod由多个容器共同组成,这些容器之间共享一定限度的资源配置以便方便写作。
Kubernetes在每个Pod中默认启动一个sandbox容器,这个容器的主要作用是提供基础环境。我们来实际验证一下:
nginx-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.8
ports:
- containerPort: 80
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: nginx-vol
- name: tomcat
image: tomcat
ports:
- containerPort: 90
volumes:
- name: nginx-vol
hostPath:
path: "/opt"
接下来,运行如下命令:
kubectl apply -f nginx-deploy.yaml
成功之后,可以看到pod如下:
kubectl get po -n nginx -l app=nginx -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-77788b9c8c-5fllr 2/2 Running 0 45m 10.244.2.55 k8s-node02 <none> <none>
nginx-deployment-77788b9c8c-bk7zw 2/2 Running 0 44m 10.244.2.56 k8s-node02 <none> <none>
看到pod运行在节点k8s-node02 ,登录该节点机器,执行如下命令:
docker ps | grep pause
执行结果如下:
415529623c1a registry.aliyuncs.com/google_containers/pause:3.4.1 "/pause" 51 minutes ago Up 51 minutes k8s_POD_nginx-deployment-77788b9c8c-bk7zw_nginx_8cea04b5-97bc-4044-b73c-b3c182b64eb7_0
4f2919dfe22b registry.aliyuncs.com/google_containers/pause:3.4.1 "/pause" 52 minutes ago Up 52 minutes k8s_POD_nginx-deployment-77788b9c8c-5fllr_nginx_d632d6e7-812d-4746-8656-6d0c98997842_0
从输出结果就可以看到,每一个pod都运行了一个pause容器,这就是sandbox容器。
对于同一个Pod中的容器来说:
-
可以通过localhost互相访问
-
看到的网络设备都是sandbox容器的网络设备
-
一个pod只有一个IP地址,这也就是sandbox容器的IP地址,也就是这个pod的Network Namespace对应的IP地址
-
一个pod中的所有网络资源都是被容器共享的
-
pod的声明周期只跟sandbox容器一致,与其他容器无关
我们可以用下图来简单表示pod的内部结构:
pod中所有容器的网络流量都是经过Sandbox容器的。这一点很重要,我们要针对网络命名空间来思考问题,而不是针对 某个容器的网络进行思考。如果你要为 Kubernetes 开发一个网络插件时,应该重点考虑的是 如何配置这个 Pod 的 Network Namespace,而不是每一个用户容器如何使用你的网络配置,这是 没有意义的。
文件协作
一个 Volume 对应的宿主机目录对于 Pod 来说就只有一个,Pod 里的容器只要声明挂载这 个 Volume,就一定可以共享这个 Volume 对应的宿主机目录。
经典案例:WAR包与Web服务器,我们现在有一个 Java Web 应用的 WAR 包,它需要被放在 Tomcat 的 webapps 目录下运行起来。
现在我有一个hello.war,就是输出Hello World! ,现在将其构建为一个镜像,如下: Dockerfile
FROM scratch
# 将 WAR 包添加到镜像中
COPY hello.war /hello.war
使用 scratch 作为基础镜像意味着镜像将不包含任何操作系统或环境,它将只包含您添加的文件。 然后执行如下命令:
docker build -t helloworld .
这样就构建成功了。这个镜像很简单,只有一个 WAR 包(hello.war)放在根目录下.
好接下来,我们来写配置文件:
apiVersion: apps/v1
kind: Deployment
metadata:
name: helloworld-deployment
namespace: hello
spec:
selector:
matchLabels:
app: hello
replicas: 1
template:
metadata:
labels:
app: hello
spec:
initContainers:
- image: registry.cn-beijing.aliyuncs.com/haohb/common:helloworld1.0
name: war
command: ["cp", "/FingerAppPublish.war", "/app"]
volumeMounts:
- mountPath: /app
name: app-volume
containers:
- name: tomcat
image: tomcat:9.0.22
command: ["/usr/local/tomcat/bin/catalina.sh", "run"]
ports:
- containerPort: 8080
hostPort: 8001
volumeMounts:
- mountPath: "/usr/local/tomcat/webapps"
name: app-volume
volumes:
- name: app-volume
emptyDir: {}
我们定义了两个容器,第一个容器使用的镜像是 registry.cn-beijing.aliyuncs.com/haohb/common:helloworld1.0,这个镜像 里只有一个 WAR 包(FingerAppPublish.war)放在根目录下。
而第二个容器则使用的是一个标准的 Tomcat 镜像。 不过,你可能已经注意到,WAR 包容器的类型不再是一个普通容器,而是一个 Init Container 类型 的容器。
在 Pod 中,所有 Init Container 定义的容器,都会比 spec.containers 定义的用户容器先启动。并且,Init Container 容器会按顺序逐一启动,而直到它们都启动并且退出了,用户容器才会启动。
所以,这个 Init Container 类型的 WAR 包容器启动后,我执行了一句 "cp /FingerAppPublish.war /app", 把应用的 WAR 包拷贝到 /app 目录下,然后退出。
而后这个 /app 目录,就挂载了一个名叫 app-volume 的 Volume。
Tomcat 容器,同样声明了挂载 app-volume 到自己的 webapps 目录下。 所以,等 Tomcat 容器启动时,它的 webapps 目录下就一定会存在 FingerAppPublish.war 文件:这个文件 正是 WAR 包容器启动时拷贝到这个 Volume 里面的,而这个 Volume 是被这两个容器共享的。
好了,我们执行如下命令进行创建:
kubectl apply -f hello-deploy.yaml
执行后,pod创建成功,我们可以通过pod的IP进行访问测试:
curl 10.244.2.71:8080/FingerAppPublish/
输出结果如下:
<html>
<body>
<h2>Hello World!</h2>
</body>
</html>
这就证明成功了,我们也可以进入这个容器查看,
kubectl exec -it -n hello helloworld-deployment-6b597758fd-nb9rt -- /bin/bash
然后,查看/usr/local/tomcat/webapps目录,
drwxr-x--- 6 root root 84 Jul 16 02:32 FingerAppPublish
-rw-r--r-- 1 root root 22158840 Jul 16 02:31 FingerAppPublish.war
可以看到确实有这个war包,在容器内,我们也可以通过localhost来访问:
curl localhost:8080/FingerAppPublish/
输出结果和上面是一样的。
sidecar
上面这个案例中,用一种“组合”方式,解决了 WAR 包与 Tomcat 容器之间耦合关系的问题。
实际上,这个所谓的“组合”操作,正是容器设计模式里最常用的一种模式,它的名字叫: sidecar。
sidecar 指的就是我们可以在一个 Pod 中,启动一个辅助容器,来完成一些独立于主进程(主容器)之外的工作。
比如,在我们的这个应用 Pod 中,Tomcat 容器是我们要使用的主容器,而 WAR 包容器的存在, 只是为了给它提供一个 WAR 包而已。所以,我们用 Init Container 的方式优先运行 WAR 包容 器,扮演了一个 sidecar 的角色。
最后
在这篇文章中,深入浅出地介绍了Pod的架构,不得不说,初次接触时可能会觉得有些迷糊!但请放心!只要用心去研究和实践,坚持几天后,肯定能对Pod有更深层次的理解。期待未来学习更多关于Pod的调度和资源管理知识!