🔄

ホームラボ向け Proxmox 自動構築 Part3 ── Keepalived + Nginx で VIP と標準ポートアクセスを実現

に公開

概要

Part1 では Proxmox VE の ISO 自動生成からノード発見・固定 IP 化まで、Part2 ではリポジトリ設定とクラスタ構築を自動化しました。本記事はその続きとして、Day2 設定の一部である VIP(仮想 IP)の高可用性構成Web UI への標準ポートアクセスを Ansible で自動化した内容を紹介します。

  • Keepalived で VIP を管理し、ノード障害時に自動フェイルオーバーを実現
  • Nginx でリバースプロキシを構成し、ポート 8006 を 443 に標準化
  • すべての設定を Ansible ロールとして実装し、ノード追加時も手動設定不要

https://github.com/tjst-t/ansible-proxmox-infra

背景

Part1・Part2 で、Proxmox VE のインストールからブートストラップ、リポジトリ設定・クラスタ構築までを自動化しました。ブートストラップ完了後のノードに対して、クラスタ構築やリポジトリ設定などの Day2 設定を行うのが configure_proxmox.yml です。

playbooks/configure_proxmox.yml(ロール適用順序)
roles:
  - role: proxmox_repo
  - role: proxmox_cluster
  - role: cloudflare_dns
  - role: proxmox_acme
  - role: nginx
  - role: proxmox_keepalived
  - role: proxmox_dc_settings

本記事では、このうち nginx ロールと proxmox_keepalived ロールの実装を解説します。

課題

ブートストラップ完了後の Proxmox 環境には、以下の課題がありました。

  • 単一障害点: 各ノードの IP に直接アクセスする運用では、そのノードがダウンすると Web UI にアクセスできなくなる。クラスタ自体は別ノードで稼働し続けるにもかかわらず、管理画面を開けない
  • 非標準ポート: Proxmox の Web UI はデフォルトでポート 8006 を使用する。https://pve01:8006 のようにポート番号を毎回指定する必要があり、ブックマークや他ツールとの連携で手間になる

アプローチ

VIP(Virtual IP)の管理には Keepalived を選びました。Keepalived は VRRP(Virtual Router Redundancy Protocol)を使ってノード間で仮想 IP を共有するソフトウェアです。MASTER ノードがダウンすると、BACKUP ノードが自動的に VIP を引き継ぎます。
Keepalived だけでは VIP の移動しかできません。VIP 宛のリクエストを Proxmox の Web UI(ポート 8006)に中継し、かつ標準ポート(443)でアクセスできるようにするために、各ノードに Nginx をリバースプロキシとして配置します。

Nginx を選んだ理由は以下の通りです。

  • ポートの標準化(8006 → 443)が設定ひとつで実現できる
  • WebSocket のプロキシに対応している(proxy_set_header Upgrade / Connection "upgrade"
  • 将来的に証明書管理を Nginx に統一できる
  • Proxmoxの公式wikiに手順の案内がある

構成のイメージ

VIP は MASTER ノードにのみ付与されるため、通常時はそのノードの Nginx だけがリクエストを受け取ります。Nginx はローカルの Proxmox Web UI(127.0.0.1:8006)に転送します。

実装の詳細 -- Keepalived

ヘルスチェックスクリプト

Keepalived は定期的にサービスのヘルスチェックを実行し、その結果に応じて VIP の所有権を判断します。ヘルスチェックには以下のスクリプトを使います。このスクリプトにより、MASTERは自分のProxmoxのWeb UIが正常に動作しているかをチェックします。もしチェックが失敗した場合は別ホストにVIPが移動します。

roles/proxmox_keepalived/templates/pve_check.sh.j2
#!/bin/bash
ss -tln | grep -q :8006
exit $?

ss -tln で TCP のリスンポートを一覧し、ポート 8006(Proxmox の Web UI)が開いているかを確認します。Proxmox のサービス(pveproxy)が正常に動作していればポート 8006 がリスンしているため、この簡易チェックで十分です。

VRRP 設定

roles/proxmox_keepalived/templates/keepalived.conf.j2
global_defs {
    script_user root
    enable_script_security
}

vrrp_script check_pve {
    script "/usr/local/bin/pve_check.sh"
    interval 2
    weight 5
}

vrrp_instance VI_1 {
    state {{ 'MASTER' if inventory_hostname == proxmox_cluster_primary_node else 'BACKUP' }}
    interface {{ proxmox_vip_interface }}
    virtual_router_id {{ proxmox_vip_router_id }}
    priority {{ 100 + (ansible_play_hosts_all.index(inventory_hostname) * -10) }}
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass {{ proxmox_vip_pass }}
    }
    virtual_ipaddress {
        {{ proxmox_vip }}
    }
    track_script {
        check_pve
    }
}

設定のポイントを解説します。

vrrp_script ブロック

  • interval 2: 2 秒ごとにヘルスチェックを実行
  • weight 5: チェック成功時に優先度を +5 する(pve-01 なら 100 → 105)。チェックが失敗すると加算が取り消され元の優先度に戻るため、正常な他ノードが相対的に高くなり MASTER に昇格する

