问题

问题

介绍

docker我正在尝试在 Linux VM 上使用以下版本(uname -a返回Linux xen 4.1.17-yocto-standard #1 SMP PREEMPT Thu Jun 2 13:29:47 PDT 2016 x86_64 GNU/Linux),从docker_gitBitBake 配方

如果我尝试运行docker version,我会得到以下输出:

Client version: 1.6.2
Client API version: 1.18
Go version (client): go1.3
Git commit (client): 7c8fca2-dirty
OS/Arch (client): linux/amd64

然后命令挂起。

它应该是什么样子

我尝试docker version在可运行的 docker 安装(Ubuntu 14.04)上执行,并得到以下输出:

Client version: 1.6.2
Client API version: 1.18
Go version (client): go1.2.1
Git commit (client): 7c8fca2
OS/Arch (client): linux/amd64
Server version: 1.6.2
Server API version: 1.18
Go version (server): go1.2.1
Git commit (server): 7c8fca2
OS/Arch (server): linux/amd64

因此,我假设在获取服务器信息时出现了某种错误。

进一步研究

我对 Go 不太熟悉,因此当我试图弄清楚这里到底发生了什么时,这部分内容可能会让我感到困惑。

我开始看这部分api/client/version.goDocker 源代码:

var versionTemplate = `Client:
 Version:      {{.Client.Version}}
 API version:  {{.Client.APIVersion}}
 Go version:   {{.Client.GoVersion}}
 Git commit:   {{.Client.GitCommit}}
 Built:        {{.Client.BuildTime}}
 OS/Arch:      {{.Client.Os}}/{{.Client.Arch}}{{if .Client.Experimental}}
 Experimental: {{.Client.Experimental}}{{end}}{{if .ServerOK}}
Server:
 Version:      {{.Server.Version}}
 API version:  {{.Server.APIVersion}}
 Go version:   {{.Server.GoVersion}}
 Git commit:   {{.Server.GitCommit}}
 Built:        {{.Server.BuildTime}}
 OS/Arch:      {{.Server.Os}}/{{.Server.Arch}}{{if .Server.Experimental}}
 Experimental: {{.Server.Experimental}}{{end}}{{end}}`

它继续到这一部分:

vd := types.VersionResponse{
    Client: &types.Version{
        Version:      dockerversion.Version,
        APIVersion:   cli.client.ClientVersion(),
        GoVersion:    runtime.Version(),
        GitCommit:    dockerversion.GitCommit,
        BuildTime:    dockerversion.BuildTime,
        Os:           runtime.GOOS,
        Arch:         runtime.GOARCH,
        Experimental: utils.ExperimentalBuild(),
    },
}

engine-api/types/client.go

// VersionResponse holds version information for the client and the server
type VersionResponse struct {
    Client *Version
    Server *Version
} 

因此,此时需要做的就是为Server成员(类型)分配一些值。这发生在上述分配*Version之后的部分:vd

serverVersion, err := cli.client.ServerVersion(context.Background())
if err == nil {
    vd.Server = &serverVersion
}

函数定义ServerVersion如下engine-api/client/version.go

// ServerVersion returns information of the docker client and server host.
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
    }

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

据我所知,上述get函数调用指向client/request.go来自 Docker 的engine API仓库

// getWithContext sends an http request to the docker API using the method GET with a specific go context.
func (cli *Client) get(ctx context.Context, path string, query url.Values, headers map[string][]string) (*serverResponse, error) {
    return cli.sendRequest(ctx, "GET", path, query, nil, headers)
}

在哪里:

  • ctxcontext.Background()
  • path/version
  • query
  • headers

并且这个文档sendRequest来自vendor/src/google.golang.org/grpc/call.go

// sendRequest writes out various information of an RPC such as Context and Message.
func sendRequest(ctx context.Context, codec Codec, callHdr *transport.CallHdr, t transport.ClientTransport, args interface{}, opts *transport.Options) (_ *transport.Stream, err error) {
    stream, err := t.NewStream(ctx, callHdr)
    if err != nil {
        return nil, err
    }
    defer func() {
        if err != nil {
            if _, ok := err.(transport.ConnectionError); !ok {
                t.CloseStream(stream, err)
            }
        }
    }()
    // TODO(zhaoq): Support compression.
    outBuf, err := encode(codec, args, compressionNone)
    if err != nil {
        return nil, transport.StreamErrorf(codes.Internal, "grpc: %v", err)
    }
    err = t.Write(stream, outBuf, opts)
    if err != nil {
        return nil, err
    }
    // Sent successfully.
    return stream, nil
}

这只是猜测了一段时间,现在我担心自己可能找错了地方。

