前言
隨著容器、芯片技術的進一步發展,以及綠色、節能、信創等方面的要求,多 CPU 架構的場景越來越常見,典型的應用場景包括:
- 信創:x86 服務器 + 鯤鵬 ARM 等信創服務器;
- 個人電腦:蘋果 Mac M1 + Windows 電腦(或舊的 Intel 芯片蘋果電腦);
- Edge:資料中心使用 x86 服務器,邊緣 Edge 端使用低功耗的 arm 邊緣設備(如樹莓派等),
容器云原生技術在這方面支持的是很好,但是實際使用中細節會有一些問題,舉一個例子,就是:如何保存/同步多架構容器 Docker 鏡像
本次先以將 Docker Hub 的鏡像同步到本地鏡像倉庫為例說明,
詞匯表
| 英文 | 中文 | 說明 |
|---|---|---|
| multi-arch image | 多架構鏡像 | |
| variant | 變體 | 不同變體指的如:redis 鏡像的 arm/v5 和 arm/v7 兩種變體 |
| manifest | 清單 | |
| manifest-list | 清單(的)串列 | |
| layer | (鏡像)層 | |
| image index | 鏡像索引 | OCI 專有名詞,含義和 manifest-list 相同 |
| manifest digest | 清單摘要 |
容器鏡像如何支持多架構
一個多架構鏡像(A multi-arch image)是一種容器鏡像,它可以組合不同架構體系(如 amd64 和 arm)的變體(variants),有時還可以組合不同作業系統(如 windows 和 linux)的變體,運行支持多架構的鏡像時,容器客戶端會自動選擇與你的 OS 和架構相匹配的鏡像變體,
多架構鏡像是基于鏡像清單和清單串列實作的,
清單(Manifests)
每個容器鏡像都由一個“清單”表示,清單是一個 JSON 檔案,用于唯一標識鏡像,并參考其層(layer)及其相應的大小,
hello-world Linux 鏡像的基本清單類似于以下內容:
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1510,
"digest": "sha256:fbf289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 977,
"digest": "sha256:2c930d010525941c1d56ec53b97bd057a67ae1865eebf042686d2a2d18271ced"
}
]
}
清單串列 (Manifest-lists)
多架構鏡像的清單串列(通常稱為 OCI 鏡像 的鏡像索引)是鏡像的集合(索引),您可以通過指定一個或多個鏡像名稱來創建一個,它包括有關每個鏡像的詳細資訊,例如支持的作業系統和體系架構、大小和清單摘要 (manifest digest),清單串列的使用方式與 docker pull 和 docker run 命令 中的鏡像名稱相同,
docker CLI 使用 docker manifest命令管理清單和清單串列,
?? Warning:
目前,該命令
docker manifest和子命令是實驗性的,有關使用實驗性命令的詳細資訊,請參閱 Docker 檔案,??筆者注:可能是因為實驗性的原因,使用程序中有幾個多架構鏡像碰到了詭異的問題,
您可以使用該命令 docker manifest inspect 查看清單串列,以下是多架構鏡像hello-world:latest 的輸出,它有三個清單:兩個用于 Linux 作業系統體系架構,一個用于 Windows 體系架構,
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 524,
"digest": "sha256:83c7f9c92844bbbb5d0a101b22f7c2a7949e40f8ea90c8b3bc396879d95e899a",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 525,
"digest": "sha256:873612c5503f3f1674f315c67089dee577d8cc6afc18565e0b4183ae355fb343",
"platform": {
"architecture": "arm64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1124,
"digest": "sha256:b791ad98d505abb8c9618868fc43c74aa94d08f1d7afe37d19647c0030905cae",
"platform": {
"architecture": "amd64",
"os": "windows",
"os.version": "10.0.17763.1697"
}
}
]
}
使用 docker manifest 保存多架構鏡像
這里是將多架構的鏡像推送到本地鏡像倉庫步驟:
-
標記每個特定于體系結構的鏡像并將其推送到容器注冊表,以下示例假定有兩個 Linux 體系結構:arm64 和 amd64,
docker tag myimage:arm64 \ 192.168.2.23:5000/multi-arch-samples/myimage:arm64 docker push 192.168.2.23:5000/multi-arch-samples/myimage:arm64 docker tag myimage:amd64 \ 192.168.2.23:5000/multi-arch-samples/myimage:amd64 docker push 192.168.2.23:5000/multi-arch-samples/myimage:amd64 -
運行
docker manifest create以創建清單串列,以將前面的鏡像合并到多架構鏡像中,docker manifest create 192.168.2.23:5000/multi-arch-samples/myimage:multi \ 192.168.2.23:5000/multi-arch-samples/myimage:arm64 \ 192.168.2.23:5000/multi-arch-samples/myimage:amd64 -
使用以下命令
docker manifest push將清單推送到鏡像倉庫:docker manifest push 192.168.2.23:5000/multi-arch-samples/myimage:multi -
使用命令
docker manifest inspect查看清單串列,上一節顯示了命令輸出的示例,
將多架構清單推送到鏡像倉庫后,使用多架構鏡像的方式與處理單架構鏡像的方式相同,例如,使用 docker pull 拉取鏡像,
保存/同步多架構鏡像實用腳本一 - 基于 docker manifest
場景一
已有多架構壓縮包 需要 load 壓縮包并將多架構鏡像上傳到本地鏡像倉庫
以 K3s 為例,官方在 release 時已經發布了多架構的離線鏡像壓縮包,分別為:
k3s-airgap-images-amd64.tar.gzk3s-airgap-images-arm.tar.gzk3s-airgap-images-arm64.tar.gz- ...
這些包已經下載好,并傳到客戶/用戶的離線環境機器上,現在需要 load 壓縮包并將多架構鏡像上傳到本地鏡像倉庫
大致步驟
docker load壓縮包- 其中的鏡像逐個打 tag, 改為
<本地鏡像倉庫地址>/.../...:<tag>-<arch> - push 鏡像
- 以上步驟重復 3 遍,將 tag 帶有
-amd64-arm-arm64的鏡像都 push 到本地鏡像倉庫 - 鏡像逐個
docker manifest create以創建清單串列 - 使用以下命令
docker manifest push將清單逐個推送到鏡像倉庫
完整腳本如下:
?? Warning:
由于本人能力有限,在使用 k3s v1.21.7+k3s1 版本的 8*3 個離線鏡像做測驗的時候,總是 5 個成功,另外 3 個出現 manifest list 的 arch 和 manifest 對不上的情況,
不知道是我腳本問題還是docker manifest命令是實驗性導致的,
有經驗的還請幫忙看看,謝謝~
#!/bin/bash
amd64_images="k3s-airgap-images-amd64.tar.gz"
arm64_images="k3s-airgap-images-arm64.tar.gz"
arm_images=""
list="k3s-images.txt"
usage() {
echo "USAGE: $0 [--amd64-images k3s-airgap-images-amd64.tar.gz] [--arm64-images k3s-airgap-images-arm64.tar.gz] [---arm-images k3s-airgap-images-arm.tar.gz] --registry my.registry.com:5000"
echo " [-l|--image-list path] text file with list of images; one image per line."
echo " [-x|--amd64-images path] amd64 arch tar.gz generated by docker save."
echo " [-a|--arm64-images path] arm64 arch tar.gz generated by docker save."
echo " [---arm-images path] arm arch tar.gz generated by docker save."
echo " [-r|--registry registry:port] target private registry:port."
echo " [-h|--help] Usage message"
}
push_manifest() {
export DOCKER_CLI_EXPERIMENTAL=enabled
manifest_list=()
for i_arch in "${arch_list[@]}"; do
manifest_list+=("$1-${i_arch}")
done
echo "Preparing manifest $1, list[${arch_list[@]}]"
docker manifest create "$1" "${manifest_list[@]}" --insecure
docker manifest push "$1" --purge --insecure
}
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
-r | --registry)
reg="$2"
shift # past argument
shift # past value
;;
-l | --image-list)
list="$2"
shift # past argument
shift # past value
;;
-x | --amd64-images)
amd64_images="$2"
shift # past argument
shift # past value
;;
-a | --arm64-images)
arm64_images="$2"
shift # past argument
shift # past value
;;
--arm-images)
arm_images="$2"
shift # past argument
shift # past value
;;
-h | --help)
help="true"
shift
;;
*)
usage
exit 1
;;
esac
done
if [[ -z $reg ]]; then
usage
exit 1
fi
if [[ $help ]]; then
usage
exit 0
fi
arch_list=()
if [[ -n "${amd64_images}" ]]; then
arch_list+=("amd64")
fi
if [[ -n "${arm64_images}" ]]; then
arch_list+=("arm64")
fi
if [[ -n "${arm_images}" ]]; then
arch_list+=("arm")
fi
image_list=()
while IFS= read -r i; do
[ -z "${i}" ] && continue
image_list+=("${i}")
done <"${list}"
for arch in "${arch_list[@]}"; do
[ -z "${arch}" ] && continue
case $arch in
amd64)
docker load --input ${amd64_images}
;;
arm64)
docker load --input ${arm64_images}
;;
arm)
docker load --input ${arm_images}
;;
esac
for i in "${image_list[@]}"; do
[ -z "${i}" ] && continue
case $i in
*/*)
image_name="${reg}/${i}"
;;
*)
image_name="${reg}/library/${i}"
;;
esac
docker tag "${i}" "${image_name}-${arch}"
docker rmi -f "${i}"
docker push "${image_name}-${arch}"
done
done
for i in "${image_list[@]}"; do
[ -z "${i}" ] && continue
case $i in
*/*)
image_name="${reg}/${i}"
;;
*)
image_name="${reg}/library/${i}"
;;
esac
push_manifest "${image_name}"
done
使用方法:
./load-images-multi-arch.sh --registry 192.168.2.23:5000 --arm-images k3s-airgap-images-arm.tar.gz
日志輸出如下:
$ ./load-images-multi-arch.sh --registry 192.168.2.23:5000 --arm_images k3s-airgap-images-arm.tar.gz
# docker load 鏡像第一輪,是 amd64 架構的
67f770da229b: Loading layer [==================================================>] 1.45MB/1.45MB
Loaded image: rancher/library-busybox:1.32.1
...
# 打 tag 并 delete 原 tag 鏡像,并 push
Untagged: rancher/coredns-coredns:1.8.3
The push refers to repository [192.168.2.23:5000/rancher/coredns-coredns]
85c53e1bd74e: Pushed
225df95e717c: Pushed
1.8.3-amd64: digest: sha256:db4f1c57978d7372b50f416d1058beb60cebff9a0d5b8bee02bfe70302e1cb2f size: 739
...
# docker load 鏡像第二輪,是 arm64 架構的
...
32626eb1fe89: Loading layer [==================================================>] 526.8kB/526.8kB
Loaded image: rancher/pause:3.1
...
Untagged: rancher/pause:3.1
The push refers to repository [192.168.2.23:5000/rancher/pause]
32626eb1fe89: Pushed
3.1-arm64: digest: sha256:2aac966ece8906a535395f92bb25f0e8e21dac737df75b381e8f9bdd3ed56528 size: 527
# docker load 鏡像第二輪,是 arm 架構的
8e322dc9c333: Loading layer [==================================================>] 5.045MB/5.045MB
efed3cfd1b26: Loading layer [==================================================>] 1.623MB/1.623MB
a46153382f22: Loading layer [==================================================>] 3.584kB/3.584kB
Loaded image: rancher/klipper-lb:v0.3.4
...
Untagged: rancher/coredns-coredns:1.8.3
The push refers to repository [192.168.2.23:5000/rancher/coredns-coredns]
9f4a0b0fd8b2: Pushed
225df95e717c: Layer already exists
1.8.3-arm: digest: sha256:dfc241eae22da74dd378535b69d7927f897acf48424cdcb90991b33f412cb7ae size: 739
# docker manifest create
Preparing manifest 192.168.2.23:5000/rancher/coredns-coredns:1.8.3, list[amd64 arm64 arm]
Created manifest list 192.168.2.23:5000/rancher/coredns-coredns:1.8.3
sha256:dc76fece93e42f05e7013e159097a0d426734fd268467f242d5b155dd49b0221
Preparing manifest 192.168.2.23:5000/rancher/klipper-helm:v0.6.6-build20211022, list[amd64 arm64 arm]
Created manifest list 192.168.2.23:5000/rancher/klipper-helm:v0.6.6-build20211022
sha256:e1c6842554ea37e66443cfab9a2422231bf8390b4c69711a74eb4cccde9d3dba
Preparing manifest 192.168.2.23:5000/rancher/klipper-lb:v0.3.4, list[amd64 arm64 arm]
Created manifest list 192.168.2.23:5000/rancher/klipper-lb:v0.3.4
sha256:98842bae8630a2aab1a94960185e152745ecf16ca69cf1eefdb53848cbc41063
Preparing manifest 192.168.2.23:5000/rancher/library-busybox:1.32.1, list[amd64 arm64 arm]
Created manifest list 192.168.2.23:5000/rancher/library-busybox:1.32.1
sha256:0b93c11bfd89ee5c971deaf9f312d115b2e1d797f79a7f68a266baecfb09a99f
Preparing manifest 192.168.2.23:5000/rancher/library-traefik:2.4.8, list[amd64 arm64 arm]
Created manifest list 192.168.2.23:5000/rancher/library-traefik:2.4.8
sha256:58464dda10504d271a17855541ed8d31a787ea25eb751ecce90e14256f23eb24
Preparing manifest 192.168.2.23:5000/rancher/local-path-provisioner:v0.0.19, list[amd64 arm64 arm]
Created manifest list 192.168.2.23:5000/rancher/local-path-provisioner:v0.0.19
sha256:0c797ef85540a4934ea84a9471f4f5a10c93f749ee668d92527361c61bbe98c3
Preparing manifest 192.168.2.23:5000/rancher/metrics-server:v0.3.6, list[amd64 arm64 arm]
Created manifest list 192.168.2.23:5000/rancher/metrics-server:v0.3.6
sha256:742595f61320bcaead987c5aafc3eb64b9a9151edb02b9e4d27f8abcae26d92e
Preparing manifest 192.168.2.23:5000/rancher/pause:3.1, list[amd64 arm64 arm]
Created manifest list 192.168.2.23:5000/rancher/pause:3.1
sha256:f3ef3cbaf2ea466a0c2a2cf3db0d9fbc30f4c24e57a79603aa0fa8999d4813b0
Skopeo 簡介
- Skopeo 簡介 - K8S 1.20 棄用 Docker 評估之 Docker CLI 的替代產品 - 東風微鳴技術博客 (ewhisper.cn)
最近 Skopeo 版本更新到了 v1.8, 最近的版本增加了一些與多架構有關的 flags, 使得通過 skopeo 進行多架構鏡像的保存/同步更為方便,
?? Notes:
目前關于多架構,只有 3 個選項,3 個選項都沒有選擇源鏡像多個架構的其中幾個的能力,但正在開發中,
具體見這個 Issue: feature: Support list of archs forsynccommand · Issue #1694 · containers/skopeo (github.com)
以下是一些相關 flags:
skopeo--override-arch <arch>: 使用arch代替機器的架構來選擇鏡像,--override-os <os>: 使用os代替機器的 OS 來選擇鏡像,--override-variant <variant>: 使用variant運行的架構的變體來選擇鏡像,(不同變體指的如:redis 鏡像的arm/v5和arm/v7兩種變體)
skopeo copy--all, -a: 如果 source-image 參考的是一個鏡像串列,那么不要只復制與當前作業系統和體系架構匹配的鏡像(取決于全域的--override-os、--override-arch和--override-variant選項的使用),而是嘗試復制串列中的所有鏡像,以及串列本身,--multi-arch: 如果源鏡像參考多架構鏡像,則控制要復制的內容,默認設定是system,system: 僅復制與系統架構匹配的鏡像all: 復制完整的多架構鏡像index-only: 僅復制鏡像索引 (image index).(index-only選項通常會失敗,除非目標中已經存在每個架構所參考的鏡像,或者目標注冊中心支持稀疏索引,)
skopeo sync--all, -a: 同上
?? Notes:
根據
skopeo copy --multi-arch index-only的描述,場景一 還有一種實作就是:
docker manifest之前的步驟,維持原狀- 將
docker manifest create和docker manifest push替換為skopeo copy --multi-arch index-only
保存/同步多架構鏡像實用腳本二 - 基于 skopeo copy
場景二
直接從 docker.io 同步鏡像到本地鏡像倉庫
以 K3s 某一版本為例,鏡像串列為:
- rancher/coredns-coredns:1.8.3
- rancher/klipper-helm:v0.6.6-build20211022
- rancher/klipper-lb:v0.3.4
- rancher/library-busybox:1.32.1
- rancher/library-traefik:2.4.8
- rancher/local-path-provisioner:v0.0.19
- rancher/metrics-server:v0.3.6
- rancher/pause:3.1
這里直接基于 鏡像搬運工 skopeo 提供的腳本做修改,修改后如下:
?? Notes:
因為較新版本的
skopeo才有上面說的一系列 flags, 我的 Ubuntu apt 安裝的skopeo還停留在v1.5版本,沒有上述功能,所以直接通過docker run方式運行
除此之外還添加了--multi-arch all選項,
#!/bin/bash
GREEN_COL="\\033[32;1m"
RED_COL="\\033[1;31m"
NORMAL_COL="\\033[0;39m"
SOURCE_REGISTRY=$1
TARGET_REGISTRY=$2
IMAGES_LIST_FILE=$3
: ${IMAGES_LIST_FILE:="k3s-images.txt"}
: ${TARGET_REGISTRY:="192.168.2.23:5000"}
: ${SOURCE_REGISTRY:="docker.io"}
set -eo pipefail
CURRENT_NUM=0
ALL_IMAGES="$(sed -n '/#/d;s/:/:/p' ${IMAGES_LIST_FILE} | sort -u)"
TOTAL_NUMS=$(echo "${ALL_IMAGES}" | wc -l)
skopeo_copy() {
if docker run -it quay.io/skopeo/stable:latest copy --insecure-policy --src-tls-verify=false --dest-tls-verify=false \
--src-creds caseycui:xxxxxxxxxxxxxxxxxxxxx --multi-arch all --override-os linux -q docker://$1 docker://$2; then
echo -e "$GREEN_COL Progress: ${CURRENT_NUM}/${TOTAL_NUMS} sync $1 to $2 successful $NORMAL_COL"
else
echo -e "$RED_COL Progress: ${CURRENT_NUM}/${TOTAL_NUMS} sync $1 to $2 failed $NORMAL_COL"
exit 2
fi
}
for image in ${ALL_IMAGES}; do
let CURRENT_N192.168.2.23:5000UM=${CURRENT_NUM}+1
skopeo_copy ${SOURCE_REGISTRY}/${image} ${TARGET_REGISTRY}/${image}
done
運行效果如下:
$ bash sync.sh
Progress: 1/8 sync docker.io/rancher/coredns-coredns:1.8.3 to 192.168.2.23:5000/rancher/coredns-coredns:1.8.3 successful
Progress: 2/8 sync docker.io/rancher/klipper-helm:v0.6.6-build20211022 to 192.168.2.23:5000/rancher/klipper-helm:v0.6.6-build20211022 successful
Progress: 3/8 sync docker.io/rancher/klipper-lb:v0.3.4 to 192.168.2.23:5000/rancher/klipper-lb:v0.3.4 successful
Progress: 4/8 sync docker.io/rancher/library-busybox:1.32.1 to 192.168.2.23:5000/rancher/library-busybox:1.32.1 successful
Progress: 5/8 sync docker.io/rancher/library-traefik:2.4.8 to 192.168.2.23:5000/rancher/library-traefik:2.4.8 successful
Progress: 6/8 sync docker.io/rancher/local-path-provisioner:v0.0.19 to 192.168.2.23:5000/rancher/local-path-provisioner:v0.0.19 successful
Progress: 7/8 sync docker.io/rancher/metrics-server:v0.3.6 to 192.168.2.23:5000/rancher/metrics-server:v0.3.6 successful
Progress: 8/8 sync docker.io/rancher/pause:3.1 to 192.168.2.23:5000/rancher/pause:3.1 successful
最終效果
最終本地的鏡像效果如下:

??????
??? Reference
- K8S 1.20 棄用 Docker 評估之 Docker 和 OCI 鏡像格式的差別 - 東風微鳴技術博客 (ewhisper.cn)
- Skopeo 簡介 - K8S 1.20 棄用 Docker 評估之 Docker CLI 的替代產品 - 東風微鳴技術博客 (ewhisper.cn)
- docker manifest | Docker Documentation
- containers/skopeo: Work with remote images registries - retrieving information, images, signing content (github.com)
- Multi-architecture images in your registry - Azure Container Registry | Microsoft Docs
- 鏡像搬運工 skopeo
本文由博客一文多發平臺 OpenWrite 發布!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/538638.html
標籤:其他
