隨著雲原生時代的到來,建構符合 12 Factor 原則的應用程式已成為確保應用程式可擴充套件性、可維護性和可移植性的重要手段。本文將結合 Docker 的容器化能力和 Go 語言的效能優勢,探討如何實踐 12 Factor 的各個導向,包含程式碼函式倉管理、依賴管理、外部組態、後端服務、建置發布與執行、程式管理、連線埠繫結、平行處理、可棄性、開發與生產環境一致性、日誌處理以及管理程式。透過實際的程式碼片段和操作步驟,演示如何將這些原則應用於實際的開發流程中,協助開發者構建更具彈性且易於管理的雲原生應用程式。

建構符合 12 Factor 原則的 Docker 與 Go 應用程式

在現代軟體開發中,12 Factor 應用程式已成為設計和佈署可擴充套件、健壯且易於維護的應用程式的重要指導原則。本文將探討如何使用 Docker 和 Go 語言來建立符合 12 Factor 原則的應用程式。

為什麼選擇 Docker 和 Go?

Docker 提供了一個輕量級且可移植的容器化平台,使得應用程式的佈署和管理變得更加簡單和高效。Go 語言則以其簡潔、高效和併發特性而聞名,非常適合構建現代化的雲原生應用程式。

12 Factor 原則概述

12 Factor 原則是一套指導開發者構建現代化應用程式的最佳實踐,包括:

  1. Codebase:一個程式碼函式庫對應多個佈署環境
  2. Dependencies:明確宣告和隔離依賴
  3. Config:將組態儲存在環境變數中
  4. Backing services:將後端服務視為附加資源
  5. Build, release, run:嚴格區分構建、發布和執行階段
  6. Processes:將應用程式作為一個或多個無狀態程式執行
  7. Port binding:透過埠繫結來暴露服務
  8. Concurrency:透過程式模型來擴充套件
  9. Disposability:最大化健壯性,透過快速啟動和優雅關閉
  10. Dev/prod parity:保持開發、測試和生產環境的一致性
  11. Logs:將日誌視為事件流
  12. Admin processes:將管理任務作為一次性程式執行

使用 Docker 和 Go 實作 12 Factor 原則

1. Codebase

使用 Gogs 建立一個 Git 倉函式庫來管理我們的程式碼函式庫。

docker run -d --name gogs -p 10080:3000 gogs/gogs

2. Dependencies

使用 Go Modules 來管理依賴。

go mod init myapp
go mod tidy

3. Config

使用環境變數來儲存組態。

import (
	"log"
	"os"
)

func main() {
	dbURL := os.Getenv("DATABASE_URL")
	if dbURL == "" {
		log.Fatal("DATABASE_URL is not set")
	}
	// ...
}

#### 內容解密:

這段程式碼展示瞭如何使用 os.Getenv 函式從環境變數中讀取 DATABASE_URL 的值。如果該變數未設定,程式將輸出錯誤訊息並終止。這種方式使得組態可以在不同的環境中靈活變更,而無需修改程式碼。

4. Backing services

將資料函式庫視為後端服務。

version: '3'
services:
  db:
    image: postgres
    environment:
      - POSTGRES_USER=myuser
      - POSTGRES_PASSWORD=mypassword

#### 內容解密:

這段 Docker Compose 組態檔案定義了一個名為 db 的服務,使用官方的 Postgres 映象,並設定了環境變數來組態資料函式庫使用者和密碼。這樣,我們可以輕鬆地在本地或生產環境中啟動和管理資料函式庫服務。

介紹

身為一位擁有近二十年程式設計經驗的開發者,我曾經在90年代開始最佳化程式碼,在2000年代發現了PHP,並陸續參與了多個大型專案的開發,同時也接觸了其他程式語言家族,如Java、Node.js,最終選擇了Go語言。我曾經為自己的內容管理產品建立了多個API,並擔任多個產品的首席開發人員,這些產品已被銷售到多個國家。我還為斯洛維尼亞國家電視台和廣播電台網站(RTV Slovenia)編寫了一個專業的API框架,該框架同時也作為軟體開發工具包使用。此外,我也曾在多個本地PHP使用者群組活動和會議上擔任演講者,並撰寫了《API Foundations in Go》一書。我的部落格(scene-si.org)每兩個月會發布新文章,值得一讀。

