Corosync + Pacemaker によるオンプレミス向けHA設計
PlatformEngineeringのLDRです。 Qごとに1日だけ、プロダクト開発や事業課題の解決に活用できそうな技術についてインプット/アウトプットする「I/O Day」があります。今回は、過去に構築したことのあるCorosync + PacemakerによるHA構成を、自立型AIエージェントで構築させてみようとした話です。
裏設定
- SaaSばかりやってるとオンプレ案件の時に環境での構築ってこれどうやってたっけ…って浦島状態になるのでたまに棚卸しが必要。DCの機器の持ち込み制限のある区域でBIOS/UEFI設定してイメージ焼いて物理へクリーンインストールしてRAID組めますか?
- 10数年前に2Sprintほど費やして構築したHA、AIを利用した今ならどこまで工数短縮できるのか?
- AIエージェントを活用した設定作業の効率化を検証する
検証の位置づけ
- 最終目標: フェイルオーバーを要件(30秒以内)通りに動作させること
- 今回はHA挙動の動作確認を目的として、AWS EC2を仮想的なサーバー環境として使用
- AWSの高可用性サービス(ALB等)は使用せず、オンプレミスと同等の構成で検証
Corosync + Pacemaker の適用領域
| 層 | 代表的な手法 | 特徴 |
|---|---|---|
| フロント〜アプリ層 | F5 / HAProxy / Keepalived | 無停止・高速切替(秒以下)が求められる |
| DB・基幹系 | Corosync + Pacemaker | ある程度のダウンタイム(〜30秒)が許容される |
💡Corosync + Pacemaker は「完全無停止」ではなく「自動復旧」を目的とした仕組み。 フロント層の無停止にはLB(F5等)を組み合わせるのが一般的。
今回 Nginx を検証対象にした理由
- PostgreSQL(DB)の方がPacemakerの典型的なユースケースに近い
- ただし今回はHA挙動(VIP切替・自動復旧)の確認が目的のため、設定がシンプルなNginxで代替
- 本番適用時はDB(PostgreSQL等)のリソースエージェントに置き換えて同様の構成が可能
AIエージェント(Aider)の制約
⚠️今回はAnthropicのAPIキーが利用できなかったため、Bedrock対応のAider(対話型)で代替した。
- Aiderは対話型のAIコーディングアシスタントであり、自律的にコマンドを実行する自立型エージェントではない
- ユーザーが指示を出し、Aiderがファイル編集と「実行すべきコマンド」を提示する形で動作する
- SSH経由での他ノードへの直接操作は不可。node1で編集した設定ファイルはnode2へ手動でscpする必要がある
- そのため
nginx-ha-requirements.mdに「編集後はscp + restartを実行すること」などの手順を明記しておくことで、Aiderがユーザーに正しい操作手順を提示できる
障害パターン別RTO要件
| 障害パターン | 内容 | 目標RTO |
|---|---|---|
| ノード障害 | サーバーダウン、Corosync停止 | 30秒以内 |
| プロセス障害 | Nginxクラッシュ、プロセス停止 | 60秒以内(restart含む) |
| ネットワーク障害 | スプリットブレイン | 別途設計が必要 |
💡今回の検証はノード障害パターン(Corosync停止)を対象とした
構成概要
graph TD
Client["クライアント"]
VIP["Virtual IP (10.0.0.100)"]
Client -->|HTTP| VIP
subgraph VPC["AWS VPC"]
subgraph node1["node1 (10.0.0.182)"]
N1["Nginx"]
PM1["Pacemaker"]
CS1["Corosync"]
end
subgraph node2["node2 (10.0.5.29)"]
N2["Nginx"]
PM2["Pacemaker"]
CS2["Corosync"]
end
end
VIP --> node1
VIP -.->|待機| node2
CS1 <-->|ハートビート UDP 5404/5405| CS2
- 2台のEC2インスタンス(Amazon Linux 2023)
- 同一サブネット内にVIP(10.0.0.100)を配置
- Corosync: ノード間ハートビート通信
- Pacemaker: リソース(VIP・Nginx)の管理・制御
当初検証したかった構成: 完全自立型AIエージェントによるHA設定管理
Anthropic APIキーが利用可能であれば、Claude Codeによる完全自立型構成が実現できる。
graph TD
API["Anthropic API\nclaude-3-5-sonnet"]
subgraph node1["node1 (10.0.0.182)"]
CC["Claude Code\n(ターミナル)"]
Files1["設定ファイル\n/etc/corosync/corosync.conf\n/etc/corosync/authkey\n/etc/nginx/nginx.conf"]
CS1["Corosync\nUDP 5404/5405"]
PM1["Pacemaker\nVIP・Nginx リソース管理"]
end
subgraph node2["node2 (10.0.5.29)"]
Files2["HA設定ファイル受信"]
CS2["Corosync\nUDP 5404/5405"]
PM2["Pacemaker\nVIP・Nginx リソース管理"]
N2["Nginx (待機中)"]
end
API -->|HTTPS APIキー認証| CC
CC -->|"① 編集\n② pcs maintenance-mode=true\n⑤ pcs resource restart Nginx\n⑥ pcs maintenance-mode=false"| Files1
CC -->|"③ scp\n④ systemctl restart corosync"| node2
Files1 --> CS1
CS1 -->|ノード障害通知| PM1
CS1 <-->|"ハートビート UDP 5404/5405"| CS2
CS2 -->|ノード障害通知| PM2
コンポーネント相関図(今回の実装: Aider + Bedrock)
graph TD
Bedrock["Amazon Bedrock\nclaude-3-5-sonnet (APAC)"]
subgraph node1["node1 (10.0.0.182)"]
Aider["Aider 対話型AIコーディングアシスタント\nコンテキスト: nginx-ha-requirements.md\n← ユーザーが自然言語で指示\n→ ファイル編集 + コマンドをユーザーに提示"]
Files1["設定ファイル\n/etc/corosync/corosync.conf\n/etc/nginx/nginx.conf"]
User["ユーザーがコマンドを実行\nscp → node2\nsystemctl restart corosync"]
CS1["Corosync\nUDP 5404/5405"]
PM1["Pacemaker\nVIP・Nginx リソース管理\nawsvip (OCF RA)"]
end
subgraph node2["node2 (10.0.5.29)"]
Files2["HA設定ファイル(scp)"]
CS2["Corosync\nUDP 5404/5405"]
PM2["Pacemaker\nVIP・Nginx リソース管理"]
N2["Nginx (待機中)"]
end
Bedrock -->|HTTPS IAMロール認証| Aider
Aider -->|編集| Files1
Aider -->|scp手順を提示| User
User -->|scp + systemctl| node2
CS1 -->|ノード障害通知| PM1
CS1 <-->|"ハートビート UDP 5404/5405"| CS2
CS2 -->|ノード障害通知| PM2
フェイルオーバートリガー 1. Corosync: ノード障害(ハートビート途絶 → 約3秒で検知) 2. Pacemaker: Nginxプロセス障害(monitor 10秒毎 → 3回失敗でF/O)
フェイルオーバーの仕組み
sequenceDiagram
participant C as クライアント
participant VIP as VIP (10.0.0.100)
participant N2 as node2 (Nginx稼働中)
participant N1 as node1 (待機中)
Note over C,N2: 通常時
C->>VIP: HTTP リクエスト
VIP->>N2: 転送
N2-->>C: 200 OK
Note over N2: node2 Corosync停止(障害)
N2--xN1: ハートビート途絶(約3秒で検知)
N1->>VIP: PacemakerがVIPをnode1のENIに移動
N1->>N1: Nginx起動
Note over C,N1: フェイルオーバー完了(12秒以内)
C->>VIP: HTTP リクエスト
VIP->>N1: 転送
N1-->>C: 200 OK
検証結果
フェイルオーバー前
- リソース稼働ノード: node2
- クラスター状態: node1・node2ともに Online、VIP・Nginx が node2 で稼働中
障害シミュレート
- node2のCorosync停止 → node2が OFFLINE
フェイルオーバー後(12秒以内)
- リソース移行先: node1(自動)
- VIPへのHTTPアクセス: 200 OK(継続)
エビデンスファイル: ha-evidence-20260312_034606.log
=== HA動作確認エビデンス === Thu Mar 12 03:46:06 UTC 2026 --- [1] フェイルオーバー前: クラスター状態 --- Cluster name: nginx-ha Cluster Summary: * Stack: corosync (Pacemaker is running) * Current DC: node1 (version 3.0.1-0.4.rc2.amzn2023.0.1-042ad46) - partition with quorum * Last updated: Thu Mar 12 03:46:06 2026 on node1 * Last change: Thu Mar 12 03:45:47 2026 by root via root on node1 * 2 nodes configured * 2 resource instances configured Node List: * Online: [ node1 node2 ] Full List of Resources: * Nginx (ocf:heartbeat:nginx): Started node2 * VirtualIP (ocf:heartbeat:awsvip): Started node2 Daemon Status: corosync: active/enabled pacemaker: active/enabled pcsd: inactive/disabled --- [2] 障害シミュレート --- アクティブノード(node2)で以下を実行してください: sudo systemctl stop corosync フェイルオーバー開始時刻: 2026-03-12 03:46:16 --- [3] フェイルオーバー監視: VIPへのポーリング(最大30秒) --- [03:46:16] +0秒: HTTP 000 (待機中...) [03:46:17] +1秒: HTTP 000 (待機中...) [03:46:18] +2秒: HTTP 000 (待機中...) [03:46:19] +3秒: HTTP 000 (待機中...) [03:46:20] +4秒: HTTP 000 (待機中...) [03:46:21] +5秒: HTTP 000 (待機中...) [03:46:22] +6秒: HTTP 000 (待機中...) [03:46:23] +7秒: HTTP 000 (待機中...) [03:46:24] +8秒: HTTP 000 (待機中...) [03:46:25] +9秒: HTTP 000 (待機中...) [03:46:26] +10秒: HTTP 000 (待機中...) [03:46:27] +11秒: HTTP 000 (待機中...) [03:46:28] +12秒: HTTP 200 ← フェイルオーバー完了 --- [4] フェイルオーバー後: クラスター状態 --- Node List: * Online: [ node1 ] * OFFLINE: [ node2 ] Full List of Resources: * Nginx (ocf:heartbeat:nginx): Started node1 * VirtualIP (ocf:heartbeat:awsvip): Started node1 (Monitoring) --- [5] フェイルオーバー後: VIPへのHTTPアクセス --- < HTTP/1.1 200 OK < Server: nginx/1.28.2 OK --- [6] 結果サマリー --- ✓ フェイルオーバー完了: 12秒(要件: 30秒以内) ✓ 要件達成 === 確認完了: ha-evidence-20260312_034606.log に保存されました ===
corosync.conf チューニングポイント
totem: transport: udpu # ユニキャスト(EC2はマルチキャスト不可) token: 3000 # ハートビートタイムアウト 3000ms join: 60 consensus: 3600 max_messages: 20 quorum: two_node: 1 # 2ノード構成用クォーラム設定
| パラメータ | 設定値 | 理由 |
|---|---|---|
transport: udpu |
ユニキャスト | EC2環境ではマルチキャスト(udp)が使用不可 |
token: 3000 |
3000ms | ノード障害の検知タイムアウト。EC2のネットワーク遅延を考慮しデフォルト(1000ms)より余裕を持たせた |
two_node: 1 |
有効 | 2ノード構成では1台停止時にクォーラム喪失を防ぐために必須 |
join: 60 |
60ms | ノードがクラスターに参加するまでの待機時間(単位: ms) |
consensus: 3600 |
3600ms | クォーラム合意のタイムアウト。EC2環境での安定性を考慮 |
F/O時間12秒の内訳:
token: 3000ms → ノード障害検知(約3秒)
↓
Pacemaker: VIP付け替え(AWS ENI操作)
↓
Nginx起動 + ヘルスチェック通過
↓
合計: 12秒以内
💡
tokenの値を下げるほど検知は速くなるが、ネットワーク瞬断での誤検知リスクが上がる。EC2環境では3000ms程度が安定動作の目安。💡クラウド環境で異なるリージョンをまたいで構成した場合、リージョン間レイテンシ(数十〜100ms超)によりCorosyncのハートビートが誤タイムアウトし、正常ノードを障害と誤検知して誤フェイルオーバーが頻発する。Corosync+Pacemakerはそもそも同一データセンター(低レイテンシ前提)の設計なので、リージョン跨ぎはアーキテクチャ的に向いていない。
スティッキー動作とは: フェイルオーバー後、元のノードが復旧しても自動的に戻らない制御
# enable,disableじゃなくてスコアで指定する。 # 0にすると復旧した元のノードに自動フェイルバックし、INFINITYにすると障害が起きても絶対に移動しなくなるので実用上は使わない。 設定: default-resource-stickiness=100
| タイミング | リソース稼働ノード |
|---|---|
| フェイルオーバー前 | node2 |
| node2障害発生 | node1(自動移行) |
| node2復旧後 | node1のまま(自動フェイルバックなし) |
→ 不要な切り替えによるサービス断を防止
セキュリティ対策
| 項目 | 対策 |
|---|---|
| SSH認証 | 公開鍵認証のみ(パスワード認証無効) |
| Corosync通信 | UDP 5404/5405をノード間のみ許可 |
| authkey | パーミッション400(オーナーのみ読み取り) |
| AWS認証 | IAMロール(最小権限)、APIキー不要 |
| IAMロール | Bedrock + ENI操作に必要なEC2 APIのみ許可 |
AIエージェント(Aider)の活用
各ノードにAider + Amazon Bedrockをインストール
# EC2環境: IAMロールで自動認証(APIキー不要) export AWS_REGION=ap-northeast-1 bash /home/ec2-user/start-aider.sh
nginx-ha-requirements.mdをコンテキストとして読み込み- 要件を理解した上で設定ファイルを編集支援
- 編集対象:
/etc/nginx/nginx.conf,/etc/corosync/corosync.conf - モデル:
bedrock/apac.anthropic.claude-3-5-sonnet-20241022-v2:0
| 項目 | Anthropic API | Amazon Bedrock(今回) |
|---|---|---|
| 認証 | APIキー(手動管理) | IAMロール(自動) |
| EC2での利用 | インターネット経由 | AWS内で完結 |
| オンプレでの利用 | 可能 | AWS認証情報で可能 |
| コスト管理 | Anthropic請求 | AWS請求に統合 |
⚠️Bedrockのモデル呼び出し方式が2種類あり、
anthropic.claude-3-5-sonnet-20241022-v2:0を直接指定するとオンデマンドスループットで呼び出そうとしてエラーになる。apac.を付けることでクロスリージョン推論プロファイル経由になり、正常に動作した。
AI以前 / AI以降: Corosync+Pacemaker設定の変化
AI以前: ハマりやすかったポイント
| # | 問題 | 症状 |
|---|---|---|
| 1 | transport のデフォルトがマルチキャスト(udp) |
EC2/VM環境でハートビートが届かずクラスター未形成。ログは「ring0 is not connected」程度 |
| 2 | authkeyの同期忘れ・パーミッション不正 | authentication failed で起動拒否。ログを見ないと原因不明 |
| 3 | pcs / crm コマンドの混在 | ネット上の古い手順(crm)と新しい手順(pcs)が混在しコピペが動かない |
| 4 | EC2で IPaddr2 を使用 |
Gratuitous ARPが効かずVIPが引き継がれない。AWSのネットワーク仕様に気づくまでが長い |
| 5 | two_node: 1 の設定漏れ |
1台停止でクォーラム喪失 → リソース全停止。「F/Oしたはずなのにサービスが上がらない」 |
| 6 | コロケーション・順序制約の未設定 | VIPとNginxが別ノードで起動、またはNginxがVIPより先に起動して失敗 |
| 7 | ログが分散 | corosync.log / pacemaker.log / journalctl の3箇所。どこを見るか分からず切り分けに時間 |
💡上記の問題は「なぜこの設定が必要か」の背景知識がないと、エラーログだけでは原因にたどり着けない。HA界隈のコミュニティでの会話を重ねて得た知見で試行錯誤して数日かかるケースも珍しくなかった。
AI以降(今回): Claude + Aider で変わったこと
| 観点 | 変化 |
|---|---|
| 設計の抜け漏れ | 要件定義・設計書の段階でチューニングポイントを網羅。udpu・two_node・制約設定が最初から含まれる |
| EC2固有の制約 | awsvip の必要性、NIC名が ens5 になる点など |
| コマンド体系 | pcs / crm の混乱なく、正しいコマンド体系で一貫して進められる |
| トラブルシュート | エラーが出た際に自然言語で質問 → 原因と対処を即座に提示 |
| ドキュメント | 設定の意図・理由が設計書に残るため、後から見ても理解できる |
まとめ
- たった半日でF/O動作まで確認できた
- オンプレミス向けNginx HA設計をAWS EC2で動作検証完了
- ノード障害パターンで12秒以内のフェイルオーバーを確認(要件: 30秒以内)
- スティッキー動作により不要なフェイルバックを防止
- AIエージェント(Aider)による設定作業支援の基盤を整備
- エビデンスログ(タイムスタンプ付き)による第三者への説明が可能
💡当時の工数と比較して概ね作業時間93%削減、14倍速で完了。 AIが「答えを知っている」というより、「ハマりポイントを事前に潰した設計書を最初から出してくれる」のが本質的な価値。
[Bonus1] 技術選定の背景
オンプレミス環境でのHA構成として Corosync + Pacemaker を選定。
| 方式 | オンプレ適用 | 備考 |
|---|---|---|
| ハードウェアLB | ✓ | コスト高(F5とか1台で2000万円するし独自の管理方法にも座学コストが発生する) |
| Corosync + Pacemaker | ✓ | OSS、実績豊富(大きなコミュニティがある) |
| Keepalived + VRRP | ✓ | 機能限定(使ったことない) |
今回のAWS検証環境での制約と対応:
| オンプレ設定 | AWS検証環境での代替 |
|---|---|
ocf:heartbeat:IPaddr2(Gratuitous ARP) |
ocf:heartbeat:awsvip(ENIセカンダリIP) |
| VRRPによるVIP引き継ぎ | AWS ENI操作によるVIP引き継ぎ |
💡オンプレ本番環境では
ocf:heartbeat:IPaddr2を使用する
[Bonus2] 作成した設定ファイル
phase1-install-packages.sh
#!/bin/bash
# =============================================================================
# フェーズ1: 両ノードへのパッケージインストールスクリプト
# 対象: Primary Node / Secondary Node(共通)
# 実行方法: 各ノードにSSH接続後、このスクリプトを実行する
#
# 使用方法:
# # ローカルからノードへスクリプトを転送
# scp -i ~/.ssh/<key>.pem phase1-install-packages.sh ec2-user@<NODE_IP>:/home/ec2-user/
#
# # ノードにSSH接続してスクリプトを実行
# ssh -i ~/.ssh/<key>.pem ec2-user@<NODE_IP>
# chmod +x /home/ec2-user/phase1-install-packages.sh
# sudo /home/ec2-user/phase1-install-packages.sh
#
# 要件: 1.2, 1.3, 1.4, 4.1, 6.1, 6.2, 6.6
# =============================================================================
set -e
# カラー出力
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_section() { echo -e "\n${YELLOW}========================================${NC}"; echo -e "${YELLOW}$1${NC}"; echo -e "${YELLOW}========================================${NC}"; }
# rootで実行されているか確認
if [ "$(id -u)" -ne 0 ]; then
log_error "このスクリプトはrootまたはsudoで実行してください"
exit 1
fi
# =============================================================================
# タスク 3.1: Python 3.8以上の確認(要件: 6.6)
# =============================================================================
log_section "タスク 3.1: Python 3.8以上の確認"
PYTHON_VERSION=$(python3 --version 2>&1)
log_info "Python バージョン: ${PYTHON_VERSION}"
# バージョンチェック(3.8以上であることを確認)
python3 -c "
import sys
version = sys.version_info
if version < (3, 8):
print(f'ERROR: Python 3.8以上が必要です。現在のバージョン: {version.major}.{version.minor}.{version.micro}')
sys.exit(1)
else:
print(f'OK: Python {version.major}.{version.minor}.{version.micro} は要件を満たしています(3.8以上)')
"
log_info "タスク 3.1 完了: Python バージョン確認OK"
# =============================================================================
# タスク 3.3: CorosyncおよびPacemakerのインストール(要件: 1.3, 1.4)
# =============================================================================
log_section "タスク 3.3: CorosyncおよびPacemakerのインストール"
log_info "パッケージをインストールしています: corosync pacemaker pcs"
dnf install -y corosync pacemaker pcs
log_info "インストール確認中..."
rpm -q corosync pacemaker pcs
log_info "タスク 3.3 完了: Corosync/Pacemaker/pcs インストールOK"
# =============================================================================
# タスク 3.5: Nginxのインストール(要件: 4.1)
# =============================================================================
log_section "タスク 3.5: Nginxのインストール"
log_info "Nginxをインストールしています..."
dnf install -y nginx
log_info "インストール確認中..."
nginx -v
log_info "タスク 3.5 完了: Nginx インストールOK"
# =============================================================================
# タスク 3.6: Aiderのインストール(要件: 6.1, 6.2)
# =============================================================================
log_section "タスク 3.6: Aiderのインストール"
log_info "python3-pipをインストールしています..."
dnf install -y python3-pip
log_info "aider-chatをインストールしています..."
# --ignore-installed: rpmでインストール済みのパッケージとの競合を回避
python3 -m pip install --ignore-installed aider-chat
log_info "インストール確認中..."
python3 -m aider --version 2>/dev/null || aider --version
log_info "タスク 3.6 完了: Aider インストールOK"
# =============================================================================
# タスク 3.8: pcsdサービスの有効化と起動(要件: 1.2)
# =============================================================================
log_section "タスク 3.8: pcsdサービスの有効化と起動"
log_info "pcsdサービスを有効化して起動しています..."
systemctl enable --now pcsd
log_info "pcsdサービスの状態確認..."
systemctl status pcsd --no-pager
log_warn "haclusterユーザーのパスワードを設定してください(クラスター認証に必要)"
log_warn "以下のコマンドを手動で実行してください:"
echo ""
echo " sudo passwd hacluster"
echo ""
log_warn "Primary NodeとSecondary Nodeで同じパスワードを設定することを推奨します"
log_info "タスク 3.8 完了: pcsd サービス有効化・起動OK"
# =============================================================================
# 完了サマリー
# =============================================================================
log_section "フェーズ1 完了サマリー"
echo ""
log_info "以下のタスクが完了しました:"
echo " ✓ 3.1 Python 3.8以上の確認"
echo " ✓ 3.3 Corosync/Pacemaker/pcs インストール"
echo " ✓ 3.5 Nginx インストール"
echo " ✓ 3.6 Aider インストール"
echo " ✓ 3.8 pcsd サービス有効化・起動"
echo ""
log_warn "残りの手動作業:"
echo " ! sudo passwd hacluster ← 両ノードで実行してください"
echo ""
log_info "次のステップ: フェーズ4(Corosync設定)へ進んでください"
phase2-corosync-setup.sh(Primary Nodeで実行)
#!/bin/bash
# =============================================================================
# フェーズ4: Corosync設定スクリプト
# nginx-ha-corosync-pacemaker
#
# 【設計方針】
# Primary NodeからSecondary Nodeへ直接scpでファイルを配布する。
# 両ノード間のSSH接続が事前に設定済みであることを前提とする。
#
# 【実行手順】
#
# Step 1: スクリプトを両ノードへ転送(ローカルマシンで実行)
# scp -i ~/.ssh/ha2026.pem phase4-corosync-setup.sh \
# ec2-user@ec2-54-65-133-39.ap-northeast-1.compute.amazonaws.com:/home/ec2-user/
# scp -i ~/.ssh/ha2026.pem phase4-corosync-setup.sh \
# ec2-user@ec2-13-158-13-202.ap-northeast-1.compute.amazonaws.com:/home/ec2-user/
#
# Step 2: Primary Nodeで実行
# ssh -i ~/.ssh/ha2026.pem ec2-user@ec2-54-65-133-39.ap-northeast-1.compute.amazonaws.com
# chmod +x ~/phase4-corosync-setup.sh
# ~/phase4-corosync-setup.sh primary \
# --primary-ip 10.0.0.182 \
# --secondary-ip 10.0.18.234 \
# --secondary-host ec2-13-158-13-202.ap-northeast-1.compute.amazonaws.com \
# --key ~/.ssh/ha2026.pem
#
# Step 3: Secondary Nodeで実行(Primary完了後)
# ssh -i ~/.ssh/ha2026.pem ec2-user@ec2-13-158-13-202.ap-northeast-1.compute.amazonaws.com
# ~/phase4-corosync-setup.sh secondary \
# --primary-ip 10.0.0.182 \
# --secondary-ip 10.0.18.234
#
# 要件: 2.1, 2.2, 2.3, 2.5, 7.2, 1.2, 8.2
# =============================================================================
set -euo pipefail
# -----------------------------------------------------------------------------
# 定数
# -----------------------------------------------------------------------------
COROSYNC_CONF="/etc/corosync/corosync.conf"
AUTHKEY_PATH="/etc/corosync/authkey"
LOG_DIR="/var/log/cluster"
CLUSTER_NAME="nginx-ha"
TOKEN_TIMEOUT=3000 # 要件 2.3: 5000ms以内
# -----------------------------------------------------------------------------
# カラー出力
# -----------------------------------------------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
# -----------------------------------------------------------------------------
# 引数パース
# -----------------------------------------------------------------------------
NODE_ROLE="${1:-}"
shift || true
PRIMARY_IP="${PRIMARY_IP:-}"
SECONDARY_IP="${SECONDARY_IP:-}"
SECONDARY_HOST="${SECONDARY_HOST:-}"
SSH_KEY="${SSH_KEY:-}"
while [[ $# -gt 0 ]]; do
case "$1" in
--primary-ip) PRIMARY_IP="$2"; shift 2 ;;
--secondary-ip) SECONDARY_IP="$2"; shift 2 ;;
--secondary-host) SECONDARY_HOST="$2"; shift 2 ;;
--key) SSH_KEY="$2"; shift 2 ;;
*) log_error "不明なオプション: $1"; exit 1 ;;
esac
done
# -----------------------------------------------------------------------------
# 入力検証
# -----------------------------------------------------------------------------
validate_inputs() {
if [[ -z "$NODE_ROLE" ]]; then
log_error "ノードロールを指定してください: primary または secondary"
echo "使用方法: $0 {primary|secondary} --primary-ip <IP> --secondary-ip <IP> [--secondary-host <HOST>] [--key <pem>]"
exit 1
fi
if [[ "$NODE_ROLE" != "primary" && "$NODE_ROLE" != "secondary" ]]; then
log_error "ノードロールは 'primary' または 'secondary' を指定してください"
exit 1
fi
if [[ -z "$PRIMARY_IP" || -z "$SECONDARY_IP" ]]; then
log_error "--primary-ip と --secondary-ip は必須です"
exit 1
fi
if [[ "$NODE_ROLE" == "primary" ]]; then
if [[ -z "$SECONDARY_HOST" ]]; then
log_error "Primary Nodeでは --secondary-host が必要です"
exit 1
fi
if [[ -z "$SSH_KEY" ]]; then
log_error "Primary Nodeでは --key (pemファイルパス) が必要です"
exit 1
fi
if [[ ! -f "$SSH_KEY" ]]; then
log_error "SSHキーファイルが見つかりません: $SSH_KEY"
exit 1
fi
fi
}
# -----------------------------------------------------------------------------
# ログディレクトリの作成(両ノード共通)
# 要件: 8.2
# -----------------------------------------------------------------------------
setup_log_dir() {
log_info "ログディレクトリを作成します: $LOG_DIR"
sudo mkdir -p "$LOG_DIR"
sudo chown root:root "$LOG_DIR"
sudo chmod 755 "$LOG_DIR"
}
# -----------------------------------------------------------------------------
# タスク 5.1: authkey生成(Primary Nodeのみ)
# 要件: 2.5, 7.2
# -----------------------------------------------------------------------------
generate_authkey() {
log_info "=== タスク 5.1: Corosync認証キーを生成 ==="
if [[ -f "$AUTHKEY_PATH" ]]; then
log_warn "authkeyが既に存在します。既存のキーを使用します。"
else
sudo corosync-keygen -l -k "$AUTHKEY_PATH"
sudo chmod 400 "$AUTHKEY_PATH"
sudo chown root:root "$AUTHKEY_PATH"
log_info "authkey生成完了"
fi
# パーミッション確認(要件 7.2)
local perm
perm=$(stat -c "%a" "$AUTHKEY_PATH")
if [[ "$perm" != "400" ]]; then
log_error "authkeyのパーミッションが正しくありません: $perm (期待値: 400)"
exit 1
fi
log_info "authkeyパーミッション確認OK: $perm"
}
# -----------------------------------------------------------------------------
# タスク 5.3: authkeyをSecondary Nodeへ配布(Primary Nodeのみ)
# 要件: 2.5
# -----------------------------------------------------------------------------
distribute_authkey() {
log_info "=== タスク 5.3: authkeyをSecondary Nodeへ配布 ==="
# scpはrootファイルを直接転送できないため、一時的に読み取り可能な状態でコピー
sudo cp "$AUTHKEY_PATH" /tmp/authkey.transfer
sudo chmod 644 /tmp/authkey.transfer
scp -i "$SSH_KEY" \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=10 \
/tmp/authkey.transfer \
"ec2-user@${SECONDARY_HOST}:/tmp/authkey"
sudo rm -f /tmp/authkey.transfer
# Secondary Node側でパーミッションを設定
ssh -i "$SSH_KEY" \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=10 \
"ec2-user@${SECONDARY_HOST}" \
"sudo mv /tmp/authkey /etc/corosync/authkey && sudo chmod 400 /etc/corosync/authkey && sudo chown root:root /etc/corosync/authkey"
# Secondary NodeのパーミッションをSSH経由で確認
local remote_perm
remote_perm=$(ssh -i "$SSH_KEY" \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=10 \
"ec2-user@${SECONDARY_HOST}" \
"stat -c '%a' /etc/corosync/authkey")
if [[ "$remote_perm" != "400" ]]; then
log_error "Secondary NodeのauthkeyパーミッションNG: $remote_perm (期待値: 400)"
exit 1
fi
log_info "Secondary Node authkeyパーミッション確認OK: $remote_perm"
}
# -----------------------------------------------------------------------------
# タスク 5.4: corosync.conf作成
# 要件: 2.1, 2.2, 2.3
# -----------------------------------------------------------------------------
create_corosync_conf() {
log_info "=== タスク 5.4: corosync.confを作成 ==="
sudo mkdir -p "$LOG_DIR"
sudo tee "$COROSYNC_CONF" > /dev/null <<EOF
# Corosync設定ファイル
# クラスター名: ${CLUSTER_NAME}
# 生成日時: $(date '+%Y-%m-%d %H:%M:%S')
#
# 要件:
# 2.1 - 有効な設定ファイル
# 2.2 - UDPユニキャスト(EC2環境ではマルチキャスト不可)
# 2.3 - トークンタイムアウト ${TOKEN_TIMEOUT}ms(上限5000ms)
totem {
version: 2
cluster_name: ${CLUSTER_NAME}
transport: udpu
token: ${TOKEN_TIMEOUT}
join: 60
consensus: 3600
max_messages: 20
}
nodelist {
node {
ring0_addr: ${PRIMARY_IP}
name: node1
nodeid: 1
}
node {
ring0_addr: ${SECONDARY_IP}
name: node2
nodeid: 2
}
}
quorum {
provider: corosync_votequorum
two_node: 1
}
logging {
to_logfile: yes
logfile: ${LOG_DIR}/corosync.log
to_syslog: yes
}
EOF
sudo chmod 644 "$COROSYNC_CONF"
sudo chown root:root "$COROSYNC_CONF"
# token値の検証(要件 2.3)
local token_val
token_val=$(grep -E "^\s*token:" "$COROSYNC_CONF" | awk '{print $2}')
if [[ -z "$token_val" ]] || [[ "$token_val" -gt 5000 ]]; then
log_error "token値が要件を満たしていません: $token_val (上限: 5000ms)"
exit 1
fi
log_info "corosync.conf作成完了 (token: ${token_val}ms)"
}
# -----------------------------------------------------------------------------
# corosync.confをSecondary Nodeへ配布(Primary Nodeのみ)
# -----------------------------------------------------------------------------
distribute_corosync_conf() {
log_info "=== corosync.confをSecondary Nodeへ配布 ==="
scp -i "$SSH_KEY" \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=10 \
"$COROSYNC_CONF" \
"ec2-user@${SECONDARY_HOST}:/tmp/corosync.conf"
ssh -i "$SSH_KEY" \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=10 \
"ec2-user@${SECONDARY_HOST}" \
"sudo mv /tmp/corosync.conf /etc/corosync/corosync.conf && sudo chmod 644 /etc/corosync/corosync.conf && sudo chown root:root /etc/corosync/corosync.conf"
log_info "Secondary Nodeへのcorosync.conf配布完了"
}
# -----------------------------------------------------------------------------
# タスク 5.6: Corosync起動
# 要件: 2.2
# -----------------------------------------------------------------------------
start_corosync() {
log_info "=== タスク 5.6: Corosyncサービスを起動 ==="
sudo systemctl enable --now corosync
local retries=6
for ((i=1; i<=retries; i++)); do
if sudo systemctl is-active --quiet corosync; then
log_info "Corosync起動確認OK"
break
fi
if [[ $i -eq $retries ]]; then
log_error "Corosyncの起動に失敗しました"
sudo systemctl status corosync --no-pager || true
exit 1
fi
log_warn "起動待機中... (${i}/${retries})"
sleep 5
done
log_warn "ring0確認(両ノード起動後に正常になります):"
sudo corosync-cfgtool -s || log_warn "ring0確認失敗(両ノード起動後に再確認してください)"
}
# -----------------------------------------------------------------------------
# タスク 5.7: Pacemaker起動
# 要件: 1.2
# -----------------------------------------------------------------------------
start_pacemaker() {
log_info "=== タスク 5.7: Pacemakerサービスを起動 ==="
sudo systemctl enable --now pacemaker
local retries=6
for ((i=1; i<=retries; i++)); do
if sudo systemctl is-active --quiet pacemaker; then
log_info "Pacemaker起動確認OK"
break
fi
if [[ $i -eq $retries ]]; then
log_error "Pacemakerの起動に失敗しました"
sudo systemctl status pacemaker --no-pager || true
exit 1
fi
log_warn "起動待機中... (${i}/${retries})"
sleep 5
done
sudo crm_mon -1 || log_warn "クラスター状態確認失敗(両ノード起動後に再確認してください)"
}
# -----------------------------------------------------------------------------
# メイン処理
# -----------------------------------------------------------------------------
main() {
validate_inputs
echo ""
log_info "=============================================="
log_info "フェーズ4: Corosync設定"
log_info "ノードロール : $NODE_ROLE"
log_info "Primary IP : $PRIMARY_IP"
log_info "Secondary IP: $SECONDARY_IP"
log_info "=============================================="
echo ""
setup_log_dir
if [[ "$NODE_ROLE" == "primary" ]]; then
generate_authkey
create_corosync_conf
distribute_authkey
distribute_corosync_conf
start_corosync
start_pacemaker
echo ""
log_info "=============================================="
log_info "Primary Node フェーズ4完了"
log_info ""
log_info "次のステップ: Secondary Nodeで以下を実行"
log_info " ~/phase4-corosync-setup.sh secondary \\"
log_info " --primary-ip $PRIMARY_IP \\"
log_info " --secondary-ip $SECONDARY_IP"
log_info ""
log_info "両ノード起動後の状態確認:"
log_info " sudo corosync-cfgtool -s"
log_info " sudo crm_mon -1"
log_info "=============================================="
else
# Secondary Node: authkeyとcorosync.confはPrimary Nodeから配布済みであることを確認
if [[ ! -f "$AUTHKEY_PATH" ]]; then
log_error "authkeyが見つかりません。Primary Nodeでスクリプトを先に実行してください。"
exit 1
fi
if [[ ! -f "$COROSYNC_CONF" ]]; then
log_error "corosync.confが見つかりません。Primary Nodeでスクリプトを先に実行してください。"
exit 1
fi
# パーミッション確認
local perm
perm=$(stat -c "%a" "$AUTHKEY_PATH")
if [[ "$perm" != "400" ]]; then
log_warn "authkeyのパーミッションを修正します: $perm -> 400"
sudo chmod 400 "$AUTHKEY_PATH"
fi
log_info "authkeyパーミッション確認OK: $(stat -c '%a' $AUTHKEY_PATH)"
start_corosync
start_pacemaker
echo ""
log_info "=============================================="
log_info "Secondary Node フェーズ4完了"
log_info ""
log_info "両ノードの状態確認:"
log_info " sudo corosync-cfgtool -s"
log_info " sudo crm_mon -1"
log_info "=============================================="
fi
}
main "$@"
phase3-pacemaker-setup.sh(Primary Nodeで実行)
#!/bin/bash
# =============================================================================
# フェーズ3: Pacemaker設定スクリプト
# nginx-ha-corosync-pacemaker
#
# 【設計方針】
# Primary NodeでPacemakerのクラスタープロパティ、リソース、制約を設定する。
# フェーズ4(Corosync設定)が両ノードで完了していることを前提とする。
#
# 【実行手順】
#
# Step 1: スクリプトをPrimary Nodeへ転送(ローカルマシンで実行)
# scp -i ~/.ssh/ha2026.pem phase5-pacemaker-setup.sh \
# ec2-user@ec2-54-65-133-39.ap-northeast-1.compute.amazonaws.com:/home/ec2-user/
#
# Step 2: Primary Nodeで実行(Primary Nodeのみで実行)
# ssh -i ~/.ssh/ha2026.pem ec2-user@ec2-54-65-133-39.ap-northeast-1.compute.amazonaws.com
# chmod +x ~/phase5-pacemaker-setup.sh
# ~/phase5-pacemaker-setup.sh --vip 10.0.0.100
#
# ※ VIPアドレスは同一サブネット内の未使用IPアドレスを指定すること
#
# 要件: 3.1, 3.2, 3.4, 3.5, 3.6, 3.7, 5.2
# =============================================================================
set -euo pipefail
# -----------------------------------------------------------------------------
# VIPアドレス(スクリプト先頭で定義 - 実際のIPに置き換えること)
# 同一サブネット内の未使用IPアドレスを設定する
# -----------------------------------------------------------------------------
VIP_ADDRESS="${VIP_ADDRESS:-REPLACE_WITH_ACTUAL_VIP}"
# -----------------------------------------------------------------------------
# カラー出力
# -----------------------------------------------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
# -----------------------------------------------------------------------------
# 引数パース
# -----------------------------------------------------------------------------
while [[ $# -gt 0 ]]; do
case "$1" in
--vip) VIP_ADDRESS="$2"; shift 2 ;;
*) log_error "不明なオプション: $1"; exit 1 ;;
esac
done
# -----------------------------------------------------------------------------
# 入力検証
# -----------------------------------------------------------------------------
validate_inputs() {
# --vip未指定の場合は対話的に入力を促す
if [[ "$VIP_ADDRESS" == "REPLACE_WITH_ACTUAL_VIP" || -z "$VIP_ADDRESS" ]]; then
echo ""
echo "VIPアドレスを入力してください。"
echo " - 同一サブネット内の未使用IPアドレスを指定すること"
echo " - 例: 10.0.0.100"
echo ""
read -rp "VIPアドレス: " VIP_ADDRESS
if [[ -z "$VIP_ADDRESS" ]]; then
log_error "VIPアドレスが入力されませんでした"
exit 1
fi
fi
# IPアドレス形式の簡易チェック
if ! echo "$VIP_ADDRESS" | grep -qE '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'; then
log_error "VIPアドレスの形式が正しくありません: $VIP_ADDRESS"
exit 1
fi
# Pacemakerが起動しているか確認
if ! sudo systemctl is-active --quiet pacemaker; then
log_error "Pacemakerが起動していません。フェーズ4を先に完了してください。"
exit 1
fi
# Corosyncが起動しているか確認
if ! sudo systemctl is-active --quiet corosync; then
log_error "Corosyncが起動していません。フェーズ4を先に完了してください。"
exit 1
fi
}
# -----------------------------------------------------------------------------
# タスク 6.1: クラスタープロパティの設定
# 要件: 5.2
# -----------------------------------------------------------------------------
configure_cluster_properties() {
log_info "=== タスク 6.1: クラスタープロパティを設定 ==="
# STONITHを無効化(AWS環境ではSTONITH代替設定)
log_info "STONITHを無効化します..."
pcs property set stonith-enabled=false
# 2ノード構成用クォーラムポリシー設定
log_info "no-quorum-policyをignoreに設定します..."
pcs property set no-quorum-policy=ignore
# スティッキー動作の設定(要件 5.2: 自動フェイルバック禁止)
log_info "default-resource-stickiness=100 を設定します(スティッキー動作)..."
pcs resource defaults update resource-stickiness=100
log_info "クラスタープロパティ設定完了"
# 設定確認
log_info "--- クラスタープロパティ確認 ---"
pcs property config | grep -E "stonith-enabled|no-quorum-policy" || true
pcs resource defaults || true
}
# -----------------------------------------------------------------------------
# タスク 6.2: VirtualIPリソースの作成
# 要件: 3.2
# -----------------------------------------------------------------------------
create_virtualip_resource() {
log_info "=== タスク 6.2: VirtualIPリソースを作成 ==="
log_info "VIPアドレス: $VIP_ADDRESS"
# 既存のVirtualIPリソースが存在する場合は削除して再作成
if pcs resource config VirtualIP &>/dev/null 2>&1; then
log_warn "既存のVirtualIPリソースを削除して再作成します..."
pcs resource delete VirtualIP
fi
# VirtualIPリソースの作成
pcs resource create VirtualIP ocf:heartbeat:awsvip \
secondary_private_ip="$VIP_ADDRESS" \
auth_type="role" \
region="ap-northeast-1" \
op monitor interval="10s" timeout="30s" \
op start timeout="30s" \
op stop timeout="30s"
log_info "VirtualIPリソース作成完了"
# 設定確認
log_info "--- VirtualIPリソース確認 ---"
pcs resource show VirtualIP || pcs resource config VirtualIP
}
# -----------------------------------------------------------------------------
# タスク 6.3: Nginxリソースの作成
# 要件: 3.1, 3.6, 3.7
# -----------------------------------------------------------------------------
create_nginx_resource() {
log_info "=== タスク 6.3: Nginxリソースを作成 ==="
# 既存のNginxリソースが存在する場合は削除して再作成
if pcs resource config Nginx &>/dev/null 2>&1; then
log_warn "既存のNginxリソースを削除して再作成します..."
pcs resource delete Nginx
fi
# Nginxリソースの作成(要件 3.1, 3.6, 3.7)
pcs resource create Nginx ocf:heartbeat:nginx \
configfile="/etc/nginx/nginx.conf" \
status10url="http://localhost/health" \
op start timeout="60s" \
op stop timeout="60s" \
op monitor interval="10s" timeout="30s" \
meta migration-threshold="3" failure-timeout="120s"
log_info "Nginxリソース作成完了"
# 設定確認
log_info "--- Nginxリソース確認 ---"
pcs resource show Nginx || pcs resource config Nginx
}
# -----------------------------------------------------------------------------
# タスク 6.4: コロケーション制約と順序制約の設定
# 要件: 3.4, 3.5
# -----------------------------------------------------------------------------
configure_constraints() {
log_info "=== タスク 6.4: コロケーション制約と順序制約を設定 ==="
# 既存の制約を削除して再作成
pcs constraint colocation remove Nginx VirtualIP 2>/dev/null || true
pcs constraint order remove VirtualIP Nginx 2>/dev/null || true
# コロケーション制約(要件 3.4): NginxとVirtualIPを同一ノードで稼働
log_info "コロケーション制約を設定します..."
pcs constraint colocation add Nginx with VirtualIP INFINITY
# 順序制約(要件 3.5): VirtualIP → Nginx の順で起動
log_info "順序制約を設定します..."
pcs constraint order VirtualIP then Nginx
log_info "制約設定完了"
# 設定確認
log_info "--- 制約確認 ---"
pcs constraint config
}
# -----------------------------------------------------------------------------
# 全体設定の確認
# -----------------------------------------------------------------------------
verify_configuration() {
log_info "=== 全体設定の確認 ==="
log_info "--- pcs status ---"
pcs status || true
echo ""
log_info "--- 設定検証チェック ---"
if pcs property config | grep -q "stonith-enabled=false"; then
log_info "✓ stonith-enabled=false 設定済み"
else
log_warn "✗ stonith-enabled=false が設定されていません"
fi
if pcs property config | grep -q "no-quorum-policy=ignore"; then
log_info "✓ no-quorum-policy=ignore 設定済み"
else
log_warn "✗ no-quorum-policy=ignore が設定されていません"
fi
if pcs resource show VirtualIP &>/dev/null 2>&1 || pcs resource config VirtualIP &>/dev/null 2>&1; then
log_info "✓ VirtualIPリソース設定済み (VIP: $VIP_ADDRESS)"
else
log_warn "✗ VirtualIPリソースが設定されていません"
fi
if pcs resource show Nginx &>/dev/null 2>&1 || pcs resource config Nginx &>/dev/null 2>&1; then
log_info "✓ Nginxリソース設定済み"
else
log_warn "✗ Nginxリソースが設定されていません"
fi
log_info "--- 制約一覧 ---"
pcs constraint config
}
# -----------------------------------------------------------------------------
# メイン処理
# -----------------------------------------------------------------------------
main() {
validate_inputs
echo ""
log_info "=============================================="
log_info "フェーズ5: Pacemaker設定"
log_info "VIPアドレス: $VIP_ADDRESS"
log_info "=============================================="
echo ""
# タスク 6.1: クラスタープロパティの設定
configure_cluster_properties
echo ""
# タスク 6.2: VirtualIPリソースの作成
create_virtualip_resource
echo ""
# タスク 6.3: Nginxリソースの作成
create_nginx_resource
echo ""
# タスク 6.4: コロケーション制約と順序制約の設定
configure_constraints
echo ""
# 全体設定の確認
verify_configuration
echo ""
log_info "=============================================="
log_info "フェーズ5: Pacemaker設定完了"
log_info ""
log_info "次のステップ: フェーズ6(Nginx設定)へ進む"
log_info " ~/phase6-nginx-setup.sh"
log_info ""
log_info "クラスター状態の継続監視:"
log_info " sudo pcs status"
log_info ""
log_info "プロパティテスト(タスク 6.5, 6.6)の確認コマンド:"
log_info " sudo pcs constraint config | grep colocation"
log_info " sudo pcs constraint config | grep order"
log_info " sudo pcs resource config Nginx | grep migration-threshold"
log_info " sudo pcs resource defaults"
log_info "=============================================="
}
main "$@"
start-aider.sh
#!/bin/bash
# Aider起動スクリプト for nginx-ha-corosync-pacemaker
# Amazon Bedrock経由でClaudeを利用
# EC2環境: IAMロールで自動認証(APIキー不要)
# オンプレ環境: AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY を事前に設定すること
#
# [モデル選択の注意]
# - anthropic.claude-sonnet-4-6 はオンデマンドスループット非対応(エラーになる)
# - anthropic.claude-3-5-sonnet-20241022-v2:0 も同様にオンデマンドスループット非対応
# - クロスリージョン推論プロファイル apac.anthropic.claude-3-5-sonnet-20241022-v2:0 を使用すること
#
# [boto3について]
# aider-chat v0.82.3 には boto3 が内包されていないため別途インストールが必要:
# pip3 install boto3 --no-deps
# pip3 install botocore --no-deps
# pip3 install s3transfer --no-deps
# (urllib3競合を避けるため --no-deps オプションを使用すること)
#
# [AWS認証について]
# aws sts get-caller-identity は環境によって動作しないため IMDSv2 で認証確認を行う
set -e
REQUIREMENTS_FILE="/home/ec2-user/nginx-ha-requirements.md"
MODEL="bedrock/apac.anthropic.claude-3-5-sonnet-20241022-v2:0"
AWS_REGION="${AWS_REGION:-ap-northeast-1}"
if [ ! -f "${REQUIREMENTS_FILE}" ]; then
echo "ERROR: 要件定義書が見つかりません: ${REQUIREMENTS_FILE}"
exit 1
fi
# AWS認証確認(IMDSv2でIAMロール確認、awscliに依存しない)
_check_auth() {
if [ -n "${AWS_ACCESS_KEY_ID}" ]; then
return 0
fi
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600" 2>/dev/null)
if [ -n "${TOKEN}" ]; then
ROLE=$(curl -s -H "X-aws-ec2-metadata-token: ${TOKEN}" \
"http://169.254.169.254/latest/meta-data/iam/security-credentials/" 2>/dev/null)
if [ -n "${ROLE}" ]; then
return 0
fi
fi
return 1
}
if ! _check_auth; then
echo "ERROR: AWS認証情報が設定されていません。"
echo "EC2環境: IAMロールに bedrock:InvokeModel 権限を付与してください"
echo "オンプレ環境: 以下を設定してください:"
echo " export AWS_ACCESS_KEY_ID='your-access-key'"
echo " export AWS_SECRET_ACCESS_KEY='your-secret-key'"
echo " export AWS_REGION=ap-northeast-1"
exit 1
fi
echo "Aiderを起動します(Bedrock経由、要件定義書をコンテキストとして読み込み)..."
echo "モデル: ${MODEL}"
echo "リージョン: ${AWS_REGION}"
echo "編集対象: /etc/corosync/corosync.conf, /etc/nginx/nginx.conf"
AWS_REGION="${AWS_REGION}" aider \
--model "${MODEL}" \
--read "${REQUIREMENTS_FILE}" \
/etc/corosync/corosync.conf \
/etc/nginx/nginx.conf
ha-evidence-test.sh
#!/bin/bash
# HA動作確認エビデンス取得スクリプト
# 使用方法: bash ha-evidence-test.sh
# 前提: node2でリソースが稼働中の状態で実行
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')
LOG="ha-evidence-${TIMESTAMP}.log"
VIP="10.0.0.100"
TIMEOUT=30 # 要件: 30秒以内
echo "=== HA動作確認エビデンス ===" | tee $LOG
date | tee -a $LOG
echo -e "\n--- [1] フェイルオーバー前: クラスター状態 ---" | tee -a $LOG
sudo pcs status | tee -a $LOG
echo -e "\n--- [2] フェイルオーバー前: VIPへのHTTPアクセス ---" | tee -a $LOG
curl -v http://${VIP}/health 2>&1 | tee -a $LOG
echo -e "\n--- [3] 障害シミュレート ---" | tee -a $LOG
echo "アクティブノード(node2)で以下を実行してください:" | tee -a $LOG
echo " sudo systemctl stop corosync" | tee -a $LOG
read -p "実行後にEnterを押してください..."
# ★ Enterを押した瞬間のタイムスタンプを記録
FAILOVER_START=$(date '+%Y-%m-%d %H:%M:%S')
FAILOVER_START_EPOCH=$(date +%s)
echo "フェイルオーバー開始時刻: ${FAILOVER_START}" | tee -a $LOG
echo -e "\n--- [4] フェイルオーバー監視: VIPへのポーリング(最大${TIMEOUT}秒) ---" | tee -a $LOG
FAILOVER_DONE=false
for i in $(seq 1 $TIMEOUT); do
RESULT=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 1 --max-time 1 http://${VIP}/health)
NOW=$(date '+%H:%M:%S')
ELAPSED=$(($(date +%s) - FAILOVER_START_EPOCH))
if [ "$RESULT" = "200" ]; then
echo "[${NOW}] +${ELAPSED}秒: HTTP ${RESULT} ← フェイルオーバー完了" | tee -a $LOG
FAILOVER_DONE=true
FAILOVER_ELAPSED=$ELAPSED
break
else
echo "[${NOW}] +${ELAPSED}秒: HTTP ${RESULT} (待機中...)" | tee -a $LOG
fi
sleep 1
done
echo -e "\n--- [5] フェイルオーバー後: クラスター状態 ---" | tee -a $LOG
sudo pcs status | tee -a $LOG
echo -e "\n--- [6] フェイルオーバー後: VIPへのHTTPアクセス ---" | tee -a $LOG
curl -v http://${VIP}/health 2>&1 | tee -a $LOG
echo -e "\n--- [7] 結果サマリー ---" | tee -a $LOG
if [ "$FAILOVER_DONE" = "true" ]; then
echo "✓ フェイルオーバー完了: ${FAILOVER_ELAPSED}秒(要件: ${TIMEOUT}秒以内)" | tee -a $LOG
if [ $FAILOVER_ELAPSED -le $TIMEOUT ]; then
echo "✓ 要件達成" | tee -a $LOG
else
echo "✗ 要件未達(${FAILOVER_ELAPSED}秒 > ${TIMEOUT}秒)" | tee -a $LOG
fi
else
echo "✗ ${TIMEOUT}秒以内にフェイルオーバー完了せず(要件未達)" | tee -a $LOG
fi
echo -e "\n=== 確認完了: ${LOG} に保存されました ===" | tee -a $LOG
/etc/corosync/corosync.conf
# Corosync設定ファイル # クラスター名: nginx-ha # 生成日時: 2026-02-26 04:03:57 # # 要件: # 2.1 - 有効な設定ファイル # 2.2 - UDPユニキャスト(EC2環境ではマルチキャスト不可) # 2.3 - トークンタイムアウト 3000ms(上限5000ms) totem { version: 2 cluster_name: nginx-ha transport: udpu token: 3000 join: 60 consensus: 3600 max_messages: 20 } nodelist { node { ring0_addr: 10.0.0.182 name: node1 nodeid: 1 } node { ring0_addr: 10.0.5.29 name: node2 nodeid: 2 } } quorum { provider: corosync_votequorum two_node: 1 } logging { to_logfile: yes logfile: /var/log/cluster/corosync.log to_syslog: yes }
/etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 4096;
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name _;
location /health {
return 200 "OK\n";
add_header Content-Type text/plain;
}
location / {
root /usr/share/nginx/html;
index index.html;
}
}
}
[Bonus3] PostgreSQL HA特有の複雑さ
Nginxと違い、PostgreSQLはステートフルなのでHA構成が格段に複雑になる。
- Primary/Standbyのレプリケーション設定(postgresql.conf、pg_hba.conf、recovery.conf)
- PostgreSQL 12以降は
recovery.confが廃止され、postgresql.confにprimary_conninfo等を記述し、standby.signalファイルを配置する方式に変更 - F/O時にStandbyをPrimaryに昇格させる処理(
pg_ctl promote) - 旧Primaryが復旧した時にSplitBrain(両方がPrimaryになる)を防ぐフェンシング設定
- データの巻き戻し(旧PrimaryをStandbyとして再参加させる
pg_rewind) - Pacemakerのリソースエージェントが
pgsqlms(旧pgsql)で、パラメータがとても多い
💡SplitBrainが起きた時に両方が書き込みを続けるとデータが壊れる。DBに限らずNFSサーバーや分散ファイルシステムでも同じ問題がある。
STONITHが実質必須。 AWS環境だとSTONITHの代替実装も必要で、さらに複雑になる。