The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

使用 Dockerfile 建置容器映象(image)時應多注意「換行符號」帶來的影響

今天使用 Visual Studio Code 的 Remote Development 將一個 .NET 6 與 SQL Server 開發環境全部放到 Docker 容器中。雖然照著文件操作都很順利,但在專案加入版控之後,問題就出現了。這篇文章我想點出現今容器化、跨平台的時代中,換行符號其實不得不面對他,否則遇到問題會無所適從。

體驗在容器中開發的感覺

  1. 先安裝好 Docker DesktopRemote Development 擴充套件

  2. 先建立基本 ASP.NET Core 6 專案並用 Visual Studio Code 開啟

    dotnet new web -n devcontainer-demo1
    code devcontainer-demo1
    
  3. 按下 F1 > Remote-Containers: Add Development Container Configuration Files

  4. 先選擇一個 Dev Container 範本: C# (.NET) and MS SQL

    image

  5. 選擇 .NET 版本: 6.0-focal

    image

  6. 選擇 Node.js 版本: none

    image

  7. 選擇要在容器中使用的工具: 這裡我們先不選,可直接按下 OK 完成設定

    image

    這樣就設定完成了! 👍

  8. 接著我們進行 Git 版控

    為了要進行版控,我先加入一個 .NET 專用的 .gitignore 檔案:

    dotnet new gitignore
    

    下圖是最終版本,基本上每個檔案你都可以點開來看:

    image

    建立版控

    git init
    git add .
    git commit -m "Initial commit"
    
  9. 執行 F1 > Remote-Containers: Reopen in Container 即可自動啟動

    image

    這個動作的背後,會全自動建置所有需要的容器映象(image),然後自動啟動,自動初始化容器環境 (安裝必要的開發工具),開啟必要的網路連線埠轉向(Port Forwarding),然後就可以很順利的讓你在 Visual Studio Code 連接到 Container 容器中進行開發、測試、執行。

體驗在版控之後發生的問題

由於專案中的 .devcontainer/Dockerfile 檔案會負責建置 Dev Container 開發容器中的所有環境,其中包含了 SQL 相關工具sqlcmd, bcp, sqlpackage 等等。

這個 .devcontainer/Dockerfile 內容如下:

#!/bin/bash
# [Choice] .NET version: 6.0-focal, 3.1-focal
ARG VARIANT="6.0-focal"
FROM mcr.microsoft.com/vscode/devcontainers/dotnet:0-${VARIANT}

# [Choice] Node.js version: none, lts/*, 18, 16, 14
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi

# Install SQL Tools: SQLPackage and sqlcmd
COPY mssql/installSQLtools.sh installSQLtools.sh
RUN bash ./installSQLtools.sh \
     && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts

# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
#     && apt-get -y install --no-install-recommends <your-package-list-here>

# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1

你可以發現到他有將 mssql/installSQLtools.sh 複製到容器中執行,重點是微軟所提供的 Dev Container 開發用容器是以 Ubuntu 20.04.4 LTS 為主要作業系統,而在執行這段命令時,會要求所有的 Shell Script 必須以 LF 為主要斷行符號,因此 Windows 常見的 CRLF 斷行符號是無法使用的,建置容器映像的過程會發生錯誤!

接著我們就來看一下這個發生錯誤的過程:

  1. 容器中並不會包含專案原始碼

    事實上,微軟的 Dev Container 會用 volumn mapping 的方式將原始碼掛載到容器中使用,因此你只有「專案執行環境」在容器中而已!

  2. 判斷 ~/.gitconfig 設定

    我們一開始在建立容器映像的時候,他預設會將 Windows 本機的 ~/.gitconfig 全域設定檔,自動複製到容器環境下的 ~/.gitconfig,因此基本上在 Ubuntu 20.04.4 LTS 底下的 Git 設定是跟 Windows 完全相同的。

    但是 Windows 底下有個 core.autocrlf=true 的預設值,這意味著當所有檔案被取出(Checkout)的時候,所有文字檔案都會自動以 CRLF 來當成換行字元,但這個問題就大了!

  3. 重新取出原始碼

    由於 .devcontainer/mssql/installSQLtools.sh 檔案剛開始是以 LF 來保存的,只要重新取出原始碼,他就會自動轉成 CRLF 換行字元,所以我們可以預期 .devcontainer/Dockerfile 在進行建置時會出現錯誤!

    你可以試著將 .devcontainer 整個移除,然後用 git reset --hard 重新取出檔案。

  4. 執行 F1 > Remote-Containers: Reopen Folder Locally 重新回到 Windows 開啟此專案

    image

  5. 執行 F1 > Rebuild Without Cache and Reopen in Container 重建 Dev Container 的容器映像

    image

    此時你就可以看到錯誤訊息了:

    image

    如果從 Terminal (終端機) 來看,你可以看到相對詳細的錯誤訊息:

    image

    發生問題的當下,第一時間並不是很好理解,但你現在應該很清楚是換行符號造成的錯誤!

正確的解決方案

這個問題最標準的解決方法如下:

  1. 執行 F1 > Remote-Containers: Reopen Folder Locally 重新回到 Windows 開啟此專案

  2. .devcontainer/mssql/ 目錄下建立一個 .gitattributes 檔案,並加入以下內容:

    *.sh    text eol=lf
    

    如此一來,就可以確保該目錄下所有的 *.sh 檔案,在 Git 取出檔案時一定會使用 LF 當成換行字元,

  3. 刪除 .devcontainer/mssql/*.sh 檔案,並使用 git checkoutgit reset --hard 重新取出檔案

    # 一定要先刪除檔案
    rm .devcontainer/mssql/*.sh
    
    # 刪除後執行取出檔案作業
    git checkout -- .devcontainer/mssql/*.sh
    
  4. 執行 F1 > Rebuild Without Cache and Reopen in Container 重建 Dev Container 的容器映像

    這次應該又可以順利建立容器了! 👍

總結

這個問題其實不太會在 macOS 或 Linux 發生,只有 Windows 用戶會遇到這個狀況,這不經讓我懷疑,是不是微軟的工程師沒有人在用 Windows 了啊? 😅

我特別為了這個問題也到 microsoft/vscode-remote-release 開了一個 Issue #7030 追蹤此議題,我認為這個檔案只要從微軟的範本中加入,就不會對 Windows 用戶帶來困擾了!

不過,我還是覺得只要你跟我一樣是愛用 Windows 的開發人員,都應該理解 CRLFLF 的差異,這樣日後在容器化的過程中,也會少踩一些地雷。

相關連結