如上图所示,一个宿主机运行N个容器,多容器带来的以下问题如何解决?
Docker使用Linux Namespaces技术来隔离不同容器运行环境(文件系统、IP地址、端口、主机名、用户等),在docker环境中,namespace由宿主机的kernel实现。
补充:在kubernetes中使用namespace实现业务的容器隔离,如将A业务的容器放置到A业务的namespace,B业务的容器放置到B业务的namespace(以业务命名namespace)。
Namespace是Linux系统的底层概念,在内核层实现,即有一些不同类型的命名空间被部署在核内,各个docker容器运行在同一个docker主进程并且共用同一个宿主机kernel,各docker容器运行在宿主机的用户空间,每个容器都要有类似虚拟机一样的相互隔离的运行空间,但是容器技术是在一个进程内实现运行指定服务的运行环境,并且可以保护宿主机内核不受其他进程的干扰和影响,如文件系统空间、网络空间、进程空间等,目前主要通过以下技术实现容器运行空间的相互隔离。
namespace | 内核版本 | 系统调用参数 | 隔离内容 | 在容器下的隔离效果 |
---|---|---|---|---|
Mount namespaces | 2.4.19 | CLONE_NEWIPC | 挂载点(文件系统) | 每个容器能看到不同的文件系统层次结构 |
每个容器都要有独立的根文件系统以及独立的用户空间,以实现在容器中启动服务并使用容器的运行环境,即一个宿主机是ubuntu的服务器可以在里面启动一个CentOS运行环境的容器并且在容器里启动一个Nginx服务。该Nginx使用的运行环境就是CentOS系统目录的运行环境,但容器中无法访问宿主机的资源,宿主机使用chroot技术将容器锁定到一个指定的运行目录。
例如:
Docker版本信息
[root@10 ~]# docker version Server: Docker Engine - Community Engine: Version: 20.10.8 API version: 1.41 (minimum version 1.12) Go version: go1.16.6 Git commit: 75249d8 Built: Fri Jul 30 19:54:13 2021 OS/Arch: linux/amd64 Experimental: false containerd: Version: 1.4.9 GitCommit: e25210fe30a0a703442421b0f60afac609f950a3 runc: Version: 1.0.1 GitCommit: v1.0.1-0-g4144b63 docker-init: Version: 0.19.0 GitCommit: de40ad0
启动nginx、tomcat容器
[root@10 ~]# docker run -d --name nginx-1 -p 80:80 nginx [root@10 ~]# docker run -d --name tomcat-1 -p 8080:8080 tomcat [root@10 ~]# docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1e3d68708949 tomcat "catalina.sh run" 28 seconds ago Up 25 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp tomcat-1 6a168a9124e2 nginx "/docker-entrypoint.…" 13 minutes ago Up 13 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp nginx-1
进入容器并查看信息
[root@10 ~]# docker exec -it f1afe2837ff1 bash root@f1afe2837ff1:/# cat /etc/issue Debian GNU/Linux 10 \n \l --容器的运行环境一般使用Debian、Ubuntu、Alpine,很少使用红帽 [root@10 ~]# docker exec -it 1e3d68708949 bash root@1e3d68708949:~# cat /etc/issue Debian GNU/Linux 11 \n \l --nginx和tomcat容器所使运行环境版本并不同
根据container id查看各容器对应的运行目录
[root@10 ~]# ll /var/lib/docker/containers/6a168a9124e229157314e5c2b081597f105be7a0c7a5247f7e03cb0237bd6e68/ total 28 -rw-r-----. 1 root root 2169 Sep 10 13:22 6a168a9124e229157314e5c2b081597f105be7a0c7a5247f7e03cb0237bd6e68-json.log drwx------. 2 root root 6 Sep 10 13:22 checkpoints -rw-------. 1 root root 2901 Sep 10 13:22 config.v2.json -rw-r--r--. 1 root root 1512 Sep 10 13:22 hostconfig.json -rw-r--r--. 1 root root 13 Sep 10 13:22 hostname -rw-r--r--. 1 root root 174 Sep 10 13:22 hosts drwx-----x. 2 root root 6 Sep 10 13:22 mounts -rw-r--r--. 1 root root 69 Sep 10 13:22 resolv.conf -rw-r--r--. 1 root root 71 Sep 10 13:22 resolv.conf.hash [root@10 ~]# ll /var/lib/docker/containers/1e3d687089496394e21a0c076a53d2c75073da5fa73c145e16bfcecd91bc54c2/ total 32 -rw-r-----. 1 root root 7686 Sep 10 13:36 1e3d687089496394e21a0c076a53d2c75073da5fa73c145e16bfcecd91bc54c2-json.log drwx------. 2 root root 6 Sep 10 13:36 checkpoints -rw-------. 1 root root 3207 Sep 10 13:36 config.v2.json -rw-r--r--. 1 root root 1516 Sep 10 13:36 hostconfig.json -rw-r--r--. 1 root root 13 Sep 10 13:36 hostname -rw-r--r--. 1 root root 174 Sep 10 13:36 hosts drwx-----x. 2 root root 6 Sep 10 13:36 mounts -rw-r--r--. 1 root root 69 Sep 10 13:36 resolv.conf -rw-r--r--. 1 root root 71 Sep 10 13:36 resolv.conf.hash
namespace | 内核版本 | 系统调用参数 | 隔离内容 | 在容器下的隔离效果 |
---|---|---|---|---|
UTS namespaces | 2.6.19 | CLONE_NEWUTS | 主机名和域名 | 每个容器可以有自己的hostname和domainame |
UTS Namespaces(UNIX Timesharing System包含运行内核的名称、版本、底层体系结构等信息)用于系统标识,其中包含了主机名hostname和域名domainname,它使得一个容器拥有属于自己的hostname标识,这个主机名标识独立于宿主机系统和其上的其他容器。
查看不同容器的内核版本和主机名称
[root@10 ~]# docker exec -it 1e3d68708949 bash root@1e3d68708949:/usr/local/tomcat# uname -a Linux 1e3d68708949 3.10.0-1160.el7.x86_64(宿主机内核版本) #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64 GNU/Linux root@1e3d68708949:/usr/local/tomcat# hostname -- tomcat容器主机名 1e3d68708949 root@1e3d68708949:/usr/local/tomcat# exit exit [root@10 ~]# docker exec -it 6a168a9124e2 bash root@6a168a9124e2:/# uname -a Linux 6a168a9124e2 3.10.0-1160.el7.x86_64(宿主机内核版本) #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64 GNU/Linux root@6a168a9124e2:/# hostname --nginx容器主机名 6a168a9124e2
namespace | 内核版本 | 系统调用参数 | 隔离内容 | 在容器下的隔离效果 |
---|---|---|---|---|
IPC namespaces | 2.6.19 | CLONE_NEWIPC | 信号量、消息队列、共享内存 | 每个容器有自己的IPC和POSIX消息队列文件系统,只有在同一个IPC namespaces的进程之间才能相互通信 |
一个容器内的进程访问,允许一个容器内的不同进程的数据(内存、缓存等)访问,但不能跨容器访问其他容器的数据,申请IPC资源就申请了一个全局唯一的32位ID,所以IPC namespace实际包含了系统IPC标识符以及实现POSIX消息队列的文件系统。在同一个IPC namespace下的进程彼此可见,不同IPC namespace下的进程则不可见。
namespace | 内核版本 | 系统调用参数 | 隔离内容 | 在容器下的隔离效果 |
---|---|---|---|---|
PID namespaces | 2.6.24 | CLONE_NEWIPC | 进程编号 | 每个PID namespaces中的进程可以有其独立的PID,每个容器可以有PID为1的root进程;由于namespaces中的进程ID与host无关,因此可以容器可以在不同的host之间迁移;容器的每个进程有两个PID:容器中的PID和host上的PID |
Linux系统中,有一个PID为1的进程(init/systemd)是其他所有进程的父进程,那么在每个容器内也要有一个父进程管理其下属的子进程,各容器的进程通过PID Namespace进程隔离(如PID编号重复、器内主进程生成或回收子进程等)。
注意:在容器中没有内核,所以也没有init或者systemd进程,因此需要手动或自定义一个进程作为容器的守护进程;且在容器中至少需要一个可以长期运行的进程作为容器的守护进程(类似于init或systemd)。
tomcat容器:
[root@10 ~]# docker exec -it 1e3d68708949 bash root@1e3d68708949:/usr/local/tomcat# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 05:36 ? 00:00:22 /usr/local/openjdk-11/bin/java -Djava.util. root 50 0 0 07:15 pts/0 00:00:00 bash root 59 50 0 07:26 pts/0 00:00:00 ps -ef
nginx容器:
[root@10 ~]# docker exec -it 6a168a9124e2 bash root@6a168a9124e2:/# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 05:22 ? 00:00:00 nginx: master process nginx -g daemon off; nginx 31 1 0 05:22 ? 00:00:00 nginx: worker process root 381 0 2 07:29 pts/0 00:00:00 bash root 387 381 0 07:29 pts/0 00:00:00 ps -ef
宿主机的PID和容器内的PID是什么关系?
查看宿主机上的PID信息,可知在nginx容器的root进程的父进程是5061,tomcat容器的root进程的父进程是6017
[root@10 ~]# ps -ef | grep nginx root 5080 5061 0 13:22 ? 00:00:00 nginx: master process nginx -g daemon off; ... [root@10 ~]# ps -ef | grep tomcat root 6035 6017 0 13:36 ? 00:00:29 /usr/local/openjdk-11/bin/java -Djava.util...
再查看PID为5061和6017的进程,它们由pid namespace隔离开来
root 5061 1 0 13:22 ? 00:00:02 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 6a168a9124e229157314e5c2b081597f105be7a0c7a5247f7e03cb0237bd6e68 -address /run/containerd/containerd.sock root 6017 1 0 13:36 ? 00:00:02 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 1e3d687089496394e21a0c076a53d2c75073da5fa73c145e16bfcecd91bc54c2 -address /run/containerd/containerd.sock
由此可见,容器内的进程都有两个PID,一个是容器内的PID,一个是宿主机上的PID,彼此成对应关系。如:
名称 | 容器 | 宿主机 |
---|---|---|
nginx | 1 | 5080 |
tomcat | 1 | 6035 |
namespace | 内核版本 | 系统调用参数 | 隔离内容 | 在容器下的隔离效果 |
---|---|---|---|---|
Network namespaces | 2.6.29 | CLONE_NEWIPC | 网络设备、网络栈、端口号 | 每个容器有其独立的网络设备、IP地址、IP路由、端口号等,这使得一个host上多个容器内的同一个应用都能绑定各自容器的80端口 |
每一个容器都类似于虚拟机一样有自己的网卡、监听端口、TCP/IP协议栈等,Docker使用Network Namespace启动一个vethX接口,如此容器将拥有自己的桥接IP地址,通常是docker0,而docker0实质上就是Linux的虚拟网桥,网桥是OSI七层模型的数据链路层的网络设备,通过MAC地址对网络进行划分,并且在不同网络直接传递数据。
查看宿主机的网卡信息
[root@10 ~]# ip a 7: veth91cce51@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 6a:58:45:50:80:da brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::6858:45ff:fe50:80da/64 scope link valid_lft forever preferred_lft forever 13: veth667994a@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 52:e5:03:c9:40:14 brd ff:ff:ff:ff:ff:ff link-netnsid 3 inet6 fe80::50e5:3ff:fec9:4014/64 scope link valid_lft forever preferred_lft forever
查看宿主机桥接设备
[root@10 ~]# brctl show bridge name bridge id STP enabled interfaces docker0 8000.0242886008ee no veth667994a veth91cce51
namespace | 内核版本 | 系统调用参数 | 隔离内容 | 在容器下的隔离效果 |
---|---|---|---|---|
User namespaces | 3.8 | CLONE_NEWIPC | 用户和用户组 | 在user namespaces中的进程的用户和组ID可以和在host上不同;每个容器可以有不同的user和group id;一个host上的非特权用户可以成为user namespaces中的特权用户 |
各个容器内可能会出现重名的用户和用户组名称,或重复的UID或GID,Docker通过User namespace各个宿主机的各个容器空间创立相同的用户名以及相同的用户UID和GID,只会把用户的作用范围限制在每个容器内,而不能访问其他容器的文件系统。
如每个容器内都有超级管理员root用户,且UID和GID均与其他容器相同。
[root@10 ~]# docker exec -it nginx-1 bash root@6a168a9124e2:/# id uid=0(root) gid=0(root) groups=0(root) [root@10 ~]# docker exec -it tomcat-1 bash root@1e3d68708949:/usr/local/tomcat# id uid=0(root) gid=0(root) groups=0(root)