# 部署概念 { #deployments-concepts }

當你要部署一個 FastAPI 應用，或其實任何類型的 Web API 時，有幾個你可能在意的概念。掌握這些概念後，你就能找出最適合部署你應用的方式。

一些重要的概念包括：

- 安全性 - HTTPS
- 開機自動執行
- 重新啟動
- 複本（執行中的行程數量）
- 記憶體
- 啟動前的前置步驟

我們將看看它們如何影響部署。

最終目標是能夠以安全、避免中斷，並盡可能高效使用運算資源（例如遠端伺服器／虛擬機）的方式來服務你的 API 用戶端。🚀

我會在這裡多介紹一些這些觀念，希望能幫你建立必要的直覺，讓你能在非常不同、甚至尚未出現的未來環境中決定要如何部署你的 API。

在思考這些概念之後，你將能夠評估與設計最適合部署你自己 API 的方式。

在接下來的章節，我會提供更具體的部署 FastAPI 應用的食譜。

但現在，先來看看這些重要的概念想法。這些概念同樣適用於任何其他類型的 Web API。💡

## 安全性 - HTTPS { #security-https }

在[前一章關於 HTTPS](https.md) 中，我們學到 HTTPS 如何為你的 API 提供加密。

我們也看到，HTTPS 通常由應用伺服器外部的元件提供，即 TLS Termination Proxy。

而且必須有某個東西負責續期 HTTPS 憑證，可能是同一個元件，也可能是不同的東西。

### HTTPS 工具範例 { #example-tools-for-https }

你可以用來作為 TLS Termination Proxy 的工具包括：

- Traefik
    - 自動處理憑證續期 ✨
- Caddy
    - 自動處理憑證續期 ✨
- Nginx
    - 搭配像 Certbot 這類外部元件進行憑證續期
- HAProxy
    - 搭配像 Certbot 這類外部元件進行憑證續期
- Kubernetes，使用如 Nginx 的 Ingress Controller
    - 搭配像 cert-manager 這類外部元件進行憑證續期
- 由雲端供應商在其服務內部處理（見下文 👇）

另一個選項是使用能幫你做更多事情的雲端服務（包含設定 HTTPS）。它可能有一些限制或要額外付費等。但在那種情況下，你就不必自己設定 TLS Termination Proxy。

我會在後續章節展示一些具體例子。

---

接下來要考慮的概念都與實際執行你的 API 的程式（例如 Uvicorn）有關。

## 程式與行程 { #program-and-process }

我們會常提到執行中的「行程（process）」，因此先釐清它的意思，以及與「程式（program）」的差異很有幫助。

### 什麼是程式 { #what-is-a-program }

「程式（program）」一詞常用來描述許多東西：

- 你寫的原始碼，也就是 Python 檔案。
- 可由作業系統執行的檔案，例如：`python`、`python.exe` 或 `uvicorn`。
- 在作業系統上執行中的特定程式，使用 CPU 並將資料存於記憶體。這也稱為「行程」。

### 什麼是行程 { #what-is-a-process }

「行程（process）」通常以更特定的方式使用，只指作業系統中正在執行的東西（如上面最後一點）：

- 在作業系統上「執行中」的特定程式。
    - 這不是指檔案或原始碼，而是特指正在被作業系統執行並管理的那個東西。
- 任何程式、任何程式碼，只有在「被執行」時才能做事。所以，當有「行程在執行」時才能運作。
- 行程可以被你或作業系統終止（kill）。此時它就停止執行，無法再做任何事。
- 你電腦上執行的每個應用程式、每個視窗等，背後都有一些行程。而且在電腦開機時，通常會同時有許多行程在跑。
- 同一個程式可以同時有多個行程在執行。

如果你打開作業系統的「工作管理員」或「系統監控器」（或類似工具），就能看到許多正在執行的行程。

例如，你大概會看到同一個瀏覽器（Firefox、Chrome、Edge 等）會有多個行程在執行。它們通常每個分頁一個行程，外加其他一些額外行程。

<img class="shadow" src="/img/deployment/concepts/image01.png">

---

