
很多開發(fā)者在開始一個新項目的時候,通常會使用 JSON,CSV 或者其他 Flat File 來模擬真實存放在數(shù)據(jù)庫中的數(shù)據(jù)。這是因為他們總是在沒有真實的數(shù)據(jù)庫環(huán)境限制和是否需要自己創(chuàng)建模擬數(shù)據(jù)庫之間左右為難。既然這樣,為什么不使用 Docker Compose 定義一個可以在幾秒鐘內創(chuàng)建、銷毀和重新創(chuàng)建的 PostgreSQL 數(shù)據(jù)庫和監(jiān)視工具?

正確創(chuàng)建配置兩個容器的 Docker 命令過于冗長。而使用 Docker Compose,你只需要記住up命令和down命令!
Up命令將創(chuàng)建指定版本的 PostgreSQL 數(shù)據(jù)庫和一個 GUI 管理工具。 Down命令會將其關閉并刪除。
基于私有容器的數(shù)據(jù)庫的好處
- 不同版本的 PostgreSQL 在行為和功能上存在差異,因此開發(fā)人員應針對一個數(shù)據(jù)庫版本進行長期開發(fā)。你可以選擇的一個版本是 9.6.12,另一個可以是 12.4。
- 大多數(shù)程序員都不是數(shù)據(jù)庫管理員或 SQL 專家??梢暬ぞ呖梢宰屗麄冎庇^地驗證其代碼的運行效果并支持手動修改數(shù)據(jù)。
- 項目的不同階段需要不同類型的存儲方案。 在項目早期,非持久型數(shù)據(jù)庫可以最大程度地減少麻煩。 在項目的后期階段,持久型數(shù)據(jù)庫提供了更實際的方案。

建立開發(fā)堆棧
下面所展示的這份 docker-compose.yml 文件定義了一個運行特定版本 PostgreSQL 和 pgAdmin 4(Postgres 最常用的管理工具)的 PostgreSQL 容器。該文件的內容值得我們詳細的探討。
version: "3.8"
services:
postgres:
image: postgres:9.6.12-alpine
container_name: some-postgres
volumes:
- "~/Documents/docker_pgsql_init:/docker-entrypoint-initdb.d"
- "~/Documents/docker_pgsql_volume:/var/lib/postgresql/data"
ports:
- 5432:5432
environment:
- POSTGRES_PASSWORD=mysecret
deploy:
restart_policy:
condition: on-failure
max_attempts: 3
pgadmin:
image: dpage/pgadmin4
container_name: some-pgadmin
volumes:
- ${PWD}/servers.json:/pgadmin4/servers.json
ports:
- 8080:80
environment:
- PGADMIN_DEFAULT_EMAIL=user@domain.com
- PGADMIN_DEFAULT_PASSWORD=admin
deploy:
restart_policy:
condition: on-failure
max_attempts: 3
Docker Compose 的文件結構
該文件定義了兩個要創(chuàng)建的“服務”:Postgres 和 pgAdmin。 每個服務都包含一個從 Docker Hub 拉取的容器。 Postgres 和 pgAdmin 將分別開放 5432 端口和 8080 端口。將你寫的任何程序指向主機名“ localhost”,然后用瀏覽器訪問http://localhost:8080 即可訪問 pgAdmin。
繼續(xù)閱讀有關如何將 pgAdmin 指向 Postgres 的說明。
PostgreSQL 的版本
在定義 Postgres 容器的這一行中,你需要準確指定所需的 Postgres 版本。在這里,version 是一個標簽,而9.6.12-alpine就是我們示例中的版本。點擊這里查看其他可用的版本。
Postgres 的存儲
上面的docker-compose.yml 文件為 Postgres 指定了兩個映射卷。 這兩個映射將使 Postgres 可以訪問你計算機上的目錄。
- 被映射到
/docker-entrypoint-initdb.d的文件夾包含了初始化 Postgres 將會用到的 SQL 文件。將所需的 SQL 文件和 Shell 腳本放在該目錄中,它們將會按字母順序自動執(zhí)行。 - 被映射到
/var/lib/postgresql/data的文件夾存放了數(shù)據(jù)庫持久化存儲所需要的真實文件。
當 Postgres 啟動時,他的運行流程如下圖所示。 如果數(shù)據(jù)庫中沒有數(shù)據(jù),那么它將執(zhí)行被映射到/docker-entrypoint-initdb.d目錄中的每個 SQL 文件和 Shell 腳本(按字母順序)。如果被映射到 /var/lib/postgresql/data的文件夾中有數(shù)據(jù),那么它將會忽略掉這些文件。

