概要
基于 Linux Kernel 的 Cgroup 、Namespace 、Union FS 等技术,对进程 进行封装隔离 ,属于 OS 层面的虚拟化技术
由于被隔离的进程独立于宿主机和其它被隔离的进程,因此称为容器
演变历史
最早基于 LXC – a linux container runtime
0.7:去除 LXC,转而使用自研的 Libcontainer
1.11:使用 runC
和 containerd
Docker 在容器的基础上,进行了进一步的封装,极大地简化了容器的创建和维护
Docker vs VM
Docker Engine 是一个 Daemon 进程,启动一个容器的时候,相当于执行了一次进程的 Fork 操作
Container
VM
启动时间
秒级
分钟级
硬盘使用
MB 级别
GB 级别
性能
接近原生
弱于
单机支持量
上千
几十
容器标准
OCI – Open Container Initiative
K8S 的主要贡献:标准化
后期 Docker 公司不得不让步,兼容 OCI 标准
核心规范:Image + Runtime
Image Specification
Docker 公司的主要贡献:基于 Union FS ,创造了 Image ,解决了容器分发 的问题
如何通过构建系统打包、生成镜像清单(Manifest)、文件系统序列化文件、镜像配置
Runtime Specification
Cgroup(由 Google 开源) + Namespace
文件系统如何解压到硬盘、Runtime 运行
Namespace
隔离性
概念
Linux Namespace 是一种 Linux Kernel 提供的资源隔离 方案
Kernel 为进程分配不同的 Namespace,不同的 Namespace 下的进程互不干扰
Linux 进程在运行时都会归属于某个 Namespace
Struct: Linux Process 1 2 3 4 5 6 struct task_struct { ... struct nsproxy *nsproxy ; ... }
Struct: Namespace 1 2 3 4 5 6 7 8 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
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg)
1 号进程一般是 systemd
,会有默认 的 Namespace
创建新进程 时,可以通过 flags
指定需要新建的 Namespace
CLONE_NEWCGROUP
CLONE_NEWIPC
CLONE_NEWNET
CLONE_NEWNS
CLONE_NEWPID
CLONE_NEWUSER
CLONE_NEWUTS
setns
int setns(int fd, int nstype)
让调用进程加入已经存在的 Namespace 中
unshare
int unshare(int flags)
让调用进程移动到新的 Namespace 中
Run a program with some namespaces unshared from the parent .
1 2 3 4 5 6 7 8 9 10 11 12 # ls -l /proc/1/ns total 0 lrwxrwxrwx 1 root root 0 Mar 1 15:31 cgroup -> 'cgroup:[4026531835]' lrwxrwxrwx 1 root root 0 Mar 1 15:31 ipc -> 'ipc:[4026531839]' lrwxrwxrwx 1 root root 0 Feb 28 23:36 mnt -> 'mnt:[4026531840]' lrwxrwxrwx 1 root root 0 Mar 1 15:31 net -> 'net:[4026531992]' lrwxrwxrwx 1 root root 0 Feb 28 15:40 pid -> 'pid:[4026531836]' lrwxrwxrwx 1 root root 0 Mar 1 15:31 pid_for_children -> 'pid:[4026531836]' lrwxrwxrwx 1 root root 0 Mar 1 15:31 time -> 'time:[4026531834]' lrwxrwxrwx 1 root root 0 Mar 1 15:31 time_for_children -> 'time:[4026531834]' lrwxrwxrwx 1 root root 0 Mar 1 15:31 user -> 'user:[4026531837]' lrwxrwxrwx 1 root root 0 Mar 1 15:31 uts -> 'uts:[4026531838]'
分类
systemd Namespace 一般就是主机 Namespace
Type
Resource
Kernel
PID
进程
2.6.14
Network
网络设备、网络协议栈、网络端口
2.6.29
IPC
System V IPC 和 POSIX 消息队列
2.6.19
Mount
挂载点
2.4.19
UTS
主机名、域名
2.6.19
USR
用户、用户组
3.8
pid namespace
不同用户的进程 是通过 pid namespace 进行隔离,不同 namespace 中可以有相同 的 pid
每个 namespace 中的 pid 可以相互隔离
net namespace
网络隔离通过 net namespace 实现
每个 net namespace 有独立的 network devices、ip addresses、ip routing tables、/proc/net 等
Docker 默认采用 veth (Virtual Ethernet devices) 的方式
将 container 中的虚拟网卡 同 host 上的一个 docker bridge:docker0 连接在一起
ipc namespace
container 中的进程交互采用的还是 Linux 进程交互的方法:信号量、消息队列、共享内存等
container 的进程间交互实际上还是 host 上具有相同 pid namespace 中的进程间交互
在 ipc 资源申请时加入 pid namespace 的信息
每个 ipc 资源有一个唯一的32位ID
mnt namespace
允许不同 namespace 的进程看到不同的文件结构
uts namespace
UTS – UNIX Time-sharing System
允许每个 container 拥有独立的 hostname 和 domain name
使其在网络上被视为一个独立的节点 而非 host 上的一个进程
user namespace
每个 container 可以有不同的 user 和 group id
在 container 内部用 container 内部的用户 执行程序而非 host 上的用户
1 2 3 4 5 6 7 # ip a show docker0 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default link/ether 02:42:58:a4:13:33 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:58ff:fea4:1333/64 scope link valid_lft forever preferred_lft forever
container 中的 eth0@if13 与宿主机上的 docker0 连接在一起
1 2 3 4 5 # docker run -it --rm busybox ip a show eth0 12: eth0@if13: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever
操作
container id: afde0a1ff737, pid@host: 1771
1 2 3 4 5 # docker run -it --rm centos bash [root@8a213344c62b /]# # docker inspect --format '{{.State.Pid}}' 8a213344c62b 1771
1 2 3 4 5 6 7 8 9 10 # lsns NS TYPE NPROCS PID USER COMMAND 4026531834 time 227 1 root /sbin/init 4026531835 cgroup 227 1 root /sbin/init 4026531836 pid 226 1 root /sbin/init 4026531837 user 227 1 root /sbin/init 4026531838 uts 223 1 root /sbin/init 4026531839 ipc 226 1 root /sbin/init 4026531840 mnt 219 1 root /sbin/init ......
1 2 3 4 5 6 7 8 9 10 11 12 # ls -l /proc/1771/ns total 0 lrwxrwxrwx 1 root root 0 Mar 1 16:06 cgroup -> 'cgroup:[4026531835]' lrwxrwxrwx 1 root root 0 Mar 1 16:06 ipc -> 'ipc:[4026532646]' lrwxrwxrwx 1 root root 0 Mar 1 16:06 mnt -> 'mnt:[4026532644]' lrwxrwxrwx 1 root root 0 Mar 1 16:04 net -> 'net:[4026532649]' lrwxrwxrwx 1 root root 0 Mar 1 16:06 pid -> 'pid:[4026532647]' lrwxrwxrwx 1 root root 0 Mar 1 16:07 pid_for_children -> 'pid:[4026532647]' lrwxrwxrwx 1 root root 0 Mar 1 16:06 time -> 'time:[4026531834]' lrwxrwxrwx 1 root root 0 Mar 1 16:07 time_for_children -> 'time:[4026531834]' lrwxrwxrwx 1 root root 0 Mar 1 16:06 user -> 'user:[4026531837]' lrwxrwxrwx 1 root root 0 Mar 1 16:06 uts -> 'uts:[4026532645]'
nsenter
是常用调试 命令,适用场景:container 本身没有太多可用命令
1 2 3 4 5 6 7 8 9 10 11 # nsenter -t 1771 -n ip a show eth0 4: eth0@if5: <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 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever # docker exec 8a213344c62b ip a show eth0 4: eth0@if5: <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 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever
Cgroup
可配额
概念
Cgroup(Control Group):Linux 下对一个或者一组进程进行资源控制 的机制
不同资源的具体管理工作由相应的 Cgroup 子系统 来实现
针对不同类型的资源限制,只需要将限制策略 在不同的子系统 上进行关联 即可
Cgroup 在不同的系统资源管理子系统中以层级树 的方式来组织管理
每个 Cgroup 可以包含其他子 Cgroup,子 Cgroup 受到 父 Cgroup 设置的资源限制
Cgroup Driver
Cgroup 本身需要一个 driver
去实现(通过控制文件 去实现功能),systemd 在启动时会加载该文件系统
systemd
当操作系统使用 systemd 作为 init system 时,初始化进程会生成一个根 cgroup 目录 并作为 cgroup 管理器
systemd 与 cgroup 紧密结合,并且为每个 systemd unit 分配 cgroup
Docker
Docker 默认使用 cgroupfs 作为 cgroup driver
问题
在 systemd 作为 init system 的系统中,默认并存两套 cgroup driver ,管理混乱
因此,kubelet 会默认 --cgroup-driver=systemd
子系统
资源分类:可压缩资源(cpu等)、不可压缩资源(memory等)
子系统
描述
blkio
设置限制每个块设备的输入输出控制
cpu
控制一个进程可以使用多少 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
配置时间周期长度,单位为微秒
cpu.cfs_quota_us
配置当前 cgroup 在 cfs_period_us 时间内最多能使用的 CPU 时间数,单位是微秒
cpu.stat
cgroup 内的进程使用 CPU 的时间统计
nr_periods:经过 cpu.cfs_period_us 的时间周期数量
nr_throttled:在经过的周期内,进程因为在指定的时间周期内用光了配额时间而受到限制的次数
throttled_time:cgroup 中的进程被限制使用 CPU 的总用时,单位纳秒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 # pwd /sys/fs/cgroup # ll total 0 dr-xr-xr-x 11 root root 0 Mar 1 15:56 blkio lrwxrwxrwx 1 root root 11 Mar 1 15:56 cpu -> cpu,cpuacct lrwxrwxrwx 1 root root 11 Mar 1 15:56 cpuacct -> cpu,cpuacct dr-xr-xr-x 11 root root 0 Mar 1 15:56 cpu,cpuacct dr-xr-xr-x 3 root root 0 Mar 1 15:56 cpuset dr-xr-xr-x 11 root root 0 Mar 1 15:56 devices dr-xr-xr-x 4 root root 0 Mar 1 15:56 freezer dr-xr-xr-x 3 root root 0 Mar 1 15:56 hugetlb dr-xr-xr-x 11 root root 0 Mar 1 15:56 memory lrwxrwxrwx 1 root root 16 Mar 1 15:56 net_cls -> net_cls,net_prio dr-xr-xr-x 3 root root 0 Mar 1 15:56 net_cls,net_prio lrwxrwxrwx 1 root root 16 Mar 1 15:56 net_prio -> net_cls,net_prio dr-xr-xr-x 3 root root 0 Mar 1 15:56 perf_event dr-xr-xr-x 11 root root 0 Mar 1 15:56 pids dr-xr-xr-x 2 root root 0 Mar 1 15:56 rdma dr-xr-xr-x 12 root root 0 Mar 1 15:56 systemd dr-xr-xr-x 11 root root 0 Mar 1 15:56 unified # ls cpu cgroup.clone_children cpuacct.usage cpuacct.usage_percpu_user cpu.cfs_quota_us dev-mqueue.mount sys-fs-fuse-connections.mount system.slice cgroup.procs cpuacct.usage_all cpuacct.usage_sys cpu.shares docker sys-kernel-config.mount tasks cgroup.sane_behavior cpuacct.usage_percpu cpuacct.usage_user cpu.stat notify_on_release sys-kernel-debug.mount user.slice cpuacct.stat cpuacct.usage_percpu_sys cpu.cfs_period_us dev-hugepages.mount release_agent sys-kernel-tracing.mount
shares 1 2 3 4 5 6 7 8 # cat cpu/cpu.shares 1024 # cat cpu/docker/cpu.shares 1024 # cat cpu/system.slice/cpu.shares 1024
流量控制
cfs_period_us 和 cfs_quota_us 控制进程使用 CPU 的绝对值 cfs_period_us 的视角是单个 CPU
1 2 3 4 5 # cat cpu/cpu.cfs_period_us 100000 # cat cpu/cpu.cfs_quota_us -1
样例 1 2 3 4 # cd cpu && mkdir cpudemo && ls cpudemo cgroup.clone_children cpuacct.usage cpuacct.usage_percpu_sys cpuacct.usage_user cpu.shares cpu.uclamp.min cgroup.procs cpuacct.usage_all cpuacct.usage_percpu_user cpu.cfs_period_us cpu.stat notify_on_release cpuacct.stat cpuacct.usage_percpu cpuacct.usage_sys cpu.cfs_quota_us cpu.uclamp.max tasks
busy_loop.go 1 2 3 4 5 6 7 8 9 10 package mainfunc main () { go func () { for { } }() for { } }
1 2 3 4 5 6 7 8 # lscpu | grep 'CPU(s):' CPU(s): 2 NUMA node0 CPU(s): 0,1 # top ... PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 66313 root 20 0 702368 964 680 R 199.0 0.0 0:19.53 busy_loop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # echo 66313 > /sys/fs/cgroup/cpu/cpudemo/cgroup.procs # cat /sys/fs/cgroup/cpu/cpudemo/cpu.cfs_period_us 100000 # cat /sys/fs/cgroup/cpu/cpudemo/cpu.cfs_quota_us -1 # echo 100000 > /sys/fs/cgroup/cpu/cpudemo/cpu.cfs_quota_us # top ... PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 66313 root 20 0 702368 964 680 R 100.0 0.0 11:26.79 busy_loop
1 2 3 4 5 6 # echo 10000 > /sys/fs/cgroup/cpu/cpudemo/cpu.cfs_quota_us # top ... PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 66313 root 20 0 702368 964 680 R 9.9 0.0 13:05.55 busy_loop
1 2 3 4 # cat /sys/fs/cgroup/cpu/cpudemo/cpu.stat nr_periods 6432 nr_throttled 6431 throttled_time 1088304303102
cpuacct
容器技术是一系列技术的组合,Linux 本身是没有容器的概念的 ,因此容器内部执行 top 命令,看到的是宿主机的内容
配置项
描述
cpuacct.usage
包含该 cgroup 及其子 cgroup 下进程使用 CPU 的时间,单位纳秒
cpuacct.stat
包含该 cgroup 及其子 cgroup 下进程使用 CPU 的时间,以及用户态和内核态的时间
1 2 3 4 5 6 7 8 9 10 11 # ls /sys/fs/cgroup/cpu/cpudemo/cpuacct.* /sys/fs/cgroup/cpu/cpudemo/cpuacct.stat /sys/fs/cgroup/cpu/cpudemo/cpuacct.usage_percpu /sys/fs/cgroup/cpu/cpudemo/cpuacct.usage_sys /sys/fs/cgroup/cpu/cpudemo/cpuacct.usage /sys/fs/cgroup/cpu/cpudemo/cpuacct.usage_percpu_sys /sys/fs/cgroup/cpu/cpudemo/cpuacct.usage_user /sys/fs/cgroup/cpu/cpudemo/cpuacct.usage_all /sys/fs/cgroup/cpu/cpudemo/cpuacct.usage_percpu_user # cat /sys/fs/cgroup/cpu/cpudemo/cpuacct.stat user 68598 system 228 # cat /sys/fs/cgroup/cpu/cpudemo/cpuacct.usage 645376450311
memory
配置项
描述
memory.usage_in_bytes
cgroup 下进程使用的内存,包含 cgroup 及其子 cgroup 下的进程使用的内存
memory.max_usage_in_bytes
cgroup 下进程使用内存的最大值,包含子 cgroup 的内存使用量
memory.limit_in_bytes
设置 cgroup 下进程最多能使用的内存, -1 = 不限制
memory.soft_limit_in_bytes
这个限制并不会阻止进程使用超过限额的内存,只是在系统内存足够时,会优先回收超过限额的内存
memory.oom_control
设置是否在 cgroup 中使用 OOM Killer,默认为使用