目錄
- Chapter 1: Hello, World!
- Chapter 2: Templates
- Chapter 3: Web Forms
- Chapter 4: Database
- Chapter 5: User Logins
- Chapter 6: Profile Page and Avatars
- Chapter 7: Error Handling
- Chapter 8: Followers
- Chapter 9: Pagination
- Chapter 10: Email Support
- Chapter 11: Facelift
- Chapter 12: Dates and Times
- Chapter 13: I18n and L10n
- Chapter 14: Ajax
- Chapter 15: A Better Application Structure
- Chapter 16: Full-Text Search
- Chapter 17: Deployment on Linux
- Chapter 19: Deployment on Docker Containers
- Chapter 20: Some JavaScript Magic
- Chapter 21: User Notifications
- Chapter 22: Background Jobs
- Chapter 23: Application Programming Interfaces (APIs)
Flask 大型教程,第十七部分:在 Linux 上部署
這是 Flask 大型教程系列的第十七篇文章,在這篇文章中,我將會將 Microblog 部署到一台 Linux 伺服器上。
在這個章節中,我達到了我的 Microblog 應用程式生命週期中的一個里程碑,因為我將討論將應用程式部署到生產伺服器上的方式,使其可以被真實用戶訪問。
部署的主題是廣泛的,因此不可能在這裡涵蓋所有可能的選項。這一章節致力於探索傳統的主機選項,作為主題,我將使用運行 Ubuntu 的專用 Linux 伺服器,以及廣受歡迎的 Raspberry Pi 迷你電腦。我將在後續章節中涵蓋其他選項,如雲端和容器部署。
這個章節的 GitHub 連結是:瀏覽、Zip、Diff。
傳統主機
當我提到「傳統主機」時,我的意思是應用程式是手動或通過腳本安裝程式在一台標準伺服器機器上安裝。這個過程涉及安裝應用程式、它的依賴項以及一個生產規模的網頁伺服器,並配置系統以使其安全。
當你準備部署自己的專案時,你需要問的第一個問題是在哪裡可以找到一台伺服器。如今,有許多經濟型主機服務。例如,每月 5 美元,Digital Ocean、Linode 或 Amazon Lightsail 將租用一台虛擬化的 Linux 伺服器給你來進行部署實驗(Linode 和 Digital Ocean 提供的入門級伺服器配備了 1GB 的 RAM,而 Amazon 僅提供 512MB)。如果你希望在不花錢的情況下練習部署,那麼 Vagrant 和 VirtualBox 是兩個結合使用可以在你自己的電腦上建立與付費伺服器類似的虛擬伺服器的工具。
就作業系統選擇而言,從技術角度來看,這個應用程式可以部署在任何主要作業系統上,這包括大量開源的 Linux 和 BSD 發行版,以及商業的 macOS 和 Microsoft Windows(macOS 是一個混合的開源/商業選項,因為它是基於 Darwin 的,Darwin 是一個開源的 BSD 衍生物)。
由於 macOS 和 Windows 是桌面作業系統,不是優化用來作為伺服器的,我將把它們作為候選項排除在外。在 Linux 或 BSD 作業系統之間的選擇大多基於偏好,所以我將選擇兩者中最受歡迎的,即 Linux。就 Linux 發行版而言,我再次根據受歡迎程度進行選擇,選擇 Ubuntu。
建立一個 Ubuntu 伺服器
如果你有興趣跟著我一起進行這次部署,你顯然需要一台伺服器來操作。我將為你推薦兩個獲取伺服器的選項,一個付費,一個免費。如果你願意花一點錢,你可以在 Digital Ocean、Linode 或 Amazon Lightsail 開設一個帳戶,並建立一個帶有當前長期支援(LTS)版本的 Ubuntu 虛擬伺服器,寫這篇文章時是 22.04。你應該使用最小的伺服器選項,那個大約每月 5 美元。成本按照你擁有伺服器的小時數來按比例計算,所以如果你建立了伺服器,玩了幾個小時然後刪除它,你將只需支付幾分錢。
免費的替代方案是基於一個你可以在自己的電腦上運行的虛擬機。使用這個選項,請在你的機器上安裝 Vagrant 和 VirtualBox,然後建立一個名為 Vagrantfile 的檔案來描述你的 VM 規格,內容如下:
Vagrantfile: Vagrant 配置。
1 |
|
這個檔案配置了一個帶有 2GB RAM 的 Ubuntu 22.04 伺服器,你將能夠從主機電腦在 IP 地址 192.168.56.10 訪問它。要建立伺服器,運行以下命令:
1 |
|
諮詢 Vagrant 命令列文件以了解管理你的虛擬伺服器的其他選項。
使用 SSH 客戶端
你的伺服器是無頭的,所以你不會像你自己的電腦上那樣有一個桌面。你將通過 SSH 客戶端連接到你的伺服器,並通過命令列在其上工作。如果你使用 Linux 或 Mac OS X,你可能已經安裝了 OpenSSH。如果你使用 Microsoft Windows,Cygwin、Git 和 Windows 子系統為 Linux 提供了 OpenSSH,所以你可以安裝這些選項中的任何一個。
當使用來自第三方提供商的虛擬伺服器時,你會得到一個 IP 地址。你可以用以下命令打開一個終端會話與你全新的伺服器:
1 |
|
你將被提示輸入一個密碼。根據服務,密碼可能已經自動生成並在你建立伺服器後顯示給你,或者你可能有選擇你自己的密碼的選項。
如果你使用 Vagrant VM,你可以使用命令打開一個終端會話:
1 |
|
如果你使用 Windows 並有一個 Vagrant VM,請注意你需要從可以調用 OpenSSH 的 ssh 命令的 shell 運行上述命令,所以它可能是一
個 bash 或類似提示符,而不是一個原生 Windows 終端。
無密碼登錄
如果你使用 Vagrant VM,你可以跳過這一節,因為你的 VM 已經由 Vagrant 自動配置為使用一個名為 vagrant 或 ubuntu 的非 root 帳戶,而且自動沒有密碼。
當使用虛擬伺服器時,建議你建立一個常規用戶帳戶來進行你的部署工作,並配置這個帳戶以便在不使用密碼的情況下登錄你,這一開始可能看起來是一個壞主意,但你會看到它不僅更方便,而且更安全。
我將建立一個名為 ubuntu 的用戶帳戶(如果你喜歡,你可以使用不同的名字)。要建立這個用戶帳戶,使用上一節的 ssh 指令登錄到你的伺服器的 root 帳戶,然後輸入以下命令來建立用戶,給它 sudo 權限,最後切換到它:
1 |
|
現在我將配置這個新的 ubuntu 帳戶使用公鑰認證,這樣你就可以在不需要輸入密碼的情況下登錄。
暫時離開你在伺服器上打開的終端會話,並在你自己的機器上啟動第二個終端。如果你使用 Windows,這需要是你可以訪問 ssh 命令的終端,所以它可能是一個 bash 或類似提示符,而不是原生 Windows 終端。在那個終端會話中,檢查 ~/.ssh 目錄的內容:
1 |
|
如果目錄列表顯示了名為 id_rsa 和 id_rsa.pub 的檔案,那麼你已經有一個鑰匙了。如果你沒有這兩個檔案,或者你根本沒有 ~/.ssh 目錄,那麼你需要通過運行以下命令來建立你的 SSH 鑰匙對,這也是 OpenSSH 工具集的一部分:
1 |
|
這個應用程式將提示你輸入一些東西,對於這些提示我建議你接受預設值,通過按 Enter 鍵在所有提示上。如果你知道你在做什麼並想要做其他事情,你當然可以。
這個命令運行後,你應該有了上面列出的兩個檔案。id_rsa.pub 檔案是你的公鑰,這是一個你將提供給第三方作為識別你的方式的檔案。id_rsa 檔案是你的私鑰,不應該與任何人分享。
你現在需要配置你的公鑰作為你伺服器上的授權主機。在你自己的電腦上打開的終端上,將你的公鑰打印到螢幕上:
1 |
|
這將是一個非常長的字符序列,可能跨越多行。你需要將這些數據複製到剪貼板,然後切換回你遠程伺服器上的終端,然後發出這些命令來儲存公鑰:
1 |
|
無密碼登錄現在應該在工作了。這個想法是你的機器上的 ssh 將通過執行需要私鑰的加密操作來識別自己給伺服器。然後伺服器使用你的公鑰驗證操作是否有效。
你現在可以登出你的 ubuntu 會話,然後從你的 root 會話登出,然後嘗試直接使用以下命令登錄到 ubuntu 帳戶:
1 |
|
這次你不應該需要輸入密碼!
保護你的伺服器
為了最小化你的伺服器被攻破的風險,你可以採取一些步驟,針對關閉攻擊者可能通過它們獲取訪問的潛在門戶。
我將進行的第一個更改是禁用通過 SSH 的 root 登錄。你現在可以無密碼訪問 ubuntu 帳戶,並可以通過 sudo 從這個帳戶運行管理員命令,所以真的沒有必要暴露 root 帳戶。要禁用 root 登錄,你需要編輯你伺服器上的 /etc/ssh/sshd_config 檔案。你的伺服器可能已經安裝了 vi 和 nano 文本編輯器,你可以使用它們來編輯檔案(如果你不熟悉它們中的任何一個,先試試 nano)。你需要在編輯器前加上 sudo,因為 SSH 配置對普通用戶不可訪問(即 sudo vi /etc/ssh/sshd_config)。你需要在這個檔案中更改單行:
/etc/ssh/sshd_config: 禁用 root 登錄。
1 |
|
注意要進行這個更改,你需要找到以 PermitRootLogin 開頭的行,並將值更改為 no,無論你的伺服器上的值是什麼。
下一個更改也在同一個檔案中。現在我將禁用所有帳戶的密碼登錄。你已經設置了無密碼登錄,所以完全沒有必要允許密碼。如果你對完全禁用密碼感到緊張,你可以跳過這個更改,但對於生產伺服器來說,這是一個好主意,因為攻擊者不斷嘗試在所有伺服器上隨機帳戶名和密碼,希望能夠幸運。要禁用密碼登錄,在 /etc/ssh/sshd_config 中更改以下行:
/etc/ssh/sshd_config: 禁用密碼登錄。
1 |
|
在你完成編輯 SSH 配置後,需要重新啟動服務以使更改生效
:
1 |
|
我將進行的第三個更改是安裝一個防火牆。這是一個軟體,它阻止在沒有明確啟用的端口上訪問伺服器:
1 |
|
這些命令安裝 ufw,即 Uncomplicated Firewall,並將其配置為僅允許在端口 22(ssh)、80(http)和 443(https)上的外部流量。任何其他端口都不會被允許。
安裝基本依賴
如果你遵循我的建議並用 Ubuntu 20.04 版本配置了你的伺服器,那麼你有一個系統,它完全支援 Python 3.8,所以這是我將用於部署的版本。
基本的 Python 解釋器可能已經預裝在你的伺服器上,但可能有一些額外的包還沒有,還有一些其他的包在 Python 之外將在建立一個健壯的、準備就緒的生產部署中很有用。對於數據庫伺服器,我將從 SQLite 切換到 MySQL。postfix 包是一個郵件傳輸代理,我將用它來發送郵件。supervisor 工具將監控 Flask 伺服器進程,並在它崩潰時自動重啟它,或者也在伺服器重啟時。nginx 伺服器將接受來自外部世界的所有請求,並將它們轉發給應用程式。最後,我將使用 git 作為我的工具選擇,直接從其 git 儲存庫下載應用程式。
1 |
|
這些安裝大多是無人值守的。根據你安裝的 Ubuntu 版本,你可能會收到一個提示,要求重新啟動服務,你可以接受預設選擇。當你運行第三個安裝語句時,你將被問到一些關於 postfix 包安裝的問題,你也可以接受它們的預設答案。
注意對於這次部署,我選擇不安裝 Elasticsearch。這個服務需要大量的 RAM,所以只有當你有一個大於 2GB RAM 的大伺服器時才可行。為了避免伺服器內存不足的問題,我將留出搜索功能。如果你有一個足夠大的伺服器,你可以從 Elasticsearch 網站下載官方 .deb 包,並按照他們的安裝指南將其添加到你的伺服器。
我還應該指出,postfix 的默認安裝可能不足以在生產環境中發送電子郵件。為了避免垃圾郵件和惡意電子郵件,許多伺服器要求發件伺服器通過安全
擴展來識別自己,這意味著你至少必須擁有一個與你的伺服器關聯的域名。如果你想學習如何完全配置一個電子郵件伺服器,使其通過標準安全測試,請參見以下 Digital Ocean 指南:
- Postfix 配置
- 添加 SPF 記錄
- DKIM 安裝和配置
安裝應用程式
現在我將使用 git 從我的 GitHub 儲存庫下載 Microblog 源代碼。如果你不熟悉 git 源控制,我建議你閱讀 git 入門。
要將應用程式下載到伺服器,請確保你在 ubuntu 用戶的家目錄中,然後運行:
1 |
|
這將在你的伺服器上安裝代碼,並將其與本章同步。如果你將本教程代碼的版本保持在你自己的 git 儲存庫中,你可以將儲存庫 URL 更改為你的,並在這種情況下你可以跳過 git checkout 命令。
現在我需要建立一個虛擬環境並用所有包依賴填充它,這些依賴我在第 15 章方便地保存到了 requirements.txt 檔案中:
1 |
|
除了 requirements.txt 中的常見要求外,我將使用三個特定於這次生產部署的包,所以它們不包含在常見要求檔案中。gunicorn 包是一個 Python 應用程式的生產網頁伺服器。pymysql 包包含 MySQL 驅動程序,使得 SQLAlchemy 能夠與 MySQL 數據庫工作。cryptography 包由 pymsql 使用,用於對 MySQL 數據庫伺服器進行身份驗證。
1 |
|
我需要建立一個 .env 檔案,帶有所有需要的環境變量:
/home/ubuntu/microblog/.env: 環境配置。
1 |
|
這個 .env 檔案與我在第 15 章中顯示的例子大致相似,但我使用了一個隨機字符串作為 SECRET_KEY。你應該在這裡生成你自己的密鑰。你可以使用以下命令:
1 |
|
對於 DATABASE_URL 變量,我定義了一個 MySQL URL。我將在下一節中向你展示如何配置數據庫。
我需要設置 FLASK_APP 環境變量到應用程式的入口點,以啟動 flask 命令工作。如果你的專案儲存庫中沒有 .flaskenv 檔案,那麼現在是添加一個的時候了。你可以通
過運行 flask –help 確認 FLASK_APP 變量是否配置。如果幫助訊息顯示了應用程式添加的 translate 命令,那麼你知道應用程式被找到了。
現在 flask 命令是功能性的,我可以編譯語言翻譯:
1 |
|
設置 MySQL
在開發期間我使用的 SQLite 數據庫非常適合簡單應用程式,但當部署一個可能需要同時處理多個請求的完整網頁伺服器時,最好使用更健壯的數據庫。因此我將設置一個我將稱為 microblog 的 MySQL 數據庫。
要管理數據庫伺服器,我將使用 mysql 命令,它應該已經在你的伺服器上安裝:
1 |
|
請注意,你需要使用 sudo 從管理員帳戶訪問 MySQL root 用戶。
這些是建立一個名為 microblog 的新數據庫,以及一個同名用戶的命令,該用戶對它擁有完全訪問權限:
1 |
|
你需要將
如果你的數據庫配置正確,你現在應該能夠運行數據庫遷移,建立所有表格:
1 |
|
在你繼續之前,確保上述命令完成並且沒有產生任何錯誤。
設置 Gunicorn 和 Supervisor
當你使用 flask run 運行伺服器時,你正在使用 Flask 附帶的網頁伺服器。這個伺服器在開發期間非常有用,但它不是用於生產伺服器的好選擇,因為它沒有考慮到性能和健壯性。與 Flask 的開發伺服器不同,對於這次部署,我決定使用 Gunicorn,這也是一個純 Python 網頁伺服器,但與 Flask 的不同,它是一個健壯的生產伺服器,被很多人使用,同時配置非常簡單。
要在 Gunicorn 下啟動 Microblog,你可以使用以下命令:
1 |
|
-b 選項告訴 Gunicorn 在哪裡聽請求,我將其設置
為在內部網絡接口的 8000 端口。通常建議在沒有外部訪問的情況下運行 Python 網頁應用程式,然後有一個非常快的網頁伺服器優化以服務靜態檔案接受來自客戶端的所有請求。這個快速的網頁伺服器將直接服務靜態檔案,並將任何針對應用程式的請求轉發給內部伺服器。我將在下一節中向你展示如何設置 nginx 作為公共面向伺服器。
-w 選項配置 Gunicorn 將運行多少個工作進程。擁有四個工作進程允許應用程式同時處理多達四個客戶端,對於一個網頁應用程式來說,通常足以處理相當多的客戶端,因為不是所有的客戶端都在不斷請求內容。根據你的伺服器擁有的 RAM 量,你可能需要調整工作進程的數量,以便你不會耗盡記憶體。
microblog:app 參數告訴 Gunicorn 如何加載應用程式實例。冒號前的名稱是包含應用程式的模塊,對於這個應用程式是 microblog.py。冒號後的名稱是應用程式實例的名稱。
雖然 Gunicorn 非常簡單地設置,但實際上從命令列運行伺服器並不是生產伺服器的好解決方案。我想做的是讓伺服器在後台運行,並且處於不斷監控的狀態,因為如果由於任何原因伺服器崩潰並退出,我想確保自動啟動一個新伺服器來取代它的位置。而且我還想確保如果機器被重啟,伺服器在啟動時自動運行,無需我登錄並啟動事物。我將使用上面安裝的 supervisor 包來做到這一點。
supervisor 實用程式使用配置檔案告訴它監控哪些程式以及如何在必要時重新啟動它們。配置檔案必須存儲在 /etc/supervisor/conf.d 中。以下是 Microblog 的配置檔案,我將其命名為 microblog.conf:
/etc/supervisor/conf.d/microblog.conf: Supervisor 配置。
1 |
|
command、directory 和 user 設置告訴 supervisor 如何運行應用程式。autostart 和 autorestart 設置自動重新啟動,由於電腦啟動或崩潰。stopasgroup 和 killasgroup 選項確保當 supervisor 需要停止應用程式以重新啟動它時,它也達到了頂級 Gunicorn 進程的子進程。
在你
寫這個配置檔案後,你必須重新加載 supervisor 服務以導入它:
1 |
|
就這樣,Gunicorn 網頁伺服器應該已經啟動並運行並且被監控了!
設置 Nginx
由 Gunicorn 提供動力的 microblog 應用程式伺服器現在在 8000 端口上私下運行。我現在需要做的是啟用我的公共面向網頁伺服器在端口 80 和 443 上,這兩個端口我在防火牆上打開以處理應用程式的網頁流量。
我希望這是一個安全的部署,所以我將配置端口 80 將所有流量轉發到端口 443,這將是加密的。所以我將從建立一個 SSL 證書開始。現在,我將建立一個自簽名的 SSL 證書,這對於測試一切都很好,但對於真實部署來說不好,因為網頁瀏覽器將警告用戶證書沒有由受信任的證書授權機構簽發。為 microblog 建立 SSL 證書的命令是:
1 |
|
命令將要求你輸入有關你的應用程式和你自己的一些訊息。這是將包含在 SSL 證書中的訊息,網頁瀏覽器將向用戶顯示它,如果他們請求查看它。上述命令的結果將是兩個名為 key.pem 和 cert.pem 的檔案,我將它們放在 Microblog 根目錄的 certs 子目錄中。
要由 nginx 服務一個網站,你需要為它寫一個配置檔案。在大多數 nginx 安裝中,這個檔案需要在 /etc/nginx/sites-available 目錄中。以下你可以看到 nginx 為 Microblog 的配置檔案,它位於 /etc/nginx/sites-available/microblog 中:
/etc/nginx/sites-available/microblog: Nginx 配置。
1 |
|
nginx 配置遠非微不足道,但我添加了一些註釋,這樣至少你知道每個部分做了什麼。如果你想要有關特定指令的訊息,請諮詢 nginx 官方文件。
網站現在已經配置好了,但還沒有啟用。要啟用它,需要在 /etc/nginx/sites-enabled 目錄中為這個檔案建立一個鏈接。Nginx 附帶了一個我真的不需要的測試網站,所以我將從刪除它開始:
1 |
|
現在我可以建立一個鏈接到 microblog 配置:
1 |
|
在你添加這個檔案後,你需要告訴 nginx 重新加載配置:
1 |
|
現在應用程式應該已經部署了。在你的網頁瀏覽器中,你可以輸入你的伺服器的 IP 地址(或如果你使用 Vagrant VM 則為 192.168.56.10),這將連接到應用程式。因為你使用了自簽名證書,你將從網頁瀏覽器收到一個警告,你將不得不駁回。
在你根據上述說明完成自己的專案部署後,我強烈建議你用一個真正的證書替換自簽名證書,這樣瀏覽器就不會警告你的用戶關於你的網站了。為此,你首先需要購買一個域名,並將其配置為指向你的伺服器的 IP 地址。一旦你有了一個域名,你可以請求一個免費的 Let’s Encrypt SSL 證書。我在我的博客上寫了一篇詳細的文章,講述如何通過 HTTPS 運行你的 Flask 應用程式。
部署應用程式更新
我想討論的最後一個關於基於 Linux 的部署的主題是如何處理應用程式升級。應用程式源代碼通過 git 安裝在伺服器上,所以每當你想要將你的應用程式升級到最新版本時,你可以只運行 git pull 下載自上次部署以來所做的新提交。
但當然,下載新版本的代碼不會導致升級。當前運行的伺服器進程將繼續運行舊代碼,這已經被讀取並儲存在記憶體中。要觸發升級,你必須停止當前伺服器並啟動一個新伺服器,以強制重新讀取所有代碼。
一般來說,進行升級比僅僅重新啟動伺服器更複雜。你可能需要應用數據庫遷移,或編譯新語言翻譯,所以實際上,執行升級的過程涉及一系列命令:
1 |
|
Raspberry Pi 主機
Raspberry Pi 是一款成本低廉的小型 Linux 電腦,它耗電量非常小,因此非常適合用來架設一個家用的網頁伺服器,它可以24小時不間斷地在線,而不需要佔用你的桌面電腦或筆記型電腦。有幾種 Linux 發行版可以在 Raspberry Pi 上運行。我的選擇是 Raspberry Pi OS,這是 Raspberry Pi 基金會的官方發行版。
為了準備 Raspberry Pi,我將安裝一個全新的 Raspberry Pi OS 發行版。我會使用 Lite 版本,因為我不需要桌面使用者介面。你可以在他們的操作系統頁面上找到 Raspberry Pi OS 的最新發行版。
Raspberry Pi OS 映像檔需要安裝在一張 SD 卡上,然後你將其插入 Raspberry Pi,以便它能夠進行開機。在 Raspberry Pi 網站上有關於如何從 Windows、Mac OS X 和 Linux 將 Raspberry Pi OS 映像檔複製到 SD 卡的指南。
當你第一次開機 Raspberry Pi 時,連接鍵盤和顯示器進行開機,這樣你才能進行設定。你至少應該啟動 SSH,這樣你就可以從你的電腦登入,更舒適地進行部署任務。
就像 Ubuntu,Raspberry Pi OS 是 Debian 的衍生版,所以上述針對 Ubuntu Linux 的指南在大部分情況下也同樣適用於 Raspberry Pi。然而,如果你計劃在家庭網路上運行一個小型應用程式,而且不需要外部存取,你可能會決定跳過一些步驟。例如,你可能不需要防火牆,或無密碼登錄。在這樣的小型電腦上,你可能會想使用 SQLite 而不是 MySQL。你可能選擇不使用 nginx,只讓 Gunicorn 伺服器直接監聽來自客戶端的請求。你可能只想要一兩個 Gunicorn 工作進程。supervisor 服務在確保應用程式始終上線方面非常有用,所以我建議你也在 Raspberry Pi 上使用它。
繼續進行到下一章。