docker的cli的call执行流程

平常我们输入一个命令,比如docker info,就是使用cli向daemon发送一次http请求,返回一些容器相关的参数

输入docker info的时候,如果daemon不是在后台运行,会有  INFO[0007] GET /v1.19/info 输出,GET相当于HTTP中的GET/POST中的method GET, v1.19相当于版本号,info就是命令的name咯

接下来我们分析源码:

serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil) //attach命令请求
//一次cli.call的调用执行
serverResp, err := cli.call("GET", "/info", nil, nil) //info 命令请求

接下来我们分析一下cli.call的执行流程:

//对应attach的请求,method="GET" path="/containers/"+cmd.Arg(0)+"/json" data=nil headers=nil
func (cli *DockerCli) call(method, path string, data interface{}, headers map[string][]string) (*serverResponse, error) {
	params, err := cli.encodeData(data) //数据编码,data类型为interface{},可以兼容所有类型的data
	if err != nil {
		sr := &serverResponse{
			body:       nil,
			header:     nil,
			statusCode: -1,
		}
		return sr, nil
	}
        //设置http的headers
	if data != nil {
		if headers == nil {
			headers = make(map[string][]string)
		}
		headers["Content-Type"] = []string{"application/json"}
	}
        //接下来就是它
	serverResp, err := cli.clientRequest(method, path, params, headers)
	return serverResp, err
}

再来看一看 cli.clientRequest执行

func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (*serverResponse, error) {
        
        //daemon的返回数据的struct为serverResp
	serverResp := &serverResponse{
		body:       nil,
		statusCode: -1,
	}
        //这是期待POST和PUT方法,貌似是写xx
	expectedPayload := (method == "POST" || method == "PUT")
	if expectedPayload && in == nil {
		in = bytes.NewReader([]byte{})
	}
	//这里是重点,这个是用golang默认的http包返回一个req对象
	req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), in)
	if err != nil {
		return serverResp, err
	}

	// Add CLI Config‘s HTTP Headers BEFORE we set the Docker headers
	// then the user can‘t change OUR headers
	//这个cli初始化的时候设置的一些数据,比如下面的cli.addr,cli.scheme都是初始化时候设置的
	for k, v := range cli.configFile.HTTPHeaders {
		req.Header.Set(k, v)
	}

	req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
	req.URL.Host = cli.addr
	req.URL.Scheme = cli.scheme

	if headers != nil {
		for k, v := range headers {
			req.Header[k] = v
		}
	}

	if expectedPayload && req.Header.Get("Content-Type") == "" {
		req.Header.Set("Content-Type", "text/plain")
	}
        //执行http的请求就在这里这行咯,cli.HTTpClient返回&http.Client{Transport: cli.transport}
        //cli.transport也是cli初始化的时候设置的,调用golang的http包向server做一次请求
	resp, err := cli.HTTPClient().Do(req)
	if resp != nil {
		serverResp.statusCode = resp.StatusCode
	}
        
        //接下来就是打杂的返回值错误和http错误码的判断
	if err != nil {
		if types.IsTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") {
			return serverResp, errConnectionFailed
		}

		if cli.tlsConfig == nil && strings.Contains(err.Error(), "malformed HTTP response") {
			return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)
		}
		if cli.tlsConfig != nil && strings.Contains(err.Error(), "remote error: bad certificate") {
			return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
		}

		return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err)
	}

	if serverResp.statusCode < 200 || serverResp.statusCode >= 400 {
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return serverResp, err
		}
		if len(body) == 0 {
			return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL)
		}
		return serverResp, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
	}

	serverResp.body = resp.Body
	serverResp.header = resp.Header
	return serverResp, nil
}

梳理一下,其实就是两步很重要,一步是 http.NewRequest,一步是 cli.HTTPClient().Do(req)

记得还有其中一堆参数实在cli初始化的时候建立的,来看看cli初始化的代码

