Flask 教程 第十九章:Docker容器上的部署

本文轉載自:https://www.jianshu.com/p/c29bc412f21a

這是Flask Mega-Tutorial系列的第十九部分,我將在其中部署Microblog到Docker容器平臺。

第十七章中,你瞭解了傳統部署,使用這種部署方式,你必須關注服務器配置的每個細節。 然後在第十八章我帶你到另一個極端——Heroku ,這是一項完全掌控配置和部署任務的服務,使你能夠全神貫注於應用程序。 在本章中,你將學習基於容器(尤其是在Docker容器平臺)的第三種應用程序部署策略。 這種部署的工作量,介於另外兩個選項之間。

容器建立在輕量級虛擬化技術的基礎上,允許應用程序及其依賴和配置完全隔離宿主機地運行,而不需要使用虛擬機等完整的虛擬化解決方案。使用虛擬機需要更多的資源,並且有時可能與宿主機相比,性能顯著下降。 配置爲容器宿主機的系統可以運行大量容器,所有這些容器共享主機的內核並直接訪問主機的硬件。 這與虛擬機不同,虛擬機必須模擬完整的系統,包括CPU,磁盤,其他硬件,內核等。

儘管必須共享內核,但容器中的隔離級別非常高。 容器具有自己的文件系統,並且可以基於容器宿主機使用不同的操作系統。 例如,你可以在Fedora宿主機上運行基於Ubuntu Linux的容器,反之亦然。 儘管容器是Linux操作系統上誕生的技術,但由於虛擬化的原因,也可以在Windows和Mac OS X宿主機上運行Linux容器。 這允許你在開發系統上測試部署操作,並且如果你願意的話,還可以將容器合併到開發工作流程中去。

本章的GitHub鏈接爲:BrowseZipDiff.

安裝Docker社區版

儘管Docker不是唯一的容器平臺,但它是迄今爲止最受歡迎的,所以我選擇了它。 有兩個版本的Docker,免費的社區版(CE)和付費的企業版(EE)。 對於本教程來說,Docker CE就夠了。

要使用Docker CE,首先必須將其安裝在系統上。 在Docker網站上有適用於Windows,Mac OS X和多個Linux發行版的安裝程序。 如果你正在使用Microsoft Windows系統,請務必注意Docker CE依賴Hyper-V。 如有必要,安裝程序將爲你啓用此功能,但請記住,啓用Hyper-V會限制諸如VirtualBox等其他虛擬化技術產品的運行。

一旦Docker CE安裝在你的系統上,你可以通過在終端窗口或命令提示符處輸入以下命令來驗證安裝是否成功:

$ docker version
Client:
 Version:      17.09.0-ce
 API version:  1.32
 Go version:   go1.8.3
 Git commit:   afdb6d4
 Built:        Tue Sep 26 22:40:09 2017
 OS/Arch:      darwin/amd64

Server:
 Version:      17.09.0-ce
 API version:  1.32 (minimum version 1.12)
 Go version:   go1.8.3
 Git commit:   afdb6d4
 Built:        Tue Sep 26 22:45:38 2017
 OS/Arch:      linux/amd64
 Experimental: true

構建容器鏡像

爲Microblog創建容器的第一步是爲它構建一個鏡像。 容器鏡像是用於創建容器的模板。 它包含容器文件系統的完整表示,以及與網絡,啓動選項等相關的各種設置。

爲應用程序創建容器鏡像的最基本方法是啓動一個要使用的基本操作系統(Ubuntu,Fedora等)容器,連接到運行在其中的bash shell進程,然後手動安裝應用程序,可以參照我在第十七章中介紹的流程進行傳統部署。 安裝完所有內容後,你可以保存容器的快照,並生成容器鏡像。 docker命令支持這種類型的工作流,但我不打算討論這種方法,因爲它非常不便,每次需要生成新鏡像時都必須手動安裝應用程序。

更好的方法是通過腳本生成容器鏡像。 創建腳本化容器鏡像的命令是docker build。 該命令從一個名爲Dockerfile的文件讀取並執行構建指令(我需要創建這些指令)。 Dockerfile基本上可以認爲是一個安裝程序腳本,它執行安裝步驟來部署應用程序,以及一些容器特定的設置。

