目標
このレッスンを終えると、次のことができるようになります:
- Patroni コールバックとは何か、いつトリガーされるかを理解する__HTMLTAG_71__
- ライフサイクル イベント用のカスタム スクリプトを実装する
- 自動化されたコールバックを構成するタスク
- ロール変更の処理 (プライマリ ↔ レプリカ)
- 通知と監視フックのセットアップ
- コールバックの失敗のトラブルシューティング
1。 Callbacks Overview
1.1.コールバックとは何ですか?
コールバック = カスタム スクリプトは、クラスタのライフサイクル イベント で Patroni によって実行されます。
使用ケース_:
- 🔔 通知: フェイルオーバーが発生したときにチームに警告するra
- 🔧 Automation: DNS、ロードバランサー構成を更新
- 📊 Monitoring: メトリクスをモニタリングにプッシュしますシステム
- 🚦 トラフィック管理: アプリケーショントラフィックをリダイレクト
- 🔐 セキュリティ: 認証情報のローテーション、更新ファイアウォール ルール
- 📝 ロギング: カスタム監査ログ
1.2。 Available callbacks
Patroni provides callback events:
| _コールバック_ | _トリガー | 使用Case_ |
|---|---|---|
on_start | PostgreSQL の開始前 | 開始前チェック、マウントボリューム_ |
on_stop | PostgreSQL が停止する前に | クリーンアップ、通知アプリケーション_ |
on_restart | PostgreSQL の再起動前 | ログの再起動イベント |
_on_reload_ | PostgreSQL 構成のリロード後 | 構成を確認変更 |
on_role_change | 役割の変更 (プライマリ ↔ レプリカ) | ほとんど重要_ - DNS、LB を更新 |
_pre_promote_ | レプリカが昇格される前 primary_ | Final checks before promotion_ |
post_promote_ | レプリカがプライマリに昇格した後_ | 更新監視を送信アラート_ |
1.3。コールバック実行フロー
Example: Failover scenario
Old Primary crashes ↓ Patroni detects failure (after TTL expires) ↓ Patroni selects best replica (node2) ↓ pre_promote callback runs on node2 ↓ PostgreSQL promoted to primary (pg_promote) ↓ post_promote callback runs on node2 ↓ on_role_change callback runs on node2 (role=master) ↓ Other replicas detect new leader ↓ on_role_change callback runs on replicas (role=replica) ↓ Failover complete
1.4。コールバック環境変数
Patroni は、環境変数 をスクリプトに渡します:
__ _HTMLTAG_266______HTMLTA G_278______ HTMLTAG_290___| 変数 | _説明 | 例___HTMLTAG_23 4___ |
|---|---|---|
_PATRONI_ROLE | _後の現在の役割変更_ | master, replica__HTMLTAG_251 ___ |
PATRONI_SCOPE | クラスターname_ | postgres_ |
PATRONI_NAME | Node name_ | node1 |
PATRONI_CLUSTER_NAME | クラスター名(エイリアス) | postgres |
PATRONI_VERSION | Patroniバージョン_ | 3.2.0 |
on_role_change:
| _変数 | _値 |
|---|---|
PATRONI_NEW_ROLE | 新規役割: マスター または レプリカ_ |
PATRONI_OLD_ROLE | 前の役割 |
2. Patroni
2.1 でコールバックを構成します。基本構成
patroni.yml 内:
scope: postgres name: node1
postgresql: callbacks: on_start: /var/lib/postgresql/callbacks/on_start.sh on_stop: /var/lib/postgresql/callbacks/on_stop.sh on_restart: /var/lib/postgresql/callbacks/on_restart.sh on_reload: /var/lib/postgresql/callbacks/on_reload.sh on_role_change: /var/lib/postgresql/callbacks/on_role_change.sh
キーポイント:
- パスは必須です絶対_ __HTM であることLTAG_366___スクリプトは実行可能 (
- 所有である必要がありますby postgres user
- 完了する必要があります すぐに (<30 秒)
- ゼロ以外の終了コード= コールバックが失敗しました (ログに記録されますが、操作はブロックされません)
chmod +x)2.2。コールバック ディレクトリ
# On all 3 nodes
sudo mkdir -p /var/lib/postgresql/callbacks
sudo chown postgres:postgres /var/lib/postgresql/callbacks
sudo chmod 750 /var/lib/postgresql/callbacks
3 を作成します。コールバック スクリプト
3.1 を実装します。 on_start コールバック
ユースケース: 開始前検証、マウントチェック
Script: /var/lib/post gresql/callbacks/on_start.sh
#!/bin/bashon_start.sh - Runs before PostgreSQL starts
set -e
LOG_FILE="/var/log/patroni/callbacks.log" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
Logging function
log() { echo "[$TIMESTAMP] [ON_START] $1" | tee -a "$LOG_FILE" }
log "Starting PostgreSQL on $PATRONI_NAME" log "Role: $PATRONI_ROLE" log "Cluster: $PATRONI_SCOPE"
Check disk space
DISK_USAGE=$(df -h /var/lib/postgresql | awk 'NR==2 {print $5}' | sed 's/%//') if [ "$DISK_USAGE" -gt 90 ]; then log "ERROR: Disk usage is ${DISK_USAGE}% - critically high!" exit 1 fi log "Disk usage: ${DISK_USAGE}%"
Check if data directory is mounted
if ! mountpoint -q /var/lib/postgresql/18/data; then log "WARNING: Data directory is not a mount point" fi
Check network connectivity to etcd
for ETCD_HOST in 10.0.1.11 10.0.1.12 10.0.1.13; do if ! nc -zw3 "$ETCD_HOST" 2379 2>/dev/null; then log "ERROR: Cannot reach etcd at $ETCD_HOST:2379" exit 1 fi done log "etcd connectivity verified"
log "Pre-start checks passed" exit 0
スクリプトの作成_:
# On all nodes sudo tee /var/lib/postgresql/callbacks/on_start.sh > /dev/null << 'EOF' #!/bin/bash set -e LOG_FILE="/var/log/patroni/callbacks.log" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') log() { echo "[$TIMESTAMP] [ON_START] $1" | tee -a "$LOG_FILE"; }log "Starting PostgreSQL on $PATRONI_NAME (Role: $PATRONI_ROLE)"
Disk space check
DISK_USAGE=$(df -h /var/lib/postgresql | awk 'NR==2 {print $5}' | sed 's/%//') if [ "$DISK_USAGE" -gt 90 ]; then log "ERROR: Disk usage ${DISK_USAGE}% too high" exit 1 fi log "Disk usage: ${DISK_USAGE}%"
log "Pre-start checks passed" exit 0 EOF
sudo chmod +x /var/lib/postgresql/callbacks/on_start.sh sudo chown postgres:postgres /var/lib/postgresql/callbacks/on_start.sh
3.2。 on_stop コールバック
ユースケース: 正常なシャットダウン通知
Script: /var/lib/ postgresql/callbacks/on_stop.sh
#!/bin/bashon_stop.sh - Runs before PostgreSQL stops
set -e
LOG_FILE="/var/log/patroni/callbacks.log" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
log() { echo "[$TIMESTAMP] [ON_STOP] $1" | tee -a "$LOG_FILE" }
log "Stopping PostgreSQL on $PATRONI_NAME" log "Role: $PATRONI_ROLE"
Notify monitoring system
if command -v curl >/dev/null 2>&1; then curl -s -X POST http://monitoring.example.com/api/events
-H "Content-Type: application/json"
-d "{ "event": "postgresql_stop", "node": "$PATRONI_NAME", "role": "$PATRONI_ROLE", "timestamp": "$TIMESTAMP" }" || log "WARNING: Failed to notify monitoring" fi
log "PostgreSQL stop initiated" exit 0
スクリプトの作成:
sudo tee /var/lib/postgresql/callbacks/on_stop.sh > /dev/null << 'EOF' #!/bin/bash set -e LOG_FILE="/var/log/patroni/callbacks.log" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') log() { echo "[$TIMESTAMP] [ON_STOP] $1" | tee -a "$LOG_FILE"; }log "Stopping PostgreSQL on $PATRONI_NAME (Role: $PATRONI_ROLE)" exit 0 EOF
sudo chmod +x /var/lib/postgresql/callbacks/on_stop.sh sudo chown postgres:postgres /var/lib/postgresql/callbacks/on_stop.sh
3.3。 on_role_change コールバック (最も重要!)
ユースケース: DNS、ロードバランサーの更新、送信通知。
Script: /var/lib/post gresql/callbacks/on_role_change.sh
#!/bin/bashon_role_change.sh - Runs when role changes (master ↔ replica)
set -e
LOG_FILE="/var/log/patroni/callbacks.log" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
log() { echo "[$TIMESTAMP] [ROLE_CHANGE] $1" | tee -a "$LOG_FILE" }
log "==========================================" log "Role change detected on $PATRONI_NAME" log "Cluster: $PATRONI_SCOPE" log "Old role: ${PATRONI_OLD_ROLE:-unknown}" log "New role: $PATRONI_ROLE" log "=========================================="
Function: Update DNS
update_dns() { local NEW_PRIMARY_IP="$1"
log "Updating DNS record for primary.postgres.local -> $NEW_PRIMARY_IP" # Example using nsupdate (BIND DNS) # nsupdate -k /etc/dns/Kpostgres.+157+12345.key << EOF # server dns-server.local # zone postgres.local # update delete primary.postgres.local A # update add primary.postgres.local 60 A $NEW_PRIMARY_IP # send # EOF # Or using API (e.g., Route53, Cloudflare) # aws route53 change-resource-record-sets --hosted-zone-id Z1234 ... log "DNS update completed"}
Function: Update HAProxy
update_haproxy() { local NEW_PRIMARY_IP="$1"
log "Notifying HAProxy about new primary: $NEW_PRIMARY_IP" # Use HAProxy stats socket # echo "set server postgres/primary addr $NEW_PRIMARY_IP" | \ # socat stdio /var/run/haproxy.sock log "HAProxy updated"}
Function: Send Slack notification
send_notification() { local MESSAGE="$1" local WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
log "Sending notification: $MESSAGE" curl -s -X POST "$WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d "{ \"text\": \"🔄 PostgreSQL Role Change\", \"attachments\": [{ \"color\": \"warning\", \"fields\": [ {\"title\": \"Node\", \"value\": \"$PATRONI_NAME\", \"short\": true}, {\"title\": \"Cluster\", \"value\": \"$PATRONI_SCOPE\", \"short\": true}, {\"title\": \"Old Role\", \"value\": \"${PATRONI_OLD_ROLE:-N/A}\", \"short\": true}, {\"title\": \"New Role\", \"value\": \"$PATRONI_ROLE\", \"short\": true}, {\"title\": \"Time\", \"value\": \"$TIMESTAMP\", \"short\": false} ] }] }" || log "WARNING: Notification failed"}
Main logic
case "$PATRONI_ROLE" in master) log "This node is now PRIMARY"
# Get this node's IP NODE_IP=$(hostname -I | awk '{print $1}') log "Node IP: $NODE_IP" # Update DNS to point to new primary update_dns "$NODE_IP" # Update load balancer update_haproxy "$NODE_IP" # Send notification send_notification "Node $PATRONI_NAME promoted to PRIMARY" # Set marker file for applications touch /tmp/postgres_is_primary rm -f /tmp/postgres_is_replica log "Primary promotion tasks completed" ;; replica) log "This node is now REPLICA" # Remove primary marker rm -f /tmp/postgres_is_primary touch /tmp/postgres_is_replica # Send notification if demoted from primary if [ "${PATRONI_OLD_ROLE}" = "master" ]; then send_notification "Node $PATRONI_NAME demoted to REPLICA" fi log "Replica role tasks completed" ;; *) log "Unknown role: $PATRONI_ROLE" exit 1 ;;esac
log "Role change handling completed successfully" exit 0
作成本番環境対応スクリプト:
sudo tee /var/lib/postgresql/callbacks/on_role_change.sh > /dev/null << 'EOF' #!/bin/bash set -eLOG_FILE="/var/log/patroni/callbacks.log" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') log() { echo "[$TIMESTAMP] [ROLE_CHANGE] $1" | tee -a "$LOG_FILE"; }
log "==========================================" log "Role change: $PATRONI_NAME" log "Old role: ${PATRONI_OLD_ROLE:-unknown}" log "New role: $PATRONI_ROLE" log "=========================================="
case "$PATRONI_ROLE" in master) log "This node is now PRIMARY" NODE_IP=$(hostname -I | awk '{print $1}') log "Node IP: $NODE_IP"
# TODO: Update DNS, load balancer, etc. # update_dns "$NODE_IP" touch /tmp/postgres_is_primary rm -f /tmp/postgres_is_replica ;; replica) log "This node is now REPLICA" rm -f /tmp/postgres_is_primary touch /tmp/postgres_is_replica ;; *) log "Unknown role: $PATRONI_ROLE" exit 1 ;;esac
log "Role change completed" exit 0 EOF
sudo chmod +x /var/lib/postgresql/callbacks/on_role_change.sh sudo chown postgres:postgres /var/lib/postgresql/callbacks/on_role_change.sh
3.4。 on_restart コールバック
ユースケース: ログを再起動し、計画されたメンテナンスについて通知します。
sudo tee /var/lib/postgresql/callbacks/on_restart.sh > /dev/null << 'EOF' #!/bin/bash set -e LOG_FILE="/var/log/patroni/callbacks.log" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') log() { echo "[$TIMESTAMP] [ON_RESTART] $1" | tee -a "$LOG_FILE"; }log "Restarting PostgreSQL on $PATRONI_NAME (Role: $PATRONI_ROLE)" exit 0 EOF
sudo chmod +x /var/lib/postgresql/callbacks/on_restart.sh sudo chown postgres:postgres /var/lib/postgresql/callbacks/on_restart.sh
3.5。 on_reload コールバック
使用例: 構成の変更が適用されたことを確認します。_
sudo tee /var/lib/postgresql/callbacks/on_reload.sh > /dev/null << 'EOF' #!/bin/bash set -e LOG_FILE="/var/log/patroni/callbacks.log" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') log() { echo "[$TIMESTAMP] [ON_RELOAD] $1" | tee -a "$LOG_FILE"; }log "Configuration reloaded on $PATRONI_NAME"
Verify critical settings
MAX_CONN=$(sudo -u postgres psql -t -c "SHOW max_connections;") log "max_connections = $MAX_CONN"
exit 0 EOF
sudo chmod +x /var/lib/postgresql/callbacks/on_reload.sh sudo chown postgres:postgres /var/lib/postgresql/callbacks/on_reload.sh
3.6。ログ ディレクトリ
# On all nodes
sudo mkdir -p /var/log/patroni
sudo chown postgres:postgres /var/log/patroni
sudo chmod 750 /var/log/patroni
4 を作成します。 Patroni 構成
4.1 を更新します。 patroni.yml にコールバックを追加
3 つのノードすべてで、 /etc/patroni/patroni.yml:
scope: postgres namespace: /service/ name: node1 # node2, node3 for other nodesrestapi: listen: 0.0.0.0:8008 connect_address: 10.0.1.11:8008 # Change per node
etcd3: hosts: 10.0.1.11:2379,10.0.1.12:2379,10.0.1.13:2379
bootstrap: dcs: ttl: 30 loop_wait: 10 retry_timeout: 10 maximum_lag_on_failover: 1048576 synchronous_mode: true synchronous_mode_strict: false
postgresql: parameters: max_connections: 100 shared_buffers: 256MB wal_level: replica max_wal_senders: 10 max_replication_slots: 10postgresql: listen: 0.0.0.0:5432 connect_address: 10.0.1.11:5432 # Change per node data_dir: /var/lib/postgresql/18/data bin_dir: /usr/lib/postgresql/18/bin
authentication: replication: username: replicator password: replicator_password superuser: username: postgres password: postgres_password
parameters: unix_socket_directories: '/var/run/postgresql'
✅ Add callbacks section
callbacks: on_start: /var/lib/postgresql/callbacks/on_start.sh on_stop: /var/lib/postgresql/callbacks/on_stop.sh on_restart: /var/lib/postgresql/callbacks/on_restart.sh on_reload: /var/lib/postgresql/callbacks/on_reload.sh on_role_change: /var/lib/postgresql/callbacks/on_role_change.sh
tags: nofailover: false noloadbalance: false clonefrom: false nosync: false
4.2 を編集します。 Patroni 設定
# On all 3 nodes sudo systemctl reload patroniVerify callbacks configured
patronictl show-config postgres
5 をリロードします。コールバックをテスト
5.1。 _restart
# Restart a node patronictl restart postgres node2Check logs
sudo tail -f /var/log/patroni/callbacks.log
Expected output:
[2024-11-25 10:30:15] [ON_RESTART] Restarting PostgreSQL on node2
5.2 でテストします。 on_reload
# Reload configuration patronictl reload postgres node2Check logs
sudo tail /var/log/patroni/callbacks.log
Expected:
[2024-11-25 10:32:45] [ON_RELOAD] Configuration reloaded on node2
5.3 でテストします。 on_role_change のテスト (フェイルオーバー)
⚠️ 重要: 非運用環境でテストしてください!
# 1. Check current primary patronictl list postgresnode1 is Leader
2. Stop primary
sudo systemctl stop patroni # On node1
3. Watch logs on node2 (will become new primary)
sudo tail -f /var/log/patroni/callbacks.log
Expected output:
[2024-11-25 10:35:10] [ROLE_CHANGE] ==========================================
[2024-11-25 10:35:10] [ROLE_CHANGE] Role change: node2
[2024-11-25 10:35:10] [ROLE_CHANGE] Old role: replica
[2024-11-25 10:35:10] [ROLE_CHANGE] New role: master
[2024-11-25 10:35:10] [ROLE_CHANGE] This node is now PRIMARY
[2024-11-25 10:35:10] [ROLE_CHANGE] Node IP: 10.0.1.12
[2024-11-25 10:35:10] [ROLE_CHANGE] Role change completed
4. Verify marker file
ls -la /tmp/postgres_is_*
-rw-r--r-- 1 postgres postgres 0 Nov 25 10:35 /tmp/postgres_is_primary
5. Restart node1 (will rejoin as replica)
sudo systemctl start patroni # On node1
6. Check node1 logs
sudo tail /var/log/patroni/callbacks.log
[2024-11-25 10:36:30] [ROLE_CHANGE] Old role: master
[2024-11-25 10:36:30] [ROLE_CHANGE] New role: replica
[2024-11-25 10:36:30] [ROLE_CHANGE] This node is now REPLICA
6。高度なコールバックの例_
6.1。 nsupdate を使用した DNS 更新
前提条件: DDNS が有効になっている BIND DNS サーバー。
#!/bin/bashUpdate DNS via nsupdate
update_dns() { local NEW_PRIMARY_IP="$1" local DNS_KEY="/etc/dns/Kpostgres.+157+12345.key" local DNS_SERVER="dns.example.com" local ZONE="postgres.local" local RECORD="primary.postgres.local"
log "Updating DNS: $RECORD -> $NEW_PRIMARY_IP" nsupdate -k "$DNS_KEY" << EOFserver $DNS_SERVER zone $ZONE update delete $RECORD A update add $RECORD 60 A $NEW_PRIMARY_IP send EOF
if [ $? -eq 0 ]; then log "DNS updated successfully" else log "ERROR: DNS update failed" return 1 fi}
In on_role_change.sh
if [ "$PATRONI_ROLE" = "master" ]; then NODE_IP=$(hostname -I | awk '{print $1}') update_dns "$NODE_IP" fi
6.2。 HAProxy バックエンドの更新
統計ソケット経由:
update_haproxy() { local NEW_PRIMARY_IP="$1" local HAPROXY_SOCKET="/var/run/haproxy.sock"log "Updating HAProxy: primary backend -> $NEW_PRIMARY_IP" echo "set server postgres-primary/node addr $NEW_PRIMARY_IP port 5432" | \ socat stdio "$HAPROXY_SOCKET" echo "set server postgres-primary/node state ready" | \ socat stdio "$HAPROXY_SOCKET" log "HAProxy backend updated"
}
6.3。領事サービス登録
register_in_consul() { local ROLE="$1" local NODE_IP="$2"log "Registering in Consul: $PATRONI_NAME as $ROLE" curl -s -X PUT "http://consul.local:8500/v1/agent/service/register" \ -H "Content-Type: application/json" \ -d "{ \"Name\": \"postgres-$ROLE\", \"ID\": \"postgres-$PATRONI_NAME\", \"Address\": \"$NODE_IP\", \"Port\": 5432, \"Tags\": [\"$ROLE\", \"patroni\"], \"Check\": { \"TCP\": \"$NODE_IP:5432\", \"Interval\": \"10s\", \"Timeout\": \"2s\" } }" log "Consul registration completed"}
Usage
NODE_IP=$(hostname -I | awk '{print $1}') register_in_consul "$PATRONI_ROLE" "$NODE_IP"
6.4。電子メール通知_
send_email_alert() { local SUBJECT="$1" local BODY="$2" local RECIPIENT="[email protected]"log "Sending email alert: $SUBJECT" echo "$BODY" | mail -s "$SUBJECT" "$RECIPIENT" log "Email sent to $RECIPIENT"}
In on_role_change.sh
if [ "$PATRONI_ROLE" = "master" ]; then send_email_alert
"[ALERT] PostgreSQL Failover: $PATRONI_NAME promoted to PRIMARY"
"Cluster: $PATRONI_SCOPE Node: $PATRONI_NAME Old Role: ${PATRONI_OLD_ROLE} New Role: $PATRONI_ROLE Time: $TIMESTAMP
Action required: Verify cluster health" fi
6.5。 Slack/Teams Webhook
詳細な Slack 通知:
send_slack_alert() { local WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL" local COLOR="$1" # good, warning, danger local TITLE="$2" local MESSAGE="$3"curl -s -X POST "$WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d "{ \"username\": \"Patroni Monitor\", \"icon_emoji\": \": database:\", \"attachments\": [{ \"color\": \"$COLOR\", \"title\": \"$TITLE\", \"text\": \"$MESSAGE\", \"fields\": [ {\"title\": \"Cluster\", \"value\": \"$PATRONI_SCOPE\", \"short\": true}, {\"title\": \"Node\", \"value\": \"$PATRONI_NAME\", \"short\": true}, {\"title\": \"Old Role\", \"value\": \"${PATRONI_OLD_ROLE:-N/A}\", \"short\": true}, {\"title\": \"New Role\", \"value\": \"$PATRONI_ROLE\", \"short\": true}, {\"title\": \"Timestamp\", \"value\": \"$TIMESTAMP\", \"short\": false} ], \"footer\": \"PostgreSQL HA\", \"footer_icon\": \"https://www.postgresql.org/media/img/about/press/elephant.png\" }] }"}
Usage
if [ "$PATRONI_ROLE" = "master" ]; then send_slack_alert "warning"
"🚨 Failover Event"
"Node $PATRONI_NAME has been promoted to PRIMARY" fi
6.6。メトリクスはモニタリングにプッシュ_
Prometheus Pushgateway にプッシュ:_
push_metrics() { local PUSHGATEWAY="http://pushgateway.local:9091" local JOB="patroni_callbacks"log "Pushing metrics to Prometheus" cat << EOF | curl -s --data-binary @- "$PUSHGATEWAY/metrics/job/$JOB/instance/$PATRONI_NAME"TYPE patroni_role_change counter
HELP patroni_role_change Number of role changes
patroni_role_change{cluster="$PATRONI_SCOPE",node="$PATRONI_NAME",new_role="$PATRONI_ROLE"} 1
TYPE patroni_role_change_timestamp gauge
HELP patroni_role_change_timestamp Timestamp of last role change
patroni_role_change_timestamp{cluster="$PATRONI_SCOPE",node="$PATRONI_NAME"} $(date +%s) EOF
log "Metrics pushed"
}
7。コールバックのベスト プラクティス_
✅ DO
- コールバックを高速に保つ
- 10 ~ 30 秒以内に完了
- Longタスク → バックグラウンド ジョブ
- 適切なログを使用
- すべてのアクションをログに記録
- タイムスタンプを含める_
- 回転ログ
- エラーを適切に処理
- 使用
set -e慎重に - エラーをキャッチ、ログに記録、続行
- ゼロ以外の終了 = 警告、失敗ではない
- 使用
- テスト徹底的に
- ステージングでテスト
- すべてのシナリオをシミュレーション_
- べき等性を検証_
- Makeスクリプト冪等
- 安全に複数回実行可能
- 変更前に確認
- 絶対値を使用パス
- PATH に依存しない
- フルパスを指定
- 安全資格情報_
- パスワードをハードコードしない_
- 環境変数またはシークレットマネージャーを使用_
- コールバックを監視する実行
- 失敗時のアラート
- 実行時間の追跡_
❌しないでください_
- 長時間ブロックしないでください
- Patroni はコールバックを待ちます_
- 遅延が長い → 遅くなるフェイルオーバー_
- フェイルオーバー中にネットワークに依存しない_
- ネットワークが分割される可能性があります
- フォールバックありメカニズム_
- コールバックを不必要に失敗させない
- 通知が失敗しても0を終了
- エラーをログに記録するが続行
- 実行しないコールバック内のデータベース クエリ_
- PostgreSQL の準備ができていない可能性があります
- デッドロックが発生する可能性があります
- PostgreSQL を変更しないでください設定
- Patroni に設定を管理させる_
- Patroni のパラメータを使用
- インタラクティブを使用しないコマンド_
- ユーザー入力なし
- 無人で実行する必要があります_
8。コールバックの問題のトラブルシューティング
8.1。コールバックが実行されていません_
Check:
# 1. Verify script exists ls -la /var/lib/postgresql/callbacks/on_role_change.sh2. Check executable permissions
Should be: -rwxr-xr-x postgres postgres
sudo chmod +x /var/lib/postgresql/callbacks/on_role_change.sh
3. Check ownership
sudo chown postgres:postgres /var/lib/postgresql/callbacks/on_role_change.sh
4. Verify path in patroni.yml
grep -A5 "callbacks:" /etc/patroni/patroni.yml
5. Check Patroni logs
sudo journalctl -u patroni -n 100 | grep -i callback
8.2。コールバック失敗_
ログの確認:
# Patroni logs sudo journalctl -u patroni | grep "callback.*failed"Callback logs
sudo tail -f /var/log/patroni/callbacks.log
Test script manually
sudo -u postgres /var/lib/postgresql/callbacks/on_role_change.sh
共通問題:
- 構文エラー:
bash -n script.shを実行します。 check - 依存関係がありません: 必要なツール (curl、nc など) をインストールします
- 許可が拒否されました: ファイル/ディレクトリを確認してください権限_
- タイムアウト: スクリプトに時間がかかりすぎます
8.3。フェールオーバーの遅延を引き起こすコールバック
コールバックの実行時間を測定:
# Add timing to script START_TIME=$(date +%s)... your callback logic ...
END_TIME=$(date +%s) DURATION=$((END_TIME - START_TIME)) log "Callback completed in ${DURATION} seconds"
If DURATION > 30, investigate and optimize
9。本番環境コールバック テンプレート
本番環境に対応した完全なテンプレート:
#!/bin/bashPatroni callback template
File: /var/lib/postgresql/callbacks/on_role_change.sh
set -euo pipefail # Exit on error, undefined vars, pipe failures
Configuration
readonly LOG_FILE="/var/log/patroni/callbacks.log" readonly LOCK_FILE="/tmp/callback_role_change.lock" readonly TIMEOUT=30 readonly SLACK_WEBHOOK="${SLACK_WEBHOOK_URL:-}"
Logging function
log() { local LEVEL="$1" shift local MESSAGE="$*" local TIMESTAMP TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') echo "[$TIMESTAMP] [$LEVEL] [ROLE_CHANGE] $MESSAGE" | tee -a "$LOG_FILE" }
Error handler
error_exit() { log "ERROR" "$1" cleanup exit 1 }
Cleanup function
cleanup() { rm -f "$LOCK_FILE" }
Ensure only one instance runs
if ! mkdir "$LOCK_FILE" 2>/dev/null; then log "WARN" "Another callback instance is running, exiting" exit 0 fi
trap cleanup EXIT
Set timeout
timeout "$TIMEOUT" bash << 'SCRIPT' || error_exit "Callback timed out after ${TIMEOUT}s"
log "INFO" "==========================================" log "INFO" "Role change detected" log "INFO" "Cluster: ${PATRONI_SCOPE:-unknown}" log "INFO" "Node: ${PATRONI_NAME:-unknown}" log "INFO" "Old role: ${PATRONI_OLD_ROLE:-unknown}" log "INFO" "New role: ${PATRONI_ROLE:-unknown}" log "INFO" "=========================================="
Main logic
case "${PATRONI_ROLE:-}" in master) log "INFO" "Handling promotion to PRIMARY"
# Get node IP NODE_IP=$(hostname -I | awk '{print $1}') log "INFO" "Node IP: $NODE_IP" # Update DNS (implement your logic) # update_dns "$NODE_IP" || log "WARN" "DNS update failed" # Update load balancer (implement your logic) # update_load_balancer "$NODE_IP" || log "WARN" "LB update failed" # Send notification if [ -n "$SLACK_WEBHOOK" ]; then curl -s -X POST "$SLACK_WEBHOOK" \ -H "Content-Type: application/json" \ -d "{\"text\": \"🚨 Failover: $PATRONI_NAME promoted to PRIMARY\"}" \ || log "WARN" "Slack notification failed" fi # Set marker files touch /tmp/postgres_is_primary rm -f /tmp/postgres_is_replica log "INFO" "PRIMARY promotion tasks completed" ;; replica) log "INFO" "Handling demotion to REPLICA" # Remove primary marker rm -f /tmp/postgres_is_primary touch /tmp/postgres_is_replica # Notify if demoted from primary if [ "${PATRONI_OLD_ROLE:-}" = "master" ]; then log "WARN" "Node demoted from PRIMARY to REPLICA" # Send alert fi log "INFO" "REPLICA tasks completed" ;; *) error_exit "Unknown role: ${PATRONI_ROLE:-unknown}" ;;esac
log "INFO" "Callback completed successfully" exit 0
SCRIPT
10。ラボ演習
ラボ 1: 基本的なコールバックのセットアップ
タスク:
- コールバック ディレクトリとスクリプト_
- patroni.yml にコールバックを追加
- Patroni をリロード_
patronictl でテストするrestart
ラボ 2: フェールオーバー コールバックのテスト
タスク:
- コールバックの監視ログ:
tail -f /var/log/patroni/callbacks.log - プライマリの停止:
sudo systemctl stop patroni_ - Verify on_role_change新しいプライマリで実行
- マーカー ファイルを確認します:
/tmp/postgres_is_* - 古いプライマリを再起動します。レプリカとして再参加することを確認
ラボ 3: Slack 通知の実装
タスク:
- Slack Webhook を取得するURL_
- on_role_change.sh に通知を追加
- フェイルオーバーをトリガーしてテスト
- Slack で受信したメッセージを確認_
ラボ 4: コールバックを測定するパフォーマンス_
タスク:
- すべてのコールバックにタイミングを追加_
- さまざまなイベント(再起動、再読み込み、フェイルオーバー)
- コールバック実行時間の分析
- 遅いコールバックの最適化
11。概要
重要なポイント
✅ コールバック = ライフサイクル時のカスタム自動化events
✅ on_role_change = フェイルオーバー自動化のための最も重要なコールバック
✅ コールバックを維持するfast(<30 秒) クイック フェイルオーバー用
✅ すべてをログに記録 デバッグ
✅ 本番前に徹底的にテスト 本番前
✅ エラーを適切に処理_ - しないブロック操作__HTMLTAG_814__HTMLTAG_815___一般的な使用例
| コールバック | 共通アクション_ |
|---|---|
on_start_ | プリフライトチェック、マウント確認_ |
on_stop | クリーンアップ、通知_ |
on_role_change | DNS、LB を更新、送信アラート_ |
再起動時 | ログのメンテナンスイベント_ |
_on_reload | 構成変更の確認 |
現在のアーキテクチャ___HTMLTAG_872__CODEBLOCK_27___
レッスン 12 の準備____HTMLTAG_874__HTMLTAG_875___レッスン 12 ではPatroni REST について説明しますAPI:
- ヘルスチェックエンドポイント
- クラスターステータスクエリ_
- API経由の構成管理
- 負荷との統合バランサー
- モニタリングとメトリクス