CUBE SUGAR CONTAINER

技術系のこと書きます。

まっさらな状態から Docker イメージを作る

Docker イメージというと、一般的には既存の Docker イメージをベースにして作る機会が多い。 そうしたとき Dockerfile にはベースとなるイメージを FROM 命令で指定する。 とはいえ、既存のイメージをベースにしない、まっさらな状態からイメージを作ることもできる。 それが FROM 命令に scratch を指定した場合になる。 今回は FROM scratch でまっさらな状態から Docker イメージを作ってみることにする。

試した環境は次の通り。

$ docker version
Client:
 Version:   18.01.0-ce
 API version:   1.35
 Go version:    go1.9.2
 Git commit:    03596f5
 Built: unknown-buildtime
 OS/Arch:   darwin/amd64
 Experimental:  false
 Orchestrator:  swarm

Server:
 Engine:
  Version:  18.01.0-ce
  API version:  1.35 (minimum version 1.12)
  Go version:   go1.9.2
  Git commit:   03596f5
  Built:    Wed Jan 10 20:13:12 2018
  OS/Arch:  linux/amd64
  Experimental: false

Ubuntu 16.04 LTS の Docker イメージを作ってみる

Docker コンテナというのは、結局のところシステムから隔離された Linux の一プロセスに過ぎない。 そのプロセスが動作するのに必要なファイルをまとめたものが Docker イメージということになる。 つまり、例えば特定の OS のルートファイルシステム一式さえあれば、その OS の Docker イメージが作れることになる。 ここでは例として Ubuntu 16.04 LTS の Docker イメージを作ってみることにする。

まずは公式で公開されている Ubuntu 16.04 LTS のルートファイルシステム一式をダウンロードしてくる。

$ wget http://cdimage.ubuntu.com/ubuntu-base/releases/16.04/release/ubuntu-base-16.04.3-base-amd64.tar.gz

あとは、それをファイルシステムのルートに展開した Dockerfile を用意すれば良い。 ADD 命令を使うと tar.gz ファイルは第二引数のパスに展開される。

$ cat << 'EOF' > Dockerfile 
FROM scratch
ADD ubuntu-base-16.04.3-base-amd64.tar.gz /
EOF

上記の Dockerfile をビルドする。

$ docker build -t example/scratch-ubuntu1604 .
Sending build context to Docker daemon  46.12MB
Step 1/2 : FROM scratch
 ---> 
Step 2/2 : ADD ubuntu-base-16.04.3-base-amd64.tar.gz /
 ---> 2e6f55bc5efd
Successfully built 2e6f55bc5efd
Successfully tagged example/scratch-ubuntu1604:latest

ちゃんと Docker イメージが登録された。

$ docker images
REPOSITORY                   TAG                 IMAGE ID            CREATED             SIZE
example/scratch-ubuntu1604   latest              2e6f55bc5efd        15 seconds ago      120MB

上記でビルドした Docker イメージからコンテナを起動してみよう。 起動するプログラムは bash にした。

$ docker run -it example/scratch-ubuntu1604 /bin/bash
root@f3359e8630cc:/#

どうやら、ちゃんと動作している。

root@f3359e8630cc:/# ls /
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@f3359e8630cc:/# cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.3 LTS"

もちろん実用という面では他にも色々とケアすべきところはあるだろうけど、これだけ単純な作業で作ることができた。

シングルバイナリだけから成る Docker イメージを作ってみる

前述した通り Docker イメージというのはコンテナとして起動する Linux プロセスの動作に必要なファイル一式を指す。 逆説的には、起動する Linux プロセスの動作に必要ないファイルというのはイメージの中で無駄ということになる。 つまり、究極的にはプロセスの動作に必要なファイル一式がシングルバイナリにまとめられていれば、そのファイルだけあれば良いことになる。 とはいえそんなことができるのか?というと Golang を使えば意外とできてしまう。 Golang は基本的に外部のライブラリに依存せず、動作に必要な全てを自前で用意している。 そのため、ビルド後のバイナリは動作に libc すら必要としない。 さすが世界で最もコンテナを活発に利用している企業がデザインした言語という感じがする。

前置きが多少長くなったけど、ここからは実際にシングルバイナリだけから成るイメージを作っていく。 まずは Golang のサンプルコードを用意する。 内容は、単なるハローワールドにした。

$ cat << 'EOF' > helloworld.go 
package main

import "fmt"

func main() {
  fmt.Printf("Hello, World!\n")
}
EOF

上記をビルドする。

$ go build helloworld.go

ここで注意すべきなのは動作させる Docker ホストと同じアーキテクチャの Linux 上でビルドすること。 当たり前だけど OS やアーキテクチャが異なる環境でビルドしたバイナリだと動かないので。

$ file helloworld
helloworld: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

あとはビルドしたバイナリを組み込んだ Docker イメージを作るだけ。 次のようにシンプル極まりない Docker ファイルを用意した。

$ cat << 'EOF' > Dockerfile 
FROM scratch
ADD helloworld /

CMD ["/helloworld"]
EOF

上記をビルドして Docker イメージを作る。

$ docker build -t example/scratch-go .
Sending build context to Docker daemon  2.292MB
Step 1/3 : FROM scratch
 ---> 
Step 2/3 : ADD helloworld /
 ---> a2e0629ddc0c
Step 3/3 : CMD ["/helloworld"]
 ---> Running in e61813649513
Removing intermediate container e61813649513
 ---> 898df177330d
Successfully built 898df177330d
Successfully tagged example/scratch-go:latest

登録されたイメージを確認すると 2.29MB という小ささに収まっている。

$ docker images
REPOSITORY                   TAG                 IMAGE ID            CREATED              SIZE
example/scratch-go           latest              898df177330d        20 seconds ago       2.29MB
example/scratch-ubuntu1604   latest              2e6f55bc5efd        About a minute ago   120MB

登録されたイメージを元にコンテナを起動してみよう。

$ docker run -t example/scratch-go
Hello, World!

ちゃんとメッセージが表示されて上手くいったようだ。

めでたしめでたし。

詳解UNIXプログラミング 第3版

詳解UNIXプログラミング 第3版