こんにちは!日々のインフラ運用やコードによる構成管理(IaC)、本当にお疲れ様です。
システムが成長するにつれて、Puppetで管理するサーバーやリソースの数はどんどん増えていきますよね。最初は一瞬で終わっていたPuppetの実行が、ある日「あれ? カタログの適用が始まるまでにずいぶん時間がかかるな……」と感じたことはありませんか?
それは、Puppet Serverの内部でマニフェストを解析して「カタログ(構成手順書)」を組み立てる「コンパイル(Compile)」プロセスが重くなっているサインです。
コンパイルの遅延は、開発サイクルを鈍らせ、デプロイのボトルネックになり、何より日々の作業のモチベーションを削いでしまいます。
この記事では、Puppetに初めて触れる方でも迷わずに、「なぜ遅いのか」を科学的に突き止め、劇的に高速化するためのプロファイリング手法を解説します。これをマスターすれば、勘に頼らないスマートなボトルネック解決ができるようになり、毎日の運用が劇的に楽になりますよ。
さあ、一緒にPuppetの深淵を覗いてみましょう!
—
1. Puppetの「コンパイル」って何?
プロファイリングを始める前に、まずはPuppetが裏側で何をしているのか、その全体像を優しく整理しておきましょう。
[ Puppet Agent ] [ Puppet Server ]
| |
| —– 1. Facts (システム情報) を送る —–> |
| | (2. コンパイル開始)
| | – Hieraからデータを引く
| | – マニフェスト(.pp)を評価する
| | – カタログ(設計図)を生成する
| <---- 3. カタログ (Catalog) を返却する ----- |
| |
[ 適用・構成管理 ]
Puppetは「マスター・エージェント型」(またはスタンドアロンの`puppet apply`)で動作します。
エージェントがサーバーのスペックやOS情報(Facts)をPuppet Serverに送ると、Server側はそれらの情報と、私たちが書いたマニフェスト(`.pp`)や設定データ(Hiera)を組み合わせて、そのサーバー専用の「設計図」を作ります。
この設計図を作るプロセスのことを「コンパイル(Compile)」と呼びます。
コンパイルが遅いということは、「設計図を描く段階で迷子になっている」状態です。どこで迷っているかを教えてくれる道具が、今回主役となる「Puppet Profiler(プロファイラー)」です。
—
2. 準備:Puppet Serverプロファイラーを有効化しよう
まずはPuppet Serverのプロファイラーを有効にしてみましょう。設定は非常にシンプルです。
設定ファイルの編集
Puppet Serverの設定ファイル(通常は `/etc/puppetlabs/puppetserver/conf.d/puppetserver.conf`)を開きます。
/etc/puppetlabs/puppetserver/conf.d/puppetserver.conf
profiler {
# ここを true に変更します(デフォルトは false またはコメントアウトされています)
# これにより、コンパイル中の各処理にかかった時間がミリ秒単位でログに出力されます。
enabled: true
}
設定の反映(サービス再起動)
設定を書き換えたら、Puppet Serverを再起動して設定を反映させます。
systemdを使ってPuppet Serverを再起動します
sudo systemctl restart puppetserver
これで準備は完了です! これからはコンパイルが走るたびに、詳細なパフォーマンスデータがログに記録されるようになります。
—
3. 動作確認:初めてのプロファイリング(HelloWorld)
プロファイラーが正しく動いているか、最もシンプルなマニフェストを使って動作確認(HelloWorld)をしてみましょう。
テスト用マニフェストの作成
一時的なテスト環境として、以下のマニフェストを作成します。
/etc/puppetlabs/code/environments/production/manifests/site.pp
node default {
# 動作確認用のシンプルな通知リソース
notify { ‘Hello Puppet Profiler!’:
message => ‘プロファイラーの動作確認です。’,
}
}
コンパイルの実行とログの確認
実際にカタログのコンパイルを発生させてみましょう。ローカル環境で手動でコンパイルをトリガーするには、以下のコマンドを実行します。
自分自身のノードに対してカタログのコンパイルをシミュレーションします
puppet agent –test –noop
コンパイルが実行されたら、Puppet Serverのログファイル(`/var/log/puppetlabs/puppetserver/puppetserver.log`)を確認してみましょう。
プロファイルログを検索します
sudo grep -i “profile” /var/log/puppetlabs/puppetserver/puppetserver.log
ログに以下のような行が出力されていれば、プロファイラーは完璧に動作しています!
2023-10-25T10:00:00.123Z INFO [puppet-server] Puppet Profile: compile (node: target-node) took 45.2 ms
2023-10-25T10:00:00.124Z INFO [puppet-server] Puppet Profile: evaluate_resources (node: target-node) took 12.1 ms
「`took 45.2 ms`(45.2ミリ秒かかった)」のように、どの処理にどれだけの時間がかかったかが一目瞭然ですね。
—
4. ログの「読み解き方」とボトルネックの特定
プロファイラーが有効になると、複雑なマニフェストをコンパイルした際に大量のプロファイル情報がログに流れます。
ここでは、「どこが遅いのか」を特定するための重要ポイントを絞って解説します。
ログの中で特に注目すべきキーワードは以下の3つです。
① `compile`
- 意味: カタログ生成全体の時間。
- 見方: これが数秒(例: `took 5000.0 ms`)を超えている場合、何らかのボトルネックが発生しています。
② `find_facts` / `retrieve_facts`
- 意味: データベース(PuppetDB)や外部システムから、対象サーバーの情報(Facts)を引っ張ってくる時間。
- 対策: ここが極端に遅い場合、ネットワークの遅延やPuppetDBのパフォーマンス低下が疑われます。
③ `evaluate_resource` / `evaluate_classes`
- 意味: 具体的なクラスやマニフェスト、Hieraデータを評価している時間。
- 対策: ここが最も重要です。 特定のクラス名(例: `evaluate_class[profile::database]`)のミリ秒が異常に長い場合、そのコードの中に遅延の原因が潜んでいます。
—
5. 遅延を引き起こす「3大犯人」とリファクタリングによる高速化
プロファイラーによって重いクラスが特定できたら、次はそのコードを綺麗にリファクタリングして高速化しましょう。現場でよく遭遇する「遅延の3大原因」とその対策を紹介します。
原因①:Hieraの「深すぎる・多すぎる」ルックアップ
Hieraは非常に便利ですが、マニフェスト内で何百回も細かく `lookup()` や `hiera()` を呼び出すと、その都度ディスクのYAMLファイルを探索するため、コンパイル時間が乗算的に増加します。
❌ アンチパターン(遅いコード)
クラス内のいたるところで個別にHieraを呼び出している
class profile::web {
$port = lookup(‘profile::web::port’)
$docroot = lookup(‘profile::web::docroot’)
$ssl = lookup(‘profile::web::ssl’)
# … これが数十個続く
}
⭕ 改善パターン(高速なコード)
Puppetの「自動パラメータルックアップ(APL)」を活用し、クラスの引数として定義します。これにより、Puppet Serverは1回のコンパイルプロセスの中で効率的にデータをマッピングします。
クラス引数として定義することで、Puppetが内部的に最適化してHieraから値を展開します
class profile::web (
Integer $port,
String $docroot,
Boolean $ssl,
) {
# マニフェスト内ではローカル変数として高速にアクセス可能
file { “${docroot}/index.html”:
ensure => file,
content => “Welcome!”,
}
}
—
原因②:`each` ループ内での重いリソース生成
配列やハッシュに対して `each` ループを回し、その中でさらに動的な計算や関数の呼び出しを行うと、コンパイル時間が爆発的に増えます。
❌ アンチパターン(遅いコード)
ユーザーの配列に対して、ループ内で毎回重い処理をシリアライズして実行している
$users = [‘alice’, ‘bob’, ‘charlie’, ‘dave’]
$users.each |$user| {
# 毎回外部のテンプレートを読み込んでパースするなどの重い処理
user { $user:
ensure => present,
comment => template(‘profile/user_comment.erb’),
}
}
⭕ 改善パターン(高速なコード)
可能な限り定義型(Defined Types)に処理を委譲するか、Puppet 4以降で導入された高速なイテレーション(反復)構文をシンプルに保ちます。また、テンプレートのパース回数を減らす工夫をします。
コメント用の共通データをあらかじめ定義するか、1回の処理で済むように工夫する
もしくは、ユーザー作成用の定義型(Defined Type)を呼び出す
profile::managed_user { $users: }
—
原因③:大きすぎる「巨大マニフェスト」
1つの `.pp` ファイルに数千行におよぶリソース定義がフラットに書かれていると、パーサーが構文解析木(AST)を作るのに膨大なCPUリソースを消費します。
❌ アンチパターン(遅いコード)
- `site.pp` や単一の `init.pp` に、Webサーバー、DB、ファイアウォール、ユーザー設定、監視設定など、すべてが詰め込まれている状態。
⭕ 改善パターン(高速なコード)
「Roles & Profiles」パターンを採用し、コンポーネントごとにクラスを細かく分割(モジュール化)しましょう。Puppet Serverは必要なクラスだけをロードしてコンパイルするため、メモリ消費量もコンパイル速度も劇的に改善します。
manifests/
├── site.pp
└── site/
├── role/
│ └── webserver.pp # ロール(役割)の定義
└── profile/
├── base.pp # 共通の基本設定
├── web.pp # Webサーバー固有の設定
└── db.pp # データベース固有の設定
—
まとめ:高速なコンパイルがもたらす最高の開発体験
Puppetのコンパイル時間を短縮することは、単に「待ち時間を減らす」だけではありません。
- CI/CDパイプラインの高速化(自動テストが数分から数十秒に!)
- デプロイの迅速化(障害復旧時の設定変更が瞬時に反映!)
- 精神的なゆとり(「実行ボタン」を押してから待つストレスからの解放!)
プロファイラーを使えば、どのマニフェストの、どのクラスがボトルネックになっているかが、誰の目にも明らかな数値として現れます。ぜひ今回紹介した設定を開発環境で有効にして、ログを眺めてみてください。
「あ、ここがボトルネックだったんだ!」という新しい発見がきっとあるはずです。
もしリファクタリングで迷ったことがあれば、いつでもこの記事に戻ってきてくださいね。あなたのインフラコードがより美しく、高速に動作することを応援しています!