为什么构建容器需要Namespace?
1、什么是Namespace?
Namespace 是 Linux 内核的一个特性,该特性可以实现在同一主机系统中,对进程 ID、主机名、用户 ID、文件名、网络和进程间通信等资源的隔离。Docker 利用 Linux 内核的 Namespace 特性,实现了每个容器的资源相互隔离,从而保证容器内部只能访问到自己 Namespace 的资源。
我们创建一个容器。
[root@master ~]# docker run -it busybox /bin/sh
/ #
此时我们在容器里执行以下ps命令,就会发现一些有趣的事情。
/ # ps
PID USER TIME COMMAND
1 root 0:00 /bin/sh
9 root 0:00 ps
可以看到,我们在 Docker 里最开始执行的 /bin/sh,就是这个容器内部的第 1 号进程 (PID=1),而这个容器里一共只有两个进程在运行。这就意味着,前面执行的 /bin/sh,以及 我们刚刚执行的 ps,已经被 Docker 隔离在了一个跟宿主机完全不同的世界当中。
本来,每当我们在宿主机上运行一个/bin/sh程序,操作系统就会给它分配一个进程号,比如PID = 1000。这个编号就是进程的唯一标识,就像员工工牌一样,可以粗略地理解为这个/bin/sh是公司里第1000名员工,而第一号员工自然就是老板这样统领全局的人物。
而现在我们通过Docker把这个/bin/sh程序运行在一个容器当中,这时候Docker就会给这个第1000号员工入职时候施一个"障眼法",让他永远看不到在他前面的999个员工,这样他就错误的认为自己就是公司的第一号员工。
这种机制,其实就是对被隔离应用的进程空间做了手脚,使得这些进程只能看到重新计算过的进程编号,比如 PID=1。可实际上,他们在宿主机的操作系统里,还是原来的第 1000 号进程。
这种技术,就是 Linux 里面的 Namespace 机制。
Linux 5.6 内核中提供了 8 种类型的 Namespace:
| Namespace名称 | 作用 | 内核版本 |
|---|---|---|
| Mount(mnt) | 隔离挂载点 | 2.4.19 |
| Process ID(pid) | 隔离进程ID | 2.6.24 |
| Network (net) | 隔离网络设备,端口号等 | 2.6.19 |
| Interprocess Communication (ipc) | 隔离 System V IPC 和 POSIX message queues | 2.6.19 |
| UTS Namespace(uts) | 隔离主机名和域名 | 2.6.19 |
| User Namespace (user) | 隔离用户和用户组 | 3.8 |
| Control group (cgroup) Namespace | 隔离 Cgroups 根目录 | 4.6 |
| Time Namespace | 隔离系统时间 | 5.6 |
2、各种Namespace作用?
(1)、Mount Namespace
实现在不同的进程中看到不同的挂载目录。使用 Mount Namespace 可以实现容器内只能看到自己的挂载信息,在容器内的挂载操作不会影响主机的挂载目录。
我们使用以下命令创建一个 bash 进程并且新建一个 Mount Namespace:
[root@master ~]# unshare --mount --fork /bin/bash
[root@master ~]#
执行完上述命令后,这时我们已经在主机上创建了一个新的 Mount Namespace,并且当前命令行窗口加入了新创建的 Mount Namespace。下面我通过一个例子来验证下,在独立的 Mount Namespace 内创建挂载目录是不影响主机的挂载目录的。
首先在 /tmp 目录下创建一个目录。
[root@master ~]# mkdir /tmp/tmpfs
创建好目录后使用 mount 命令挂载一个 tmpfs 类型的目录。命令如下:
[root@master ~]# mount -t tmpfs -o size=20m tmpfs /tmp/tmpfs/
然后使用df命令查看一下已经挂载的目录信息:
[root@master ~]# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/centos-root 26G 4.6G 22G 18% /
devtmpfs 1.9G 0 1.9G 0% /dev
tmpfs 1.9G 0 1.9G 0% /dev/shm
tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup
tmpfs 1.9G 20M 1.9G 2% /run
tmpfs 378M 0 378M 0% /run/user/0
/dev/sda1 1014M 183M 832M 18% /boot
tmpfs 20M 0 20M 0% /tmp/tmpfs
可以看到 /tmp/tmpfs 目录已经被正确挂载。为了验证主机上并没有挂载此目录,我们新打开一个命令行窗口,同样执行 df 命令查看主机的挂载信息:
[root@master ~]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 1.9G 0 1.9G 0% /dev
tmpfs 1.9G 0 1.9G 0% /dev/shm
tmpfs 1.9G 20M 1.9G 2% /run
tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup
/dev/mapper/centos-root 26G 4.6G 22G 18% /
/dev/sda1 1014M 183M 832M 18% /boot
tmpfs 378M 0 378M 0% /run/user/0
通过上面输出可以看到主机上并没有挂载 /tmp/tmpfs,可见我们独立的 Mount Namespace 中执行 mount 操作并不会影响主机。
为了进一步验证我们的想法,我们继续在当前命令行窗口查看一下当前进程的 Namespace 信息,命令如下:
[root@master ~]# ls -l /proc/self/ns/
total 0
lrwxrwxrwx 1 root root 0 Dec 2 18:39 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Dec 2 18:39 mnt -> mnt:[4026532476]
lrwxrwxrwx 1 root root 0 Dec 2 18:39 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Dec 2 18:39 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Dec 2 18:39 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Dec 2 18:39 uts -> uts:[4026531838]
然后在新打开的命令行窗口,使用相同的命令查看一下主机上的 Namespace 信息:
[root@master ~]# ls -l /proc/self/ns/
total 0
lrwxrwxrwx 1 root root 0 Dec 2 18:39 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Dec 2 18:39 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Dec 2 18:39 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Dec 2 18:39 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Dec 2 18:39 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Dec 2 18:39 uts -> uts:[4026531838]
通过对比两次命令的输出结果,我们可以看到,除了 Mount Namespace 的 ID 值不一样外,其他Namespace 的 ID 值均一致。
通过以上结果我们可以得出结论,使用 unshare 命令可以新建 Mount Namespace,并且在新建的 Mount Namespace 内 mount 是和外部完全隔离的。
(2)、PID Namespace
PID Namespace 的作用是用来隔离进程。在不同的 PID Namespace 中,进程可以拥有相同的 PID 号,利用 PID Namespace 可以实现每个容器的主进程为 1 号进程,而容器内的进程在主机上却拥有不同的PID。例如一个进程在主机上 PID 为 122,使用 PID Namespace 可以实现该进程在容器内看到的 PID 为 1。
我们使用以下命令创建一个bash进程,并且新建一个PID Namespace:
[root@master ~]# unshare --pid --fork --mount-proc /bin/bash
[root@master ~]#
执行完上述命令后,我们在主机上创建了一个新的 PID Namespace,并且当前命令行窗口加入了新创建的 PID Namespace。在当前的命令行窗口使用 ps aux 命令查看一下进程信息:
[root@master ~]# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 115684 2144 pts/0 S 18:47 0:00 /bin/bash
root 13 0.0 0.0 155452 1848 pts/0 R+ 18:49 0:00 ps aux
通过上述命令输出结果可以看到当前 Namespace 下 bash 为 1 号进程,而且我们也看不到主机上的其他进程信息。
(3)、UTS Namespace
UTS Namespace 主要是用来隔离主机名的,它允许每个 UTS Namespace 拥有一个独立的主机名。
例如我们的主机名称为 master,使用 UTS Namespace 可以实现在容器内的主机名称为docker 或者其他任意自定义主机名。
同样我们通过一个实例来验证下 UTS Namespace 的作用,首先我们使用 unshare 命令来创建一个 UTS Namespace:
[root@master ~]# hostname // 查看当前主机名
master
[root@master ~]# unshare --uts --fork /bin/bash
创建好 UTS Namespace 后,当前命令行窗口已经处于一个独立的 UTS Namespace 中,下面我们使用 hostname 命令(hostname 可以用来查看主机名称)设置一下主机名:
[root@master ~]# hostname -b docker
[root@master ~]# hostname
docker
然后我们新打开一个命令行窗口,使用相同的命令查看一下主机的 hostname:
[root@master ~]# hostname
master
可以看到主机的名称仍然为 master,并没有被修改。由此,可以验证 UTS Namespace 可以用来隔离主机名。
(4)、IPC Namespace
IPC Namespace 主要是用来隔离进程间通信的。例如 PID Namespace 和 IPC Namespace 一起使用可以实现同一 IPC Namespace 内的进程彼此可以通信,不同 IPC Namespace 的进程却不能通信。
我们使用unshare命令来创建一个IPC Namespace:
[root@master ~]# unshare --ipc --fork /bin/bash
[root@docker ~]#
下面我们需要借助两个命令来实现对IPC Namespace的验证。
- ipcs -q : 用来查看系统间通信队列列表
- ipcmk -Q : 用来创建系统间通信队列
我们首先使用 ipcs -q 命令查看一下当前 IPC Namespace 下的系统通信队列列表:
[root@docker ~]# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
由上可以看到当前无任何系统通信队列,然后我们使用 ipcmk -Q 命令创建一个系统通信队列:
[root@docker ~]# ipcmk -Q
Message queue id: 0
再次使用 ipcs -q 命令查看当前 IPC Namespace 下的系统通信队列列表:
[root@docker ~]# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x4a19cc47 0 root 644 0 0
可以看到我们已经成功创建了一个系统通信队列。然后我们新打开一个命令行窗口,使用ipcs -q 命令查看一下主机的系统通信队列:
[root@master ~]# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
通过上面的实验,可以发现,在单独的 IPC Namespace 内创建的系统通信队列在主机上无法看到。即 IPC Namespace 实现了系统通信队列的隔离。
(5)、User Namespace
User Namespace 主要是用来隔离用户和用户组的。一个比较典型的应用场景就是在主机上以非 root 用户运行的进程可以在一个单独的 User Namespace 中映射成 root 用户。使用 User Namespace 可以实现进程在容器内拥有 root 权限,而在主机上却只是普通用户。
User Namespace 的创建是可以不使用 root 权限的。下面我们以普通用户的身份创建一个 User Namespace,命令如下:
[root@docker ~]# su - test
Last login: Thu Dec 2 19:11:29 CST 2021 on pts/0
[test@docker ~]$ unshare --user -r /bin/bash
[root@docker ~]#
CentOS7 默认允许创建的 User Namespace 为 0,如果执行上述命令失败( unshare 命令返回的错误为 unshare: unshare failed: Invalid argument ),需要使用以下命令修改系统允许创建的 User Namespace 数量
命令为:echo 65535 > /proc/sys/user/max_user_namespaces,然后再次尝试创建 User Namespace。
然后执行id命令查看一下当前的用户信息:
[root@docker ~]# id
uid=0(root) gid=0(root) groups=0(root)
[root@docker ~]#
通过上面的输出可以看到我们在新的 User Namespace 内已经是 root 用户了。下面我们使用只有主机 root 用户才可以执行的 reboot 命令来验证一下,在当前命令行窗口执行 reboot 命令:
[root@docker ~]# reboot
Failed to open /dev/initctl: Permission denied
Failed to talk to init daemon.
可以看到,我们在新创建的 User Namespace 内虽然是 root 用户,但是并没有权限执行 reboot 命令。这说明在隔离的 User Namespace 中,并不能获取到主机的 root 权限,也就是说 User Namespace 实现了用户和用户组的隔离。
(6)、Net Namespace
Net Namespace 是用来隔离网络设备、IP 地址和端口等信息的。Net Namespace 可以让每个进程拥有自己独立的 IP 地址,端口和网卡信息。例如主机 IP 地址为 192.168.209.1 ,容器内可以设置独立的 IP 地址为 172.16.4.1。
我们使用ip a命令查看下主机上网络信息:
[root@docker ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:30:6d:8c brd ff:ff:ff:ff:ff:ff
inet 192.168.209.148/24 brd 192.168.209.255 scope global noprefixroute dynamic ens32
valid_lft 1760sec preferred_lft 1760sec
inet6 fe80::8081:c385:2b72:fe59/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:d7:5e:07:6e brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:d7ff:fe5e:76e/64 scope link
valid_lft forever preferred_lft forever
我们使用以下命令创建一个 Net Namespace:
[root@docker ~]# unshare --net --fork /bin/bash
[root@docker ~]# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
[root@docker ~]#
可以看到,宿主机上有 lo、eth0、docker0 等网络设备,而我们新建的 Net Namespace 内则与主机上的网络设备不同。
3、为什么Docker需要Namespace?
当 Docker 新建一个容器时, 它会创建这六种 Namespace,然后将容器中的进程加入这些 Namespace 之中,使得 Docker 容器中的进程只能看到当前 Namespace 中的系统资源。
所以,Docker 容器这个听起来玄而又玄的概念,实际上是在创建容器进程时,指定了这个进程 所需要启用的一组 Namespace 参数。这样,容器就只能“看”到当前 Namespace 所限定的 资源、文件、设备、状态,或者配置。而对于宿主机以及其他不相关的程序,它就完全看不到 了。
容器其实是一种特殊的就进程。
在使用 Docker 的时候,并没有一个真正的“Docker 容器”运行在宿主机里面。Docker 项目帮助用 户启动的,还是原来的应用进程,只不过在创建这些进程时,Docker 为它们加上了各种各样的 Namespace参数。这些进程就会觉得自己是各自 PID Namespace 里的第 1 号进程,只能看到各自 Mount Namespace 里挂载的目录和文件,只能访问到各自 Network Namespace 里的网络设备,就仿佛运行在一个个“容器”里面,与世隔绝。
正是由于 Docker 使用了 Linux 的这些 Namespace 技术,才实现了 Docker 容器的隔离,可以说没有 Namespace,就没有 Docker 容器。
为什么构建容器需要Namespace?的更多相关文章
- [ci]jenkins构建容器项目java-helloworld-非docker plugin模式
栗子思路说明: 不使用任何docker plugin 使用jenkins server本地(含mvn环境)构建,无jenkins slave jenkins server本地构建的war包,推送dep ...
- .NET 7 SDK 开始 支持构建容器化应用程序
微软于 8 月 25 日在.NET官方博客上,.NET 7 SDK 将包括对创建容器化应用程序的支持,作为构建发布过程的一部分,从而绕过需要.显式 Docker 构建阶段. 这一决定背后的基本认知是简 ...
- 026.[转] 基于Docker及Kubernetes技术构建容器云平台 (PaaS)
[编者的话] 目前很多的容器云平台通过Docker及Kubernetes等技术提供应用运行平台,从而实现运维自动化,快速部署应用.弹性伸缩和动态调整应用环境资源,提高研发运营效率. 本文简要介绍了与容 ...
- Dockerfile构建容器---构建本地tomcat
前序 这是我第一次摸索.做个笔记记录一下. 首先准备好tomcat与jdk解压到与Dockerfile同级的目录下, 构建文件命名必须为Dockerfile, 为什么同级, 因为build的时候会默认 ...
- asp.net core webapi 使用ef 对mysql进行增删改查,并生成Docker镜像构建容器运行
1.构建运行mysql容器,添加数据库user 参考Docker创建运行多个mysql容器,地址 http://www.cnblogs.com/heyangyi/p/9288402.html 添加us ...
- Docker中使用多阶段Dockerfile构建容器镜像image(镜像优化)
使用多阶段构建 预计阅读时间: 6分钟 多阶段构建是守护程序和客户端上需要Docker 17.05或更高版本的新功能.多阶段构建对于那些努力优化Dockerfiles同时使其易于阅读和维护的人来说非常 ...
- 使用Dockerfile构建容器镜像
Dockerfile官方文档: https://docs.docker.com/engine/reference/builder/ 获取容器镜像的方法 容器镜像是容器模板,通过容器镜像才能快速创建容器 ...
- Centos上Docker 使用dockerfile构建容器实现ssh
这几日在学习docker.遇到的问题数一年都数不完,网上大多数都是ubuntu的,百度或者谷歌的时候心好累.写写文档来帮助使用centos的docker爱好者们. docker基本操作这里就不介绍了 ...
- Dockerfile 构建容器
本文是最简单的Dockerfile教程,创建tomcat容器,并跑自己的java程序 首先需要准备几个东西 1.你的java web(test.war) 程序,最好打包成一个 war:(主要是没测试 ...
- Docker+etcd+flanneld+kubernets 构建容器编排系统(1)
Docker: Docker Engine, 一个client-server 结构的应用, 包含Docker daemon,一个 用来和daemon 交互的REST API, 一个命令行应用CLI. ...
随机推荐
- 2025年我用 Compose 写了一个 Todo App
标题党嫌疑犯实锤 序言 从2月12日到3月4日这整整三周时间里,我从零开始又学习了一次 Compose. 为什么说又,是因为这已经是我第二次学习这套课程了. 故事从 4 年前说起,2021 年在意外获 ...
- AXUI一个面向设计的UI前端框架,好用
以下是官方介绍: ax的中文意义是:斧子,读音[aeks],取其攻击力强.简单实用之意为本前端框架命名.本团队开发了诸多网站项目,使用了许多常见的前端框架,结合实际项目经验,借鉴了同行的经验,特自主开 ...
- HarmonyOS Next 鸿蒙开发-如何使用服务端下发的RSA公钥(字符串)对明文数据进行加密
如何使用服务端下发的RSA公钥(字符串)对明文数据进行加密 将服务器下发的RSA公钥字符串替换掉pubKeyStr即可实现,具体可参考如下代码: import { buffer, util } fro ...
- go module基本使用
前提 go版本为1.13及以上 官方文档 如果你想更深层次的了解GO MODULE的意义及开发者们的顾虑,可以直接访问官方文档(EN) https://github.com/golang/go/wik ...
- python正则表达式笔记2
由 '\' 和一个字符组成的特殊序列在以下列出. 如果普通字符不是ASCII数位或者ASCII字母,那么正则样式将匹配第二个字符.比如,\$ 匹配字符 '$'. \number匹配数字代表的组合.每个 ...
- 【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit(10)
1.问题描述: 离线推送,锁屏的时候没有弹出消息,只有下拉在通知中心里面显示.请问是否是正常的? 解决方案: 检查一下是否存在图片风控:https://developer.huawei.com/con ...
- 什么是 CSS 设计模式
这是转载的,先收藏到我的博客园. 什么是设计模式? 曾有人调侃,设计模式是工程师用于跟别人显摆的,显得高大上:也曾有人这么说,不是设计模式没用,是你还没有到能懂它,会用它的时候. 先来看一下比较官方的 ...
- 【Java】常用类
一.String类 java.lang.String类的使用 注意:String可以String s = "";,是因为String类型在后面自动补充了'\0' char初始化不能 ...
- Java 中如何判断对象是否是垃圾?不同垃圾回收方法有何区别?
Java 中如何判断对象是否是垃圾? 在 Java 中,垃圾是指不再被引用的对象.JVM 使用以下两种方法判断对象是否是垃圾: 1. 引用计数法(Reference Counting) 工作原理 每个 ...
- 什么是 Java 中的 JIT(Just-In-Time)?
Java 中的 JIT(Just-In-Time)编译器 1. JIT 的定义 JIT(Just-In-Time)编译器是一种用于 Java 虚拟机(JVM)的动态编译技术.它在 Java 程序运行时 ...