1. 客户端架构概述

1.1 设计理念

Moby客户端SDK采用简洁的设计理念,为开发者提供易用的Go语言接口来与Docker守护进程交互:

  • 简单易用: 提供直观的API接口,隐藏底层HTTP通信复杂性
  • 版本兼容: 支持API版本自动协商和向后兼容
  • 类型安全: 使用强类型定义,避免运行时错误
  • 可配置性: 支持多种连接方式和配置选项
  • 错误处理: 统一的错误处理机制,提供详细的错误信息

1.2 客户端架构设计

graph TB
    subgraph "客户端应用层"
        UserApp[用户应用程序]
        DockerCLI[Docker CLI]
        ThirdParty[第三方工具]
    end
    
    subgraph "客户端SDK层"
        ClientSDK[Client SDK]
        APIClient[API Client Interface]
        ConfigManager[配置管理器]
        VersionNegotiator[版本协商器]
    end
    
    subgraph "HTTP通信层"
        HTTPClient[HTTP Client]
        ConnectionPool[连接池]
        RequestBuilder[请求构建器]
        ResponseParser[响应解析器]
    end
    
    subgraph "传输层"
        UnixSocket[Unix Socket]
        TCPSocket[TCP Socket]
        NamedPipe[Named Pipe - Windows]
        TLS[TLS加密]
    end
    
    subgraph "Docker守护进程"
        DockerDaemon[Docker Daemon]
        APIServer[API Server]
    end
    
    UserApp --> ClientSDK
    DockerCLI --> ClientSDK
    ThirdParty --> ClientSDK
    
    ClientSDK --> APIClient
    ClientSDK --> ConfigManager
    ClientSDK --> VersionNegotiator
    
    APIClient --> HTTPClient
    HTTPClient --> ConnectionPool
    HTTPClient --> RequestBuilder
    HTTPClient --> ResponseParser
    
    HTTPClient --> UnixSocket
    HTTPClient --> TCPSocket
    HTTPClient --> NamedPipe
    HTTPClient --> TLS
    
    UnixSocket --> DockerDaemon
    TCPSocket --> DockerDaemon
    NamedPipe --> DockerDaemon
    TLS --> DockerDaemon
    
    DockerDaemon --> APIServer

2. Client核心结构分析

2.1 Client结构体定义

// 文件路径: client/client.go
// Client 是执行所有Docker操作的API客户端核心结构

type Client struct {
	clientConfig                      // 嵌入客户端配置

	// API版本协商相关
	negotiated    atomic.Bool         // 是否已完成版本协商的原子标志
	negotiateLock sync.Mutex          // 版本协商过程的单飞锁,避免并发协商

	// HTTP传输层管理
	// 当客户端传输是*http.Transport(默认)时,需要做一些额外操作(如关闭空闲连接)
	// 存储原始传输,因为http.Client的传输会被链路追踪库包装
	baseTransport *http.Transport
}

// clientConfig 包含客户端的所有配置信息
type clientConfig struct {
	host      string              // Docker守护进程地址
	version   string              // API版本
	client    *http.Client        // HTTP客户端
	proto     string              // 协议类型(unix, tcp等)
	addr      string              // 连接地址
	basePath  string              // API基础路径
	scheme    string              // URL协议方案
	customHTTPHeaders map[string]string // 自定义HTTP头
	manualOverride    bool        // 手动覆盖标志
}

2.2 客户端创建流程

2.2.1 NewClientWithOpts 主要构造函数

