在本文,我將介紹幾種不用 Docker 就可以構建容器的方法。我將以 OpenFaaS 作為參考案例,它的工作負載使用了 OCI 格式的容器鏡像。OpenFaaS 是 Kubernetes 的一個 CaaS 平臺,可以運行微服務和添加 FaaS 及事件驅動工具。
第一個示例將展示如何使用 Docker CLI 內置的 buildkit 選項,然后是單獨使用buildkit,最后是谷歌的容器構建器Kaniko。
本文涉及的工具都是基于 Dockerfile 文件來構建鏡像的,因此,任何限制用戶只能使用 JAVA (jib)或 Go (ko)的工具都不在討論范圍之內。
Docker 有什么問題?
Docker 在 armhf、arm64 和x86_64平臺上運行良好。Docker CLI 不僅用于構建/發布/運行鏡像,多年來它還背負了太多的東西,現在還與 Docker Swarm 和 Docker EE 特性捆綁在一起。
Docker 之外的選擇
有一些項目試圖讓“docker”回到它原本的組件身份,也就是我們最初都喜愛的用戶體驗:
- Docker——Docker現在使用containerd來運行容器,并且支持使用buildkit進行高效的緩存式構建。
- Podman和buildah組合——由RedHat/IBM使用他們自己的OSS工具鏈來生成OCI鏡像。Podman是無守護進程和無根的,但最后仍然需要掛載文件系統以及使用UNIX套接字。
- pouch——來自阿里巴巴,被標榜為“高效的企業級容器引擎”。它像Docker一樣,使用了containerd,并支持容器級別的隔離(runc)和“輕量級虛擬機”(如runV)。
- 獨立版本的buildkit——buildkit是由Docker公司的Tonis Tiigi創建的,一個全新的具有緩存和并發支持的容器構建器。buildkit目前僅作為守護進程運行,但你可能會聽到有人說不是這樣的。實際上,它會派生守護進程,然后在構建后將其終止。
- img——img由Jess Frazelle開發,對buildkit進行了封裝。與其他工具相比,它并沒有更大的吸引力。在2018年下半年之前,這個項目一直很活躍,但之后只發布了幾個補丁。img聲稱自己是無守護進程的,但它使用的是buildkit,所以這里有值得商榷的地方。我聽說img提供了比buildkit的CLI buildctr更好的用戶體驗,但需要注意的是,img只針對x86_64平臺發布了二進制文件,不支持armhf/arm64。
- k3c——使用containerd和buildkit重建初始Docker原始、經典、樸素、輕量級的體驗。
在所有的選項中,我最喜歡 k3c,但它使用起來比較繁瑣,它把所有東西都捆綁在一個二進制文件中,這很可能會與其他軟件發生沖突。它運行的是自己的嵌入式 containerd 和 buildkit 二進制文件。
由于我們關注的是“構建”部分以及相對穩定的選項,所以我們將著重看一下:
- Docker的buildkit;
- 單獨的buildkit;
- kaniko。
由于 OpenFaaS CLI 可以輸出任意構建器都可以使用的標準“構建上下文”,所以上述的所有東西都可以實現。
構建一個測試應用程序
讓我們從一個 Golang HTTP 中間件開始,并借此來展示 OpenFaaS 的通用性。
- --lang指定構建模板;
- build-test是函數的名字;
- --prefix是Docker Hub用戶名,用于推送我們的OCI鏡像。
我們將獲得以下這些內容:
./
├── build-test
│ └── handler.go
└── build-test.yml
1 directory, 2 files
處理程序修改起來很容易,還可以通過 vendoring 或 Go 模塊來添加其他依賴項。
package function
import (
"fmt"
"io/ioutil"
"net/http"
)
func Handle(w http.ResponseWriter, r *http.Request) {
var input []byte
if r.Body != nil {
defer r.Body.Close()
body, _ := ioutil.ReadAll(r.Body)
input = body
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(fmt.Sprintf("Hello world, input was: %s", string(input))))
}
使用一般的方式構建
構建這個應用程序的一般方式是這樣的:
faas-cli build -f build-test.yml
./template/golang-middleware/Dockerfile中包含了模板和 Dockerfile 的本地緩存。
這次構建拉取了三個鏡像:
FROM openfaas/of-watchdog:0.7.3 as watchdog
FROM golang:1.13-alpine3.11 as build
FROM alpine:3.12
如果使用傳統的構建器,將按順序拉取每個鏡像。
等一會兒構建就完成了,現在本地庫中就有了構建的鏡像。
我們還可以使用 faas-cli push -f build-test.yml將鏡像推到注冊表中。
使用 buildkit 和 Docker 構建
這是最簡單的做法,構建起來也很快。
DOCKER_BUILDKIT=1 faas-cli build -f build-test.yml
我們可以看到,Docker 守護進程會自動切換到 buildkit 構建器。
Buildkit 有很多優點:
- 更復雜的緩存;
- 如果可能的話,可以先運行后面的指令——也就是說,可以在“sdk”層的構建完成之前,下載“runtime”鏡像;
- 在第二次構建時速度超級快。
有了 buildkit,所有的基礎鏡像都可以一次性被拉取到本地庫中,因為 FROM(下載)命令不是按順序執行的。
FROM openfaas/of-watchdog:0.7.3 as watchdog
FROM golang:1.13-alpine3.11 as build
FROM alpine:3.11
這個在 mac 上也可以使用,因為 buildkit 是由運行在 VM 中 Docker 守護進程負責代理的。
使用單獨的 buildkit 構建
要單獨使用 buildkit 進行鏡像構建,我們需要在 linux 主機上單獨運行 buildkit,因此不能使用 Mac。
faas-cli build通常會運行或分叉出 docker,因為這個命令實際上只是一個包裝器。因此,為了繞過這種行為,我們需要創建一個構建上下文,類似下面這樣:
faas-cli build -f build-test.yml --shrinkwrap
[0] > Building build-test.
Clearing temporary build folder: ./build/build-test/
Preparing ./build-test/ ./build/build-test//function
Building: alexellis2/build-test:latest with golang-middleware template. Please wait..
build-test shrink-wrApped to ./build/build-test/
[0] < Building build-test done in 0.00s.
[0] Worker done.
Total build time: 0.00
這個上下文可以在./build/build-test/文件夾中找到,其中包含了函數代碼和模板及其入口點和 Dockerfile。
./build/build-test/
├── Dockerfile
├── function
│ └── handler.go
├── go.mod
├── main.go
└── template.yml
1 directory, 5 files
現在我們需要運行 buildkit,可以從源代碼開始構建,或者從上游獲取二進制文件。
curl -sSLf https://github.com/moby/buildkit/releases/download/v0.6.3/buildkit-v0.6.3.linux-amd64.tar.gz | sudo tar -xz -C /usr/local/bin/ --strip-components=1
如果你仔細看一下發布頁,你會發現 buildkit 也支持 armhf 和 arm64。
在新窗口中運行 buildkit 守護進程:
sudo buildkitd
WARN[0000] using host network as the default
INFO[0000] found worker "l1ltft74h0ek1718gitwghjxy", labels=map[org.mobyproject.buildkit.worker.executor:oci org.mobyproject.buildkit.worker.hostname:nuc org.mobyproject.buildkit.worker.snapshotter:overlayfs], platforms=[linux/amd64 linux/386]
WARN[0000] skipping containerd worker, as "/run/containerd/containerd.sock" does not exist
INFO[0000] found 1 workers, default="l1ltft74h0ek1718gitwghjxy"
WARN[0000] currently, only the default worker can be used.
INFO[0000] running server on /run/buildkit/buildkitd.sock
現在我們開始構建,并將配置文件的位置作為構建上下文傳給它。我們需要 buildctl 命令,buildctl 是守護進程的一個客戶端,它將指定如何構建鏡像以及在構建完成后應該做什么,比如導成 tar、忽略構建或將其推送到注冊表。
buildctl build --help
NAME:
buildctl build - build
USAGE:
To build and push an image using Dockerfile:
$ buildctl build --frontend dockerfile.v0 --opt target=foo --opt build-arg:foo=bar --local context=. --local dockerfile=. --output type=image,name=docker.io/username/image,push=true
OPTIONS:
--output value, -o value Define exports for build result, e.g. --output type=image,name=docker.io/username/image,push=true
--progress value Set type of progress (auto, plain, tty). Use plain to show container output (default: "auto")
--trace value Path to trace file. Defaults to no tracing.
--local value Allow build access to the local directory
--frontend value Define frontend used for build
--opt value Define custom options for frontend, e.g. --opt target=foo --opt build-arg:foo=bar
--no-cache Disable cache for all the vertices
--export-cache value Export build cache, e.g. --export-cache type=registry,ref=example.com/foo/bar, or --export-cache type=local,dest=path/to/dir
--import-cache value Import build cache, e.g. --import-cache type=registry,ref=example.com/foo/bar, or --import-cache type=local,src=path/to/dir
--secret value Secret value exposed to the build. Format id=secretname,src=filepath
--allow value Allow extra privileged entitlement, e.g. network.host, security.insecure
--ssh value Allow forwarding SSH agent to the builder. Format default|<id>[=<socket>|<key>[,<key>]]
我使用下面的命令獲得與 Docker 命令等價的效果:
sudo -E buildctl build --frontend dockerfile.v0
--local context=./build/build-test/
--local dockerfile=./build/build-test/
--output type=image,name=docker.io/alexellis2/build-test:latest,push=true
在運行這個命令之前,你需要先運行 docker login,或者使用一組有效的未加密憑證來創建 $HOME/.docker/config.json 文件。
使用 img 和 buildkit 構建
由于我從未使用過 img,也沒有聽說有團隊在大規模使用它,所以我想要嘗試一下。
首先它不支持多平臺架構,armhf 和 ARM64 平臺沒有對應的二進制文件,而且項目年齡不算短了,所以不太可能會提供多平臺支持。
x86_64平臺的最新版本是 2019 年 5 月 7 號的 v0.5.7,使用 Go 1.11 構建:
sudo curl -fSL "https://github.com/genuinetools/img/releases/download/v0.5.7/img-linux-amd64" -o "/usr/local/bin/img"
&& sudo chmod a+x "/usr/local/bin/img"
它的構建選項就像是 buildctl 的一個子集
img build --help
Usage: img build [OPTIONS] PATH
Build an image from a Dockerfile.
Flags:
-b, --backend backend for snapshots ([auto native overlayfs]) (default: auto)
--build-arg Set build-time variables (default: [])
-d, --debug enable debug logging (default: false)
-f, --file Name of the Dockerfile (Default is 'PATH/Dockerfile') (default: <none>)
--label Set metadata for an image (default: [])
--no-cache Do not use cache when building the image (default: false)
--no-console Use non-console progress UI (default: false)
--platform Set platforms for which the image should be built (default: [])
-s, --state directory to hold the global state (default: /home/alex/.local/share/img)
-t, --tag Name and optionally a tag in the 'name:tag' format (default: [])
--target Set the target build stage to build (default: <none>)
要構建一個鏡像需要做這些事情:
sudo img build -f ./build/build-test/Dockerfile -t alexellis2/build-test:latest ./build/build-test/
由于這樣或那樣的原因,img 實際上沒能構建成功。可能是因為試圖以非 root 用戶身份進行一些優化。
fatal error: unexpected signal during runtime execution
[signal SIGSEGV: segmentation violation code=0x1 addr=0xe5 pc=0x7f84d067c420]
runtime stack:
runtime.throw(0xfa127f, 0x2a)
/home/travis/.gimme/versions/go1.11.10.linux.amd64/src/runtime/panic.go:608 +0x72
runtime.sigpanic()
/home/travis/.gimme/versions/go1.11.10.linux.amd64/src/runtime/signal_unix.go:374 +0x2f2
goroutine 529 [syscall]:
runtime.cgocall(0xc9d980, 0xc00072d7d8, 0x29)
/home/travis/.gimme/versions/go1.11.10.linux.amd64/src/runtime/cgocall.go:128 +0x5e fp=0xc00072d7a0 sp=0xc00072d768 pc=0x4039ee
os/user._Cfunc_mygetgrgid_r(0x2a, 0xc000232260, 0x7f84a40008c0, 0x400, 0xc0004ba198, 0xc000000000)
似乎已經存在三個類似的問題。
使用 Kaniko 構建
Kaniko 是谷歌的容器構建器,旨在為容器構建提供沙箱。你可以將其作為一次性容器,也可以將其作為獨立的二進制文件。
docker run -v $PWD/build/build-test:/workspace
-v ~/.docker/config.json:/kaniko/config.json
--env DOCKER_CONFIG=/kaniko
gcr.io/kaniko-project/executor:latest
-d alexellis2/build-test:latest
- -d指定在成功構建后應該將鏡像放在哪里。
- -v將當前目錄掛載到Kaniko容器中,還添加了config.json配置文件,指定將鏡像推送到哪個遠程注冊表。
Kaniko 提供了緩存支持,但需要手動管理和保存,因為 Kaniko 是在一次性模式下運行的,不像 Buildkit 那樣是守護進程。
以上各種工具的總結
- Docker——傳統的構建器
安裝 Docker 是個“大工程”,可能會給你的系統帶來比預想的要多得多的東西。Docker 構建器是最古老的,也是最慢的。要注意在安裝 Docker 時附帶安裝的網橋,它可能會與使用相同私有 IP 段的其他私有網絡發生沖突。
- Docker——與buildkit一起
這是最快的工具選擇,改動最少,只需要加個DOCKER_BUILDKIT=1就可以啟用。
- 單獨的buildkit
這個選項非常適合集群內構建,或者不需要 Docker 的系統(如 CI 或執行器)。它需要 Linux 主機,在 MacOS 上的使用體驗太差,或許可以運行一個額外的 VM 或主機,然后通過 TCP 來訪問?
- Kaniko
使用 Kaniko 仍然需要安裝 Docker,但不管怎樣,它畢竟提供了另一種選擇。
全文總結
你可以在 OpenFaaS 中使用普通的容器構建器,也可以使用 faas-cli build --shrinkwrap,并將構建上下文傳給首選工具。
下面是使用相應工具構建 OpenFaaS 容器的示例:
- 谷歌云構建
- GitHub Actions
- Jenkins
- GitLab CI
在 OpenFaaS 云上,我們使用本文介紹的上下文傳遞方法和 buildkit 守護進程提供了完全不需要人工干預的 CI/CD 構建體驗。對于其他用戶,我建議使用 Docker,或者帶有 buildkit 的 Docker。
你可以使用 GitHub 或 GitLab 集成構建自托管的 OpenFaaS 云環境。
對于 faasd 用戶,你的主機上只安裝了 containerd,而沒有安裝 docker,所以最好的選擇是下載 buildkit。
原文鏈接:
https://blog.alexellis.io/building-containers-without-docker/