Docker 與容器化

AdSense

容器化 (Containerization) 與容器 (Container)

容器化是一種軟體部署流程,它將應用程式的程式碼與其運作所需的所有檔案和函式庫打包在一起,讓應用程式可以在任何基礎設施上運行。傳統上,要在電腦上執行任何應用程式,你必須安裝與機器作業系統相符的版本。例如,你需要在 Windows 機器上安裝 Windows 版本的軟體套件。然而,有了容器化技術,你只需建立一個單一的軟體套件,也就是容器,就可以在所有類型的裝置和作業系統上執行

容器依賴於主機作業系統的核心 (kernel) 來運作。這表示在主機上運行的容器會共享主機的核心,同時保持獨立的使用者空間 (isolated user spaces)。這使得應用程式可以獨立於主機作業系統運行,而且你可以在容器中安裝一個作業系統,例如 Ubuntu 20。

容器引擎 (Container Engine)

容器引擎,或稱作容器執行環境 (container runtime),這個軟體的目的是讓開發者基於容器映像檔 (container images) 來建立容器。它扮演著容器和作業系統之間的媒介,提供並管理應用程式所需的資源。例如,容器引擎可以在同一個作業系統上管理多個容器,同時確保它們彼此獨立且獨立於底層基礎設施。

Docker

Docker,或稱作Docker 引擎 (Docker Engine),也是容器引擎,是非常流行的開源容器執行環境,它讓軟體開發人員能夠在各種平台上建置、部署和測試容器化應用程式。Docker 容器是應用程式和相關檔案的獨立封裝 (self-contained packages),是使用 Docker 框架建立的。

Docker 的核心概念圍繞著三個基礎建構區塊:映像檔 (Images)容器 (Containers)資料卷 (Volumes)。它們到底是什麼呢?


映像檔 (Image) – 藍圖

映像檔就像是你應用程式的範本藍圖。它是唯讀不可變動 (immutable) 的。

它包含了:

  • 你的程式碼
  • 依賴項 (例如 Node.js、Python)
  • 系統工具和函式庫
  • 作業系統層級的內容 (例如 Ubuntu base)

範例 1

透過執行 docker cli 來下載包含 Node.js v18 的 Docker 映像檔。

1
docker pull node:18
  • docker pulldocker image pull 的別名:從映像檔註冊伺服器 (registry) 下載映像檔。
  • node:18:映像檔名稱。

範例 2

製作映像檔。

為了解決開發環境不一致的問題,避免在不同的機器上重複進行冗長的環境設定和套件安裝。透過將應用程式、所有依賴項、函式庫和設定檔封裝成一個完整的映像檔 (由多層 layer 組成),確保程式碼在任何地方 (無論是本地端、測試環境還是像 AWS EC2 這樣的雲端平台) 都能以相同的方式運行,實現一次建立,到處運行 (Build Once, Run Anywhere) 的目標。

Dockerfile 是用來自動化建立 Docker 映像檔的文字檔。它包含了一系列指令,每條指令都會在映像檔中建立一個新的層 (layer)。

寫 Dockerfile 時,有兩個空間要分清楚:

  1. 建置主機 (Host):就是你寫 Dockerfile 的電腦,通常是你的本機或 CI/CD 的 runner。
  2. 映像檔 / 容器內部:Docker 建置後產生的新環境,它有自己的檔案系統 (隔離的 Linux 檔案系統)。

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM node:18

WORKDIR /user/src/app

COPY package*.json ./ # 把本機當前目錄下的 package*.json 檔案,複製到映像檔的當前目錄

RUN npm install

COPY . . # 把本機當前目錄下的所有檔案,複製到映像檔的當前目錄

EXPOSE 3000

CMD ["npm", "start"]
  • FROM baseImage:tag:指定你的應用程式所基於的基礎映像檔,例如 node:18
  • WORKDIR /the/workdir/path:設定映像檔的路徑所有後續指令都將在這個目錄下執行。以 / 開頭,是映像檔根目錄下的絕對路徑
  • COPY source dest:將本地端的檔案或目錄複製到映像檔中。通常會分兩步:
    1. COPY package*.json ./:先複製 package.jsonpackage-lock.json 等設定檔以利用 Docker 的快取。./映像檔的當前目錄,也就是 /user/src/app/
    2. COPY . .:再複製其餘程式碼。第一個 . 是本機當前目錄下的所有檔案,第二個 . 是映像檔的當前目錄,也就是 /user/src/app/
  • RUN command:在映像檔建立過程中執行命令,例如 npm install 用來安裝 package.json 中的套件。安裝的東西會放進映像檔的 /user/src/app/node_modules
  • EXPOSE port:宣告容器將會暴露 (使用) 的埠號。但僅為文件化,不會自動開放,需要搭配 docker run -p 3005:3000 <image_name> 映射到主機。
  • CMD ["executable"]定義容器啟動時要執行的預設命令。相當於在 /user/src/app 裡執行 npm start

此外,.dockerignore 檔案用於指定在建立映像檔時要忽略的檔案和目錄,例如 node_modules。這樣可以大大減少最終映像檔的大小,並加快建置速度。

容器是依照映像檔建立的,所以容器的目錄結構會跟映像檔的目錄結構一樣。

最後,在本機當前目錄下使用 docker build -t <image_name> . 指令來建置 Docker 映像檔,其中 -t 用於為映像檔命名,. 表示使用當前目錄下的 Dockerfile。


容器 (Container) – 執行個體 (Running Instance)

