Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Goの公式dockerイメージを使ってみた

More than 5 years have passed since last update.

Goの公式dockerイメージとは?

  • これの事 : golang Repository | Docker Hub Registry - Repositories of Docker Images
    • アナウンス記事 : Docker Hub Official Repos: Announcing Language Stacks | Docker Blog
    • 「各言語の実行環境が整ったコンテナ を動かすためのイメージ」を公式に公開したぜ、という話
    • ということはつまり、 Goアプリの実行・テスト・プロファイリング・ビルド といった作業を「公式のお墨付き且つ使い捨ての環境」で行うことが出来る という事。素敵
    • 言語のバージョンも揃っているので、バージョン間の挙動差異を確認したい時にサクっと環境をGETすることが出来る

使い方の例

実行環境として使う (Start a Go instance in your app)

Goで作成したcliツールを公式イメージのコンテナで動かすケースを試してみる(拙作 で試してみる)

事前準備

  • 対象goアプリをgit cloneする(ローカルにあればそこにcd)
$ git clone https://github.com/goldeneggg/ipcl.git
$ cd ipcl
  • 公式記事にあるサンプルの形式でDockerfileを用意する
    • FROMで指定するベースイメージは、tagに-onbuildというsuffixが付いているものを選択
      • このイメージには、ONBUILD処理が複数定義されている
    • buildする際、このONBUILDで定義されている処理も動く
      • ONBUILDで処理を定義した場合、 下流の/派生先の Dockerfileのcontextで処理が実行される
        • 派生先DockerfileのFROM句 直後に処理を埋め込むイメージ
      • 公式のサンプルではコンテナ上にappという名前で自・Goアプリがインストールされるけど、ONBUILDを使う事によりbuildを実行したディレクトリの内容次第でこのappの実体が変わる。という仕組み
        • つまり、Dockerfile自体に手を加えること無く使い回しが効く
$ vi Dockerfile

FROM golang:1.3.1-onbuild

# 対象goアプリ/ツールがコンテナ上では app という名前でインストールされるので、その名前を指定して実行
## 実行時引数が無いケース
# CMD ["app"]
## 実行時引数があるケース
CMD ["app", "192.168.56.24/0"]
  • このDockerfileを(任意のtagを付けて)buildする
    • build時に行われる処理の流れ
      • golang/1.3.1/onbuild イメージの親(FROMで指定されてる)イメージの golang/1.3.1 がpull&buildされる
        • Dockerfileはこれ。goバイナリのダウンロード・GOPATH等の環境変数設定・buildで使うwrapperスクリプト go-wrapper のコピー・などなどが実行される
      • golang/1.3.1/onbuild イメージがpull&buildされる。ONBUILDにより下記処理がビルド時に実行される
        • カレントディレクトリのファイル群をコンテナの/go/src/appにコピー
        • go-wrapper downloadの実行
        • go-wrapper installの実行
$ docker build -t my-ipcl .

:
(ONBUILD ここから)
# Executing 3 build triggers
Step onbuild-0 : COPY . /go/src/app
 ---> 370cb70d6c73
Step onbuild-1 : RUN go-wrapper download
 ---> Running in c6751d9cb63b
+ exec go get -v -d
github.com/goldeneggg/ipcl (download)
github.com/jessevdk/go-flags (download)
 ---> bc940f23da94
Step onbuild-2 : RUN go-wrapper install
 ---> Running in 04625b77395b
+ exec go install -v
github.com/goldeneggg/ipcl/parser
github.com/goldeneggg/ipcl/writer
github.com/jessevdk/go-flags
app
 ---> b6115dc12578
 ---> b6115dc12578
(ONBUILD ここまで)
:

goアプリの実行

  • 実行すると、CMDで指定したコマンド(と引数)の結果が表示される
$ docker run -it --rm --name my-running-ipcl my-ipcl

source_cidr : 192.168.56.0/24
network     : 192.168.56.0
mask        : 255.255.255.0
host_num    : 254
min_address : 192.168.56.1
max_address : 192.168.56.254
broadcast   : 192.168.56.255
  • appに渡す引数を動的に指定したい場合は、DockerfileのCMDENTRYPOINTにすれば良い
FROM golang:1.3.1-onbuild

# 実行時引数は docker run 時に指定する
ENTRYPOINT ["app"]
$ docker build -t my-ipcl .
$ docker run -it --rm --name my-running-ipcl my-ipcl 192.168.56.0/24  # <- 実行時引数指定