本文適用物件

本文適用於所有以撰寫應用程式為生的開發者。我將涵蓋軟體開發的廣泛主題,試圖建立良好的開發實踐,同時參考12 Factor App宣言。本文將涵蓋以下主題:

  • I. 程式碼函式庫 - 在版本控制中追蹤一個程式碼函式庫,多次佈署
  • II. 相依性 - 明確宣告和隔離相依性
  • III. 組態 - 將組態儲存在環境中
  • IV. 後端服務 - 將後端服務視為附加資源
  • V. 建置、發布、執行 - 嚴格區分建置和執行階段
  • VI. 程式 - 將應用程式執行為一個或多個無狀態程式
  • VII. 連線埠繫結 - 透過連線埠繫結匯出服務
  • VIII. 平行性 - 透過程式模型擴充套件
  • IX. 可棄性 - 透過快速啟動和優雅關閉最大化強健性
  • X. 開發/生產環境一致性 - 盡可能保持開發、預發布和生產環境的一致性
  • XI. 日誌 - 將日誌視為事件流
  • XII. 管理程式 - 將管理任務作為一次性程式執行

涵蓋這些概念將使您對12 Factor App的設計和試圖解決的問題有一個全面的瞭解。本文試圖提供一套最佳實踐來引導您,並透過個別章節提供實際範例。

如何學習本文

在閱讀本文的過程中,我將提供多個範例來說明開發API時的常見做法。這些範例已釋出在GitHub上。您可以跟隨本文中的範例,或者單獨閱讀每個章節,以瞭解該章節的知識。這些範例是獨立的,但通常建立在前幾章的基礎上。在使用本文時,請務必遵循「需求」一節的說明。

Linux和Docker

本文的範例依賴於Linux主機上的最新docker-engine安裝。

自有硬體

如果您有自己的硬體,建議的組態是:

  • 2個CPU核心
  • 2GB RAM
  • 128GB磁碟(SSD)

雲端快速入門

如果您沒有自己的硬體,可以在Digital Ocean上建立虛擬伺服器。您可以使用DigitalOcean的推薦連結獲得$10的信用額度,同時也可以幫助我減少一些主機費用。在註冊後,建立一個具有執行Docker引擎的Linux例項非常簡單,只需點選幾下即可。

首先,請點選頁面頂部標頭上的綠色按鈕「Create Droplet」,然後在開啟的頁面上導航到「One-click apps」,並從列表中選擇「Docker」。

請注意,執行Docker可能會佔用大量磁碟空間。某些Docker映像檔可能會「重達」1 GB或更多。因此,建議選擇至少具有30GB磁碟空間的例項。

建立Digital Ocean Droplet

Digital Ocean提供了CLI工具doctl,它是一個Go程式,可以與其API介面互動,以允許您列出正在執行的Droplet、建立新的Droplet、關閉它們等等。如果您不需要24/7執行的例項,您可以使用API根據您的需求啟動和關閉它。這樣可以節省約一半的成本。

下載並安裝doctl,然後執行doctl auth init以對Digital Ocean API進行身份驗證。您可以從儀錶板的頂部產生令牌。

# 安裝doctl
wget https://github.com/digitalocean/doctl/releases/download/vX.Y.Z/doctl-X.Y.Z-linux-amd64.tar.gz
tar xf doctl-X.Y.Z-linux-amd64.tar.gz
sudo mv doctl /usr/local/bin/

# 初始化doctl
doctl auth init

內容解密:

上述命令用於安裝和初始化doctl。首先,我們從GitHub下載doctl的壓縮檔,然後解壓縮並將其移動到/usr/local/bin/目錄下。接著,執行doctl auth init命令以使用產生的令牌對Digital Ocean API進行身份驗證。這樣,我們就可以使用doctl來管理Digital Ocean上的資源。

程式碼範例

package main

import (
    "fmt"
    "log"

    "github.com/digitalocean/godo"
)

func main() {
    // 建立新的godo客戶端
    client := godo.NewClient(nil)

    // 列出所有Droplet
    droplets, _, err := client.Droplets.List(ctx, nil)
    if err != nil {
        log.Fatal(err)
    }

    for _, droplet := range droplets {
        fmt.Println(droplet.Name)
    }
}

內容解密:

上述Go程式碼範例演示瞭如何使用godo函式庫來列出Digital Ocean上的所有Droplet。首先,我們建立了一個新的godo客戶端,然後使用Droplets.List方法來檢索Droplet列表。如果發生錯誤,我們將記錄錯誤並離開。最後,我們遍歷Droplet列表並列印每個Droplet的名稱。這個範例展示瞭如何使用godo函式庫與Digital Ocean API進行互動。

使用doctl進行DigitalOcean驗證與資源管理

在開始使用DigitalOcean的API之前,需要先進行驗證。以下指令用於初始化doctl的驗證:

doctl auth init

執行後,需要輸入DigitalOcean的access token,例如:

DigitalOcean access token: 24df63816084cb8270fbe03a7c98341a

驗證成功後,會顯示:

Validating token: OK

此後,便可自由地列出映像檔(images)並透過API建立新的droplet。

列出包含Docker的映像檔

要列出包含Docker的映像檔,可以執行以下指令:

doctl compute image list | grep docker

範例輸出可能如下:

23219707 Docker 17.03.0-ce on 14.04 docker
24232340 Docker 17.04.0-ce on 16.04 docker-16-04

內容解密:

  • doctl compute image list用於列出所有可用的映像檔。
  • grep docker用於篩選出包含“docker”關鍵字的映像檔。

建立與刪除Droplet的指令碼

為了方便建立和刪除droplet,我們可以建立兩個指令碼:create.shdestroy.sh

建立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

內容解密:

  1. doctl compute droplet list asx --format ID --no-header列出名為“asx”的droplet的ID。
  2. wc -l計算輸出的行數,用於檢查droplet是否存在。
  3. 若droplet不存在,則使用doctl compute droplet create建立一個新的droplet。
  4. --image docker-16-04指定使用包含Docker的映像檔。
  5. --size 2gb指定droplet的大小。
  6. --region ams3指定droplet的地區。
  7. --ssh-keys $(./ssh-key.sh)指定用於連線droplet的SSH金鑰。

刪除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

內容解密:

  1. 邏輯與create.sh類別似,但用於檢查droplet是否存在,若存在則刪除。
  2. doctl compute droplet delete asx -f -v強制刪除名為“asx”的droplet。

連線至Droplet

要連線至新建立的droplet,可以使用以下指令取得其公網IP:

doctl compute droplet list asx --format PublicIPv4 --no-header

然後使用SSH連線:

ssh $(doctl compute droplet list asx --format PublicIPv4 --no-header)

內容解密:

  1. doctl compute droplet list asx --format PublicIPv4 --no-header取得droplet的公網IP。
  2. 使用SSH連線到該IP。

Docker網路組態

在Docker中,微服務之間的通訊是透過建立自定義網路來實作的。

建立自定義橋接網路

docker network create -d bridge --subnet 172.25.0.0/24 party

內容解密:

  1. docker network create用於建立新的網路。
  2. -d bridge指定網路驅動為橋接模式。
  3. --subnet 172.25.0.0/24指定網路的子網。
  4. party是網路的名稱。

MySQL資料函式庫例項的快速啟動

可以使用Docker快速啟動MySQL例項。

啟動MySQL例項(mysql.sh)

#!/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

內容解密:

  1. 設定MySQL例項的名稱和資料儲存路徑。
  2. 使用docker run啟動MySQL容器,設定主機名、容器名稱、網路、資料卷掛載和環境變數。
  3. --net=party將容器加入到之前建立的“party”網路中。

版本控制系統(VCS)與程式碼管理

使用版本控制系統(如Git)來管理程式碼對於協作開發和版本追蹤至關重要。

使用Git進行版本控制

建議使用GitHub、Bitbucket或自託管的GitLab、Gogs等服務來託管Git儲存函式庫。確保定期備份並有還原程式。

為公司專案選擇合適的Git託管服務

對於公司專案,Bitbucket是不錯的選擇,提供免費的私有倉函式庫和合理的價格計畫。

為社群專案選擇合適的Git託管服務

對於社群專案,GitHub是首選,提供豐富的工具和龐大的社群支援。

管理多個應用程式或微服務的程式碼函式庫

建議每個專案或微服務使用單獨的倉函式庫以保持簡單。若多個應用程式共用程式碼,應將共用程式碼分離到獨立的套件中。