LoginSignup
15
7

More than 5 years have passed since last update.

mobyを使ってgolangからDockerコンテナを操作する

Last updated at Posted at 2017-09-23

はじめに

  • golangから動的にDockerコンテナを動的に扱いたくて方法を調べた。
  • ソースコードから部分的に抜粋してきてるので、もしかしたらそのまま動かないかもしれません(ごめんなさい)

参考リンク集

パッケージのインポートでハマる

  • 2017/09/23時点では、 github.com/moby/moby および github.com/docker/docker がほぼ同一のAPIを提供している
  • 使用する構造体は、それぞれ適切なパッケージからインポートしなければエラーが起きる
  • 現状、下記のようにインポート先を分ければ動くらしい
    • Docker APIへの接続クライアントは github.com/moby/moby からインポートする
    • それ以外は github.com/docker/docker からインポートする
import (
    "log"

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

func main() {
    cl, err := client.NewEnvClient()
    if err != nil {
        log.Fatalf("%v", err)
    }
}

イメージ関係

イメージのプル

import (
    "log"
    "context"

    "github.com/docker/docker/api/types"
)

func main() {
    opts := types.ImagePullOptions{}
    _, err := cl.ImagePull(context.TODO(), "<image_name>", opts)
    if err != nil {
        log.Fatalf("%v", err)
    }
}

Dockerfileからイメージビルド

  • Dockerfile は tarファイル として固めておく
    • gzip してもしなくてもOK
  • ビルド開始後、処理完了前に抜けると、ビルドが完了しなかった
    • ctx.Done() 待ってみたけど、返ってこなかった
    • こういうときどうするのが正解か分かんなかったので、とりあえずsleep
import (
    "os"
    "log"
    "context"
    "bufio"

    "github.com/docker/docker/api/types"
)

func main() {

    // tarファイル取得
    tarfile, _ := os.Open("<tarfile_path>")
    defer tarfile.Close()

    // イメージのビルド
    opts := types.ImageBuildOptions{
        Tags:        []string{"<repository_name>"},
        ForceRemove: true,
    }
    resp, err := cl.ImageBuild(context.TODO(), bufio.NewReader(tarfile), opts)
    if err != nil {
        log.Fatalf("%v", err)
    }
    defer resp.Body.Close()

    // ちょっと待たないと先に死んじゃう
    time.Sleep(100 * time.Millisecond)
}

(おまけ) tarファイルの作成

import (
    "os"
    "ioutil"

    "archive/tar"
    "compress/gzip"
)

func main() {

    // tarファイル生成
    tarfile, _ := os.Create("<tarfile_path>")
    defer tarfile.Close()

    // gzip
    gzw := gzip.NewWriter(tarfile)
    defer gzw.Close()

    // tar
    tw := tar.NewWriter(gzw)
    defer tw.Close()

    // contentの取得
    body, _ := ioutil.ReadFile("<contentfile_path>")

    header := &tar.Header{
        Name: "<dockerfile_path_in_tarfile>",
        Size: int64(len(body)),
    }
    if err := tw.WriteHeader(header); err != nil {
        log.Fatalf("%v", err)
    }
    if _, err := tw.Write(body); err != nil {
        log.Fatalf("%v", err)
    }
}

コンテナ関係

コンテナ作成

  • 前述の通り、パッケージのインポート先が github.com/docker/docker になっていることに注意
import (
    "context"
    "log"

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

func main() {
    config := &container.Config{
        Image: "<image_name>",
    }
    hostConfig := &container.HostConfig{}
    netConfig := &network.NetworkingConfig{}
    resp, err := cl.ContainerCreate(context.TODO(), config, hostConfig, netConfig, "<container_name>")
    if err != nil {
        log.Fatalf("%v", err)
    }

    log.Printf("%v", resp.ID) // ContainerID
}
  • ゲストのポートをホストにバインドする
  • Dockerfileで EXPOSE していても、 configExposedPorts は必要らしい(これで小一時間ハマった)
config := &container.Config{
    Image:        "<image_name>",
    ExposedPorts: nat.PortSet{nat.Port("<guest_port>"): struct{}{}},
}

hostConfig := &container.HostConfig{
    PortBindings: nat.PortMap{
        nat.Port("<guest_port>"): []nat.PortBinding{{HostPort: "<host_port>"}},
    },
}
  • 特定のネットワークに所属させる
netConfig := &network.NetworkingConfig{
    EndpointsConfig: map[string]*network.EndpointSettings{
        "<network_name>": {
            IPAMConfig: &network.EndpointIPAMConfig{
                IPv4Address: "<guest_ip_address>",
            },
        },
    },
}
  • AutoRemove と AutoRestart
  • どちらかのみ設定可能(同時に設定すると起動時にエラーする)
hostConfig := &container.HostConfig{
    RestartPolicy: container.RestartPolicy{
        Name: "always",
    },
    AutoRemove: false,
}

コンテナへのファイルコピー

  • コピーするファイルの渡し方でちょっと迷った
  • tarファイル をオープンして、 bufio.NewReader() でラップするとうまくいった
import (
    "os"
    "log"
    "bufio"

    "github.com/docker/docker/api/types"
)

func main() {

    // archive の オープン
    archive, _ := os.Open("<archive_path>")
    defer archive.Close()

    // コンテナへコピー
    opts := types.CopyToContainerOptions{}
    if err := cl.CopyToContainer(context.TODO(), "<container_id>", "<guest_destination_path>", bufio.NewReader(archive), opts); err != nil {
        log.Fatalf("%v", err)
    }
}

ネットワーク関係

ネットワーク作成

import (
    "context"
    "log"

    "github.com/docker/docker/api/types"
    "github.com/docker/docker/api/types/network"
)

func main() {
    opts := types.NetworkCreate{
        IPAM: &network.IPAM{
            Config: []network.IPAMConfig{
                {Subnet: "<subnet>"},
            },
        },
    }
    if _, err = cl.NetworkCreate(context.TODO(), "<network_name>", opts); err != nil {
        log.Fatalf("%v", err)
    }
}

一覧取得関係

  • フィルタ条件の渡し方でちょっと迷う
    • 基本的には、 map[string][]string をJSON形式になおしてあげるとOK

イメージ一覧の取得

import (
    "context"
    "log"
    "encoding/json"

    "github.com/docker/docker/api/types"
    "github.com/docker/docker/api/types/filters"
)

func main() {

    // フィルタ条件
    filtMap := map[string][]string{"reference": {"<repository_name>:<version>"}}
    filtBytes, _ := json.Marshal(filtMap)
    filt, err := filters.FromParam(string(filtBytes))
    if err != nil {
        log.Fatalf("%v", err)
    }

    opts := types.ImageListOptions{Filters: filt}
    images, err := cl.ImageList(context.TODO(), opts)
    if err != nil {
        log.Fatalf("%v", err)
    }
}

コンテナ一覧の取得

  • All にすると、停止中コンテナも含める
  • Quiet にすると、コンテナIDだけ返ってくる
import (
    "context"
    "log"
    "encoding/json"

    "github.com/docker/docker/api/types"
    "github.com/docker/docker/api/types/filters"
)

func main() {

    // フィルタ条件
    filtMap := map[string][]string{"name": {"<container_name>"}}
    filtBytes, _ := json.Marshal(filtMap)
    filt, _ := filters.FromParam(string(filtBytes))

    opts := types.ContainerListOptions{
        All:     true,
        Quiet:   true,
        Filters: filt,
    }
    resp, err := cl.ContainerList(context.TODO(), opts)
    if err != nil {
        log.Fatalf("%v", err)
    }
}

ネットワーク一覧取得

import (
    "context"
    "log"
    "encoding/json"

    "github.com/docker/docker/api/types"
    "github.com/docker/docker/api/types/filters"
)

func main() {

    // フィルタ条件
    filtMap := map[string][]string{"name": {"<network_name>"}}
    filtBytes, _ := json.Marshal(filtMap)
    filt, err := filters.FromParam(string(filtBytes))
    if err != nil {
        log.Fatalf("%v", err)
    }

    opts := types.NetworkListOptions{Filters: filt}
    nets, err := cl.NetworkList(context.TODO(), opts)
    if err != nil {
        log.Fatalf("%v", err)
    }
}
15
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
7