DockerでSemaphore UIをデプロイ:自宅ラボ(HomeLab)向けWebベースAnsible管理

HomeLab tutorial - IT technology blog
HomeLab tutorial - IT technology blog

アプローチの比較:自宅環境でのAnsible管理

複数のベアメタルマシン、仮想マシン、Proxmoxノードにまたがる構成管理は、通常メインのワークステーションから直接 ansible-playbook -i inventory.ini site.yml を実行することから始まります。最初のうちは生のCLIコマンドを実行するだけでも十分に機能します。

しかし、すぐに問題が山積みになってきます。Playbookのログはターミナルのスクロールバックの中に消え去り、cronによる定期実行ジョブには一元的な可視性がなく、秘密鍵は保護されないままローカルのシェル設定内に放置されます。スマートフォンやサブのノートPCから実行をトリガーすることなど、ほぼ不可能です。

一元的なWebオーケストレーターを探す際、自宅ラボ(HomeLab)エンジニアは通常次の3つの選択肢を検討します:

  • 生のCLI & Crontab: オーバーヘッドとセットアップの手間は完全にゼロです。しかし、実行履歴、マルチユーザーのロール分離、シークレットの一元管理、Webhook連携などは犠牲になります。
  • Red Hat AWX(オープンソース版Ansible Tower): エンタープライズ向けのベンチマークです。AWXはフル機能の自動化コントローラー機能を提供しますが、Kubernetesクラスタ(またはK3s)が必須であり、アイドル状態のままでも4GB〜8GBのRAMを消費します。合計メモリが16GB程度のIntel NUCやミニPCでは、システムリソースを過剰に占有してしまいます。
  • Semaphore UI: GoとVue.jsで構築された、レスポンスが良く高速なWebダッシュボードです。Semaphoreは標準的なDockerコンテナ内で軽快に動作し、RAM消費量はわずか約80MB〜120MB程度です。PostgreSQLバックエンドをサポートし、インベントリやVaultシークレットをネイティブに処理できるほか、組み込みのcronスケジューリングやWebhookにも対応しています。

比較表

評価項目 CLI + Crontab Red Hat AWX Semaphore UI
メモリフットプリント 最小限(実行時 <50MB) 大(アイドル時 4GB〜8GB RAM) 軽量(約100MB RAM)
デプロイ対象 ベアメタル / ワークステーション Kubernetes / K3s / Minikube Docker / Docker Compose
Webダッシュボード & ログ なし(ターミナル出力のみ) 充実(エンタープライズ重視) 洗練され高速、リアルタイムログストリーミング
RBAC & 認証情報 手動ファイルパーミッション きめ細かなオブジェクトレベルRBAC チーム、プロジェクト & キーストア
自宅ラボ適性 素早いテストに最適 リソース的に厳しいことが多い 機能とフットプリントの最適なバランス

Semaphore UIのメリットとデメリット

コンテナを立ち上げる前に、Semaphoreの強みと限界について確認しておきましょう。

メリット

  • 抜群のリソース効率: 2GBのRaspberry Pi 4や非特権LXCコンテナ上でも、メモリ圧迫を意識することなくSemaphoreを実行できます。
  • ネイティブなAnsible互換性: 内部で標準のAnsible CLIバイナリを直接呼び出すため、既存のPlaybook、ロール、コレクションを構文変更なしでそのまま実行できます。
  • 一元化された認証情報ストア: SSH鍵、sudoパスワード、Ansible VaultのパスフレーズはPostgreSQL内で暗号化されて安全に保存され、ジョブ実行時にのみメモリへ注入されます。
  • 洗練されたAPI & Webhook: Giteaのプッシュフックや、Prometheus / Alertmanagerからのアラート経由で実行を自動的にトリガーできます。
  • リアルタイムログストリーミング: 色分けされた永続ログと実行時間の追跡機能により、ブラウザ上のターミナルからタスクの実行をリアルタイムで確認できます。

