k8s 中 pod 是如何做到网络共享的
前言
在k8s中, pod是编排的最小单位, 在同一个pod中, 容器之间能够共享hostname
network
等内容.
共享network
, 简单说就是同一个pod中的容器, 可以通过访问localhost
互相访问, 且端口占用会冲突.
在之前的介绍中提到过, 容器的隔离是通过namespace
技术实现的, 网络隔离自然也是通过Network Namespace
实现. 每个network namespace
中都有自己的一套网络资源, 比如: IP地址, 路由表, 网卡等.
那么网络共享的原理, 自然也就是将多个容器加入到同一个network namespace
中咯.
令多个容器共用一套network namespace
, 在docker
中可以这样做:
# 方式一: 创建一个network, 然后所有容器都使用这个网络
docker network create mynetwork
docker run --name container1 --network=mynetwork nginx
docker run --name container2 --network=mynetwork nginx
# 方式二: 启动一个容器, 然后将新的容器加入到已有容器的网络中
docker run --name container1 nginx
docker run --name container2 --network container:container1 nginx
而k8s则是容器的管理者, 它又是怎么选择的呢?
k8s 的网络共享
在k8s
中, 选择了第二种方式来共享网络, 不止是网络, 包括volume
也是这样. 这样设计可能是为了更大的灵活性吧. 具体原因没有细究.
但是, 如果说我们在启动容器的时候, 要将其加入到已有容器的网络中, pod中的容器就必须有一个是先启动的, 这样后续的容器才能加入. 那么问题来了, pod中哪个容器能够最先启动呢? 难道我们在定义pod时还需要定义容器的启动顺序吗? 显然不是这样的.
那么k8s
是如何解决容器启动顺序的问题呢? 处理方式也十分简单粗暴, 在所有容器启动之前, 先启动一个默认的容器, 后续所有容器就可以都加入此容器的命名空间中了. 这个预先启动的容器什么都不做, 只是为了后续容器加入.
pause容器查看
口说无凭, 我们启动一个pod来看一下:
apiVersion: v1
kind: Pod
metadata:
name: test
namespace: hj
spec:
shareProcessNamespace: true
restartPolicy: Never
containers:
- name: nginx
image: nginx
- name: busybox
image: busybox
command: ["/bin/sh"]
args: ["-c", "sleep infinity"]
此时查看运行中的容器列表, 那么你就能够看到pause
容器:
另外, 因为我们开启了shareProcessNamespace
共享进程空间, 因此我们可以进入容器查看当前运行的所有进程:
也可以清楚的看到pause
进程, 与当前容器在同一个进程命名空间中.
(另外, 这里可以看到我们exec
进入的sh
进程, 其父进程PID是0, 与容器挂载目录中说的也对上了嘛. exec的原理是将进程强行加入已有的命名空间中)
pause容器
pause
需要具有如下特点:
- 体积小 (方便快速拉取镜像)
- 不占用资源(否则每个pod启动一个, 用户资源都被吃光了)
- 稳定 (一旦挂了, 接入此容器命名空间的关联容器都会被波及, 所以此容器不能挂)
那么, pause
到底做了什么呢? 在github可以看到镜像构建的源码. 其主要运行的源码在这里.
简单说, pause
容器什么也没干. 进程在启动后, 不停的调用pause
函数进入睡眠, 从而使得容器能够持续运行而又不消耗系统资源. 同时越是简单的就越是稳定. 而这个容器的大小也不过742KB.
至此, pod网络共享的谜团算是大致解开了. 虽然没有特别深入, 但对于理解pod也有一定的帮助.
最后再提一嘴, 通过 pause 容器来共享网络的方式, 是在CRI层实现的, 也就是不同的容器实现者可能对应不同的实现, 启动 pause 容器的方式是 docker 的方式. 自然还有其他的实现方式, 比如kata containers会直接创建出一个轻量级虚拟机来充当 Pod