// NewClientWithOpts 是创建Docker客户端的主要方法
// 支持多种配置选项,提供灵活的初始化方式
func NewClientWithOpts(ops ...Opt) (*Client, error) {
	// 1. 创建默认客户端配置
	clientConfig := clientConfig{
		host:    DefaultDockerHost,              // 默认为Unix socket
		version: MaxAPIVersion,                  // 使用最高支持的API版本
		scheme:  "http",                        // 默认HTTP协议
		client:  defaultHTTPClient(DefaultDockerHost, nil, nil), // 默认HTTP客户端
	}

	// 2. 应用配置选项
	for _, op := range ops {
		if err := op(&clientConfig); err != nil {
			return nil, err
		}
	}

	// 3. 创建HTTP客户端(如果未指定)
	if clientConfig.client == nil {
		httpClient, err := newHTTPClient(clientConfig.host, clientConfig.version)
		if err != nil {
			return nil, err
		}
		clientConfig.client = httpClient
	}

	// 4. 解析主机地址和协议
	proto, addr, basePath, err := parseHost(clientConfig.host)
	if err != nil {
		return nil, err
	}

	clientConfig.proto = proto
	clientConfig.addr = addr
	clientConfig.basePath = basePath

	// 5. 创建Client实例
	client := &Client{
		clientConfig: clientConfig,
	}

	// 6. 保存基础传输层(用于后续资源清理)
	if t, ok := client.client.Transport.(*http.Transport); ok {
		client.baseTransport = t
	}

	return client, nil
}

2.2.2 配置选项模式实现

// Opt 定义客户端配置选项的函数类型
// 使用函数式选项模式,提供灵活的配置方式
type Opt func(*clientConfig) error

// FromEnv 从环境变量配置客户端
// 读取DOCKER_HOST, DOCKER_API_VERSION, DOCKER_CERT_PATH等环境变量
func FromEnv(c *clientConfig) error {
	// 读取Docker主机地址
	if dockerHost := os.Getenv("DOCKER_HOST"); dockerHost != "" {
		c.host = dockerHost
	}

	// 读取API版本
	if version := os.Getenv("DOCKER_API_VERSION"); version != "" {
		if !isValidVersion(version) {
			return fmt.Errorf("environment variable DOCKER_API_VERSION (%s) is not a valid version", version)
		}
		c.version = version
		c.manualOverride = true
	}

	// 读取TLS配置
	if dockerTLSVerify := os.Getenv("DOCKER_TLS_VERIFY"); dockerTLSVerify != "" {
		if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath == "" {
			return errors.New("DOCKER_TLS_VERIFY specified, but no DOCKER_CERT_PATH environment variable")
		}
	}

	// 配置TLS
	if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" {
		tlsOptions := tlsconfig.Options{
			CAFile:             filepath.Join(dockerCertPath, "ca.pem"),
			CertFile:           filepath.Join(dockerCertPath, "cert.pem"),
			KeyFile:            filepath.Join(dockerCertPath, "key.pem"),
			InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "",
		}
		
		tlsConfig, err := tlsconfig.Client(tlsOptions)
		if err != nil {
			return err
		}
		
		c.client = newHTTPClientWithTLS(c.host, tlsConfig)
	}

	return nil
}

// WithHost 设置Docker守护进程地址
func WithHost(host string) Opt {
	return func(c *clientConfig) error {
		hostURL, err := parseHostURL(host)
		if err != nil {
			return err
		}
		c.host = hostURL.String()
		return nil
	}
}

// WithVersion 设置API版本
func WithVersion(version string) Opt {
	return func(c *clientConfig) error {
		if !isValidVersion(version) {
			return fmt.Errorf("%s is not a valid version", version)
		}
		c.version = version
		c.manualOverride = true
		return nil
	}
}

// WithAPIVersionNegotiation 启用API版本自动协商
// 客户端会自动与服务器协商最佳的API版本
func WithAPIVersionNegotiation() Opt {
	return func(c *clientConfig) error {
		if c.manualOverride {
			return errors.New("API version negotiation cannot be enabled when the API version has been manually overridden")
		}
		c.negotiateVersion = true
		return nil
	}
}

// WithHTTPClient 使用自定义HTTP客户端
func WithHTTPClient(client *http.Client) Opt {
	return func(c *clientConfig) error {
		if client != nil {
			c.client = client
		}
		return nil
	}
}

