跳到主要内容

1、Pod基础原理

Pod基础原理

img

目录

[toc]

1、Pod 基本概念

前面的课程中我们了解了 Kubernetes 的基本架构,以及如何使用资源清单在集群中部署一个应用。我们也了解到了 Pod 是 Kubernetes 集群中最基本的调度单元,我们平时在集群中部署的应用都是以 Pod 为单位的,而并不是我们熟知的容器,这样设计的目的是什么呢?为何不直接使用容器呢?

2、为什么需要 Pod

假设 Kubernetes 中调度的基本单元就是容器,对于一个非常简单的应用可以直接被调度直接使用,没有什么问题。但是往往还有很多应用程序是由多个进程组成的,有的同学可能会说把这些进程都打包到一个容器中去不就可以了吗?理论上是可以实现的,但是不要忘记了容器运行时管理的进程是 pid=1 的主进程,其他进程死掉了就会成为僵尸进程,没办法进行管理了,这种方式本身也不是容器推荐的运行方式,一个容器最好只干一件事情(当然,你一个容器里跑多个进程也是可以的,例如用systemd/hypevisor去管理),所以在真实的环境中不会使用这种方式。

那么我们就把这个应用的进程进行拆分,拆分成一个一个的容器总可以了吧?但是不要忘记一个问题,拆分成一个一个的容器后,是不是就有可能出现一个应用下面的某个进程容器被调度到了不同的节点上呀?往往我们应用内部的进程与进程间通信(通过 IPC 或者共享本地文件之类)都是要求在本地进行的,也就是需要在同一个节点上运行。

所以我们需要一个更高级别的结构来将这些容器绑定在一起,并将他们作为一个基本的调度单元进行管理,这样就可以保证这些容器始终在同一个节点上面,这也就是 Pod 设计的初衷。

Pod是Kubernetes创建和管理的最小单元,一个Pod由一个容器或多个容器组成,这些容器共享存储、网络

Pod 是一组紧密关联的容器集合,是 Kubernetes 调度的基本单位,容器进程运行在不同 PID、IPCNetwork 和 UTS namespace,且彼此之间共享网络

Pod 的设计理念是支持多个容器在一个 Pod 中共享网络和文件系统,可以通过进程间通信文件共享这种简单高效的方式组合完成服务。

Pod特点:

  • 一个Pod可以理解为是一个应用实例,提供服务
  • Pod中容器始终部署在一个Node上
  • Pod中容器共享网络、存储资源
  • Kubernetes直接管理Pod,而不是容器;

Pod主要用法:

  • 运行单个容器:最常见的用法,在这种情况下,可以将Pod看做是单个容器的抽象封装

  • 运行多个容器:封装多个紧密耦合且需要共享资源的应用程序;(这种应用场景实际工作中还是比较少的,比较特殊一点的;)

如果有这些需求,你可以运行多个容器:

  • 两个应用之间发生文件交互

  • 两个应用需要通过127.0.0.1或者socket通信

  • 两个应用需要发生频繁的调用

3、Pod 原理

在一个 Pod 下面运行几个关系非常密切的容器进程,这样一来这些进程本身又可以收到容器的管控,又具有几乎一致的运行环境,也就完美解决了上面提到的问题。

其实 Pod 也只是一个逻辑概念,真正起作用的还是 Linux 容器的 Namespace 和 Cgroup 这两个最基本的概念,Pod 被创建出来其实是一组共享了一些资源的容器而已。首先 Pod 里面的所有容器,都是共享的同一个 Network Namespace,但是涉及到文件系统的时候,默认情况下 Pod 里面的容器之间的文件系统是完全隔离的,但是我们可以通过声明来共享同一个 Volume。

img

1.pod中共享网络的实现

共享网络:将业务容器网络加入到“负责网络的容器”实现网络共享。