问题

  • 什么原因导致docker versiondocker run hello-worlddocker imagesdocker psdocker info挂起以及如何修复?
  • 或者有没有更有效的方法来检查这个错误的原因?

答案1

您的strace输出强烈表明 Docker 客户端无法与 Docker 守护进程通信,后者默认在 创建一个套接字/var/run/docker.sock

Docker 守护进程应该是一个系统服务(在 systemd 上,位于 ,/lib/systemd/system/docker.service套接字配置为),但它可以使用后跟任何可选选项来/lib/systemd/system/docker.socket独立启动。/usr/bin/docker daemon

strace应该守护进程而不是客户

strace在 Docker 守护进程上使用

  1. 获取 Docker 守护进程的进程 ID。这两个命令都会将 PID 存储在名为 的变量中$DOCKER_PID

    • 直接从插座:

      DOCKER_PID=$(sudo lsof -Ua /var/run/docker.sock | awk '/^docker/ {print $2}' | head -1)
      
    • 系统:

      DOCKER_PID=$(systemctl show -p MainPID docker.service | awk -F'=' '{print $NF}')
      
    • 其他:

      DOCKER_PID=$(ps aux | grep 'docker daemon' | grep -v 'grep' | awk '{print $2}' | head -1)
      
  2. strace现在您有了 PID,可以在 Docker 守护进程上使用:

    sudo strace -vvvfts1000 -p $DOCKER_PID
    
  3. 在单独的终端中,运行通常挂在 Docker 客户端中的命令。

    docker version
    
  4. 观察straceDocker 守护进程来见证从套接字监听端开始发生的情况。

解释strace输出

以下是运行时守护进程应该执行的操作docker version

  1. 阅读客户发送的内容:

    [pid 14291] 12:34:36 <... read resumed> "GET /v1.22/version HTTP/1.1\r\nHost: \r\nUser-Agent: Docker-Client/1.10.3 (linux)\r\n\r\n", 4096) = 81
    
  2. 收集有关系统的信息:

    [pid 14291] 12:34:36 uname({sysname="Linux", nodename="node51", release="4.4.0-22-generic", version="#40-Ubuntu SMP Thu May 12 22:03:46 UTC 2016", machine="x86_64", domainname="(none)"}) = 0
    
  3. 回复客户端有关系统的信息:

    [pid 14291] 12:34:36 write(3, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nServer: Docker/1.10.3 (linux)\r\nDate: Mon, 13 Jun 2016 17:34:36 GMT\r\nContent-Length: 194\r\n\r\n{\"Version\":\"1.10.3\",\"ApiVersion\":\"1.22\",\"GitCommit\":\"20f81dd\",\"GoVersion\":\"go1.6.1\",\"Os\":\"linux\",\"Arch\":\"amd64\",\"KernelVersion\":\"4.4.0-22-generic\",\"BuildTime\":\"Wed, 20 Apr 2016 14:19:16 -0700\"}\n", 334) = 334
    
  4. 然后客户端(docker version)显示服务器返回的信息:

    Server:
     Version:      1.10.3
     API version:  1.22
     Go version:   go1.6.1
     Git commit:   20f81dd
     Built:        Wed, 20 Apr 2016 14:19:16 -0700
     OS/Arch:      linux/amd64
    

在您的问题中,您的 Docker 守护进程显然没有执行步骤#3,因为如果它执行了,客户端就会看到回复,但客户端没有收到任何东西。

您应该能够使用此信息来找出 Docker 守护进程未回复客户端请求的原因。

可能的原因

您提供的信息不足以查明 Docker 客户端无法从 Docker 守护程序获取回复的原因,但这里有一些提示:

  • Docker 守护进程是否正在运行?
  • 如果在前台启动 Docker 守护进程会发生什么?:sudo docker daemon
  • Docker 守护进程是否正在监听 处的套接字/var/run/docker.sock? 应该在某处sudo lsof -p $DOCKER_PID显示“ ”。/var/run/docker.sock type=STREAM
  • 是否存在可以阻止客户端或守护进程中的某些内容的安全策略?在 Linux 上,SELinux 和 AppArmor 可能会引起混淆,因为为它们设置的策略可能会拒绝访问。
  • 在守护进程中strace,如果您没有从客户端收到 HTTP GET 请求,则意味着服务器没有从套接字接收到任何内容。
  • 如果您这样做了docker version并在守护进程中看到strace没有uname()调用,则守护进程甚至没有尝试获取有关系统的信息。
  • write()如果在守护进程中看到调用strace,则意味着守护进程已经回复,但是客户端没有看到它。
  • 这可能是您正在使用的旧版 Docker 中的一个已知问题。请尝试升级。

相关内容