// WithTimeout 设置请求超时时间
func WithTimeout(timeout time.Duration) Opt {
	return func(c *clientConfig) error {
		c.client.Timeout = timeout
		return nil
	}
}

2.3 HTTP请求处理机制

2.3.1 请求发送核心函数

// 文件路径: client/request.go
// sendRequest 是所有HTTP请求的核心发送函数
// 处理请求构建、发送、错误处理等完整流程
func (cli *Client) sendRequest(ctx context.Context, method, path string, query url.Values, body io.Reader, headers http.Header) (*http.Response, error) {
	// 1. API版本协商(如果启用且未完成)
	if err := cli.checkVersion(ctx); err != nil {
		return nil, err
	}

	// 2. 构建完整的请求URL
	req, err := cli.buildRequest(ctx, method, path, body, headers)
	if err != nil {
		return nil, err
	}

	// 3. 添加查询参数
	if query != nil {
		req.URL.RawQuery = query.Encode()
	}

	// 4. 发送HTTP请求
	resp, err := cli.client.Do(req)
	if err != nil {
		// 处理连接错误
		if err, ok := err.(net.Error); ok {
			if err.Timeout() {
				return nil, errors.New("request timeout")
			}
		}
		
		// 处理上下文取消
		if errors.Is(err, context.Canceled) {
			return nil, err
		}
		
		return nil, fmt.Errorf("error during request: %v", err)
	}

	// 5. 检查HTTP状态码和错误响应
	if err := cli.checkResponseError(resp); err != nil {
		resp.Body.Close()
		return nil, err
	}

	return resp, nil
}

// buildRequest 构建HTTP请求对象
func (cli *Client) buildRequest(ctx context.Context, method, path string, body io.Reader, headers http.Header) (*http.Request, error) {
	// 1. 构建请求URL
	apiURL := &url.URL{
		Scheme: cli.scheme,
		Host:   DummyHost, // 对于Unix socket使用虚拟主机名
		Path:   cli.basePath + path,
	}

	// 2. 创建HTTP请求
	req, err := http.NewRequestWithContext(ctx, method, apiURL.String(), body)
	if err != nil {
		return nil, err
	}

	// 3. 设置请求头
	req.Host = cli.addr
	req.Header.Set("User-Agent", "Docker-Client/"+cli.version+" ("+runtime.GOOS+")")
	
	// 添加自定义头
	for k, v := range cli.customHTTPHeaders {
		req.Header.Set(k, v)
	}
	
	// 添加传入的头
	for k, v := range headers {
		req.Header[k] = v
	}

	return req, nil
}

// checkResponseError 检查响应错误
func (cli *Client) checkResponseError(resp *http.Response) error {
	if resp.StatusCode >= 400 {
		body, err := io.ReadAll(resp.Body)
		if err != nil {
			return fmt.Errorf("HTTP %d: unable to read body", resp.StatusCode)
		}

		var errorResponse common.ErrorResponse
		if err := json.Unmarshal(body, &errorResponse); err != nil {
			return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
		}

		return fmt.Errorf("HTTP %d: %s", resp.StatusCode, errorResponse.Message)
	}
	return nil
}

2.3.2 便捷HTTP方法

// GET请求方法
func (cli *Client) get(ctx context.Context, path string, query url.Values, headers http.Header) (*http.Response, error) {
	return cli.sendRequest(ctx, http.MethodGet, path, query, nil, headers)
}

// POST请求方法(JSON体)
func (cli *Client) post(ctx context.Context, path string, query url.Values, body any, headers http.Header) (*http.Response, error) {
	jsonBody, headers, err := prepareJSONRequest(body, headers)
	if err != nil {
		return nil, err
	}
	return cli.sendRequest(ctx, http.MethodPost, path, query, jsonBody, headers)
}