我们可以指定新创建的容器和一个已经存在的容器共享一个 Network Namespace,在运行容器(docker 容器)的时候只需要指定 --net=container:目标容器名 这个参数就可以了。但是这种模式有一个明显的问题那就是容器的启动有先后顺序问题,那么 Pod 是怎么来处理这个问题的呢?那就是加入一个中间容器(没有什么架构是加一个中间件解决不了的),这个容器叫做 Infra 容器,而且这个容器在 Pod 中永远都是第一个被创建的容器,这样是不是其他容器都加入到这个 Infra 容器就可以了,这样就完全实现了 Pod 中的所有容器都和 Infra 容器共享同一个 Network Namespace 了,如下图所示:

注意: 每个pod一个ip; 一个pod至少有2个容器:一个infra容器,一个业务容器;

imgimg

所以当我们部署完成 Kubernetes 集群的时候,首先需要保证在所有节点上可以拉取到默认的 Infra 镜像,默认情况下 Infra 镜像地址为 registry.k8s.io/pause:3.8,这个容器占用的资源非常少,但是这个镜像默认是需要科学上网的,所以很多时候我们在部署应用的时候一直处于 Pending 状态或者报 sandbox image 相关的错误信息,大部分是因为所有 Pod 最先启动的容器镜像都拉不下来,肯定启动不了,启动不了其他容器肯定也就不能启动了:

[root@master1 ~]#kubelet --help|grep infra
--pod-infra-container-image string Specified image will not be pruned by the image garbage collector. CRI implementations have their own configuration to set this image. (default "registry.k8s.io/pause:3.8") (DEPRECATED: will be removed in 1.27. Image garbage collector will get sandbox image information from CRI.)

从上面图中我们可以看出普通的容器加入到了 Infra 容器的 Network Namespace 中,所以这个 Pod 下面的所有容器就是共享同一个 Network Namespace 了。普通容器不会创建自己的网卡,配置自己的 IP,而是和 Infra 容器共享 IP、端口范围等,而且容器之间的进程可以通过 lo 网卡设备进行通信:

  • 也就是容器之间是可以直接使用 localhost 进行通信的;

  • 看到的网络设备信息都是和 Infra 容器完全一样的;

  • 也就意味着同一个 Pod 下面的容器运行的多个进程不能绑定相同的端口;

  • 而且 Pod 的生命周期只跟 Infra 容器一致,而与容器 A 和 B 无关**。**

💘 实站:pod中共享网络的实现 infra container-2022.12.8(成功测试)

  • 实验环境
实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
k8s version:v1.21
CONTAINER-RUNTIME:docker://20.10.7
  • 实验软件(无)

下面,我们来测试下:

  • 用docker run创建一个nginx容器,并进入到容器里测试nginx服务:
[root@k8s-master1 ~]#docker run -d  --name=web nginx
[root@k8s-master1 ~]#docker ps|grep nginx
8e409843a733 nginx "/docker-entrypoint.…" 10 seconds ago Up 9 seconds 80/tcp web

[root@k8s-master1 ~]#docker exec web curl localhost:80
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 615 100 615 0 0 300k 0 --:--:-- --:--:-- --:--:-- 600k
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
  • 用docker run创建一个busybox测试容器,进入容器,测试是否可访问通nginx机器:
[root@k8s-master ~]#docker run -it busybox sh
[root@k8s-master1 ~]#docker run -it busybox sh
/ #
/ # curl localhost
sh: curl: not found
/ # curl localhost:80
sh: curl: not found
/ # wget localhost:80
Connecting to localhost:80 (127.0.0.1:80)
wget: can't connect to remote host (127.0.0.1): Connection refused
/
  • 因为这2个容器处于不同的网络命名空间,因此是不可以通过127.0.0.1来访问的。

  • 现在,我们来创建一个pod:

[root@k8s-master ~]#vim pod-net.yaml

