通常来说,docker daemon 只能通过 unix domain socket 进行连接,但是通过 SSH 隧道可以连接另一台机器上的 docker daemon。docker 官方也提供了 SSH 隧道连接远程 docker daemon 的文档。
连接 docker daemon 可以通过 docker 官方的命令行工具,也可以通过 SDK 进行连接。
命令行方式
使用 docker 命令行连接之前,需要配置好 SSH 免密登录账户,并且该账户需要有访问 docker unix domain socket 的权限。配置完成之后,就可以直接使用命令行进行连接,如下图
官方文档也提供了一些比较高级的用法,比如创建命名的 context 用于连接特定的 docker daemon, 感兴趣的读者可以参考文末的链接进行探索。
SDK 方式
使用 SDK 方式需要首先建立 SSH 连接,然后在这个连接上传输 docker daemon 和 docker client 之间的协议。这也就是通常说的隧道。
dockerDialer 函数的参数分别是host, user, port, priKey, 这几个参数用于建立 SSH 连接,之后在这个连接上转发 unix 协议,并且将地址设置为 /var/run/docker.sock。
dockerDialer 的返回值是一个连接,将这个连接用于创建 httpClient,并把这个httpClient 传递给 docker sdk 的 NewClientWithOpts 函数即可。
func GetRemoteDockerClient(host, user, port string, prikey []byte) (*client.Client, error) {
dockerDialer := func(network string, addr string) (net.Conn, error) {
signer, err := ssh.ParsePrivateKey(prikey)
if err != nil {
return nil, err
}
sshConfig := &ssh.ClientConfig{
User: user,
Timeout: time.Second * 30,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
}
conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", host, port), sshConfig)
if err != nil {
return nil, errors.Wrapf(err, "dail tcp %s err: %v", host, err)
}
return conn.Dial("unix", "/var/run/docker.sock")
}
httpClient := http.Client{
Transport: &http.Transport{
Dial: dockerDialer,
TLSHandshakeTimeout: time.Second * 30,
IdleConnTimeout: time.Second * 30,
ResponseHeaderTimeout: time.Second * 30,
},
}
return client.NewClientWithOpts(client.WithAPIVersionNegotiation(), client.WithHTTPClient(&httpClient))
}
参考链接
- docker remote doc: https://docs.docker.com/engine/security/protect-access/