你是否需要掛載這兩個目錄?這個得視情況而定。下表描述了通過分別映射 Postgres 的兩個卷中的一個而預期的行為。

我的建議是對這兩個文件目錄都做映射。如果你不想再初始化你的數(shù)據(jù)庫,你可以從 init 目錄中刪除文件。你也可以在你的計算機上刪除任何可能已經持久化的數(shù)據(jù)(當你下一次運行docker-compose up命令時,Docker 將會在其位置重新創(chuàng)建一個空文件夾)。
專業(yè)提示:你可以將 CSV 文件放在計算機的 init 文件夾中,然后在 init 目錄中通過適當?shù)?SQL 命令將 CSV 文件中的數(shù)據(jù)填充到數(shù)據(jù)表中。
CREATE TABLE Employee(id, first_name, last_name, salary);
COPY Employee FROM '/docker-entrypoint-initdb.d/emp.csv'
WITH (FORMAT CSV, HEADER);
pgAdmin 存儲

盡管 pgAdmin 只是一個用于查看和配置數(shù)據(jù)庫的工具,但必須配置其與數(shù)據(jù)庫的連接。 這可以通過可視化工具中的add server指令完成。這里需要注意的是,主機名(the hostname)是我們在 YML 文件中配置的 container_name 這一參數(shù)的名稱,即 some-postgres。同樣地,密碼也已經在 YML 文件中指定了,即 mysecret。
另一種方法是通過在 JSON 文件中指定這些配置(除了密碼之外的所有配置),通過這種方式可以免去大量的單擊和輸入操作。為了避免手動配置 pgAdmin 連接到 Postgres,我們需要將該 JSON 文件映射到容器的/pgadmin4/servers.json上(在示例 YML 文件中的第 22 行)。
設置文件可以指定 pgAdmin 和 Postgres 之間的多個連接(以不同用戶的身份連接或者連接到多個不同的數(shù)據(jù)庫)。下面是只有一個數(shù)據(jù)庫連接的示例。
{
"Servers": {
"1": {
"Name": "my-postgres",
"Group": "Servers",
"Port": 5432,
"Username": "postgres",
"Host": "some-postgres",
"SSLMode": "prefer",
"MaintenanceDB": "postgres"
}
}
}
網絡
container_name 參數(shù)僅僅只表示一個名字。但是 pgAdmin 將使用這個名稱訪問 5432 端口上的數(shù)據(jù)庫。原因如下圖所示。這兩個容器通過私有網絡相互連接,因此可以通過它們的主機名(容器名)也就是 some-postgres 和 some-pgadmin 相互訪問。然而,你的主機(也就是你的計算機和 web 瀏覽器)只能訪問容器對外暴露的 5432 端口和 8080 端口,因此你可以通過 localhost:5432 和 localhost:8080 訪問它們。

