前一篇建立了內部 CA,讓後續服務可以取得同一條信任鏈簽發的憑證。今天開始建立真正承載資料的 PostgreSQL 節點,並把儲存、服務身分與連線權限接成一條完整路徑。
PostgreSQL 高可用建立在穩定的單節點基礎上。每一台資料庫節點都需要正確的資料目錄、可驗證的伺服器身分,以及清楚的連線與權限邊界。完成這些準備後,三台節點便能繼續建立串流複寫(Streaming Replication)。
本文閱讀方式
- 完整理解技術與底層原理:依序閱讀全文。
- 掌握完成部署所需的基本概念:優先閱讀標有「⭐」的章節。
- 快速了解當天的部署內容:閱讀文末的 Lab/實作章節。
- 跟著系列建立完整系統:依照 GitHub 詳細部署文件 的天數順序操作。
這裡聚焦在 VM 取得資料磁碟後,如何讓 PostgreSQL 確實把資料寫到預期位置。
PVE 為 VM 提供虛擬磁碟後,虛擬機作業系統(Guest OS)會先把它視為區塊裝置(Block Device)。建立檔案系統(Filesystem)並掛載後,PostgreSQL 才能在其中建立資料目錄(Data Directory)。

圖(一)PVE 提供 VM 資料磁碟後,Guest OS 必須建立檔案系統與掛載點,PostgreSQL 才能使用其中的資料目錄。
PostgreSQL 對 Guest OS 掛載的檔案系統讀寫,看見的是作業系統提供的儲存介面。下方的 Ceph RBD、LVM-thin 或實體 SSD 則決定 WAL 落盤延遲與故障方式。
將 PostgreSQL 資料放在獨立磁碟,方便分別管理容量、掛載與 I/O,也能避免資料庫資料耗盡根檔案系統。這種分離負責管理本機儲存,備份與跨節點副本需要另外建立。
持久掛載可以使用檔案系統標籤寫入 /etc/fstab,避免裝置名稱因偵測順序改變而掛錯磁碟。文末實作會先以 lsblk 與 wipefs -n 確認資料磁碟,再建立檔案系統與掛載點。檔案系統標籤負責後續辨識,格式化前則必須另外確認裝置。
即使資料磁碟尚未掛載,/var/lib/postgresql 這個目錄也可能存在於根檔案系統。如果只看到目錄存在便啟動服務,PostgreSQL 可能把資料寫到錯誤磁碟。
啟動 PostgreSQL 前,先列出區塊裝置,再查詢 /var/lib/postgresql 實際位於哪個檔案系統:
lsblk -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINTS
findmnt -no SOURCE,FSTYPE,TARGET \
-T /var/lib/postgresql
lsblk 應顯示預期的資料磁碟、檔案系統及掛載位置。findmnt 顯示的 SOURCE 應對應同一顆資料磁碟,TARGET 應為 /var/lib/postgresql。如果 TARGET 顯示 /,代表這個目錄目前位於根檔案系統,應先完成資料磁碟掛載再啟動 PostgreSQL。
確認掛載正確並啟動 PostgreSQL 後,再查詢執行個體真正使用的資料目錄,並反查該路徑所屬的檔案系統:
PGDATA="$(sudo -u postgres \
psql -Atqc 'SHOW data_directory;')"
printf 'data_directory=%s\n' "$PGDATA"
findmnt -no SOURCE,FSTYPE,TARGET \
-T "$PGDATA"
最後一次 findmnt 的 SOURCE 與 TARGET 應和前面的資料磁碟及掛載點一致。這樣才能驗證磁碟存在、掛載正確,而且 PostgreSQL 確實將資料寫在這顆磁碟上。
PostgreSQL 文件中的資料庫叢集(Database Cluster),是由同一個 PostgreSQL 伺服器執行個體(Server Instance)管理、共用一個資料目錄的一組資料庫。多台主機共同提供服務的架構才是本文所稱的高可用叢集。