デメリット

  • 動的インベントリのビジュアライザ非搭載: Gitリポジトリから取得した動的インベントリスクリプトを実行することはできますが、UI内でトポロジーマップなどを視覚的に描画・確認することはできません。
  • シンプルな権限マトリクス: プロジェクト単位でユーザーを分離できますが、AWXのようにインベントリごとや認証情報ごとのきめ細かな権限ポリシー設定には対応していません。
  • コレクションの依存関係管理: Playbookが community.docker や ansible.posix などのコレクションに依存している場合、リポジトリの requirements.yml に含めるか、カスタムコンテナイメージにあらかじめ組み込んでおく必要があります。

推奨セットアップアーキテクチャ

Docker Composeを使用してSemaphoreとPostgreSQLを連携稼働させることで、各サービスがきれいに分離され、標準の pg_dump ユーティリティを使った日次バックアップも容易に行えます。

今回のデプロイ構成には以下の要素が含まれます:

  1. PostgreSQL 16 エンジン: SemaphoreはMySQLやSQLiteもサポートしていますが、PostgreSQLはマイグレーションを安定して処理し、複数のPlaybookを同時ディスパッチする際にも信頼性の高い並行処理を提供します。
  2. 公式Semaphoreコンテナ: Ansible Core、Git、OpenSSHクライアントツールが標準で同梱されたAlpineベースのイメージです。
  3. 隔離されたDockerブリッジネットワーク: データベースポートの公開をSemaphoreのみに厳密に制限し、Webトラフィックはポート3000経由でTLS対応のリバースプロキシ(Traefik、Nginx Proxy Manager、Caddyなど)にルーティングします。
  4. 名前付き永続ボリューム: コンテナをアップグレードしても、データベースの状態、暗号化シークレット、クローンされたPlaybookのキャッシュを確実に保持します。

実装ガイド:DockerへのSemaphoreのデプロイ

ステップ1:ディレクトリ構成の準備と環境変数のシークレット設定

Dockerホストにログインし、スタック用の専用ディレクトリ構成を作成します:

mkdir -p ~/semaphore/{config,data/postgres}
cd ~/semaphore

Cookieの署名、データベース暗号化、PostgreSQL認証用の暗号鍵を生成します:

COOKIE_HASH=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -base64 32)
DB_PASS=$(openssl rand -hex 16)

cat <<EOF > .env
SEMAPHORE_COOKIE_HASH=${COOKIE_HASH}
SEMAPHORE_ACCESS_KEY_ENCRYPTION=${ENCRYPTION_KEY}
POSTGRES_PASSWORD=${DB_PASS}
EOF

chmod 600 .env
echo "ランダムなシークレットを含む.envファイルを生成しました。"

ステップ2:Docker Composeファイルの作成

プロジェクトフォルダ内に docker-compose.yml ファイルを作成します:

nano docker-compose.yml

以下のサービス設定を追加します。認証情報は .env ファイルから直接読み込まれます:

networks:
  semaphore-net:
    driver: bridge

volumes:
  semaphore-postgres:
  semaphore-data:
  semaphore-config:

services:
  postgres:
    image: postgres:16-alpine
    container_name: semaphore-postgres
    restart: unless-stopped
    networks:
      - semaphore-net
    volumes:
      - semaphore-postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: semaphore
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: semaphore
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U semaphore -d semaphore"]
      interval: 5s
      timeout: 5s
      retries: 5

  semaphore:
    image: semaphoreui/semaphore:v2.10.35
    container_name: semaphore-ui
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - semaphore-net
    ports:
      - "3000:3000"
    environment:
      SEMAPHORE_DB_USER: semaphore
      SEMAPHORE_DB_PASS: ${POSTGRES_PASSWORD}
      SEMAPHORE_DB_HOST: postgres
      SEMAPHORE_DB_PORT: "5432"
      SEMAPHORE_DB_DIALECT: postgres
      SEMAPHORE_DB: semaphore
      SEMAPHORE_PLAYBOOK_PATH: /tmp/semaphore
      SEMAPHORE_ADMIN_PASSWORD: InitialAdminPassword123!
      SEMAPHORE_ADMIN_NAME: ラボ管理者
      SEMAPHORE_ADMIN_EMAIL: [email protected]
      SEMAPHORE_ADMIN: admin
      SEMAPHORE_ACCESS_KEY_ENCRYPTION: ${SEMAPHORE_ACCESS_KEY_ENCRYPTION}
      SEMAPHORE_COOKIE_HASH: ${SEMAPHORE_COOKIE_HASH}
    volumes:
      - semaphore-config:/etc/semaphore
      - semaphore-data:/tmp/semaphore

