前言
在現代雲端架構中,資料庫通常部署在受保護的私有網路環境(Private Subnet)中,以提升安全性。AWS RDS(Relational Database Service)作為主流的托管資料庫服務,提供了多種連線方式,但對於初學者來說,如何在不同環境(本機、Docker、Kubernetes)中正確連線到 RDS 往往充滿挑戰。
這篇文章將深入探討:
- AWS RDS 網路架構:公有子網 vs 私有子網的差異
- 直接連線方式:當 RDS 設為 Publicly Accessible 時
- SSH 隧道(SSH Tunneling):透過 Bastion Host 連線私有 RDS
- 容器環境連線:在 Docker 和 Kubernetes Pod 中使用 psql
- psql 完整命令參考:從基礎查詢到進階操作
- 安全最佳實踐:如何保護資料庫連線與憑證
- 常見問題排查:連線失敗的系統化診斷方法
無論你是在本機開發、容器化部署、或是 Kubernetes 叢集中操作,這篇文章都能幫助你建立安全可靠的資料庫連線。
AWS RDS 網路架構概覽
在開始連線之前,我們需要理解 AWS RDS 的網路架構。RDS 實例可以部署在不同的網路環境中,每種配置都有不同的連線方式和安全考量。
公有子網 vs 私有子網
兩種部署方式的比較
| 特性 | 公有子網 (Publicly Accessible) | 私有子網 (Private) |
|---|---|---|
| 直接連線 | ✅ 可以從網際網路直接連線 | ❌ 無法直接連線 |
| 安全性 | ⚠️ 較低,暴露在公網 | ✅ 高,完全隔離 |
| 連線方式 | psql 直連 | 需要 Bastion Host / VPN |
| 適用場景 | 開發測試環境 | 生產環境(推薦) |
| 成本 | RDS 費用 | RDS + Bastion Host 費用 |
| 維護複雜度 | 低 | 中等(需管理 Bastion) |
最佳實踐:
- ✅ 生產環境:使用私有子網 + Bastion Host 或 VPN
- ✅ 開發環境:可使用公有子網,但必須嚴格限制 Security Group
- ❌ 絕對避免:生產環境使用 Publicly Accessible + 0.0.0.0/0 (完全開放)
連線方式一:直接連線(公有子網)
當 RDS 實例設定為 Publicly Accessible 時,可以直接從網際網路連線。這是最簡單的方式,但也是最不安全的。
完整連線流程
步驟 1:安裝 PostgreSQL Client
不同作業系統的安裝方式:
# macOS (使用 Homebrew)
brew install postgresql
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install postgresql-client
# RHEL/CentOS/Amazon Linux
sudo yum install postgresql
# Windows (使用 Chocolatey)
choco install postgresql
# 驗證安裝
psql --version
# 應該顯示:psql (PostgreSQL) 15.x
步驟 2:取得 RDS 連線資訊
在 AWS Console 中取得以下資訊:
Endpoint(終端節點):
- AWS Console > RDS > Databases > 選擇你的資料庫
- 在 “Connectivity & security” 頁籤中找到 “Endpoint”
- 格式:
yourdb.xxx.ap-east-1.rds.amazonaws.com
Port(埠號):
- 預設為
5432 - 可在同一頁面確認
- 預設為
Master username(主帳號):
- 在 “Configuration” 頁籤中找到
- 預設通常是
postgres
Database name(資料庫名稱):
- 建立時指定的初始資料庫名稱
- 預設為
postgres
密碼:
- 建立時設定的密碼
- 或從 AWS Secrets Manager 中取得
步驟 3:設定 Security Group
這是最關鍵的步驟!Security Group 就像是 RDS 的防火牆。
AWS Console 操作步驟:
- AWS Console > RDS > Databases > 選擇你的資料庫
- 點擊 “VPC security groups” 連結
- 選擇 “Inbound rules” 頁籤
- 點擊 “Edit inbound rules”
- 新增規則:
Type: PostgreSQL Protocol: TCP Port range: 5432 Source: My IP (AWS 會自動偵測你的 IP) Description: Dev access from my IP - 點擊 “Save rules”
步驟 4:連線到 RDS
基本連線命令:
psql \
-h yourdb.xxx.ap-east-1.rds.amazonaws.com \
-p 5432 \
-U postgres \
-d postgres
# 參數說明:
# -h: RDS Endpoint (host)
# -p: 埠號 (port)
# -U: 使用者名稱 (username)
# -d: 資料庫名稱 (database)
執行後會提示輸入密碼:
Password for user postgres:
成功後會看到:
psql (15.4)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
postgres=>
使用環境變數簡化連線
為了避免每次都輸入密碼,可以使用環境變數:
# 設定環境變數
export PGHOST=yourdb.xxx.ap-east-1.rds.amazonaws.com
export PGPORT=5432
export PGUSER=postgres
export PGDATABASE=postgres
export PGPASSWORD=your-password # ⚠️ 不安全,見下方說明
# 現在可以直接執行 psql
psql
更安全的做法:使用 .pgpass 檔案
# 建立 ~/.pgpass 檔案
echo "yourdb.xxx.ap-east-1.rds.amazonaws.com:5432:postgres:postgres:your-password" > ~/.pgpass
# 設定權限(必須是 0600,否則 psql 會拒絕使用)
chmod 0600 ~/.pgpass
# 現在 psql 會自動讀取密碼
psql -h yourdb.xxx.ap-east-1.rds.amazonaws.com -U postgres -d postgres
.pgpass 檔案格式:
hostname:port:database:username:password
可以使用萬用字元:
# 允許任何資料庫
yourdb.xxx.ap-east-1.rds.amazonaws.com:5432:*:postgres:your-password
# 允許任何 RDS 實例
*.ap-east-1.rds.amazonaws.com:5432:*:postgres:your-password
連線方式二:SSH 隧道(私有子網)
在生產環境中,RDS 通常部署在私有子網(Private Subnet)中,無法直接從網際網路連線。這時需要透過 Bastion Host(跳板機)建立 SSH 隧道。
SSH 隧道運作原理
SSH 隧道(SSH Tunneling)也稱為 SSH Port Forwarding,它可以讓你透過 SSH 連線,將本機的某個埠號轉發到遠端伺服器的另一個埠號。
網路拓撲圖
步驟 1:準備 Bastion Host
Bastion Host(跳板機)是部署在公有子網的 EC2 實例,用於安全地存取私有資源。
建立 Bastion Host:
啟動 EC2 實例:
- AMI: Amazon Linux 2 或 Ubuntu
- Instance Type: t3.micro(夠用)
- Network: 選擇 RDS 所在的 VPC
- Subnet: Public Subnet(必須有 Internet Gateway)
- Auto-assign Public IP: Enable
- Security Group: 建立新的 SG(見下方)
設定 Bastion Security Group:
Inbound Rules: - Type: SSH Protocol: TCP Port: 22 Source: My IP (限制只有你的 IP 可以連線) Outbound Rules: - Type: All traffic Destination: 0.0.0.0/0設定 RDS Security Group:
Inbound Rules: - Type: PostgreSQL Protocol: TCP Port: 5432 Source: Bastion Security Group ID (sg-xxx)這樣設定後,只有從 Bastion Host 發出的連線才能連到 RDS。
下載 SSH 私鑰:
- 建立 EC2 時會要求你建立或選擇 Key Pair
- 下載
.pem檔案到本機(例如~/Downloads/bastion.pem) - 移動到安全位置並設定權限:
mkdir -p ~/.ssh mv ~/Downloads/bastion.pem ~/.ssh/ chmod 400 ~/.ssh/bastion.pem
步驟 2:測試 SSH 連線
先確認可以 SSH 到 Bastion Host:
ssh -i ~/.ssh/bastion.pem ec2-user@BASTION_PUBLIC_IP
# Amazon Linux 使用 ec2-user
# Ubuntu 使用 ubuntu
成功後會看到:
[ec2-user@ip-10-0-1-50 ~]$
這時你已經在 Bastion Host 上了。可以測試是否能連到 RDS:
# 在 Bastion Host 上安裝 psql
sudo yum install postgresql -y # Amazon Linux
# 或
sudo apt-get install postgresql-client -y # Ubuntu
# 測試連線 RDS
psql -h yourdb.xxx.ap-east-1.rds.amazonaws.com -U postgres -d postgres
# 如果成功,代表 Bastion → RDS 的網路路徑是通的
# 輸入 \q 離開 psql
# 輸入 exit 離開 Bastion
步驟 3:建立 SSH 隧道
回到本機,建立 SSH 隧道:
ssh -i ~/.ssh/bastion.pem \
-L 5433:yourdb.xxx.ap-east-1.rds.amazonaws.com:5432 \
-N \
ec2-user@BASTION_PUBLIC_IP
參數說明:
-i ~/.ssh/bastion.pem: 指定 SSH 私鑰-L 5433:yourdb.xxx.ap-east-1.rds.amazonaws.com:5432: 本機埠號轉發- 格式:
本機埠號:目標主機:目標埠號 - 將本機的 5433 轉發到 RDS 的 5432
- 格式:
-N: 不執行遠端命令(只建立隧道)ec2-user@BASTION_PUBLIC_IP: 連線到 Bastion Host
執行後,終端機會卡住(這是正常的,表示隧道正在運作)。
步驟 4:透過隧道連線 RDS
另開一個終端機視窗,執行:
psql -h localhost -p 5433 -U postgres -d postgres
# 或使用 127.0.0.1
psql -h 127.0.0.1 -p 5433 -U postgres -d postgres
重要:
- Host 必須是
localhost或127.0.0.1(不是 RDS endpoint) - Port 必須是
5433(你在-L參數中設定的本機埠號)
成功後會看到:
psql (15.4)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
postgres=>
背景執行 SSH 隧道
每次都要開一個終端機視窗很麻煩,可以將 SSH 隧道放到背景執行:
# 方法 1: 使用 -f 參數
ssh -f -i ~/.ssh/bastion.pem \
-L 5433:yourdb.xxx.ap-east-1.rds.amazonaws.com:5432 \
-N \
ec2-user@BASTION_PUBLIC_IP
# -f: 在背景執行
# 方法 2: 使用 nohup
nohup ssh -i ~/.ssh/bastion.pem \
-L 5433:yourdb.xxx.ap-east-1.rds.amazonaws.com:5432 \
-N \
ec2-user@BASTION_PUBLIC_IP &
# 檢查隧道是否運作
lsof -i :5433
# 應該會看到 ssh 程序正在監聽 5433 埠號
# 關閉隧道
kill $(lsof -t -i :5433)
使用 SSH Config 簡化設定
編輯 ~/.ssh/config:
# Bastion Host
Host bastion
HostName BASTION_PUBLIC_IP
User ec2-user
IdentityFile ~/.ssh/bastion.pem
ServerAliveInterval 60
ServerAliveCountMax 3
# RDS Tunnel
Host rds-tunnel
HostName localhost
User postgres
LocalForward 5433 yourdb.xxx.ap-east-1.rds.amazonaws.com:5432
ProxyJump bastion
設定後,連線變得更簡單:
# 建立隧道
ssh -N rds-tunnel
# 在另一個終端機連線
psql -h localhost -p 5433 -U postgres -d postgres
連線方式三:Docker 容器環境
在容器化環境中,應用程式通常運行在 Docker 容器內。我們需要從容器內部連線到 RDS。
Docker 網路架構
情境 1:從現有容器連線
如果你的應用容器已經在運行,可以直接進入容器執行 psql:
# 列出所有運行中的容器
docker ps
# 輸出範例:
# CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
# abc123def456 node:18 "node app" 2 hours ago Up 2 hours app-server
# 進入容器(使用 bash)
docker exec -it app-server bash
# 或使用 sh(如果是 Alpine Linux)
docker exec -it app-server sh
# 在容器內安裝 psql(如果尚未安裝)
# Debian/Ubuntu 基底
apt-get update && apt-get install -y postgresql-client
# Alpine Linux 基底
apk add --no-cache postgresql-client
# 連線到 RDS
psql -h yourdb.xxx.ap-east-1.rds.amazonaws.com -U postgres -d postgres
情境 2:啟動專用的 psql 客戶端容器
如果只是想快速連線查詢,可以使用官方 PostgreSQL 映像檔:
# 方法 1: 互動模式(推薦用於臨時查詢)
docker run -it --rm \
--name postgres-client \
-e PGPASSWORD=your-password \
postgres:15 \
psql -h yourdb.xxx.ap-east-1.rds.amazonaws.com \
-U postgres \
-d postgres
# 參數說明:
# -it: 互動模式
# --rm: 容器結束後自動刪除
# --name: 容器名稱
# -e PGPASSWORD: 設定密碼環境變數(避免每次輸入)
# postgres:15: 使用 PostgreSQL 15 映像檔
# psql ...: 容器啟動後執行的命令
# 方法 2: 進入容器 shell
docker run -it --rm postgres:15 bash
# 在容器內執行
psql -h yourdb.xxx.ap-east-1.rds.amazonaws.com -U postgres -d postgres
情境 3:透過 Docker Compose
在 docker-compose.yml 中定義 psql 客戶端:
version: '3.8'
services:
# 你的應用服務
app:
image: your-app:latest
environment:
- DATABASE_URL=postgresql://postgres:password@yourdb.xxx.ap-east-1.rds.amazonaws.com:5432/postgres
networks:
- app-network
# psql 客戶端(用於管理)
db-client:
image: postgres:15
environment:
- PGHOST=yourdb.xxx.ap-east-1.rds.amazonaws.com
- PGPORT=5432
- PGUSER=postgres
- PGPASSWORD=your-password
- PGDATABASE=postgres
networks:
- app-network
# 保持容器運行
command: tail -f /dev/null
# 或使用 stdin_open 和 tty
stdin_open: true
tty: true
networks:
app-network:
driver: bridge
使用方式:
# 啟動所有服務
docker-compose up -d
# 進入 db-client 容器
docker-compose exec db-client bash
# 在容器內直接執行 psql(環境變數已設定)
psql
# 或從容器外直接執行 SQL
docker-compose exec db-client psql -c "SELECT version();"
# 停止服務
docker-compose down
從容器連線的安全考量
連線方式四:Kubernetes Pod 環境
在 Kubernetes 叢集中(例如 AWS EKS),Pod 與 RDS 之間的連線涉及更複雜的網路架構。
Kubernetes 與 RDS 網路架構
關鍵概念:Pod 到 RDS 的連線路徑
Pod Network(CNI):
- EKS 使用 VPC CNI,Pod 獲得 VPC 子網的 IP 位址
- Pod IP 範圍通常與 Node 所在的子網相同
Security Group 設定:
- RDS Security Group 必須允許來自 Node Security Group 的連線
- 因為 Pod 的流量會透過 Node 轉發(SNAT)
DNS 解析:
- Pod 內可以直接使用 RDS Endpoint
- CoreDNS 會將其解析為 RDS 的私有 IP
情境 1:從現有 Pod 連線
# 列出所有 Pod
kubectl get pods -n default
# 輸出範例:
# NAME READY STATUS RESTARTS AGE
# app-deployment-abc123 1/1 Running 0 2h
# redis-xyz789 1/1 Running 0 5h
# 進入 Pod(執行 bash)
kubectl exec -it app-deployment-abc123 -n default -- bash
# 或使用 sh
kubectl exec -it app-deployment-abc123 -n default -- sh
# 在 Pod 內安裝 psql(如果尚未安裝)
apt-get update && apt-get install -y postgresql-client
# 連線到 RDS
psql -h yourdb.xxx.ap-east-1.rds.amazonaws.com -U postgres -d postgres
# 離開 Pod
exit
情境 2:直接從 Pod 執行單一命令
不需要進入 Pod,直接執行 psql 命令:
# 執行單一 SQL 查詢
kubectl exec -it app-deployment-abc123 -n default -- \
psql -h yourdb.xxx.ap-east-1.rds.amazonaws.com \
-U postgres \
-d postgres \
-c "SELECT version();"
# 執行多個命令
kubectl exec -it app-deployment-abc123 -n default -- \
psql -h yourdb.xxx.ap-east-1.rds.amazonaws.com \
-U postgres \
-d postgres \
-c "SELECT * FROM users LIMIT 5;"
情境 3:部署專用的 psql 客戶端 Pod
建立一個臨時的 PostgreSQL 客戶端 Pod:
kubectl run psql-client \
--rm -i --tty \
--image=postgres:15 \
--namespace=default \
--env="PGHOST=yourdb.xxx.ap-east-1.rds.amazonaws.com" \
--env="PGPORT=5432" \
--env="PGUSER=postgres" \
--env="PGPASSWORD=your-password" \
--env="PGDATABASE=postgres" \
--restart=Never \
--command -- psql
# 參數說明:
# --rm: Pod 結束後自動刪除
# -i --tty: 互動模式
# --image: 使用的容器映像
# --env: 設定環境變數
# --restart=Never: 不自動重啟
# --command -- psql: 執行 psql 命令
成功後會直接進入 psql 互動介面:
psql (15.4)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
postgres=>
離開 psql 後,Pod 會自動刪除。
情境 4:使用 Kubernetes Deployment
建立一個長期運行的 psql 客戶端 Deployment:
# psql-client-deployment.yaml
apiVersion: v1
kind: Secret
metadata:
name: rds-credentials
namespace: default
type: Opaque
stringData:
PGHOST: "yourdb.xxx.ap-east-1.rds.amazonaws.com"
PGPORT: "5432"
PGUSER: "postgres"
PGPASSWORD: "your-password"
PGDATABASE: "postgres"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: psql-client
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: psql-client
template:
metadata:
labels:
app: psql-client
spec:
containers:
- name: postgres-client
image: postgres:15
envFrom:
- secretRef:
name: rds-credentials
command:
- sh
- -c
- |
# 安裝額外工具
apt-get update && apt-get install -y vim curl
# 保持容器運行
tail -f /dev/null
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
部署並使用:
# 部署
kubectl apply -f psql-client-deployment.yaml
# 等待 Pod 就緒
kubectl wait --for=condition=ready pod -l app=psql-client --timeout=60s
# 進入 Pod
kubectl exec -it deployment/psql-client -- bash
# 在 Pod 內執行 psql(環境變數已設定)
psql
# 或從外部直接執行查詢
kubectl exec deployment/psql-client -- psql -c "SELECT current_database();"
# 刪除部署
kubectl delete -f psql-client-deployment.yaml
使用 Kubernetes Jobs 執行一次性查詢
如果只需要執行一次性的資料庫操作(如資料遷移),可以使用 Job:
# db-migration-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
namespace: default
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migration
image: postgres:15
env:
- name: PGHOST
valueFrom:
secretKeyRef:
name: rds-credentials
key: PGHOST
- name: PGUSER
valueFrom:
secretKeyRef:
name: rds-credentials
key: PGUSER
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: rds-credentials
key: PGPASSWORD
- name: PGDATABASE
valueFrom:
secretKeyRef:
name: rds-credentials
key: PGDATABASE
command:
- sh
- -c
- |
# 執行資料遷移 SQL
psql <<EOF
CREATE TABLE IF NOT EXISTS migrations (
id SERIAL PRIMARY KEY,
version VARCHAR(50) NOT NULL,
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO migrations (version) VALUES ('v1.0.0');
EOF
echo "Migration completed successfully"
backoffLimit: 3
執行 Job:
# 建立 Job
kubectl apply -f db-migration-job.yaml
# 查看 Job 狀態
kubectl get jobs
# 查看 Job 日誌
kubectl logs job/db-migration
# 刪除 Job
kubectl delete job db-migration
psql 互動介面完整命令參考
進入 psql 後,你會看到 postgres=> 或 dbname=> 提示符。以下是完整的命令參考。
Meta-Commands(以 \ 開頭的命令)
這些命令不是 SQL,而是 psql 特有的管理命令:
資料庫管理命令
| 命令 | 說明 | 範例 |
|---|---|---|
\l 或 \list | 列出所有資料庫 | \l |
\l+ | 列出資料庫(包含大小和描述) | \l+ |
\c dbname | 切換到指定資料庫 | \c my_database |
\conninfo | 顯示當前連線資訊 | \conninfo |
\du | 列出所有使用者(角色) | \du |
\du+ | 列出使用者詳細資訊 | \du+ |
範例輸出:
-- 列出所有資料庫
postgres=> \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
my_db | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(4 rows)
-- 切換資料庫
postgres=> \c my_db
You are now connected to database "my_db" as user "postgres".
my_db=>
-- 顯示連線資訊
my_db=> \conninfo
You are connected to database "my_db" as user "postgres" on host "yourdb.xxx.rds.amazonaws.com" at port "5432".
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Schema 和資料表管理
| 命令 | 說明 | 範例 |
|---|---|---|
\dn | 列出所有 schema | \dn |
\dt | 列出當前 schema 的資料表 | \dt |
\dt *.* | 列出所有 schema 的資料表 | \dt *.* |
\dt public.* | 列出 public schema 的資料表 | \dt public.* |
\dt+ | 列出資料表(包含大小) | \dt+ |
\d tablename | 描述資料表結構 | \d users |
\d+ tablename | 詳細描述資料表 | \d+ users |
\di | 列出索引 | \di |
\dv | 列出 views | \dv |
\df | 列出函式 | \df |
\ds | 列出 sequences | \ds |
範例輸出:
-- 列出資料表
my_db=> \dt
List of relations
Schema | Name | Type | Owner
--------+---------------+-------+----------
public | users | table | postgres
public | posts | table | postgres
public | comments | table | postgres
(3 rows)
-- 描述資料表結構
my_db=> \d users
Table "public.users"
Column | Type | Collation | Nullable | Default
------------+-----------------------------+-----------+----------+-----------------------------------
id | integer | | not null | nextval('users_id_seq'::regclass)
email | character varying(255) | | not null |
name | character varying(100) | | |
created_at | timestamp without time zone | | | CURRENT_TIMESTAMP
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"users_email_key" UNIQUE CONSTRAINT, btree (email)
Referenced by:
TABLE "posts" CONSTRAINT "posts_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)
顯示模式控制
| 命令 | 說明 | 範例 |
|---|---|---|
\x | 切換擴展顯示模式(垂直顯示) | \x |
\x auto | 自動判斷使用哪種顯示模式 | \x auto |
\pset format | 設定輸出格式 | \pset format wrapped |
\timing | 開啟/關閉查詢時間顯示 | \timing |
範例:
-- 預設水平顯示
my_db=> SELECT * FROM users LIMIT 1;
id | email | name | created_at
----+------------------+--------+--------------------
1 | user@example.com | John | 2024-01-01 10:00:00
(1 row)
-- 開啟擴展模式(垂直顯示)
my_db=> \x
Expanded display is on.
my_db=> SELECT * FROM users LIMIT 1;
-[ RECORD 1 ]------------------------
id | 1
email | user@example.com
name | John
created_at | 2024-01-01 10:00:00
-- 關閉擴展模式
my_db=> \x
Expanded display is off.
-- 開啟查詢時間顯示
my_db=> \timing
Timing is on.
my_db=> SELECT COUNT(*) FROM users;
count
-------
1000
(1 row)
Time: 5.234 ms
SQL 查詢命令
以下是常用的 SQL 查詢範例:
-- 查看資料庫版本
SELECT version();
-- 查看當前資料庫
SELECT current_database();
-- 查看當前使用者
SELECT current_user;
-- 查看所有資料表(SQL 方式)
SELECT tablename FROM pg_tables WHERE schemaname = 'public';
-- 查詢前 N 筆資料
SELECT * FROM users LIMIT 5;
-- 查詢特定欄位
SELECT id, email, name FROM users;
-- 條件查詢
SELECT * FROM users WHERE email = 'user@example.com';
-- 模糊查詢
SELECT * FROM users WHERE name LIKE 'John%';
-- 排序
SELECT * FROM users ORDER BY created_at DESC;
-- 計數
SELECT COUNT(*) FROM users;
-- 分組統計
SELECT DATE(created_at), COUNT(*)
FROM users
GROUP BY DATE(created_at)
ORDER BY DATE(created_at) DESC;
-- 查詢不重複值
SELECT DISTINCT email FROM users;
-- JOIN 查詢
SELECT u.name, p.title
FROM users u
INNER JOIN posts p ON u.id = p.user_id
LIMIT 10;
資料更新命令
-- 更新單一記錄
UPDATE users
SET name = 'John Doe'
WHERE id = 1;
-- 更新多筆記錄
UPDATE users
SET email = LOWER(email)
WHERE email LIKE '%@EXAMPLE.COM';
-- 批次更新
UPDATE users
SET updated_at = CURRENT_TIMESTAMP
WHERE created_at < '2024-01-01';
-- 刪除記錄
DELETE FROM users WHERE id = 999;
-- 刪除符合條件的記錄
DELETE FROM users WHERE created_at < '2020-01-01';
-- 清空資料表(保留結構)
TRUNCATE TABLE users;
-- 插入記錄
INSERT INTO users (email, name)
VALUES ('new@example.com', 'New User');
-- 插入多筆記錄
INSERT INTO users (email, name) VALUES
('user1@example.com', 'User 1'),
('user2@example.com', 'User 2'),
('user3@example.com', 'User 3');
-- 插入並回傳結果
INSERT INTO users (email, name)
VALUES ('test@example.com', 'Test User')
RETURNING id, email;
交易控制
-- 開始交易
BEGIN;
-- 執行多個操作
UPDATE users SET name = 'Updated' WHERE id = 1;
INSERT INTO audit_log (action, user_id) VALUES ('update', 1);
-- 提交交易
COMMIT;
-- 或回滾交易
ROLLBACK;
-- 設定儲存點
BEGIN;
UPDATE users SET name = 'First Update' WHERE id = 1;
SAVEPOINT sp1;
UPDATE users SET name = 'Second Update' WHERE id = 2;
-- 回滾到儲存點
ROLLBACK TO SAVEPOINT sp1;
-- 只有第一個更新會生效
COMMIT;
進階查詢技巧
-- 查看資料表大小
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
-- 查看資料庫大小
SELECT
datname,
pg_size_pretty(pg_database_size(datname)) AS size
FROM pg_database
ORDER BY pg_database_size(datname) DESC;
-- 查看正在執行的查詢
SELECT
pid,
usename,
application_name,
client_addr,
state,
query,
query_start
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY query_start;
-- 終止慢查詢
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'active'
AND query_start < now() - interval '5 minutes';
-- 查看索引使用情況
SELECT
schemaname,
tablename,
indexname,
idx_scan,
idx_tup_read,
idx_tup_fetch
FROM pg_stat_user_indexes
ORDER BY idx_scan DESC;
-- 查看未使用的索引
SELECT
schemaname,
tablename,
indexname
FROM pg_stat_user_indexes
WHERE idx_scan = 0
AND indexname NOT LIKE '%_pkey';
匯出與匯入
-- 匯出查詢結果到 CSV
\copy (SELECT * FROM users) TO '/tmp/users.csv' WITH CSV HEADER;
-- 從 CSV 匯入資料
\copy users FROM '/tmp/users.csv' WITH CSV HEADER;
-- 匯出整個資料表
\copy users TO '/tmp/users_backup.csv' WITH CSV HEADER;
注意:\copy 在客戶端執行,檔案路徑是相對於執行 psql 的機器。COPY(沒有反斜線)在伺服器端執行,檔案路徑是相對於資料庫伺服器。
其他實用命令
| 命令 | 說明 | 範例 |
|---|---|---|
\? | 顯示所有 backslash 命令 | \? |
\h SQL | 顯示 SQL 語法說明 | \h SELECT |
\q | 離開 psql | \q |
\e | 開啟編輯器編輯最後的命令 | \e |
\i filename | 執行檔案中的 SQL | \i /path/to/script.sql |
\o filename | 將輸出重導向到檔案 | \o /tmp/output.txt |
\! command | 執行 shell 命令 | \! ls -la |
\password | 更改密碼 | \password postgres |
\echo text | 顯示文字 | \echo 'Hello World' |
安全最佳實踐
密碼管理
1. 使用 IAM 認證(AWS RDS)
AWS RDS 支援使用 IAM 角色進行認證,無需管理密碼:
# 啟用 RDS IAM 認證
# AWS Console > RDS > Modify > Database authentication > Enable IAM authentication
# 在 EC2/EKS 上使用 IAM Role 產生臨時 token
TOKEN=$(aws rds generate-db-auth-token \
--hostname yourdb.xxx.ap-east-1.rds.amazonaws.com \
--port 5432 \
--username iamuser \
--region ap-east-1)
# 使用 token 連線(15 分鐘有效)
psql "host=yourdb.xxx.ap-east-1.rds.amazonaws.com \
port=5432 \
dbname=postgres \
user=iamuser \
password=$TOKEN \
sslmode=require"
2. 強制使用 SSL 連線
-- 檢查是否使用 SSL
SELECT ssl_is_used();
-- 查看 SSL 版本
\conninfo
-- 強制要求 SSL(在 psql 連線字串中)
psql "host=yourdb.xxx.rds.amazonaws.com \
port=5432 \
dbname=postgres \
user=postgres \
sslmode=require"
-- sslmode 選項:
-- disable: 不使用 SSL
-- allow: 嘗試 SSL,失敗則降級
-- prefer: 優先使用 SSL(預設)
-- require: 強制 SSL,不驗證憑證
-- verify-ca: 強制 SSL,驗證憑證
-- verify-full: 強制 SSL,驗證憑證和主機名
3. 最小權限原則
-- 建立唯讀使用者
CREATE ROLE readonly WITH LOGIN PASSWORD 'secure-password';
-- 授予連線權限
GRANT CONNECT ON DATABASE my_db TO readonly;
-- 授予 schema 使用權限
GRANT USAGE ON SCHEMA public TO readonly;
-- 授予所有資料表的 SELECT 權限
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
-- 對未來建立的資料表也生效
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO readonly;
-- 建立應用程式使用者(讀寫權限,但不能刪除資料表)
CREATE ROLE appuser WITH LOGIN PASSWORD 'secure-password';
GRANT CONNECT ON DATABASE my_db TO appuser;
GRANT USAGE ON SCHEMA public TO appuser;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO appuser;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO appuser;
4. 審計與監控
-- 啟用查詢日誌(AWS RDS Parameter Group)
-- log_statement = 'all' -- 記錄所有語句(效能影響大)
-- log_statement = 'ddl' -- 只記錄 DDL(CREATE, ALTER, DROP)
-- log_statement = 'mod' -- 記錄資料修改(INSERT, UPDATE, DELETE)
-- 查看最近的連線
SELECT
usename,
application_name,
client_addr,
backend_start,
state
FROM pg_stat_activity
ORDER BY backend_start DESC;
-- 查看失敗的連線嘗試(需啟用 log_connections)
-- 在 CloudWatch Logs 中查看:
-- /aws/rds/instance/your-db/postgresql
5. 網路隔離
最佳實踐檢查清單:
- ✅ RDS 部署在私有子網
- ✅ Publicly Accessible 設為 No
- ✅ Security Group 限制來源 IP 或 Security Group
- ✅ 使用 Bastion Host 或 VPN 連線
- ✅ 啟用 SSL 連線(sslmode=require)
- ✅ 使用 IAM 認證(如可行)
- ✅ 定期輪換密碼
- ✅ 使用最小權限原則
- ✅ 啟用自動備份
- ✅ 啟用 Multi-AZ(高可用性)
- ✅ 啟用 CloudWatch 監控
- ✅ 啟用 Performance Insights
常見問題排查
問題 1:連線逾時(Connection Timeout)
診斷命令:
# 1. 測試網路連通性
# 從本機測試
telnet yourdb.xxx.ap-east-1.rds.amazonaws.com 5432
# 或使用 nc
nc -zv yourdb.xxx.ap-east-1.rds.amazonaws.com 5432
# 成功會顯示:
# Connection to yourdb.xxx.ap-east-1.rds.amazonaws.com 5432 port [tcp/postgresql] succeeded!
# 2. DNS 解析測試
nslookup yourdb.xxx.ap-east-1.rds.amazonaws.com
dig yourdb.xxx.ap-east-1.rds.amazonaws.com
# 3. 路由追蹤
traceroute yourdb.xxx.ap-east-1.rds.amazonaws.com
# 4. 檢查本機防火牆(macOS)
sudo pfctl -sr | grep 5432
# 5. 從 Bastion Host 測試
ssh -i ~/.ssh/bastion.pem ec2-user@BASTION_IP
telnet yourdb.xxx.ap-east-1.rds.amazonaws.com 5432
問題 2:認證失敗(Authentication Failed)
psql: error: connection to server at "yourdb.xxx.rds.amazonaws.com" (10.0.2.100), port 5432 failed: FATAL: password authentication failed for user "postgres"
可能原因與解決方法:
# 1. 密碼錯誤
# 從 AWS Secrets Manager 取得正確密碼
aws secretsmanager get-secret-value \
--secret-id rds-db-credentials \
--query SecretString \
--output text | jq -r .password
# 2. 使用者不存在
# 連線為 master user,建立新使用者
psql -h ... -U postgres -c "CREATE USER myuser WITH PASSWORD 'mypassword';"
# 3. 資料庫不存在
# 列出所有資料庫
psql -h ... -U postgres -l
# 建立資料庫
psql -h ... -U postgres -c "CREATE DATABASE my_db;"
# 4. IP 位址被拒絕(pg_hba.conf)
# RDS 的 pg_hba.conf 由 AWS 管理,但可以透過 Parameter Group 調整
# 檢查連線是否使用 SSL
psql "host=... sslmode=require user=postgres dbname=postgres"
問題 3:SSL 連線問題
psql: error: connection to server at "yourdb.xxx.rds.amazonaws.com" failed: SSL connection has been closed unexpectedly
解決方法:
# 1. 下載 RDS SSL 憑證
wget https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
# 2. 使用憑證連線
psql "host=yourdb.xxx.rds.amazonaws.com \
port=5432 \
dbname=postgres \
user=postgres \
sslmode=verify-full \
sslrootcert=global-bundle.pem"
# 3. 或降低 SSL 要求(不建議用於生產環境)
psql "host=yourdb.xxx.rds.amazonaws.com \
port=5432 \
dbname=postgres \
user=postgres \
sslmode=require"
問題 4:Kubernetes Pod 無法連線
# 1. 檢查 Pod 是否可以解析 RDS Endpoint
kubectl exec -it pod-name -- nslookup yourdb.xxx.ap-east-1.rds.amazonaws.com
# 2. 檢查 Pod 是否可以連通 RDS 埠號
kubectl exec -it pod-name -- nc -zv yourdb.xxx.ap-east-1.rds.amazonaws.com 5432
# 3. 檢查 Node Security Group
# AWS Console > EC2 > Security Groups
# 找到 EKS Node 的 SG,記下 SG ID (例如 sg-0123456789abcdef0)
# 4. 確認 RDS Security Group 允許 Node SG
# AWS Console > RDS > Databases > Your DB > Connectivity & security
# VPC security groups > Edit inbound rules
# 新增規則:
# Type: PostgreSQL
# Source: sg-0123456789abcdef0 (Node SG)
# 5. 檢查環境變數是否正確設定
kubectl exec -it pod-name -- env | grep PG
問題 5:連線數過多
psql: error: FATAL: sorry, too many clients already
解決方法:
-- 查看當前連線數
SELECT COUNT(*) FROM pg_stat_activity;
-- 查看最大連線數限制
SHOW max_connections;
-- 查看各使用者的連線數
SELECT
usename,
COUNT(*) as connection_count
FROM pg_stat_activity
GROUP BY usename
ORDER BY connection_count DESC;
-- 終止閒置連線
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle'
AND state_change < now() - interval '10 minutes';
-- 增加最大連線數(需修改 RDS Parameter Group)
-- AWS Console > RDS > Parameter groups > Edit parameters
-- 找到 max_connections,修改為更大的值(例如 200)
-- 套用後需要重啟 RDS 實例
效能優化技巧
連線池(Connection Pooling)
對於高流量應用,使用連線池可以大幅提升效能:
# 安裝 PgBouncer(在應用伺服器或 Bastion Host)
sudo apt-get install pgbouncer
# 設定 /etc/pgbouncer/pgbouncer.ini
[databases]
my_db = host=yourdb.xxx.rds.amazonaws.com port=5432 dbname=my_db
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
# 應用程式連到 PgBouncer,而非直接連 RDS
psql -h pgbouncer-host -p 6432 -U postgres -d my_db
查詢優化
-- 使用 EXPLAIN ANALYZE 分析查詢
EXPLAIN ANALYZE
SELECT u.name, p.title
FROM users u
JOIN posts p ON u.id = p.user_id
WHERE u.email = 'user@example.com';
-- 建立索引加速查詢
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_posts_user_id ON posts(user_id);
-- 檢視慢查詢(需啟用 log_min_duration_statement)
-- 在 RDS Parameter Group 設定:
-- log_min_duration_statement = 1000 (記錄超過 1 秒的查詢)
-- 在 CloudWatch Logs 中查看慢查詢日誌
總結
本文深入探討了如何在各種環境中連線 AWS RDS PostgreSQL:
- 直接連線:適用於開發測試,需正確設定 Security Group
- SSH 隧道:生產環境最佳實踐,透過 Bastion Host 安全連線
- Docker 容器:使用官方 PostgreSQL 映像檔或在應用容器中安裝 psql
- Kubernetes Pod:理解 Pod 網路與 Node Security Group 的關係
- psql 命令:完整的互動介面命令參考,從基礎到進階
- 安全實踐:IAM 認證、SSL 連線、最小權限、網路隔離
- 問題排查:系統化診斷連線、認證、網路問題
關鍵要點:
- ✅ 生產環境永遠使用私有子網 + Bastion Host
- ✅ 使用
.pgpass檔案或 Secret 管理服務儲存密碼 - ✅ 強制使用 SSL 連線(sslmode=require)
- ✅ 遵循最小權限原則,不同用途使用不同帳號
- ✅ 啟用監控與審計,定期檢視連線與查詢日誌
- ✅ 使用連線池減少資料庫負擔
掌握這些技巧後,你就能在任何環境中安全、高效地操作 AWS RDS PostgreSQL!
