前言
本文对namespace,cgroup和union fs做深入介绍,而docker使用将不再赘述,具体的docker使用见该文docker汇总
docker概述
Linux内核提供了namespace(进程隔离),cgroup(资源管控),以及union fs(联合文件系统),对进程进行封装隔离,属于操作系统层面的虚拟化技术,由于隔离的进程独立于宿主和其他的隔离的进程,因此也称其为容器。所以当我们谈容器技术的时候,其实就是在谈基于Linux内核的namespace,cgroup以及union fs等技术。而docker借助Linux内核的namespace和cgroup的技术优势,加上自己基于union fs独创镜像的方式,就火起来了。
docker在容器的基础上,进一步的封装,从文件系统,网络互联到进程隔离等等,大大的简化了容器的创建和维护,使docker比虚拟机技术更轻便。docker的创新点就在文件系统。
为什么要用docker
- 更高效地利用系统资源
docker的资源利用率高,虚拟机是要虚拟一个操作系统,操作系统本身是有开销的,比如开机占用几百兆内存。 但是容器不一样,它是基于namespace,cgroup和union fs,共享主机的内核。 不需要虚拟化任何东西,不需要加载额外的很多资源来模拟一个完整的操作系统,所以总体的资源利用率很高。
- 更快速的启动时间
正因为不用模拟操作系统,所以不需要操作系统的启动时间,所以启动时间很快。
- 一致的运行环境
- 持续交付和部署
- 更轻松地迁移
- 更轻松地维护和扩展
先来看虚拟机,最下面是物理服务器,上面是主机的操作系统,上面的Hypervisor是虚拟化技术,任何虚拟机都是在Hypervisor之上再装一个操作系统,从虚拟化层面来看调用链很长,并且有两个操作系统。
而容器技术,下面两层一样 ,在主机的操作系统上面不需要再虚拟化任何的操作系统,只需要一个docker engine,所以中间就节省了很多东西,不仅仅是节省了操作系统的资源开销,问题排查也变得更简单了,不需要去排查操作系统的问题了。
性能对比
概述
Linux Namespace 是Linux内核提供的资源隔离方案:
- 系统可以为进程分配不同的 Namespace;
- 保证不同的 Namespace 资源独立分配、进程彼此隔离,即不同的Namespace 下的进程互不干扰 。
实现
进程数据结构
struct task_struct { ... /* namespaces */ struct nsproxy *nsproxy; ... }
Namespace 数据结构
struct nsproxy { atomic_t count; struct uts_namespace *uts_ns; struct ipc_namespace *ipc_ns; struct mnt_namespace *mnt_ns; struct pid_namespace *pid_ns_for_children; struct net *net_ns; }
操作方法
• clone
# 在创建新进程的系统调用时,可以通过 flags 参数指定需要新建的 Namespace 类型 // CLONE_NEWCGROUP / CLONE_NEWIPC / CLONE_NEWNET / CLONE_NEWNS /CLONE_NEWPID /CLONE_NEWUSER / CLONE_NEWUTS int clone(int (*fn)(void *), void *child_stack, int flags, void *arg)
• setns
# 该系统调用可以让调用进程加入某个已经存在的 Namespace 中 int setns(int fd, int nstype)
• unshare
# 该系统调用可以将调用进程移动到新的 Namespace 下 int unshare(int flags)
隔离性
Pid namespace
- 不同用户的进程就是通过 Pid namespace 隔离开的,且不同 namespace 中可以有相同 Pid。
- 有了 Pid namespace, 每个 namespace 中的 Pid 能够相互隔离。
net namespace
- 网络隔离是通过 net namespace 实现的, 每个 net namespace 有独立的network devices, IP addresses, IP routing tables, /proc/net 目录。
- Docker 默认采用 veth 的方式将 container 中的虚拟网卡同 host 上的一个 docker bridge: docker0 连接在一起。
ipc namespace
- Container 中进程交互还是采用 linux 常见的进程间交互方法 (interprocess communication – IPC), 包括常见的信号量、消息队列和共享内存。
- container 的进程间交互实际上还是 host上 具有相同 Pid namespace 中的进程间交互,因此需要在 IPC 资源申请时加入 namespace 信息 - 每个 IPC 资源有一个唯一的 32 位 ID。
mnt namespace
- mnt namespace 允许不同 namespace 的进程看到的文件结构不同,这样每个 namespace 中的进程所看到的文件目录就被隔离开了。
uts namespace
- UTS(“UNIX Time-sharing System”) namespace允许每个 container 拥有独立的 hostname 和 domain name, 使其在网络上可以被视作一个独立的节点而非 Host 上的一个进程。
user namespace
- 每个 container 可以有不同的 user 和 group id, 也就是说可以在 container 内部用 container 内部的用户执行程序而非 Host 上的用户。
常用操作
查看当前系统的 namespace:
# lsns –t <type> [root@node2 ~]# lsns -t net NS TYPE NPROCS PID USER COMMAND 4026531956 net 121 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 22 [root@node2 ~]# lsns -t pid NS TYPE NPROCS PID USER COMMAND 4026531836 pid 121 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 22
查看某进程的 namespace:
# ls -la /proc/<pid>/ns/ [root@node2 ~]# ls -la /proc/1/ns/ 总用量 0 dr-x--x--x 2 root root 0 3月 14 20:43 . dr-xr-xr-x 9 root root 0 3月 14 20:43 .. lrwxrwxrwx 1 root root 0 3月 14 20:44 ipc -> ipc:[4026531839] lrwxrwxrwx 1 root root 0 3月 14 20:44 mnt -> mnt:[4026531840] lrwxrwxrwx 1 root root 0 3月 14 20:44 net -> net:[4026531956] lrwxrwxrwx 1 root root 0 3月 14 20:43 pid -> pid:[4026531836] lrwxrwxrwx 1 root root 0 3月 14 20:44 user -> user:[4026531837] lrwxrwxrwx 1 root root 0 3月 14 20:44 uts -> uts:[4026531838]
进入某 namespace 运行命令:
# nsenter -t <pid> -n ip addr # -n ip addr 表示查看net 的ns 的网络配置 [root@node2 ~]# nsenter -t 1 -n ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state ... 2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc ... 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc ...
下次测试通过nsenter看容器内的网路
[root@node2 ~]# docker run -it --name centos -P centos /bin/bash [root@9d204d09c27e /]# 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 14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.17.0.2/16 scope global eth0
[root@node2 /]# docker ps|grep centos 9d204d09c27e centos "/bin/bash" 44 seconds ago Up 43 seconds centos [root@node2 /]# docker inspect 9d204d09c27e | grep -i pid "Pid": 4159, "PidMode": "", "PidsLimit": 0, [root@node2 /]# nsenter -t 4159 -n 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 14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.17.0.2/16 scope global eth0
Cgroups
概述
- Cgroups (Control Groups)是 Linux 下用于对一个或一组进程进行资源控制和监控的机制;
- 可以对诸如 CPU 使用时间、内存、磁盘 I/O 等进程所需的资源进行限制;
- 不同资源的具体管理工作由相应的 Cgroup 子系统(Subsystem)来实现 ;
- 针对不同类型的资源限制,只要将限制策略在不同的的子系统上进行关联即可 ;
- Cgroups 在不同的系统资源管理子系统中以层级树(Hierarchy)的方式来组织管理:每个 Cgroup 都可以包含其他的子 Cgroup,因此子 Cgroup 能使用的资源除了受本 Cgroup 配置的资源参数限制,还受到父Cgroup 设置的资源限制 。
实现
进程数据结构
struct task_struct { #ifdef CONFIG_CGROUPS struct css_set __rcu *cgroups; struct list_head cg_list; #endif }
css_set 是 cgroup_subsys_state 对象的集合数据结构
struct css_set { /* * Set of subsystem states, one for each subsystem. This array is * immutable after creation apart from the init_css_set during * subsystem registration (at boot time). */ struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; };
配额和度量
cgroups 实现了对资源的配额和度量
- blkio: 这个子系统设置限制每个块设备的输入输出控制。例如:磁盘,光盘以及 USB 等等。
- CPU: 这个子系统使用调度程序为 cgroup 任务提供 CPU 的访问。
- cpuacct: 产生 cgroup 任务的 CPU 资源报告。
- cpuset: 如果是多核心的 CPU,这个子系统会为cgroup 任务分配单独的 CPU 和内存。
- devices: 允许或拒绝 cgroup 任务对设备的访问。
- freezer: 暂停和恢复 cgroup 任务。
- memory: 设置每个 cgroup 的内存限制以及产生内存资源报告。
- net_cls: 标记每个网络包以供 cgroup 方便使用。
- ns: 名称空间子系统。
- pid: 进程标识子系统。
CPU 子系统
cpu.shares: 可出让的能获得 CPU 使用时间的相对值。
cpu.cfs_period_us:cfs_period_us 用来配置时间周期长度,单位为 us(微秒)。
cpu.cfs_quota_us:cfs_quota_us 用来配置当前 Cgroup 在 cfs_period_us 时间内最多能使用的 CPU 时间数,单位为 us(微秒)。
cpu.stat : Cgroup 内的进程使用的 CPU 时间统计。
nr_periods : 经过 cpu.cfs_period_us 的时间周期数量。
nr_throttled : 在经过的周期内,有多少次因为进程在指定的时间周期内用光了配额时间而受到限制。
throttled_time : Cgroup 中的进程被限制使用 CPU 的总用时,单位是 ns(纳秒)。
Linux 调度器
内核默认提供了5个调度器,Linux 内核使用 struct sched_class 来对调度器进行抽象:
- Stop 调度器,stop_sched_class:优先级最高的调度类,可以抢占其他所有进程,不能被其他进程抢占;
- Deadline 调度器,dl_sched_class:使用红黑树,把进程按照绝对截止期限进行排序,选择最小进程进行调度运行;
- RT 调度器, rt_sched_class:实时调度器,为每个优先级维护一个队列;
CFS 调度器, cfs_sched_class:完全公平调度器,采用完全公平调度算法,引入虚拟运行时间概念;
- IDLE-Task 调度器, idle_sched_class:空闲调度器,每个 CPU 都会有一个 idle 线程,当没有其他进程可以调度时,调度运行 idle 线程。
CFS 调度器
https://www.cnblogs.com/XiaoliBoy/p/10410686.html
直接看上面这篇文章。
操作
# 在 cgroup cpu 子系统目录中创建目录结构 [root@node2 ~]# cd /sys/fs/cgroup/cpu [root@node2 cpu]# mkdir cpudemo # 运行 busyloop,go程序两个线程两个for死循环 # 执行 top 查看 CPU 使用情况,CPU 占用 200% # 通过 cgroup 限制 cpu [root@node2 cpu]# cd cpudemo/ # 获取busyloop的进程号,把进程添加到 cgroup 进程配置组 [root@node2 cpudemo]# echo 16872 >cgroup.procs # 设置 cpuquota [root@node2 cpudemo]# echo 10000 > cpu.cfs_quota_us # 执行 top 查看 CPU 使用情况,CPU 占用变为10%
cpuacct 子系统
用于统计 Cgroup 及其子 Cgroup 下进程的 CPU 的使用情况。
- cpuacct.usage
- cpuacct.stat
包含该 Cgroup 及其子 Cgroup 下进程使用的 CPU 时间,以及用户态和内核态的时间。
Memory 子系统
- memory.usage_in_bytes
cgroup 下进程使用的内存,包含 cgroup 及其子 cgroup 下的进程使用的内存 - memory.max_usage_in_bytes
- cgroup 下进程使用内存的最大值,包含子 cgroup 的内存使用量。
- memory.limit_in_bytes
设置 Cgroup 下进程最多能使用的内存。如果设置为 -1,表示对该 cgroup 的内存使用不做限制。 - memory.soft_limit_in_bytes
这个限制并不会阻止进程使用超过限额的内存,只是在系统内存足够时,会优先回收超过限额的内存,使之向限定值靠拢。 - memory.oom_control
设置是否在 Cgroup 中使用 OOM(Out of Memory)Killer,默认为使用。当属于该 cgroup 的进程使用的内存超过最大的限定值时,会立刻被 OOM Killer 处理。
操作
# 在 cgroup memory 子系统目录中创建目录结构 [root@node2 ~]# cd /sys/fs/cgroup/memory/ [root@node2 memory]# mkdir memorydemo # 运行 malloc(一个不断申请heap的程序) # 查看内存使用情况 [root@node2 memory]# watch 'ps -aux|grep malloc|grep -v grep' # 通过 cgroup 限制 memory # 把进程添加到cgroup进程配置组 [root@node2 memory]# cd memorydemo/ [root@node2 memorydemo]# echo 11111 >cgroup.procs 设置 memory.limit_in_bytes [root@node2 memorydemo]# echo 104960000 > memory.limit_in_bytes # 等待进程被 oom kill
Cgroup driver
systemd:
- 当操作系统使用 systemd 作为 init system 时,初始化进程生成一个根 cgroup 目录结构并作为 cgroup
管理器。 - systemd 与 cgroup 紧密结合,并且为每个 systemd unit 分配 cgroup。
cgroupfs:
- docker 默认用 cgroupfs 作为 cgroup 驱动。
k8s和docker存在问题:
- 在 systemd 作为 init system 的系统中,默认并存着两套 groupdriver。
- 这会使得系统中 Docker 和 kubelet 管理的进程被 cgroupfs 驱动管,而 systemd 拉起的服务由systemd 驱动管,让 cgroup 管理混乱且容易在资源紧张时引发问题。
因此 kubelet 会默认--cgroup-driver=systemd,若运行时 cgroup 不一致时,kubelet 会报错。
文件系统 Union fs
概述
Union FS
- 将不同目录挂载到同一个虚拟文件系统下 (unite several directories into a single virtual filesystem)的文件系统(rootfs)
- 支持为每一个成员目录(类似Git Branch)设定 readonly、readwrite 和 whiteout-able 权限
- 文件系统分层, 对 readonly 权限的 branch 可以逻辑上进行修改(增量地, 不影响 readonly 部分的)。
- 通常 Union FS 有两个用途, 一方面可以将多个 disk 挂到同一个目录下, 另一个更常用的就是将一个readonly 的 branch 和一个 writeable 的 branch 联合在一起。
容器使用了union fs文件系统,可以将多个目录,mount成为一个目录。就是将两个不同的目录,挂载到同一个目录下面,然后通过读写权限的设置,使得呈现最终的文件目录给容器。
容器镜像
Docker的文件系统
docker本质是在主机上启的一个进程,这个进程本质上是没有单独的kernel的,也就是说对于容器来说是哦没有bootfs的,但是每一个进程需要看到自己的文件系统,所有它有自己单独的rootfs。没有bootfs,复用主机的kernel,有自己的rootfs,这个rootfs通过容器驱动加载出来,被这个进程所拥有,这个进程真正看到的就是这个容器的rootfs。
标准的Linux文件系统组成:
- Bootfs(boot file system)
Bootloader - 引导加载 kernel, Kernel - 当 kernel 被加载到内存中后 umount(卸载) bootfs。
- rootfs (root file system)
/dev,/proc,/bin,/etc 等标准目录和文件。 对于不同的 linux 发行版, bootfs 基本是一致的,但 rootfs 会有差别。
Docker 启动
Linux
- 在启动后,首先将 rootfs 设置为 readonly, 进行一系列检查, 然后将其切换为 “readwrite”供用户使用。
Docker 启动
- 初始化时也是将 rootfs 以 readonly 方式加载并检查,然而接下来利用 union mount 的方式将一个readwrite 文件系统挂载在 readonly 的 rootfs 之上;
- 并且允许再次将下层的 FS(file system) 设定为 readonly 并且向上叠加。
- 这样一组 readonly 和一个 writeable 的结构构成一个 container 的运行时态, 每一个 FS 被称作一个 FS层。
写操作
由于镜像具有共享特性,所以对容器可写层的操作需要依赖存储驱动提供的写时复制和用时分配机制,以此来支持对容器可写层的修改,进而提高对存储和内存资源的利用率。
写时复制
共享基础层,所有的变更都是在可写层做的
- 写时复制,即 Copy-on-Write。
一个镜像可以被多个容器使用,但是不需要在内存和磁盘上做多个拷贝。
在需要对镜像提供的文件进行修改时,该文件会从镜像的文件系统被复制到容器的可写层的文件系统进行修改,而镜像里面的文件不会改变。
不同容器对文件的修改都相互独立、互不影响。
用时分配
- 按需分配空间,而非提前分配,即当一个文件被创建出来后,才会分配空间。
存储驱动
目前的容器驱动基本都是OverlayFS
了
深入剖析docker核心技术(namespace、cgroups、union fs、网络)(二):https://developer.aliyun.com/article/1417848