前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TKEStack适配ARM架构之路

TKEStack适配ARM架构之路

原创
作者头像
哈里
修改2021-02-18 15:59:02
1.9K0
修改2021-02-18 15:59:02
举报
文章被收录于专栏:容器容器

1. 前言

腾讯TKEStack作为面向私有云业务场景的开源容器平台,应对的场景也会比较多样,比如国产服务器有一大阵营是基于arm架构的,那在国产化趋势下,客户的服务器架构可能会出现x86和arm混布在一起的情况;再比如随着IoT物联网的来临,以树莓派为代表的智能硬件上使用容器服务也会成为一种趋势。这意味着TKEStack单纯在x86服务器上运行是远远不够的,对于arm架构的支持,势在必行。

如何支持arm架构

简单来说,就是重新适配arm 架构:对于可执行文件,需要重新编译;对于容器镜像,需要重新构建。

因为不同架构的指令集不一样,在一个架构下编译并生成的二进制可执行文件,包含的是这个架构下的指令,直接将这个可执行文件放到另一架构上运行,会报cannot execute binary file类似错误。对于容器镜像,跨架构执行则会报:standard_init_linux.go:211: exec user process caused "exec format error"类似错误。如果平时遇见了上述错误,那一定是执行了架构不匹配的文件或容器镜像。

问题挑战

常规适配arm架构的做法就是在arm服务器上,把应用程序的编译、构建、打包的流程都走一遍,然后再将生成的arm组件包,跟x86的组件包分别命名,再打包一起交付给客户,然后部署时,由客户选择安装x86的组件或是arm组件。

常规流程不仅需额外引入一台arm服务器,在上面再搭建一套CI/CD流程,并且由于arm机器还未普及,该流程也限制了TKEStack开发人员及开源社区的参与。另外x86组件跟arm组件分别命名,这导致使用到这些组件的代码都要仔细重构或校验,以确保代码里使用了正确的版本,这也给代码维护人员带来了负担。

经过调研之后,TKEStack采取了不改变原本代码结构、构建流程的方式下,做到跨平台交付多架构组件的目标:通过充分利用容器技术及虚拟化技术,最小化了TKEStack适配arm(及未来其他架构)的改动量,也减轻了后续维护多架构的工作量。

2.?适配准备:组件梳理

TKEStack对外交付的是一个installer安装包,里面除了TKEStack本身组件外,还包含了搭建一个Kubernetes集群所需的依赖:docker、kubeadm、k8s组件。具体如下图所示:

图一:TKEStack组件梳理
图一:TKEStack组件梳理

从组件梳理图可以看出,TKEStack的自研组件已经全量容器化了,所以TKEStack适配arm的核心就在于如何能够以统一的方式构建多个架构(x86 / arm)的容器镜像,并且在使用到这些容器镜像的地方,都能最小化代码改动,不因引入多个架构而导致部署容器时使用到错误的版本。

3. 容器技术:docker manifest list

在多个架构(x86 / arm)或者多个平台上(linux_amd64 / windows_amd64)上使用容器镜像时,就不得不提Docker公司在2017年9月提出的特性:docker manifest list。一份manifest list可以理解成是多个容器镜像汇总在一起的清单列表:清单里列明了每个镜像适用的平台或者架构,以及用于找到该镜像的哈希值。

如下图中间部分所示,命令 docker manifest inspect app:v1 查看了容器镜像 app:v1 的清单列表,得知在 linux/amd64(x86架构)平台上,app:v1 对应的容器镜像应为哈希 sha256:xxx 指向的镜像,而在 linux/arm64/v8(arm架构)平台上,则应对应哈希 sha256:yyy 指向的镜像。所以当客户端向镜像仓库发起请求,准备拉取 app:v1 镜像时,客户端会根据镜像仓库返回的清单列表,从中选出架构匹配的镜像,再去拉取相应的镜像。也就是说,从用户的角度来看,不用担心架构的差异,服务端会为用户屏蔽掉架构的区别。

图二:docker manifest list解析过程
图二:docker manifest list解析过程