// POST请求方法(原始体)
func (cli *Client) postRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers http.Header) (*http.Response, error) {
	return cli.sendRequest(ctx, http.MethodPost, path, query, body, headers)
}

// PUT请求方法
func (cli *Client) put(ctx context.Context, path string, query url.Values, body any, headers http.Header) (*http.Response, error) {
	jsonBody, headers, err := prepareJSONRequest(body, headers)
	if err != nil {
		return nil, err
	}
	return cli.putRaw(ctx, path, query, jsonBody, headers)
}

// DELETE请求方法
func (cli *Client) delete(ctx context.Context, path string, query url.Values, headers http.Header) (*http.Response, error) {
	return cli.sendRequest(ctx, http.MethodDelete, path, query, nil, headers)
}

// HEAD请求方法
func (cli *Client) head(ctx context.Context, path string, query url.Values, headers http.Header) (*http.Response, error) {
	return cli.sendRequest(ctx, http.MethodHead, path, query, nil, headers)
}

3. API版本协商机制

3.1 版本协商流程

sequenceDiagram
    participant Client as Docker Client
    participant Daemon as Docker Daemon
    
    Note over Client: 启用版本协商时的流程
    
    Client->>Client: 检查是否已协商版本
    Client->>Daemon: GET /version (获取服务端版本信息)
    Daemon->>Client: 返回版本信息
    Client->>Client: 比较客户端与服务端版本
    Client->>Client: 选择兼容的最高版本
    Client->>Client: 更新客户端API版本
    Client->>Client: 标记协商完成
    
    Note over Client,Daemon: 后续所有API调用使用协商后的版本

3.2 版本协商实现

// NegotiateAPIVersion 执行API版本协商
// 查询服务器支持的API版本,选择客户端和服务器都支持的最高版本
func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
	// 单飞模式确保只执行一次版本协商
	cli.negotiateLock.Lock()
	defer cli.negotiateLock.Unlock()

	// 检查是否已完成协商
	if cli.negotiated.Load() {
		return
	}

	// 1. 获取服务器版本信息
	ping, _ := cli.ping(ctx)
	
	// 2. 确定协商后的版本
	negotiatedVersion := cli.getAPIVersion(ping)
	
	// 3. 更新客户端版本
	cli.version = negotiatedVersion
	cli.negotiated.Store(true)
}

// getAPIVersion 根据ping响应确定API版本
func (cli *Client) getAPIVersion(ping types.Ping) string {
	// 如果服务器提供了API版本信息
	if ping.APIVersion != "" {
		// 选择客户端和服务器都支持的最高版本
		if versions.LessThan(ping.APIVersion, cli.version) {
			return ping.APIVersion // 服务器版本较低,使用服务器版本
		}
		return cli.version // 客户端版本较低或相等,使用客户端版本
	}
	
	// 服务器未提供版本信息,使用fallback版本
	return fallbackAPIVersion
}

// checkVersion 检查并执行版本协商(如果需要)
func (cli *Client) checkVersion(ctx context.Context) error {
	if cli.negotiateVersion && !cli.negotiated.Load() {
		cli.NegotiateAPIVersion(ctx)
	}
	return nil
}

// ClientVersion 返回客户端使用的API版本
func (cli *Client) ClientVersion() string {
	return cli.version
}

// ServerVersion 获取服务器版本信息
func (cli *Client) ServerVersion(ctx context.Context) (types.Version, error) {
	resp, err := cli.get(ctx, "/version", nil, nil)
	if err != nil {
		return types.Version{}, err
	}
	defer ensureReaderClosed(resp)

	var server types.Version
	err = json.NewDecoder(resp.Body).Decode(&server)
	return server, err
}

4. 容器操作API实现

4.1 容器创建API

