### libcontainer版本1.2.0
### libcontainer源码可以在vendor中找到
一、libcontainer介绍
1. Docker的本质离不开Linux内核的很多高级特性;
2. Docker Daemon作为一个常驻进程,管理Client请求的同时,还管理所有的Docker容器;
Linux操作系统内核态对容器的管理,需要为用户态的Daemon服务,
如果在Daemon与Linux内核之间有一套完善的API接口,那么两者的衔接将变得尤为顺畅;
libcontainer的出现标志着内核API的出现。
3. libcontainer是一套实现容器的Go语言解决方案;
libcontainer实现过程中使用了Linux内核特性 namespace与 cgroup;
同时还采用了capability与文件权限控制等一系列技术;
基于这些特性,除了创建容器之外,libcontainer还可以完成容器生命周期的任务。
4. libcontainer为Docker容器提供了隔离、独立的环境;
隔离的效果是容器内部和外部的隔离;
libcontainer需要完成的工作正像容器内外一样,
既需要在容器外做容器的配置与管理,同时还需要再容器内部完成相应的初始化工作。
二、libcontianer模块分析
· Docker容器范畴内一切与Linux内核相关的技术实现,都可以认为是通过libcontainer来完成的;
· libcontainer指定了所有与容器相关的配置信息:
1. namespace;
2. cgroup;
3. 容器的网络栈配置信息;
4. 容器的挂载点配置、设备信息;
5. netlink (用于完成 内核态进程与 用户态进程的通信)
三、namespace
· namespace是libcontainer中最重要的一个模块;
在linux中创建进程一般会使用fork等系统调用; (fork 复制一个子进程)
/* 创建进程时,fork等系统调用完成子进程对父进程task_struct的复制,fork还可以传入和namespace的相关参数;
而Linux操作系统通过exec来启动子进程的运行。 */
回到容器的创建,由于容器的本质也是进程,所以在派生进程时,Docker Daemon传入了与namespace相关的flag参数,实现namespace的创建;
确保容器被执行之后,也就是在进程在运行时达到namespae隔离的效果。
以下是libcontainer需要的clone标志内核支持:
func init() {
namespaceList = Namespaces{
{Key: "NEWNS", Value: syscall.CLONE_NEWNS, File: "mnt"},
{Key: "NEWUTS", Value: syscall.CLONE_NEWUTS, File: "uts"},
{Key: "NEWIPC", Value: syscall.CLONE_NEWIPC, File: "ipc"},
{Key: "NEWUSER", Value: syscall.CLONE_NEWUSER, File: "user"},
{Key: "NEWPID", Value: syscall.CLONE_NEWPID, File: "pid"},
{Key: "NEWNET", Value: syscall.CLONE_NEWNET, File: "net"},
}
}
虽然namespace中加入了用户命名空间( CLONE_NEWUSER),但是libcontainer由于一些特殊原因却并未将其完全实现;
且网络命名空间( CLONE_NEWNET)的支持也要视用户对容器的需求而定,若用户指定容器的网络模式为host,则libcontainer不为容器创建网络命名空间。
namespace的完全生效,并不仅仅依靠namespace的创建,同时还需要对每一个namespace进行初始化;
如: CLONE_NEWNS (设置clone自己的命名空间)
CLONE_NEWNET (设置网络命名空间)
CLONE_NEWUTS (设置uts命名空间)
需要初始化信息参数,其他命名空间采用默认值。
· namespace的创建
Docker Daemon一方面从自身所在的namespace创建新的namespace供给容器使用;
另一方面在容器外部的namespace中,为容器分配所需的命名空间资源;
func Exec(container *libcontainer.Config, stdin io.Reader, stdout, stderr io.Writer, console string, rootfs, dataPath string, args []string, createCommand CreateCommand, startCallback func()) (int, error)
1. 创建管道用于将外面的进程( Docker Daemon)跨namespace传递给容器;
// syncPipe, err := syncpipe.NewSyncPipe()
2. 创建一个容器内部的可执行命令
// command := createCommand(container, console, rootfs, dataPath, os.Args[0], syncPipe.Child(), args)
3. 启动该命令实现namespace的初始化
// if err := command.Start(); err != nil {...}
4. 初始化设置Cgroup限制
// cgroupRef, err := SetupCgroups(container, command.Process.Pid)
5. 初始化net信息,再将net信息通过管道传递给容器
· namespace初始化
Docker Daemon有两种方法将资源配置信息传递到容器:
1. 创建时的管道;2. docker daemon持久化到宿主机的container.json文件;
容器中的第一个进程为dockerinit,dockerinit完成了容器自身namespace内部挂载资源、用户资源、网络资源的初始化工作;
func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syncPipe *syncpipe.SyncPipe, args []string) (err error)
· 总结:
容器的整个生命周期中,namespace是最基础的一块;
Docker容器的很多功能均在namespace基础上完成;
隔离的内部环境创建完毕之后,内部环境的初始化才能进行。
四、cgroup
· cgroup是Linux内核的一个特性,可以帮助用户对一组进程进行资源使用的控制、统计以及隔离;
Docker利用对cgroup的支持,完成对Docker容器进程组的资源限制、统计以及隔离。
· cgroup结构体:
type Cgroup struct {
Name string `json:"name,omitempty"`
Parent string `json:"parent,omitempty"` // name of parent cgroup or slice
// 设备device
AllowAllDevices bool `json:"allow_all_devices,omitempty"` // If this is true allow access to any kind of device within the container. If false, allow access only to devices explicitly listed in the allowed_devices list.
AllowedDevices []*devices.Device `json:"allowed_devices,omitempty"`
// 内存
Memory int64 `json:"memory,omitempty"` // Memory limit (in bytes)
MemoryReservation int64 `json:"memory_reservation,omitempty"` // Memory reservation or soft_limit (in bytes)
MemorySwap int64 `json:"memory_swap,omitempty"` // Total memory usage (memory + swap); set `-1' to disable swap
// CPU
CpuShares int64 `json:"cpu_shares,omitempty"` // CPU shares (relative weight vs. other containers)
CpuQuota int64 `json:"cpu_quota,omitempty"` // CPU hardcap limit (in usecs). Allowed cpu time in a given period.
CpuPeriod int64 `json:"cpu_period,omitempty"` // CPU period to be used for hardcapping (in usecs). 0 to use system default.
CpusetCpus string `json:"cpuset_cpus,omitempty"` // CPU to use
// Freezer
Freezer FreezerState `json:"freezer,omitempty"` // set the freeze value for the process
// systemd
Slice string `json:"slice,omitempty"` // Parent slice to use for systemd
}
· cgroup分析:
Docker对cgroup的支持主要有五点:
1. 设备(device): 通过 AllowAllDevices和 AllowedDevices字段来控制容器可以访问的设备;
AllowAllDevices == true表示可以访问所有设备,否则只能访问 AllowedDevices中的。
2. 内存(memory): 限制容器运行时的内存上限
3. CPU: 限制容器对CPU的使用时间、频率和占比
4. Freezer: 可以使容器挂起,节省CPU资源;
docker pause/unpause就是采用了cgroup的子系统;
容器进程组挂起,不意味着进程已经终止,
linux内核的角度来看,被挂起的进程仍然拥有完整的任务结构(task_struct),
但是Freezer保证了容器进程不会被CPU调度到。
5. Slice: 属于有systemd方面的配置
五、网络
· Docker容器的网络管理在底层都是借助libcontainer的network包完成的;
network包定义了Docker容器的网络栈类型,Docker容器的网络模式有:
1. bridge模式: 为容器配置虚拟网卡( veth)网络接口对,并配置loopback接口;
2. host模式: 不为容器创建网络命名空间;
3. other container模式: 不为容器创建网络命名空间,让容器与容器之间共享网络命名空间;
4. none模式: 为容器创建网络命名空间,配置loopback接口。
· libcontainer为了表示网络接口的数据接口,抽象出了network结构体:
type Network struct {
// 设置网络类型
Type string `json:"type,omitempty"`
// 代表容器网络命名空间
NsPath string `json:"ns_path,omitempty"`
// 代表容器网络使用的网桥设备名
Bridge string `json:"bridge,omitempty"`
// veth(虚拟网卡)的前缀
VethPrefix string `json:"veth_prefix,omitempty"`
// 容器网络接口的IP地址和子网掩码
Address string `json:"address,omitempty"`
// 用于设置网关地址,作为接口的默认地址
Gateway string `json:"gateway,omitempty"`
// 如果创建了接口对,代表容器网络接口的MTU值
// 特别是type == veth时
Mtu int `json:"mtu,omitempty"`
}
六、挂载
· Docker容器除了镜像提供的rootf,还会使用valume使得容器的文件系统可以共享主机的资源;
除了volume之外,容器的配置文件也是通过挂载的方法,使容器可以访问mount之外的空间;(hostname、hosts、resolv.conf)
· Mount的对象:
type Mount struct {
Type string `json:"type,omitempty"`
Source string `json:"source,omitempty"` // 源地址
Destination string `json:"destination,omitempty"` // 目的挂载地址
Writable bool `json:"writable,omitempty"`
Relabel string `json:"relabel,omitempty"` // z表示可共享,Z表示不可共享
Private bool `json:"private,omitempty"`
}
对于Docker Daemon而言,记录Mount的源地址以及目的地址是很重要的;
在此基础上还配置了一些属性配置,比如是否开启容器对Mount的写权限。
七、设备
· 对于容器来说,使用Linux内核管辖范围内的设备,是一个非常基本的需求;
libcontainer使用devices包来为Docker提供设备。 (特殊设备,存在于shell中)
· libcontainer中设备的定义:
type Device struct {
Type rune `json:"type,omitempty"`
Path string `json:"path,omitempty"` // It is fine if this is an empty string in the case that you are using Wildcards
MajorNumber int64 `json:"major_number,omitempty"` // Use the wildcard constant for wildcards.
MinorNumber int64 `json:"minor_number,omitempty"` // Use the wildcard constant for wildcards.
CgroupPermissions string `json:"cgroup_permissions,omitempty"` // Typically just "rwm"
FileMode os.FileMode `json:"file_mode,omitempty"` // The permission bits of the file's mode
}
· 默认情况下,会创建一些必备的设备:
1. /dev/null: 黑洞文件,用于重定向丢弃数据或者获取null值
2. /dev/zero: 用于覆盖信息或者填充空白文件
3. /dev/tty: 用于
4. /dev/urandom:随机数发生器,来源于设备噪声
5. /dev/console
6. /dev/full: 表示满文件,向里面写入时会报错
...
八、nsinit
· nsinit只需要一个roofs以及一个容器的配置文件container.conf就能创建一个容器;
· JSON文件container.json需要处于rootfs的根目录下,并且文件中含有的配置信息需要包括:
容器的环境变量、网络、容器Capability等内容
九、其他模块
· Netlink作为Linux的一套接口,提供进程间用户态与内核态之间的通信方式;
· security模块负责容器的安全方面,可以配置容器的Capability;
· Capability可以极大的限制用户进程的权限;
但是仍然有很多不能覆盖的方面,Docker的namespace没有完全实现用户命名空间,
因此Docker中的root和Linux的root时同一个用户;
Docker在尽量让root权限降到很小的情况下,还支持SELinux和Apparmor为容器提供安全
十、总结
· Docker的运行不能没有libcontainer,在libcontainer的基础之上,任何开发者都能再造类似的容器引擎;
· libcontainer中核心模块就是namespace和cgroup;
namespace主要提供了隔离性;
cgroup提供了性能限制
十一、附录(docker容器进程的层级关系)
1. 原始状态:
systemd(1)─┬─NetworkManager(990)─┬─{NetworkManager}(995)
│ └─{NetworkManager}(1001)
├─VGAuthService(917)
├─agetty(1028)
├─auditd(881)───{auditd}(882)
├─chronyd(915)
├─containerd(1025)─┬─{containerd}(1263)
│ ├─{containerd}(1264)
│ ├─{containerd}(1265)
│ ├─{containerd}(1267)
│ ├─{containerd}(1268)
│ ├─{containerd}(1365)
│ ├─{containerd}(1366)
│ ├─{containerd}(1367)
│ └─{containerd}(1373)
├─crond(1020)
├─dbus-daemon(907)───{dbus-daemon}(938)
├─dockerd(3691)─┬─{dockerd}(3692)
│ ├─{dockerd}(3693)
│ ├─{dockerd}(3694)
│ ├─{dockerd}(3695)
│ ├─{dockerd}(3696)
│ ├─{dockerd}(3697)
│ ├─{dockerd}(3698)
│ ├─{dockerd}(3699)
│ ├─{dockerd}(3700)
│ ├─{dockerd}(3701)
│ └─{dockerd}(3702)
...
2. run一个tomcat容器
systemd(1)─┬─NetworkManager(990)─┬─{NetworkManager}(995)
│ └─{NetworkManager}(1001)
├─VGAuthService(917)
├─agetty(1028)
├─auditd(881)───{auditd}(882)
├─chronyd(915,chrony)
├─containerd(1025)─┬─{containerd}(1263)
│ ├─{containerd}(1264)
│ ├─{containerd}(1265)
│ ├─{containerd}(1267)
│ ├─{containerd}(1268)
│ ├─{containerd}(1365)
│ ├─{containerd}(1366)
│ ├─{containerd}(1367)
│ └─{containerd}(1373)
├─containerd-shim(2775)─┬─java(2794)─┬─{java}(2828)
│ │ ├─{java}(2829)
│ │ ├─{java}(2830)
│ │ ├─{java}(2831)
│ │ ├─{java}(2832)
│ │ ├─{java}(2833)
│ │ ├─{java}(2834)
│ │ ├─{java}(2835)
│ │ ├─{java}(2836)
│ │ ├─{java}(2837)
│ │ ├─{java}(2838)
│ │ ├─{java}(2848)
│ │ ├─{java}(2849)
│ │ ├─{java}(2850)
│ │ ├─{java}(2851)
│ │ ├─{java}(2852)
│ │ ├─{java}(2853)
│ │ ├─{java}(2854)
│ │ ├─{java}(2855)
│ │ ├─{java}(2856)
│ │ ├─{java}(2857)
│ │ ├─{java}(2858)
│ │ ├─{java}(2859)
│ │ ├─{java}(2860)
│ │ ├─{java}(2861)
│ │ ├─{java}(2862)
│ │ ├─{java}(2863)
│ │ ├─{java}(2864)
│ │ ├─{java}(2865)
│ │ ├─{java}(2866)
│ │ ├─{java}(2867)
│ │ ├─{java}(2868)
│ │ ├─{java}(2869)
│ │ ├─{java}(2870)
│ │ ├─{java}(2871)
│ │ ├─{java}(2872)
│ │ ├─{java}(2873)
│ │ ├─{java}(2874)
│ │ └─{java}(2875)
│ ├─{containerd-shim}(2776)
│ ├─{containerd-shim}(2777)
│ ├─{containerd-shim}(2778)
│ ├─{containerd-shim}(2779)
│ ├─{containerd-shim}(2780)
│ ├─{containerd-shim}(2781)
│ ├─{containerd-shim}(2782)
│ ├─{containerd-shim}(2783)
│ ├─{containerd-shim}(2817)
│ └─{containerd-shim}(2818)
├─crond(1020)
├─dbus-daemon(907,dbus)───{dbus-daemon}(938)
├─dockerd(1374)─┬─docker-proxy(2754)─┬─{docker-proxy}(2755)
│ │ ├─{docker-proxy}(2756)
│ │ ├─{docker-proxy}(2757)
│ │ ├─{docker-proxy}(2758)
│ │ ├─{docker-proxy}(2759)
│ │ └─{docker-proxy}(2760)
│ ├─docker-proxy(2761)─┬─{docker-proxy}(2762)
│ │ ├─{docker-proxy}(2763)
│ │ ├─{docker-proxy}(2764)
│ │ ├─{docker-proxy}(2765)
│ │ └─{docker-proxy}(2766)
│ ├─{dockerd}(1439)
│ ├─{dockerd}(1440)
│ ├─{dockerd}(1441)
│ ├─{dockerd}(1442)
│ ├─{dockerd}(1443)
│ ├─{dockerd}(1475)
│ ├─{dockerd}(1497)
│ └─{dockerd}(1498)
...
3. 再run一个tomcat容器
systemd(1)─┬─NetworkManager(990)─┬─{NetworkManager}(995)
│ └─{NetworkManager}(1001)
├─VGAuthService(917)
├─agetty(1028)
├─auditd(881)───{auditd}(882)
├─chronyd(915,chrony)
├─containerd(1025)─┬─{containerd}(1263)
│ ├─{containerd}(1264)
│ ├─{containerd}(1265)
│ ├─{containerd}(1267)
│ ├─{containerd}(1268)
│ ├─{containerd}(1365)
│ ├─{containerd}(1366)
│ ├─{containerd}(1367)
│ └─{containerd}(1373)
├─containerd-shim(2775)─┬─java(2794)─┬─{java}(2828)
│ │ ├─{java}(2829)
│ │ ├─{java}(2830)
│ │ ├─{java}(2831)
│ │ ├─{java}(2832)
│ │ ├─{java}(2833)
│ │ ├─{java}(2834)
│ │ ├─{java}(2835)
│ │ ├─{java}(2836)
│ │ ├─{java}(2837)
│ │ ├─{java}(2838)
│ │ ├─{java}(2848)
│ │ ├─{java}(2849)
│ │ ├─{java}(2850)
│ │ ├─{java}(2851)
│ │ ├─{java}(2852)
│ │ ├─{java}(2853)
│ │ ├─{java}(2854)
│ │ ├─{java}(2855)
│ │ ├─{java}(2856)
│ │ ├─{java}(2857)
│ │ ├─{java}(2858)
│ │ ├─{java}(2859)
│ │ ├─{java}(2860)
│ │ ├─{java}(2861)
│ │ ├─{java}(2862)
│ │ ├─{java}(2863)
│ │ ├─{java}(2864)
│ │ ├─{java}(2865)
│ │ ├─{java}(2866)
│ │ ├─{java}(2867)
│ │ ├─{java}(2868)
│ │ ├─{java}(2869)
│ │ ├─{java}(2870)
│ │ ├─{java}(2871)
│ │ ├─{java}(2872)
│ │ ├─{java}(2873)
│ │ ├─{java}(2874)
│ │ └─{java}(2875)
│ ├─{containerd-shim}(2776)
│ ├─{containerd-shim}(2777)
│ ├─{containerd-shim}(2778)
│ ├─{containerd-shim}(2779)
│ ├─{containerd-shim}(2780)
│ ├─{containerd-shim}(2781)
│ ├─{containerd-shim}(2782)
│ ├─{containerd-shim}(2783)
│ ├─{containerd-shim}(2817)
│ └─{containerd-shim}(2818)
├─containerd-shim(3131)─┬─java(3150)─┬─{java}(3182)
│ │ ├─{java}(3183)
│ │ ├─{java}(3184)
│ │ ├─{java}(3185)
│ │ ├─{java}(3186)
│ │ ├─{java}(3187)
│ │ ├─{java}(3188)
│ │ ├─{java}(3189)
│ │ ├─{java}(3190)
│ │ ├─{java}(3191)
│ │ ├─{java}(3192)
│ │ ├─{java}(3193)
│ │ ├─{java}(3194)
│ │ ├─{java}(3195)
│ │ ├─{java}(3196)
│ │ ├─{java}(3197)
│ │ ├─{java}(3198)
│ │ ├─{java}(3199)
│ │ ├─{java}(3200)
│ │ ├─{java}(3201)
│ │ ├─{java}(3202)
│ │ ├─{java}(3203)
│ │ ├─{java}(3204)
│ │ ├─{java}(3205)
│ │ ├─{java}(3206)
│ │ ├─{java}(3207)
│ │ ├─{java}(3208)
│ │ ├─{java}(3209)
│ │ ├─{java}(3210)
│ │ ├─{java}(3211)
│ │ ├─{java}(3212)
│ │ ├─{java}(3213)
│ │ ├─{java}(3214)
│ │ ├─{java}(3215)
│ │ ├─{java}(3216)
│ │ ├─{java}(3217)
│ │ ├─{java}(3218)
│ │ ├─{java}(3219)
│ │ ├─{java}(3220)
│ │ ├─{java}(3221)
│ │ └─{java}(3222)
│ ├─{containerd-shim}(3132)
│ ├─{containerd-shim}(3133)
│ ├─{containerd-shim}(3134)
│ ├─{containerd-shim}(3135)
│ ├─{containerd-shim}(3136)
│ ├─{containerd-shim}(3137)
│ ├─{containerd-shim}(3138)
│ ├─{containerd-shim}(3139)
│ └─{containerd-shim}(3172)
├─crond(1020)
├─dbus-daemon(907,dbus)───{dbus-daemon}(938)
├─dockerd(1374)─┬─docker-proxy(2754)─┬─{docker-proxy}(2755)
│ │ ├─{docker-proxy}(2756)
│ │ ├─{docker-proxy}(2757)
│ │ ├─{docker-proxy}(2758)
│ │ ├─{docker-proxy}(2759)
│ │ └─{docker-proxy}(2760)
│ ├─docker-proxy(2761)─┬─{docker-proxy}(2762)
│ │ ├─{docker-proxy}(2763)
│ │ ├─{docker-proxy}(2764)
│ │ ├─{docker-proxy}(2765)
│ │ └─{docker-proxy}(2766)
│ ├─docker-proxy(3110)─┬─{docker-proxy}(3111)
│ │ ├─{docker-proxy}(3112)
│ │ ├─{docker-proxy}(3113)
│ │ ├─{docker-proxy}(3114)
│ │ ├─{docker-proxy}(3115)
│ │ ├─{docker-proxy}(3117)
│ │ └─{docker-proxy}(3118)
│ ├─docker-proxy(3116)─┬─{docker-proxy}(3119)
│ │ ├─{docker-proxy}(3120)
│ │ ├─{docker-proxy}(3121)
│ │ ├─{docker-proxy}(3122)
│ │ └─{docker-proxy}(3123)
│ ├─{dockerd}(1439)
│ ├─{dockerd}(1440)
│ ├─{dockerd}(1441)
│ ├─{dockerd}(1442)
│ ├─{dockerd}(1443)
│ ├─{dockerd}(1475)
│ ├─{dockerd}(1497)
│ └─{dockerd}(1498)
...