前言

在現代雲端架構中,資料庫通常部署在受保護的私有網路環境(Private Subnet)中,以提升安全性。AWS RDS(Relational Database Service)作為主流的托管資料庫服務,提供了多種連線方式,但對於初學者來說,如何在不同環境(本機、Docker、Kubernetes)中正確連線到 RDS 往往充滿挑戰。

這篇文章將深入探討:

  1. AWS RDS 網路架構:公有子網 vs 私有子網的差異
  2. 直接連線方式:當 RDS 設為 Publicly Accessible 時
  3. SSH 隧道(SSH Tunneling):透過 Bastion Host 連線私有 RDS
  4. 容器環境連線:在 Docker 和 Kubernetes Pod 中使用 psql
  5. psql 完整命令參考:從基礎查詢到進階操作
  6. 安全最佳實踐:如何保護資料庫連線與憑證
  7. 常見問題排查:連線失敗的系統化診斷方法

無論你是在本機開發、容器化部署、或是 Kubernetes 叢集中操作,這篇文章都能幫助你建立安全可靠的資料庫連線。

AWS RDS 網路架構概覽

在開始連線之前,我們需要理解 AWS RDS 的網路架構。RDS 實例可以部署在不同的網路環境中,每種配置都有不同的連線方式和安全考量。

公有子網 vs 私有子網

Mermaid Diagram

兩種部署方式的比較

特性公有子網 (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 時,可以直接從網際網路連線。這是最簡單的方式,但也是最不安全的。

完整連線流程

Mermaid Diagram

步驟 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 中取得以下資訊:

  1. Endpoint(終端節點)

    • AWS Console > RDS > Databases > 選擇你的資料庫
    • 在 “Connectivity & security” 頁籤中找到 “Endpoint”
    • 格式:yourdb.xxx.ap-east-1.rds.amazonaws.com
  2. Port(埠號)

    • 預設為 5432
    • 可在同一頁面確認
  3. Master username(主帳號)

    • 在 “Configuration” 頁籤中找到
    • 預設通常是 postgres
  4. Database name(資料庫名稱)

    • 建立時指定的初始資料庫名稱
    • 預設為 postgres
  5. 密碼

    • 建立時設定的密碼
    • 或從 AWS Secrets Manager 中取得

步驟 3:設定 Security Group

這是最關鍵的步驟!Security Group 就像是 RDS 的防火牆。

Mermaid Diagram

AWS Console 操作步驟

  1. AWS Console > RDS > Databases > 選擇你的資料庫
  2. 點擊 “VPC security groups” 連結
  3. 選擇 “Inbound rules” 頁籤
  4. 點擊 “Edit inbound rules”
  5. 新增規則:
    Type: PostgreSQL
    Protocol: TCP
    Port range: 5432
    Source: My IP (AWS 會自動偵測你的 IP)
    Description: Dev access from my IP
    
  6. 點擊 “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 連線,將本機的某個埠號轉發到遠端伺服器的另一個埠號。

Mermaid Diagram

網路拓撲圖

Mermaid Diagram

步驟 1:準備 Bastion Host

Bastion Host(跳板機)是部署在公有子網的 EC2 實例,用於安全地存取私有資源。

建立 Bastion Host

  1. 啟動 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(見下方)
  2. 設定 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
    
  3. 設定 RDS Security Group

    Inbound Rules:
    - Type: PostgreSQL
      Protocol: TCP
      Port: 5432
      Source: Bastion Security Group ID (sg-xxx)
    

    這樣設定後,只有從 Bastion Host 發出的連線才能連到 RDS。

  4. 下載 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 必須是 localhost127.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 網路架構

Mermaid Diagram

情境 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

從容器連線的安全考量

Mermaid Diagram

連線方式四:Kubernetes Pod 環境

在 Kubernetes 叢集中(例如 AWS EKS),Pod 與 RDS 之間的連線涉及更複雜的網路架構。

Kubernetes 與 RDS 網路架構

Mermaid Diagram

關鍵概念:Pod 到 RDS 的連線路徑

  1. Pod Network(CNI)

    • EKS 使用 VPC CNI,Pod 獲得 VPC 子網的 IP 位址
    • Pod IP 範圍通常與 Node 所在的子網相同
  2. Security Group 設定

    • RDS Security Group 必須允許來自 Node Security Group 的連線
    • 因為 Pod 的流量會透過 Node 轉發(SNAT)
  3. 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 特有的管理命令:

Mermaid Diagram

資料庫管理命令

命令說明範例
\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'

安全最佳實踐

密碼管理

Mermaid Diagram

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. 網路隔離

Mermaid Diagram

最佳實踐檢查清單

  • ✅ RDS 部署在私有子網
  • ✅ Publicly Accessible 設為 No
  • ✅ Security Group 限制來源 IP 或 Security Group
  • ✅ 使用 Bastion Host 或 VPN 連線
  • ✅ 啟用 SSL 連線(sslmode=require)
  • ✅ 使用 IAM 認證(如可行)
  • ✅ 定期輪換密碼
  • ✅ 使用最小權限原則
  • ✅ 啟用自動備份
  • ✅ 啟用 Multi-AZ(高可用性)
  • ✅ 啟用 CloudWatch 監控
  • ✅ 啟用 Performance Insights

常見問題排查

問題 1:連線逾時(Connection Timeout)

Mermaid Diagram

診斷命令

# 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:

  1. 直接連線:適用於開發測試,需正確設定 Security Group
  2. SSH 隧道:生產環境最佳實踐,透過 Bastion Host 安全連線
  3. Docker 容器:使用官方 PostgreSQL 映像檔或在應用容器中安裝 psql
  4. Kubernetes Pod:理解 Pod 網路與 Node Security Group 的關係
  5. psql 命令:完整的互動介面命令參考,從基礎到進階
  6. 安全實踐:IAM 認證、SSL 連線、最小權限、網路隔離
  7. 問題排查:系統化診斷連線、認證、網路問題

關鍵要點

  • ✅ 生產環境永遠使用私有子網 + Bastion Host
  • ✅ 使用 .pgpass 檔案或 Secret 管理服務儲存密碼
  • ✅ 強制使用 SSL 連線(sslmode=require)
  • ✅ 遵循最小權限原則,不同用途使用不同帳號
  • ✅ 啟用監控與審計,定期檢視連線與查詢日誌
  • ✅ 使用連線池減少資料庫負擔

掌握這些技巧後,你就能在任何環境中安全、高效地操作 AWS RDS PostgreSQL!

參考資源