--- title: "客製化自己的 Docker 映像檔流程" date: 2022-10-15 is_modified: false disqus: cynthiahackmd image: https://i.imgur.com/mlrB0F5.png categories: - "資訊科技 › 開發與輔助工具" tags: - "Docker" - "Linux/Unix" --- {%hackmd @CynthiaChuang/Github-Page-Theme %} <br> 這篇網誌中,我們會從無到有逐步建立一份客製映像檔,內容包括:Docker 安裝與配置、基礎映像檔下載、撰寫 Dockerfile,再透過 `docker build` 建立成映像檔,最後將映像檔上傳至倉庫。 這篇算是 [〈WSL|在 WSL2 中安装 Docker〉](https://hackmd.io/@CynthiaChuang/Install-Docker-in-WSL2)的前篇?原本是要寫給客戶的教學文件,不過我覺得拿出來當網誌好像也還行?所以就稍微擴寫、改述並刪除了些會被我主管請去喝茶東西,~~看來應該還能騙到一篇!?~~ <!--more--> <p class="illustration"> <img src="https://i.imgur.com/mlrB0F5.png" alt="Docker 三元素:Dockerfile、Docker 映像檔以及 Docker 容器"> Docker 三元素:Dockerfile、Docker 映像檔以及 Docker 容器(圖片來源: <a href="https://medium.com/platformer-blog/practical-guide-on-writing-a-dockerfile-for-your-application-89376f88b3b5">Platformer — A WSO2 Company|Medium</a>) </p> :::warning :warning: **環境確認** 本說明採用的環境是 **Ubuntu 20.04**,與 docker 相關之指令應可在多數環境中執行。但考慮到環境可能性眾多,可能遇到的情況族繁不及備載,因此執行本說明時有遇到問題請查閱 Docker 官方文件。 ::: ## Step1|Docker 環境確認 既然是客製化 Docker 映像檔,當然得先裝有 Docker 啦!可以先確定版號的指令,來確認開發環境是否安裝有 Docker: ```bash= $ docker version ``` 若果正確安裝的話,應該看到相關 Client 與 Server 的版號資訊,類似這樣: ```bash= Client: Version: 20.10.12 API version: 1.41 Go version: go1.16.2 Git commit: 20.10.12-0ubuntu2~20.04.1 Built: Wed Apr 6 02:14:38 2022 OS/Arch: linux/amd64 Context: default Experimental: true Server: Engine: Version: 20.10.12 API version: 1.41 (minimum version 1.12) Go version: go1.16.2 Git commit: 20.10.12-0ubuntu2~20.04.1 Built: Thu Feb 10 15:03:35 2022 OS/Arch: linux/amd64 Experimental: false ``` <br> 否則會得到 docker 指令不存在的錯誤訊息: ```bash= $ docker version bash: docker: command not found ``` 如果已經安裝的話,可以直接跳到 [1-2 章節](#Step1-2|確認服務是否啟動)來確認服務是否啟動;若是沒有安裝的話,就跟我一起來裝 Docker 吧! ### Step1-1|安裝 Docker 我的開發環境是 Ubuntu 20.04 ,裡面已經有安裝 Docker 了,但我發現 Docker 似乎有新版本,所以打算順便升級 Docker 來玩玩。 在這邊我先移除了舊版的的相關套件,再用**設置 Docker 的儲存庫(Repository)** 的方式來安裝。這種安裝法會方便於安裝與升級,這也是 Docker 官方推薦的安裝方式。 1. **移除舊版 Docker** ```bash= sudo apt-get remove docker docker-engine docker.io containerd runc ``` 2. **設置 Docker 的儲存庫** 相比以前一行安裝 docker.io,新的版本的多了許多的前置步驟: - 更新 apt-get 索引,並安裝用 HTTPS 傳輸的套件以及 CA 證書 ```bash= $ sudo apt-get update $ sudo apt-get install \ ca-certificates \ curl \ gnupg \ lsb-release ``` - 添加 Docker 的官方 GPG key ```bash= $ sudo mkdir -p /etc/apt/keyrings $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg $ sudo chmod a+r /etc/apt/keyrings/docker.gpg ``` - 然後,我們需要向 sources.list 中添加 Docker 儲存庫 ```bash= $ echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null ``` 3. **安裝 Docker Engine** 最後再次更新 apt-get 索引,並才能安裝最新版本的 Docker Engine、containerd 和 Docker Compose。 ```bash= $ sudo apt-get update $ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin ``` ### Step1-2|確認服務是否啟動 安裝完成後,查看一下 Docker 服務是否有正常啟動: ```bash= $ sudo service docker status ``` 或是 ```bash= $ sudo systemctl status docker ``` <br> 如果正常啟動的話,可以看到綠色的 active 狀態: <p class="illustration"> <img src="https://i.imgur.com/7TCVsZr.png" alt="正常啟動會顯示綠色的 active"> 正常啟動會顯示綠色的 active </p> 若是未啟動,則啟動 service: ```bash= $ sudo service docker start ``` 或是 ```bash= $ sudo systemctl start docker ``` <br> 最後,確認是否能夠下載測試映像檔並在容器中運行它: ```bash= $ sudo docker run hello-world ``` 若是成功執行會印出 `Hello from Docker` 的訊息並關閉容器: <p class="illustration"> <img src="https://i.imgur.com/4Xf0bop.png" alt="Hello from Docker"> Hello from Docker~ </p> ### Step1-3|以非 root 身份管理 Docker 在一般情況下,`docker` 指令需要 root 權限,其他使用者則必須使用 `sudo` 才能操作 `docker` 指令。因此如果不想每次都得在 `docker` 指令前加上 `sudo`,就得將使用者添加一個名為 docker 的群組中。 首先先建立一個名為 docker 的群組,並將使用者添加到其中。當 Docker 守護進程啟動時,它會建立一個可供 docker 群組成員訪問的 Unix domain socket。 ```bash= $ sudo groupadd docker $ sudo usermod -aG docker $USER ``` 更新使用者群組並重啟 docker: ```bash= $ newgrp docker $ service docker restart ``` 最後,試試看可不可以在沒有 `sudo` 下執行 docker: ```bash= $ docker run hello-world ``` ## Step2|下載基礎映像檔 若要客製自己的映像檔時,我們必須先選定一個**基礎映像檔(Base Image)**,再依此映像檔,加入所需開發套件和功能,層層堆疊逐步形成特定用途的客製映像檔。 該基礎映像檔的來源,可以是任何公開的映像檔倉庫(Public Image Registry),如 [Docker Hub](https://hub.docker.com/) 或是 [NVIDIA NGC](https://catalog.ngc.nvidia.com/?filters=&orderBy=scoreDESC&query=container),裡面存放了數量龐大的映像檔可供使用者下載;當然,也可以是自己或是組織建置的私有倉庫(Private)。 ### Step2-1|從公開倉庫取得映像檔 從公開倉庫取得映像檔是較常見的方式,通常無須額外權限與設定,就可直接使用下載指令。 以下載 [Jupyter Notebook](https://hub.docker.com/r/jupyter/datascience-notebook) 為例,我們從先在 Docker Hub 上確認所要下載的映像檔與標籤後,就直接使用指令將映像檔下載到本地端。 ```bash= $ docker pull jupyter/datascience-notebook:latest ``` 下載完成後會出現摘要與狀態提示下載成功: <p class="illustration"> <img src="https://i.imgur.com/1v8yKkF.png" alt="提示下載成功"> 提示下載成功 </p> ### Step2-2|從私人倉庫取得映像檔 若要從私有倉庫取得映像檔,則必須先**登入**該倉庫。 ```bash= $ docker login -u {Username} -p {Password} {Registry} Login Succeeded ``` `docker login` 可以登入到指定倉庫,如果未指定倉庫位址,則會預設登入官方的公開倉庫 Docker Hub。 <br> 完成登入後,後續的操作可能會與公開倉庫取的存取步驟有所差異,但整體流程差不多: 1. 確認所要下載的映像檔與標籤 2. 使用指令將映像檔下載到本地端 ## Step3|撰寫 Dockerfile 挑選完基礎映像檔後,就可根據自身需求撰寫 Dockerfile 加入所需套件和功能。 在撰寫 Dockerfile 時,是以 **FROM 指令**為開頭,往下根據每一行定義會製作出的一個唯讀資料層,當啟動執行成容器時,才會再疊上寫入層。因此建議撰寫時盡可能刪除每層不必要的資料,以降低映像檔大小。 我這邊就以剛剛從取得映像檔為基礎,向上安裝了 ==h5py==、 ==pandas== 與 ==gensim==...等 Python 套件,以提供一個簡單的 Dockerfile 範例,方便後續說明的進行。 想要詳細了解如何撰寫 Dockerfile 的話,還是建議去看看官方文件 - [〈Best practices for writing Dockerfiles〉](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/),絕對不是因為我不想寫文件了 XDDD ```dockerfile= From jupyter/datascience-notebook:latest RUN pip install --no-cache-dir --upgrade pip \ && pip install --no-cache-dir \ "h5py==3.7.0" \ "pandas==1.4.3" \ "gensim==4.2.0" \ && rm -rf ~/.cache/pip ``` 撰寫完成後,將其進行存檔並命名為 `Dockerfile`。 ## Step4|建立映像檔 完成 Dockerfile 的撰寫後,就可以用 `docker build` 的指令將映像檔建立起來。 ```bash= $ docker build -t demo-push:v1 . ``` 其中,`-t` 是為映像檔進行命名 **`demo-push`** 為倉庫與映像檔名稱、 **`v1`** 則是標籤,請依照習慣自行命名。指令執行時,它會找尋目前資料夾中名為 Dockerfile 的建置文件;但如果你的建置文件並非取名為 Dockerfile,這時需要在指令中加上 `-f` 指定建置文件: ```bash= $ docker build -t demo-push:v1 -f MyDockerfile . ``` 如果需要更多關於 docker build 的介紹,請閱讀 [Docker 官方文件](https://docs.docker.com/engine/reference/commandline/build/)。 <br> :::warning :warning: **注意!兩個命令的末尾都有一個點 `.`** 。 ::: <br> 當建立成功後可在終端機上看到下列訊息: ```bash= Removing intermediate container cc2418f30dff ---> 2c7464ff7613 Successfully built 2c7464ff7613 Successfully tagged demo-push:v1 ``` <br> :::info :information_source: **補充說明|docker build 命令後的點號(.)** 它並不是用於指定 Dockerfile 文件所在的位置的! 它並不是用於指定 Dockerfile 文件所在的位置的! 它並不是用於指定 Dockerfile 文件所在的位置的! 重點說三次!在這邊 <code>.</code> 確實是用來表示目前目錄,但卻不是用在指定 Dockerfile 所在路徑,因為那是 <code>-f</code> 的守備範圍! <br> 那麼指定這目錄的目的?這與 Docker 的運行架構有關: <p class="illustration"> <img src="https://i.imgur.com/i2do1xG.png" alt="Docker daemon 與 client 的交互運作"> Docker daemon 與 client 的交互運作(圖片來源: <a href="https://ithelp.ithome.com.tw/articles/10236066">iT 邦幫忙</a>) </p> 在 Docker 的運行架構中,存在 Docker Daemon(服務端守護進程)和 Client 兩個部分。Docker Daemon 會提供一組名為 Docker Remote API 的 REST API;而 Client 的代表就是我們常用的指令,指令則會透過 Docker Remote API 與 Docker Daemon 互動,從而完成各種功能。 簡而言之, Docker 的運行架構就是個標準的 C/S 架構。 <br> 那麼這邊又產生了一個問題,如果在建構映像檔時有涉及 <code>COPY</code>、 <code>ADD</code>...等操作文件的指令時,因為並非在本地建構,而是在服務端建構的,那麼在 C/S 架構中, Docker Daemon 該如何獲得本地檔案? 這邊就有一個 **映像檔建構上下文(Context)** 的概念。當建構的時候,使用者會指定建構映像檔上下文的路徑,而 docker build 指令再得知這路徑後,會將這路徑下的所有內容打包上傳給 Docker Daemon。而 Docker Daemon 在收到打包的內容後,會將它展開就能獲得建構映像檔所需的一切檔案。 那回到一開始的問題 **docker build 命令後點號(.)的意思**,指得就是將目前的目錄下的所有內容打包上傳給 Docker Daemon。 ::: ## Step5|上傳映像檔 當成功建構映像檔後,若要將它發布出去,可以將其上傳至 Docker Hub。 <p class="illustration"> <img src="https://i.imgur.com/vRaJFcV.png?1" alt="Docker Hub Icon"> Docker Hub Icon </p> ### Step5-1|註冊與登入 雖先前從 Docker Hub 下載映像檔到本地端時,並不需要登入即可下載;但若要上傳映像檔必須完成登入: ```bash= $ docker login -u {Username} -p {Password} Login Succeeded ``` 如果沒有帳號則必須先前往官網註冊。 ### Step5-2|創建儲存庫(Create Repository) <p class="illustration"> <img src="https://i.imgur.com/ZBsYe54.png" alt="Create Repository"> Create Repository </p> 如果沒有現有的儲存庫,可藉由 UI 的填寫建立一個新的儲存庫: <p class="illustration"> <img src="https://i.imgur.com/7cDFwlx.png" alt="填寫資料建立儲存庫"> 填寫資料建立儲存庫 </p> 在建立完成後可以得到一個 push 的指令,其中 tagname 需換成自定義的標籤,我們這邊使用 v1: ```bash= $ docker push cynthia/demo-push:v1 ``` ### Step5-3|本地端映像檔重命名 在上一步驟可以發現從倉庫取得的指令中,其: - **倉庫與映像檔名稱**: `{DockerHub UserName}/{ImageName}` - 倉庫路徑(RegistryURL): `cynthia` - 映像檔名稱(ImageName): `demo-push` - **標籤**:`v1` 若此時直接使用指令上傳,會得到映像檔不存在錯誤。 ```bash= $ docker push cynthia/demo-push:v1 The push refers to repository [cynthia/demo-push] An image does not exist locally with the tag: cynthia/demo-push ``` <br> 這是因為剛剛在建立映像檔時,所使用的倉庫與映像檔名稱為 `demo-push`,而非 `cynthia/demo-push`,因此需要先重命名: ```bash= # docker tag {local_name}:{local_tag} {remote_name}:{remote_tag} $ docker tag demo-push:v1 cynthia/demo-push:v1 ``` ### Step5-4|上傳至倉庫 重新命名後,再使用上傳指令進行上傳: ```bash= docker push cynthia/demo-push:v1 ``` <br> 上傳完成後,在終端機與平台上都可以看到相對應的提示: <p class="illustration"> <img src="https://i.imgur.com/bj21oeR.png" alt=""> </p> <p class="illustration"> <img src="https://i.imgur.com/mW7xBoS.png" alt=""> </p> ## 參考資料 1. [Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/)。檢自 Docker Documentation (2022-07-13)。 2. Xu Sheng (2018-03-09)。[docker build命令后 . 号的意思](https://www.xuxusheng.com/post/docker-build命令后-号的意思)。檢自 XuSheng's Blog (2022-07-13)。 3. Baohua Yang (2022-05-12)。[使用 Dockerfile 定制镜像](https://vuepress.mirror.docker-practice.com/image/build/#镜像构建上下文-context)。檢自 Docker 从入门到实践 (2022-07-13)。 ## 更新紀錄 :::spoiler 最後更新日期:2022-10-15 - 2022-10-15 發布 - 2022-08-09 完稿 - 2022-07-13 起稿 ::: <br><br> > **本文作者**: 辛西亞.Cynthia > **本文連結**: [辛西亞的技能樹](https://cynthiachuang.github.io/The-Workflow-for-Creating-Your-Own-Custom-Image/) / [hackmd 版本](https://hackmd.io/@CynthiaChuang/The-Workflow-for-Creating-Your-Own-Custom-Image) > **版權聲明**: 部落格中所有文章,均採用 [姓名標示-非商業性-相同方式分享 4.0 國際](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en) (CC BY-NC-SA 4.0) 許可協議。轉載請標明作者、連結與出處!
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.