容器是一個映像檔的執行複本 - 就像是基於藍圖而運行的應用程式

  • 與主機和其他容器隔離
  • 預設為暫時性的 (停止後會銷毀,除非有額外設定)
  • 可以執行、停止、重新啟動和刪除它

範例 1

透過執行 docker cli 來使用 node:18 映像檔執行一個容器。

1
docker run node:18
  • docker rundocker container run 的別名:從映像檔建立並執行一個新的容器。
  • node:18:映像檔名稱。

範例 2

接續映像檔 (Image) – 藍圖的範例 2,使用製作好的映像檔來執行一個容器。

1
docker run -p 3005:3000 <image_name>
  • -p:將主機上的 3005 連接埠,映射到容器內的 3000 連接埠。
  • node:18:映像檔名稱。

資料卷 (Volume) – 持久性儲存 (Persistent Storage)

資料卷是一個存在於容器外部的儲存空間

  • 容器是短暫 (ephemeral) 的:如果它們被刪除,裡面的資料就會消失。
  • 資料卷可以讓你的資料安全可重複使用
  • 非常適合用於資料庫、上傳的檔案、設定檔等。
  • 你需要透過容器來存取資料卷。

範例 1

將已存在的資料卷掛載到容器的目錄上,這樣你的容器就可以讀取或寫入持久性資料。

從名為 ubuntu 的映像檔建立並執行一個新的容器。將名為 mydata 的資料卷掛載到容器內部的 /app/data 目錄。

1
docker run -v mydata:/app/data ubuntu
  • docker run:從映像檔建立並執行一個新的容器。
  • -v:掛載一個資料卷。
  • mydata:資料卷名稱。
  • /app/data:容器內部的一個虛擬目錄路徑,就像一個掛載點 (mount point)
  • ubuntu:映像檔名稱。使用 Ubuntu 映像檔作為容器的基礎作業系統。

範例 2

1. 將資料庫檔案複製到資料卷

利用 Bind Mount (綁定掛載) 來達成。

Bind Mount 是一種 Docker 的資料持久化機制,它允許你將主機作業系統上的檔案或目錄直接掛載到容器內的指定路徑

簡單來說,它在主機和容器之間建立了一個直接的「連結」

  • 主機的某個路徑 (例如:/home/user/my-data)
  • 容器的某個路徑 (例如:/app/data)

透過 Bind Mount,這兩個路徑會指向同一個底層儲存位置

在一個臨時容器中啟動一個互動式的 Ubuntu shell,我可以在其中透過容器的虛擬路徑 /my-path 存取當前主機目錄 . 中的資料庫檔案。接著,透過容器的虛擬路徑 /app/data 將檔案複製到一個名為 mydata 的資料卷。容器會在結束後自動刪除。mydata 這個資料卷在 Docker 的管理下,可以被其他容器重複使用。

Copy the database files to a volumn

1
2
3
docker run -it --rm -v .:/my-path -v mydata:/app/data ubuntu /bin/bash

cp my-path/files.tar.zst app/data/
  • -it:結合兩個旗標,讓你可以在終端機互動式地操作容器。
        * -i:保持標準輸入 (STDIN) 開啟 (互動模式)。
        * -t:分配一個虛擬終端 (pseudo-terminal) (就像一個 shell)。
  • --rm:當容器結束時自動移除。避免產生過多臨時容器。
  • -v .:/my-path:將你當前的主機目錄 . 掛載到容器內部的 /my-path 目錄。
  • -v mydata:/app/data:將一個新建立的、名為 mydata 的資料卷掛載到容器內部的 /app/data 目錄。
  • ubuntu:映像檔名稱。使用 Ubuntu 映像檔作為容器的基礎作業系統。
  • /bin/bash:啟動一個互動式的 Bash shell,讓你可以探索容器、移動檔案、執行命令等。
  • cp source_file target_file:從來源複製檔案到目標。

2. 執行帶有持久性資料的資料庫容器

執行一個 Oracle 18c XE (Express Edition) 資料庫,並讓它在 Docker 容器內帶有資料持久性 (data persistence)網路存取 (network access)自動重新啟動 (auto-restart) 的功能。

1
2
3
4
5
6
7
docker run -d \
--name my-oracle-database \
-p 1521:1521 \
-e ORACLE_PASSWORD=<your password> \
-v mydata:/opt/oracle \
--restart unless-stopped \
gvenzl/oracle-xe:18
  • -d:以分離模式 (detached mode) (在背景) 執行容器。
  • --name:給容器一個自定義的名稱。
  • -p 1521:1521:將你主機上的 1521 連接埠映射到容器內的 1521 連接埠。容器會監聽 1521 連接埠,這樣你就可以從 Docker 外部連線。1521 是 Oracle 資料庫的預設連接埠。
  • -e:設定環境變數。
  • -e ORACLE_PASSWORD=<your password>:設定 Oracle SYSTEM 使用者的初始密碼。
  • -v mydata:/opt/oracle:將名為 mydata 的 Docker 資料卷掛載到 Oracle 資料庫容器內部的 /opt/oracle
  • --restart unless-stopped:如果容器崩潰、系統或 Docker 守護程序 (daemon) 重新啟動,它會重新啟動容器。如果你手動停止了容器,Docker 會記住這一點,即使在 Docker 守護程序重新啟動後,它也不會再次重新啟動,除非你明確地執行 docker start
  • gvenzl/oracle-xe:18:映像檔名稱。

參考資料:
docker image pull
docker container run
Docker hub - gvenzl/oracle-xe
20 分鐘入門 Docker,建立屬於你自己的 Docker Image|六角學院|2023 鐵人賽 #23