隨著雲端原生架構的興起,12-Factor 應用程式方法論已成為構建現代化應用程式的重要。本文將結合 Go 語言的特性和 Docker 容器化技術,探討如何實踐 12-Factor 的核心原則,並提供具體的程式碼範例和操作步驟。從程式碼函式倉管理、依賴關係處理、組態設定到日誌管理和程式模型,文章將逐步引導開發者構建更具彈性、可移植性和可擴充套件性的雲端原生應用。同時,也將探討如何利用 DigitalOcean 和 Gogs 等工具提升開發效率和程式碼管理能力,開發更完善的開發流程。
遵循 12-Factor 原則的 Docker 與 Go 應用程式開發
在現代軟體開發中,12-Factor 應用程式開發方法論已成為建構可擴充套件、易於維護的雲端原生應用的重要指導原則。結合 Docker 和 Go 語言的強大功能,開發者可以更輕鬆地實作這些原則,從而開發出色的雲端應用。
為何選擇 Docker 和 Go?
Docker 提供了一個輕量級的容器化平台,能夠確保應用程式在不同環境中的一致性和可移植性。Go 語言則以其簡潔、高效和並發處理能力著稱,非常適合用於建構現代化的雲端原生應用。
十二要素應用程式開發的核心原則
- 單一程式碼函式庫,多重佈署:使用版本控制系統管理單一程式碼函式庫,並透過不同的佈署流程來滿足多環境需求。
- 明確宣告依賴關係:透過 Go Modules 等工具明確管理專案依賴,避免環境差異帶來的問題。
- 將組態儲存在環境變數中:使用環境變數來儲存敏感資訊和組態引數,提高應用的靈活性和安全性。
- 將後端服務視為附加資源:將資料函式庫、快取等後端服務視為附加資源,並透過 URL 或其他機制進行連線。
- 嚴格區分建置、發布和執行階段:利用 Docker 和 CI/CD 工具實作自動化的建置、測試和佈署流程。
- 以一個或多個無狀態程式執行應用:設計無狀態的應用程式,並透過外部服務儲存狀態資訊。
- 透過埠繫結提供服務:使應用程式能夠透過埠繫結來提供服務,方便進行負載平衡和擴充套件。
- 透過程式模型進行擴充套件:利用 Go 語言的並發特性,實作高效的程式級擴充套件。
- 最大化健壯性,實作快速啟動和優雅關閉:設計應用程式能夠快速啟動,並在關閉時進行必要的清理工作。
- 保持開發、預發和生產環境的一致性:利用 Docker 容器化技術,確保不同環境之間的高度一致性。
- 將日誌視為事件流:設計應用程式將日誌輸出為事件流,方便進行日誌收集和分析。
- 將管理任務作為一次性程式執行:將管理任務,如資料函式庫遷移等,設計為一次性程式,方便執行和維護。
實作範例
使用 Docker 建置 Go 應用程式
# 使用官方的 Go 映像作為建置環境
FROM golang:alpine AS builder
# 設定工作目錄
WORKDIR /app
# 複製 Go Modules 檔案
COPY go.mod go.sum ./
# 下載 Go Modules 依賴
RUN go mod download
# 複製應用程式原始碼
COPY . .
# 建置 Go 應用程式
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o main .
# 使用精簡的 Alpine 映像作為執行環境
FROM alpine:latest
# 設定工作目錄
WORKDIR /root/
# 從建置環境中複製編譯好的二進位制檔案
COPY --from=builder /app/main .
# 暴露應用程式埠
EXPOSE 8080
# 執行應用程式
CMD ["./main"]
內容解密:
FROM golang:alpine AS builder:使用官方的 Go 語言映像作為建置階段的基礎映像,並命名為builder。WORKDIR /app:在容器中設定工作目錄為/app。COPY go.mod go.sum ./:將go.mod和go.sum複製到容器的工作目錄中,用於管理 Go Modules 依賴。RUN go mod download:下載並安裝 Go Modules 中定義的依賴套件。COPY . .:將當前目錄下的所有檔案複製到容器的工作目錄中。RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o main .:編譯 Go 應用程式,產生名為main的可執行檔。FROM alpine:latest:使用精簡的 Alpine Linux 映像作為執行階段的基礎映像,以減少映像大小。COPY --from=builder /app/main .:從建置階段的容器中複製編譯好的main可執行檔到當前容器的工作目錄。EXPOSE 8080:宣告容器需要暴露的埠,這裡是 8080 埠,用於存取應用程式。CMD ["./main"]:設定容器啟動時預設執行的命令,即執行編譯好的main可執行檔。
介紹與背景
身為一位擁有近二十年程式設計經驗的開發者,我經歷了從90年代的程式碼最佳化到2000年代接觸PHP,並陸續探索了Java、Node.js,乃至於Go等多種程式語言家族的旅程。在這一路走來,我不僅為自己的內容管理產品開發了多個API,還曾長官開發多個大型專案,並將其成功推向市場,服務於多個國家。其中,我為斯洛維尼亞國家電視台及廣播電台(RTV Slovenia)開發了一套專業的API框架,該框架同時兼具軟體開發工具包的功能。此外,我也曾在多個本地PHP使用者群組活動及會議上擔任演講者,並撰寫了《API Foundations in Go》一書。我的技術部落格位於scene-si.org,每月定期發布新文章,分享我的技術心得與經驗。
本文讀者
本文主要針對所有以開發應用程式為職業的開發者。書中涵蓋了軟體開發領域的多個主題,旨在建立良好的開發實踐,並參考了《12 Factor App宣言》。透過本文,讀者將能夠深入瞭解《12 Factor App宣言》所提出的設計理念及其試圖解決的問題。
涵蓋主題
- I. Codebase:一個程式碼函式庫對應多個佈署環境
- II. Dependencies:明確宣告並隔離依賴項
- III. Config:在環境中儲存組態
- IV. Backing services:將後端服務視為附加資源
- V. Build, release, run:嚴格區分建置、發布和執行階段
- VI. Processes:以一個或多個無狀態程式執行應用程式
- VII. Port binding:透過埠繫結匯出服務
- VIII. Concurrency:透過程式模型進行擴充套件
- IX. Disposability:透過快速啟動和優雅關閉最大化健壯性
- X. Dev/prod parity:保持開發、預發布和生產環境盡可能相似
- XI. Logs:將日誌視為事件流
- XII. Admin processes:將管理任務作為一次性程式執行
如何學習本文
在本文中,我將透過多個範例來說明在開發API時常見的操作。這些範例已發布在GitHub上。讀者可以跟隨書中的範例進行學習,也可以單獨閱讀每個章節,以掌握該章節的知識。雖然每個範例都是獨立的,但它們通常建立在前一章節的基礎上。因此,在學習過程中,請務必遵循「需求」一節的指示。
需求
本文以開發和佈署微服務為案例,示範了12因子應用程式的開發。因此,在閱讀本文的範例時,需要滿足一些先決條件。
Linux與Docker
本文的範例依賴於Linux主機上的最新docker-engine安裝。
自己的硬體
如果您擁有自己的硬體,建議組態如下:
- 2個CPU核心
- 2GB RAM
- 128GB磁碟(SSD)
雲端快速入門
如果您不想使用自己的硬體,可以在Digital Ocean上快速建立虛擬伺服器。透過此推薦連結,您將獲得10美元的信用額度,同時也能幫助我減少一些主機費用。
建立Digital Ocean虛擬伺服器的步驟如下:
- 點選頁面頂部的「Create Droplet」綠色按鈕。
- 在開啟的頁面中,導航至「One-click apps」,並選擇「Docker」。
- 選擇合理的磁碟大小(至少30GB)。
- 點選頁面底部的「Create」綠色按鈕,完成設定。
使用API建立Digital Ocean虛擬伺服器
Digital Ocean提供了CLI工具doctl,這是一個Go語言編寫的程式,可以與其API介面互動,允許您列出正在執行的虛擬伺服器、建立新的虛擬伺服器、關閉它們等。如果您不需要24/7執行的例項,可以使用API根據您的需求啟動和關閉虛擬伺服器。
程式碼解析
// 以下是一個簡單的Go語言範例,用於展示如何使用doctl工具。
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
// 執行doctl命令以列出所有虛擬伺服器
cmd := exec.Command("doctl", "compute", "droplet", "list")
output, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Println(string(output))
}
內容解密:
此範例展示瞭如何使用Go語言執行外部命令(在此案例中為doctl),並取得其輸出。首先,我們匯入必要的套件,包括fmt、log和os/exec。在main函式中,我們使用exec.Command建立了一個新的命令物件,該命令用於執行doctl compute droplet list,即列出所有Digital Ocean虛擬伺服器的命令。然後,我們呼叫cmd.CombinedOutput()來執行該命令並取得其輸出。如果命令執行過程中出現錯誤,我們將記錄該錯誤並離開程式。最後,我們將命令的輸出以字串形式列印到控制檯。
重點回顧
本文旨在幫助讀者深入瞭解12因子應用程式的設計理念和最佳實踐。透過對各個主題的詳細介紹和實際範例的演示,讀者將能夠掌握如何開發和佈署符合12因子原則的微服務。同時,本文也提供了在不同環境下(如Linux和雲端)佈署和執行微服務的和建議。
使用 DigitalOcean 進行 Docker 開發機器的自動化佈署
在進行 Docker 開發時,使用高效的開發機器可以提升工作效率。本文介紹如何使用 DigitalOcean 的 API 自動化佈署 Docker 開發機器,以節省成本並提高開發效率。
步驟一:安裝 doctl 並進行身份驗證
首先,需要下載並安裝 doctl,這是 DigitalOcean 提供的命令列工具。安裝完成後,透過 doctl auth init 命令進行身份驗證。
程式碼範例:身份驗證
# doctl auth init
DigitalOcean access token: 24df63816084cb8270fbe03a7c98341a
Validating token: OK
內容解密:
doctl auth init:初始化doctl的身份驗證。- 輸入 DigitalOcean 的存取令牌(access token),該令牌可以在 DigitalOcean 的控制台中生成。
Validating token: OK表示身份驗證成功。
步驟二:建立和銷毀 Droplet
使用 doctl 可以輕鬆地建立和銷毀 Droplet。以下是建立和銷毀 Droplet 的指令碼示例。
程式碼範例:建立 Droplet(create.sh)
#!/bin/bash
CHECK=$(doctl compute droplet list asx --format ID --no-header | wc -l)
if [ "$CHECK" == "0" ]; then
echo "Creating ASX droplet"
doctl compute droplet create asx -v \
--image docker-16-04 \
--size 2gb \
--region ams3 \
--ssh-keys $(./ssh-key.sh)
else
echo "Droplet ASX already running"
fi
內容解密:
- 檢查名為
asx的 Droplet 是否存在,如果不存在則建立。 - 使用
docker-16-04映象建立 Droplet,大小為 2GB,位於ams3區域。 --ssh-keys引數指定了 SSH 金鑰,可以透過./ssh-key.sh指令碼取得。
程式碼範例:銷毀 Droplet(destroy.sh)
#!/bin/bash
CHECK=$(doctl compute droplet list asx --format ID --no-header | wc -l)
if [ "$CHECK" == "0" ]; then
echo "Droplet ASX not started"
else
echo "Deleting droplet asx"
doctl compute droplet delete asx -f -v
fi
內容解密:
- 檢查名為
asx的 Droplet 是否存在,如果存在則刪除。 - 使用
-f引數強制刪除 Droplet。
連線 Droplet
建立 Droplet 後,可以透過 SSH 連線到它。
程式碼範例:連線 Droplet
ssh $(doctl compute droplet list asx --format PublicIPv4 --no-header)
內容解密:
- 使用
doctl compute droplet list命令取得 Droplet 的公有 IP 地址。 - 透過 SSH 連線到該 IP 地址。
Docker 網路組態
在進行微服務開發時,通常需要在不同的服務之間建立通訊。為此,可以建立一個名為 party 的 Docker 網路。
程式碼範例:建立 Docker 網路
#!/bin/bash
docker network create -d bridge --subnet 172.25.0.0/24 party
內容解密:
- 使用
docker network create命令建立一個名為party的橋接網路。 - 指定子網為
172.25.0.0/24。
MySQL 資料函式庫佈署
可以使用 Docker 快速佈署一個 MySQL 資料函式庫例項。
程式碼範例:佈署 MySQL
#!/bin/bash
NAME="mysql"
APP_DIR=$(dirname $(readlink -f $0))
mkdir -p ${APP_DIR}/data/$NAME
docker stop $NAME
docker rm $NAME
DOCKERFILE="titpetric/percona-xtrabackup"
docker run --restart=always \
-h $NAME \
--name $NAME \
--net=party \
-v ${APP_DIR}/data/$NAME/data:/var/lib/mysql \
-e MYSQL_ALLOW_EMPTY_PASSWORD="yes" \
-d $DOCKERFILE
內容解密:
- 停止並刪除現有的 MySQL 容器(如果存在)。
- 使用
titpetric/percona-xtrabackup映象建立一個新的 MySQL 容器。 - 將容器加入到
party網路中,並對映資料卷。
原始碼版本控制
使用 Git 等版本控制系統可以有效地管理原始碼。本文推薦使用 GitHub 或 Bitbucket 等服務來託管程式碼倉函式庫。
使用Gogs實作零成本自託管Git服務
在開發過程中,版本控制系統是不可或缺的一部分。目前,GitHub和Bitbucket是最流行的選擇,但它們可能無法滿足所有需求,例如對程式碼的完全控制或避免速率限制等問題。Gogs是一個優秀的零成本自託管Git解決方案,能夠提供與GitHub和Bitbucket相似的功能,並且可以輕鬆遷移到其他平台。
為什麼選擇Gogs
雖然GitHub和Bitbucket提供了豐富的功能和便利性,但自託管Git儲存函式庫可以提供更高的安全性和控制性。Gogs是用Go語言編寫的,具有跨平台的優勢,並且支援多種資料函式庫後端,包括MySQL和SQLite。
Gogs的優點
- 零成本:Gogs是一個開源專案,無需支付額外費用。
- 自託管:將Git儲存函式庫託管在自己的伺服器上,確保程式碼的安全性和控制性。
- 易於遷移:可以輕鬆將Gogs遷移到其他Git平台,如GitLab、GitHub或Bitbucket。
安裝和執行Gogs
Gogs提供了多種安裝方式,包括二進位制檔案和Docker映像檔。為了方便管理和維護,本文將使用Docker來執行Gogs。
使用Docker執行Gogs
#!/bin/bash
NAME="gogs"
APP_DIR=$(dirname $(readlink -f $0))
mkdir -p ${APP_DIR}/data/$NAME
docker stop $NAME
docker rm $NAME
docker run -d --net=party --restart=always --name=$NAME -h $NAME -p 10022:22 -p 3000:3000 -v ${APP_DIR}/data/$NAME:/data gogs/gogs
內容解密:
NAME="gogs":定義容器名稱為"gogs"。APP_DIR=$(dirname $(readlink -f $0)):取得當前指令碼所在的目錄路徑。mkdir -p ${APP_DIR}/data/$NAME:建立資料儲存目錄。docker stop $NAME和docker rm $NAME:停止並刪除現有的Gogs容器(如果存在)。docker run:執行新的Gogs容器,並對映埠10022(SSH)和3000(HTTP)。--restart=always:設定容器在離開時自動重啟。-v ${APP_DIR}/data/$NAME:/data:將主機上的資料目錄掛載到容器內的/data目錄,實作資料持久化。
執行此指令碼後,Gogs將在Docker容器中執行,並可透過http://yourserver:3000/存取。
組態Gogs
首次存取Gogs時,將進入安裝頁面。可以選擇使用MySQL或SQLite作為資料函式庫後端。如果使用MySQL,需要先建立一個名為"gogs"的資料函式庫。
docker exec mysql mysqladmin create gogs
內容解密:
docker exec mysql mysqladmin create gogs:在MySQL容器中執行命令,建立名為"gogs"的資料函式庫。
登入和建立倉函式庫
安裝完成後,第一個註冊的使用者將成為管理員。可以建立新的倉函式庫,並按照Gogs提供的指示初始化倉函式庫。
mkdir the12factors
cd the12factors
touch README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin http://peer.lan:3000/titpetric/the12factors.git
git push -u origin master
內容解密:
mkdir the12factors和cd the12factors:建立並進入新倉函式庫的目錄。touch README.md:建立一個新的README檔案。git init:初始化Git儲存函式庫。git add README.md和git commit -m "first commit":將README檔案新增到倉函式庫並提交。git remote add origin http://peer.lan:3000/titpetric/the12factors.git:將本地倉函式庫與Gogs上的遠端倉函式庫關聯。git push -u origin master:將本地變更推播到遠端倉函式庫。
備份Gogs
如果使用SQLite作為資料函式庫後端,備份Gogs非常簡單。只需停止Gogs容器,封裝資料目錄,然後重新啟動容器即可。
#!/bin/bash
NAME="gogs"
docker stop $NAME
cd data && tar -zcvf gogs-backup-$(date +"%Y%m%d").tgz gogs/ && cd ..
docker start $NAME
內容解密:
docker stop $NAME:停止Gogs容器,確保資料已寫入磁碟。cd data && tar -zcvf gogs-backup-$(date +"%Y%m%d").tgz gogs/ && cd ..:封裝Gogs的資料目錄,建立備份檔案。docker start $NAME:重新啟動Gogs容器。
此備份指令碼可以定期執行,以確保Gogs資料的安全。如果使用MySQL作為資料函式庫後端,也需要備份MySQL資料函式庫,以確保完整性。