圖(二)一個 PostgreSQL 執行個體管理一個資料庫叢集,並將資料分別保存在 PGDATA 內的不同目錄。
使用者在 SQL 中看到的資料庫、資料表與索引都是邏輯物件。使用預設資料表空間時,每個資料庫會依內部識別碼存放在 base 下,資料表與索引再對應到其中的資料檔案。這些檔案交由 PostgreSQL 管理,直接手動修改可能破壞資料一致性。
一個 PostgreSQL 執行個體只管理一個資料庫叢集。同一台主機可以啟動多個執行個體,但它們必須使用不同的資料目錄與連接埠。理解單一執行個體如何保存資料後,便能繼續掌握 WAL、資料目錄與節點角色如何把單機結構延伸到多台主機。
Debian 的 postgresql-common 以「版本/叢集名稱」管理 PostgreSQL 執行個體,例如 18/main:
| 項目 | Debian 範例 | 用途 |
|---|---|---|
| PostgreSQL 版本 | 18 |
指定伺服器與用戶端主要版本 |
| Debian 叢集名稱 | main |
在同一台主機辨識不同執行個體 |
| 資料目錄 | /var/lib/postgresql/18/main |
保存資料庫檔案與 WAL |
| 主要設定 | /etc/postgresql/18/main/postgresql.conf |
設定監聽、TLS、WAL 與紀錄行為 |
| 用戶端驗證規則 | /etc/postgresql/18/main/pg_hba.conf |
依連線類型、資料庫、角色與來源選擇驗證方式 |
| 叢集控制 | pg_ctlcluster 18 main |
啟動、停止、重新啟動或重新載入該執行個體 |
| 狀態檢查 | pg_lsclusters |
顯示版本、名稱、連接埠、狀態與資料目錄 |
postgresql.service 是 Debian 用來統一管理多個資料庫叢集的上層服務。實際的 18/main 對應 postgresql@18-main.service。上層服務顯示 active 只代表統一管理層已啟動。特定執行個體還要透過 pg_lsclusters、實際執行個體狀態與一條 SQL 查詢完成驗收。
PostgreSQL 上游文件通常假設設定檔位於資料目錄。Debian 套件則將設定集中放在 /etc/postgresql/版本/叢集名稱,資料放在 /var/lib/postgresql/版本/叢集名稱。排錯時應透過 SHOW config_file;、SHOW hba_file; 與 SHOW data_directory; 查詢目前執行個體真正使用的位置。
若要從 PostgreSQL 官方 Apt 軟體庫(PGDG)安裝指定版本,安裝 postgresql-common 會先取得 Debian 的管理工具。接著設定官方軟體庫、更新套件索引,並確認目標版本出現 Candidate,才代表套件來源已準備完成。
PostgreSQL 啟動後會有一個主要伺服器程序負責監聽。新的用戶端連線通過基本檢查後,伺服器會建立後端程序(Backend Process)處理該連線的查詢。以本系列採用的遠端 TLS 連線為例,用戶端還要依序通過網路、TLS、身分驗證與資料庫授權。
CA 完成私鑰、CSR、憑證簽發與信任根部署後,每一次資料庫連線的 TLS 協商都由 PostgreSQL 使用已部署的伺服器憑證與私鑰直接完成。