apiVersion: v1
kind: Pod
metadata:
labels:
app: test
name: pod-net-test
namespace: default
spec:
containers:
- image: busybox
name: test
command: ["/bin/sh","-c","sleep 12h"]
- image: nginx
name: web
  • apply一下,并查看
[root@k8s-master1 ~]#kubectl apply -f pod-net.yaml 
pod/pod-net-test created
[root@k8s-master1 ~]#kubectl get po
NAME READY STATUS RESTARTS AGE
pod-net-test 2/2 Running 0 2m7s
  • 现在,我们以同样的方式进入到刚才创建的pod的2个容器里面:

先进到test pod里,可以看到里面监听了80端口,而buxybox容器是没有80端口服务的。这个是另一个容器监听的端口。

[root@k8s-master1 ~]#kubectl exec -it pod-net-test -c test -- netstat -antlp|grep 80
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:52518 127.0.0.1:80 TIME_WAIT -
tcp 0 0 :::80 :::* LISTEN -

[root@k8s-master1 ~]#kubectl exec -it pod-net-test -c web -- curl localhost:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[root@k8s-master1 ~]#
  • 此时,是否可以在busybox容器了访问127.0.0.1:80服务呢?--是可以访问到的。
[root@k8s-master1 ~]#kubectl exec -it pod-net-test -c test -- wget localhost:80
Connecting to localhost:80 (127.0.0.1:80)
saving to 'index.html'
index.html 100% |********************************| 615 0:00:00 ETA
'index.html' saved
  • 我们怎么可以看到这个infra container容器呢?-->到pod所在节点上用命令docker ps|grep Pod名称来查看即可。
[root@k8s-master1 ~]#kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-net-test 2/2 Running 0 9m20s 10.244.169.135 k8s-node2 <none> <none>
[root@k8s-master1 ~]#ssh root@172.29.9.33
root@172.29.9.33's password:
Last login: Wed Nov 23 07:33:13 2022 from k8s-master1
[root@k8s-node2 ~]#docker ps|grep pod-net-test
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1e74c55b0e5f nginx "/docker-entrypoint.…" 9 minutes ago Up 9 minutes k8s_web_pod-net-test_default_f82b3287-eb30-4bbb-8d7c-b046055cc572_0
306de81a82c7 busybox "/bin/sh -c 'sleep 1" 10 minutes ago Up 10 minutes k8s_test_pod-net-test_default_f82b3287-eb30-4bbb-8d7c-b046055cc572_0
a56dcf0c53ad registry.aliyuncs.com/google_containers/pause:3.2 "/pause" 10 minutes ago Up 10 minutes k8s_POD_pod-net-test_default_f82b3287-eb30-4bbb-8d7c-b046055cc572_0

实验结束。😘

2.pod中共享存储的实现

共享存储:容器通过数据卷(volume)共享数据。

对于文件系统 Kubernetes 是怎么实现让一个 Pod 中的容器共享的呢?

默认情况下容器的文件系统是互相隔离的,要实现共享只需要在 Pod 的顶层声明一个 Volume,然后在需要共享这个 Volume 的容器中声明挂载即可。

img

比如下面的示例:

apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
volumes:
- name: varlog
hostPath:
path: /var/log/counter
containers:
- name: count
image: busybox
args:
- /bin/sh
- -c
- >
i=0;
while true;
do
echo "$i: $(date)" >> /var/log/1.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
mountPath: /var/log
- name: count-log
image: busybox
args: [/bin/sh, -c, 'tail -n+1 -f /opt/log/1.log']
volumeMounts:
- name: varlog
mountPath: /opt/log

img

img

示例中我们在 Pod 的顶层声明了一个名为 varlog 的 Volume,而这个 Volume 的类型是 hostPath,也就意味这个宿主机的 /var/log/counter 目录将被这个 Pod 共享,共享给谁呢?在需要用到这个数据目录的容器上声明挂载即可,也就是通过 volumeMounts 声明挂载的部分,这样我们这个 Pod 就实现了共享容器的 /var/log 目录,而且数据被持久化到了宿主机目录上。