// 文件路径: client/container_create.go
// ContainerCreate 创建新容器
// 这是客户端最复杂的API之一,需要处理多种配置和版本兼容性
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
	// 1. 参数验证
	if config == nil {
		return container.CreateResponse{}, cerrdefs.ErrInvalidArgument.WithMessage("config is nil")
	}

	var response container.CreateResponse

	// 2. 版本检查(确保API版本协商已完成)
	if err := cli.checkVersion(ctx); err != nil {
		return response, err
	}

	// 3. API版本兼容性处理
	// 检查StopTimeout功能是否支持(API 1.25+)
	if err := cli.NewVersionError(ctx, "1.25", "stop timeout"); config.StopTimeout != nil && err != nil {
		return response, err
	}
	
	// 检查平台指定功能是否支持(API 1.41+)
	if err := cli.NewVersionError(ctx, "1.41", "specify container image platform"); platform != nil && err != nil {
		return response, err
	}
	
	// 检查健康检查间隔功能是否支持(API 1.44+)
	if err := cli.NewVersionError(ctx, "1.44", "specify health-check start interval"); config.Healthcheck != nil && config.Healthcheck.StartInterval != 0 && err != nil {
		return response, err
	}

	// 4. 主机配置兼容性处理
	if hostConfig != nil {
		// API 1.25以下版本不支持AutoRemove
		if versions.LessThan(cli.ClientVersion(), "1.25") {
			hostConfig.AutoRemove = false
		}
		
		// Linux平台且API 1.42以下不支持ConsoleSize
		if platform != nil && platform.OS == "linux" && versions.LessThan(cli.ClientVersion(), "1.42") {
			hostConfig.ConsoleSize = [2]uint{0, 0}
		}

		// 处理挂载选项的版本兼容性
		if versions.LessThan(cli.ClientVersion(), "1.44") {
			for _, m := range hostConfig.Mounts {
				if m.BindOptions != nil {
					if m.BindOptions.ReadOnlyForceRecursive {
						return response, errors.New("bind-recursive=readonly requires API v1.44 or later")
					}
					if m.BindOptions.NonRecursive && versions.LessThan(cli.ClientVersion(), "1.40") {
						return response, errors.New("bind-recursive=disabled requires API v1.40 or later")
					}
				}
			}
		}

		// 标准化能力(capabilities)
		hostConfig.CapAdd = normalizeCapabilities(hostConfig.CapAdd)
		hostConfig.CapDrop = normalizeCapabilities(hostConfig.CapDrop)
	}

	// 5. MAC地址兼容性处理
	// API 1.44+ 容器级MAC地址被废弃
	if versions.GreaterThanOrEqualTo(cli.ClientVersion(), "1.44") {
		config.MacAddress = ""
	}

	// 6. 构建查询参数
	query := url.Values{}
	if platform != nil {
		if p := formatPlatform(*platform); p != "unknown" {
			query.Set("platform", p)
		}
	}

	if containerName != "" {
		query.Set("name", containerName)
	}

	// 7. 构建请求体
	body := container.CreateRequest{
		Config:           config,
		HostConfig:       hostConfig,
		NetworkingConfig: networkingConfig,
	}

	// 8. 发送HTTP请求
	resp, err := cli.post(ctx, "/containers/create", query, body, nil)
	defer ensureReaderClosed(resp)
	if err != nil {
		return response, err
	}

	// 9. 解析响应
	err = json.NewDecoder(resp.Body).Decode(&response)
	return response, err
}

4.2 容器操作API实现

// ContainerStart 启动容器
func (cli *Client) ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error {
	// 构建查询参数
	query := url.Values{}
	if len(options.CheckpointID) != 0 {
		query.Set("checkpoint", options.CheckpointID)
	}
	if len(options.CheckpointDir) != 0 {
		query.Set("checkpoint-dir", options.CheckpointDir)
	}

	// 发送POST请求
	resp, err := cli.post(ctx, "/containers/"+containerID+"/start", query, nil, nil)
	ensureReaderClosed(resp)
	return err
}

