docker源码学习-docker deamon (1)

谈到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服务监听处理请求。

推荐阅读更多精彩内容