SaltStackによるインフラ自動化:状態管理・分散構成・API連携の実践ガイド

SaltStackの概要と通信アーキテクチャ

SaltStackはPythonベースで開発された構成管理およびリモート実行プラットフォームです。PuppetやAnsibleと同様のユースケースを持ちますが、ZeroMQを活用した非同期メッセージングモデルを採用しており、数千ノード規模でも低遅延なバッチ処理が可能です。実行形態はLocal、Minion/Master(C/S)、SSH経由、Syndic中継など柔軟に選択できます。

内部通信はZeroMQの2つのパターンに依存しています。

  • Pub/Sub (ポート4505): マスター側がタスクをブロードキャストする发布システム。全ミニオンはこのポートに永続接続を維持し、タスクをサブスクライブします。
  • REQ/REP (ポート4506): ミニオンが実行結果やイベントをマスターへ返送する応答システム。

マスタープロセスは ProcessManagerPublisherReqServerMWorker などのサブデーモンで構成され、タスクの分散配分と結果の集約を並行処理します。

環境構築とPKI認証フロー

RHEL/CentOS系環境では、公式リポジトリを登録後 salt-mastersalt-minion をインストールします。初回起動時、両ノードは /etc/salt/pki/ 配下にRSA鍵ペアを生成します。

ミニオン起動後、/etc/salt/minion にマスターのIPアドレスとノード固有の id を定義します。マスター側では salt-key コマンドで未承認の公開鍵を確認し、salt-key -a <node_id> で承認すると、公開鍵は minions/ ディレクトリへ移動し、以降の通信は暗号化されます。

リモート実行と状態定義(SLS)

コマンド体系は salt [ターゲット] [モジュール].[関数] [引数] で統一されています。

# 全ノードの死活確認
salt '*' test.ping

# 特定ノードへのコマンド送信
salt 'db-primary.local' cmd.run 'systemctl status postgresql'

構成管理はYAML形式のSLSファイルで宣言的に記述します。インデントはスペース2個が必須であり、タブは使用できません。キーバリューはコロン、リストはハイフンで区切ります。

データベース基盤のデプロイ例:

# db_setup.sls
install_db_packages:
  pkg.installed:
    - names:
      - postgresql-server
      - postgresql-contrib

initialize_db_cluster:
  cmd.run:
    - name: postgresql-setup --initdb
    - unless: systemctl is-active postgresql

configure_db_service:
  service.running:
    - name: postgresql
    - enable: True
    - reload: True
    - watch:
      - file: /var/lib/pgsql/data/postgresql.conf

/var/lib/pgsql/data/postgresql.conf:
  file.managed:
    - source: salt://db/postgresql.conf.j2
    - template: jinja
    - context:
        shared_buffers: 2GB
        max_connections: 200

実行フローでは、マスターがSLSをミニオンへ配信し、ミニオンは /var/cache/salt/minion/ に一時展開した後、宣言された状態へ収束させる処理を順次実行します。

データストア: GrainsとPillar

SaltStackは2つのデータストアを提供します。

  • Grains: ミニオン起動時に収集される静的メタデータ(OSディストリビューション、カーネルバージョン、メモリ容量、ディスク情報など)。salt '*' grains.items で参照可能。カスタムGrainsは /etc/salt/grains_grains/ 配下のPythonスクリプトで拡張でき、saltutil.sync_grains で即時反映されます。
  • Pillar: ターゲットノードごとに限定配信される動的データ。マスター側で定義され、対象ミニオン以外からは参照できないため、認証情報や環境固有のパラメータを安全に管理できます。

Pillarの定義例:

# /srv/pillar/base/env.sls
{% if grains['os_family'] == 'RedHat' %}
db_port: 5432
log_level: info
{% elif grains['os_family'] == 'Debian' %}
db_port: 5433
log_level: debug
{% endif %}

GrainsとPillarの違いは、前者が「ノード自体の属性」、後者が「マスターから注入される設定値」である点です。ターゲット選択では -G (Grains) や -I (Pillar) フラグが利用できます。