source_cidr : 192.168.56.0/24
network     : 192.168.56.0
mask        : 255.255.255.0
host_num    : 254
min_address : 192.168.56.1
max_address : 192.168.56.254
broadcast   : 192.168.56.255

# 引数が無い場合Usageを表示する想定だが、その通り動くか? -> OK
$ docker run -it --rm --name my-running-ipcl my-golang-ipcl

Target CIDR(or CIDR list file) is not assigned


Usage:
  ipcl [OPTIONS] <CIDR TEXT | -f <FILE>>

Application Options:
  -f, --file=    Filepath listed target CIDR
  -c, --csv=     Output format is csv
  -t, --tsv=     Output format is tsv
  -v, --version  Print version

Help Options:
  -h, --help     Show this help message

コンパイル環境として使う

自作Goアプリをコンテナ上でビルドしたい場合

事前準備

  • 対象goアプリをgit cloneする(ローカルにあればそこにcd)
$ git clone https://github.com/goldeneggg/ipcl.git
$ cd ipcl

※公式記事のサンプル通りでは動かない

  • 公式記事にあるサンプルの形式でgo buildmake してみる
    • -v-wのpathは$GOPATH/src/<対象appのpackage path>に置き換える
      • GOPATH/go
  • ちょっとハマった...
    • 外部ライブラリに依存している場合、事前にgo getするなりして依存解決しておかないとbuildエラーになる
    • makeコマンドが無い(公式Dockerfileではapt-get installしているはずなのだが)、github上のDockerfileとdockerhubに公開されてるイメージに差分があるのかしら。。。
$ docker run --rm -v "$(pwd)":/go/src/github.com/goldeneggg/ipcl -w /go/src/github.com/goldeneggg/ipcl golang:1.3.1 go build -v

main.go:11:2: cannot find package "github.com/jessevdk/go-flags" in any of:
        /usr/src/go/src/pkg/github.com/jessevdk/go-flags (from $GOROOT)
        /go/src/github.com/jessevdk/go-flags (from $GOPATH)
$ docker run --rm -v "$(pwd)":/go/src/github.com/goldeneggg/ipcl -w /go/src/github.com/goldeneggg/ipcl golang:1.3.1 make

exec: "make": executable file not found in $PATH
  • 対策として、
    • go buildする前に、依存パッケージのgo getを行うようにする
    • makeが動かなかった公式のgolang:1.3.1用Dockerfileをローカルでbuildして使う
      • 折角公開されているイメージをローカルでbuildし直して使う、というのは本来やるべきではないと思うが
$ wget https://raw.githubusercontent.com/docker-library/golang/master/1.3.1/go-wrapper
$ wget https://raw.githubusercontent.com/docker-library/golang/master/1.3.1/Dockerfile

$ docker build -t golang:1.3.1 .

通常コンパイル (Compile your app inside the Docker container)

  • 普通にgo buildする場合
    • -vカレントディレクトリ(=git cloneしたディレクトリ) と コンテナのGOPATH配下(/go配下) をマウント
    • -wでコンテナ側の作業ディレクトリを同じGOPATH配下にしており、ビルドの成果物はここに出力される =マウントしているサーバー側のカレントディレクトリに置かれる
$ docker run --rm -v "$(pwd)":/go/src/github.com/goldeneggg/ipcl -w /go/src/github.com/goldeneggg/ipcl golang:1.3.1 bash -c 'go get -d ./... && go build -v'

github.com/goldeneggg/ipcl/parser
github.com/goldeneggg/ipcl/writer
github.com/goldeneggg/ipcl
  • コンパイルで生成された実行ファイルをサーバー側で確認
$ ls ipcl
ipcl
  • Makefileを用意していてmakeを実行する場合
$ docker run --rm -v "$(pwd)":/go/src/github.com/goldeneggg/ipcl -w /go/src/github.com/goldeneggg/ipcl golang:1.3.1 make

go get github.com/jessevdk/go-flags
go test ./parser
ok      github.com/goldeneggg/ipcl/parser       0.004s
go build -o /ipcl

