在現代軟體開發流程中,容器化技術已成為不可或缺的一環。本文將探討如何利用 Docker 容器化 Rust Actix Web 服務,從而提升開發效率、簡化佈署流程並確保應用程式在不同環境中的一致性。首先,我們會建立一個基礎的 Docker 映像,然後逐步最佳化,最後使用 Docker Compose 管理多容器應用。
Rust 的高效能和 Actix Web 框架的靈活性使其成為構建 Web 服務的絕佳選擇。然而,將 Rust Web 服務佈署到生產環境時,確保一致性和可靠性至關重要。Docker 提供了輕量級、可移植的解決方案,將應用程式及其依賴項封裝到容器中,從而解決開發環境和生產環境之間的差異問題。透過 Docker,我們可以輕鬆地構建、釋出和執行 Rust Web 服務,並確保其在不同環境中的行為一致。
12 佈署 Web 服務使用 Docker
本章涵蓋以下主題:
- 簡介 Rust 伺服器和應用的生產佈署
- 編寫第一個 Docker 容器
- 建立資料函式庫容器
- 使用 Docker 封裝 Web 服務
- 使用 Docker Compose 協調 Docker 容器
在前面的章節中,我們學習瞭如何使用 Rust 建立 Web 服務和 Web 應用。我們還探討了非同步程式設計,甚至涉及了 P2P 架構。我們在本地開發環境中測試了我們的應用程式。但這些只是第一步,最終目標通常是佈署到生產環境。
在本章中,我們將重點關注使用一種流行的生產佈署方法——容器化來封裝軟體。這涉及將應用程式的元件及其依賴項封裝在容器中。然後,這個容器可以佈署在多個環境中,包括雲端。使用容器的優點之一是,應用程式與其他容器保持乾淨的隔離,避免了不相容函式庫的風險。
我們將詳細介紹將 Rust Web 服務容器化所需的步驟。一旦 Web 服務以 Docker 容器的形式提供,從生產佈署的角度來看,它與任何其他語言編寫的 Web 服務或應用程式沒有區別。所有佈署 Docker 容器的標準和選項都將適用。
生產佈署簡介
在這一節中,我們將介紹兩個主題,它們將概述生產佈署在軟體生命週期中的位置,以及 Docker 作為佈署的容器技術的作用。
軟體佈署週期
軟體佈署週期涉及多個級別的開發者單元和整合測試,然後是釋出版本的準備和佈署。一旦釋出版本佈署並執行,系統就會被監控,關鍵引數被測量,並進行最佳化。
雖然生產佈署生命週期中的具體步驟因團隊和 DevOps 技術而異,但圖 12.1 顯示了一組典型的步驟。
圖 12.1 生產佈署生命週期
不同的組織使用的實際開發步驟和術語差異很大,但讓我們來看看這些階段,以大致瞭解:
詳細步驟說明
軟體佈署是一個複雜的過程,涉及多個階段,從開發到最終的生產佈署。每個階段都有其特定的目標和挑戰。以下是軟體佈署生命週期的詳細步驟:
開發與單元測試:開發人員編寫程式碼並進行單元測試,以確保個別元件的功能正確。
整合測試:將不同模組或元件整合在一起,並進行測試以確保它們能正確協作。
構建與封裝:將程式碼編譯或構建成可執行檔案或佈署包,並將其封裝以便於分發。
測試環境佈署:將封裝好的應用程式佈署到測試環境中,進行進一步的測試,如功能測試、效能測試等。
自動化測試:執行自動化測試套件,以驗證應用程式的功能、效能和安全性。
預生產環境佈署:將應用程式佈署到預生產環境(或稱為 staging 環境),進行最後的驗證。
生產環境佈署:一旦所有測試透過並驗證完成,將應用程式佈署到生產環境中。
監控與維護:在生產環境中監控應用程式的效能、錯誤日誌等,並根據需要進行維護更新。
這個過程可能根據專案需求、團隊規模和所採用的 DevOps 工具有所不同,但整體流程提供了一個基本的框架,用於管理和最佳化軟體的佈署。
使用 Docker 封裝 Web 服務
Docker 提供了一個輕量級且便攜的方式來封裝應用程式及其依賴項。使用 Docker,我們可以建立一個包含應用程式執行所需的一切的容器,包括程式碼、執行時、函式庫等。這使得應用程式能夠在不同的環境中一致地執行。
建立 Docker 容器
要使用 Docker 封裝我們的 Rust Web 服務,我們首先需要建立一個 Dockerfile。Dockerfile 是一個文字檔案,包含了用於構建 Docker 映像的所有命令。
# 使用官方 Rust 映像作為基礎
FROM rust:latest
# 設定工作目錄
WORKDIR /app
# 複製 Cargo.toml 和 Cargo.lock
COPY Cargo.toml Cargo.lock ./
# 複製原始碼
COPY src ./src
# 編譯 Rust 程式
RUN cargo build --release
# 暴露埠號
EXPOSE 8080
# 執行應用程式
CMD ["cargo", "run", "--release"]
建置與執行 Docker 容器
一旦我們有了 Dockerfile,我們就可以使用以下命令來構建和執行我們的 Docker 容器:
# 建置 Docker 映像
docker build -t my-rust-app .
# 執行 Docker 容器
docker run -p 8080:8080 my-rust-app
這將啟動一個包含我們 Rust Web 服務的 Docker 容器,並將主機的 8080 連線埠對映到容器的 8080 連線埠。
使用 Docker Compose 協調多個容器
對於涉及多個服務(如資料函式庫和 Web 服務)的更複雜應用程式,我們可以使用 Docker Compose。Docker Compose 是一個用於定義和執行多容器 Docker 應用的工具。
docker-compose.yml 範例
version: '3'
services:
web:
build: .
ports:
- "8080:8080"
depends_on:
- db
environment:
- DATABASE_URL=postgres://user:password@db:5432/database
db:
image: postgres
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=database
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
這個 docker-compose.yml 檔案定義了兩個服務:web 和 db。web 服務是我們的 Rust Web 服務,而 db 服務是一個 PostgreSQL 資料函式庫。web 服務依賴於 db 服務,這意味著 db 服務必須在 web 服務啟動之前啟動。
使用 Docker Compose 管理多個容器
要使用 Docker Compose 建置和啟動所有服務,只需執行:
docker-compose up --build
這個命令會根據需要建置映像並啟動容器。它還會設定必要的網路,以便服務之間可以相互通訊。
使用Docker容器化技術佈署Rust網路服務
在軟體開發的生命週期中,從編寫程式碼到佈署至生產環境,需要經過多個階段,包括本地建置、開發測試、整合測試、封裝發行、組態與佈署、安全設定以及執行監控。在本章節中,我們將重點關注如何使用Docker容器化技術來封裝和佈署Rust網路服務。
軟體開發與佈署流程
- 建置(Build):開發人員在本地環境中編寫或修改程式碼,並建置二進位檔案。通常,這是一個開發建置,以便於除錯和減少建置時間。
- 開發測試(Dev test):開發人員在本地開發環境中執行單元測試。
- 預發布(Staging):程式碼與其他分支合併,並佈署到預發布環境中。在此階段,進行涉及其他開發人員編寫的程式碼和模組的整合測試。
- 封裝發行(Package for release):在成功完成整合測試後,建構最終的生產建置。封裝方式取決於二進位檔案的佈署方式,例如獨立二進位檔案、容器或公有雲端服務。
- 組態與佈署(Configure and deploy):生產二進位檔案被佈署到目標環境(例如虛擬機器),並設定必要的組態和環境引數。這也是與生產基礎設施中的其他元件建立連線的階段。
- 安全設定(Secure):組態額外的安全需求,例如身份驗證、授權、網路和伺服器安全等。
- 執行與監控(Operate and monitor):啟動伺服器以接收網路請求,並使用網路、伺服器、應用程式和雲端監控工具監控伺服器的效能。
在本章節中,我們將使用Docker Compose來簡化組態、自動化建置以及啟動和停止執行網路服務所需的Docker容器集合。
你將學到的內容
- 建置發行二進位檔案和封裝:學習如何將Rust伺服器建置為Docker映像檔,以便佈署到任何具有容器執行時的宿主機上。你將學習如何編寫Dockerfile、建立Docker卷和網路、組態環境變數、執行多步驟Docker建置,以及減小最終Docker映像檔的大小。
- 組態和佈署網路服務:學習如何使用Docker Compose定義網路服務和Postgres資料函式庫容器的執行時組態,定義它們之間的依賴關係,組態執行時環境變數,初始化Docker建置,以及透過簡單命令啟動和停止Docker容器。
讓我們從簡要介紹Docker開始。
Docker容器基礎
容器技術改變了軟體的建置、佈署和管理方式,透過彌合開發和IT維運團隊之間的差距實作了DevOps自動化。Docker既是公司的名稱,也是軟體產品的名稱(www.docker.com)。
Docker容器架構
圖表翻譯: 上圖展示了Docker容器的分層架構。硬體層之上是作業系統,作業系統之上執行Docker引擎,Docker引擎之下可以執行多個獨立的容器,每個容器內包含一個或多個應用程式。
Docker容器是完全隔離的環境,具有自己的行程、網路介面和卷掛載。Docker容器的一個重要特點是它們最終分享相同的作業系統核心。傳統的虛擬機器是物理硬體的抽象,將一台物理伺服器轉變為多台“邏輯”伺服器。虛擬機器管理程式允許多台虛擬機器在單台機器上執行,每台虛擬機器都包含作業系統的完整副本。另一方面,容器是應用層的抽象,將程式碼和依賴項封裝在一起。多個容器在同一台物理機器上執行,並與其他容器分享OS核心。
程式碼範例:編寫Dockerfile
# 使用官方Rust映像檔作為基礎映像檔
FROM rust:alpine
# 設定工作目錄
WORKDIR /app
# 複製Cargo.toml和Cargo.lock
COPY Cargo.toml Cargo.lock ./
# 建置依賴項
RUN cargo build --release
# 複製原始碼
COPY . .
# 建置應用程式
RUN cargo build --release
# 暴露埠號
EXPOSE 8080
# 執行應用程式
CMD ["cargo", "run", "--release"]
內容解密:
FROM rust:alpine:使用官方Rust映像檔作為基礎映像檔,這裡選擇了根據Alpine Linux的版本,以減小映像檔大小。WORKDIR /app:設定工作目錄為/app。COPY Cargo.toml Cargo.lock ./:將Cargo.toml和Cargo.lock複製到工作目錄,用於建置依賴項。RUN cargo build --release:建置依賴項,這裡使用--release旗標進行最佳化建置。COPY . .:將當前目錄下的所有檔案複製到工作目錄。RUN cargo build --release:再次建置應用程式,這次包含了原始碼。EXPOSE 8080:暴露8080埠號,供外部存取。CMD ["cargo", "run", "--release"]:設定預設命令,用於執行應用程式。
透過本章節,你將能夠使用Docker容器化技術來佈署Rust網路服務,提高開發效率和佈署靈活性。
使用Docker容器化Rust Web服務
在軟體開發過程中,開發環境與生產環境之間的差異常常導致佈署問題。Docker容器技術有效地解決了這一問題。開發者可以在Dockerfile中定義基礎設施組態、環境設定、依賴下載和建置指令。Dockerfile是一種YAML語法的文字檔,用於指定Docker映像的引數,如基礎映像、環境變數、檔案系統掛載、暴露的連線埠等。
Docker容器技術的優勢
Dockerfile被建置成自訂的Docker映像,這種映像是建立多個容器執行的範本。開發者可以在本地測試Docker映像,然後將其交付給維運團隊進行生產佈署。由於Docker映像保證在任何Docker主機上執行的一致性,因此大大減少了軟體應用在生產環境中的佈署摩擦和人為錯誤。
安裝Docker開發環境
要在本機開發機器或伺服器(macOS、Windows或Linux)上安裝Docker開發環境,請參考Docker官方檔案:https://docs.docker.com/get-docker/。
編寫第一個Docker容器
本文將檢查Docker安裝、編寫Dockerfile並將其建置成Docker映像,以及使用多階段建置最佳化最終Docker映像的大小。
檢查Docker安裝
首先,從終端機檢查Docker安裝:
docker --version
預期輸出類別似於:
Docker version 20.10.16, build aa7e414
接下來,測試官方Docker映像:
docker pull hello-world
docker images
docker run hello-world
如果看到「Hello from Docker!」訊息,則表示Docker環境正常運作。
編寫簡單的Docker容器
建立一個新的Rust專案:
cargo new --bin docker-rust
cd docker-rust
在Cargo.toml中新增Actix Web依賴:
[dependencies]
actix-web = "4.2.1"
編輯src/main.rs:
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn gm() -> impl Responder {
HttpResponse::Ok().body("Hello, Good morning!")
}
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello there!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(gm)
.route("/hello", web::get().to(hello))
})
.bind(("0.0.0.0", 8080))?
.run()
.await
}
執行cargo run並測試:
localhost:8080localhost:8080/hello
容器化Web服務
建立Dockerfile-basic檔案:
# 使用主要的Rust Docker映像
FROM rust
# 將應用程式複製到Docker映像中
COPY . /app
# 設定工作目錄
WORKDIR /app
# 建置應用程式
RUN cargo build --release
# 啟動應用程式
CMD ["./target/release/docker-rust"]
建置Docker映像:
docker build -f Dockerfile-basic . -t docker-rust-basic
內容解密:
FROM rust:使用官方Rust映像作為基礎映像。COPY . /app:將目前目錄下的所有檔案複製到容器中的/app目錄。WORKDIR /app:將工作目錄設定為/app。RUN cargo build --release:在容器中執行cargo build --release指令建置Rust應用程式。CMD ["./target/release/docker-rust"]:設定容器啟動時執行的預設指令。
本章節展示瞭如何使用Docker容器化Rust Web服務,從而簡化了開發和佈署流程。接下來,我們將進一步最佳化Docker映像的大小。
使用多階段Docker建置最佳化Rust Actix Web服務
在前面的章節中,我們已經成功地將一個基本的Rust Actix Web服務容器化,但是生成的Docker映像大小達到1.32 GB,這對於一個簡單的Web服務來說太大了。本文將介紹如何使用多階段Docker建置來最佳化Docker映像的大小。
多階段Docker建置
多階段Docker建置是一種將Docker映像建置過程分成多個階段的方法,每個階段可以使用不同的基礎映像。這種方法的優點是可以減少最終Docker映像的大小,提高安全性,並且可以自動化建立多個版本的二進位制檔案。
建立新的Dockerfile
首先,在專案根目錄下建立一個新的Dockerfile,名為Dockerfile-lite,並新增以下內容:
# 使用主要的Rust Docker映像
FROM rust as build
# 將應用程式複製到Docker映像中
COPY . /app
# 設定工作目錄
WORKDIR /app
# 建置應用程式
RUN cargo build --release
# 使用Google Distroless作為執行階段映像
FROM gcr.io/distroless/cc-debian11
# 從建置階段複製應用程式
COPY --from=build /app/target/release/docker-rust /app/docker-rust
WORKDIR /app
# 啟動應用程式
CMD ["./docker-rust"]
建置Docker映像
執行以下命令來建置Docker映像:
docker build -f Dockerfile-lite . -t docker-rust-lite
檢查Docker映像
執行以下命令來檢查建置好的Docker映像:
docker images
你應該會看到類別似以下的輸出:
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-rust-lite latest 40103591baaf 12 seconds ago 31.8MB
執行Docker映像
執行以下命令來執行Docker映像:
docker run -p 8080:8080 -t docker-rust-lite
測試Web服務
開啟瀏覽器,測試以下URL:
localhost:8080localhost:8080/hello
你應該會看到相應的問候訊息。
多階段Docker建置的原理
多階段Docker建置使用多個FROM陳述式來參考特定階段的映像。每個階段可以使用AS關鍵字命名。在Dockerfile-lite範例中,我們有兩個階段。第一個階段建置了一個釋出的二進位制檔案。第二個階段使用Google Distroless作為執行階段映像,並複製了之前建立的釋出二進位制檔案,從而產生了一個較小的Docker映像。
圖表說明:多階段Docker建置流程
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title Rust Web 服務 Docker 容器化佈署
package "Docker 架構" {
actor "開發者" as dev
package "Docker Engine" {
component [Docker Daemon] as daemon
component [Docker CLI] as cli
component [REST API] as api
}
package "容器運行時" {
component [containerd] as containerd
component [runc] as runc
}
package "儲存" {
database [Images] as images
database [Volumes] as volumes
database [Networks] as networks
}
cloud "Registry" as registry
}
dev --> cli : 命令操作
cli --> api : API 呼叫
api --> daemon : 處理請求
daemon --> containerd : 容器管理
containerd --> runc : 執行容器
daemon --> images : 映像檔管理
daemon --> registry : 拉取/推送
daemon --> volumes : 資料持久化
daemon --> networks : 網路配置
@enduml
圖表翻譯: 此圖示呈現了多階段Docker建置的流程。第一階段負責建置應用程式,第二階段則負責建立最終的執行環境並複製必要的二進位制檔案。