// ContainerStop 停止容器
func (cli *Client) ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error {
	query := url.Values{}
	if options.Timeout != nil {
		query.Set("t", strconv.Itoa(*options.Timeout))
	}
	if options.Signal != "" {
		query.Set("signal", options.Signal)
	}

	resp, err := cli.post(ctx, "/containers/"+containerID+"/stop", query, nil, nil)
	ensureReaderClosed(resp)
	return err
}

// ContainerList 列出容器
func (cli *Client) ContainerList(ctx context.Context, options container.ListOptions) ([]types.Container, error) {
	query := url.Values{}

	if options.All {
		query.Set("all", "1")
	}

	if options.Limit != -1 {
		query.Set("limit", strconv.Itoa(options.Limit))
	}

	if options.Size {
		query.Set("size", "1")
	}

	if options.Since != "" {
		query.Set("since", options.Since)
	}

	if options.Before != "" {
		query.Set("before", options.Before)
	}

	if options.Filters.Len() > 0 {
		filterJSON, err := filters.ToJSON(options.Filters)
		if err != nil {
			return nil, err
		}
		query.Set("filters", filterJSON)
	}

	resp, err := cli.get(ctx, "/containers/json", query, nil)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var containers []types.Container
	err = json.NewDecoder(resp.Body).Decode(&containers)
	return containers, err
}

// ContainerRemove 删除容器
func (cli *Client) ContainerRemove(ctx context.Context, containerID string, options container.RemoveOptions) error {
	query := url.Values{}
	if options.RemoveVolumes {
		query.Set("v", "1")
	}
	if options.RemoveLinks {
		query.Set("link", "1")
	}
	if options.Force {
		query.Set("force", "1")
	}

	resp, err := cli.delete(ctx, "/containers/"+containerID, query, nil)
	ensureReaderClosed(resp)
	return err
}

5. 连接管理和传输层

5.1 连接类型支持

graph TB
    subgraph "客户端连接管理"
        ConnectionManager[连接管理器]
    end
    
    subgraph "Unix Socket连接"
        UnixSocketClient[Unix Socket Client]
        UnixSocketPath["/var/run/docker.sock"]
    end
    
    subgraph "TCP连接"
        TCPClient[TCP Client]
        TCPAddr["tcp://localhost:2376"]
        TLSConfig[TLS配置]
    end
    
    subgraph "Windows Named Pipe"
        NamedPipeClient[Named Pipe Client]  
        PipePath["npipe:////./pipe/docker_engine"]
    end
    
    ConnectionManager --> UnixSocketClient
    ConnectionManager --> TCPClient
    ConnectionManager --> NamedPipeClient
    
    UnixSocketClient --> UnixSocketPath
    TCPClient --> TCPAddr
    TCPClient --> TLSConfig
    NamedPipeClient --> PipePath

5.2 连接创建实现

// parseHost 解析主机地址,支持多种连接类型
func parseHost(host string) (proto string, addr string, basePath string, err error) {
	var u *url.URL
	u, err = url.Parse(host)
	if err != nil {
		return "", "", "", err
	}

	switch u.Scheme {
	case "unix":
		// Unix socket连接
		return "unix", u.Path, "", nil
		
	case "tcp":
		// TCP连接
		return "tcp", u.Host, "", nil
		
	case "npipe":
		// Windows命名管道
		return "npipe", u.Path, "", nil
		
	case "fd":
		// 文件描述符连接
		return "fd", u.Path, "", nil
		
	case "ssh":
		// SSH连接(通过SSH隧道)
		return "ssh", u.Host, "", nil
		
	case "http", "https":
		// HTTP/HTTPS连接
		return u.Scheme, u.Host, u.Path, nil
		
	default:
		return "", "", "", fmt.Errorf("unsupported connection type %q", u.Scheme)
	}
}