這是Microblog的一份基礎的Dockerfile

Dockerfile: Microblog的Dockerfile。

FROM python:3.6-alpine

RUN adduser -D microblog

WORKDIR /home/microblog

COPY requirements.txt requirements.txt
RUN python -m venv venv
RUN venv/bin/pip install -r requirements.txt
RUN venv/bin/pip install gunicorn

COPY app app
COPY migrations migrations
COPY microblog.py config.py boot.sh ./
RUN chmod +x boot.sh

ENV FLASK_APP microblog.py

RUN chown -R microblog:microblog ./
USER microblog

EXPOSE 5000
ENTRYPOINT ["./boot.sh"]

Dockerfile中的每一行都是一條命令。 FROM命令指定將在其上構建新鏡像的基礎容器鏡像。 這樣一來,你從一個現有的鏡像開始,添加或改變一些東西,並最終得到一個派生的鏡像。 鏡像由名稱和標籤來標記,它們之間用冒號分隔。 該標籤用作版本控制機制,允許容器鏡像提供多個版本。 我選擇的鏡像的名稱是python,它是Python的官方Docker鏡像。 該鏡像的標籤允許你指定解釋器版本和基礎操作系統。 3.6-alpine標籤選擇安裝在Alpine Linux上的Python 3.6解釋器。 由於其體積小,Alpine Linux發行版比起更常見的發行版(例如Ubuntu)會更多地被使用。 你可以在Python鏡像庫中查看Python鏡像可用的標籤。

RUN命令在容器的上下文中執行任意命令。 這與你在shell提示符下輸入命令相似。 adduser -D microblog命令創建一個名爲microblog的新用戶。 大多數容器鏡像都使用root作爲默認用戶,但以root身份運行應用程序並不是一個好習慣,所以我創建了自己的用戶。

WORKDIR命令設置將要安裝應用程序的默認目錄。 當我在上面創建microblog用戶時,會自動創建了一個主目錄,所以現在我將該目錄設置爲默認目錄。 在Dockerfile中的任何剩餘命令執行以及運行容器時,其當前目錄爲這個默認目錄。

COPY命令將文件從你的機器複製到容器文件系統。 該命令需要兩個或更多參數,源文件/目錄和目標文件/目錄。 源文件必須與Dockerfile所在的目錄相關。 目的地可以是絕對路徑,也可以是相對於在之前的WORKDIR命令中設置的目錄的路徑。 在這第一個COPY命令中,我將requirements.txt文件複製到容器文件系統的microblog用戶的主目錄中。

容器中有了requirements.txt文件,我就可以使用RUN命令創建一個虛擬環境。 首先我創建它,然後在其中安裝所有依賴。 由於依賴文件僅包含通用依賴項,因此我明確安裝gunicorn,以將其用作Web服務器。 當然,我也可以在我的requirements.txt文件中添加gunicorn。

接下來的三個COPY命令從頂級目錄中複製app包,含有數據庫遷移的migrations目錄以及中的microblog.pyconfig.py腳本。 我還複製了一個新文件,boot.sh,我將在下面討論它。

RUN chmod命令確保將這個新的boot.sh文件正確設置爲可執行文件。 如果你使用的是基於Unix的文件系統,並且你的源文件已被標記爲可執行文件,則複製的文件將會已是可執行的。 我顯式地對其進行授權,是因爲在Windows上很難設置可執行位。 如果你正在使用Mac OS X或Linux,你可能不需要這個步驟,但有了它也不會有什麼問題。

ENV命令在容器中設置環境變量。我需要設置FLASK_APP,它是flask命令所依賴的。

下面的RUN chown命令將存儲在/home/microblog中的所有目錄和文件的所有者設置爲新的microblog用戶。 儘管我在Dockerfile的頂部附近創建了該用戶,但所有命令的默認用戶仍爲root,因此所有這些文件的屬主都需要切換到microblog用戶,以便在容器啓動時該用戶可以正確運行這些文件。

下一行中的USER命令使得這個新的microblog用戶成爲任何後續指令的默認用戶,並且也是容器啓動時的默認用戶。

EXPOSE命令配置該容器將用於服務的端口。 這是必要的,以便Docker可以適當地在容器中配置網絡。 我選擇了標準的Flask端口5000,但這其實可以是任意端口。