現在我們知道「行程」與「程式」的差異了，繼續談部署。

## 開機自動執行 { #running-on-startup }

多數情況下，當你建立一個 Web API，你會希望它「一直在執行」，不中斷，讓客戶端隨時可用。除非你有特定理由只在某些情況下才執行，但大部分時候你會希望它持續運作並且可用。

### 在遠端伺服器上 { #in-a-remote-server }

當你設定一台遠端伺服器（雲端伺服器、虛擬機等），最簡單的作法就是像本機開發時一樣，手動使用 `fastapi run`（它使用 Uvicorn）或類似的方式。

這在「開發期間」會運作良好而且有用。

但如果你與伺服器的連線中斷，正在執行的行程很可能會死掉。

而如果伺服器被重新啟動（例如更新後、或雲端供應商進行遷移），你大概「不會注意到」。因此你甚至不知道要手動重啟行程。你的 API 就會一直掛著。😱

### 開機自動啟動 { #run-automatically-on-startup }

通常你會希望伺服器程式（例如 Uvicorn）在伺服器開機時自動啟動，且不需任何「人工介入」，讓你的 API（例如 Uvicorn 執行你的 FastAPI 應用）總是有行程在跑。

### 獨立程式 { #separate-program }

為了達成這點，你通常會有一個「獨立的程式」來確保你的應用在開機時會被啟動。很多情況下，它也會確保其他元件或應用一併啟動，例如資料庫。

### 開機自動啟動的工具範例 { #example-tools-to-run-at-startup }

能做到這件事的工具包括：

- Docker
- Kubernetes
- Docker Compose
- Docker 的 Swarm 模式
- Systemd
- Supervisor
- 由雲端供應商在其服務內部處理
- 其他...

我會在後續章節給出更具體的例子。

## 重新啟動 { #restarts }

和確保你的應用在開機時會執行一樣，你大概也會希望在發生失敗之後，它能「自動重新啟動」。

### 人都會犯錯 { #we-make-mistakes }

我們身為人，常常會犯錯。軟體幾乎總是有藏在各處的「臭蟲（bugs）」🐛

而我們開發者會在發現這些 bug 後持續改進程式碼、實作新功能（也可能順便加進新的 bug 😅）。

### 小錯誤自動處理 { #small-errors-automatically-handled }

使用 FastAPI 建構 Web API 時，如果我們的程式碼出錯，FastAPI 通常會把它限制在觸發該錯誤的單次請求之中。🛡

用戶端會收到「500 Internal Server Error」，但應用會繼續處理之後的請求，而不是整個崩潰。

### 更嚴重的錯誤 - 當機 { #bigger-errors-crashes }

然而，仍可能有一些情況，我們寫的程式碼「讓整個應用當機」，使 Uvicorn 與 Python 都崩潰。💥

即便如此，你大概也不會希望應用因為某處錯誤就一直處於死亡狀態，你可能會希望它「繼續運作」，至少讓沒有壞掉的「路徑操作（path operations）」能持續服務。

### 當機後重新啟動 { #restart-after-crash }

在這些會讓「執行中行程」整個崩潰的嚴重錯誤案例裡，你會希望有個外部元件負責「重新啟動」該行程，至少嘗試幾次...

/// tip

...不過，如果整個應用「一啟動就立刻」崩潰，那持續無止境地重啟大概沒有意義。但在這類情況下，你很可能會在開發過程中就發現，或至少在部署後馬上注意到。

所以讓我們專注在主要情境：應用在未來某些特定案例中可能會整體崩潰，但此時重新啟動仍然是有意義的。

///

你大概會希望把負責重新啟動應用的東西放在「外部元件」，因為那個時候，應用本身連同 Uvicorn 與 Python 都已經掛了，在同一個應用的程式碼裡也無法做什麼。

### 自動重新啟動的工具範例 { #example-tools-to-restart-automatically }

多數情況下，用來「在開機時啟動程式」的同一套工具，也會負責處理自動「重新啟動」。

例如，可以由下列工具處理：

- Docker
- Kubernetes
- Docker Compose
- Docker 的 Swarm 模式
- Systemd
- Supervisor
- 由雲端供應商在其服務內部處理
- 其他...