圖(三)本系列的遠端連線抵達 PostgreSQL 後,依序通過 TLS、pg_hba.conf、SCRAM、LOGIN 與 CONNECT。工作階段(Session)建立後,每一筆 SQL 都會接受物件權限檢查。本機 Unix Socket 則由 local 規則選擇 peer 等驗證方式。
排錯時應由上往下確認連線停在哪一階段。工作階段建立前任一階段失敗,整條連線會被拒絕。建立後若缺少物件權限,通常只會拒絕該 SQL。
這三層都可能讓連線失敗,但控制的範圍不同:
| 控制位置 | 回答的問題 | 設定原則 |
|---|---|---|
| OPNsense/主機網路政策 | 哪些來源可以把封包送到 TCP 5432? | 只允許具有實際用途的來源網段 |
listen_addresses |
PostgreSQL 要在哪些本機位址接受 TCP 連線? | 只監聽需要提供資料庫服務的介面 |
pg_hba.conf |
到達 PostgreSQL 的連線使用哪種驗證方式,或直接拒絕? | 明確允許必要連線,其餘拒絕 |
| PostgreSQL 權限 | 登入後可以操作哪些資料庫物件? | 依角色用途授予最小權限 |
listen_addresses 只決定 PostgreSQL 綁定哪些本機 IP。即使只寫 10.77.30.11,任何能到達該位址的來源都可能嘗試連線。來源限制由防火牆與 pg_hba.conf 執行。
防火牆允許 TCP 5432,代表封包可以抵達資料庫節點。PostgreSQL 接著檢查監聽位址、TLS、pg_hba.conf、密碼與物件權限,全部通過後才接受登入及操作。
今天使用的是單向 TLS:PostgreSQL 提供伺服器憑證,用戶端使用預先信任的根憑證驗證資料庫伺服器。登入者身分則由 PostgreSQL 角色與 SCRAM-SHA-256 密碼驗證。
| 機制 | 驗證對象 | 主要作用 |
|---|---|---|
| TLS 與伺服器憑證 | 用戶端驗證 PostgreSQL 伺服器 | 加密連線,並降低把密碼與查詢送往冒牌伺服器的風險 |
| SCRAM-SHA-256 | PostgreSQL 驗證登入角色 | 證明用戶端知道該角色的密碼,伺服器保存 SCRAM Verifier |
| PostgreSQL 權限 | PostgreSQL 檢查已登入角色 | 決定該角色可以連哪個資料庫、使用哪個 Schema 與操作哪些物件 |
TLS 負責加密連線並驗證伺服器身分,SCRAM 負責驗證登入角色的密碼。兩者共同保護不同階段。
PostgreSQL 會在同一個 TCP 5432 上處理 TLS 與非 TLS 連線,再由用戶端設定和 pg_hba.conf 決定是否接受。
設定 ssl = on 會讓伺服器具備 TLS 能力。若要強制遠端連線使用 TLS,伺服器端要以 hostssl 規則接受指定來源並移除會匹配相同來源的寬鬆 host 規則。用戶端則應明確使用 sslmode=verify-full。
verify-full 會同時檢查兩件事:憑證能否連到用戶端信任的根 CA,以及實際連線名稱是否存在於伺服器憑證的 SAN。require 負責要求加密,verify-full 再加入信任鏈與伺服器名稱驗證。
每台資料庫節點應在本機產生自己的私鑰,再透過內部 CA 取得伺服器憑證。私鑰全程留在產生它的節點,各節點使用獨立金鑰。
每張伺服器憑證包含:
把規劃中的 VIP 名稱預先放入 SAN,能讓用戶端日後以固定服務名稱連線。用戶端會將自己輸入的連線名稱與 SAN 比對,VIP 當下所在的節點則可以隨高可用狀態切換。
伺服器送出的 server.crt 以節點憑證放在第一段,後面附上中繼 CA 憑證。用戶端另外保存根憑證。server.key 只讓 PostgreSQL 帳號讀取。ssl_ca_file 提供伺服器信任的 CA,pg_hba.conf 則決定是否要求用戶端憑證。
HBA 是主機式驗證(Host-Based Authentication)的縮寫。每一筆規則主要包含:
| 欄位 | 判斷內容 |
|---|---|
| TYPE | Unix Socket、一般 TCP、TLS TCP 或其他連線類型 |
| DATABASE | 用戶端要求連入的資料庫 |
| USER | 用戶端要求使用的 PostgreSQL 角色 |
| ADDRESS | 用戶端來源網段。local 規則沒有此欄 |
| METHOD | peer、scram-sha-256、cert、reject 等驗證方式 |
常見連線類型的差異如下:
local:匹配本機 Unix Socket。本機 TCP Loopback 屬於 host 類型。hostssl:只匹配已使用 TLS 的 TCP 連線。hostnossl:只匹配未使用 TLS 的 TCP 連線。host:同時可能匹配 TLS 與非 TLS TCP 連線。本機管理規則常使用 peer。它只適用於本機 Unix Socket,PostgreSQL 會取得目前作業系統帳號,確認它是否可以使用所要求的資料庫角色。因此 sudo -u postgres psql 可以讓作業系統的 postgres 帳號以資料庫的 postgres 角色進入。遠端密碼登入由另一條 host 或 hostssl 規則控制。作業系統帳號與 PostgreSQL 角色是兩套不同的身分資料。
PostgreSQL 由上往下選擇第一條同時符合連線類型、資料庫、角色與來源的規則,並以該規則的驗證結果結束判定。完全找不到符合規則的連線會被拒絕。
以下用一份簡化的 pg_hba.conf 示範規則排列方式:
# TYPE DATABASE USER ADDRESS METHOD
local all postgres peer
hostssl appdb app_user 10.77.40.0/24 scram-sha-256
host all all 0.0.0.0/0 reject
host all all ::/0 reject
本機作業系統的 postgres 帳號透過 Unix Socket 連線時會選中第一條。來源為 10.77.40.25 的用戶端若使用 TLS,以 app_user 連入 appdb,會選中第二條並進行 SCRAM 驗證。其他 IPv4 或 IPv6 遠端連線會分別選中最後兩條並被拒絕。
因此精確規則應放在寬鬆規則之前,檔案最後再以 IPv4 與 IPv6 的 reject 明確收尾。本次保留套件提供的本機管理規則,後續再依複寫與應用程式的實際來源加入精確的遠端規則。
修改 pg_hba.conf 後要重新載入設定。可以使用 pg_ctlcluster 18 main reload 或 PostgreSQL 的重新載入函式。若同時修改只能在啟動時讀取的參數,則要重新啟動。pg_hba_file_rules 可先檢查規則解析結果與錯誤,實際套用結果則要透過正向及負向連線測試確認。
password_encryption = 'scram-sha-256' 決定之後建立或重設密碼時產生哪種密碼驗證資料(Password Verifier)。既有角色會保留原本的 MD5 Verifier,重新設定密碼後才會產生 SCRAM Verifier。
pg_hba.conf 的 scram-sha-256 則決定符合該規則的連線要使用 SCRAM 完成驗證。前者控制密碼如何保存,後者控制這次連線使用哪種驗證方法,兩個位置需要互相配合。
兩個設定分別位於不同檔案:
# /etc/postgresql/18/main/postgresql.conf
password_encryption = 'scram-sha-256'
# /etc/postgresql/18/main/pg_hba.conf
# TYPE DATABASE USER ADDRESS METHOD
hostssl appdb app_user 10.77.40.0/24 scram-sha-256
第一段讓之後建立或重設的密碼保存為 SCRAM Verifier。第二段要求符合條件的遠端連線使用這份 Verifier 完成驗證。資料庫保存 Verifier,內容通常以 SCRAM-SHA-256$ 開頭。
設定角色密碼時可以使用 \password 互動輸入,避免把明文密碼直接留在 Shell History 或文件。人員、應用程式、複寫、監控與備份也應分別使用獨立且符合最小權限的憑證或密碼。
PostgreSQL 統一使用角色(Role)表示使用者與權限集合。具有 LOGIN 屬性的角色可以作為連線身分。沒有 LOGIN 的角色則適合集中一組權限,再授予其他角色成為成員。
登入與授權是兩個階段。pg_hba.conf 及 SCRAM 確認登入身分,角色的 LOGIN 屬性與資料庫的 CONNECT 權限決定能否建立 Session。成功連線後,每一筆 SQL 還要依序通過 Schema、Table 或 Sequence 等物件權限檢查。
物件擁有者(Owner)與被授權角色也要分開。擁有者可以修改或刪除自己建立的物件。只取得 SELECT 或寫入權限的角色,能力較容易限制與撤銷。
ALTER DEFAULT PRIVILEGES 會套用到指定擁有者未來建立的物件。既有物件維持目前權限,其他成員角色也各自擁有獨立的預設權限。應用程式擁有者、讀寫與唯讀角色,適合等到服務及存取需求明確後再分別建立。建立基線時則讓資料庫與管理角色維持最小權限。
PostgreSQL 18 可以分別記錄連線接收、驗證與授權階段。啟用連線與離線紀錄有助於保留複寫、Proxy 與切換過程的排錯證據,並需評估連線頻率、紀錄量、敏感資訊與保存期限。
常見錯誤可依層次判讀:
| 現象 | 優先檢查 |
|---|---|
| 名稱無法解析或沒有路由 | DNS、/etc/hosts、路由與 Gateway |
| Connection timed out | 防火牆、網路路徑與目的位址 |
| Connection refused | 服務狀態、TCP 5432 與 listen_addresses |
| 憑證驗證失敗 | 根憑證、憑證鏈、SAN、有效期間與系統時間 |
no pg_hba.conf entry |
連線類型、來源、資料庫、角色與規則順序 |
| Password authentication failed | 角色、密碼與 SCRAM Verifier |
| Permission denied | Database、Schema、Table 或 Sequence 權限 |
本次 Lab 會建立三台彼此獨立的 PostgreSQL 節點,並驗證它們具備一致且可檢查的單機基線。完整 GUI 位置、逐台指令與預期輸出放在實作文件,正文保留操作順序與驗證重點。
GitHub 詳細部署文件:Day 20|建立 PostgreSQL 節點與安全基線
| PVE 節點 | VM | VMID | Database VLAN IP | 資料磁碟 |
|---|---|---|---|---|
| pve01 | pg01 | 221 | 10.77.30.11/24 |
16 GiB local-lvm |
| pve02 | pg02 | 222 | 10.77.30.12/24 |
16 GiB local-lvm |
| pve03 | pg03 | 223 | 10.77.30.13/24 |
16 GiB local-lvm |
三台各使用 2 vCPU、2 GiB RAM、至少 24 GiB 系統磁碟,網卡接到 vmbr1 並使用 VLAN 30。Default Gateway 與 DNS 都是 10.77.30.1。
reject 收斂其餘 IPv4 與 IPv6 遠端連線。appdb 與 dba_admin,從磁碟到 PostgreSQL 規則逐層保存基準。每台先以 lsblk 和 wipefs -n 確認資料磁碟的身分,再建立檔案系統。完成掛載及套件安裝後,對照:
findmnt /var/lib/postgresql 顯示資料磁碟掛載位置與 ext4。/etc/fstab 只有一筆 LABEL=pgdata。pg_lsclusters 顯示 18/main 已啟動。SHOW data_directory; 顯示資料目錄位於 /var/lib/postgresql 之下。本次實作使用獨立資料磁碟,方便分開管理系統與資料庫的容量、掛載及 I/O。PostgreSQL 本身只要求資料目錄位於可用且權限正確的檔案系統。驗收時需同時核對裝置、掛載與 PostgreSQL 實際路徑。