最後,ENTRYPOINT命令定義了容器啓動時應該執行的默認命令。 這是啓動應用程序Web服務器的命令。 爲了保持良好的代碼組織邏輯,我決定爲此創建一個單獨的腳本,正是我之前複製到容器的boot.sh文件。 這裏是這個腳本的內容:

boot.sh:Docker容器啓動腳本。

#!/bin/sh
source venv/bin/activate
flask db upgrade
flask translate compile
exec gunicorn -b :5000 --access-logfile - --error-logfile - microblog:app

這是一個相當標準的啓動腳本,與第十七章第十八章的部署啓動十分類似。 激活虛擬環境,執行遷移框架升級數據庫,編譯語言翻譯,最後用gunicorn運行服務器。

請注意gunicorn命令之前的exec。 在shell腳本中,exec觸發正在運行腳本的進程被給定的命令來替換掉,而不是將這個命令作爲新進程啓動。 這很重要,因爲Docker會將容器的生命與其上運行的第一個進程關聯起來。 在像這樣的情況下,啓動進程不是容器的主進程,你需要確保主進程取代啓動進程,以確保容器不會提前停止。

Docker的一個有趣的方面是容器寫入stdoutstderr的任何內容都將被捕獲並存儲爲容器的日誌。 出於這個原因,-access-logfile--error-logfile都配置爲-,它將日誌發送到標準輸出,以便它們作爲日誌由Docker存儲。

Dockerfile寫好後,我現在可以構建容器鏡像了:

$ docker build -t microblog:latest .

我給docker build命令的-t參數設置了新容器鏡像的名稱和標籤。 .表示容器構建的基礎目錄,這就是Dockerfile所在的目錄。 構建過程將執行Dockerfile中的所有命令並創建鏡像,該鏡像將存儲在你自己的機器上。

你可以使用docker images命令獲取本地鏡像的列表:

$ docker images
REPOSITORY    TAG          IMAGE ID        CREATED              SIZE
microblog     latest       54a47d0c27cf    About a minute ago   216MB
python        3.6-alpine   a6beab4fa70b    3 months ago         88.7MB

此列表將包含你的新鏡像以及它的基礎鏡像。 每當你對應用程序進行更改後,都可以通過再次運行build命令來更新容器鏡像。

啓動容器

使用已創建的鏡像,你現在可以運行應用程序的容器版本。 通過docker run命令,通常再搭配大量的參數,就可以完成容器的啓動。 我將首先向你展示一個基本的例子:

$ docker run --name microblog -d -p 8000:5000 --rm microblog:latest
021da2e1e0d390320248abf97dfbbe7b27c70fefed113d5a41bb67a68522e91c

--name選項爲新容器提供了一個名稱。 -d選項告訴Docker在後臺運行容器。 如果沒有-d,容器將作爲前臺應用程序運行,從而阻塞你的命令提示符。 -p選項將容器端口映射到主機端口。 第一個端口是主機上的端口,右邊的端口是容器內的端口。 上面的例子暴露了主機端口8000,其對應容器中的端口5000,因此即使內部容器使用5000,你也將在宿主機上訪問端口8000來訪問應用程序。 一旦容器停止,--rm選項將使其自動被刪除。 雖然這不是必需的,但完成或中斷的容器通常不再需要,因此可以自動刪除。 最後一個參數是容器使用的容器鏡像名稱和標籤。 運行上述命令後,可以在http://localhost:8000上訪問該應用程序。

docker run的輸出是分配給新容器的ID。 這是一個很長的十六進制字符串,在隨後的命令中你可以使用它來引用容器。 實際上,只有前幾個字符是必需的,足以保證ID的唯一性。

如果你想看看哪些容器正在運行,你可以使用docker ps命令:

$ docker ps
CONTAINER ID  IMAGE             COMMAND      PORTS                   NAMES
021da2e1e0d3  microblog:latest  "./boot.sh"  0.0.0.0:8000->5000/tcp  microblog

你可以看到,其實docker ps命令顯示的是縮短了的容器ID。 如果你現在想停止容器,你可以使用docker stop

$ docker stop 021da2e1e0d3
021da2e1e0d3

