ko 是一个由 Google 开发的 Go 语言工具用于快速构建 Go 应用程序的容器镜像。ko 直接调用本地的 Go 编译器进行编译,然后将编译后的二进制文件打包成镜像层(Layer)追加到基础镜像中,最后修改镜像的入口点(Entrypoint)完成构建。整个过程不需要 Docker 的参与,也无需编写 Dockerfile 文件,因此可以很方便地集成在 CI/CD 环境中。

ko 安装简单,只需在 Release 页面下载对应平台的二进制文件即可,macOS 系统上可以使用 Homebrew 安装 ko:

1
brew install ko

安装完成后调用 ko login 命令登录镜像仓库:

如果使用 docker login 命令登录过可以忽略该步骤,ko 会自动使用 docker 的凭证。

1
ko login ${REGISTRY} --username=${USERNAME} --password=${PASSWORD}

使用命令构建

ko 提供了非常方便的命令行工具,使得我们可以像使用 go build 命令编译 Go 项目一样轻松完成镜像构建,下面来初始化一个简单的 Go 项目:

1
2
3
4
5
6
7
8
9
10
11
12
13
mkdir hello-ko 

cd hello-ko

go mod init hello-ko

cat <<EOF > main.go
package main

func main() {
println("Hello, ko!")
}
EOF

使用 ko 构建镜像并推送:

1
2
3
4
5
6
7
8
9
10
$ export KO_DOCKER_REPO=docker.io/lin2ur

$ ko build . -t 1.0.0 -B --sbom=none
2024/06/28 18:12:26 Using base cgr.dev/chainguard/static:latest@sha256:68b8855b2ce85b1c649c0e6c69f93c214f4db75359e4fd07b1df951a4e2b0140 for hello
2024/06/28 18:12:27 current folder is not a git repository. Git info will not be available
2024/06/28 18:12:27 Building hello for linux/amd64
2024/06/28 18:12:27 Publishing lin2ur/hello-ko:1.0.0
...
2024/06/28 18:12:35 lin2ur/hello-ko:1.0.0: digest: sha256:92ca4d352bcfd5025c47a558df8cff6ac2e67d1a6073fb5bc5303b6423bc2362 size: 1209
2024/06/28 18:12:35 Published lin2ur/hello-ko:1.0.0@sha256:92ca4d352bcfd5025c47a558df8cff6ac2e67d1a6073fb5bc5303b6423bc2362

ko build 的第一个参数用于指定 Go 项目的路径,-t 参数指定镜像的标签,--sbom=none 表示不生成软件构建材料清单(SBOM)。KO_DOCKER_REPO 环境变量用于指定镜像仓库,-B 参数表示使用 Go module 名称作为镜像名,在这个例子中是 hello-ko,再结合 -t 参数指定的镜像标签,最终的镜像名为 docker.io/lin2ur/hello-ko:1.0.0。如果不想使用 Go module 名称作为镜像名,可以将 -B 参数替换为 --bare,此时镜像名为 KO_DOCKER_REPO 环境变量的值。

构建完毕后我们来运行一下:

1
2
$ docker run --rm lin2ur/hello-ko:1.0.0
Hello, ko!

使用配置文件构建

命令行适用于单个目标的简单构建场景,如果需要定制化镜像,比如替换基础镜像、设置 go build 编译参数、构建多平台支持镜像等,需要使用 .ko.yaml 文件来配置构建过程,下面来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
defaultBaseImage: ubuntu # <- 指定基础镜像

defaultPlatforms: # <- 指定镜像平台
- linux/arm64
- linux/amd64

builds: # <- 指定编译目标
- id: server
dir: ./cmd/server # <- 指定编译目录
env: # <- 指定编译环境变量
- GOPRIVATE=git.internal.example.com
flags: # <- 指定 go build 参数
- -trimpath
ldflags: # <- 指定链接器参数
- -s
- -w
- -X version.GitCommit={{ .Git.ShortCommit }}
- -X version.GitBranch={{ .Git.Branch }}
- id: client
dir: ./cmd/client
ldflags:
- -s
- -w
- -X version.GitCommit={{ .Git.ShortCommit }}
- -X version.GitBranch={{ .Git.Branch }}

在这份配置文件中指定基础镜像为 ubuntu 并配置了两个构建目标。dir 字段指定了目标的路径;env 字段指定了编译环境变量,注意这个环境变量只会被传递给 go build 命令,不会在镜像中生效;flags 字段指定了 go build 参数;ldflags 字段指定了链接器参数,其中{{ .Git.ShortCommit }}{{ .Git.Branch }} 是 ko 提供的模板变量,用于获取 Git 仓库的提交 ID 和分支名称,更多可用的模板变量可以参考官方文档,以 server 构建目标的配置为例,等效于下面的编译命令:

1
2
export GOPRIVATE=git.internal.example.com
go build -trimpath -ldflags "-s -w -X version.GitCommit=xxxx -X version.GitBranch=xxxx" -o ./cmd/server

总结

大多数情况下 ko 都能很好地满足我们的需求,但是 ko 也有一些局限性,无法更多地定制化镜像,比如无法在镜像中预设环境变量,无法像 Docker 一样在构建过程中执行命令安装依赖等,不过我们在构建基础镜像的过程中完成这些操作。