对于TKEStack,只要TKEStack在构建完多架构容器镜像后,并推送重命名后的x86架构容器镜像(带amd64后缀,如app-amd64:v1)跟arm架构容器镜像(带arm64后缀,如app-arm64:v1)至镜像仓库后,再生成一份不带架构后缀(如app:v1)的清单列表 manifest list指向多架构镜像,就可以在用户无感知的情况下,既能实现原本x86的机器正常拉取amd64的镜像,也能让新增的arm的机器拉取到arm64的镜像:

图三:docker manifest list构建及推送过程
图三:docker manifest list构建及推送过程

到此,支持多架构后最小化代码改动的目标已有了方案:通过额外引入一层清单列表 manifest list,屏蔽多架构信息,让原本的代码以及用户在使用到容器镜像时,不因引入多个架构镜像而混乱。

4. 虚拟化及内核技术:QEMU 和 binfmt_misc

目标之二:保持原本容器镜像的构建流程,不因支持多架构后,因额外引入硬件平台要求,而限制了开源社区的参与。想要达到这个目标,就得实现跨平台构建容器镜像:在已普及的x86平台上,编译构建适用arm平台的容器镜像。

QEMU可以模拟很多平台,所以只要想办法在构建跨平台的容器镜像时,将其他平台的可执行文件传递给QEMU,由QEMU模拟对应的平台并执行,就可以达到跨平台构建的目的。而Linux 内核中的 binfmt_misc功能,刚好能将任意类型的可执行文件,传递至指定的用户态应用程序运行。所以只要在x86平台上安装QEMU模拟器,并在binfmt_misc中注册QEMU,让Linux遇到其他平台的执行文件时就传递给QEMU,这样就可以实现跨平台执行arm指令了。

5. 整体解决方案

通过上述容器技术及虚拟化技术后,TKEStack适配arm架构的整体方案如下:

  • (1)预先安装支持多架构的QEMU模拟器,并将QEMU注册到内核binfmt_misc中,然后在构建容器镜像时,通过--platform参数指定需要构建的目标架构,比如 docker build --platform linux/amd64 用来构建x86架构的镜像,docker build --platform linux/arm64 用来构建arm架构的镜像。如此就可以在原本自动构建流程里,生成跨平台容器镜像。
  • (2)构建流程里生成的容器镜像,镜像名严格按照架构信息打上后缀区分开来(app-amd64:v1 / app-arm64:v1),并逐个推送至镜像仓库后,再额外创建一个不带架构后缀的清单列表manifest list(app:v1),指向这些带了架构后缀的镜像,用以屏蔽多架构信息,不因镜像名含有架构后缀而改变使用体验。

这套方案的通用代码已经提取放到github上了:https://github.com/Shangru-WU/multi-arch-example。github上的示例主要有两个操作:(1)make image构建多平台镜像,(2)make push构建多平台镜像后,再推送镜像及manifest list至镜像仓库。

makefile代码解析

代码语言:javascript
复制
.PHONY: docker.buildx.install
docker.buildx.install:
	@$(ROOT_DIR)/build/lib/docker-buildx.sh docker_buildx:multi_arch_support

.PHONY: image
image: docker.buildx.install
	VERSION=$(VERSION) $(ROOT_DIR)/build/lib/image-build.sh image_build:build

.PHONY: push
push: docker.buildx.install
	VERSION=$(VERSION) $(ROOT_DIR)/build/lib/image-build.sh image_build:push

make image跟make push操作都会依赖于docker.buildx.install,docker.buildx.install主要是完成跨平台构建镜像的准备工作。这里选择docker?buildx而不是直接使用原生docker build,是因为buildx将会是下一代镜像构建的标准。buildx兼容现有docker build的特性,并额外对docker build进行优化。比如docker build在构建镜像时,只会按着Dockerfile上面步骤一步步串行执行下来。但buildx会尝试分析Dockerfile上的哪些步骤并无相互依赖,然后并行执行这些步骤,以提升构建速度。

docker buildx安装解析

buildx的准备工作全在$(ROOT_DIR)/build/lib/docker-buildx.sh脚本里,主要是判断docker版本,docker版本需大于等于19.03或者docker api版本大于等于1.40,然后下载buildx插件并启用docker实验特性export DOCKER_CLI_EXPERIMENTAL=enabled,再紧接着就是安装QEMU多架构模拟器以及注册到内核。注册过程也会检查内核版本,内核版本需大于等于4.8方能顺利注册。