回顧一下,應用程序配置中有許多來自環境變量的選項。 例如,Flask密鑰,數據庫URL和電子郵件服務器選項都是從環境變量中導入的。 在上面的docker run例子中,我沒有考慮這些,因此所有這些配置選項都將使用默認值。

在更實際的例子中,你將在容器內設置這些環境變量。 你在前面的章節看到,Dockerfile中的ENV命令設置了環境變量,對於將變爲靜態的變量來說,這是一個方便的選項。 但是,對於依賴於安裝的變量,將它們作爲構建過程的一部分並不方便,因爲你希望容器鏡像具有良好的可移植性。 如果你想將應用程序作爲容器鏡像提供給另一個人,你希望該人員能夠按原樣使用它,而不必使用不同的變量重新構建它。

所以構建時的環境變量可能很有用,但是也需要有可以通過docker run命令設置的運行時環境變量,對於這些變量,可以使用-e選項來設置。 以下示例設置了密鑰和gmail帳戶:

$ docker run --name microblog -d -p 8000:5000 --rm -e SECRET_KEY=my-secret-key \
    -e MAIL_SERVER=smtp.googlemail.com -e MAIL_PORT=587 -e MAIL_USE_TLS=true \
    -e MAIL_USERNAME=<your-gmail-username> -e MAIL_PASSWORD=<your-gmail-password> \
    microblog:latest

由於具有許多環境變量定義,docker run命令行非常長的情況並不罕見。

使用第三方“容器化”服務

Microblog的容器版本看起來不錯,但我還沒有真正考慮過很多關於存儲的問題。 實際上,由於我沒有設置DATABASE_URL環境變量,因此應用程序正在使用默認SQLite數據庫並將數據存儲在容器內部的文件系統上。 當你停止並刪除容器時,你認爲數據去哪裏了? 數據也會被刪除!

容器中的文件系統是臨時的,這意味着它隨着容器的刪除而刪除。 你可以將數據寫入容器內的文件系統,並且容器可以正常讀寫數據,但如果出於任何原因需要回收容器並將其替換爲新的容器,則應用程序保存到容器內的任何數據將永遠丟失。

容器應用程序的一個好的設計策略是保持應用程序容器無狀態。 如果你的應用程序代碼和數據容器沒有任何問題,可以將其丟棄並替換爲新的容器,容器變爲真正的一次性容器,這在簡化升級部署方面非常有用。

但是,這意味着數據必須放在應用程序容器之外的某個位置。 這就是神奇的Docker生態系統發揮作用的地方了。 Docker容器鏡像倉庫包含大量的容器鏡像。你已經瞭解了Python容器鏡像,我正在使用它作爲我的Microblog容器的基礎鏡像。 除此之外,Docker還爲Docker容器鏡像倉庫中的許多其他語言,數據庫和其他服務維護鏡像,如果這還不夠,Docker容器鏡像倉庫還允許公司爲其產品發佈容器鏡像,並且像你我這樣的常規用戶也可以發佈自己的鏡像。 這意味着安裝第三方服務需要做出的努力會減少成只需在Docker容器鏡像倉庫中找到合適的鏡像,並通過帶有適當參數的docker run命令啓動它。

所以我現在要做的是創建兩個額外的容器,一個用於MySQL數據庫,另一個用於Elasticsearch服務,然後我將加長啓動Microblog容器的命令, 以使其能夠訪問這兩個新的容器。

添加MySQL容器

像許多其他產品和服務一樣,MySQL在Docker鏡像倉庫中提供了公共容器鏡像。 就像我自己的Microblog容器一樣,MySQL依賴於需要傳遞給docker run的環境變量。 他們配置了密碼,數據庫名稱等。在鏡像倉庫中有許多MySQL鏡像時,我決定使用由MySQL官方團隊維護的鏡像。 你可以在其鏡像倉庫頁面找到有關MySQL容器鏡像的詳細信息:https://hub.docker.com/r/mysql/mysql-server/

回顧一下在第十七章中設置MySQL的繁瑣過程,你就會讚歎在Docker中部署MySQL的輕鬆體驗。 這裏是啓動MySQL服務器的docker run命令:

