【入門編】Puppet Facterのカスタムファクト(Custom Facts)徹底活用!環境固有情報の抽出とマニフェスト分岐の極意 – インフラ構成管理(IaC)活用バイブル

やあ、こんにちは!
インフラストラクチャの自動化と日々格闘している君なら、一度は「このサーバーが今、どのクラウドのどのリージョンにいるのか」「オンプレミスのどのラックの、どの役割を持つマシンなのか」をマニフェスト側で綺麗に判定できたらどんなに楽だろう…と考えたことがあるはずだ。

世の中にはたくさんの構成管理ツールがあるけれど、Puppetの真骨頂は、この「ノードの現状(Fact)を完璧に把握し、あるべき姿(Manifest)へ自動で収束させる」という思想にある。

今回は、デフォルトのFacterでは取得できない環境固有の情報を、PythonやRubyを使って華麗に抽出し、マニフェストの条件分岐を美しくコントロールする「カスタムファクト(Custom Facts)」の極意を伝授しよう。
これをマスターすれば、スパゲッティのように入り組んだ複雑なif文とはお別れだ。毎日のインフラ管理が劇的にスマートになること間違いなしだよ。

—

1. PuppetとFacterの役割:おさらいの基礎知識

まずは、これからPuppetの世界に飛び込む君のために、Facterが果たす役割を整理しておこう。

  • Puppet(構成管理ツール): 「このファイルはこうあるべき」「このパッケージはインストールされているべき」というあるべき姿(Manifest)を定義し、システムをその状態に保つ司令塔。
  • Facter(情報収集エージェント): Puppetが実行される直前に、そのサーバーのOS、IPアドレス、メモリ容量、CPUコア数といった環境変数(Fact)を自動で収集する偵察部隊。

Puppetのマニフェストは、このFacterが集めてきた情報(`$facts[‘os’][‘family’]` など)を元にして、「もしUbuntuならこの設定、CentOSならあの設定」と条件分岐を行う。

デフォルトの限界と、カスタムファクトの出番

標準のFacterは優秀だけど、「自社で付与したAWSの特定タグ」や「社内DBに登録されている独自のサーバーID」「レガシー環境特有のハードウェア識別子」までは知る由もない。
そこで登場するのが、今回解説する「カスタムファクト」だ。君の手で新しい「目」を作り、Facterに持たせるわけさ。

—

2. 開発環境のセットアップと「Hello World」的ファクト作成

百聞は一見にしかず。実際に手を動かして、自分だけのカスタムファクトを作ってみよう。
今回は、もっとも手軽かつ強力なRubyによるカスタムファクトの実装から始めるよ。

ステップ1: ディレクトリ構造の理解

Puppetのモジュール内には、カスタムファクトを配置する黄金のパスが決まっている。

modules/
└── my_custom_fact/
└── lib/
└── facter/
└── environment_role.rb # ここにカスタムファクトを書く

この `lib/facter/` ディレクトリに置かれたスクリプトは、Puppetエージェントが実行される際、自動的にターゲットサーバーに同期され、実行される仕組みになっている。なんてエレガントなんだろう!

ステップ2: 初めてのカスタムファクト(Hello World)

それでは、「このサーバーは開発環境か、それとも本番環境か」を判定するシンプルなカスタムファクトを作ってみよう。
`modules/my_custom_fact/lib/facter/environment_role.rb` を作成し、以下のように記述してほしい。

modules/my_custom_fact/lib/facter/environment_role.rb

Facterのコア機能を呼び出す
require ‘facter’

Facter.addで新しいファクトの名前を定義する
Facter.add(:server_tier) do
# 実行環境のガード条件(例えば、特定のOSでのみ動かしたい場合などに使う)
confine kernel: ‘Linux’

# 実際にデータを収集するブロック
setcode do
# ここでは簡易的に、ファイルが存在するかどうかで環境を判定してみる
if File.exist?(‘/etc/production_marker’)
‘production’
else
‘development’
end
end
end

ステップ3: 動作確認

正しくファクトが認識されているか、コマンドラインから確認してみよう。Puppetが同梱している `facter` コマンドを使えば一発だ。

作成したカスタムファクトをロードして確認する
facter -p server_tier

もしサーバー上に `/etc/production_marker` がなければ、画面には `development` と表示されるはずだ。
どうだい?たったこれだけのコードで、君のサーバーは自分自身が「どこに属しているか」を語り始めるんだ。

—

3. 実践!クラウドタグやレガシー識別子の動的抽出(Python編)

「Rubyもいいけど、ウチの自動化スクリプトはPythonで統一したいんだよね」という賢い君のために、外部スクリプト(Python)をFacterから呼び出す高度なテクニックも紹介しよう。

例えば、AWS上で稼働しているインスタンスのメタデータから「プロジェクト名」のタグを動的に取得し、それをPuppetに渡したいケースを想定してみよう。

