記一個 Docker 映象無法執行的坑

aneasystone本尊發表於2019-08-03

最近在工作中遇到了一個 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/bashdocker 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 builddocker 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
複製程式碼

坑之總結

  • 坑一:映象如果配置了 entrypointdocker run 的時候應該加上 --entrypoint 引數來檢查映象
  • 坑二:指令碼執行時報 not found,最可能的原因是 Sha-Bang 寫的有問題,檢查指令碼檔案中是否存在隱藏字元,譬如 Windows 的換行符,這裡不得不吐槽下,這個 not found 提示真的讓人迷惑,提示裡能不能把隱藏字元也帶上?
  • 坑三:建議關閉 Git 的 autocrlf 配置

參考

  1. How to see docker image contents
  2. Standard_init_linux.go:175 exec user process caused no such file
  3. GitHub 第一坑:換行符自動轉換
  4. 自定義 Git - 配置 Git

相關文章