这个方式也是 Kubernetes 中一个非常重要的设计模式:sidecar 模式的常用方式。典型的场景就是容器日志收集,比如上面我们的这个应用,其中应用的日志是被输出到容器的 /var/log 目录上的,这个时候我们可以把 Pod 声明的 Volume 挂载到容器的 /var/log 目录上,然后在这个 Pod 里面同时运行一个 sidecar 容器,他也声明挂载相同的 Volume 到自己容器的 /var/log (或其他)目录上,这样我们这个 sidecar 容器就只需要从 /var/log 目录下面不断消费日志发送到 Elasticsearch 中存储起来就完成了最基本的应用日志的基本收集工作了。

除了这个应用场景之外使用更多的还是利用 Pod 中的所有容器共享同一个 Network Namespace 这个特性,这样我们就可以把 Pod 网络相关的配置和管理也可以交给一个 sidecar 容器来完成,完全不需要去干涉用户容器,这个特性在现在非常火热的 Service Mesh(服务网格)中应用非常广泛,典型的应用就是 Istio,不过也不用着急,后面也会和大家一起探讨的。

💘 实验:pod中共享存储的实现:存储卷测试-2022.12.8(成功测试)

  • 实验环境
实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
k8s version:v1.21
CONTAINER-RUNTIME:docker://20.10.7
  • 实验软件(无)

  • 问题:在一台主机上创建的2个容器,他们之家的网络和文件系统能互相访问的吗?-->不能,因为做了NS(资源隔离)。

  • 那么pod该如何打破这种资源限制呢?

通过数据卷方式可以打破这种资源限制;

img

我们来测试下

  • 创建pod

[root@k8s-master ~]#vim pod.yaml

apiVersion: v1
kind: Pod
metadata:
labels:
app: test
name: pod-volume-test
namespace: default
spec:
containers:
- image: busybox
name: test
command: ["/bin/sh","-c","sleep 12h"]
volumeMounts: # 数据卷挂载
- name: log # 指定挂载的数据卷名称
mountPath: /data # 数据卷挂载到容器中的路径
- image: nginx
name: web
volumeMounts:
- name: log
mountPath: /usr/share/nginx/html
volumes: # 定义数据卷
- name: log # 数据卷名称
emptyDir: {} # 数据卷类型
  • apply一下pod.yaml
[root@k8s-master1 ~]#kubectl apply -f pod.yaml 
pod/pod-volume-test created
[root@k8s-master1 ~]#kubectl get po
NAME READY STATUS RESTARTS AGE
pod-volume-test 2/2 Running 0 16s
  • 我们来进到容器busybox里面:查看/data下是空的
[root@k8s-master1 ~]#kubectl exec -it  pod-volume-test -c test -- ls /data
  • 退出后,我们再次进到nginx容器里:查看/usr/share/nginx/html下是空的
[root@k8s-master1 ~]#kubectl exec -it  pod-volume-test -c web -- ls /usr/share/nginx/html

此时,2个容器的相应目录都是空的:

  • 我们在nginx容器里,新创建一个网站indexl.html文件:
[root@k8s-master1 ~]#kubectl exec -it  pod-volume-test -c web -- sh
# cd /usr/share/nginx/html
# ls
#
# echo hello > index.html
#
  • 退出容器后,我们进到test容器里,查看/data目录下是否会出现刚才创建的文件:-->会出现的。
[root@k8s-master1 ~]#kubectl exec -it  pod-volume-test -c test -- ls /data
index.html
[root@k8s-master1 ~]#kubectl exec -it pod-volume-test -c test -- cat /data/index.html
hello
  • 此时,我再在里面修改下这个index.html文件,再到web容器里观察下效果:发现文件内容被修改成了666;