內部網絡的名稱可以在 compose 文件中指定,但是為一個從未被代碼引用的網絡命名沒有任何價值!如果你還是好奇,可以隨時查看你的私人及臨時網絡的名稱。在下面的代碼片段中,我從桌面運行了 Docker Compose,因此將該網絡命名為desktop_default。
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
23a6be9b8021 bridge bridge local
49a120440f88 desktop_default bridge local
44a949b56fa7 host host local
3892b16dca2d none null local
Docker 和 docker-compose 命令
在這里,我必須承認我在介紹中過分簡化了命令,但只是稍微簡化了一點。
為了啟動容器,可以使用 docker-compose up -d。-d 參數(shù)指定這是一個 detached 模式,它將會在后臺運行,并且不會影響你在命令提示符中執(zhí)行其他命令。
$ docker-compose up -d
Creating network "desktop_default" with the default driver
Creating some-postgres ... done
Creating some-pgadmin ... done
$
為了關閉并刪除容器,你可以使用 docker-compose down -v。-v 參數(shù)表示刪除容器在運行時使用的卷。這個不會刪除容器映射到計算機上的目錄。
$ docker-compose down -v
Stopping some-pgadmin ... done
Stopping some-postgres ... done
Removing some-pgadmin ... done
Removing some-postgres ... done
Removing network desktop_default
隨著時間的推移,如果不使用 -v 標志,就會累積不必要的卷。你可以使用 docker volume ls 來驗證這一點。
如果要調試一個沒有正確啟動的容器,請使用 docker logs [container_name]。例如,由于 init 目錄中的一個 SQL 文件中出現(xiàn)錯誤,數(shù)據(jù)庫可能無法正確初始化。通過執(zhí)行 docker logs some-postgres 命令,可以生成容器啟動時記錄的日志,通過對該日志的查閱,我在一個特殊命名的 SQL 文件中發(fā)現(xiàn)一個錯誤:
/usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/broken.sql
ERROR: syntax error at end of input at character 34
STATEMENT: CREATE TABLE Songs(id, name, year
psql:/docker-entrypoint-initdb.d/broken.sql:1: ERROR: syntax error at end of input
LINE 1: CREATE TABLE Song(id, name, year
日志告訴我在 broken.sql 文件的第 1 行有一個錯誤。該命令缺少閉括號和分號。我可以修復這個錯誤,并使用 down 和 up 來驗證。
使用 Python
使用 localhost 作為 YML 文件中指定的主機名和密碼,連接到數(shù)據(jù)庫很容易。
import psycopg2
# connect to DB
conn = psycopg2.connect(host="localhost", dbname="postgres", user="postgres",
password="mysecret")
cursor = conn.cursor()
# execute SQL commands in SQL file
cursor.execute(open("emp.sql", "r").read())
# retrieve data from the database
cursor.execute("SELECT * FROM Employee")
print(cursor.fetchall())
使用 pgAdmin
簡單的訪問 http://localhost:8080 即可進入登錄界面,使用你在 docker-compose.yml 文件中定義的用戶名和密碼登錄即可(在我們的示例中是 user@domain.com 和 admin)。

如果你使用的是本文之前討論的 servers.json 文件來指定連接細節(jié),你將會在展開用戶界面左側的導航樹時,收到系統(tǒng)要求你輸入 Postgres 數(shù)據(jù)庫的密碼的提示。在我們示例的 docker-compose.yml 文件中,這個密碼是 mysecret 。如果你并沒有創(chuàng)建 servers.json 文件或文件中有錯誤,你就必須手動添加服務器。

現(xiàn)在,你應該能夠查看和操作數(shù)據(jù)庫了。

進入 PSQL
有時候,開發(fā)者需要一個熟悉的命令行。 Docker 使得訪問 PSQL 和執(zhí)行高級用戶命令等操作變得更加容易。 執(zhí)行下面的命令進入 PSQL 命令行。

連接后,你就可以執(zhí)行所有 PSQL 命令,例如,輸入 \i 用于導入外部數(shù)據(jù)庫,輸入 \dt 顯示數(shù)據(jù)表的描述,輸入 \df 顯示函數(shù)的描述。想要退出,可以使用 \q 命令。
$ docker exec -it some-postgres psql -U postgres
psql (9.6.12)
Type "help" for help.
postgres=# \dt
List of relations
Schema | Name | Type | Owner
--------+--------------+-------+----------
public | peak | table | postgres
public | climb | table | postgres
public | climber | table | postgres
pgAdmin 的替代品

pgAdmin 是 PostgreSQL 最常見的 GUI 管理工具,但我們還有其他選擇。 Adminer 的使用更加簡單,并且你可能已經擁有使用它的經驗了,因為它支持多種風格的 SQL。 如果你是剛開始使用 PostgreSQL 或者只有非常簡單的需求,那么它可能是一個更合適你的工具。
在登錄界面上,設置主機名為 some-postgres ,密碼為mysecret。

要在你的環(huán)境中用 Adminer 替換 pgAdmin,你需要在 docker-compose.yml 中替換幾行有關 pgAdmin 容器的定義。
adminer:
image: adminer
container_name: some-adminer
ports:
- 8080:8080
deploy:
restart_policy:
condition: on-failure
max_attempts: 3
引用
所有優(yōu)秀的開發(fā)者都依賴于產品文檔和其他人員的經驗。 這是我在創(chuàng)建工作流程和編寫本文時引用的來源。
- https://hub.docker.com/_/postgres
- https://hub.docker.com/_/adminer
- https://docs.docker.com/compose/compose-file/
- https://www.pgadmin.org/docs/pgadmin4/latest/container_deployment.html
- https://technology.amis.nl/2020/01/02/pgadmin-in-docker-provision-connections-and-passwords/
- https://stackoverflow.com/questions/42248198/how-to-mount-a-single-file-in-a-volume