vrrp_instance ブロック

  • state: インベントリの proxmox_cluster_primary_node と一致するノードが初期 MASTER、それ以外は BACKUP
  • advert_int 1: 1 秒ごとに VRRP 広告を送信。MASTER がダウンすると、BACKUP は約 3 秒(advert_int x 3 + α)で VIP を引き継ぐ
  • authentication: 同一ネットワーク上の意図しない VRRP インスタンスとの干渉を防ぐための認証

優先度の自動計算

priority {{ 100 + (ansible_play_hosts_all.index(inventory_hostname) * -10) }}

ansible_play_hosts_all はプレイブック実行対象のホスト一覧をインベントリ順に返します。.index() でそのホストのインデックスを取得し、-10 を掛けています。

ホスト インデックス 計算結果
pve-01 0 100 + (0 * -10) = 100
pve-02 1 100 + (1 * -10) = 90
pve-03 2 100 + (2 * -10) = 80

この方式により、ノードを追加してもインベントリに記述するだけで優先度が自動決定されます。手動で各ノードの priority を管理する必要がありません。

Ansible タスク

roles/proxmox_keepalived/tasks/main.yml
- name: Install keepalived
  apt:
    name: keepalived
    state: present

- name: Deploy health check script
  template:
    src: pve_check.sh.j2
    dest: /usr/local/bin/pve_check.sh
    mode: '0755'

- name: Deploy keepalived configuration
  template:
    src: keepalived.conf.j2
    dest: /etc/keepalived/keepalived.conf
  notify: restart keepalived

- name: Ensure keepalived is started and enabled
  systemd:
    name: keepalived
    state: started
    enabled: yes

- name: Add VIP domain to /etc/hosts
  lineinfile:
    path: /etc/hosts
    line: "{{ proxmox_vip }} pve-cluster.{{ bootstrap_domain }}"
    state: present

最後のタスクで VIP のドメイン名を /etc/hosts に登録しています。これにより、クラスタ内のノードからも VIP ドメイン名で Proxmox にアクセスできます。

実装の詳細 -- Nginx

リバースプロキシ設定

roles/nginx/templates/pve-proxy.conf.j2
upstream proxmox {
    server 127.0.0.1:8006;
}

server {
    listen 80;
    rewrite ^(.*) https://$host$1 permanent;
}

server {
    listen 443 ssl;
    server_name _;

    ssl_certificate /etc/pve/local/pve-ssl.pem;
    ssl_certificate_key /etc/pve/local/pve-ssl.key;

    proxy_redirect off;
    location / {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass https://proxmox;
        proxy_buffering off;
        client_max_body_size 0;
        proxy_connect_timeout  3600s;
        proxy_read_timeout  3600s;
        proxy_send_timeout  3600s;
        send_timeout  3600s;
    }
}

各ディレクティブの役割を見ていきます。

HTTP → HTTPS リダイレクト

ポート 80 へのアクセスは HTTPS にリダイレクトします。

SSL 証明書

現時点では Proxmox がデフォルトで生成する自己署名証明書(/etc/pve/local/pve-ssl.pem)を使用しています。ブラウザでアクセスすると証明書の警告が表示されますが、動作上の問題はありません。次回の記事で Let's Encrypt 証明書の自動発行を扱い、この制限を解消する予定です。

WebSocket 対応

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

この 3 行が WebSocket のプロキシに必要な設定です。Proxmox の Web UI では VM/LXC のコンソール接続(noVNC、xterm.js)が WebSocket を使用しています。これがないとコンソールが開けません。

タイムアウト設定

proxy_connect_timeout  3600s;
proxy_read_timeout  3600s;
proxy_send_timeout  3600s;
send_timeout  3600s;

コンソール接続やバックアップ・リストアなどの長時間操作に対応するため、タイムアウトを 3600 秒(1 時間)に設定しています。

proxy_pass https://proxmox

pveproxy はポート 8006 で HTTPS を提供しているため、proxy_passhttps:// を指定します。

client_max_body_size 0

ISO イメージや VM テンプレートのアップロードにサイズ制限をかけないよう、0(無制限)にしています。

systemd 依存関係の設定

Nginx が参照する SSL 証明書ファイル(/etc/pve/local/pve-ssl.pem)は、Proxmox の pmxcfs(Proxmox Cluster File System)が提供しています。pmxcfs は pve-cluster.service によって管理されるため、このサービスが起動する前は /etc/pve/ 配下のファイルが存在しません。

Nginx がこのサービスより先に起動すると、証明書ファイルが見つからずエラーになります。これを防ぐために、systemd の依存関係を設定します。

roles/nginx/tasks/main.yml(抜粋)
- name: Create systemd override directory for NGINX
  file:
    path: /etc/systemd/system/nginx.service.d
    state: directory

- name: Deploy systemd override for pve-cluster dependency
  copy:
    content: |
      [Unit]
      After=pve-cluster.service
      Wants=pve-cluster.service
    dest: /etc/systemd/system/nginx.service.d/override.conf
  notify:
    - daemon-reload
    - restart nginx

