2017/09/04

GolangでDocker Remote APIを使ってDockerfileからイメージ作成する方法

photo by Daniel Ramirez

golangからDocker Remote APIを使ってイメージ・コンテナを操作する方法を覚えたので、次はDockerfileからイメージを作成し、実行する方法を学んだ。

Docker Remote APIの基本的な使い方は以下の記事を参照ください。


かなりハマったので、皆さんの助けになればと思う。

環境は以下のとおり。

  • Mac OSX
  • go 1.8.3
  • Docker for Mac 17.6.1-ce


Dockerfileの作成し、tar.gzでアーカイブする


Docker Remote APIでDockerfileを扱う場合はtar.gz形式でアーカイブする
このことを知らずにローカルにDockerfileを作って参照してError response from daemon: Cannot locate specified Dockerfile: Dockerfileというエラーがでてかなりハマった。

ディレクトリ構成は以下のとおり。
.
├── Dockerfile
└── script.sh


Dockerfileを作成する。
FROM alpine

ADD ./ /root
WORKDIR /root

CMD ["sh", "script.sh"]

alpineのイメージを使い、コンテナにscript.shを追加、起動時にshellで実行するようにしている。
シェルスクリプトの中身は、サンプルなので文字列を出力するだけの簡単なもの。
#! /bin/sh

echo "hello world from container"


最後にtarコマンドでアーカイブする。gzipで圧縮するかどうかは任意。(だから正確にはtar.gzではなくtarな気がするけど雰囲気でtar.gzという拡張子にしている)

また今回はDockerfile.tar.gzというファイル名にしたが、任意の名前でも良い。
# Dockerfileがあるディレクトリに移動
$ cd /path/to/Dockerfile

# ディレクトリ配下すべてをアーカイブ化する
$ tar -zcvf Dockerfile.tar.gz ./

$ ls
Dockerfile Dockerfile.tar.gz script.sh

これでDockerfileを使う準備ができた。
あとはDocker Remote APIにDockerfileを渡してイメージを作成する。



Dockerfileからイメージを作成し、コンテナを起動する


package main

import (
  "fmt"
  "io"
  "io/ioutil"
  "os"
  "time"

  "github.com/docker/docker/api/types"
  "github.com/docker/docker/api/types/container"
  "github.com/moby/moby/client"
  "golang.org/x/net/context"
)

func check(err error) {
  if err != nil {
    panic(err)
  }
}

func main() {
  ctx := context.Background()
  cli, err := client.NewEnvClient()
  check(err)

  // Dockerfile.tar.gzを読み込む
  cwd, _ := os.Getwd()
  file, err := os.Open(cwd + "/Dockerfile.tar.gz")
  defer file.Close()

  // イメージ名はUnix時間
  imageName := fmt.Sprintf("%v", time.Now().Unix())

  // Dockerfileからイメージを作成する
  res, err := cli.ImageBuild(ctx, file, types.ImageBuildOptions{
    Tags:        []string{imageName},
    ForceRemove: true,
  })
  check(err)
  defer res.Body.Close()

  fmt.Printf("OSType: %s\n", res.OSType)

  b, err := ioutil.ReadAll(res.Body)
  check(err)
  fmt.Println(string(b))

  fmt.Printf("Build Image. Image's name is %v\n", imageName)

  // イメージ名を指定しコンテナを作成する
  resp, err := cli.ContainerCreate(ctx, &container.Config{
    Image: imageName,
  }, nil, nil, "")
  check(err)

  // コンテナの実行
  err = cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
  check(err)

  _, err = cli.ContainerWait(ctx, resp.ID)
  check(err)

  out, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{
    ShowStdout: true,
  })
  check(err)

  // 実行結果の出力
  io.Copy(os.Stdout, out)

  // 起動したコンテナを削除する
  err = cli.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{})
  check(err)
}

上記のコードを実行すると、以下のように出力される。
$ ls
Dockerfile.tar.gz  Dockerfile  main.go  script.sh

$ go run main.go
OSType: linux{"stream":"Step 1/4 : FROM alpine\n"}
{"stream":" ---\u003e 7328f6f8b418\n"}
{"stream":"Step 2/4 : ADD ./ /root\n"}
{"stream":" ---\u003e ee82dcc482cb\n"}
{"stream":"Removing intermediate container f9d0fe0cfd2b\n"}
{"stream":"Step 3/4 : WORKDIR /root\n"}
{"stream":" ---\u003e fd8e0cecb48f\n"}
{"stream":"Removing intermediate container fea92bde02dd\n"}
{"stream":"Step 4/4 : CMD sh script.sh\n"}
{"stream":" ---\u003e Running in ee7b6a9e1dcd\n"}
{"stream":" ---\u003e 3f9c3bb0eeb0\n"}
{"stream":"Removing intermediate container ee7b6a9e1dcd\n"}
{"stream":"Successfully built 3f9c3bb0eeb0\n"}
{"stream":"Successfully tagged 1503883258:latest\n"}


Build Image. Image's name is 1503883258

hello world from container



以下、処理の詳細について解説する。

Dockerfile.tar.gzを開く


今回はDockerfile.tar.gzというファイル名にしたが、任意のファイル名で問題ない。
os.Getwdでルートディレクトリを取得、os.Openでファイルを開いている。
cwd, _ := os.Getwd()
file, err := os.Open(cwd + "/Dockerfile.tar.gz")
defer file.Close()

Dockerfileからイメージを作成する


イメージ名は重複させたくなかったのでUnix時間を使っている。
imageBuildのbuildContextにDockerfile.tar.gzを渡してイメージを作成している。
imageName := fmt.Sprintf("%v", time.Now().Unix())
res, err := cli.ImageBuild(ctx, file, types.ImageBuildOptions{
  Tags:        []string{imageName},
  ForceRemove: true,
})

ImageBuildOptionsにもDockerfileを指定できるのだが、buildContentを指定せずDockerfile: "./Dockerfile"のように指定したらError response from daemon: Cannot locate specified Dockerfile: Dockerfileというエラーが出る。絶対パス、相対パス、ファイル名変更しても見つからないと言われ、思いっきりハマってしまった。

よくよく調べてみるとここのDockerfileオプションは、buildContext内のDockerfileの名前だった。(Dockerfileを"hoge"のように別名にしたときに指定するオプション)
// Bad. [Error response from daemon: Cannot locate specified Dockerfile: Dockerfile]
res, err := cli.ImageBuild(ctx, nil, types.ImagesBuildOptions{
  Dockerfile:  "./Dockerfile",
  Tags:        []string{imageName},
  ForceRemove: true,
})



参考サイト






以上

written by @bc_rikko

0 件のコメント :

コメントを投稿