$ docker run --name mysql -d -e MYSQL_RANDOM_ROOT_PASSWORD=yes \
    -e MYSQL_DATABASE=microblog -e MYSQL_USER=microblog \
    -e MYSQL_PASSWORD=<database-password> \
    mysql/mysql-server:5.7

這就對了! 在安裝了Docker的任何機器上,你可以運行上面的命令,就會得到一個完成安裝的MySQL服務器,它具有一個隨機生成的root密碼,一個名爲microblog的全新數據庫和一個名字相同的用戶,該用戶具備訪問這個數據庫的所有權限。 請注意,你需要輸入正確的密碼,以便它可以從MYSQL_PASSWORD環境變量獲得。

現在在應用程序方面,我需要添加一個MySQL客戶端軟件包,就像我在Ubuntu上進行傳統部署一樣。 我將再次使用pymysql,我可以將它添加到Dockerfile中:

Dockerfile:添加pymysql到Dockerfile中。

# ...
RUN venv/bin/pip install gunicorn pymysql
# ...

任何時候對應用程序或Dockerfile進行更改後,都需要重建容器鏡像:

$ docker build -t microblog:latest .

現在我可以再次啓動Microblog,但是這次連接到數據庫容器,以便兩者都可以通過網絡進行通信:

$ docker run --name microblog -d -p 8000:5000 --rm -e SECRET_KEY=my-secret-key \
    -e MAIL_SERVER=smtp.googlemail.com -e MAIL_PORT=587 -e MAIL_USE_TLS=true \
    -e MAIL_USERNAME=<your-gmail-username> -e MAIL_PASSWORD=<your-gmail-password> \
    --link mysql:dbserver \
    -e DATABASE_URL=mysql+pymysql://microblog:<database-password>@dbserver/microblog \
    microblog:latest

--link選項告訴Docker讓正要運行的容器可以訪問參數中指定的容器。 該參數包含由冒號分隔的兩個名稱。 第一部分是要鏈接的容器的名稱或ID,在本例中是我在上面創建的一個名爲mysql的容器。 第二部分定義了一個可以在這個容器中用來引用鏈接的主機名。 這裏我使用dbserver作爲代表數據庫服務器的通用名稱。

通過建立兩個容器之間的鏈接,我可以設置DATABASE_URL環境變量,以便SQLAlchemy被引導使用其他容器中的MySQL數據庫。 數據庫URL將使用dbserver作爲數據庫主機名,microblog作爲數據庫名稱和用戶,以及你在啓動MySQL時選擇的密碼。

我在試用MySQL容器時注意到的一件事是,這個容器需要幾秒鐘才能完全運行並準備好接受數據庫連接。 如果啓動MySQL容器,然後立刻啓動應用容器,在boot.sh腳本嘗試運行flask db migrate時,則可能會因數據庫未準備好接受連接而失敗。 爲了使我的解決方案更加健壯,我決定在boot.sh中添加一個重試循環:

boot.sh:重試數據庫連接。

#!/bin/sh
source venv/bin/activate
while true; do
    flask db upgrade
    if [[ "$?" == "0" ]]; then
        break
    fi
    echo Upgrade command failed, retrying in 5 secs...
    sleep 5
done
flask translate compile
exec gunicorn -b :5000 --access-logfile - --error-logfile - microblog:app

此循環檢查flask db upgrade命令的退出代碼,如果它不爲零,則認爲出現了問題,因此它會等待5秒鐘然後重試。

添加Elasticsearch容器

Elasticsearch Docker文檔演示瞭如何將該服務作爲單一節點以用於開發模式,以及部署兩個節點的生產環境服務。 現在,我將使用單節點模式,並使用引擎開源的“oss”鏡像。 容器使用以下命令啓動:

$ docker run --name elasticsearch -d -p 9200:9200 -p 9300:9300 --rm \
    -e "discovery.type=single-node" \
    docker.elastic.co/elasticsearch/elasticsearch-oss:6.1.1

這個docker run命令與我用於Microblog和MySQL的命令有很多相似之處,但是有一些有趣的區別。 首先,有兩個-p選項,這意味着這個容器將在兩個端口上而不是一個端口上進行監聽。 端口9200和9300都映射到主機中的相同端口。

