楊育晟(Peter Yang)

嗨, 我叫育晟, 部落格文章主題包含了程式設計、財務金融及投資...等等,內容多是記錄一些學習的過程和心得,任何想法都歡迎留言一起討論。



Email: ycy.tai@gmail.com
LinkedIn: Peter Yang
Github: ycytai

Docker 基礎概念與實作(Basic Docker concept and hands on)

什麼是 Docker

Docker 是一個用於實現容器化 (containerization) 的工具,所謂的容器化就是讓服務的 OS 層或是應用層虛擬化,讓各個 services 間達到完全隔離,彼此不互相污染。

為什麼需要 Docker

Docker 能夠輕鬆讓 application 和 Infrastructure 隔離。在過去,如果同一份 code 要在不同環境下跑起來,需要多費許多力氣去 setup,然而 Docker 則大幅的減低了這樣的 effort,讓同份 code 可以迅速在不同電腦上跑起來,Docker 能夠幫我們在不同機器上迅速重現環境。

初步認識 Docker 的運作

Docker 所使用的架構為 client-server architecture,在 client 端可以下 Docker 內建的指令去對 Docker 的各個 container 進行操作,例如等等會看到的 docker run, docker build 等等,而 server 端裡的 docker daemon 則會 listen client 端送出的指令進行動作,一般從官網上下載安裝的 Docker Desktop 裡,就已同時包含兩者在內了。

舉例 Container 的建立

簡單舉例建立 container 的運作流程,Client 端送出指令建立 container,Server 端接收後,檢查有沒有現有的 image,如果有,就使用現有的 image 建立 container,如果沒有,就往 Registry 去找需要的 image (預設為 Docker Hub,就像 Docker 的 Github),找到 image 後 pull 下來,然後完成建立 container 的動作。

使用 Docker 該認識的名詞

看完初步的介紹,不難發現 Docker 裡有些專有名詞必須認識,下方整理常見的 term 跟解釋,方便邊實作邊理解。

名稱 常見中文 解釋
container 容器 服務所運行的地方,擁有獨立的 file system,像個容器一樣和外界隔絕,透過 docker 跑起來的各個服務,都會有自己的 container,可以想像成一台VM。
image 映像檔 使用 Docker 將服務 build 完後所產生的檔案,可以想像成光碟一樣,大家可以拿著 image 去有安裝 docker 的電腦上把 container 跑起來,並得到同樣的結果。
volume 掛載 Docker 中保存資料的機制,也是官方建議使用 Docker 時的資料儲存方式。
Dockerfile - 建立 image 時會依循 Dockerfile 中的步驟和指令完成。
build - Docker 依照 Dockerfile 建立 image 的過程。

實際使用 Docker 運行服務

安裝 Docker

在本地端要使用 Docker,就是安裝 Docker Desktop,直接去官網下載,用 Mac 的要注意有分 M1 跟 Intel 版本的。

確認安裝成功

確認能夠打開 Docker,看到 GUI 後。

打開 terminal,執行 docker run hello-world,確認是否安裝成功。

ycy@YCY-Mac ~ % docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (arm64v8)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

將第一個 Docker container run 起來

假設目前我有個 fastapi 的專案結構如下

.
├── main.py
├── poetry.lock
└── pyproject.toml

api 的主要內容如下

# ./main.py

from typing import Union

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/product/{product_id}")
def get_product(product_id: str):
    return {"result": f"This is product {product_id}"}

此時在專案中加入 Dockerfile

FROM python:3.10-slim

ENV POETRY_VERSION=1.4.1

RUN pip install "poetry==$POETRY_VERSION"

WORKDIR /code
COPY poetry.lock pyproject.toml /code/

RUN poetry config virtualenvs.create false && poetry install

COPY . /code

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

此時的專案架構如下,同一層中多了一個 Dockerfile。

.
├── Dockerfile # <-- this is new
├── main.py
├── poetry.lock
└── pyproject.toml

接著用 docker build 來產生專案的 image, -t 是幫 image 加上 tag,後方的 . 則是 Dockerfile 所在的路徑。

docker build -t demo/api .

執行結果如下, 會依照 Dockerfile 中的步驟依序執行。

(docker-demo-py3.10) ycy@YCY-Mac docker-demo % docker build -t demo/api .
[+] Building 27.5s (11/11) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 37B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => [internal] load metadata for docker.io/library/python:3.10-slim
 => [internal] load build context
 => => transferring context: 196.01kB
 => [1/6] FROM docker.io/library/python:3.10-slim@sha256:dc88dfceb13b58dc6ff12840c28f6c774a8692945db4e9c2dabeaf939b7f2614
 => [2/6] RUN pip install "poetry==1.4.1"
 => [3/6] WORKDIR /code
 => [4/6] COPY poetry.lock pyproject.toml /code/
 => [5/6] RUN poetry config virtualenvs.create false && poetry install
 => [6/6] COPY . /code
 => exporting to image
 => => exporting layers
 => => writing image sha256:0af281ac987329955a6b87c97bfbe0bd8620921f453d8b9744337a1d4ddf960b
 => => naming to docker.io/demo/api

這時去 Docker Desktop 裡的 image 查看,就能看到剛 build 好的 image 在那了。

此時的 container 還沒跑起來,我們可以用剛 build 好的 image 來跑 container。

下指令 docker run ,另外幫 container 加上名稱,然後再把 container 裡的 port forward 到 local 的 port,最後指定剛 build 好的 image。

順帶一提,8000:8000 分別代表 local port:container port,如果沒有 forward 出來,沒辦法直接在自己的電腦上來碰到 container 裡的服務。

docker run --name my-api -p 8000:8000 demo/api

執行後可以先回到 Docker Desktop 上,可以看到正在運行中的 container。

在 terminal 中看執行結果,也能看到 fastapi 運行中的 log。

(docker-demo-py3.10) ycy@YCY-Mac docker-demo % docker run --name my-api -p 8000:8000 demo/api
INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

然後到 browser 中,瀏覽 http://127.0.0.1:8000/ ,就能夠看到跑起來的服務。

查看 swagger (http://127.0.0.1:8000/docs) 也正常。

再回到 terminal 看 log,就多出了剛剛的瀏覽紀錄。

(docker-demo-py3.10) ycy@YCY-Mac docker-demo % docker run --name my-api -p 8000:8000 demo/api

...

INFO:     172.17.0.1:59242 - "GET /docs HTTP/1.1" 200 OK
INFO:     172.17.0.1:59242 - "GET /openapi.json HTTP/1.1" 200 OK

恭喜成功的使用 docker 跑起來第一個 container 了!

Fun to know

Docker 是用 go 寫的。

Docker is written in the Go programming language and takes advantage of several features of the Linux kernel to deliver its functionality.

參考連結

Docker docs - The underlying technology

Containerization (computing)

Docker port forwarding not working

Tags:
# docker
# container