## 複本：行程與記憶體 { #replication-processes-and-memory }

在 FastAPI 應用中，使用像 `fastapi` 指令（執行 Uvicorn）的伺服器程式，即使只在「一個行程」中執行，也能同時服務多個客戶端。

但很多情況下，你會想同時執行多個工作行程（workers）。

### 多個行程 - Workers { #multiple-processes-workers }

如果你的客戶端比單一行程所能處理的更多（例如虛擬機規格不大），而伺服器 CPU 有「多核心」，那你可以同時執行「多個行程」載入相同的應用，並把所有請求分散給它們。

當你執行同一個 API 程式的「多個行程」時，通常稱為「workers（工作行程）」。

### 工作行程與連接埠 { #worker-processes-and-ports }

還記得文件中[關於 HTTPS](https.md) 的說明嗎：在一台伺服器上，一組 IP 與連接埠的組合只能由「一個行程」監聽？

這裡同樣適用。

因此，若要同時擁有「多個行程」，就必須有「單一行程」在該連接埠上監聽，然後以某種方式把通信傳遞給各個工作行程。

### 每個行程的記憶體 { #memory-per-process }

當程式把東西載入記憶體時，例如把機器學習模型存到變數中，或把大型檔案內容讀到變數中，這些都會「消耗一些伺服器的記憶體（RAM）」。

而多個行程通常「不共享記憶體」。這表示每個執行中的行程都有自己的東西、變數與記憶體。如果你的程式碼會用掉大量記憶體，「每個行程」都會消耗等量的記憶體。

### 伺服器記憶體 { #server-memory }

例如，如果你的程式碼載入一個「1 GB 大小」的機器學習模型，當你用一個行程執行你的 API，它就會至少吃掉 1 GB 的 RAM。若你啟動「4 個行程」（4 個 workers），每個會吃 1 GB RAM。總計你的 API 會吃掉「4 GB RAM」。

如果你的遠端伺服器或虛擬機只有 3 GB RAM，嘗試載入超過 4 GB 的 RAM 就會出問題。🚨

### 多個行程 - 範例 { #multiple-processes-an-example }

在這個例子中，有一個「管理行程（Manager Process）」會啟動並控制兩個「工作行程（Worker Processes）」。

這個管理行程大概就是在 IP 的「連接埠」上監聽的那個。它會把所有通信轉發到各個工作行程。

那些工作行程才是實際執行你的應用的，它們會完成主要的計算，接收「請求」並回傳「回應」，也會把你放在變數中的東西載入 RAM。

<img src="/img/deployment/concepts/process-ram.drawio.svg">

當然，同一台機器上除了你的應用之外，通常也會有「其他行程」在執行。

有個有趣的細節是，每個行程的「CPU 使用率」百分比會隨時間大幅「變動」，但「記憶體（RAM）」通常維持相對「穩定」。

如果你的 API 每次做的計算量相近，且客戶很多，那「CPU 使用率」也可能「相對穩定」（而不是快速上下起伏）。

### 複本與擴展的工具與策略範例 { #examples-of-replication-tools-and-strategies }

要達成這些有很多種作法。我會在後續章節（例如談到 Docker 與容器時）介紹更具體的策略。

主要的限制是：必須有「單一」元件負責處理「公開 IP」上的「連接埠」。接著它必須能把通信「轉發」到被複製的「行程／workers」。

以下是一些可能的組合與策略：

- Uvicorn 搭配 `--workers`
    - 一個 Uvicorn「管理行程」會在「IP」與「連接埠」上監聽，並啟動「多個 Uvicorn 工作行程」。
- Kubernetes 與其他分散式「容器系統」
    - 由「Kubernetes」層在「IP」與「連接埠」上監聽。複本的方式是有「多個容器」，每個容器內執行「一個 Uvicorn 行程」。
- 由「雲端服務」替你處理
    - 雲端服務很可能「替你處理複本」。它可能讓你定義「要執行的行程」或「容器映像」，無論如何，多半會是「單一 Uvicorn 行程」，而由雲端服務負責進行複製。

