多平臺容器映象構建就看這一篇
阿新 • • 發佈:2020-12-25
## 前言
### 願景與現實
早在1995年,就有“write once and run anywhere”(WORA,編寫一次即可在任何地方執行)用於描述 Java 應用程式。時過20年,Docker 高聲喊出了自己的口號——“Build Once, Run Anywhere”(一次構建,隨處可用)。
願望是美好的,然而,現實總比理想骨感。 Linux、Windows 這些不同的作業系統擁有不同的系統 API; x86、Arm、IBM PowerPC 這些不同的硬體平臺的指令集不同,某些同平臺的硬體甚至擁有不同的專用指令集用於加速應用。一次構建,隨處可用面臨著巨大的挑戰,要構建能夠在不同作業系統、不同硬體平臺的執行的應用程式,仍然需要工程師們針對具體的作業系統和硬體平臺進行海量的移植工作。
### Why and How
既然多平臺的支援這麼麻煩、充滿挑戰,我們是不是可以放棄支援?然而隨著國產化大潮和 IoT 物聯網的來臨,我們編寫的應用程式不僅僅會在X86伺服器上執行,新時代的工程師們不得不面對更多的硬體平臺,放棄多平臺的支援無疑是放棄更寬廣的未來。多平臺的支援,勢在必行。
我們正處在一個波瀾壯闊的大時代中,新技術、新工具在一次次的迭代升級,不斷從Proposal (提議)到 Prototype(原型),再逐漸的實用化。
虛擬化技術使得我們可以做到模擬其它硬體平臺;Docker 等容器技術打破混亂,讓開發、編譯、執行環境一致化;Golang、Rust 這樣原生支援多系統多平臺的程式語言遮蔽大量底層差異,降低跨平臺應用的開發難度。這一系列前人智慧火花匯聚到一起,發生了奇妙的反應——WORA 真正變的觸手可及,夢想的陽光已經照進現實。
本篇章會大量分析技術原理及實現細節,對於希望快速 GET 可執行方案的同學,可以直接跳轉到【**可執行方案回顧**】檢視。
## 如何支援多平臺
要了解容器映象是如何支援多平臺的,那我們需要仔細聊聊 Manifest。使用過容器技術的同學都知道,我們執行容器所使用的映象是由多層構成的,而這些層的清單和其它容器資訊共同存放在 Manifest 當中。
### Manifest
我們通常使用的容器映象是x86平臺的,執行 `docker manifest inspect harbor-community.tencentcloudcr.com/multi-arch/alpine` 命令可以檢視映象 `harbor-community.tencentcloudcr.com/multi-arch/alpine` 的 Manifest 內容是一個 JSON 物件(如程式碼段-01所示)。各個欄位的解釋如下:
1. `mediaType` 欄位宣告這是一個V2 Manifes。
2. `schemaVersion` 版本。
3. `config` 映象配置資訊。
4. `layers` 映象層資訊。
```
// 程式碼段-01
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1507,
"digest": "sha256:f70734b6a266dcb5f44c383274821207885b549b75c8e119404917a61335981a"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 2813316,
"digest": "sha256:cbdbe7a5bc2a134ca8ec91be58565ec07d037386d1f1d8385412d224deafca08"
}
]
}
```
顯而易見的是,Manifest 當中並沒有任何欄位描述映象的平臺資訊。那應該怎麼樣支援多平臺呢?
我們可以設想一個簡單粗暴的,無視映象的平臺,強行把交叉編譯出來的其它平臺的二進位制程式新增到映象內,使用 Repository 名稱或者 Tag 名稱來區分不同平臺的映象,例如 coredns/coredns:coredns-arm64。在使用的時候,人工或者通過指令碼判斷應該拉取那個映象。
### Schema 2
Ohhhhh,這當然可以跑起來,但是難免太挫了吧?事實上,早在2015年底 Docker 社群的 manifest v2.2 規格文件(也叫 Schema 2,參見 https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-2.md )中就提及了多平臺映象,該功能通過 `manifest list(也叫做 fat manifest)` 引用多個不同平臺映象的 Manifest 實現。
首先讓我們看看 `manifest` 是什麼樣的,執行 `docker manifest inspect alpine` 命令可以檢視Docker Hub 上的多平臺映象 `alpine` 的 Manifest。
```
// 程式碼段-02
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 528,
"digest": "sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 528,
"digest": "sha256:71465c7d45a086a2181ce33bb47f7eaef5c233eace65704da0c5e5454a79cee5",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v6"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 528,
"digest": "sha256:c929c5ca1d3f793bfdd2c6d6d9210e2530f1184c0f488f514f1bb8080bb1e82b",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v7"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 528,
"digest": "sha256:3b3f647d2d99cac772ed64c4791e5d9b750dd5fe0b25db653ec4976f7b72837c",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "v8"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 528,
"digest": "sha256:90baa0922fe90624b05cb5766fa5da4e337921656c2f8e2b13bd3c052a0baac1",
"platform": {
"architecture": "386",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 528,
"digest": "sha256:5d950b30f229f0c53dd7dd7ed6e0e33e89d927b16b8149cc68f59bbe99219cc1",
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 528,
"digest": "sha256:a5426f084c755f4d6c1d1562a2d456aa574a24a61706f6806415627360c06ac0",
"platform": {
"architecture": "s390x",
"os": "linux"
}
}
]
}
```
可以看出來 `manifest list` 是一個 JSON 陣列,陣列當中應用了不同平臺映象的 Manifest。所以,推送多平臺映象時,我們需要先分別推送不同平臺的映象層;然後建立 `manifest list` , 再引用平臺映象的 Manifest,最後把 `manifest list` 上傳到 Registry 服務。而拉取映象時,客戶端應當設定 HTTP 的請求頭欄位 `Accept` 值為 `application/vnd.docker.distribution.manifest.v2+json`和`application/vnd.docker.distribution.manifest.list.v2+json`,然後檢查服務端返回的響應頭欄位`Content-Type` 判斷是舊映象格式,新映象格式或者時映象清單。
拋開規格文件來說,只要我們使用的 Registry 服務的 Distribution 版本不低於 v2.3,Docker CLI 版本不低於 v1.10 就能過支援多平臺映象功能。
## 構建多平臺映象
要構建多平臺的容器映象,我們需要確保容器基礎映象和應用程式的程式碼或者二進位制都是目標平臺的。
### 程式程式碼
一些程式語言的編譯器能夠為其它平臺編譯二進位制檔案,最為著名的包括 Golang 和 Rust。我們將使用 Golang 編寫一個演示用 web 程式——通過 HTTP 訪問檢視 web 服務程式的作業系統、硬體平臺等資訊。具體程式碼如程式碼段-03 所示。
```
// 程式碼段-03
package main
import (
"net/http"
"runtime"
"github.com/gin-gonic/gin"
)
var (
r = gin.Default()
)
func main() {
r.GET("/", indexHandler)
r.Run(":9090")
}
func indexHandler(c *gin.Context) {
var osinfo = map[string]string{
"arch": runtime.GOARCH,
"os": runtime.GOOS,
"version": runtime.Version(),
}
c.JSON(http.StatusOK, osinfo)
}
```
我們在 MacOS 上使用 go run 執行程式碼段-03, httpie 工具訪問本機`:9090` 埠,將會看見如下資訊。
![enter image description here](https://img2020.cnblogs.com/other/2041406/202012/2041406-20201225201910292-1561445637.png)
程式碼準備好了,現在我們有兩種構建方法:手動編譯,使用 `docker build` 構建映象;使用 docker buildx 工具自動化編譯構建。
### 手動編譯構建
#### 前置條件
- Dockerd 啟用 `experimental`
我們需要在 Docker daemon 配置檔案中配置 `"experimental": true`開啟實驗性功能:
```
$ vi /etc/docker/daemon.json
{
"experimental": true
}
```
修改 Docker daemon 配置需要重啟服務使配置生效:
```
$ sudo systemctl restart docker.service
```
使用 `docker version` 命令檢視版本資訊,配置生效後可以看到 `Server: Docker Engine` 中有 `Experimental: true` :
```
$ sudo docker version
Client: Docker Engine - Community
Version: 19.03.12
API version: 1.40
Go version: go1.13.10
Git commit: 48a66213fe
Built: Mon Jun 22 15:45:36 2020
OS/Arch: linux/amd64
Experimental: false
Server: Docker Engine - Community
Engine:
Version: 19.03.12
API version: 1.40 (minimum version 1.12)
Go version: go1.13.10
Git commit: 48a66213fe
Built: Mon Jun 22 15:44:07 2020
OS/Arch: linux/amd64
Experimental: true
containerd:
Version: 1.2.13
GitCommit: 7ad184331fa3e55e52b890ea95e65ba581ae3429
runc:
Version: 1.0.0-rc10
GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd
docker-init:
Version: 0.18.0
GitCommit: fec3683
`
```
如果您使用的 Docker CLI 版本低於 `v20.10` ,執行 `docker manifest` 命令會看到報錯提示 `docker manifest is only supported on a Docker cli with experimental cli features enabled` ,此時我們需要執行 `export DOCKER_CLI_EXPERIMENTAL="enabled"` 開啟客戶端實驗特性支援。在 `v20.10` 及以上版本的 Docker CLI 會預設開啟實驗特性,無需額外操作。
#### 交叉編譯
在我們的 Golang 程式碼中沒有使用 CGO 的時候,通過簡單設定環境變數就能夠交叉編譯出其它平臺和作業系統上能夠執行的二進位制檔案。其中:
1. `GOARCH` 用於指定編譯的目標平臺,如 `amd64`、`arm64`、`riscv64` 等平臺。
2. `GOOS` 用於指定編譯的目標系統,如 `darwin`、`linux`。
本篇中,我們構建能夠在 Linux 發行版中執行的容器映象,所以編譯目標系統環境變數`GOOS`統一設定為`linux`。執行程式碼段0-4中的命令構建出二進位制檔案備用。
```
// 程式碼段-04
#!/bin/bash
IMAGE?=kofj/multi-demo
NOCOLOR:='\033[0m'
RED:='\033[0;31m'
GREEN:='\033[0;32m'
BUILD_ARCH?=$(uname -m)
BUILD_OS?=$(uname -s)
BUILD_PATH:=build/docker/linux
LINUX_ARCH?=amd64 arm64 riscv64
LDFLAGS:="-s -w -X github.com/kofj/multi-arch-demo/cmd/info.BuildArch=$(BUILD_ARCH) -X github.com/kofj/multi-arch-demo/cmd/info.BuildOS=$(BUILD_OS)"
for arch in ${LINUX_ARCH}; do
echo ===================;
echo ${GREEN}Build binary for ${RED}linux/$$arch${NOCOLOR};
echo ===================;
GOARCH=$$arch GOOS=linux go build -o ${BUILD_PATH}/$$arch/webapp -ldflags=${LDFLAGS} -v cmd/main.go;
done
```
#### 構建各個平臺的映象
首先,我們編寫一個 Dockerfile用於構建映象。
```
FROM scratch
LABEL authors="Fanjian Kong"
ADD webapp /app/
WORKDIR /app
CMD ["/app/webapp"]
```
然後,分別構建不同平臺的映象,可以使用如程式碼段-05的指令碼幫助構建。
```
// 程式碼段-05
#!/bin/bash
IMAGE?=kofj/multi-demo
NOCOLOR:='\033[0m'
RED:='\033[0;31m'
GREEN:='\033[0;32m'
LINUX_ARCH?=amd64 arm64 riscv64
BUILD_PATH:=build/docker/linux
for arch in ${LINUX_ARCH}; do
echo =================== ;
echo ${GREEN}Build docker image for ${RED}linux/$$arch${NOCOLOR} ;
echo =================== ;
cp Dockerfile.slim ${BUILD_PATH}/$$arch/Dockerfile ;
docker build -t ${IMAGE}:$$arch ${BUILD_PATH}/$$arch ;
done
```
#### 建立 Manifest List
我們使用`docker manifest` 子命令管理 manifest list。其中,`docker manifest create` 子命令用於在本地建立一個 manifest list。該命令需要指定待 manifest list 地址和一系列的 manifests。例如需要建立包含`amd64`和 `arm64` 兩個平臺映象的 manifest list,則命令如:
```
docker manifest create kofj/multi-demo kofj/multi-demo:amd64 kofj/multi-demo:arm64
```
`docker manifest create` 命令的詳細幫助資訊如下所示:
```
# docker manifest create --help
Usage: docker manifest create MANIFEST_LIST MANIFEST [MANIFEST...]
Create a local manifest list for annotating and pushing to a registry
Options:
-a, --amend Amend an existing manifest list
--insecure Allow communication with an insecure registry
```
我們按照上述方法創建出來的 manifest list 中並沒有說明其中的 manifest 是什麼作業系統和平臺的,`docker manifest annotate` 命令用於註釋創建出來的 manifest list。例如註釋某個 manifest 是 `linxu`系統 `arm64` 平臺的,則命令:
```
docker manifest annotate kofj/multi-demo kofj/multi-demo:arm64 --os linux --arch arm64
```
`docker manifest annotate` 命令的詳細幫助資訊如下所示:
```
# docker manifest annotate --help
Usage: docker manifest annotate [OPTIONS] MANIFEST_LIST MANIFEST
Add additional information to a local image manifest
Options:
--arch string Set architecture
--os string Set operating system
--os-features strings Set operating system feature
--variant string Set architecture variant
```
**注意:**
1. 建立 manifest list 清單的過程中會檢查遠端倉庫中 manifests 是否存在,所以我們必修提前推送映象到遠端。如果遠端倉庫是不安全的,在建立的過程中需要新增引數 `--inseure`。
2. 使用 `docker manifest annotate` 註釋 manifest list 的時候不需要使用`--insecure`。
為了方便使用,我們可以使用下述程式碼段-06 的指令碼建立 manifest list。
```
// 程式碼段-06
#!/bin/bash
IMAGE?=kofj/multi-demo
NOCOLOR:='\033[0m'
RED:='\033[0;31m'
GREEN:='\033[0;32m'
LINUX_ARCH?=amd64 arm64 riscv64
IMAGES=$(foreach arch,$(LINUX_ARCH),$(IMAGE):$(arch))
@echo ${GREEN}Create manifest for ${RED} \( ${LINUX_ARCH}\) ${NOCOLOR};
@docker manifest create ${IMAGE} ${IMAGES}
@for arch in ${LINUX_ARCH}; do \
echo ${GREEN}Annotate manifest ${RED}linux/$$arch${NOCOLOR}; \
echo ===================; \
docker manifest annotate ${IMAGE} ${IMAGE}:$$arch --os linux --arch $$arch; \
done
```
#### 推送 manifest list
當我們完成 manifest list 的建立工作後,它還是儲存在本地的。這時候,還需要推送到遠端的映象倉庫。與推送普通映象不同,推送 manifest list 需要使用 `docker manifest push` 命令進行。如果我們要推送 `kofj/multi-demo` 這個 manifest list,則命令如:
```
docker manifest push kofj/multi-demo
```
使用 `docker manifest push` 命令可以通過附加`--purge` 選項在推送完成後刪除儲存在本地的 manifest list; 當我們的目標倉庫沒有使用或者使用了非可信 TLS 證書的時候,則需要使用 `--insecure` 選項。
### buildx 自動構建
#### 軟體依賴
- **Docker >= 19.03**: 自該 Docker 版本包含 buildx。
- **Linux kernel >= 4.8**: 自該Linux核心版本 binfmt_misc 支援 fix-binary (F) flag。fix_binary 標誌允許核心在容器或chroot內使用binfmt_misc註冊的二進位制格式處理程式,即使該處理程式二進位制檔案不是該容器或chroot內可見的檔案系統的一部分。
- **binfmt_misc file system mounted**: 需要掛載binfmt_misc檔案系統,以便使用者空間工具可以控制此核心功能,即註冊和啟用處理程式。
- **Docker Desktop >= 2.1.0** 如果是使用的 Docker Desktop。
| Environment | Docker 安裝包 | Kernel | binfmt-support | (F) Flag |
| -------------------------------- | ----------------- | ------ | -------------- | -------- |
| **需求** | >= 19.03 | >= 4.8 | >= 2.1.7 | yes |
| **Ubuntu:** | | | | |
| 18.04 (bionic) | 17.12.1 docker.io | 4.15.0 | 2.1.8 | yes |
| 19.04 (disco) | 18.09.5 docker.io | 5.0 | 2.2.0 | yes |
| 19.10 (eoan) | 19.03.2 docker.io | 5.3 | 2.2.0 | yes |
| 20.04 (focal) | 19.03.2 docker.io | 5.5 | 2.2.0 | yes |
| **Debian:** | | | | |
| 9 (stretch) | - | 4.9.0 | 2.1.6 | no |
| 10 (buster) | 18.09.1 docker.io | 4.19.0 | 2.2.0 | yes |
| 11 (bullseye/testing) | 19.03.4 docker.io | 5.4 | 2.2.0 | yes |
| **騰訊雲** | | | | |
| Ubuntu 16.04 (xenial) | 18.09.7 docker.io | 4.4.0 | 2.1.6-1 | no |
| Ubuntu 18.04 (bionic) | 19.03.6 docker.io | 4.15.0 | 2.1.8-2 | yes |
| **亞馬遜 EC2:** | | | | |
| Ubuntu 16.04 (xenial) | 18.09.7 docker.io | 4.4.0 | 2.1.6 | no |
| Ubuntu 18.04 (bionic) | 18.09.7 docker.io | 4.15.0 | 2.1.8 | yes |
| **Travis (谷歌 GCP):** | | | | |
| Ubuntu 14.04 (trusty) | 17.09.0 docker-ce | 4.4.0 | 2.1.4 | no |
| Ubuntu 16.04 (xenial) | 18.06.0 docker-ce | 4.15.0 | 2.1.6 | no |
| Ubuntu 18.04 (bionic) | 18.06.0 docker-ce | 4.15.0 | 2.1.8 | yes |
| **Github Actions (微軟 Azure):** | | | | |
| Ubuntu 16.04 (xenial) | 3.0.8 moby-engine | 4.15.0 | 2.1.6 | no |
| Ubuntu 18.04 (bionic) | 3.0.8 moby-engine | 5.0.0 | 2.1.8 | yes |
#### 配置 Buildx
buildx 從 19.03 開始與 Docker CE 捆綁釋出,但是需要我們在 Docker CLI 上啟用實驗性功能開開啟。可以通過兩種方式啟用它:
1. 將 `"experimental": "enabled”` 新增到 Docker CLI 的配置檔案 `~/.docker/config.json`。
2. 另外一種方法時設定環境變數 `DOCKER_CLI_EXPERIMENTAL=enabled`。
使用 Docker Desktop 的同學可以通過 UI 選單 `Preferences` → `Command Line` 進入 Docker CLI 配置介面,通過Switch 開關 `Enable experimental features`啟用實驗性功能。
image-20201006100313617.png![enter image description here](https://img2020.cnblogs.com/other/2041406/202012/2041406-20201225201910658-375168988.png)
如果需要使用最新版本的 buildx,可以從 https://github.com/docker/buildx/releases/latest 下載最新的二進位制發行版,並將其複製到`~/.docker/cli-plugins`資料夾中,重新命名為`docker-buildx`,然後更改執行許可權:
```
chmod +x ~/.docker/cli-plugins/docker-buildx
```
最後讓我們驗證 buildx 是否已經可用了:
```
$ docker buildx version
github.com/docker/buildx v0.3.1-tp-docker 6db68d029599c6710a32aa7adcba8e5a344795a7
```
#### 配置 binfmt_misc
QEMU 是一個很棒的開源專案,它可以模擬很多平臺。將 QEMU 和 Docker 結合起來使用能使得我們更容易的構建跨平臺的容器映象。整合 QEMU依賴於 Linux 核心功能 。Linux 核心中的 `binfmt_misc`功能可以使得核心識別任意型別的可以執行檔案格式,並傳遞到特定的使用者空間應用程式和虛擬機器(https://zh.wikipedia.org/wiki/Binfmt_misc)。當 Linux 遇到一種無法識別的可執行檔案格式(比如說其它平臺的可執行檔案格式)時,它會檢查有沒有配置任何“使用者空間應用程式”用於處理它。如果檢測到了,就將可執行檔案傳遞給該應用程式。
為此,我們需要在核心當中註冊其它平臺的可執行檔案格式。
對於使用 Docker Desktop(MacOS 和 Windows 上都是)的同學,因為預設配置了 `binfmt_misc`,可以跳過這一步。而使用 Linux 發行版作業系統的同學則需要自行安裝配置 `binfmt_misc`,以便能夠非原生的其它平臺的映象。
要在宿主機上執行其它 CPU 平臺的指令,需要安裝 QEMU 模擬器。因為程式執行時會在當前程式可見的檔案系統中查詢動態庫,而在容器或chroot環境中註冊的處理程式在其它的 cgroup namespace 中可能無法找到,所以需要靜態編譯連線的QEMU。同時,我們需要安裝一個包含足夠新的update-binfmts二進位制檔案的包,以便能夠支援fix-binary(F)標誌,並在註冊QEMU模擬器時實際使用,這樣才能結合 buildx 一起映象跨平臺構建。
QEMU 和 binfmt_misc 支援工具可以通過**宿主機**或者**Docker 容器映象**安裝。但是,使用Docker映象安裝配置能讓事情變得更加簡單。映象 `docker/binfmt` 中包含QEMU二進位制檔案和在binfmt_misc中註冊QEMU的安裝指令碼。
```
docker run --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d
```
執行完後,我們驗證下是否註冊成功了。成功註冊後,`/proc/sys/fs/binfmt_misc` 目錄中會有多個`qemu-`字首的檔案。檢視 `/proc/sys/fs/binfmt_misc/qemu-aarch64` 檔案內容,可以看到 falgs 標誌為 `OCF`,說明這個處理程式是通過 (F)標誌註冊的,能夠正常的結合 buildx 完成跨平臺構建。
```
⚡ root@kofj-hk ~ ls -al /proc/sys/fs/binfmt_misc
total 0
drwxr-xr-x 2 root root 0 Oct 12 20:19 .
dr-xr-xr-x 1 root root 0 Oct 12 20:19 ..
-rw-r--r-- 1 root root 0 Oct 12 20:19 python2.7
-rw-r--r-- 1 root root 0 Oct 12 20:19 python3.6
-rw-r--r-- 1 root root 0 Oct 12 20:21 qemu-aarch64
-rw-r--r-- 1 root root 0 Oct 12 20:21 qemu-arm
-rw-r--r-- 1 root root 0 Oct 12 20:21 qemu-ppc64le
-rw-r--r-- 1 root root 0 Oct 12 20:21 qemu-s390x
--w------- 1 root root 0 Oct 12 20:19 register
-rw-r--r-- 1 root root 0 Oct 12 20:19 status
⚡ root@kofj-hk ~ cat /proc/sys/fs/binfmt_misc/qemu-aarch64
enabled
interpreter /usr/bin/qemu-aarch64
flags: OCF
offset 0
magic 7f454c460201010000000000000000000200b7
mask ffffffffffffff00fffffffffffffffffeffff
```
#### 使用 buildx 構建
前置依賴注備好後,我們終於可以使用 buildx 構建多平臺映象了。與其它方案不同的是,使用 buildx 可以讓我們不必改動 dockerfile。
Buildx 始終使用 BuildKit 引擎構建映象,不需要配置環境變數`DOCKER_BUILDKIT=1`。BuildKit 可以很好的用於多個平臺的構建,而不僅適用於我們當前構建映象時所使用的平臺和作業系統。進行構建時,使用 `--platform`標誌可以用於指定構建輸出的目標平臺(例如 `linux/amd64`,`linux/arm64`,`linux/riscv64`)。
首先,我們先準備好 Dockerfile 檔案:
```
FROM golang:1.14 as builder
COPY . /src
WORKDIR /src
RUN ls -al && go build -a -tags netgo -ldflags '-w' -mod=vendor -v -o /src/bin/webapp /src/cmd/main.go
# Final image
FROM ubuntu:18.04
LABEL authors="Fanjian Kong"
COPY --from=builder /src/bin/webapp /app/
WORKDIR /app
CMD ["/app/webapp"]
```
然後,讓我們嘗試下執行 buildx。
```
✘ ⚡ root@kofj-hk ~ docker buildx build --platform linux/amd64,linux/arm64,linux/arm -t harbor-community.tencentcloudcr.com/multi-arch/demo:2020-10-12 . --push
error: auto-push is currently not implemented for docker driver, please create a new builder instance
```
居然報錯 `error: auto-push is currently not implemented for docker driver, please create a new builder instance` 了!別擔心,這是因為 Docker 預設的 builder 是不支援多平臺構建的。我們可以通過 `docker buildx ls` 檢視當前節點上的 builder 有哪些。
```
⚡ root@kofj-hk ~ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
default * docker
default default running linux/amd64, linux/386
```
為了使用多平臺構建功能,我們需要新建一個 builder,並設定當前 builder 為新建的。
```
# 新建同時切換 builder
docker buildx create --use --name mybuilder
# 只新建,然後再切換 builder
docker buildx create --name mybuilder
docker buildx use mybuilder
```
現在,讓我們再次執行 buildx,看著一切向著期待的方向發展了。
#### 注意事項
**⚠️注意1**:到目前位置,buildx支援 `linux/amd64, linux/386, linux/arm/v7, linux/arm/v6, linux/arm64, linux/ppc64le, linux/s390x`。所以 `docker/binfmt` 映象僅註冊了 arm、ppc64le 和 s390x 的處理程式。如果你需要構建、執行 RISC-V 平臺的容器映象,建議使用 multiarch/qemu-user-static 映象映象配置。
```
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
```
**⚠️注意2**:在 [軟體依賴](https://iwiki.woa.com/download/resources/com.oa.iwiki.cherry/res/cherry-preview.html#軟體依賴) 中我們提到需要 Linux 核心版本 >= 4.8.0;如果在核心版本為 3.10.0 的系統(比如 CentOS)上執行 `docker/binfmt`,會出現報錯 `Cannot write to /proc/sys/fs/binfmt_misc/register: write /proc/sys/fs/binfmt_misc/register: invalid argument`,這是由於核心不支援 (F)標誌造成的。出現這種情況,建議您升級系統核心或者換使用較高版本核心的 Linux 發行版。
![enter image description here](https://img2020.cnblogs.com/other/2041406/202012/2041406-20201225201911078-146110941.png)
## 小結
多年前,大規模部署應用程式是一項非常耗費人力、財力、時間,還需要大量技能和技巧的事務,工程師們還需要應對應用程式所執行的每一臺伺服器的環境差異。這對大公司而言是個極其沉重的負擔,小公司更是無力應對。
正如多年前人們無法想象大規模部署複雜的應用程式只需要一個 `kubectl create` 命令,不久前我們也不會想到構建多平臺的容器映象只需要一個 `docker buildx build`。但是,我們還有更加廣闊的想象空間,自動化流程、更多平臺的支援、更智慧簡單的工具,你能想到的都有可能在不久的將來變成現實。
技術的發展進步,不斷降低了生產活動中社會平均勞動時間,提升了生產力,能夠釋放勞動者去做更多有益的探索。讓我們不斷學習、擁抱、應用新技術,在時代的浪潮中勇往直前。
### 可執行方案回顧
1. 確保使用的 Linux 發行版**核心>=4.8.0**(推薦使用 Ubuntu 18.04 以上的 TLS 發行版),且 **Docker >= 19.03**;
2. 啟用Docker CLI 實驗性功能: `export DOCKER_CLI_EXPERIMENTAL=enabled`;
3. 配置其它平臺的模擬器:`docker run --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d`;
4. 新建 Docker builder 例項支援多平臺構建: `docker buildx create --use --name mybuilder`;
5. 在專案目錄中執行構建: `docker buildx build --platform linux/amd64,linux/arm64,linux/arm -t harbor-community.tencentcloudcr.com/${YOUR_NAMESPACE}/multi-arch:2020-10-12 . --push`。
6. 相關演示程式碼、指令碼可以在 https://github.com/kofj/multi-arch-demo.git 獲取。
### 推廣時間
目前,騰訊雲**容器映象服務 TCR**已完成公測進入商業化階段。我們也已經對部分使用者開放了 Multi Arch 映象和 OCI 雲原生製品支援。如果您對該功能感興趣,歡迎聯絡客服開通。
![mult-arch-tcr-console](https://img2020.cnblogs.com/other/2041406/202012/2041406-20201225201911412-1360319071.png)
>【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多幹貨!!
![](https://img2020.cnblogs.com/other/2041406/202012/2041406-20201225201911803-687500