初学docker,了解docker remote API的时候根本不知道怎么使用在开发中,通过DockerClient这个项目能很好的认识到API在开发过程中的使用,如何建立连接等。也希望像我一样的初学者能通过这个项目了解docker API的使用。
一. 基本配置
访问docker远程API有两种方法,使用Unix套接字和HTTP端口。
Unix套接字:docker监听unix:///var/run/docker.sock这个unix套接字。
HTTP远程访问:需要修改配置使能支持远程访问,在ubuntu环境下找到/etc/init/docker.conf文件,找到 DOCKER_OPTS并修改,接着重启docker后台进程。
DOCKER_OPTS="-H tcp://0.0.0.0:4243 -H unix:///var/run/docker.sock" $sudo service docker restart 验证 $netstat -ant |grep 4243 tcp6 0 0 :::4243 :::* LISTEN
然后就可以访问了。
举例如下:
$curl http://127.0.0.1:4243/info {"Containers":4,"Debug":0,"Driver":"aufs","DriverStatus":[["Root Dir","/var/lib/docker/aufs"],["Dirs","63"]],"ExecutionDriver":"native-0.2","IPv4Forwarding":1,"Images":55,"IndexServerAddress":"https://index.docker.io/v1/","InitPath":"/usr/bin/docker","InitSha1":"","KernelVersion":"3.13.0-32-generic","MemoryLimit":1,"NEventsListener":0,"NFd":16,"NGoroutines":15,"OperatingSystem":"Ubuntu precise (12.04.5 LTS)","SwapLimit":0} $curl GET http://127.0.0.1:4243/containers/json [{"Command":"crate -Des.cluster.name=cratecluster -Des.node.name=crate1 -Des.transport.publish_port=4300 -Des.network.publish_host=115.159.76.38 -Des.multicast.enabled=false -Des.discovery.zen.ping.unicast.hosts=115.159.76.38:4300,115.159.26.242:4300 -Des.discovery.zen.minimum_master_nodes=2","Created":1418829276,"Id":"dfd58a62a7c941218088f6e43d8dc17ecfb8cd0783e6c4e338c321d6d39c4bbd","Image":"docker.cn/docker/crate:0.45","Names":["/node1"],"Ports":[{"IP":"0.0.0.0","PrivatePort":4200,"PublicPort":4200,"Type":"tcp"},{"IP":"0.0.0.0","PrivatePort":4300,"PublicPort":4300,"Type":"tcp"}],"Status":"Up 2 weeks"}]
如果想要了解完整的docker API可以访问官网:https://docs.docker.com/reference/api/docker_remote_api/ 。
二. DockerClient
下面开始进入dockerClient这个项目,dockerclient.go中实现了对docker remote API的封装。
dockerClient项目:https://github.com/samalba/dockerclient。
1.创建DockerClient
a. NewDockerClient()
func NewDockerClient(daemonUrl string, tlsConfig *tls.Config) (*DockerClient, error) { return NewDockerClientTimeout(daemonUrl, tlsConfig, time.Duration(defaultTimeout)) }
使用NewDockerClient方法新建一个dockerclient,需要传入daemonUrl和tlsConfig两个参数,创建成功会返回一个DockerClient。其中两个参数具体含义如下:
daemonUrl: 需要传入一个后台docker进程的Url,比如unix:///var/run/docker.sock。
tlsConfig: tlsConfig是cropto/tls包中的Config结构类型,Config结构类型用于配置TLS客户端或服务端,具体参阅cropto/tls包。
TLS:Transport Layer Security(安全传输层协议)
TLS用于在两个通信应用程序之间提供保密性和数据完整性。
使用举例:
docker, _ := dockerclient.NewDockerClient("unix:///var/run/docker.sock", nil)
在NewDockerClient方法中调用NewDockerClientTimeout方法实现具体的DockerClient的创建。
b.NewDockerClientTimeout()
func NewDockerClientTimeout(daemonUrl string, tlsConfig *tls.Config, timeout time.Duration) (*DockerClient, error) { u, err := url.Parse(daemonUrl) if err != nil { return nil, err } if u.Scheme == "tcp" { if tlsConfig == nil { u.Scheme = "http" } else { u.Scheme = "https" } } httpClient := newHTTPClient(u, tlsConfig, timeout) return &DockerClient{u, httpClient, tlsConfig, 0}, nil }
需要向NewDockerClientTimeout方法传入三个参数,分别是daemonUrl、tlsConfig和timeout。在此方法中会先解析daemonUrl,net/url包中的Parse方法解析daemonUrl,返回一个URL结构类型。
URL结构类型:
type URL struct { Scheme string Opaque string // 编码后的不透明数据 User *Userinfo // 用户名和密码信息 Host string // host或host:port Path string RawQuery string // 编码后的查询字符串,没有'?' Fragment string // 引用的片段(文档位置),没有'#'}
URL基本格式如下:
scheme://[userinfo@]host/path[?query][#fragment]
timeout:timeout是time包中的Duration类型,代表两个时间点之间经过的时间,以纳秒为单位。timeout默认为30s(defaultTimeout)
解析完daemonUrl后,对返回的URL的scheme进行判断,tcp协议并且有安全传输协议的设置scheme为https,否则设为http。当然scheme也有可能是unix。接着需要新建一个HTTPClient,通过向newHTTPClient方法传入三个参数,包括解析后的URL、tlsConfig和timeout。
c. newHTTPClient()
func newHTTPClient(u *url.URL, tlsConfig *tls.Config, timeout time.Duration) *http.Client { httpTransport := &http.Transport{ TLSClientConfig: tlsConfig, } switch u.Scheme { default: httpTransport.Dial = func(proto, addr string) (net.Conn, error) { return net.DialTimeout(proto, addr, timeout) } case "unix": socketPath := u.Path unixDial := func(proto, addr string) (net.Conn, error) { return net.DialTimeout("unix", socketPath, timeout) } httpTransport.Dial = unixDial // Override the main URL object so the HTTP lib won't complain u.Scheme = "http" u.Host = "unix.sock" u.Path = "" } return &http.Client{Transport: httpTransport} }
newHTTPClient主要是新建一个HTTPClient(就是建立一个网络连接),在方法中先新建一个Transport结构类型的httpTransport实例。
Transport结构类型:
type Transport struct { Proxy func(*Request) (*url.URL, error) // Proxy指定一个对给定请求返回代理的函数。 Dial func(network, addr string) (net.Conn, error) // Dial指定创建TCP连接的拨号函数。如果Dial为nil, // 会使用net.Dial。 TLSClientConfig *tls.Config // TLSClientConfig指定用于tls.Client的TLS配置信息 // 如果该字段为nil,会使用默认的配置信息。 TLSHandshakeTimeout time.Duration DisableKeepAlives bool DisableCompression bool MaxIdleConnsPerHost int ResponseHeaderTimeout time.Duration }
接着对URL的scheme进行判断,如果是unix(也就是访问由docker监听的unix套接字),那么进入case "unix",否则进入默认的default。(能传输到这里的一般是unix、http和https)
Example: unix:///var/run/docker.sock
case "unix": 先新建一个socketPath为URL中Path的内容(如上例子中u.path=/var/run/docker.sock), 接着新建网络连接的拨号方法unixDial,即要赋值给httpTransport实例中的Dial方法。在unixDial方法中调用net包中的DialTimeout方法实现采用超时的网络连接。
func DialTimeout(network, address string, timeout time.Duration) (Conn, error) 即 net.DialTimeout("unix", "/var/run/docker.sock", timeout) 举例 conn, err := net.DialTimeout("tcp", "google.com:80", timeout)
最后返回一个Client结构类型实例表示HTTP客户端。
Client结构类型:
type Client struct { Transport RoundTripper // Transport指定执行独立、单次HTTP请求的机制。 CheckRedirect func(req *Request, via []*Request) error Jar CookieJar // Jar指定cookie管理器。 Timeout time.Duration}
最后返回NewDockerClientTimeout方法中,httpClient建立完成,接着返回一个建立完成的DockerClient结构类型实例。
DockerClient结构类型:
type DockerClient struct { URL *url.URL HTTPClient *http.Client TLSConfig *tls.Config monitorEvents int32 }
至此DockerClient创建完成。
2. 发送请求处理
a. doRequest()
func (client *DockerClient) doRequest(method string, path string, body []byte, headers map[string]string) ([]byte, error) { b := bytes.NewBuffer(body) req, err := http.NewRequest(method, client.URL.String()+path, b) if err != nil { return nil, err } req.Header.Add("Content-Type", "application/json") if headers != nil { for header, value := range headers { req.Header.Add(header, value) } } resp, err := client.HTTPClient.Do(req) if err != nil { if !strings.Contains(err.Error(), "connection refused") && client.TLSConfig == nil { return nil, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err) } return nil, err } defer resp.Body.Close() data, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } if resp.StatusCode == 404 { return nil, ErrNotFound } if resp.StatusCode >= 400 { return nil, Error{StatusCode: resp.StatusCode, Status: resp.Status, msg: string(data)} } return data, nil }
doRequest方法完成发送请求的工作,比如你想获得docker的信息,那么可以通过doRequest方法发送“GET”请求。下面介绍doRequest方法的具体实现。
b := bytes.NewBuffer(body) 方法原型: func NewBuffer(buf []byte) *Buffer
NewBuffer使用buf(即body)作为初始内容创建并初始化一个Buffer,本方法用于创建一个用于读取已存在数据的buffer(即b)。
req, err := http.NewRequest(method, client.URL.String()+path, b) 方法原型: func NewRequest(method, urlStr string, body io.Reader) (*Request, error)
NewRequest使用指定的方法、网址和可选的主题创建并返回一个新的*Request(即req)。Request类型代表一个服务端接收到的或者客户端发送出去的HTTP请求。比如上面所说的一个例子:
curl GET http://127.0.0.1:4243/containers/json
那么method=GET, urlStr=http://127.0.0.1:4243/containers/json , body=nil。
Request结构类型:
type Request struct { Method string //Method指定HTTP方法(GET,POST,PUT等)。对客户端,""代表GET。 URL *url.URL //URL在服务器端表示被请求的URL,在客户端表示要访问的URL。 Proto string ProtoMajor int ProtoMinor int Header Header //Header字段表示HTTP请求的头域。 Body io.ReadCloser //Body是请求的主体。 ContentLength int64 TransferEncoding []string Close bool Host string Form url.Values PostForm url.Values MultipartForm *multipart.Form Trailer Header RemoteAddr string RequestURL string TLS *tls.ConnectionState }
具体的各个字段详细解释可以参考帮助文档或者访问下面一个网址:
帮助文档翻译版:http://git.oschina.net/liudiwu/pkgdoc(或者:http://mygodoc.oschina.mopaas.com )
Go包方法使用:https://github.com/astaxie/gopkg 。
req.Header.Add("Content-Type", "application/json") if headers != nil { for header, value := range headers { req.Header.Add(header, value) } } 方法原型 type Header map[string][]string func (h Header) Add(key, value string)
Add方法添加键值对到req的Header字段,header字段即HTTP请求的头域。
resp, err := client.HTTPClient.Do(req) if err != nil { if !strings.Contains(err.Error(), "connection refused") && client.TLSConfig == nil { return nil, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err) } return nil, err } 方法原型 func (c *Client) Do(req *Request) (resp *Response, err error)
Do方法发送请求,返回HTTP回复。
defer resp.Body.Close() data, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } if resp.StatusCode == 404 { return nil, ErrNotFound } if resp.StatusCode >= 400 { return nil, Error{StatusCode: resp.StatusCode, Status: resp.Status, msg: string(data)} } return data, nil 方法原型 func ReaderAll(r io.Reader) ([]byte, error)
ReaderAll方法从请求返回的resp的Body中读取数据知道EOF活着遇到error,返回读取的数据和遇到的错误。并对返回的HTTP状态码进行判断,最后返回数据并关闭resp.Body。
Response结构类型:
type Response sctuct { Status string StatusCode int Proto string ProtoMajor int ProtoMinor int Header Header Body io.ReadCloser ContentLength int64 TransferEncoding []string Close bool Trailer Header Request *Request TLS *tls.ConnectionState }
总结:上面是DockerClient中主要的建立连接的方式,对于我这样的初学者来说这个项目起到基础的帮助作用,对于开发docker管理项目起到帮助。
参考资料:
1.https://docs.docker.com/reference/api/docker_remote_api/
2.https://github.com/samalba/dockerclient
3.http://git.oschina.net/liudiwu/pkgdoc
4.http://mygodoc.oschina.mopaas.com
5.https://github.com/astaxie/gopkg