这个问题是笔者尝试把项目部署到k8s集群上的时候遇到的,简单记录一下。估计使用M1芯片的Macbook构建镜像的开发者多少都会遇到类似的问题。

能在本地运行却无法在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的配置,说到底还是经验不足啊。