ステップ3:サービスの起動とログの確認

デタッチモードでスタックを起動します:

docker compose up -d

コンテナの初期化ログを追跡し、データベースマイグレーションが正常に完了したか確認します:

docker compose logs -f semaphore

10〜15秒ほどで、起動完了を示す次のような出力が表示されます:

DBマイグレーションを実行中...
マイグレーションが完了しました
管理者アカウントが作成されました
サーバーがポート :3000 で稼働中

ステップ4:初期ログインとプロジェクトのセットアップ

Webブラウザで http://<your-server-ip>:3000 にアクセスします。初期認証情報であるユーザー名 admin、パスワード InitialAdminPassword123! を使ってログインしてください。ログイン後、すぐに右上隅の User Profile(ユーザープロファイル)に移動し、管理者パスワードを強固なフレーズに変更してください。

次に、New Project をクリックし、名前を HomeLab Core と入力します。プロジェクトは、認証情報、リポジトリ、Playbookがスコープ分けされる独立したワークスペースとして機能します。

ステップ5:キーストア、リポジトリ、インベントリの設定

Semaphoreでは、自動化タスクを主に5つの構成要素(ビルディングブロック)で管理します:

1. Key Store(認証情報)

Key Store > New Key の順に移動します。ここではAnsibleが対象ホストにアクセスするために使用する秘密鍵を登録します:

  • Name: homelab-ssh-key
  • Type: SSH Key
  • Username: ansible(対象マシン上に設定されているデプロイ用ユーザー)
  • Private Key: 秘密鍵のテキスト(例:~/.ssh/id_ed25519 ファイルの内容)を貼り付けます。

2. Repositories(リポジトリ)

Repositories > New Repository の順に移動します。Semaphoreは実行前にPlaybookリポジトリをクローンします:

  • Name: homelab-playbooks
  • URL: https://github.com/your-username/homelab-infra.git(または内部Giteaのアドレス)
  • Branch: main
  • Access Key: パブリックリポジトリの場合は None を選択し、プライベートリポジトリの場合はKey StoreからSSHデプロイキーを選択します。

3. Inventory(インベントリ)

Inventory > New Inventory の順に移動します:

  • Name: lan-servers
  • Type: Static
  • SSH Key: homelab-ssh-key を選択
  • Inventory Contents: 標準的なINIまたはYAML構文を使用してホスト定義を入力します:
[proxmox]
192.168.1.10 hostname=pve-01.lan
192.168.1.11 hostname=pve-02.lan

[docker_hosts]
192.168.1.20 hostname=docker-apps.lan
192.168.1.21 hostname=docker-media.lan

[all:vars]
ansible_python_interpreter=/usr/bin/python3

4. Environment(環境変数)

Environment > New Environment の順に移動します。Ansibleの --extra-vars 引数と同様に、追加の実行時変数をJSON形式で指定します:

{
  "auto_upgrade_packages": true,
  "dns_servers": [
    "192.168.1.1",
    "1.1.1.1"
  ]
}

5. Task Templates(タスクテンプレート)

Task Templates > New Template の順に移動します:

  • Name: Nightly Package Updates
  • Playbook Filename: playbooks/patch-all.yml(Gitリポジトリのルートからの相対パス)
  • Inventory: lan-servers
  • Repository: homelab-playbooks
  • Environment: 上記で定義した環境ブロックを選択します。

ステップ6:Playbookの実行とスケジュール設定

新規作成したタスクテンプレートで Run をクリックします。ブラウザ上のターミナルに実行ログが1行ずつストリーミングされ、ok、changed、failed の各タスクが色付きでハイライト表示されます。

メンテナンスを完全自動化したい場合は、タスクテンプレートの設定を開き、0 3 * * 0(毎週日曜日の03:00 UTC)のような標準的なcronパターンを入力して Save をクリックします。Semaphoreがスケジュールに従ってジョブを自動実行し、後から見返せるよう完全なログを保存してくれます。

Share: