最近在工作中遇到了一個 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
配置