镜像构建解析

通过docker buildx完成跨平台构建镜像的准备工作后,构建镜像流程就比较简单了,主要是通过 docker buildx build --platform 指定目标平台进行构建:

代码语言:javascript
复制
PLATFORMS=${PLATFORMS:-"linux_amd64 linux_arm64"}
for platform in ${PLATFORMS}; do
	os=${platform%_*}
	arch=${platform#*_}
	image_plat="${os}/${arch}"
	image_name="${DES_REGISTRY}-${arch}:${VERSION}"
	echo "===========> Building docker image ${image_name}"
	docker buildx build --platform ${image_plat} --load -t ${image_name} -f ${ROOT_DIR}/build/docker/Dockerfile ${ROOT_DIR}
done

示例代码里Dockerfile采用的是多阶段构建的方式,这也是为了达到最小化代码改动而引入的:第一阶段构建直接在Dockerfile里执行make build,意思就是在原平台怎么编译代码的,就算是跨平台也采用同样的编译流程,保留原本make build的方式。编译完后再把编译结果拷贝至最后的构建的容器镜像。

代码语言:javascript
复制
FROM golang:1.15.8 AS builder
ARG TARGETPLATFORM
RUN echo "building for ${TARGETPLATFORM}"
WORKDIR ${WORKDIR}
COPY . .
RUN make build
#####################################
FROM alpine:3.12.3
COPY --from=builder ${WORKDIR}/bin/main /app/
ENTRYPOINT ["/app/main"]

镜像推送解析

docker buildx build执行完后,多平台容器镜像已经生成,后续推送到镜像仓库时,则额外执行了docker manifest create及docker manifest annotate操作,为多平台镜像创建个manifest list,并把两者关联起来。最后再执行docker manifest push将清单列表也推送至镜像仓库。

代码语言:javascript
复制
docker push ${image_name}
docker manifest create --amend ${manifest_name} ${image_name}
docker manifest annotate ${manifest_name} ${image_name} \
	--os ${os} --arch ${arch} ${variant}
docker manifest push --purge ${manifest_name}

?至此通用的构建流程就走完了:(1)不改变原本make build方式,只是在外层多加了跨平台构建;(2)不改变原本拉取镜像方式,只是在推送镜像时多加了层清单列表manifest list。

6. 小结

容器化技术确实带来了很多很多便利,在一开始适配arm架构的过程中,谁也不曾料到,原来最后可以不对原本流程做任何重构的,只需要在外层引入新的技术便可。这也对那些已经无人维护,但又要进行国产化适配的代码带来了希望:在不入侵原有体系的情况下,达到适配的目的。即便需要进行适配、编译、调试,也可以在本地通过跨架构的方案实现,而不用一遍遍的来回将代码拷贝至arm编译机调试。

当然本文主要是作为一个大方向的通用方法论,毕竟实际过程中会遇见很多意料之外的事。没有任何方案能十全十美的,在多架构适配过程中,我们也遇见过代码里的系统调用在arm平台上不支持,需要改整段代码的情况,或者遇见在UOS(统一操作系统)里,有些系统信息不是按照标准方式返回的,需要额外绕过的情况。这种情况只能是见一个处理一个,但整体思路依旧是保持着适配时最小化改动这样的目标前行。

相关代码指引:https://github.com/Shangru-WU/multi-arch-example

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 前言
    • 如何支持arm架构
      • 问题挑战
      • 2.?适配准备:组件梳理
      • 3. 容器技术:docker manifest list
      • 4. 虚拟化及内核技术:QEMU 和 binfmt_misc
      • 5. 整体解决方案
        • makefile代码解析
          • docker buildx安装解析
            • 镜像构建解析
              • 镜像推送解析
              • 6. 小结
              相关产品与服务
              容器镜像服务
              容器镜像服务(Tencent Container Registry,TCR)为您提供安全独享、高性能的容器镜像托管分发服务。您可同时在全球多个地域创建独享实例,以实现容器镜像的就近拉取,降低拉取时间,节约带宽成本。TCR 提供细颗粒度的权限管理及访问控制,保障您的数据安全。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
              http://www.vxiaotou.com