After=pve-cluster.service で起動順序を制御し、Wants=pve-cluster.service で pve-cluster が起動していなければ起動を試みます。

Ansible タスク(全体)

roles/nginx/tasks/main.yml
- name: Install NGINX
  apt:
    name: nginx
    state: present

- name: Create systemd override directory for NGINX
  file:
    path: /etc/systemd/system/nginx.service.d
    state: directory

- name: Deploy systemd override for pve-cluster dependency
  copy:
    content: |
      [Unit]
      After=pve-cluster.service
      Wants=pve-cluster.service
    dest: /etc/systemd/system/nginx.service.d/override.conf
  notify:
    - daemon-reload
    - restart nginx

- name: Remove default NGINX config
  file:
    path: /etc/nginx/sites-enabled/default
    state: absent
  notify: reload nginx

- name: Deploy Proxmox proxy configuration
  template:
    src: pve-proxy.conf.j2
    dest: /etc/nginx/sites-enabled/pve-proxy.conf
  notify: reload nginx

- name: Flush handlers to apply NGINX config immediately
  meta: flush_handlers

- name: Ensure NGINX is started and enabled
  systemd:
    name: nginx
    state: started
    enabled: yes

meta: flush_handlers を途中で呼んでいるのは、設定ファイルの変更を即座に反映するためです。ハンドラは通常タスクの末尾で実行されますが、flush_handlers で強制的にこの時点で実行し、後続タスクで Nginx が正しい設定で稼働していることを保証します。

フェイルオーバーの動作

通常時

pve-01 (MASTER, priority 100, VIP 所有) ← ユーザーアクセス
pve-02 (BACKUP, priority 90)

pve-01 が MASTER としてVIP を保持し、すべてのリクエストを処理します。

pve-01 障害時

pve-01 (ダウン)
pve-02 (MASTER に昇格, VIP 引き継ぎ) ← ユーザーアクセス(自動切り替え)

pve-01 の Keepalived が VRRP 広告を停止すると、pve-02 が数秒以内に MASTER に昇格し、VIP を引き継ぎます。ユーザーは同じ URL でアクセスを続けられます。

pve-01 復旧時

pve-01 (MASTER に復帰, VIP 奪還)
pve-02 (BACKUP に降格)

Keepalived はデフォルトでプリエンプション(preempt)が有効です。pve-01 が復旧すると優先度が高いため自動的に MASTER を奪還し、VIP を取り戻します。VIP の移動時に一瞬の通信断が発生するため、手動で戻したい場合は nopreempt を設定する選択肢もあります。

動作確認の方法

フェイルオーバーが正しく動作しているかは、以下のコマンドで確認できます。

# VIP がどのノードに割り当てられているか確認
ip addr show | grep 172.16.1.10

# Keepalived のログを確認
journalctl -u keepalived -f

# Nginx 経由で Web UI にアクセスできるか確認
curl -sk https://172.16.1.10

MASTER ノードで Keepalived を停止(systemctl stop keepalived)すると、BACKUP ノードへの VIP 移動をリアルタイムで確認できます。

インベントリ設定

VIP 関連の変数はインベントリのグループ変数で定義します。

inventory/test/hosts.yml
proxmox_nodes:
  hosts:
    pve-01:
      ansible_host: "172.16.1.11"
      proxmox_hostname: "pve01"
      proxmox_ip: "172.16.1.11"
      proxmox_network_interface: "vmbr0"
    pve-02:
      ansible_host: "172.16.1.12"
      proxmox_hostname: "pve02"
      proxmox_ip: "172.16.1.12"
      proxmox_network_interface: "vmbr0"
  vars:
    proxmox_cluster_name: "pve-cluster"
    proxmox_vip: "172.16.1.10"
    proxmox_vip_domain: "pve-cluster.example.com"
    proxmox_vip_router_id: 51
    proxmox_vip_pass: "vIPpASS1"
    proxmox_vip_interface: "vmbr0"
変数 デフォルト値 説明
proxmox_vip 172.16.0.250 VIP のアドレス。同一サブネット内の未使用 IP を指定
proxmox_vip_interface ens18 VIP をバインドするインターフェース
proxmox_vip_router_id 51 VRRP のルーター ID。同一ネットワーク上で一意である必要がある
proxmox_vip_pass pVEvipv1 VRRP 認証パスワード。全ノードで共通

まとめ

Keepalived と Nginx を Ansible ロールとして実装し、Proxmox クラスタの管理アクセスに以下を実現しました。

  • VIP によるフェイルオーバー: ノード障害時に数秒で自動切り替え
  • ポート標準化: https://pve-cluster.example.com でアクセス可能(ポート番号の指定が不要)
  • WebSocket 対応: VM/LXC のコンソール接続がリバースプロキシ経由で動作
  • ノード追加時の手動設定不要: 優先度はインベントリ順で自動計算

現時点では Proxmox デフォルトの自己署名証明書を使用しているため、ブラウザで証明書の警告が表示されます。次回の記事では、ACME(Let's Encrypt)を使った証明書の自動発行を扱い、この課題を解消する予定です。

Discussion