// newHTTPClient 创建HTTP客户端
func newHTTPClient(host string, tlsConfig *tls.Config) (*http.Client, error) {
	proto, addr, _, err := parseHost(host)
	if err != nil {
		return nil, err
	}

	transport := &http.Transport{
		TLSClientConfig: tlsConfig,
	}

	switch proto {
	case "unix":
		// Unix socket连接配置
		transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
			return (&net.Dialer{}).DialContext(ctx, "unix", addr)
		}
		
	case "npipe":
		// Windows命名管道连接配置
		transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
			return winio.DialPipeContext(ctx, addr)
		}
		
	case "tcp":
		// TCP连接配置
		transport.DialContext = (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}).DialContext
	}

	return &http.Client{
		Transport:     transport,
		CheckRedirect: CheckRedirect, // 自定义重定向处理
		Timeout:       0,             // 不设置全局超时,由请求上下文控制
	}, nil
}

6. 错误处理机制

6.1 错误类型定义

// 客户端错误接口
type APIError interface {
	error
	StatusCode() int
}

// 版本错误
type versionError struct {
	requiredVersion string
	feature         string
}

func (e versionError) Error() string {
	return fmt.Sprintf("feature %q requires API version %s or higher", e.feature, e.requiredVersion)
}

// 连接错误
type connectionError struct {
	host string
	err  error
}

func (e connectionError) Error() string {
	return fmt.Sprintf("cannot connect to Docker daemon at %s: %v", e.host, e.err)
}

6.2 错误处理实现

// NewVersionError 创建版本错误
func (cli *Client) NewVersionError(ctx context.Context, requiredVersion, feature string) error {
	if err := cli.checkVersion(ctx); err != nil {
		return err
	}
	
	if versions.LessThan(cli.version, requiredVersion) {
		return versionError{
			requiredVersion: requiredVersion,
			feature:         feature,
		}
	}
	return nil
}

// 统一错误处理函数
func (cli *Client) wrapError(err error) error {
	if err == nil {
		return nil
	}

	// 网络连接错误
	if netErr, ok := err.(net.Error); ok {
		if netErr.Timeout() {
			return fmt.Errorf("timeout: %v", err)
		}
		return connectionError{host: cli.host, err: err}
	}

	// 上下文取消错误
	if errors.Is(err, context.Canceled) {
		return err
	}

	// 其他错误
	return err
}

7. 实际使用示例

7.1 基础使用示例

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/moby/moby/client"
	"github.com/moby/moby/api/types/container"
	"github.com/moby/moby/api/types/image"
	"github.com/moby/moby/api/types/network"
	"github.com/docker/go-connections/nat"
)

