理解docker,主要从namesapce,cgroups,联合文件,运行时(runC),网络几个方面。接下来咱们会花一些时间,分别介绍。node
namesapce主要是隔离做用,cgroups主要是资源限制,联合文件主要用于镜像分层存储和管理,runC是运行时,遵循了oci接口,通常来讲基于libcontainer。网络主要是docker单机网络和多主机通讯模式。linux
Cgroup是control group的简写,属于Linux内核提供的一个特性,用于限制和隔离一组进程对系统资源的使用,也就是作资源QoS,这些资源主要包括CPU、内存、block I/O和网络带宽。Cgroup从2.6.24开始进入内核主线,目前各大发行版都默认打开了Cgroup特性。
Cgroups提供了如下四大功能:git
cgroup中实现的子系统及其做用以下:github
每一个子系统的目录下有更详细的设置项,例如:
cpu
CPU资源的控制也有两种策略,一种是彻底公平调度 (CFS:Completely Fair Scheduler)策略,提供了限额和按比例分配两种方式进行资源控制;另外一种是实时调度(Real-Time Scheduler)策略,针对实时进程按周期分配固定的运行时间。配置时间都以微秒(µs)为单位,文件名中用us表示。docker
cpuset CPU绑定:
除了限制 CPU 的使用量,cgroup 还能把任务绑定到特定的 CPU,让它们只运行在这些 CPU 上,这就是 cpuset 子资源的功能。除了 CPU 以外,还能绑定内存节点(memory node)。
在把任务加入到 cpuset 的 task 文件以前,用户必须设置 cpuset.cpus 和 cpuset.mems 参数。json
memory:
segmentfault
这里专门讲一下监控和统计相关的参数,好比cadvisor采集的那些参数。网络
# Run a container that will spawn 300 processes. docker run cirocosta/stress pid -n 300 Starting to spawn 300 blocking children [1] Waiting for SIGINT # Open another window and see that we have 300 # PIDS docker stats CONTAINER … MEM USAGE / LIMIT PIDS a730051832 … 21.02MiB / 1.951GiB 300
# let's get the ID of the container. Docker uses that ID # to name things in the host to we can probably use it to # find the cgroup created for the container # under the parent docker cgroup docker ps CONTAINER ID IMAGE COMMAND a730051832e7 cirocosta/stress "pid -n 300" # Having the prefix in hands, let's search for it under the # mountpoint for cgroups in our system find /sys/fs/cgroup/ -name "a730051832e7*" /sys/fs/cgroup/cpu,cpuacct/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959 /sys/fs/cgroup/cpuset/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959 /sys/fs/cgroup/devices/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959 /sys/fs/cgroup/pids/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959 /sys/fs/cgroup/freezer/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959 /sys/fs/cgroup/perf_event/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959 /sys/fs/cgroup/blkio/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959 /sys/fs/cgroup/memory/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959 /sys/fs/cgroup/net_cls,net_prio/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959 /sys/fs/cgroup/hugetlb/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959 /sys/fs/cgroup/systemd/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959 # There they are! Docker creates a control group with the name # being the exact ID of the container under all the subsystems. # What can we discover from this inspection? We can look at the # subsystem that we want to place contrainst on (PIDs), for instance: tree /sys/fs/cgroup/pids/docker/a7300518327d... /sys/fs/cgroup/pids/docker/a73005183... ├── cgroup.clone_children ├── cgroup.procs ├── notify_on_release ├── pids.current ├── pids.events ├── pids.max └── tasks # Which means that, if we want to know how many PIDs are in use right # now we can look at 'pids.current', to know the limits, 'pids.max' and # to know which processes have been assigned to this control group, # look at tasks. Lets do it: cat /sys/fs/cgroup/pids/docker/a730...c660a75a959/tasks 5329 5371 5372 5373 5374 5375 5376 5377 (...) # continues until the 300th entry - as we have 300 processes in this container # 300 pids cat /sys/fs/cgroup/pids/docker/a730051832e7d7764...9/pids.current 300 # no max set cat /sys/fs/cgroup/pids/docker/a730051832e7d77.../pids.max max
通常在安装k8s的过程当中常常会遇到以下错误:ide
create kubelet: misconfiguration: kubelet cgroup driver: "cgroupfs" is different from docker cgroup driver: "systemd"
其实此处错误信息已经很明白了,就是docker 和kubelet指定的cgroup driver不同。 docker
支持systemd和cgroupfs两种驱动方式。经过runc代码能够更加直观了解。
工具
关于cgroups在runc的代码部分,你们能够点击进去详细阅读。这边咱们只讲一个大概。
首先container的建立是由factory调用create方法实现的,而cgroup相关,factory实现了根据配置文件cgroup drive驱动的配置项,新建CgroupsManager的方法,systemd和cgroupfs两种实现方式:
// SystemdCgroups is an options func to configure a LinuxFactory to return // containers that use systemd to create and manage cgroups. func SystemdCgroups(l *LinuxFactory) error { l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager { return &systemd.Manager{ Cgroups: config, Paths: paths, } } return nil } // Cgroupfs is an options func to configure a LinuxFactory to return containers // that use the native cgroups filesystem implementation to create and manage // cgroups. func Cgroupfs(l *LinuxFactory) error { l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager { return &fs.Manager{ Cgroups: config, Paths: paths, } } return nil }
抽象cgroup manager接口。接口以下:
type Manager interface { // Applies cgroup configuration to the process with the specified pid Apply(pid int) error // Returns the PIDs inside the cgroup set GetPids() ([]int, error) // Returns the PIDs inside the cgroup set & all sub-cgroups GetAllPids() ([]int, error) // Returns statistics for the cgroup set GetStats() (*Stats, error) // Toggles the freezer cgroup according with specified state Freeze(state configs.FreezerState) error // Destroys the cgroup set Destroy() error // The option func SystemdCgroups() and Cgroupfs() require following attributes: // Paths map[string]string // Cgroups *configs.Cgroup // Paths maps cgroup subsystem to path at which it is mounted. // Cgroups specifies specific cgroup settings for the various subsystems // Returns cgroup paths to save in a state file and to be able to // restore the object later. GetPaths() map[string]string // Sets the cgroup as configured. Set(container *configs.Config) error }
在建立container的过程当中,会调用上面接口的方法。例如:
在container_linux.go中,
func (c *linuxContainer) Set(config configs.Config) error { c.m.Lock() defer c.m.Unlock() status, err := c.currentStatus() if err != nil { return err } ... if err := c.cgroupManager.Set(&config); err != nil { // Set configs back if err2 := c.cgroupManager.Set(c.config); err2 != nil { logrus.Warnf("Setting back cgroup configs failed due to error: %v, your state.json and actual configs might be inconsistent.", err2) } return err } ... }
接下来咱们重点讲一下fs的实现。
在fs中,基本上每一个子系统都是一个文件,如上图。
重点说一下memory.go,即memory子系统,其余子系统与此相似。
关键方法:
func (s *MemoryGroup) Apply(d *cgroupData) (err error) { path, err := d.path("memory") if err != nil && !cgroups.IsNotFound(err) { return err } else if path == "" { return nil } if memoryAssigned(d.config) { if _, err := os.Stat(path); os.IsNotExist(err) { if err := os.MkdirAll(path, 0755); err != nil { return err } // Only enable kernel memory accouting when this cgroup // is created by libcontainer, otherwise we might get // error when people use `cgroupsPath` to join an existed // cgroup whose kernel memory is not initialized. if err := EnableKernelMemoryAccounting(path); err != nil { return err } } } defer func() { if err != nil { os.RemoveAll(path) } }() // We need to join memory cgroup after set memory limits, because // kmem.limit_in_bytes can only be set when the cgroup is empty. _, err = d.join("memory") if err != nil && !cgroups.IsNotFound(err) { return err } return nil }
func (raw *cgroupData) path(subsystem string) (string, error) { mnt, err := cgroups.FindCgroupMountpoint(subsystem) // If we didn't mount the subsystem, there is no point we make the path. if err != nil { return "", err } // If the cgroup name/path is absolute do not look relative to the cgroup of the init process. if filepath.IsAbs(raw.innerPath) { // Sometimes subsystems can be mounted together as 'cpu,cpuacct'. return filepath.Join(raw.root, filepath.Base(mnt), raw.innerPath), nil } // Use GetOwnCgroupPath instead of GetInitCgroupPath, because the creating // process could in container and shared pid namespace with host, and // /proc/1/cgroup could point to whole other world of cgroups. parentPath, err := cgroups.GetOwnCgroupPath(subsystem) if err != nil { return "", err } return filepath.Join(parentPath, raw.innerPath), nil }
func (raw *cgroupData) join(subsystem string) (string, error) { path, err := raw.path(subsystem) if err != nil { return "", err } if err := os.MkdirAll(path, 0755); err != nil { return "", err } if err := cgroups.WriteCgroupProc(path, raw.pid); err != nil { return "", err } return path, nil }