另一個區別在於用於引用容器鏡像的語法。 對於我在本地構建的鏡像,語法是<name>:<tag>。 MySQL容器使用格式爲稍微更完整的<account>/<name>:<tag>語法,適用於在Docker鏡像倉庫中引用容器鏡像。 我使用的Elasticsearch鏡像遵循模式<registry>/<account><name>:<tag>,其中包括鏡像倉庫的地址作爲第一個組件。 此語法用於未託管在Docker鏡像倉庫中的鏡像。 在本處,Elasticsearch在docker.elastic.co上運行自己的容器鏡像倉庫服務,而不是使用由Docker維護的主鏡像倉庫。

所以,現在我已經啓動並運行了Elasticsearch服務,我可以修改Microblog容器的啓動命令以創建指向它的鏈接並設置Elasticsearch服務URL:

$ docker run --name microblog -d -p 8000:5000 --rm -e SECRET_KEY=my-secret-key \
    -e MAIL_SERVER=smtp.googlemail.com -e MAIL_PORT=587 -e MAIL_USE_TLS=true \
    -e MAIL_USERNAME=<your-gmail-username> -e MAIL_PASSWORD=<your-gmail-password> \
    --link mysql:dbserver \
    -e DATABASE_URL=mysql+pymysql://microblog:<database-password>@dbserver/microblog \
    --link elasticsearch:elasticsearch \
    -e ELASTICSEARCH_URL=http://elasticsearch:9200 \
    microblog:latest

在運行此命令之前,如果你仍然在運行Microblog容器,請先停止它。 還要仔細操作來爲數據庫設置正確的密碼,並讓Elasticsearch服務的參數處於命令中的恰當位置。

現在你應該可以訪問http://localhost:8000並使用搜索功能。 如果你遇到任何錯誤,可以通過查看容器日誌來對其進行排查。 你很可能希望查看Microblog容器的日誌,其中將顯示任何Python堆棧跟蹤:

$ docker logs microblog

Docker容器鏡像倉庫

現在我已經在Docker上使用三個容器來運行了完整的應用程序,其中兩個容器來自公開的第三方鏡像。 如果你想提供自己的容器鏡像給其他人,那麼你必須將它們推送到任何人都可以獲取到的Docker鏡像倉庫中。

要訪問Docker鏡像倉庫,你需要轉到https://hub.docker.com併爲自己創建一個帳戶。 確保你選擇一個你喜歡的用戶名,因爲這將用於你發佈的所有鏡像。

爲了能夠從命令行訪問你的賬戶,你需要使用docker login命令登錄:

$ docker login

如果你一直跟隨我的引導,現在你的計算機上已經有一個名爲microblog:latest的鏡像存儲在本地。 爲了能夠將這個鏡像推送到Docker鏡像倉庫中,它需要重新命名以包含該帳戶,正如來自MySQL的鏡像。 這是通過docker tag命令完成的:

$ docker tag microblog:latest <your-docker-registry-account>/microblog:latest

如果你再次用docker images列出你的鏡像,你會看到兩個Microblog條目,一個是microblog:latest,另一個還包括你的帳戶名。 它們實際上是同一鏡像的兩個別名。

要將鏡像發佈到Docker鏡像倉庫,請使用docker push命令:

$ docker push <your-docker-registry-account>/microblog:latest

現在你的鏡像被公開了,你可以像MySQL和服務那樣,說明如何安裝它並從Docker鏡像倉庫運行。

容器化應用的部署

讓你的應用程序在Docker容器中運行的最大的好處之一是,一旦該容器在你的本地測試通過了,就可以將它們運行到任何提供Docker支持的平臺。 例如,你可以使用第十七章中推薦的Digital Ocean,Linode或Amazon Lightsail上的相同服務器。 即使這些提供商提供的最便宜的產品也足以讓Docker運行一些容器。

Amazon Container Service(ECS)使你能夠創建一個容器宿主機集羣,以在其中運行容器。在集成完備的AWS環境中,提供了水平擴展和負載平衡,以及爲容器鏡像使用私有容器鏡像倉庫的功能。

最後,容器編排平臺例如Kubernetes通過允許你以簡單的YAML格式文本文件描述你的多容器部署邏輯,來提供了更高級別的自動化和便利性, 負載均衡,水平擴展,密鑰的安全管理以及滾動升級和回滾。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章