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)))}
0 0