func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientFlags) *DockerCli {
	cli := &DockerCli{
		in:      in,
		out:     out,
		err:     err,
		keyFile: clientFlags.Common.TrustKey,
	} //输入,输出,错误的IO,以及tls的keyfile

	cli.init = func() error {

		clientFlags.PostParse()

		hosts := clientFlags.Common.Hosts

		switch len(hosts) {
		case 0:
			defaultHost := os.Getenv("DOCKER_HOST")
			if defaultHost == "" {
				defaultHost = opts.DefaultHost
			}
		//看看这个DefaultHost是多少
		//DefaultHTTPHost = "127.0.0.1"  DefaultHTTPPort = 2375 
		// DefaultHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort)
		//其实本地使用的是DefaultUnixSocket = "/var/run/docker.sock"

			defaultHost, err := opts.ValidateHost(defaultHost)
			if err != nil {
				return err
			}
			hosts = []string{defaultHost}
		case 1:
			// only accept one host to talk to
		default:
			return errors.New("Please specify only one -H")
		}

		protoAddrParts := strings.SplitN(hosts[0], "://", 2)
		cli.proto, cli.addr = protoAddrParts[0], protoAddrParts[1]
		//协议地址在这里初始化,proto="tcp/http/..." addr="127.0.0.1/...."
              
		if cli.proto == "tcp" {
			// error is checked in pkg/parsers already
			parsed, _ := url.Parse("tcp://" + cli.addr)
			cli.addr = parsed.Host
			cli.basePath = parsed.Path
		}

		if clientFlags.Common.TLSOptions != nil {
			cli.scheme = "https"
			var e error
			cli.tlsConfig, e = tlsconfig.Client(*clientFlags.Common.TLSOptions)
			if e != nil {
				return e
			}
		} else {
			cli.scheme = "http"
		}

		if cli.in != nil {
			cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
		}
		if cli.out != nil {
			cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
		}

		// The transport is created here for reuse during the client session.
		cli.transport = &http.Transport{
			TLSClientConfig: cli.tlsConfig,
		}
		//这个很关键cli.transport的设置
		sockets.ConfigureTCPTransport(cli.transport, cli.proto, cli.addr)

		configFile, e := cliconfig.Load(cliconfig.ConfigDir())
		if e != nil {
			fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e)
		}
		cli.configFile = configFile

		return nil
	}

	return cli
}

最后再来看看clientFlags是怎么来的吧?

在docker/docker/client.go里面初始化的
var clientFlags = &cli.ClientFlags{FlagSet: new(flag.FlagSet), Common: commonFlags}

//------------------------------------------------------------------------------------------------------

//学以致用,做一个自己版本的cli去请求docker的daemon的

package main

import (
        _"bytes"
        "fmt"
        "io/ioutil"
        "net/http"
        "os"
        "runtime"
        "time"
        "net"
)

func main(){

   httppost()

}

func ConfigureTCPTransport(tr *http.Transport, proto, addr string) {
        timeout := 32 * time.Second
        if proto == "unix" {
                // No need for compression in local communications.
                tr.DisableCompression = true
                tr.Dial = func(_, _ string) (net.Conn, error) {
                        return net.DialTimeout(proto, addr, timeout)
                }
        } else {
                tr.Proxy = http.ProxyFromEnvironment
                tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
        }
}

func httppost() {

       tr := &http.Transport{}
       ConfigureTCPTransport(tr,"unix", "/var/run/docker.sock")

        client := &http.Client{Transport:tr}

        req, err := http.NewRequest("GET", "/v1.20/info", nil) //v1.20这个是我自己的docker api version
        if err != nil {
                fmt.Println(111111111111111111)
        }

        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("User-Agent", "Docker-Client/"+"1.8.0"+" ("+runtime.GOOS+")")
        //1.8.0也是我自己的docker version的版本
        req.URL.Host = "/var/run/docker.sock"
        req.URL.Scheme = "http"

        resp, err := client.Do(req)

        fmt.Printf("%v ---- %v ---------- %v\n ",resp,err,os.Getenv("DOCKER_HOST"))

        if err ==nil {

          defer resp.Body.Close()
        }

        //defer resp.Body.Close()

        body, err := ioutil.ReadAll(resp.Body)
        if err != nil {
                // handle error
        }

        fmt.Println(string(body))

}

总结来说,docker的这个请求还是用golang里面的http包,这个http包的执行流程过大和复杂,本篇文章就不概述了。

时间: 2024-10-10 05:37:13

docker的cli的call执行流程的相关文章

docker exec的具体执行流程

首先,做一个docker exec的请求: docker exec -it 5504f937f7bb sh 对应的docker -d(启动的docker daemon)的输出为: INFO[0211] POST /v1.20/containers/5504f937f7bb/exec      INFO[0211] POST /v1.20/exec/fc9c11ae6ac4827ea507e885c888bdb37c8f7b906347b9272adf8d580a6417df/start  INF