圖(四)以 pg01 為例,資料磁碟掛載在 /var/lib/postgresql,PostgreSQL 實際資料目錄也位於這個掛載點之下。截圖時已完成後續 Patroni 部署,因此子目錄已由 Day 20 的 18/main 改為 18/patroni,但資料仍保存在同一顆資料磁碟。
三台使用相同的根憑證指紋建立對 ca01 的信任,但各自在本機產生不同私鑰。憑證檢查要確認:
server.key SHA-256 雜湊不同。這些結果用來確認三台共用同一個信任來源,但私鑰應由各節點自行產生並留在本機。正文以 pg01 與 pg02 的雜湊作為代表性抽查。

圖(五)pg01 的伺服器憑證可沿中繼 CA 驗證到受信任根憑證,SAN、有效期間與檔案權限也符合設定。畫面最後保留私鑰的 SHA-256 雜湊供跨節點比較。

圖(六)pg02 的私鑰同樣只允許作業系統的 postgres 帳號讀取,而且 SHA-256 雜湊與 pg01 不同,證明抽查的兩台各自持有獨立私鑰。pg03 依照相同流程建立,正文省略內容重複的截圖。
重新啟動 PostgreSQL 後,逐台確認:
pg_lsclusters 顯示 18/main 為 online。SHOW ssl; 顯示 TLS 能力已啟用。ss -lntp 顯示 TCP 5432 只綁定 Loopback 與該節點的 Database VLAN IP。SHOW hba_file; 指向實際使用的 pg_hba.conf。pg_hba_file_rules 沒有解析錯誤,檔案最後有 IPv4 與 IPv6 的拒絕規則。findmnt 與 SHOW data_directory; 在服務重啟後仍保持一致。SHOW ssl; 可以驗證伺服器已啟用 TLS。完整的遠端身分驗證還需要由受允許的用戶端以 verify-full 實際連線。本次 HBA 由本機管理規則與最終 reject 組成,因此先驗證憑證鏈、SAN、私鑰權限與伺服器設定。