/// tip

先別擔心這裡提到的「容器」、Docker 或 Kubernetes 如果現在還不太懂。

我會在未來的章節進一步說明容器映像、Docker、Kubernetes 等等：[容器中的 FastAPI - Docker](docker.md)。

///

## 啟動前的前置步驟 { #previous-steps-before-starting }

很多情況下，你會希望在應用「啟動之前」先執行一些步驟。

例如，你可能想先執行「資料庫遷移」。

但在多數情況下，你會希望這些步驟只執行「一次」。

所以，你會希望用「單一行程」來執行那些「前置步驟」，在啟動應用之前完成。

而且即使之後你要為應用本身啟動「多個行程」（多個 workers），你也必須確保只有一個行程在跑那些前置步驟。若由「多個行程」去跑，會在「平行」中重複同樣的工作；而如果那些步驟像是資料庫遷移這類敏感操作，它們之間可能會互相衝突。

當然，也有一些情況，重複執行前置步驟不會有問題；在那種情況下就容易處理得多。

/// tip

另外請記住，依照你的設定，在某些情況下你「甚至可能不需要任何前置步驟」才能啟動應用。

這種情況下，你就不用為此費心了。🤷

///

### 前置步驟策略範例 { #examples-of-previous-steps-strategies }

這會「高度取決於」你「部署系統」的方式，而且很可能與你如何啟動程式、處理重新啟動等有關。

以下是一些可能的做法：

- 在 Kubernetes 中使用一個「Init Container」，它會在你的應用容器之前先執行
- 一個 bash 腳本先跑前置步驟，然後再啟動你的應用
    - 你仍然需要有機制來啟動／重新啟動「那個」bash 腳本、偵測錯誤等

/// tip

我會在未來關於容器的章節提供更具體的範例：[容器中的 FastAPI - Docker](docker.md)。

///

## 資源使用率 { #resource-utilization }

你的伺服器（群）是可以被「消耗／利用」的「資源」，你的程式會使用 CPU 的計算時間，以及可用的 RAM 記憶體。

你想要消耗／利用多少系統資源？直覺上可能會想「不要太多」，但實際上，你大概會希望在「不當機」的前提下「盡可能用多一點」。

如果你花錢租了 3 台伺服器，卻只用了它們少量的 RAM 與 CPU，那你可能是在「浪費金錢」💸、也「浪費伺服器電力」🌎 等。

在那種情況下，可能更好的是只用 2 台伺服器，並以更高的比例使用它們的資源（CPU、記憶體、磁碟、網路頻寬等）。

另一方面，如果你有 2 台伺服器，且你使用了它們「100% 的 CPU 與 RAM」，某個時刻一個行程會要求更多記憶體，伺服器就得用磁碟當作「記憶體」（這可能慢上數千倍），甚至「當機」。或是某個行程需要做計算時，必須等到 CPU 再度空閒。

這種情況下，最好是再加一台伺服器，並在上面跑部分行程，讓所有行程都有「足夠的 RAM 與 CPU 時間」。

也有機會因為某些原因，你的 API 使用量出現「尖峰」。也許它爆紅了，或是其他服務或機器人開始使用它。在這些情況下，你可能會希望留有一些額外資源以策安全。

你可以設定一個「目標數字」，例如資源使用率落在「50% 到 90%」之間。重點是，這些大概就是你會想測量並用來調校部署的主要指標。

你可以用像 `htop` 這樣的簡單工具，查看伺服器的 CPU 與 RAM 使用量，或各行程的使用量。也可以用更複雜的監控工具，分散式地監看多台伺服器，等等。

## 重點回顧 { #recap }

這裡介紹了一些你在決定如何部署應用時應該記住的主要概念：

- 安全性 - HTTPS
- 開機自動執行
- 重新啟動
- 複本（執行中的行程數量）
- 記憶體
- 啟動前的前置步驟

理解這些想法與如何套用它們，應能給你足夠的直覺，在設定與調整部署時做出各種決策。🤓

在接下來的章節，我會提供更多可行策略的具體範例。🚀