Docker Client创建和命令执行

//后续补上Docker的架构分析 Docker Client创建及命令执行 Docker架构:http://www.infoq.com/cn/articles/docker-source-code-analysis-part1 从整体的架构图中,可以看出Docker client在架构中的位置,Docker client是用户用来和Docker Daemon(主体部分)建立通信的客户端.用户使用Docker可执行文件发起管理container的请求. Go语言:Go是Google开发的一种编译

Docker源码分析(二):Docker Client创建与命令执行

1. 前言 如今,Docker作为业界领先的轻量级虚拟化容器管理引擎,给全球开发者提供了一种新颖.便捷的软件集成测试与部署之道.在团队开发软件时,Docker可以提供可复用的运行环境.灵活的资源配置.便捷的集成测试方法以及一键式的部署方式.可以说,Docker的优势在简化持续集成.运维部署方面体现得淋漓尽致,它完全让开发者从持续集成.运维部署方面中解放出来,把精力真正地倾注在开发上. 然而,把Docker的功能发挥到极致,并非一件易事.在深刻理解Docker架构的情况下,熟练掌握Docker C

Hive SQL执行流程分析

转自 http://www.tuicool.com/articles/qyUzQj 最近在研究Impala,还是先回顾下Hive的SQL执行流程吧. Hive有三种用户接口: cli (Command line interface) bin/hive或bin/hive –service cli 命令行方式(默认) hive-server/hive-server2 bin/hive –service hiveserver 或bin/hive –service hiveserver2 通过JDBC/

yii执行流程

yii执行流程 原文:http://www.cnblogs.com/bluecobra/archive/2011/11/30/2269207.html 一 目录文件 |-framework     框架核心库 |--base         底层类库文件夹,包含CApplication(应用类,负责全局的用户请求处理,它管理的应用组件集,将提供特定功能给整个应用程序),CComponent(组件类,该文件包含了基于组件和事件驱动编程的基础类,从版本1.1.0开始,一个行为的属性(或者它的公共成员

python学习之内部执行流程,内部执行流程,编码(一)

python的执行流程: 加载内存--->词法分析--->语法分析--->编译--->转换字节码---->转换成机器码---->供给CPU调度 python的编码: 1. ASCII    2的256次表示. 2. UNICODE  最好16位表示2字节 3. UTF-8 能就8位表示就用8位表示,节省资源. python的注释: 字符 '#': 只注释一行 字符"  '''  ":注释多行 python的教程参数: 用 sys模块 的argv参数,

(一)熟悉执行流程——基于ThinkPHP3.2的内容管理框架OneThink学习

ThinkPHP作为国内具有代表性的PHP框架,经过多年的发展,受到越来越多公司与开发者的青睐.我也在忙里偷闲中抽出部分时间,来学习这个优秀的框架.在开始学习这个框架时,最好通过实例来学习,更容易结合实际的生产情况,促进学习的效果:这里我就选择由ThinkPHP团队开发的基于ThinkPHP3.2的内容管理框架OneThink来学习,从了解它的执行流程→熟悉流程中各个细节→了解模版标签→自己实际去使用标签→再了解它的实际执行过程……通过这样一个流程来熟悉如何基于ThinkPHP开发出一套CMS系

使用Caffe进行手写数字识别执行流程解析

之前在 http://blog.csdn.net/fengbingchun/article/details/50987185 中仿照Caffe中的examples实现对手写数字进行识别,这里详细介绍下其执行流程并精简了实现代码,使用Caffe对MNIST数据集进行train的文章可以参考  http://blog.csdn.net/fengbingchun/article/details/68065338 : 1.   先注册所有层,执行layer_factory.hpp中类LayerRegis

ASP.NET MVC4 执行流程

MVC在底层和传统的asp.net是一致的,在底层之上,相关流程如下: 1)Global.asax里,MvcApplication对象的Application_Start()事件中,调用 RouteConfig.RegisterRoutes(RouteTable.Routes); 来注册路由规则. 2)RouteConfig.RegisterRoutes()方法里,给出的默认规则为 {controller}/{action}/{id} . a. 在有特别需要的时候,到这里来修改规则. b. 未指