圖(七)截圖時環境已由 Patroni 管理,資料目錄也已改為 18/patroni。這張圖補充確認節點進入高可用架構後,TLS、pg_hba.conf 與 Database VLAN 監聽設定仍持續生效。本次 18/main、Loopback 與 VLAN 監聽結果則以前述逐台指令驗收。
| 驗證項目 | 主要證據 | 可以得到的結論 |
|---|---|---|
| 驗證一:本次實作的儲存配置 | lsblk、findmnt 與 SHOW data_directory; |
三台 PostgreSQL 的資料目錄都位於各自規劃的資料磁碟 |
| 驗證二:獨立伺服器身分 | 憑證鏈、SAN、檔案權限,以及 pg01 與 pg02 的私鑰雜湊 | 三台依相同流程建立節點憑證。抽查的 pg01 與 pg02 各自持有不同私鑰 |
| 驗證三:最小服務基線 | pg_lsclusters、SHOW ssl;、TCP 5432 與 pg_hba_file_rules |
PostgreSQL 已在預期位址提供 TLS 能力,pg_hba.conf 可以正常解析並以 IPv4、IPv6 的最終 reject 規則收尾 |
SHOW data_directory; 確認資料落在預期位置。reject 規則收斂遠端資料庫連線。appdb 與基本管理角色,完成單機服務基線。下一篇是 Day 21|讓三台 PostgreSQL 保持資料同步:串流複寫、WAL、複寫槽與熱待命。我們會直接建立 PostgreSQL 原生複寫,觀察 WAL 如何從 pg01 傳到 pg02、pg03,以及 Write、Flush 與 Replay 為什麼代表不同的複寫進度。