之前在工作的時候很常接觸到 Docker 或者是聽到過,覺得應該是時候來認真了解一下,希望能夠學會幾項個人認為重要的東西,其中當然也包括實作的內容。
什麼是 Docker ?
主要提供一個快速且輕量級的虛擬機讓使用者建立、測試和部屬應用程式[什麼是 Docker?| AWS]。
Docker 的運作方式
很多文章都會提到 Docker 和虛擬機之間的差別,虛擬機的話主要是打包你整個系統環境,方便你在不同的硬體設備上使用;而 Docker 則不需要作業系統就可以在容器中執行應用程式。
Docker Image 是一個唯讀模板,建立 Image 需要依靠 Dockerfile,記錄著這個映像檔主要提供的環境或是應用服務等,而 Image 最主要的功能也是用來建立 Docker Container。
容器(Container)
Docker 也就是倚靠 Container 來執行應用程式,每一個容器都是互相獨立不會受到影響的。
倉庫(Repository)
倉庫的概念就與 Github 有點類似,是用來集中存放 Image 檔案的地方,最大的倉庫有 Docker Hub 可以在這上面找到,別人建立好的 Image,也只需要將它 pull 下來就可以去啟 Container。
安裝 Docker
通常會直接到官方網站上直接下載 Desktop 版本,如果是習慣透過指令來安裝的話,網站內也有提供相關的指令。
在開始之前先上一段來自 Docker docs 上面介紹 Dockerfile reference,應該可以對整個 Dockerfile 有一點概念:
Docker can build images automatically by reading the instructions from a Dockerfile A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image.
上一篇使用 Seleium 做動態網路爬蟲 - 在Yahoo!股市爬自動取多個股價,嘗試將這個應用透過 Docker 給打包起來。
現在則需要建立一個能夠跑這項應用的 Docker Container,要有這個容器之前,可以透過 Dockerfile 來設計我們的 Image。
Docker Container 的功能要有以下:
- 可以執行 Python 的環境。
- 可以啟動 Selenium 的 WebDriver。
- 在 Docker Container Console 裡面顯示我們的結果。
$ mkdir docker
$ cd docker
*註:檔案直接以 Dockerfile 來命名,不需要加上副檔名。
一般來說,根據你的應用服務決定你的 Docker Image 的設計,不太會希望說要從灌系統開始,畢竟會讓你的容器變得很大一個,以及啟動也會花上不少的時間,所以盡可能的會以服務使用的內容來決定。
FROM 可以理解成啟動 Contaniner 的系統,由於 Selenium 爬蟲的服務是由 Python 環境來啟動的,所以可以到 Docker Hub 中去找到 Python Docker Image,在說明欄當中,有一個部分是叫做「Simple Tags」可以指定 Python 的版本。
*註:在版本號後面如果是選取 alpine 的話,指的是架設 Python 所需要的容量會小很多。
FROM python:3.12-rc-alpine
MAINTAINER 用來撰寫說明的欄位,通常會記錄這個 Dockerfile 主要維護的人是誰,也可以給 E-mail的資訊
MAINTAINER Jackson Xu
這時候環境算是大概建立好了,接下來就是把想要跑的應用或服務程式碼給複製到 Contaniner 當中,不過你也可以啟好 Container 之後去 clone github 上面的專案下來跑。
先嘗試啟動一個簡單的 Python 專案
來測試一下 Container 能不能跑起來:
# app.py
# flask run
from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return "<h1>Hello, Docker!</h1>"
如果想啟動這個 Python Flask API Server 的時候,會需要再 CMD 上輸入 flask run 或 python3 -m flask run 來啟動它。
現在則是要將這個 app.py 檔案給複製一份到 Image 裡面,同時也需要一份 Python 套件的安裝檔案 requirements.txt。
# requirements.txt
flask==2.2.2
*註:一開始是根據 Local 端來查詢 flask 的版本號,不過有點過於老舊是 1.1.2 版本的,現在已經更新到 2.2.2,如果你遇到這樣的問題 ImportError: cannot import name 'escape' from 'jinja2',可能要檢查一下當前的版本喔。
在 CMD 則會這樣輸入 pip install requirements.txt 來達到安裝指定套件和版本的功能。
複製本地端的檔案到 Image
這邊會有兩個指令可以使用分別是 ADD 和 COPY,兩者功能皆是一樣的,都是將檔案給複製進去 Docker Image 裡面,不過有一個差別在於 COPY 只能夠複製本地端的檔案或整個目錄,ADD 則可以透過遠端 url 的檔案到 Docker Image 裡。
爬過一些文章,有些人則建議在實際操作上面不會使用 ADD 方法來抓取網路上的檔案,則會使用 RUN curl or wget 的方式來抓。
在複製檔案之前,我們可以先為這項應用服務建立一個資料夾,而建立資料夾的方法是 WORKDIR 加上你的資料夾名稱:
WORKDIR /app
接下來就是將要複製的檔案存到這個 /app 目錄(資料夾)底下,複製 app.py 檔案就會使用 COPY 的方式進行如下:
COPY app.py /
COPY requirements.txt /
如此一來,就會將同樣目錄底下的檔案一併複製到指定的目錄(/app)底下。
那實際上 /app 到底是在哪裡,可以透過 Docker desktop 內的 container terminal 去查看。
在來就是替我們的 Python 執行環境安裝一下會需要的套件,指令如下:
RUN pip3 install -r requirements.txt
環境以及檔案都設置完成後,就需要來啟動我們的 Flask API Server 了,操作的指令需要藉由 CMD 來完成。
CMD python3 -m flask run
連線到你的 Docker Container 應用
如果要外部可以連線到 Docker Container 啟動的服務時,需要將對應的 PORT 給映射出來,這時候會使用到的指令是 EXPOSE。
由於會啟動的 Flask app 會打開的是 5000,為了方便使用,對外的 PORT 也設定成一樣的,有關 docker port 概念和使用可以參考 使用 Docker - Port 及 Volume。
EXPOSE 5000
如何使用 Dockerfile 建立 Docker Image?
指令非常的簡單,如果你有其他需求,也可以參考 Docker build 的官方文件。
# docker build -t <tag> . --no-cache
$ docker build -t python-docker . --no-cache
- -t 替你的 Image 加上一個名字或標籤,通常以 name:tag 或是 name 的格式來表示。
- -no-cache 是加速 build image 的方法,可以參考最佳化 Dockerfile - 活用 cache。
# docker create -i -t --name <container-name> <image-name>
$ docker create -i -t --name flask-app python-docker
這邊我也嘗試了很久,有關於 docker run 和 docker create 的差別,當我們想要從 Local 端去連線這個 Docker container 啟動的 API 時,會需要把 Port 對外連線的機制開啟,爬了一下文章找到 Docker run vs create 有討論 docker run = docker create + docker start,之後如果 Docker Image 已經 Build 起來,就可以直接透過 Docker run 來建立 Container 和啟動它。
關於更多 Docker run 可以參考 快速建立你的第一個Docker服務。
# docker run --name <container-name> -p <PORT> -d <image-name>
$ docker run --name flask-app -p 5000:5000 -d python-docker