目が点! すっごく小さいDockerイメージ! tweetコマンドをDockerを使って動くようにした
世の中のDockerイメージは大きすぎて、ダウンロードに時間がかかりすぎる!(某CM風)
Dockerイメージを作っていると悩まされるのがDockerイメージ(以下イメージ)のサイズです。イメージサイズが大きいとダウンロードやアップロードに時間がかかり、不便です。
そこで、テザリングでパソコンをインターネットに繋げているときにダウンロードしても、通信量・通信時間が気にならないような大きさのイメージを作りました。
https://hub.docker.com/r/kotaru/tweet/
その大きさはなんと、3MB!
"目が"点ですよ。メガだけに!
はぁぁぁぁい!ずっと言いたくて言えなかったダジャレを言ったところで、このイメージを作った過程を説明します。なにをしたかをとっとと知りたい人はまとめを見ましょう。
tweet command written in Go
Go言語を覚えたてのころに作ったtweetコマンドですが、最近更新しました。Dockerを使って、Go言語の開発環境がなくても簡単に使えるようにしました。
なお、動かすためにはTwitterのAPIキーが必要で、環境変数にセットする必要があります。詳しくは上記のリンクで。
tweetコマンドを作った背景
このコマンドを作った時は、まだ研究室にSlackが導入されていませんでした。サーバで長期実行プログラムが終わったときに、通知するいい方法がなかったので、このコマンドを作りました。
機能はシンプルで、「標準入力から受け取った文字列をそのままツイートする」だけです。案外便利なので今でも普通につぶやく時に自分は使ってます。後輩に変人だと言われてしまったが...w
(注意) 重要な情報はツイートしないようにしてくださいね
Docker multi stage build with scratch
さてさて、本題といきましょうか。
Docker 17.05からmulti stage buildという機能が追加されています。
詳細はリンク先を参照してください。
簡単に説明すると、アプリケーションをビルドするためのコンテナとアプリケーションを実行するコンテナを分離することができるという話です。
分割することで、ビルド時のみに必要なファイルをわざわざ消すシェルスクリプトを書かなくてすみ、綺麗で保守性の高いDockerfileをかけるようになります。
下記は私の作ったtweetコマンドをDocker上で動かすためのDockerfileです。
FROM golang:1.11.2-alpine3.8 as builder RUN apk --update --no-cache add git ca-certificates WORKDIR /go/src/github.com/kotaru23/tweet COPY . . RUN go get . RUN CGO_ENABLED=0 GOOS=linux go build -o tweet FROM scratch COPY --from=builder /go/src/github.com/kotaru23/tweet/tweet /go/bin/ COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt CMD ["/go/bin/tweet"]
注目するべきは、3点あります。
- FROMが2回書かれている。そのうち1つには "as builder"と書かれている
- COPYの後に"--from=builder"と書かれている
- FROM scratch
説明
FROMが2回書かれていますが、これは決して間違いではありません。
1つ目のFROMから次のFROMまでの間では、tweetコマンドをソースコードからコンパイルしています。また、この後の工程のために"builder"という名前をコンテナにつけました。
FROM golang:1.11.2-alpine3.8 as builder RUN apk --update --no-cache add git ca-certificates WORKDIR /go/src/github.com/kotaru23/tweet COPY . . RUN go get . RUN CGO_ENABLED=0 GOOS=linux go build -o tweet
2つ目のFROM以降は、アプリケーション実行用のイメージです。
1つ目のイメージを継承しているのではなく、全く別のイメージです。
FROM scratch COPY --from=builder /go/src/github.com/kotaru23/tweet/tweet /go/bin/ COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt CMD ["/go/bin/tweet"]
COPY --from=builder "builderイメージのパス" "コピー先のディレクトリ"
とすることで、先ほど作成したbuilderイメージからファイルを持ってきています!
Go言語で書かれたアプリケーションは、外部ライブラリ(C言語等)を使用していなければ、バイナリ1つをコピー&ペーストすれば実行できます。(バイナリは各OS, CPUのアーキテクチャごとにコンパイルする必要があります)
Go言語のライブラリ、ランタイムなどは全てバイナリの中に含まれているようです。
このtweetコマンドはGo言語で書かれていてバイナリとCA証明書があれば動くようになっているので、先のbuilderコンテナからバイナリとCA証明書を実行用コンテナに持ってきています。
FROM scratch
scratchとは、ほとんど何もないオフィシャルイメージです。/bin/shすらないようです。詳しくは下記リンク
https://hub.docker.com/_/scratch/
このイメージはファイルを他の場所からコピーしてきて、バイナリを実行するぐらいの用途にしか使えないのではないかと思います。JavaなどのVMを使用する言語で書かれたアプリケーションでは使えないかもしれません。しかし、外部依存のない純粋なGo言語で書かれたアプリケーションであれば、バイナリを持ってくるだけで実行できます。今回の例では通信をするためにCA証明書もビルド用のコンテナから持ってきていますが、それだけです。
まとめ
3MBのイメージを作るために必要な3つのこと
- Go言語で書かれたアプリケーション(C言語の依存なし)
- Docker multi stage build
- scratchイメージ
あとがき
あ、"3"って数字使いすぎですかね。コンサルタントの真似ですw
それとBuildKitの話ができなかったのが残念です。
最後に言わせてほしい。
$ echo "純子ちゃん、やーらしか!" | \ docker run --rm -i \ -e TWITTER_CONSUMER_KEY=$TWITTER_CONSUMER_KEY \ -e TWITTER_CONSUMER_SECRET=$TWITTER_CONSUMER_SECRET \ -e TWITTER_ACCESS_TOKEN_KEY=$TWITTER_ACCESS_TOKEN_KEY \ -e TWITTER_ACCESS_TOKEN_SECRET=$TWITTER_ACCESS_TOKEN_SECRET \ -e TWITTER_SCREEN_NAME=$TWITTER_SCREEN_NAME \ kotaru/tweet