使用容器進行 Go 開發
先決條件
逐步完成 將您的映像檔作為容器執行 模組的步驟,以瞭解如何管理容器的生命週期。
簡介
在本模組中,您將瞭解如何在容器中執行資料庫引擎,並將其連接到範例應用程式的擴展版本。您將看到一些用於保存持久性資料和連接容器以彼此通訊的選項。最後,您將學習如何使用 Docker Compose 有效地管理此類多容器本機開發環境。
本地資料庫和容器
您將使用的資料庫引擎稱為 CockroachDB。它是一個現代化的、雲原生的、分散式 SQL 資料庫。
您將使用 CockroachDB 的 Docker 映像檔、pq
如需 Go 與 CockroachDB 之間關係的更多資訊,請參閱 CockroachDB 文件
儲存
資料庫的重點是擁有永續的資料儲存空間。磁碟區 (Volumes) 是用於保存 Docker 容器產生和使用的資料的首選機制。因此,在您啟動 CockroachDB 之前,請先為它建立磁碟區。
要建立受管理的磁碟區,請執行
$ docker volume create roach
roach
您可以使用以下指令檢視 Docker 執行個體中所有受管理磁碟區的清單
$ docker volume list
DRIVER VOLUME NAME
local roach
網路
範例應用程式和資料庫引擎將透過網路互相通訊。有不同種類的網路設定方式,而您將使用所謂的使用者定義橋接網路。它將為您提供 DNS 查閱服務,以便您可以透過主機名稱來參考您的資料庫引擎容器。
以下指令會建立一個名為 `mynet` 的新橋接網路
$ docker network create -d bridge mynet
51344edd6430b5acd121822cacc99f8bc39be63dd125a3b3cd517b6485ab7709
與受管理磁碟區的情況一樣,有一個指令可以列出 Docker 執行個體中設定的所有網路
$ docker network list
NETWORK ID NAME DRIVER SCOPE
0ac2b1819fa4 bridge bridge local
51344edd6430 mynet bridge local
daed20bbecce host host local
6aee44f40a39 none null local
您的橋接網路 `mynet` 已成功建立。其他三個名為 `bridge`、`host` 和 `none` 的網路是預設網路,它們是由 Docker 本身建立的。雖然這與本指南無關,但您可以在 網路概觀 章節中了解更多關於 Docker 網路的資訊。
為磁碟區和網路選擇好的名稱
俗話說,電腦科學中只有兩件難事:快取失效和命名事物。以及差一錯誤 (off-by-one errors)。
在為網路或受管理磁碟區選擇名稱時,最好選擇一個能表明預期用途的名稱。本指南旨在簡潔,因此使用了簡短、通用的名稱。
啟動資料庫引擎
現在,例行工作已完成,您可以在容器中執行 CockroachDB,並將其連接到您剛建立的磁碟區和網路。當您執行以下指令時,Docker 將從 Docker Hub 拉取映像檔並在本地端為您執行
$ docker run -d \
--name roach \
--hostname db \
--network mynet \
-p 26257:26257 \
-p 8080:8080 \
-v roach:/cockroach/cockroach-data \
cockroachdb/cockroach:latest-v20.1 start-single-node \
--insecure
# ... output omitted ...
請注意巧妙地使用標籤 `latest-v20.1` 來確保您拉取的是 20.1 的最新修補程式版本。可用標籤的多樣性取決於映像檔維護者。在這裡,您的目的是擁有 CockroachDB 的最新修補程式版本,同時隨著時間的推移不會偏離已知的工作版本太遠。要查看 CockroachDB 映像檔可用的標籤,您可以前往 Docker Hub 上的 CockroachDB 頁面 現在資料庫引擎已啟動,在您的應用程式開始使用它之前,還有一些設定要做。幸運的是,這並不多。您必須 您可以藉助 CockroachDB 內建的 SQL shell 來完成此操作。要在資料庫引擎執行的同一個容器中啟動 SQL shell,請輸入 在 SQL shell 中,建立範例應用程式將使用的資料庫 使用使用者名稱 `totoro` 在資料庫引擎中註冊一個新的 SQL 使用者帳戶。 授予新使用者必要的權限 輸入 `quit` 退出 shell。 以下是與 SQL shell 互動的範例。 現在您已啟動並設定資料庫引擎,您可以將注意力轉移到應用程式。 此模組的範例應用程式是您先前模組中使用的 要取出範例應用程式,請執行 應用程式的 您還有一個針對另一個業務需求的更新。該需求是 現在它將會是 應用程式會以包含儲存在資料庫中的訊息計數的字串回應,並以括號括起來。 範例輸出: 以下是 儲存庫還包含 無論您是更新了舊的範例應用程式,還是取出了新的範例應用程式,都必須建置這個新的 Docker 映像檔,以反映應用程式原始程式碼的變更。 您可以使用熟悉的 現在,執行您的容器。這次您需要設定一些環境變數,以便您的應用程式知道如何存取資料庫。目前,您將直接在 由於您是在不安全模式下執行 CockroachDB 叢集,因此密碼的值可以是任何值。 在正式環境中,請勿在不安全模式下執行。 關於此命令,有幾點需要注意。 這次您將容器埠 或者,如果您願意,也可以使用正確的 URL 目前儲存的訊息總數為 您透過其主機名稱 實際密碼無關緊要,但必須將其設定為某個值,以免混淆範例應用程式。 您剛執行的容器名為 在上一節中,您已經測試了使用 應用程式會以訊息的內容回應,這表示它已儲存在資料庫中 傳送另一則訊息 同樣地,您會收到訊息的值 執行 curl 並查看訊息計數器顯示的內容 在此範例中,您傳送了兩則訊息,並且資料庫保留了它們。還是它有保留?停止並移除所有容器,但不要移除磁碟區,然後再試一次。 首先,停止容器 然後,移除它們 確認它們已消失 然後再次啟動它們,先啟動資料庫 接著是服務 最後,查詢您的服務 太好了!來自資料庫的記錄計數是正確的,儘管您不僅停止了容器,而且還在啟動新的執行個體之前移除了它們。差異在於 CockroachDB 的受管理磁碟區,您重複使用了它。新的 CockroachDB 容器已從磁碟讀取資料庫檔案,就像它在容器外執行時一樣。 請記住,您是在不安全模式下執行 CockroachDB。現在您已經建置並測試了您的應用程式,在繼續之前,是時候結束所有工作了。您可以使用 現在您知道容器 ID 了,您可以使用 在繼續之前,請停止 CockroachDB 和 此時,您可能想知道是否有辦法避免處理 在本節中,您將建立一個 Docker Compose 檔案,以使用單個命令啟動您的 在您的應用程式目錄中,建立一個名為 這個 Docker Compose 設定非常方便,因為您不必輸入要傳遞給 如果可用,Docker Compose 將自動從 確切值對於此範例並不重要,因為您是在不安全模式下執行 CockroachDB。請確保將變數設定為某個值,以避免發生錯誤。 檔案名稱 這裡不會涵蓋任何這些進階使用案例。 Docker Compose 真正酷炫的功能之一是變數替換。您可以在 Compose 檔案的 Docker 文件的變數替換區段中,記載了其他處理未定義或空值的 方法。 在套用對 Compose 設定檔所做的變更之前,您可以使用以下命令驗證設定檔的內容: 執行此命令時,Docker Compose 會讀取 啟動您的應用程式並確認它正在執行。 您傳遞了 Docker Compose 是一個很有用的工具,但它也有其自身的特殊之處。例如,除非提供 由於您的設定現在由 Docker Compose 執行,它已為其分配了一個專案名稱,因此您的 CockroachDB 執行個體會獲得一個新的磁碟區。這表示您的應用程式將無法連線到資料庫,因為此新磁碟區中不存在該資料庫。終端機將顯示資料庫的驗證錯誤。 由於您使用 這不是什麼大問題。您只需連線到 CockroachDB 執行個體,並執行三個 SQL 命令來建立資料庫和使用者,如設定資料庫引擎中所述。 因此,請從另一個終端機登入到資料庫引擎: 並執行與之前相同的命令來建立資料庫 原本可以連線您先前使用的磁碟區,但就本範例而言,這樣做弊大於利,而且它也提供了一個機會,展示如何透過 現在,測試您的 API 端點。在新終端機中,執行以下命令: 您應該會收到以下回應: 要停止 Docker Compose 啟動的容器,請在執行 您可以像使用 要以分離模式啟動由 Compose 檔案定義的堆疊,請執行: 然後,您可以使用 您可以執行 本章中特意未涵蓋一些相關但有趣的主題。對於更具冒險精神的讀者,本節提供了一些進一步研究的指標。 受管磁碟區並不是為您的容器提供持久性儲存的唯一方法。強烈建議您熟悉可用的儲存選項及其使用案例,這些內容在在 Docker 中管理資料中有說明。 您執行了一個 CockroachDB 執行個體,這足以滿足本範例的需求。但是,可以執行一個 CockroachDB 叢集,它由多個 CockroachDB 執行個體組成,每個執行個體都在自己的容器中執行。由於 CockroachDB 引擎在設計上是分散式的,因此您只需對程序進行極少的更改,即可執行具有多個節點的叢集。 這種分散式設定提供了有趣的可能性,例如應用混沌工程技術來模擬叢集部分故障,並評估您的應用程式應對此類故障的能力。 如果您有興趣試用 CockroachDB 叢集,請查看: 由於您沒有執行 CockroachDB 執行個體叢集,您可能會想知道是否可以使用非分散式資料庫引擎。答案是「可以」,如果您要選擇更傳統的 SQL 資料庫,例如 PostgreSQL 在此模組中,您設定了一個容器化的開發環境,您的應用程式和資料庫引擎在不同的容器中運行。您還編寫了一個 Docker Compose 檔案,它將兩個容器連結在一起,並提供簡便的開發環境啟動和關閉方式。 在下一模組中,您將了解在 Docker 中運行功能測試的一種可能方法。$ docker exec -it roach ./cockroach sql --insecure
CREATE DATABASE mydb;
CREATE USER totoro;
GRANT ALL ON DATABASE mydb TO totoro;
$ sudo docker exec -it roach ./cockroach sql --insecure
#
# Welcome to the CockroachDB SQL shell.
# All statements must be terminated by a semicolon.
# To exit, type: \q.
#
# Server version: CockroachDB CCL v20.1.15 (x86_64-unknown-linux-gnu, built 2021/04/26 16:11:58, go1.13.9) (same version as client)
# Cluster ID: 7f43a490-ccd6-4c2a-9534-21f393ca80ce
#
# Enter \? for a brief introduction.
#
root@:26257/defaultdb> CREATE DATABASE mydb;
CREATE DATABASE
Time: 22.985478ms
root@:26257/defaultdb> CREATE USER totoro;
CREATE ROLE
Time: 13.921659ms
root@:26257/defaultdb> GRANT ALL ON DATABASE mydb TO totoro;
GRANT
Time: 14.217559ms
root@:26257/defaultdb> quit
oliver@hki:~$
認識範例應用程式
docker-gs-ping
應用程式的擴充版本。您有兩個選項docker-gs-ping
本機副本,以符合本章中介紹的新擴充版本;或$ git clone https://github.com/docker/docker-gs-ping-dev.git
# ... output omitted ...
main.go
現在包含資料庫初始化程式碼,以及實作新業務需求的程式碼/send
的 HTTP POST
請求,其中包含 { "value" : string }
JSON 必須將值儲存到資料庫中。<3
") 的文字訊息回應對 /
的請求。Hello, Docker! (7)
main.go
的完整原始程式碼清單。package main
import (
"context"
"database/sql"
"fmt"
"log"
"net/http"
"os"
"github.com/cenkalti/backoff/v4"
"github.com/cockroachdb/cockroach-go/v2/crdb"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
db, err := initStore()
if err != nil {
log.Fatalf("failed to initialize the store: %s", err)
}
defer db.Close()
e.GET("/", func(c echo.Context) error {
return rootHandler(db, c)
})
e.GET("/ping", func(c echo.Context) error {
return c.JSON(http.StatusOK, struct{ Status string }{Status: "OK"})
})
e.POST("/send", func(c echo.Context) error {
return sendHandler(db, c)
})
httpPort := os.Getenv("HTTP_PORT")
if httpPort == "" {
httpPort = "8080"
}
e.Logger.Fatal(e.Start(":" + httpPort))
}
type Message struct {
Value string `json:"value"`
}
func initStore() (*sql.DB, error) {
pgConnString := fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s sslmode=disable",
os.Getenv("PGHOST"),
os.Getenv("PGPORT"),
os.Getenv("PGDATABASE"),
os.Getenv("PGUSER"),
os.Getenv("PGPASSWORD"),
)
var (
db *sql.DB
err error
)
openDB := func() error {
db, err = sql.Open("postgres", pgConnString)
return err
}
err = backoff.Retry(openDB, backoff.NewExponentialBackOff())
if err != nil {
return nil, err
}
if _, err := db.Exec(
"CREATE TABLE IF NOT EXISTS message (value TEXT PRIMARY KEY)"); err != nil {
return nil, err
}
return db, nil
}
func rootHandler(db *sql.DB, c echo.Context) error {
r, err := countRecords(db)
if err != nil {
return c.HTML(http.StatusInternalServerError, err.Error())
}
return c.HTML(http.StatusOK, fmt.Sprintf("Hello, Docker! (%d)\n", r))
}
func sendHandler(db *sql.DB, c echo.Context) error {
m := &Message{}
if err := c.Bind(m); err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
err := crdb.ExecuteTx(context.Background(), db, nil,
func(tx *sql.Tx) error {
_, err := tx.Exec(
"INSERT INTO message (value) VALUES ($1) ON CONFLICT (value) DO UPDATE SET value = excluded.value",
m.Value,
)
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
return nil
})
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
return c.JSON(http.StatusOK, m)
}
func countRecords(db *sql.DB) (int, error) {
rows, err := db.Query("SELECT COUNT(*) FROM message")
if err != nil {
return 0, err
}
defer rows.Close()
count := 0
for rows.Next() {
if err := rows.Scan(&count); err != nil {
return 0, err
}
rows.Close()
}
return count, nil
}
Dockerfile
,它幾乎與先前模組中介紹的多階段 Dockerfile
完全相同。它使用官方 Docker Go 映像檔來建置應用程式,然後透過將編譯的二進位檔放入更精簡的無發行版映像檔來建置最終映像檔。建構應用程式
build
命令建置映像檔$ docker build --tag docker-gs-ping-roach .
執行應用程式
docker run
命令中執行此操作。稍後,您將看到使用 Docker Compose 的更方便方法。$ docker run -it --rm -d \
--network mynet \
--name rest-server \
-p 80:8080 \
-e PGUSER=totoro \
-e PGPASSWORD=myfriend \
-e PGHOST=db \
-e PGPORT=26257 \
-e PGDATABASE=mydb \
docker-gs-ping-roach
8080
映射到主機埠 80
。因此,對於 GET
請求,您可以直接使用 curl localhost
$ curl localhost
Hello, Docker! (0)
$ curl http://localhost/
Hello, Docker! (0)
0
。這沒問題,因為您尚未向您的應用程式發布任何內容。db
來參考資料庫容器。這就是為什麼您在啟動資料庫容器時使用 --hostname db
的原因。rest-server
。這些名稱對於管理容器生命週期很有用# Don't do this just yet, it's only an example:
$ docker container rm --force rest-server
測試應用程式
GET
查詢您的應用程式,並且它為儲存的訊息計數器傳回了零。現在,向它發布一些訊息$ curl --request POST \
--url http://localhost/send \
--header 'content-type: application/json' \
--data '{"value": "Hello, Docker!"}'
{ "value": "Hello, Docker!" }
$ curl --request POST \
--url http://localhost/send \
--header 'content-type: application/json' \
--data '{"value": "Hello, Oliver!"}'
{ "value": "Hello, Oliver!" }
$ curl localhost
Hello, Docker! (2)
$ docker container stop rest-server roach
rest-server
roach
$ docker container rm rest-server roach
rest-server
roach
$ docker container list --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
$ docker run -d \
--name roach \
--hostname db \
--network mynet \
-p 26257:26257 \
-p 8080:8080 \
-v roach:/cockroach/cockroach-data \
cockroachdb/cockroach:latest-v20.1 start-single-node \
--insecure
$ docker run -it --rm -d \
--network mynet \
--name rest-server \
-p 80:8080 \
-e PGUSER=totoro \
-e PGPASSWORD=myfriend \
-e PGHOST=db \
-e PGPORT=26257 \
-e PGDATABASE=mydb \
docker-gs-ping-roach
$ curl localhost
Hello, Docker! (2)
結束所有操作
list
命令列出您正在執行的容器$ docker container list
docker container stop
和 docker container rm
,如先前模組中所示。docker-gs-ping-roach
容器。使用 Docker Compose 提高生產力
docker
命令的長參數清單。您在本系列中使用的玩具範例需要五個環境變數來定義與資料庫的連線。一個真正的應用程式可能需要更多更多。然後還有相依性的問題。理想情況下,您希望確保在執行應用程式之前啟動資料庫。而啟動資料庫執行個體可能需要另一個具有許多選項的 Docker 命令。但是,有一種更好的方法可以協調這些部署以用於本機開發目的。docker-gs-ping-roach
應用程式和 CockroachDB 資料庫引擎。設定 Docker Compose
docker-compose.yml
的新文字檔案,其中包含以下內容。version: "3.8"
services:
docker-gs-ping-roach:
depends_on:
- roach
build:
context: .
container_name: rest-server
hostname: rest-server
networks:
- mynet
ports:
- 80:8080
environment:
- PGUSER=${PGUSER:-totoro}
- PGPASSWORD=${PGPASSWORD:?database password not set}
- PGHOST=${PGHOST:-db}
- PGPORT=${PGPORT:-26257}
- PGDATABASE=${PGDATABASE:-mydb}
deploy:
restart_policy:
condition: on-failure
roach:
image: cockroachdb/cockroach:latest-v20.1
container_name: roach
hostname: db
networks:
- mynet
ports:
- 26257:26257
- 8080:8080
volumes:
- roach:/cockroach/cockroach-data
command: start-single-node --insecure
volumes:
roach:
networks:
mynet:
driver: bridge
docker run
命令的所有參數。您可以在 Docker Compose 檔案中以宣告方式執行此操作。Docker Compose 文件頁面 內容相當廣泛,並且包含 Docker Compose 檔案格式的完整參考。.env
檔案.env
檔案讀取環境變數。由於您的 Compose 檔案需要設定 PGPASSWORD
,請將以下內容新增到 .env
檔案PGPASSWORD=whatever
合併 Compose 檔案
docker-compose.yml
是 docker compose
命令在未提供 -f
旗標時預設辨識的檔案名稱。這表示如果您的環境有此需求,您可以有多個 Docker Compose 檔案。此外,Docker Compose 檔案是…可組合的(一語雙關),因此可以在命令列中指定多個檔案,將配置的各個部分合併在一起。以下列出一些此功能非常實用的情境範例:Docker Compose 中的變數替換
environment
區段中看到一些範例。舉例說明:PGUSER=${PGUSER:-totoro}
表示在容器內,環境變數 PGUSER
應設定為與執行 Docker Compose 的主機上的值相同。如果主機上沒有此名稱的環境變數,則容器內的變數將取得預設值 totoro
。PGPASSWORD=${PGPASSWORD:?database password not set}
表示如果主機上未設定環境變數 PGPASSWORD
,Docker Compose 將顯示錯誤。這是可以接受的,因為您不希望將密碼的預設值寫死在程式碼中。您可以在 .env
檔案中設定密碼值,該檔案是您機器上的本地檔案。將 .env
新增到 .gitignore
中,以防止密碼被提交到版本控制系統,始終是一個好主意。驗證 Docker Compose 設定
$ docker compose config
docker-compose.yml
檔案,將其解析為記憶體中的資料結構,盡可能進行驗證,並從其內部表示重新建構該設定檔並印出。如果由於錯誤而無法執行此操作,Docker 會改為印出錯誤訊息。使用 Docker Compose 建構和執行應用程式
$ docker compose up --build
--build
旗標,因此 Docker 將編譯您的映像檔,然後啟動它。--build
旗標,否則在更新原始碼時不會觸發重建。編輯原始碼後,在執行 docker compose up
時忘記使用 --build
旗標,是一個非常常見的陷阱。# ... omitted output ...
rest-server | 2021/05/10 00:54:25 failed to initialise the store: pq: password authentication failed for user totoro
roach | *
roach | * INFO: Replication was disabled for this cluster.
roach | * When/if adding nodes in the future, update zone configurations to increase the replication factor.
roach | *
roach | CockroachDB node starting at 2021-05-10 00:54:26.398177 +0000 UTC (took 3.0s)
roach | build: CCL v20.1.15 @ 2021/04/26 16:11:58 (go1.13.9)
roach | webui: http://db:8080
roach | sql: postgresql://root@db:26257?sslmode=disable
roach | RPC client flags: /cockroach/cockroach <client cmd> --host=db:26257 --insecure
roach | logs: /cockroach/cockroach-data/logs
roach | temp dir: /cockroach/cockroach-data/cockroach-temp349434348
roach | external I/O path: /cockroach/cockroach-data/extern
roach | store[0]: path=/cockroach/cockroach-data
roach | storage engine: rocksdb
roach | status: initialized new cluster
roach | clusterID: b7b1cb93-558f-4058-b77e-8a4ddb329a88
roach | nodeID: 1
rest-server exited with code 0
rest-server | 2021/05/10 00:54:25 failed to initialise the store: pq: password authentication failed for user totoro
rest-server | 2021/05/10 00:54:26 failed to initialise the store: pq: password authentication failed for user totoro
rest-server | 2021/05/10 00:54:29 failed to initialise the store: pq: password authentication failed for user totoro
rest-server | 2021/05/10 00:54:25 failed to initialise the store: pq: password authentication failed for user totoro
rest-server | 2021/05/10 00:54:26 failed to initialise the store: pq: password authentication failed for user totoro
rest-server | 2021/05/10 00:54:29 failed to initialise the store: pq: password authentication failed for user totoro
rest-server exited with code 1
# ... omitted output ...
restart_policy
設定部署的方式,因此失敗的容器每 20 秒就會重新啟動一次。因此,為了修復問題,您需要登入到資料庫引擎並建立使用者。您之前在設定資料庫引擎中已執行過此操作。$ docker exec -it roach ./cockroach sql --insecure
mydb
、使用者 totoro
,並授予該使用者必要的權限。完成此操作後(範例應用程式容器會自動重新啟動),rest-service
將停止失敗和重新啟動,終端機將會靜默。restart_policy
Compose 檔案功能,將彈性引入您的部署中。測試應用程式
$ curl http://localhost/
Hello, Docker! (0)
關閉
docker compose up
的終端機中按下 ctrl+c
。要在容器停止後移除它們,請執行 docker compose down
。分離模式
docker
命令一樣,使用 -d
旗標,以分離模式執行由 docker compose
命令啟動的容器。$ docker compose up --build -d
docker compose stop
停止容器,並使用 docker compose down
移除它們。進一步探索
docker compose
來查看其他可用的命令。總結
永久儲存
CockroachDB 叢集
其他資料庫