Pythonによるデータ収集スクリプト

まずは、AWSのIMDSv2(Instance Metadata Service Version 2)から情報を引くPythonスクリプトを書く。

!/usr/bin/env python3
— coding: utf-8 —

import json
import urllib.request

def get_aws_tag():
try:
# AWS IMDSv2のトークンを取得(セキュリティ考慮)
token_url = “http://169.254.169.254/latest/api/token”
req = urllib.request.Request(token_url, method=”PUT”, headers={“X-aws-ec2-metadata-token-ttl-seconds”: “60”})
with urllib.request.urlopen(req, timeout=1) as response:
token = response.read().decode(‘utf-8’)

# インスタンスIDを取得して識別子とする(簡易的な例)
id_url = “http://169.254.169.254/latest/meta-data/instance-id”
req_id = urllib.request.Request(id_url, headers={“X-aws-ec2-metadata-token”: token})
with urllib.request.urlopen(req_id, timeout=1) as response:
instance_id = response.read().decode(‘utf-8’)

return {“instance_id”: instance_id, “cloud”: “aws”}
except Exception:
# クラウド外(オンプレミスなど)の場合はレガシーな識別子を返す
return {“instance_id”: “legacy-onpremise-01”, “cloud”: “onpremise”}

if __name__ == “__main__”:
# 結果をJSON形式で標準出力に出力する(Facterがこれを読み取る)
print(json.dumps(get_aws_tag()))

このスクリプトを `modules/my_custom_fact/files/get_env_info.py` として配置し、Ruby側からキックする形にするのが、メンテナンス性において非常に美しい設計だ。

—

4. マニフェスト内での美しすぎる条件分岐の極意

さて、集めてきたカスタムファクトを、いよいよPuppetのマニフェスト(例: `manifests/site.pp` やクラスファイル)で料理しよう。

「これをマスターすれば、毎日の作業が劇的に楽になりますよ」——その理由がここにある。きたないゴリ押しのコードではなく、ファクトに基づいた宣言的な記述を見てほしい。

class profile::webserver {
# カスタムファクトの値を変数に受ける($facts[‘ファクト名’] で安全にアクセス)
$tier = $facts[‘server_tier’]
$cloud_type = $facts[‘cloud’] # Pythonから取得したデータ想定

# 1. 環境(Tier)に応じた設定ファイルの切り替え
case $tier {
‘production’: {
$nginx_worker_processes = ‘auto’
$allow_ips = [‘10.0.0.0/8’] # 社内網のみ
notify { ‘Running in PRODUCTION mode. Be careful!’: }
}
‘development’: {
$nginx_worker_processes = 1
$allow_ips = [‘0.0.0.0/0’] # どこからでもアクセス可
notify { ‘Running in DEVELOPMENT mode. Have fun!’: }
}
default: {
fail(“Unknown server tier: ${tier}”)
}
}

# 2. クラウド環境に応じた追加パッケージの導入
if $cloud_type == ‘aws’ {
package { ‘amazon-cloudwatch-agent’:
ensure => installed,
}
}

# 共通のNginx設定
class { ‘nginx’:
worker_processes => $nginx_worker_processes,
}
}

この設計がもたらす圧倒的なメリット

1. マニフェストが環境ごとに分岐しない: 「本番用」「開発用」でコードベースを別々に分ける必要がなくなる。1つのコードが、自律的に自身の環境を認識して最適解を選ぶ。
2. ハードコーディングの排除: 「このIPはこのサーバー専用」といった属人化しがちなハードコードを排除し、Facterが動的に真実(Single Source of Truth)を持ってきてくれる。

—

5. シニアエンジニアからの実践アドバイス(トラブルシューティング)

最後に、現場でカスタムファクトを運用する上で絶対に知っておくべき、知見と注意点を授けよう。

  • 例外処理(エラーハンドリング)を絶対にサボるな

カスタムファクト内で外部APIの叩きすぎやタイムアウトが発生すると、Puppetの実行全体が失敗する。PythonやRubyスクリプトを書くときは、必ずタイムアウトを設定し、失敗した場合はデフォルト値を返す(フォールバック)実装を義務付けよう。

  • ファクトの肥大化に注意

FacterはPuppetが走るたびに実行される。巨大なログファイルや重いDBクエリをカスタムファクト内で実行すると、サーバーのプロビジョニングが致命的に遅くなる。取得するデータは「軽量かつ、変化の少ない識別子」に絞るのがプロの技だ。

—

さあ、準備はいいかい?
デフォルトの枠組みにとらわれず、インフラストラクチャに「文脈」を持たせるカスタムファクト。これを使いこなせば、君の管理するサーバー群は、まるで意思を持っているかのように自ら最適化を始めるはずだ。

明日からのインフラ自動化が、もっとエキサイティングで楽しいものになりますように! Happy Automating!

タイトルとURLをコピーしました