Docker Engine SDKでリモートのDocker daemonにSSH経由で接続する

以前の記事でDocker Engine SDKの使い方について解説しました。この頃 (2020/12) はビルドに少し工夫 が必要でしたが、今 (2022/09) では go mod tidy で用意された go.mod でそのままビルドできるようになっていました。 この記事では、Docker Engine SDKを使って、リモートサーバー上のDocker daemonSSH経由で接続する方法について書きます。 haibara-works.hatenablog.com

結論

下記のコードは、ssh://user@example.com で示されるサーバー上にあるDocker daemonに接続してimageを一覧表示するサンプルです。 ポイントは github.com/docker/cli/cli/connhelper をimportしていることです。

package main

import (
    "context"
    "fmt"
    "net/http"

    "github.com/docker/cli/cli/connhelper"
    "github.com/docker/docker/api/types"
    "github.com/docker/docker/client"
)

func main() {
    ctx := context.Background()

    opts, err := withSSHConn("ssh://user@example.com")
    if err != nil {
        panic(err.Error())
    }

    cli, err := client.NewClientWithOpts(opts...)
    if err != nil {
        panic(err.Error())
    }

    images, err := cli.ImageList(ctx, types.ImageListOptions{})
    if err != nil {
        panic(err.Error())
    }

    for _, image := range images {
        fmt.Println(image.RepoTags)
    }
}

func withSSHConn(host string) ([]client.Opt, error) {
    helper, err := connhelper.GetConnectionHelper(host)
    if err != nil {
        return nil, err
    }

    httpClient := &http.Client{
        Transport: &http.Transport{
            DialContext: helper.Dialer,
        },
    }

    return []client.Opt{
        client.WithHTTPClient(httpClient),
        client.WithHost(helper.Host),
        client.WithDialContext(helper.Dialer),
    }, nil
}

失敗した方法

dockerコマンドを使ってSSH経由でリモートのdaemonに接続するには、以下のようにcontextを作成します。

$ docker context create ssh-to-server --docker "host=ssh://user@example.com"
$ docker context use ssh-to-server

# リモートのdaemonに対する操作
$ docker image ls

Protect the Docker daemon socket | Docker Documentation

Docker Engine SDKでもこれと同様にできるかなと思い、以下のようにclient.NewClientWithOptsに渡すWithHostSSHのURLを指定していました。

cli, err := client.NewClientWithOpts(client.WithHost("ssh://user@example.com"))

しかしこれでは上手くいかず、下記のエラーが発生します。

error during connect: Get "http://user%40example.com/v1.41/images/json": dial tcp: lookup user@example.com: no such host