func main() {
	// 1. 创建客户端(从环境变量配置)
	cli, err := client.NewClientWithOpts(
		client.FromEnv,                    // 从环境变量读取配置
		client.WithAPIVersionNegotiation(), // 启用API版本协商
	)
	if err != nil {
		log.Fatal("创建客户端失败:", err)
	}
	defer cli.Close()

	ctx := context.Background()

	// 2. 获取系统信息
	info, err := cli.Info(ctx)
	if err != nil {
		log.Fatal("获取系统信息失败:", err)
	}
	fmt.Printf("Docker版本: %s\n", info.ServerVersion)
	fmt.Printf("容器数量: %d\n", info.Containers)

	// 3. 拉取镜像
	fmt.Println("正在拉取nginx:latest镜像...")
	reader, err := cli.ImagePull(ctx, "nginx:latest", image.PullOptions{})
	if err != nil {
		log.Fatal("拉取镜像失败:", err)
	}
	defer reader.Close()
	
	// 显示拉取进度
	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		var progress struct {
			Status   string `json:"status"`
			Progress string `json:"progress,omitempty"`
		}
		json.Unmarshal(scanner.Bytes(), &progress)
		if progress.Progress != "" {
			fmt.Printf("\r%s: %s", progress.Status, progress.Progress)
		} else {
			fmt.Println(progress.Status)
		}
	}

	// 4. 创建容器
	containerConfig := &container.Config{
		Image:        "nginx:latest",
		ExposedPorts: nat.PortSet{"80/tcp": struct{}{}},
		Labels: map[string]string{
			"example": "client-demo",
		},
	}

	hostConfig := &container.HostConfig{
		PortBindings: nat.PortMap{
			"80/tcp": []nat.PortBinding{
				{HostIP: "0.0.0.0", HostPort: "8080"},
			},
		},
		RestartPolicy: container.RestartPolicy{
			Name: "unless-stopped",
		},
	}

	networkingConfig := &network.NetworkingConfig{}

	resp, err := cli.ContainerCreate(
		ctx,
		containerConfig,
		hostConfig,
		networkingConfig,
		nil,
		"nginx-demo",
	)
	if err != nil {
		log.Fatal("创建容器失败:", err)
	}

	fmt.Printf("容器已创建,ID: %s\n", resp.ID)

	// 5. 启动容器
	if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil {
		log.Fatal("启动容器失败:", err)
	}
	fmt.Println("容器已启动")

	// 6. 获取容器日志
	logOptions := container.LogsOptions{
		ShowStdout: true,
		ShowStderr: true,
		Follow:     false,
		Tail:       "10",
	}

	logReader, err := cli.ContainerLogs(ctx, resp.ID, logOptions)
	if err != nil {
		log.Fatal("获取容器日志失败:", err)
	}
	defer logReader.Close()

	fmt.Println("容器日志:")
	io.Copy(os.Stdout, logReader)

	// 7. 列出运行中的容器
	containers, err := cli.ContainerList(ctx, container.ListOptions{})
	if err != nil {
		log.Fatal("列出容器失败:", err)
	}

	fmt.Printf("\n运行中的容器 (%d个):\n", len(containers))
	for _, c := range containers {
		fmt.Printf("- %s: %s (%s)\n", 
			c.Names[0], c.Image, c.State)
	}
}

7.2 高级配置示例

package main

import (
	"context"
	"crypto/tls"
	"net/http"
	"time"

	"github.com/moby/moby/client"
)

func createAdvancedClient() (*client.Client, error) {
	// 创建自定义TLS配置
	tlsConfig := &tls.Config{
		InsecureSkipVerify: false,
		MinVersion:         tls.VersionTLS12,
	}

	// 创建自定义HTTP传输
	transport := &http.Transport{
		TLSClientConfig:     tlsConfig,
		MaxIdleConns:        10,
		IdleConnTimeout:     30 * time.Second,
		DisableCompression:  true,
	}

	// 创建自定义HTTP客户端
	httpClient := &http.Client{
		Transport: transport,
		Timeout:   60 * time.Second,
	}

	// 创建Docker客户端
	cli, err := client.NewClientWithOpts(
		client.WithHost("tcp://remote-docker:2376"),    // 远程Docker主机
		client.WithVersion("1.52"),                     // 指定API版本
		client.WithHTTPClient(httpClient),              // 自定义HTTP客户端
		client.WithHTTPHeaders(map[string]string{       // 自定义HTTP头
			"X-Custom-Header": "my-value",
		}),
	)
	if err != nil {
		return nil, err
	}

	return cli, nil
}

// 带上下文和超时的操作示例
func containerOperationWithTimeout() error {
	cli, err := createAdvancedClient()
	if err != nil {
		return err
	}
	defer cli.Close()

	// 创建带超时的上下文
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// 执行操作
	containers, err := cli.ContainerList(ctx, container.ListOptions{All: true})
	if err != nil {
		return err
	}

	fmt.Printf("找到 %d 个容器\n", len(containers))
	return nil
}

通过这个详细的客户端SDK分析,我们可以看到Moby客户端采用了优雅的设计模式,提供了丰富的配置选项和强大的功能,使得开发者可以轻松地与Docker守护进程进行交互。