Docker基础之Linux Namespaces

Docker基础之Linux Namespaces
如上图所示,一个宿主机运行N个容器,多容器带来的以下问题如何解决?

  • 怎样保证每个容器都有不同的文件系统并互不影响?
  • 一个docker主进程内的各个容器都是其子进程,如何实现同一个主进程下不同类型的子进程?各个进程间通信能相互访问内存数据吗?
  • 每个容器如何解决IP及端口分配问题?
  • 多个容器的主机名能一样吗?
  • 每个容器都要不要有root用户?如何解决账户重名问题?

Docker使用Linux Namespaces技术来隔离不同容器运行环境(文件系统、IP地址、端口、主机名、用户等),在docker环境中,namespace由宿主机的kernel实现。

补充:在kubernetes中使用namespace实现业务的容器隔离,如将A业务的容器放置到A业务的namespace,B业务的容器放置到B业务的namespace(以业务命名namespace)。

Linux Namespaces技术

Namespace是Linux系统的底层概念,在内核层实现,即有一些不同类型的命名空间被部署在核内,各个docker容器运行在同一个docker主进程并且共用同一个宿主机kernel,各docker容器运行在宿主机的用户空间,每个容器都要有类似虚拟机一样的相互隔离的运行空间,但是容器技术是在一个进程内实现运行指定服务的运行环境,并且可以保护宿主机内核不受其他进程的干扰和影响,如文件系统空间、网络空间、进程空间等,目前主要通过以下技术实现容器运行空间的相互隔离。

Mount Namespaces

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

UTS Namespaces

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

IPC Namespaces

namespace 内核版本 系统调用参数 隔离内容 在容器下的隔离效果
IPC namespaces 2.6.19 CLONE_NEWIPC 信号量、消息队列、共享内存 每个容器有自己的IPC和POSIX消息队列文件系统,只有在同一个IPC namespaces的进程之间才能相互通信

一个容器内的进程访问,允许一个容器内的不同进程的数据(内存、缓存等)访问,但不能跨容器访问其他容器的数据,申请IPC资源就申请了一个全局唯一的32位ID,所以IPC namespace实际包含了系统IPC标识符以及实现POSIX消息队列的文件系统。在同一个IPC namespace下的进程彼此可见,不同IPC namespace下的进程则不可见。

PID Namespaces

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

Network Namespaces

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

Docker基础之Linux Namespaces

User Namespaces

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)
上一篇:在k8s中使用Namespaces


下一篇:Kubernetes namespace介绍