运维 M1 芯片构建容器镜像的跨平台问题

lanzhiheng · 2023年01月03日 · 最后由 wikimo 回复于 2023年01月13日 · 492 次阅读

这个问题是笔者尝试把项目部署到 k8s 集群上的时候遇到的,简单记录一下。估计使用 M1 芯片的 Macbook 构建镜像的开发者多少都会遇到类似的问题。原文链接: https://step-by-step.tech/posts/m1-build-image-issue

L1002263.jpg

能在本地运行却无法在 k8s 集群上运行的镜像

当要把某个项目部署到 k8s 集群的时候,必先要对这个项目进行容器化。也就是需要编写对应的Dockerfile文件。项目运行所需要依赖的环境都将在Dockerfile文件中指定。不过这里面也会有些坑。

笔者遇到的问题是构建出来的镜像可以在本地运行,但是在 k8s 集群上就是运行失败。要说明的是,笔者的本地机器是 M1 芯片的 Macbook。

比如lanzhiheng/stone这个镜像,在本地docker run本地好好的

> docker run -it -p 4000:3000 lanzhiheng/stone
...
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

但是在 k8s 运行不了(这里是先把镜像推送到 Docker Hub,然后 k8s 从上面去拉取)

> kubectl run blog-on-k8s --image=lanzhiheng/stone

> kubectl get pods  blog-on-k8s
NAME          READY   STATUS             RESTARTS      AGE
blog-on-k8s   0/1     CrashLoopBackOff   3 (24s ago)   88s

而且出问题的时候,是没办法看到更详细的日志的。该服务目前的状态就是 Pod 还在,但是里面的 Container(容器) 启动失败,所以没有办法进入容器内部实施调试。

笔者初出茅庐,一直在猜想会不会是 k8s 有专门的镜像制作方式,会不会 Docker 制作的镜像需要某种特殊处理才能在 k8s 上使用?然而上网搜了一下始终没有找到相关的镜像处理器,看来正常情况下 Docker 打包的镜像应该是可以直接用在 k8s 上了。为了简化这个问题,笔者弄了个更简单的镜像来测试。

简化问题

为了测试 Docker 的镜像是不是真的能在 k8s 上面使用,直接在 k8s 跑一下 nginx 的 Docker 官方镜像就知道了。

> kubectl run  nginx --image=nginx

> kubectl get pods nginx
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          65s

还是能跑起来的。这么说 Docker 的镜像还是能用在 k8s 上的,那就是笔者构建的镜像有问题了,我再尝试往官方镜像套一层试试看,Dockerfile文件尽可能简单

FROM nginx
> docker build -t lanzhiheng/nginx  .
> docker push lanzhiheng/nginx

本地用 Docker 服务针对该镜像创建容器是没啥问题的

> docker run -it lanzhiheng/nginx
...
2022/12/31 03:48:34 [notice] 1#1: signal 28 (SIGWINCH) received
2022/12/31 03:48:34 [notice] 1#1: signal 28 (SIGWINCH) received

然而推送到 Docker Hub,然后 k8s 拉下来跑就有问题了

> kubectl run my-nginx --image=lanzhiheng/nginx

> kubectl get pods  my-nginx
NAME       READY   STATUS             RESTARTS     AGE
my-nginx   0/1     CrashLoopBackOff   1 (8s ago)   74s

官方的镜像nginx跟笔者自己包了一层的镜像lanzhiheng/nginx本质上并无太大区别。然而笔者的镜像在自己的 Macbook 上用 Docker 跑没啥问题,然后到了线上的 k8s 环境就有问题了。

现在想想最可能的原因就是笔者的 M1 芯片 Macbook 打包的镜像无法在基于 Linux 的 k8s 服务上直接使用。后来笔者跑去一台 Linux 服务器上构建一摸一样的镜像(同一个 Dockerfile)

> kubectl run my-nginx-from-linux --image=lanzhiheng/nginx-from-linux

> kubectl get pods  my-nginx-from-linux
NAME                  READY   STATUS    RESTARTS   AGE
my-nginx-from-linux   1/1     Running   0          55s

这次能运行成功,这么看来问题就出在 M1 芯片的 Macbook 上了。看来系统不同构建的镜像还是不能通用的。

解决方案

从前面的现象可知,M1 芯片 Macbook 在默认情况下构建的镜像在 Macbook 上使用没啥太大问题,然后到了 Linux 的机器上可能就有问题了。不管是用基于 Linux 的 Docker 来跑还是 k8s 来跑都是不行的

> docker run lanzhiheng/nginx

WARNING: The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64) and no specific platform was requested
exec /docker-entrypoint.sh: exec format error

类似的提示信息,在 k8s 环境下还不一定能看得到,所以打包镜像的时候一定要注意跨平台的问题。其实解决方案也很简单,我们只需要在 M1 芯片的 Mac 上构建镜像的时候加上参数--platform linux/amd64,那么打包出来的镜像就可以在 Linux 的机器上使用了。

> docker build -t lanzhiheng/nginx-for-linux --platform linux/amd64  .
> docker push lanzhiheng/nginx-for-linux

简单点我就不登录 Linux 的机器了,直接用 k8s 来跑了(我的 k8s 连接了远端的集群,M1 的 Macbook 有点跑不起 k8s 服务)。

> kubectl run nginx-from-macbook-image --image=lanzhiheng/nginx-for-linux
> kubectl get pods  nginx-from-macbook-image
NAME                       READY   STATUS    RESTARTS   AGE
nginx-from-macbook-image   1/1     Running   0          55s

这里只是以nginx的镜像作个例子,Rails 项目的解决方案也是类似,只需要加上参数--platform linux/amd64就能在 M1 的 Macbook 上构建出可以在 Linux 平台上使用的镜像了。这里就不赘述了,反正笔者亲测可用。

总结

今天这个问题算是项目容器化之路中耽误我最长时间的问题了,一个系统兼容性的问题,但这种问题对于新手来说真的很难排查。笔者调试这个问题花了大概 2 天的时间,有点绝望。后面尝试把问题简化(换别的官方镜像来做试验),一步步排除问题,起码确认了不是Dockerfile的问题,最终才定位到是镜像构建的系统不兼容所导致的。

其实笔者只要长个心眼,去 Linux 的机器上用该环境的 Docker 服务运行一下 Macbook 构建出来的镜像就能从提示信息中更快地定位出问题了,而不用一直去折腾 k8s 的配置,说到底还是经验不足啊。

我们升级 k8s 也遇到过,从 MacBook 下载的镜像升级 k8s 时候一直报错,最后也是看了好几天,才发现是镜像的问题。之后在 linux 下载的镜像才完成了 k8s 升级。

在生产环境中编译不行吗

我是在 github actions 里面 build 的。

amonlei 回复

当然可以啊。

Rei 回复

后期有这个打算了。不过现在感觉上容器还有点早。

M1 芯片是基于 arm 的,在 x86 机器上所以不行

M 系 Apple 本地构建其实不好弄的 build_opts="--platform linux/amd64" # building at M1 mac for linux 判断一下加这句

哈哈,写的两个问题,时区、platform 都遇到过了…… 😀

需要 登录 后方可回复, 如果你还没有账号请 注册新账号