此‘容器’非彼容器

很多人可能已经知道了这一点:其实并不存在真正的Linux“container”,在Linux中没有所谓的“容器”。 众所周知,容器是使用Linux内核的两个特性(称为namespaces和cgroups)运行的常规进程。 namespace可以为进程提供“视图”用来隐藏namespace之外的所有内容,从而为自己提供一个独立的运行环境,在其中运行的进程将无法看到或干扰其他进程。

命名空间包含以下这些:
• Hostname
• Process IDs
• File System
• Network interfaces
• Inter-Process Communication (IPC)

虽然我在上面说过,在名称空间中运行的进程不会干扰到其他进程,但是一个进程理论上可以使用其所在的物理计算机上的所有资源,从而使其他进程无法使用资源。 为了限制这一点,Linux推出了称为cgroups的特性。 进程可以像命名空间一样在cgroup中运行,但是cgroup限制了该进程可以使用的资源。 这些资源包括CPU,RAM,块设备I/O,网络I/O等,CPU通常受毫核(内核的千分之一)限制,而内存则受RAM字节数限制。 该进程本身可以正常运行,但是将只能使用cgroup限制范围内的最大CPU,并且如果进程超出cgroup上设置的内存限制,则会出现内存不足错误。

关于namespace和cgroups可以参考之前的文章 什么是容器:namespces和cgroups

我想在这里指出的是cgroups和每个namespce类型都是独立的特性, 上面列出的命名空间的某些子集可以使用或根本不使用,你只能使用cgroup或两者的某种其他组合(好吧,你仍在使用名称空间和cgroup,但仅使用根目录cgroup)。命名空间和cgroup也可用于进程组(一个命名空间中运行的多个进程),这样的话,进程之间就可以看到彼此并进行交互,或者在单个cgroup中运行它们,这样这些进程将一起被限制在特定数量的CPU和RAM中。

组合的组合

当在Docker上以通常方式运行容器时,Docker会为每个容器创建namespace和cgroup并让它们一一映射, 这就是开发人员对容器的通常看法。
image.png
容器本质上是独立的孤岛,但它们可能具有映射到宿主机的存储卷或端口,以便它们可以进行通信。但是,通过一些额外的命令行参数,你可以使用单个namespace组合Docker容器。 首先,我们创建一个nginx容器。

$ cat <<EOF >> nginx.conf
> error_log stderr;
> events { worker_connections  1024; }
> http {
>     access_log /dev/stdout combined;
>     server {
>         listen 80 default_server;
>         server_name example.com www.example.com;
>         location / {
>             proxy_pass http://127.0.0.1:2368;
>         }
>     }
> }
> EOF
$ docker run -d --name nginx -v `pwd`/nginx.conf:/etc/nginx/nginx.conf -p 8080:80 nginx

接着,在其中运行ghost容器,添加额外的参数以使其加入到nginx容器的命名空间之中。

$ docker run -d --name ghost --net=container:nginx --ipc=container:nginx --pid=container:nginx ghost

现在,nginx容器可以将本地主机上的请求直接代理到我们的ghost容器,访问http://localhost:8080/,能够... 这些命令创建一组在同一命名空间中运行的容器,在此命名空间中运行的Docker容器可以发现彼此并进行通信。
image.png

Pod是某种容器吗?

现在看到可以将namespace和cgroup与多个进程结合使用,而这,正是Kubernetes Pods的本质。 Pod允许指定要运行的容器,Kubernetes会以正确的方式自动设置namespace和cgroup,但是比上述方式要复杂得多,因为Kubernetes不使用Docker网络(它使用CNI)
image.png
一旦以这种方式设置了容器,每个进程就会“感觉”在同一台机器上运行一样。 它们可以在localhost上互相通信,可以使用共享卷,甚至可以使用IPC或互相发送信号,例如HUP或TERM(在Kubernetes 1.7中具有共享的PID名称空间,Docker> = 1.13)。

假设现在要运行nginx并使用confd,confd会在每次添加/删除应用程序服务器时更新nginx配置并重新启动nginx。 假设有一个etcd服务器,其中包含后端应用程序服务器的IP地址,当列表更改时,confd可以得到通知并写出新的nginx配置,并向Nginx发送HUP信号,从而使得nginx能够重新加载配置。
image.png
仅使用Docker时的方式是将nginx和confd都放在一个容器中,因为Docker只有一个入口点,所以需要使两个进程都运行在诸如supervisor之类的条件下,这是不太理想的,因为需要使用supervisord运行每个nginx副本。 更重要的是,Docker仅“了解”supervisord,因为这是entrypoint,它不了解每个进程,这意味着其他工具无法通过Docker API获取该信息,Nginx可能会崩溃,但是Docker并不知道。
image.png
Kubernetes借助Pod来管理每个进程,从而洞悉每个进程的状态,这样,它可以通过API向用户提供相关状态信息,当服务崩溃时还可以提供重启或自动记录日志等服务。
image.png
Pod是伪装成API的容器
通过以这种方式将容器组合到Pod中,我们实质上可以创建其他人可以使用的“ API”添加到Pods中的容器。 从正常的Web API角度来看,这不是API,而是其他Pod可以使用的抽象API。

以上面的nginx + confd为例,confd对nginx进程一无所知,它所知道的仅仅只是监视etcd中的值并将HUP信号发送到进程或运行命令,它运行的应用程序不应该是nginx,而可以是任意一种应用程序,这样就可以使用confd容器镜像和配置,并将其与任意数量不同类型的Pod替换。 可以在其中执行操作的pod通常称为“sidecar(边车)容器”,可以想象一下摩托车上边车的图像。

你还可以想象其他类型的抽象,诸如istio之类的服务网格可以作为边车固定,无需更改主应用程序即可提供服务路由,遥测和策略执行。也可以使用多个sidecar,没有什么可以阻止你同时使用confd sidecar和istio sidecar。通过这种方式组合的应用程序,可以构建更加复杂和可靠的系统,同时使单个app保持简洁。


EngineerLeo
598 声望38 粉丝

专注于云原生、AI等相关技术