这个问题是笔者尝试把项目部署到 k8s 集群上的时候遇到的,简单记录一下。估计使用 M1 芯片的 Macbook 构建镜像的开发者多少都会遇到类似的问题。原文链接: https://step-by-step.tech/posts/m1-build-image-issue
当要把某个项目部署到 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 的配置,说到底还是经验不足啊。