高度な制御と状態関係

SLSファイル間の依存関係は以下のキーワードで宣言します。

  • require: 指定状態が正常に適用された後に実行。
  • watch: 監視対象の状態が変更された場合に、サービスリロード/再起動をトリガー。
  • include: 外部SLSファイルを取り込み、モジュール化を促進。
  • extend: 既存IDの状態宣言を継承・上書き。

ターゲット指定はノードIDだけでなく、IPサブネット(-S)、ノードグループ(-N)、複合論理(-C)、バッチ処理(-b)に対応します。本番環境では top.sls で環境ごとの適用マッピングを一元管理するのが標準的です。

分散アーキテクチャと実行モード

Masterlessモード

file_client: local を設定し、salt-call --local state.highstate を実行します。マスター不要でSLSファイルをローカルで評価するため、単一ノードの検証やネットワーク断絶環境に利用されます。

Multi-Master構成

ミニオン設定にマスターIPのリストを配列形式で記載します。キー共有にはGitリポジトリまたはNFS共有が推奨されます。これによりマスター単一障害点を排除できます。

Syndic中継

大規模階層環境で利用されるミドルウェアです。下位マスター上に salt-syndic を配置し、syndic_master: <上位マスターIP> を設定。上位マスター側では order_masters: True を有効化します。Syndicは上位からの指示を配下ミニオンへ中継し、結果を上位へ集約します。

Salt SSH

salt-ssh コマンドはRosterファイル(/etc/salt/roster)に基づきSSH経由で制御します。ミニオンデーモンが不要で、初回実行時に公開鍵を自動配置します。レガシー環境や一時的なアクセス制御に適しています。

ジョブ管理と非同期実行

同期実行ではタイムアウトにより結果が欠落するリスクがあります。非同期実行は --async フラグまたは local_async クライアントで即座にJob ID(JID)を返却し、後続処理で状態を追跡できます。

# 非同期タスク発行
salt --async '*' cmd.run 'yum update -y'

# JIDによる結果照会
salt-run jobs.lookup_jid 20231025120000123456

ジョブキャッシュは keep_jobs 設定で管理され、MySQLなどのReturnerで永続化可能です。Runner(salt-run)はマスター側で動作する集約モジュール群であり、manage.statusjobs.list_jobs などが代表例です。

REST APIの構築と活用

salt-api パッケージと rest_cherrypy インターフェースを使用すると、HTTPS経由でインフラ操作を自動化できます。SSL証明書の配置、PAM認証によるトークン発行、X-Auth-Token ヘッダーによるセッション維持が必要です。

import requests
import json

class SaltAPI:
    def __init__(self, base_url, user, password):
        self.url = base_url
        self.token = self._authenticate(user, password)

    def _authenticate(self, user, pwd):
        payload = {'username': user, 'password': pwd, 'eauth': 'pam'}
        resp = requests.post(f'{self.url}/login', json=payload, verify=False)
        return resp.json()['return'][0]['token']

    def run_async(self, target, function):
        headers = {'X-Auth-Token': self.token}
        payload = {'client': 'local_async', 'tgt': target, 'fun': function}
        resp = requests.post(self.url, json=payload, headers=headers, verify=False)
        return resp.json()['return'][0]['jid']

    def fetch_job(self, jid):
        headers = {'X-Auth-Token': self.token}
        resp = requests.get(f'{self.url}/jobs/{jid}', headers=headers, verify=False)
        return resp.json()

Windows環境におけるGUI制御の留意点

Windowsインストーラーでデプロイされたミニオンは、デフォルトでシステムサービスとして実行されます。WindowsのSession 0隔離仕様により、サービスコンテキストではデスクトップUI操作や対話型プロセスの制御が制限されます。GUI操作をSalt経由で実行する必要がある場合は、services.mscsalt-minion サービスを無効化し、C:\salt\salt-minion.exe をユーザーセッション上で直接実行プロセスとして起動する構成へ変更する必要があります。

8月10日 03:17 投稿