DRYな備忘録

Don't Repeat Yourself.

GoのコードからDockerコンテナの起動を実装する

やったこと

  • docker clientをGoのコードからimportする
  • container作成
  • container起動
  • container停止(& 自動削除)

苦労したこと

  • けっこうdocker(現moby)のコードの移り変わりが激しくて、vendorを固定するのが苦労した

できなかったこと

main.go

package main

import (
    "context"
    "fmt"
    "time"

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

func main() {

    c, err := client.NewEnvClient()
    if err != nil {
        panic(fmt.Errorf("NewEnvClient: %v", err))
    }

    ctx := context.Background()

    /* 後述する「うごかなかったもの」がここに来ます */

    // 1) コンテナをつくる
    cc := &container.Config{
        Image:        "nginx",
        ExposedPorts: nat.PortSet{nat.Port("80"): struct{}{}},
        // Entrypoint:   strslice.StrSlice{"nginx", "-g", "daemon off;"},
    }
    hc := &container.HostConfig{
        // `--port 8080:80`
        PortBindings: nat.PortMap{
            nat.Port("80"): []nat.PortBinding{nat.PortBinding{HostPort: "8080"}},
        },
        // `--rm`
        AutoRemove: true,
    }
    // docker network らへん
    nc := &network.NetworkingConfig{}

    body, err := c.ContainerCreate(ctx, cc, hc, nc, "foo")
    if err != nil {
        panic(fmt.Errorf("ContainerCreate: %v", err))
    }
    fmt.Printf("Created Container:\n%+v\n", body)

    // 2) コンテナのスタート
    if err := c.ContainerStart(ctx, body.ID, types.ContainerStartOptions{}); err != nil {
        panic(fmt.Errorf("ContainerStart: %v", err))
    }

    // あること確認
    check(ctx, c)

    // 3) 20秒だけ延命
    time.Sleep(20 * time.Second)

    // 4) コンテナの停止
    //    --rm を投げてるのでコンテナ自体も削除される
    timeout := 5 * time.Second
    if err := c.ContainerStop(ctx, body.ID, &timeout); err != nil {
        panic(fmt.Errorf("ContainerStop: %v", err))
    }

    // ないこと確認
    check(ctx, c)
}

// コンテナのリストを取得する確認用の関数
func check(ctx context.Context, c *client.Client) {

    containers, err := c.ContainerList(ctx, types.ContainerListOptions{All: true})
    if err != nil {
        panic(err)
    }

    fmt.Println("List of containers")
    for _, container := range containers {
        fmt.Printf(" - %s (%s)\n", container.ID, container.Image)
    }
}

これで、 go run main.go とすると、20秒だけホストの8080にnginxが現れるということになります。

f:id:otiai10:20170524163022p:plain

f:id:otiai10:20170524162509p:plain

f:id:otiai10:20170524163152p:plain

よっしゃ。

うごかなかったもの

   rp, err := c.ImagePull(ctx, "nginx:latest", types.ImagePullOptions{})
    if err != nil {
        panic(fmt.Errorf("ImagePull: %v", err))
    }
    defer rp.Close()
    // エラーは無いんだけど、ContainerCreateで参照できないんだよなあ

    // じゃあImageSaveかなと思ったけど引数がImageIDsなので、とりあえずハードコードで投げる
    rs, err := c.ImageSave(ctx, []string{"bf2b4c2d7bf53b4d0d28fa6af60e51c418317d2ada40ed6e5d5c290248d2a469"})
    if err != nil {
        panic(fmt.Errorf("ImageSave: %v", err))
    }
    defer rs.Close()
    // まあ無いと言われる

    // 確認用
    images, err := c.ImageList(ctx, types.ImageListOptions{})
    if err != nil {
        panic(fmt.Errorf("ImageList: %v", err))
    }
    for _, img := range images {
        fmt.Printf("%+v\n", img)
    }

    // ImagePullで得たio.ReadCloserに色々入ってんのかな?
    // やたらでかそうだし、もしかしてイメージそのもの?

この部分が動かなかったので、あらかじめホストマシンで docker pull nginx を実行しないと動かないコードになってしまった。くやしい。次はこれを解決したいです。

DRYな備忘録として

追記

できました otiai10.hatenablog.com

Docker

Docker