docker源码学习-docker daemon(1 )

来源:互联网 发布:数据透视表里求和项 编辑:程序博客网 时间:2024/06/06 20:15

谈到docker源码,其实网上有很多的源码的分析的文章,也看过一些大牛写的docker源码解读的文章,收获很大。我之前也想去看docker的源码,但是当我把源码下载下来一看,源码太多了,不知道该从何处下手,看一个功能点的代码,看完之后只知道个大概,不久就忘记的一干二净,在加上docker现在版本更新飞速,所以就更跟不上那个脚步了。但是我又一直想看看docker的源码,所以,我就想了个办法,既然你版本更新太快,那我就从你docker的第一个版本看起,一看总共才20几个go文件,顿时压力没有那么大了。。。

docker版本:v0.1.0

总结:docker v0.1.0版本完全是基于lxc来实现的。

docker 启动
先检查docker可执行文件的绝对路径是否在/sbin/init目录下已经存在
如果在,则设置docker容器启动之前的环境
做如下操作:设置网络:添加路由,切换用户,启动docker程序

func main() {    if docker.SelfPath() == "/sbin/init" {        // Running in init mode        docker.SysInit()        return    }    // FIXME: Switch d and D ? (to be more sshd like)    fl_daemon := flag.Bool("d", false, "Daemon mode")    fl_debug := flag.Bool("D", false, "Debug mode")    flag.Parse()    rcli.DEBUG_FLAG = *fl_debug    if *fl_daemon {        if flag.NArg() != 0 {            flag.Usage()            return        }        if err := daemon(); err != nil {            log.Fatal(err)        }    } else {        if err := runCommand(flag.Args()); err != nil {            log.Fatal(err)        }    }}

如果不存在则根据参入的命令行参数:去选择是启动docker deamon 还是执行 docker cli 的命令调用

如果是deamon (-d)则检查是否有多余的参数,如果有则退出,显示帮助信息
如果没有则启动docker deamon,在来看看docker deamon 启动过程,具体干了些什么事情。

看看daemon的实现:
主要是创建一个server对象, 然后通过这个server创建tcp服务端:其中最主要的是 在这个是在server的创建,创建过程比较复杂点,然后是在服务启动tcp监听哪里,用到了反射技术,通过把对用的docker 的cmd 命令和对应的server的方法对应上,然后通过方法名称获取对应的方法执行对应命令的方法,从而相应对应的tcp客户端发送的命令

创建server的详细过程归纳如下:创建server实质就是创建runtime对象,runtime对象中封装了所有docker daemon运行时所需要的所有的信息,在创建runtime时,首先会在 /var/lib/docker目录下创建对应的文件:containers,graph文件夹,然后创建对应的镜像tag存储对象,通过名为lxcbr0的卡的网络创建网络管理,最后创建dockerhub的认证对象AuthConfig,至此server对象创建完毕。其中最复杂的就是网络管理的创建。

下面我们来先看看一个完整的server对象的创建,到底干了些什么事情;

func daemon() error {    service, err := docker.NewServer()    if err != nil {        return err    }    return rcli.ListenAndServe("tcp", "127.0.0.1:4242", service)}

server的创建

func NewServer() (*Server, error) {    rand.Seed(time.Now().UTC().UnixNano())    //检查程序运行的arch是否是amd64    if runtime.GOARCH != "amd64" {        log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)    }    //新建运行时环境;这是重点,runtime中实现的docker的所有命令行中所有命令的api。    runtime, err := NewRuntime()    if err != nil {        return nil, err    }    srv := &Server{        runtime: runtime,    }    return srv, nil}

对应的NewRuntime()的方法:

func NewRuntime() (*Runtime, error) {    return NewRuntimeFromDirectory("/var/lib/docker")}

对应的NewRuntimeFromDirectory()的方法:

func NewRuntimeFromDirectory(root string) (*Runtime, error) {    //创建/var/lib/docker/containers文件夹    runtime_repo := path.Join(root, "containers")    if err := os.MkdirAll(runtime_repo, 0700); err != nil && !os.IsExist(err) {        return nil, err    }//这个判断的意思是:创建这个文件夹,如果报错,并且错误信息不是 文件见已经存在,则返回    //创建/var/lib/docker/graph目录,同事创建Graph对象    g, err := NewGraph(path.Join(root, "graph"))    if err != nil {        return nil, err    }    /*        func NewGraph(root string) (*Graph, error) {        abspath, err := filepath.Abs(root)        if err != nil {            return nil, err        }        // Create the root directory if it doesn't exists        if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) {            return nil, err        }        return &Graph{            Root: abspath,        }, nil    }    */    ///var/lib/docker/repositories文件夹和Graph对象创建TagStore    repositories, err := NewTagStore(path.Join(root, "repositories"), g)    if err != nil {        return nil, fmt.Errorf("Couldn't create Tag store: %s", err)    }    //通过名为lxcbr0的卡的网络创建网络管理    netManager, err := newNetworkManager(networkBridgeIface)在看看网络管理的创建,实质上通过指定名为lxcbr0的的网络接口来实现的,一个网络管理的实例其实包括了:网桥名字,ip网络,ip分配器,端口分配器,端口映射器,那么在实例化一个网络管理的时候,实质上就是要将这些属性全部赋值,ip分配器实质上就是一个ip地址的chanle,里面的ip地址是通过lxcbr0接口的ip 和对应的网关mask计算得到的子网ip。端口分配器,实质就是一个在存放了指定范围49153~65535个int的chanle,端口映射器实际上就是一个设置iptalbe和清楚iptable的一个方法。    /*          type NetworkManager struct {            bridgeIface   string  //网桥名字            bridgeNetwork *net.IPNet //ip网络            ipAllocator   *IPAllocator //ip分配器,就是我们在创建容器的时候给容器分配ip的,其实就是一个包含了网卡的ip和网关,同时有又一个ip池            portAllocator *PortAllocator //端口分配器 就是我们在创建容器的时候给容器分配端口的            portMapper    *PortMapper        }        func newNetworkManager(bridgeIface string) (*NetworkManager, error) {            addr, err := getIfaceAddr(bridgeIface) //获取指定lxcbr0网卡的ip            if err != nil {                return nil, err            }            network := addr.(*net.IPNet)            ipAllocator, err := newIPAllocator(network)            /*                  type IPAllocator struct {                    network *net.IPNet                    queue   chan (net.IP)                }                通过网卡lxcbr0的第一个ip和网关mask 得到当前这个网卡下的所有子网ip 并且封装成一个ip分配器(IPAllocator)                func newIPAllocator(network *net.IPNet) (*IPAllocator, error) {                    alloc := &IPAllocator{                        network: network,                    }                    if err := alloc.populate(); err != nil {                        return nil, err                    }                    return alloc, nil                }                func (alloc *IPAllocator) populate() error {                    firstIP, _ := networkRange(alloc.network)                    size, err := networkSize(alloc.network.Mask)                    if err != nil {                        return err                    }                    // The queue size should be the network size - 3                    // -1 for the network address, -1 for the broadcast address and                    // -1 for the gateway address                    alloc.queue = make(chan net.IP, size-3)                    for i := int32(1); i < size-1; i++ {                        ipNum, err := ipToInt(firstIP)                        if err != nil {                            return err                        }                        ip, err := intToIp(ipNum + int32(i))                        if err != nil {                            return err                        }                        // Discard the network IP (that's the host IP address)                        if ip.Equal(alloc.network.IP) {                            continue                        }                        alloc.queue <- ip                    }                    return nil                }            */            if err != nil {                return nil, err            }            //创建端口分配器(实质是以一个存放49153~65535)个端口的int chanle            portAllocator, err := newPortAllocator(portRangeStart, portRangeEnd)            if err != nil {                return nil, err            }            /*              const (                networkBridgeIface = "lxcbr0"                portRangeStart     = 49153                portRangeEnd       = 65535            )            type PortAllocator struct {                ports chan (int)            }                func newPortAllocator(start, end int) (*PortAllocator, error) {                    allocator := &PortAllocator{}                    allocator.populate(start, end)                    return allocator, nil                }            */            //端口映射器通过设置iptables规则来处理将外部端口映射到容器。 它跟踪所有映射,并能够随意取消映射            portMapper, err := newPortMapper()            /*                type PortMapper struct {                    mapping map[int]net.TCPAddr                }                func newPortMapper() (*PortMapper, error) {                    mapper := &PortMapper{}                    if err := mapper.cleanup(); err != nil {                        return nil, err                    }                    if err := mapper.setup(); err != nil {                        return nil, err                    }                    return mapper, nil                }                func (mapper *PortMapper) cleanup() error {                    // Ignore errors - This could mean the chains were never set up                    iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER")                    iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER")                    iptables("-t", "nat", "-F", "DOCKER")                    iptables("-t", "nat", "-X", "DOCKER")                    mapper.mapping = make(map[int]net.TCPAddr)                    return nil                }                func (mapper *PortMapper) setup() error {                    if err := iptables("-t", "nat", "-N", "DOCKER"); err != nil {                        return errors.New("Unable to setup port networking: Failed to create DOCKER chain")                    }                    if err := iptables("-t", "nat", "-A", "PREROUTING", "-j", "DOCKER"); err != nil {                        return errors.New("Unable to setup port networking: Failed to inject docker in PREROUTING chain")                    }                    if err := iptables("-t", "nat", "-A", "OUTPUT", "-j", "DOCKER"); err != nil {                        return errors.New("Unable to setup port networking: Failed to inject docker in OUTPUT chain")                    }                    return nil                }                func iptables(args ...string) error {                    if err := exec.Command("/sbin/iptables", args...).Run(); err != nil {                        return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " "))                    }                    return nil                }            */            manager := &NetworkManager{                bridgeIface:   bridgeIface,                bridgeNetwork: network,                ipAllocator:   ipAllocator,                portAllocator: portAllocator,                portMapper:    portMapper,            }            return manager, nil        }    */    if err != nil {        return nil, err    }    //加载/var/lib/docker/.dockercfg生成对应的auth对象    /*    type AuthConfig struct {        Username string `json:"username"`        Password string `json:"password"`        Email    string `json:"email"`        rootPath string `json:-`    }    */    authConfig, err := auth.LoadConfig(root)    if err != nil && authConfig == nil {        // If the auth file does not exist, keep going        return nil, err    }    runtime := &Runtime{        root:           root,        repository:     runtime_repo,        containers:     list.New(),        networkManager: netManager,        graph:          g,        repositories:   repositories,        authConfig:     authConfig,    }    if err := runtime.restore(); err != nil {        return nil, err    }    return runtime, nil    /*          //读取/var/lib/docker/containers目录下的所有文件夹(实际就是所有之前运行过的容器的目录,目录名为对应容器的id)        func (runtime *Runtime) restore() error {            dir, err := ioutil.ReadDir(runtime.repository)            if err != nil {                return err            }            for _, v := range dir {                id := v.Name()                container, err := runtime.Load(id)                if err != nil {                    Debugf("Failed to load container %v: %v", id, err)                    continue                }                Debugf("Loaded container %v", container.Id)            }            return nil        }        //load的实现流程如下:通过获取对应容器id目录下的config.json文件数据来实力话一个container对象,        func (runtime *Runtime) Load(id string) (*Container, error) {            container := &Container{root: runtime.containerRoot(id)}            if err := container.FromDisk(); err != nil {                return nil, err            }            //最后检查config.json(实际就是对应容器的容器信息文件)是否被更改过            if container.Id != id {                return container, fmt.Errorf("Container %s is stored at %s", container.Id, id)            }            if err := runtime.Register(container); err != nil {                return nil, err            }            return container, nil        }        //最后将加载的容器注册到runtime中的容器list中去,具体流程如下        func (runtime *Runtime) Register(container *Container) error {            //先检查runtime中的容器list中是否存在,存在则提示错误            if container.runtime != nil || runtime.Exists(container.Id) {                return fmt.Errorf("Container is already loaded")            }            //检查容器id是否为空 如果为空则说明容器是错误的,则退出返回错误            if err := validateId(container.Id); err != nil {                return err            }            //设置容器的runtime            container.runtime = runtime            //设置容器的状态以及容器的标准输出,输出,错误流,然后将标准输出和错误写入磁盘指定的文件中            // Setup state lock (formerly in newState()            lock := new(sync.Mutex)            container.State.stateChangeLock = lock            container.State.stateChangeCond = sync.NewCond(lock)            // Attach to stdout and stderr            container.stderr = newWriteBroadcaster()            container.stdout = newWriteBroadcaster()            // Attach to stdin            if container.Config.OpenStdin {                container.stdin, container.stdinPipe = io.Pipe()            } else {                container.stdinPipe = NopWriteCloser(ioutil.Discard) // Silently drop stdin            }            // Setup logging of stdout and stderr to disk            if err := runtime.LogToDisk(container.stdout, container.logPath("stdout")); err != nil {                return err            }            if err := runtime.LogToDisk(container.stderr, container.logPath("stderr")); err != nil {                return err            }            // done            //将container 加入到runtime中的容器list的最后            runtime.containers.PushBack(container)            return nil        }    */

创建tcp服务端:

创建一个监听 接受tcp的请求,为每个请求开启一个单独的携程处理请求 ,如果有请求到来则进行处理

    func ListenAndServe(proto, addr string, service Service) error {        //创建一个监听         listener, err := net.Listen(proto, addr)        if err != nil {            return err        }        log.Printf("Listening for RCLI/%s on %s\n", proto, addr)        defer listener.Close()        for {            //接受tcp的请求            if conn, err := listener.Accept(); err != nil {                return err            } else {                go func() {                    if DEBUG_FLAG {                        CLIENT_SOCKET = conn                    }                    //如果有请求到来则进行处理                    if err := Serve(conn, service); err != nil {                        log.Printf("Error: " + err.Error() + "\n")                        fmt.Fprintf(conn, "Error: "+err.Error()+"\n")                    }                    conn.Close()                }()            }        }        return nil    }    //获取请求中的参数然后调用call,call的一系列调度过程如下    func Serve(conn io.ReadWriter, service Service) error {        r := bufio.NewReader(conn)        var args []string        if line, err := r.ReadString('\n'); err != nil {            return err        } else if err := json.Unmarshal([]byte(line), &args); err != nil {            return err        } else {            return call(service, ioutil.NopCloser(r), conn, args...)        }        return nil    }        func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {            return LocalCall(service, stdin, stdout, args...)        }

根据参数是否有值来执行不同方法,如果没有参数,则执行runtime的help方法,也就是我们通常输入docker 这个命令看到的那些heilp的信息,如果有参数,在进行参数的处理,处理逻辑:获取第二个参数,就是docker 后的命令,然后获取命令之后的所有参数,进行整条命令的打印日志输出,之后再通过cmd命令和反射技术去找到对应的cmd所对应的方法,最后找到方法将参数传入方法,执行cmd对应的方法,结构返回connect中。至此整个deamon启动,到处理具体的api请求全部完成

        func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {            if len(args) == 0 {                args = []string{"help"}            }            flags := flag.NewFlagSet("main", flag.ContinueOnError)            flags.SetOutput(stdout)            flags.Usage = func() { stdout.Write([]byte(service.Help())) }            if err := flags.Parse(args); err != nil {                return err            }            cmd := flags.Arg(0)            log.Printf("%s\n", strings.Join(append(append([]string{service.Name()}, cmd), flags.Args()[1:]...), " "))            if cmd == "" {                cmd = "help"            }            method := getMethod(service, cmd)            if method != nil {                return method(stdin, stdout, flags.Args()[1:]...)            }            return errors.New("No such command: " + cmd)        }        func getMethod(service Service, name string) Cmd {            if name == "help" {                return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {                    if len(args) == 0 {                        stdout.Write([]byte(service.Help()))                    } else {                        if method := getMethod(service, args[0]); method == nil {                            return errors.New("No such command: " + args[0])                        } else {                            method(stdin, stdout, "--help")                        }                    }                    return nil                }            }            methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])            method, exists := reflect.TypeOf(service).MethodByName(methodName)            if !exists {                return nil            }            return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {                ret := method.Func.CallSlice([]reflect.Value{                    reflect.ValueOf(service),                    reflect.ValueOf(stdin),                    reflect.ValueOf(stdout),                    reflect.ValueOf(args),                })[0].Interface()                if ret == nil {                    return nil                }                return ret.(error)            }        }    */}

最后做个总结:docker daemon是运行过程如下:首先在添加网桥的默认路由,切换用户权限等操作,然后再在/var/lib/docker目录下创建containers,graph等文件夹(如果已经存在则将containers中的所有容器加载到runtime(我认为runtime实际就是docker-deamon的运行时环境)运行时环境中),然后创建image的tag存储,然后创建对应的网络管理器,最后加载认证文件,最后启动tpc服务监听处理请求。

0 0
原创粉丝点击