docker源码学习-docker run 的具体实现(2 )
来源:互联网 发布:软件著作权税务备案 编辑:程序博客网 时间:2024/05/18 02:15
在第一博文中看的docker deamon的启动过程是如何实现的,这篇博文主要看的是docker run 这个命令启一个容器是如何实现的。docker大多数的命令对应的处理方法都在runtime .go这个文件中。但是run方法的实现在container.go文件中,接下来我们来看看这个run这个方法是如何实现的。
func (container *Container) Run() error { if err := container.Start(); err != nil { return err } container.Wait() return nil}
func (container *Container) Start() error { if err := container.EnsureMounted(); err != nil { return err } if err := container.allocateNetwork(); err != nil { return err } if err := container.generateLXCConfig(); err != nil { return err } params := []string{ "-n", container.Id, "-f", container.lxcConfigPath(), "--", "/sbin/init", } // Networking params = append(params, "-g", container.network.Gateway.String()) // User if container.Config.User != "" { params = append(params, "-u", container.Config.User) } // Program params = append(params, "--", container.Path) params = append(params, container.Args...) container.cmd = exec.Command("/usr/bin/lxc-start", params...) // Setup environment container.cmd.Env = append( []string{ "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", }, container.Config.Env..., ) var err error if container.Config.Tty { err = container.startPty() } else { err = container.start() } if err != nil { return err } // FIXME: save state on disk *first*, then converge // this way disk state is used as a journal, eg. we can restore after crash etc. container.State.setRunning(container.cmd.Process.Pid) container.ToDisk() go container.monitor() return nil}
start()方法中第一步先检查是否有mount的目录挂在,如果有则进行挂载,如果已经挂载过,则不操作,返回,如果没有挂载过,则进行相应的挂载。然后是分配容器的网络,之后根据lxc的配置文件模板生成lxc启动的配置文件。然后组装lxc启动的命令行参数,并且指定容器的cmd为lxc的启动命令,在指定lxc经常的环境,判断是否需要将容器的输出,输出到标准输出终端tty,最后启动容器,如果没有错误,则改变内存中container(实际上是runtime中的container list中的容器)的状态为running,将相应的将容器信息写入到/var/lib/docker/containers/容器id/config.json文件中。然后监控容器(其实就是监控容器这个进程是否退出,退出,则释放容器配置的网络,解除容器的挂载目录,然后更新/var/lib/docker/containers/容器id/config.json文件)
之后,我们来分别看看容器挂载和网络的创建,是如何实现的。
磁盘挂载实现:
func (container *Container) EnsureMounted() error { if mounted, err := container.Mounted(); err != nil { return err } else if mounted { return nil } return container.Mount()}
首先检查是否挂载,如果挂载成功,则返回。具体的挂载的方法如下:
func (container *Container) Mounted() (bool, error) { return Mounted(container.RootfsPath())}
func Mounted(mountpoint string) (bool, error) { mntpoint, err := os.Stat(mountpoint) if err != nil { if os.IsNotExist(err) { return false, nil } return false, err } parent, err := os.Stat(filepath.Join(mountpoint, "..")) if err != nil { return false, err } mntpointSt := mntpoint.Sys().(*syscall.Stat_t) parentSt := parent.Sys().(*syscall.Stat_t) return mntpointSt.Dev != parentSt.Dev, nil}
挂载的过程:首先检查/var/lib/docker/containerId/rootfs是否存在,如果不存在则返回,表示容器没有挂载,在检查父目录/var/lib/docker/containerId是否存在,最后检查父目录和子目录是否是同一个设备id。
如果没有mount,则进行mout操作:
func (container *Container) Mount() error { image, err := container.GetImage() if err != nil { return err } return image.Mount(container.RootfsPath(), container.rwPath())}
func (image *Image) Mount(root, rw string) error { if mounted, err := Mounted(root); err != nil { return err } else if mounted { return fmt.Errorf("%s is already mounted", root) } layers, err := image.layers() if err != nil { return err } // Create the target directories if they don't exist if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) { return err } if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { return err } // FIXME: @creack shouldn't we do this after going over changes? if err := MountAUFS(layers, rw, root); err != nil { return err } // FIXME: Create tests for deletion // FIXME: move this part to change.go // Retrieve the changeset from the parent and apply it to the container // - Retrieve the changes changes, err := Changes(layers, layers[0]) if err != nil { return err } // Iterate on changes for _, c := range changes { // If there is a delete if c.Kind == ChangeDelete { // Make sure the directory exists file_path, file_name := path.Dir(c.Path), path.Base(c.Path) if err := os.MkdirAll(path.Join(rw, file_path), 0755); err != nil { return err } // And create the whiteout (we just need to create empty file, discard the return) if _, err := os.Create(path.Join(path.Join(rw, file_path), ".wh."+path.Base(file_name))); err != nil { return err } } } return nil}
检查是否已经挂载过,挂载过则直接返回信息:已经挂载过。否则进行相关的挂载,挂载过程如下:首先获取镜像的只读层,根据传入的参数创建读写层目录:/var/lib/docker/containerId/rootfs/rw文件夹,然后进行auft的mout操作:其实就是将/var/lib/docker/containerId/rootfs/rw和对应的image的只读成关联上,也就是在只读层上加一层读写层,最后syscall.mount()实现mout操作,syscall.mount的实现也是通过调用系统指令SYS_ACCT实现的。
func MountAUFS(ro []string, rw string, target string) error { // FIXME: Now mount the layers rwBranch := fmt.Sprintf("%v=rw", rw) roBranches := "" for _, layer := range ro { roBranches += fmt.Sprintf("%v=ro:", layer) } branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches) return mount("none", target, "aufs", 0, branches)}func mount(source string, target string, fstype string, flags uintptr, data string) (err error) { return syscall.Mount(source, target, fstype, flags, data)}
接下来说网络的实现:网络的实现其实相对简单,因为在上一篇博文中详细介绍了networkManage这个struct的实例化过程,以及其中的字段。实现过程如下:
func (container *Container) allocateNetwork() error { iface, err := container.runtime.networkManager.Allocate() if err != nil { return err } container.NetworkSettings.PortMapping = make(map[string]string) for _, port := range container.Config.Ports { if extPort, err := iface.AllocatePort(port); err != nil { iface.Release() return err } else { container.NetworkSettings.PortMapping[strconv.Itoa(port)] = strconv.Itoa(extPort) } } container.network = iface container.NetworkSettings.IpAddress = iface.IPNet.IP.String() container.NetworkSettings.IpPrefixLen, _ = iface.IPNet.Mask.Size() container.NetworkSettings.Gateway = iface.Gateway.String() return nil}
func (manager *NetworkManager) Allocate() (*NetworkInterface, error) { ip, err := manager.ipAllocator.Acquire() if err != nil { return nil, err } iface := &NetworkInterface{ IPNet: net.IPNet{IP: ip, Mask: manager.bridgeNetwork.Mask}, Gateway: manager.bridgeNetwork.IP, manager: manager, } return iface, nil}
func (alloc *IPAllocator) Acquire() (net.IP, error) { select { case ip := <-alloc.queue: return ip, nil default: return net.IP{}, errors.New("No more IP addresses available") } return net.IP{}, nil}
首先从runtime的networkManager获取一个网络接口(实际的处理过程:从networkManager中的ip分配器中获取一个ip,用networkmanage的mask,Gateway新建一个网卡),然后根据container中的指定的端口来做端口映射(实际处理过程:从networkManager中的端口分配器中娶一个端口,然后做port的iptable规则,如果出错则端口分配器回收端口,最后将容器的映射端口存入容器的export数组中)
端口映射的处理:
func (iface *NetworkInterface) AllocatePort(port int) (int, error) { extPort, err := iface.manager.portAllocator.Acquire() if err != nil { return -1, err } if err := iface.manager.portMapper.Map(extPort, net.TCPAddr{IP: iface.IPNet.IP, Port: port}); err != nil { iface.manager.portAllocator.Release(extPort) return -1, err } iface.extPorts = append(iface.extPorts, extPort) return extPort, nil}
func (alloc *PortAllocator) Acquire() (int, error) { select { case port := <-alloc.ports: return port, nil default: return -1, errors.New("No more ports available") } return -1, nil}
func (mapper *PortMapper) Map(port int, dest net.TCPAddr) error { if err := mapper.iptablesForward("-A", port, dest); err != nil { return err } mapper.mapping[port] = dest return nil}
func (mapper *PortMapper) iptablesForward(rule string, port int, dest net.TCPAddr) error { return iptables("-t", "nat", rule, "DOCKER", "-p", "tcp", "--dport", strconv.Itoa(port), "-j", "DNAT", "--to-destination", net.JoinHostPort(dest.IP.String(), strconv.Itoa(dest.Port)))}
- docker源码学习-docker run 的具体实现(2 )
- [docker]docker命令docker run的小结
- docker源码学习-docker daemon(1 )
- Docker学习笔记(2)-- Docker常用命令
- Docker源码分析(四):Docker Daemon之NewDaemon实现
- Docker源码分析(四):Docker Daemon之NewDaemon实现
- Docker源码分析(四):Docker Daemon之NewDaemon实现
- Docker run 命令的使用方法
- Docker run 命令的使用方法
- Docker run命令的使用方法
- docker run的时候参数
- docker run的各种参数
- Docker run 命令的使用方法
- Docker run 命令的使用方法
- docker学习(2)--docker镜像、容器、仓库和docker的安装
- docker源码学习-main
- docker deamon源码学习
- 【docker 17 源码分析】 docker run container 源码分析二 docker start
- Django runserver错误
- git获取远程主机分支
- [Leetcode]_1 Two Sum
- 第七章 函数——C++得编程模块
- mapper代理方法开发mybatis
- docker源码学习-docker run 的具体实现(2 )
- HDU 1301 Jungle Roads
- ZOJ 1610 Count the Colors(线段树 区间覆盖)
- Oracle listagg 函数
- node.js&pm2搭建node生产环境
- 使用form表单中自带的fieldset与legend
- python dir()和vars()的区别
- Android 屏幕适配方案
- jQuery的ajax详解