[root@k8s-master1 ~]#kubectl exec -it  pod-volume-test -c test -- sh
/ # echo 666 > /data/index.html
/ # exit
[root@k8s-master1 ~]#kubectl exec -it pod-volume-test -c web -- curl localhost:80
666

因此,通过这种,"数据卷共享数据",的方式来达到了共享存储的目的;

实验结束。😘

4、如何划分 Pod

上面我们介绍了 Pod 的实现原理,了解到了应该把关系紧密的容器划分到同一个 Pod 中运行,那么怎么来区分“关系紧密”呢?举一个简单的示例,比如我们的 Wordpress 应用,是一个典型的前端服务器和后端数据服务的应用(php(apache)+mysql),那么你认为应该使用一个 Pod 还是两个 Pod 呢?

如果在同一个 Pod 中同时运行服务器程序和后端的数据库服务这两个容器,理论上肯定是可行的,但是不推荐这样使用。我们知道一个 Pod 中的所有容器都是同一个整体进行调度的,但是对于我们这个应用 Wordpress 和 MySQL 数据库一定需要运行在一起吗?当然不需要,我们甚至可以将 MySQL 部署到集群之外对吧?所以 Wordpress 和 MySQL 即使不运行在同一个节点上也是可行的,只要能够访问到即可。

img

但是如果你非要强行部署到同一个 Pod 中呢?从某个角度来说是错误的,比如现在我们的应用访问量非常大,一个 Pod 已经满足不了我们的需求了,怎么办呢?扩容对吧,但是扩容的目标也是 Pod,并不是容器,比如我们再添加一个 Pod,这个时候我们就有两个 Wordpress 的应用和两个 MySQL 数据库了,而且这两个 Pod 之间的数据是互相独立的,因为 MySQL 数据库并不是简单的增加副本就可以共享数据了,所以这个时候就得分开部署了,采用第二种方案,这个时候我们只需要单独扩容 Wordpress 的这个 Pod,后端的 MySQL 数据库并不会受到扩容的影响。

img

将多个容器部署到同一个 Pod 中的最主要参考就是应用可能由一个主进程和一个或多个的辅助进程组成,比如上面我们的日志收集的 Pod,需要其他的 sidecar 容器来支持日志的采集。所以当我们判断是否需要在 Pod 中使用多个容器的时候,我们可以按照如下的几个方式来判断:

  • 这些容器是否一定需要一起运行,是否可以运行在不同的节点上

  • 这些容器是一个整体还是独立的组件

  • 这些容器一起进行扩缩容会影响应用吗

基本上我们能够回答上面的几个问题就能够判断是否需要在 Pod 中运行多个容器了。

Pod的设计: 其实在我们理解 Pod 的时候,有一个比较好的类比的方式就是把 Pod 看成我们之前的 “虚拟机”,而容器就是虚拟机中运行的一个用户程序,这样就可以很好的来理解 Pod 的设计。

关于我

我的博客主旨:

  • 排版美观,语言精炼;
  • 文档即手册,步骤明细,拒绝埋坑,提供源码;
  • 本人实战文档都是亲测成功的,各位小伙伴在实际操作过程中如有什么疑问,可随时联系本人帮您解决问题,让我们一起进步!

🍀 微信二维码

x2675263825 (舍得), qq:2675263825。

img

🍀 微信公众号

《云原生架构师实战》

img

🍀 语雀

https://www.yuque.com/xyy-onlyone

https://www.yuque.com/xyy-onlyone/exkgza?# 《语雀博客》

img

🍀 博客

www.onlyyou520.com

img

img

🍀 csdn

https://blog.csdn.net/weixin_39246554?spm=1010.2135.3001.5421

img

🍀 知乎

https://www.zhihu.com/people/foryouone

img

最后

好了,关于本次就到这里了,感谢大家阅读,最后祝大家生活快乐,每天都过的有意义哦,我们下期见!

img

1