クロスコンパイル (Cross-compile your app inside the Docker container)

  • 通常コンパイルと異なるのは下記の2点
    • 使用するオフィシャルイメージはtagに-crossというsuffixの付いたもの
      • 通常コンパイル同様、ローカルでbuildして使う
        • これもベースイメージをローカルでbuildしたからであって、ローカルでbuildし直して使う のは本来やるべきではない
    • docker runの際に-eでプラットフォーム環境変数を指定する
      • GOOS
      • GOARCH
  • ベースとなっているgolang:1.3.1イメージをローカルでbuildしたので、golang:1.3.1-crossもローカルでbuildする
    • Dockerfileはこれ。go自体をクロスプラットフォームでbuildする処理を行っている
$ wget https://raw.githubusercontent.com/docker-library/golang/master/1.3.1/cross/Dockerfile
$ docker build -t golang:1.3.1-cross .
  • クロスコンパイル実行 (go buildで)
    • -eでプラットフォーム環境変数指定。例としてwindowsを
      • windowsを指定しているので実行ファイルは.exe形式で生成される
$ docker run --rm -v "$(pwd)":/go/src/github.com/goldeneggg/ipcl -w /go/src/github.com/goldeneggg/ipcl -e GOOS=windows -e GOARCH=386 golang:1.3.1-cross bash -c 'go get -d ./... && go build -v'

github.com/goldeneggg/ipcl/parser
github.com/goldeneggg/ipcl/writer
github.com/jessevdk/go-flags
github.com/goldeneggg/ipcl
  • コンパイルで生成された実行ファイルをサーバー側で確認
$ ls ipcl.exe
ipcl.exe
  • こんな感じのスクリプトを用意しとけば、複数プラットフォーム向けのクロスコンパイル → 実行ファイル取得 までを 常に同じGo環境を使って一括実施する ことができる
$ vi cross_compiles.sh
#!/bin/sh

PKG_PATH=$1
P=`basename ${PKG_PATH}`
PKG_NAME=${P%.*}

OS_S=("linux" "windows")
ARCH_S=("386" "amd64")

for os in ${OS_S[@]}
do
  for arch in ${ARCH_S[@]}
  do
    bin=${PKG_NAME}_${os}_${arch}
    if [ ${os} = "windows" ]
    then
        bin=${bin}.exe
    fi

    docker run --rm -v "$(pwd)":/go/src/${PKG_PATH} -w /go/src/${PKG_PATH} -e GOOS=${os} -e GOARCH=${arch} golang:1.3.1-cross bash -c "go get -d ./... && go build -o ${bin} -v"
  done
done
$ ./cross_compiles.sh github.com/goldeneggg/ipcl

github.com/goldeneggg/ipcl/parser
github.com/goldeneggg/ipcl/writer
github.com/jessevdk/go-flags
github.com/goldeneggg/ipcl
github.com/goldeneggg/ipcl/parser
github.com/goldeneggg/ipcl/writer
github.com/jessevdk/go-flags
github.com/goldeneggg/ipcl
github.com/goldeneggg/ipcl/parser
github.com/goldeneggg/ipcl/writer
github.com/jessevdk/go-flags
github.com/goldeneggg/ipcl
github.com/goldeneggg/ipcl/parser
github.com/goldeneggg/ipcl/writer
github.com/jessevdk/go-flags
github.com/goldeneggg/ipcl

$ ls ipcl*
ipcl_linux_386  ipcl_linux_amd64  ipcl_windows_386.exe  ipcl_windows_amd64.exe

所感

  • これまで言語の実行環境用イメージは自前でDockerfileを書いて用意していたが、「dockerの公式」という箔が付いたイメージが公開された事で、種々の作業について合意形成が行い易くなったと思う
    • イメージの内容について理解する必要がない。勉強して理解したい人だけがやればいい
    • イメージの内容を保証する必要がない
    • オレオレ文化の抑止効果
      • 個人・部署・会社を跨いだノウハウ共有のハードルが下がった
  • とはいえ、公式イメージも随時修正されるもの(という想定)で考えておかねばならないわけで、現場で公式イメージを使用する運用を行うのであれば「どの / いつ時点の commitのイメージを使用したか?」はチーム内で管理・共有しておくべきである気がする
    • 管理・共有が面倒であれば「社内では、ある時点で公式をforkして、docker buildし直したイメージを使うようにする」とか良しなにルールを決めるなり何なり
jpshadowapps
Ruby(Rails)/Go/Python(Django)/PHP/Java/Vue.js/Linux/MySQL/Docker/AWS(SAA,DVA,SOA)/WebAssembly/Vim/VSCode/Fintech/Payment
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away