记一个 Docker 镜像无法运行的坑
最近在工作中遇到了一个 Docker 镜像无法运行的问题,事后总结时发现其中有几个点挺有意思,值得记录下来以备后用,也可以避免其他人踩坑。
一、运行镜像报错
我的开发环境是 Windows,使用 Docker Desktop 作为本地 Docker 环境。最近在一个项目中需要把 war 包打成 Docker 镜像并推送到仓库里去,却发现打好的这个镜像运行不起来,运行时报下面这样的错:
# docker run --rm -it manager
standard_init_linux.go:207: exec user process caused "no such file or directory"
检查 Dockerfile 文件,是非常简单的:
FROM openjdk:8-jre-alpine
ADD entrypoint.sh entrypoint.sh
ADD *.war app.war
EXPOSE 8088
RUN chmod 755 entrypoint.sh
ENTRYPOINT ["./entrypoint.sh"]
镜像是基于 openjdk 的基础镜像,将 entrypoint.sh
和 *.war
拷贝到镜像中,并将 entrypoint.sh
作为默认的启动脚本文件。初看应该是没问题的,war 包在本地使用 java -jar
也可以跑起来。
二、检查镜像
那么这个镜像是怎么回事?看报错信息,应该是运行镜像时,找不到某个文件或目录,但是我的这个镜像一共就两个文件,一个 entrypoint.sh
一个 war 包,难道这两个文件没有 ADD 到镜像里?
于是试着运行命令 docker run --rm -it manager sh
,想着进 shell 查看下文件,发现依然报错,难道镜像里没有 sh 脚本?试着连换了几个命令:docker run --rm -it manager /bin/bash
和 docker run --rm -it manager ls
发现都是这个错,这怎么可能呢,连 ls 都没有?
无奈 Google 之,才发现原来如果镜像配置了 entrypoint
,要使用下面这样的方式来检查镜像:
# docker run --rm -it --entrypoint sh manager
进到容器里来了之后,使用 ls 可以看到文件都在,没毛病:
/ # ls
app.war entrypoint.sh lib opt run sys var
bin etc media proc sbin tmp
dev home mnt root srv usr
使用 java -jar app.war
可以正常运行,但是奇怪的是,./entrypoint.sh
脚本却运行不了:
/ # ./entrypoint.sh
sh: ./entrypoint.sh: not found
这个脚本文件明明就在这,却报错 not found,这让我一度怀疑人生,遇到灵异事件了。
三、换行惹的祸
不得已只能继续 Google 之,发现网络上跟我一样的人还有很多,脚本无法运行最可能的原因是 Sha-Bang
写的有问题,所谓 Sha-Bang 就是 #!
,通常会写在 shell 文件第一行,用于指定命令行解释器。类似于下面这样:
/ # cat entrypoint.sh
#!/bin/sh
blabla
使用 ls /bin/sh
发现 /bin/sh
文件也在,应该没问题啊。苦思冥想之际,突然脑海中闪过一个想法,难道这里有隐藏字符?一般遇到这种灵异事件的时候,都很可能和隐藏字符有关。使用 cat -v
查看文件,果然发现这里的 /bin/sh
后面多了个 ^M
:
/ # cat -v entrypoint.sh
#!/bin/sh^M
blabla
顿时豁然开朗,这不就是 Windows 下的换行符吗?一切都是换行符惹的祸。
使用 dos2unix
将 entrypoint.sh 文件中的 Windows 格式的换行符转为 UNIX 格式,再一次使用 docker build
,这次终于运行成功了。
四、最坑的 Git 配置
到这里本来已经结束了,不过后来又发生了一件小事,让我又发现另一个坑,才找到了这个问题最根本的原因。当我解决了这个问题之后,就去给同事分享,可是听了我的分享之后,同事却一脸懵逼的表示自己从来都没遇到过这个问题,并且在他电脑上给我演示了一遍 docker build
和 docker run
,一切正常,并没有报错,同样的一份代码,为什么在我的电脑上 build 就有问题?看着同事对我露出的迷之微笑,我又一次陷入了困惑。
我让他把 entrypoint.sh
发给我,看了下,他的换行符格式竟然是 UNIX 的,可是我本地代码换行符明明是 Windows 的,使用 git status
也可以看到 nothing to commit, working tree clean
,表明本地代码和仓库代码是一样的,为什么换行符却不一样呢?
查看 Git 的配置文件,和他的对比了一下,发现一个很可能的疑点:
[core]
autocrlf = true
查询官网文档,终于找到了原因,原来 Git 在 pull 代码的时候可能会偷偷的对你的代码做手脚。如果你配置了 autocrlf = true
,那么当你签出代码时,Git 会自动的把 LF 转换成 CRLF,然后当你 push 的时候,又自动的将 CRLF 转换成 LF。这看上去很贴心的功能,实际上却有着很大的漏洞,譬如像我这样,直接在本地打镜像,或者需要直接将 shell 文件上传到 Linux 服务器上运行的,都可能会出问题。关键这个配置在 Windows 下默认是打开的,所以建议把这个配置关掉:
$ git config --global core.autocrlf false
坑之总结
- 坑一:镜像如果配置了
entrypoint
,docker run
的时候应该加上--entrypoint
参数来检查镜像 - 坑二:脚本运行时报 not found,最可能的原因是
Sha-Bang
写的有问题,检查脚本文件中是否存在隐藏字符,譬如 Windows 的换行符,这里不得不吐槽下,这个 not found 提示真的让人迷惑,提示里能不能把隐藏字符也带上? - 坑三:建议关闭 Git 的
autocrlf
配置
参考
- How to see docker image contents
- Standard_init_linux.go:175 exec user process caused no such file
- GitHub 第一坑:换行符自动转换
- 自定义 Git - 配置 Git
赞,虽然一开始我就 autocrlf置为了 false,但是一直不知道 git 是这么个处理逻辑