YAMAGUCHI::weblog

噛み付き地蔵に憧れて、この神の世界にやってきました。マドンナみたいな男の子、コッペです。

Go製アプリケーションのコンテナ化にはkoを推したい

はじめに

こんにちは、Google Cloudでオブザーバビリティを担当しているものです。Cloud Operations suiteをよろしくおねがいします。(宣伝終わり)

この記事はGo Advent Calendar 2021 その1の22日目の記事です。昨日は @sago35tk さんの「ESP32 向けに TinyGo をセットアップする」でした。TinyGoのコアな情報を日本語で教えてくれるtakasagoさんには本当にいつも感謝しています。

さて、今日はGo製のアプリケーションをdockerlessでコンテナ化できるkoの紹介をします。koは本当にイチオシのツールで、みんなに使ってもらいたいのでぜひ使ってください。

github.com

DockerによるGo製アプリのコンテナ化

まず最もポピュラーと思われるDockerを用いた場合のGo製アプリケーションのコンテナ化の方法についておさらいします。Go製のアプリケーションで最もシンプルな構成の場合、go build をして特定のパスに置いた後、アプリケーションが使うポートを EXPOSE して、CMD もしくは ENTRYPOINT でそのパスを指定してあげる、というような形になります。

具体的には、次のような簡単なGoのアプリケーションがあった場合

package main

import (
    "log"
    "net/http"
)

const port = "8888"

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, world!"))
    })
    log.Println("starting server on :" + port)
    if err := http.ListenAndServe(":"+port, nil); err != nil {
        log.Fatalf("error running server: %v", err)
    }
}

Dockerfileとして次のようなものを用意する、ということです。

FROM golang:1.17.5-bullseye as builder
WORKDIR /dist
RUN go build -o server

FROM gcr.io/distroless/base-debian11
WORKDIR /app
COPY --from=builder /dist/server /app/server
EXPOSE 8888
CMD ["/app/server"]

koを使った場合

しかしながら、これだけのためにわざわざDockerをいれたいかと言われたら、自分は入れたくありません!そこでkoを使います。

github.com

本日二回目の埋め込みリンクです。koはGo製アプリケーション専用のDockerlessコンテナビルドツールなのですが、たとえば上のアプリケーションの場合は、go build を叩くかのように、設定ファイル等用意する必要なく、この ko publish コマンド一発でコンテナが作れます。

$ ko publish --local --base-import-paths .
2021/12/22 01:08:18 Using base gcr.io/distroless/static:nonroot for test
2021/12/22 01:08:19 Building test for linux/amd64
2021/12/22 01:08:20 Loading ko.local/test:4a9444968723e9d5d24d25b07aaaa504c9ea8a1921273a3753028e5e6d9f5dc9
2021/12/22 01:08:20 Loaded ko.local/test:4a9444968723e9d5d24d25b07aaaa504c9ea8a1921273a3753028e5e6d9f5dc9
2021/12/22 01:08:20 Adding tag latest
2021/12/22 01:08:20 Added tag latest
ko.local/test:4a9444968723e9d5d24d25b07aaaa504c9ea8a1921273a3753028e5e6d9f5dc9

いろいろオプションが付いていますが、これは気にしないことにして、このコマンドによって ko.local/test というイメージができました。これは go.mod でモジュール名を test にしているからです。また ko.localレジストリ名が自動でprefixになっているということです。( --local オプションをつけたことによる。)

たとえば KO_DOCKER_REPO という環境変数gcr.io/foo/bar を指定して、アプリケーションのモジュール名を github.com/xxx/yyy とした場合には、コンテナイメージ名は gcr.io/foo/bar/github.com/xxx/yyy としてくれる、ということです。

koのコマンドがGo製のアプリケーションをビルドした後、よしなにそれをコンテナ内に配置し、エントリーポイントの設定をしてくれ、設定したコンテナレジストリ向けの名前でイメージを作成してくれます。簡単ですね!

ko のインストール

さて、koは簡単そうだ、という雰囲気がわかったところで、ko自体のインストール方法ですが、これも超簡単です。ko自体もただのGo製ツールですので、Goが手元にインストールされている人であれば

go install github.com/google/ko@latest

これでおしまいです。Dockerも必要ありません。macOSな環境では本当にこれは便利ですね!

当然バイナリ配布もされているので、手元にダウンロードしてくればそれで終わりです。各種方法は公式ドキュメントを見てください。

ko の挙動のカスタマイズ

とりあえず最低限動かせるコマンドを紹介したので、次に ko の挙動を変えたい場合にどうするかを紹介します。たとえば、koで作るコンテナのベースイメージを変更したい場合にはどうしたらいいでしょうか。(デフォルトは gcr.io/distroless/static:nonroot )あるいはGoのビルドの際に必要な環境変数を渡したい場合にはどうしたらいいでしょうか。こうしたカスタマイズは .ko.yaml というYAMLファイルで設定します。例えば次のような具合です。

defaultBaseImage: gcr.io/distroless/base-debian11

builds:
  - id: main
    dir: .
    main: .
    env:
      - CGO_ENABLED=0

ko とコンテナランタイムとの連携

さらに ko が便利なのはコンテナランタイムと連携する場合です。たとえば Kubernetes の deployment のYAML内でコンテナのイメージを指定しますが、そこを ko の記法を使ってコンテナを指定しておくことができます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deployment
spec:
  replicas: 3
  ...
  template:
    spec:
      containers:
      - name: my-app
        image: ko://github.com/my-user/my-repo/cmd/app

これを ko resolve -f deployment.yaml で渡してあげると、まず github.com/my-user/my-repo/cmd/app にあるアプリケーションを ko でビルドして、環境変数 KO_DOCKER_REPO で指定しているコンテナレジストリにプッシュして、さらに ko://... の文字列をそのイメージのパスで置き換えるということをした deployment.yaml の値を返してくれます。これを kubectl apply にパイプで渡してあげたりするわけです。

apply をするだけであれば ko apply というショートカットも用意してくれています。とにかく雑にGo製アプリケーションをコンテナ化してどこかにデプロイするのがとても簡単になります。

Dockerに投げる場合でも

docker run --rm -p 8888:8888 $(ko publish --local .)

というようなワンライナーで上げられたり、Cloud Runに投げるときは

gcloud run deploy --image=$(ko publish --local .)

という具合にできます。

ko とツールの連携

さきほどちらりと kubectl との連携させるための ko resolve というコマンドを紹介しましたが、Kubernetesを扱う場合、たとえば自分は skaffold を使ってテスト環境を上げたりしています。

skaffoldでは最近 ko がサポートされたので、skaffold.yaml にkoを指定するだけで一気通貫でコンテナのビルドからKubernetesクラスタへのデプロイまでいけます!

build:
  artifacts:
  - image: my-simple-go-app
    ko:
      fromImage: gcr.io/distroless/base-debian11
      labels:
        org.opencontainers.image.licenses: Apache-2.0
        ...

おわりに

大変雑にkoを紹介しましたが、これは概ね ko の公式ドキュメント(=README)に記載されているものです。

github.com

ただいかんせんぱっと見が分かりづらいと思われるので「本当に簡単に使えます!」ということだけにフォーカスして紹介しました。来月にはDocker Desktopの有料化も始まります。コンテナ化を行うだけであれば、いまや選択肢は数多くありますので、koもその一つとして知っておいて損はないと思います。ぜひ試してみてください。