SIer向け　Puppetベタープラクティス（書き方編）

# どんな内容？
ややたたき台的な内容。
SI案件でPuppetを使う場合、どうゆうマニフェストの書き方がベターかなという内容です。特に変更が頻繁にある多機能（１台にミドルウェアたくさん）なサーバに対して、Puppetを使うような場合です。（...という目的で書き始めましたが、だんだん書き進めていくうちに、こんな編集ツールがあれば、こんなマニフェストの書き方もあるよみたいな提案的な内容になってます。）
かなり詰め込んでいるので、**Puppetの基本的な内容には詳しく触れていません**。

# ベタープラクティスの概要
結構長文なので、先に概要を。
ペタープラクティスは、**Puppetマニフェスト自体をできるだけ簡単に書いて、パラメータなどをたくさん変更するときはツールを使って編集する**です。

- ①manifests,modulesに、ifなどの条件分岐を使わない
- ②modulesのclassは、共通部品として作る
- ③配布するテンプレートファイルの穴埋めは多用しない
- ④Hieraで、 各サーバに適用するmodulesの組み合わせとパラメータを書く（hiera_include）
- ⑤Hieraの階層は、共通設定用、各サーバ用の２階層ぐらいにする
- ⑥パラメータのデフォルト値は、⑤の共通用設定ファイルにまとめる
- ⑦各サーバの定義が増えすぎて編集が大変な場合、簡易ツールを作って編集する

①〜⑤の内容は他の方の投稿とかなりかぶってる気がしますが、⑦をするためには①〜⑥をしている方がやりやすいため話の流れ上①〜⑤にも触れています。
ベタープラクティスに入る前に、Puppetを使うメリットと、使った場合の課題についてふれます。

# Puppetを使うメリット
本題の前に、Puppetを使ってどんなメリットがあるかです。

Puppetは、本来であればシンプル設計のサーバを量産するのに向いている気がします。ただ、SI案件だと１台に多くの機能を詰め込んだようなサーバを相手にすることがあります。正直なところ、そんな一品モノなサーバに対してPuppetは向かない気がしますが、それでもあえてPuppetを使うメリットをあげるとすれば次の点です。

- ①サーバの設定をテキスト化できるため、サーバ間の差分や変更内容を把握しやすい
- ②実際にサーバに反映する前に、変わる設定が確認できる（noop,ドライラン）
- ③設定変更が間違っていた場合用のリストア機能がある（filebucket）
- ④納品後のトラブル解析のために、別環境にサーバを再現できる（※Puppetでどこまで設定をカバーしているかによります）

あと、シンプル設計のサーバの場合、

- ⑤同じ設定のサーバを量産できるため、スケールアウト時の構築コストが削減できる

があります。
　
# SI案件でPuppetを使う場合の課題
なぜ、ベタープラクティスをわざわざ考えないといけないかということで、SI案件でPuppetを使う場合に何が問題になるかです。

- たび重なる仕様変更で各サーバの設定が複雑になって、Puppetのマニフェストも複雑なんだけど
- あるサーバだけ設定する/しないが積み重なりすぎて、マニフェスト書き分け面倒なんだけど
- Puppetのマニフェストが複雑だと、読みとかないといけない分変更に時間かかるんだけど
- 案件規模が小さくなった（シュリンクした）ときに、Puppetを使える人がいないんだけど
- かといって、簡単なルールのみでマニフェスト書くと、編集対象多くなりすぎなんだけど

ようはSI案件でよくある、だれでもできるように簡単にしておけの理由と変わらないですが、それだと単純変更ですむ分、編集箇所増えすぎて大変すぎってことが課題です。

# ベタープラクティス
ようやく本題。
一言でベタープラクティスを表すと、**Puppetマニフェスト自体はできるだけ簡単に書いて、パラメータなどをたくさん変更するときはツールを使って編集する**です。
　
イメージしやすいように、サンプルマニフェストを元に紹介します。
例で定義するのはwebサーバを３台です。

| 共通設定 |guestユーザがある|
|:-------|:--------------|
| web01  |Apache (ServerLimit,MaxClients:128) + vsftpd |
| web02  |Apache (ServerLimit,MaxClients:128) + Git |
| web03  |Apache (ServerLimit,MaxClients:512) + Git |


イメージ的には、３台webサーバがあって、コンテンツ更新用にftpを使っているものと、Gitクライアントを使っているものが混在する。さらにパラメータチューニングとして試験的に一部サーバでMaxClientsを変えているです。（あくまでマニフェスト例なので、実用的な設定まではやっていないです。インストールだけとか。）
　
全体のファイル構成は次の通りです。


```
/etc/puppet/
|-- hiera.yaml
|-- hieradata
|   |-- default.yaml
|   |-- web01.yaml
|   |-- web02.yaml
|   `-- web03.yaml
|-- hieratool
|   |-- hieratool.rb
|   `-- patch.yaml
|-- manifests
|   `-- site.pp
`-- modules
    |-- apache
    |   |-- manifests
    |   |   |-- config.pp
    |   |   |-- install.pp
    |   |   `-- service.pp
    |   `-- templates
    |       `-- default
    |           |-- httpd.conf.erb
    |           `-- prefork.conf.erb
    |-- base
    |   `-- manifests
    |       `-- account.pp
    |-- git
    |   `-- manifests
    |       `-- install.pp
    `-- vsftpd
        `-- manifests
            |-- install.pp
            `-- service.pp
```

まずは、manifests、modulesに関わる部分について。
## ①manifests,modulesに、ifなどの条件分岐を使わない
条件分岐を使わない理由は、既存のマニフェストを別サーバに利用することになった場合に、そのまま利用できるかを読み解かないといけないケースをなくすためです（特にロジック的な部分）。例えば、あるサーバだけ特定のファイルを配布するとかをmodulesで定義してししまうと、別サーバに利用するときにその条件分岐の妥当性をいちいちみるのは設定変更のスピードが落ちるため微妙です。

## ②modulesのclassは、共通部品として作る
①とかぶりますが、同じclassを使っているのに利用するサーバで挙動が違うとメンテナンスしにくいため、基本同じclassを使ったら同じ挙動をするようにします。ただ、利用するテンプレートをパラメータで渡すとか、設定ファイル内の値をパラメータを渡すとかは、便利なため使います。
classは、

- install.pp：パッケージのインストール定義
- config.pp：設定ファイルの定義
- service.pp：サービスの状態定義

の３タイプくらいがシンプルでよいです。
よくある、init.ppやparams.ppは使いません。
init.ppは、classが増えたときにinit.ppも変更するのが面倒なため個人的に不要です。
params.ppは、Hieraを使うのであれば役割がかぶるため個人的に不要です。
なお、あるサーバだけあるファイルを別途配布したいとかは、config_piyo.ppみたいに定義してそのサーバだけconfig.ppとconfig_piyo.ppを使うようにします。

## ③配布するテンプレートファイルの穴埋めは多用しない
ここでいう穴埋めは、ERB形式のテンプレートファイルにパラメータを埋め込めるようにして、サーバごとに設定を変えられる部分のことです（たぶん公式用語ではないです）。穴埋めをうまく使えば、管理するテンプレートが減って便利な部分があるんですが、次のようなケースで作り直しが面倒なため多用はお勧めしないです。

- ミドルウェア設定を大幅に変える必要性がでたとき
- ミドルウェアのバージョンが変わって、デフォルトの設定ファイルが変わったとき

とりあえず、数カ所だけ穴埋めして管理ファイルが劇的に少なくなる場合は使うです。

ここまでの内容に関わる部分のサンプルマニフェストです。
ちょっとくどいかもしれませんが、一部の設定ファイルを除き全部載せています。関わる部分だけ載せた方が見やすい反面、マニフェスト全体はどうなっているのかイメージしにくいので。

## サンプルマニフェスト（manifests,modules）
manifestsのsite.pp、ここはHieraを使ってclassの組み合わせを読み込む定義だけです。
条件分岐なしです。だた、Hieraの階層構造を増やさないといけない場合は、ここで階層振り分け用のパラメータをノードごとに設定するとかはでてくるかもしれません。あと、filebucketはサンプルにはないですが、設定が必要な場合追加が必要です。


```/etc/puppet/manifests/site.pp
node default {
  hiera_include("classes")
}
```

Apacheのmodulesのmanifestsです。ここでも条件分岐なしです。
設定ファイルを指定するconfig.ppにはテンプレートのファイル名をパラメータとして渡すことによって、ミドルウェア設定の柔軟性をもたせています。
ちなみに、クラスへの引数としてデフォルトのパラメータを指定することもできますが、以降のHieraの役割とかぶるため思い切って使っていません。

```/etc/puppet/modules/apache/manifests/install.pp
class apache::install (
  $httpd_ver,
) {
  package { "httpd":
    ensure => $httpd_ver,
  }
}
```

```/etc/puppet/modules/apache/manifests/config.pp
class apache::config (
  $httpd_conf_tmpl,
  $prefork_conf_tmpl,
  $server_limit,
  $max_limits,
) {
  file { "/etc/httpd/conf/httpd.conf":
    ensure => file,
    mode => "0644",
    owner => "root",
    group => "root",
    content => template($httpd_conf_tmpl),
    notify => Service["httpd"],
    require => Package["httpd"],
  }

  file { "/etc/httpd/conf.d/prefork.conf":
    ensure => file,
    mode => "0644",
    owner => "root",
    group => "root",
    content => template($prefork_conf_tmpl),
    notify => Service["httpd"],
    require => Package["httpd"],
  }
}
```

```/etc/puppet/modules/apache/manifests/service.pp
class apache::service (
) {
  service { "httpd":
    ensure => running,
    require => Package["httpd"],
  }
}
```

Apacheのmodulesのtemplatesです。
穴埋めを多用しないといいつつ、以降の紹介の都合上穴埋めを使っていますが、基本穴埋めは少なめにします。設定ファイルの違いは上のconfig.ppへのテンプレートファイルのパス指定で対応します。テンプレートファイルのディレクトリ構成は、どういう構成がよいのか難しいのですが、ここでは、defaultフォルダの下に共通テンプレートを置いています。
もし別途、全然違うテンプレートを使う場合、
/etc/puppet/modules/apache/templates/web03/httpd.conf.erb
みたいにフォルダで分ける想定です。

httpd.confは変えている部分の差分だけです。

```/etc/puppet/modules/apache/templates/default/httpd.conf.erb
@@ -92,7 +92,7 @@
 #
 # If your host doesn't have a registered DNS name, enter its IP address here.
 #
-#ServerName www.example.com:80
+ServerName <%= hostname %>:80
 
 #
 # Deny access to the entirety of your server's filesystem. You must
```

```/etc/puppet/modules/apache/templates/default/prefork.conf.erb
<IfModule mpm_prefork_module>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
ServerLimit <%= server_limit %>
MaxClients <%= max_limits %>
MaxRequestsPerChild 10000
</IfModule>
```

共通設定のbaseとその他ミドルウェアのvsftpd、Gitです。
Apacheだけだと、以降の Hieraの例が微妙になってしまうので、とりあえず用意した程度です。

```/etc/puppet/modules/base/manifests/account.pp
class base::account (
) {
  group { "guest":
    gid => 1000,
    ensure => present,
  }

  user { "guest":
    ensure => present,
    home => "/home/guest",
    managehome => true,
    uid => 1000,
    gid => 1000,
    shell => "/bin/bash",
  }
}
```

```/etc/puppet/modules/git/manifests/install.pp
class git::install (
  $git_ver,
) {
  package { "git":
    ensure => $git_ver,
  }
}
```

```/etc/puppet/modules/vsftpd/manifests/install.pp
class vsftpd::install (
  $vsftpd_ver,
) {
  package { "vsftpd":
    ensure => $vsftpd_ver,
  }
}
```

```/etc/puppet/modules/vsftpd/manifests/service.pp
class vsftpd::service (
) {
  service { "vsftpd":
    ensure => running,
    require => Package["vsftpd"],
  }
}
```

次に、Hieraについてです。

## ④Hieraで、 各サーバに適用するmodulesの組み合わせとパラメータを書く（hiera_include）
Hieraは、manifestsにパラメータを渡す機能です。Hieraを使うと、パラメータ部分を別ファイルで管理することができ、また階層的な定義も出来るため、うまく使いこなせば、マニフェストの見通しが良くなります。
実際にPuppet反映時には、まずは個別的な定義ファイルを参照してそこに値がなければ、より共通的な定義ファイルを参照するみたいな動作になります。逆な言い方をすると、共通的な定義ファイルの値を、個別的な定義ファイルの値でオーバーライドできます。
なので、より共通的な値をまとめることができ、かつ、細かい設定が必要な場合は個別に定義することができます。
通常はパラメータ値だけですが、hiera_includeを使えば、classの組み合わせも扱うことができます。

## ⑤Hieraの階層は、共通設定用、各サーバ用の２階層ぐらいにする
Hieraは使いこなせば便利ですが、階層構造を多くすると学習コストが高くなるため、まずは２階層ぐらいがよいです。階層構造を複雑にしすぎると、結局どの値を使っているの？ってなるためです。
共通設定用と各サーバ用の定義の２階層ぐらいであれば、人にも説明しやすいうえに、共通設定をまとめることもできるためまずはそのくらいから始めます。メンバー内にHieraが浸透すれば、より多層にするのもよいかもしれません。

## ⑥パラメータのデフォルト値は、⑤の共通用設定ファイルにまとめる
普通のHieraの使い方であれば、共通設定用のファイルには、共通的に使うパラメータが書いてあります。
そういう使い方以外にも、実はパラメータのデフォルト値を共通設定用ファイルに書いておいて、そのパラメータを実際に使うかは個別設定ファイル側でclassを使う定義をすることによって決めることがきます。具体的に言うと、全サーバではApache関連のパラメータは使わないけど、共通設定ファイルにApache関連のデフォルトパラメータを書いても害はないということです。そのパラメータが実際に使われるかは、webサーバの定義設定でApache関連のclassを使う定義を書いた場合です。

ただし、ちょっと邪道な？使い方な気がするため、今のHieraの仕様ではこんな使い方もできるというだけで、今後Hieraの仕様が変わるとうまく機能しない可能性もわずかながらあります。

## サンプルマニフェスト(Hiera部分)
ここまでのHiera分のサンプルマニフェストです。
　
hiera.yamlは、Hieraの設定です。
backendsにYAML形式、
datadirはYAMLファイルの置き場所、
hierarchyはHiera参照の階層定義で、まずは各サーバ個別設定用のホスト名、
その下が共通設定用のdefaultとして定義しています。

```/etc/puppet/hiera.yaml
---
:backends:
  - yaml
:yaml:
  :datadir: /etc/puppet/hieradata
:hierarchy:
  - "%{::hostname}"
  - default
```

default.yamlは共通設定用の定義として用意しています。
classはguestユーザ用のbase::acountのみです。それ以外は、全サーバに使われないですが、使われた時のデフォルト値を定義しています。またrequireなどの順序関係も指定できます。

```/etc/puppet/hieradata/default.yaml
---
classes:
- base::account
apache::install::httpd_ver: installed
apache::config::httpd_conf_tmpl: apache/default/httpd.conf.erb
apache::config::prefork_conf_tmpl: apache/default/prefork.conf.erb
apache::config::server_limit: '256'
apache::config::max_limits: '256'
apache::config::require: Class[apache::install]
apache::sevice::require: Class[apache::config]
vsftpd::install::vsftpd_ver: installed
vsftpd::servcie::require: Class[vsftpd::install]
git::install::git_ver: installed
```

web01用の定義ファイル、web01.yamlです。
vsftpdのインストール、ServerLimit,MaxClientsは128で設定しています。

```/etc/puppet/hieradata/web01.yaml
---
classes:
- apache::install
- apache::config
- apache::service
- vsftpd::install
- vsftpd::service
apache::config::server_limit: '128'
apache::config::max_limits: '128'
```

web02用の定義ファイル、web02.yamlです。
Gitのインストール、ServerLimit,MaxClientsは128で設定しています。

```/etc/puppet/hieradata/web02.yaml
---
classes:
- apache::install
- apache::config
- apache::service
- git::install
apache::config::server_limit: '128'
apache::config::max_limits: '128'
```

web03用の定義ファイル、web03.yamlです。
Gitのインストール、ServerLimit,MaxClientsは512で設定しています。

```/etc/puppet/hieradata/web03.yaml
---
classes:
- apache::install
- apache::config
- apache::service
- git::install
apache::config::server_limit: '512'
apache::config::max_limits: '512'
```

default.yaml、web01.yaml、web02.yaml、web03.yaymlが最初のサーバ説明の、

| 共通設定 |guestユーザがある|
|:-------|:--------------|
| web01  |Apache (ServerLimit,MaxClients:128) + vsftpd |
| web02  |Apache (ServerLimit,MaxClients:128) + Git |
| web03  |Apache (ServerLimit,MaxClients:512) + Git |

に、ほぼ対応するような定義になっています。サーバ単位の設定をみるのであれば、そこそこシンプルに表現できます。

## ⑦各サーバの定義が増えすぎて編集が大変な場合、簡易ツールを作って編集する
ここまで読んでいただいた方の中には、これだとサーバ台数が増えた時に、YAMLファイル編集面倒そうだなと思っている方がいるかもしれません。
事実そうです。数台程度であればこのままでもよさそうですが、サーバが数十台以上になると、サーバごとの定義ファイルが多くなりエディタ等で編集するのはかなり面倒です。全部のwebサーバの定義を全部変えるとかであればまだ良いのですが、webサーバグループ1にはこの変更、webサーバグループ3にはこの変更とか、グループ単位で設定が違う場合の変更は結構大変です。

なので、たくさん編集する時はツールを使って編集した方が楽という結論になりました。簡単な内容であれば、findコマンドやsedコマンドをうまく使えばいけますが、ちょっとこったことをしようとすると難しいので、ちょっとしたRubyスクリプトを書きました。

とりあえずこんなツールがあればいいなと思ったものを書いてみただけなので、造りが微妙でもご了承ください。
ツールの概要として、

- ①既存のHiera定義から、変更したい部分のみをパッチファイルとして抽出する（select）
- ②できたパッチファイルで、実際に値を変更する部分をエディタで書き換える
- ③書き換えたパッチファイルを、Hieraの定義に反映する（更新の場合update、削除の場合delete）

となります。

①の抽出時には、パラメータ名やYAMLファイル名の一部をオプション指定して、フィルターをかけられるようにしています。加えて、パッチファイル生成時に、値が同じサーバの指定をまとめて定義されるようにしました。（全ファイルの値を列挙するだけだと、編集の大変さは変わらないため）

ツールの配置場所は、hieradataディレクトリと同じ階層にhieratoolディレクトリを作って配置します。
　　
ツールの細かい実装部分の説明は、省略します。スクリプトをいちお載せていますが、次章の使い方例を見ていただいた方がイメージしやすいと思います。こんなツールがあればいいなで終わっちゃうと、実際使ったときどんな感じなのって微妙な感じになるため、とりあえず試作ツールを作ってみて公開しました。

今のところ、エラー処理が不十分だったり、コメント文が消える実装だったり、valueが配列やハッシュになっている場合の挙動があやしいです。

**なので普段、使っているHieraの定義ファイルに対して実行する場合は、自己責任でお願いします。
最低限、実行前にHiera定義をまるごとバックアップするなどしてください。**


```/etc/puppet/hieratool/hieratool.rb
#! /usr/bin/env ruby
# coding: utf-8

require 'yaml'
require 'optparse'
require 'readline'
require 'fileutils'
require 'pp'

params = ARGV.getopts('', 'mode:', 'nodes:', 'keys:', 'file:')
puts params if $DEBUG

if params['mode'] == nil or 
  (params['mode'] != 'select' and params['mode'] != 'update' and params['mode'] != 'delete')
  puts "ERROR: mode option"
  puts "\t--mode select|update|delete"
  exit 1
end

work_dir = File.expand_path(File.dirname(__FILE__))
Dir.chdir(File.join(work_dir, "../hieradata"))
hiera_file_list = Dir.glob("./**/*.yaml")

if params['file'] != nil
  patch_file = params['file']
else
  patch_file = File.join(work_dir, 'patch.yaml')
end

hiera_data_hash = {}
hiera_file_list.each do |file_name|
#  hiera_data_hash[file_name] = YAML.load_file(file_name)
  node = file_name.gsub(/^\.\/(.+)\.yaml$/, '\1')
  hiera_data_hash[node] = YAML.load_file(file_name)
end

case params['mode']
when 'select'
  pp hiera_data_hash if $DEBUG
  if params['nodes'] != nil
    regexp = Regexp.union(params['nodes'].split(','))
    hiera_data_hash.select! { |node, hiera_data| node =~ regexp}
  end
  if params['keys'] != nil
    regexp = Regexp.union(params['keys'].split(','))
    hiera_data_hash.each do |node, hiera_data|
      if params['keys'].include?('classes')
        hiera_data.select! { |key, value| key =~ regexp }
      else
        hiera_data.select! { |key, value| 
          if key == 'classes'
            value.select!{ |class_name| class_name =~ regexp }
            value.empty? ? false : true
          else
            key =~ regexp
          end
        }
      end
    end
  end
  pp hiera_data_hash if $DEBUG
  
  patch_data_hash = {}
  hiera_data_hash.each do |node, hiera_data|
    hiera_data.each do |key, val|
      if patch_data_hash.has_key? (key)
        is_equal_val = false
        patch_data_hash[key].each do | nodes_val |
          if nodes_val['value'] == val
            nodes_val['nodes'].push(node)
            is_equal_val = true
          end
        end
        if is_equal_val == false
          patch_data_hash[key].push({ 'nodes' => [node], 'value' => val })
        end
      else
        patch_data_hash[key] = [{ 'nodes' => [node], 'value' => val }]
      end
    end
  end
  
  puts '=' * 50 if $DEBUG
  pp patch_data_hash if $DEBUG
  
  puts '=' * 50 if $DEBUG
  str = YAML.dump(patch_data_hash)
  puts str
  File::open(patch_file, 'w') do |file|
    file.puts str
  end
when 'update'
  input = Readline.readline("Does hieradata update by #{patch_file}y/n[n]? : ")
  if input.chomp.downcase != 'y'
    puts 'cancel'
    exit 0
  end
  patch_data_hash = YAML.load_file(patch_file)

  pp patch_data_hash if $DEBUG
  update_val_count = 0
  patch_data_hash.each do |key, list|
   list.each do |data|
     data["nodes"].each do |node|
       puts node if $DEBUG
       pp hiera_data_hash if $DEBUG
       hiera_data_hash[node] = {} if hiera_data_hash[node] == nil
       pp hiera_data_hash[node] if $DEBUG
       pp hiera_data_hash[node][key] if $DEBUG
       pp data["value"] if $DEBUG
       if hiera_data_hash[node][key] != data["value"]
         is_update = false
         if key == 'classes'
           if hiera_data_hash[node][key] != nil
             if (data["value"] & hiera_data_hash[node][key]).size != data["value"].size
               hiera_data_hash[node][key].concat(data["value"])
               hiera_data_hash[node][key].uniq!
               is_update = true
            end
           else
             hiera_data_hash[node][key] = data["value"]
             is_update = true
          end
         else
           hiera_data_hash[node][key] = data["value"]
           is_update = true
         end
         update_val_count += 1 if is_update
       end
      end
    end
  end

  hiera_data_hash.each do |node, hiera_data|
    file_name = './' + node + '.yaml'
    FileUtils.mkdir_p(File.dirname(file_name)) unless FileTest::directory?(File.dirname(file_name))
    str = YAML.dump(hiera_data)
    puts file_name if $DEBUG
    puts str if $DEBUG
    File::open(file_name, 'w') do |file|
      file.puts str
    end
  end

   puts "update " + update_val_count.to_s + " values."
when 'delete'

  input = Readline.readline("Does hieradata delete by #{patch_file}y/n[n]? : ")
  if input.chomp.downcase != 'y'
    puts 'cancel'
    exit 0
  end
  patch_data_hash = YAML.load_file(patch_file)

  pp patch_data_hash if $DEBUG
  delete_val_count = 0
  patch_data_hash.each do |key, list|
   list.each do |data|
     data["nodes"].each do |node|
       puts node if $DEBUG
       pp hiera_data_hash if $DEBUG
       
       pp hiera_data_hash[node] if $DEBUG
       pp hiera_data_hash[node][key] if $DEBUG
       pp data["value"] if $DEBUG
       
       if key != 'classes'
         hiera_data_hash[node].delete(key)
         delete_val_count += 1         
       else
         data["value"].each do |class_name|
           before_size = hiera_data_hash[node]['classes'].size
           ret = hiera_data_hash[node]['classes'].delete(class_name)
           after_size = hiera_data_hash[node]['classes'].size
           delete_val_count += (before_size - after_size) if ret != nil
         end
       end
      end
    end
  end

  hiera_data_hash.each do |node, hiera_data|
    file_name = './' + node + '.yaml'
    FileUtils.mkdir_p(File.dirname(file_name)) unless FileTest::directory?(File.dirname(file_name))
    str = YAML.dump(hiera_data)
    puts file_name if $DEBUG
    puts str if $DEBUG
    File::open(file_name, 'w') do |file|
      file.puts str
    end
  end

  puts "delete " + delete_val_count.to_s + " values."
end
```

patch.yamlツールで生成されるパッチファイルです。

```/etc/puppet/hieratool/patch.yaml
一時的なファイルのため、こここでは未掲載。
```

# どんな風に使うの？
上記のサンプルマニフェストに対して、ちょっとした変更をしながら使い方を紹介します。
動作確認した環境は、

```
ruby 2.0.0p481 (2014-05-08 revision 45883) [universal.x86_64-darwin14]
```

です。※古い1.8.7とか古いバージョンだと、ハッシュの順序が不定とかだった気がします。なので古いバージョンでは、うまく動かない可能性があります。

## 変更前の状態

- web01：Apache (ServerLimit,MaxClients:128) + vsftpd
- web02：Apache (ServerLimit,MaxClients:128) + Git
- web03：Apache (ServerLimit,MaxClients:512) + Git

## ユースケース

- ①web02サーバの、ServerLimit,MaxClientsを128から512に変更する。
- ②webサーバ全台に、Gitをインストールするようにする。
- ③web01のvsftpdのを削除する（厳密にはPuppetでの管理対象外にする）。
- ④開発用DBサーバdb01に、web03と同じApache設定する定義を追加する。

## ツールのオプションなど

- ①既存のHiera定義から、変更したい部分のみをパッチファイルとして抽出する（select）

```
ruby hieratool.rb --mode select --keys キーの名前 --nodes ノード名
```

keys,nodesは必須でないです。キーの名前はパラメータ名、ノード名はyamlファイル名とほぼ同じです。指定に部分一致するものを抽出します。,で複数指定可能（or）です。

- ②書き換えたパッチファイルでHieraの定義を更新する（update）

```
ruby hieratool.rb --mode update
```

パラメータの値の部分を変更します。

- ③パッチファイルにあるキーをHieraの定義から削除する（delete）

```
ruby hieratool.rb --mode delete
```

パッチファイル記載されているパラメータを削除します。classesについては、class名が一致した場合削除です。

さきほどのユースケースを元に実際にどうやって変更するかです。
## ①web02サーバの、ServerLimit,MaxClientsを128から512に変更する
### ①-1既存定義からweb02のServerLimit,MaxClientsの取り出してパッチファイル生成

```
ruby hieratool.rb --mode select --keys apache::config::server_limit,apache::config::max_limits --nodes web02
```

生成されたパッチファイル
それぞれのパラメータ名（キー）に、どのサーバに該当するかのnodes指定と、パラメータの値valueがでます。

```/etc/puppet/hieratool/patch.yaml
---
apache::config::server_limit:
- nodes:
  - web02
  value: '128'
apache::config::max_limits:
- nodes:
  - web02
  value: '128'
```

### ①-2パッチファイルをエディタで編集128を512に。

```/etc/puppet/hieratool/patch.yaml
---
apache::config::server_limit:
- nodes:
  - web02
  value: '512'
apache::config::max_limits:
- nodes:
  - web02
  value: '512'
```

### ①-3パッチファイルをHieraの定義に反映する

```
ruby hieratool.rb --mode update
```

変更後のweb02の定義。値が512に変わっています。

```/etc/puppet/hieradata/web02.yaml
---
classes:
- apache::install
- apache::config
- apache::service
- git::install
apache::config::server_limit: '512'
apache::config::max_limits: '512'
```

## ②webサーバ全台に、Gitをインストールするようにする。

### ②-1既存定義からファイル名がwebにマッチするファイルから、Gitに関わる部分を取り出してパッチファイル生成

```
ruby hieratool.rb --mode select --keys git --nodes web
```

生成されたパッチファイル
現時点で、web02,web03に入っているのでその分でます。

```/etc/puppet/hieratool/patch.yaml
---
classes:
- nodes:
  - web02
  - web03
  value:
  - git::install
```

### ②-2パッチファイルをエディタで編集。対象ノードにweb01を追加。

```/etc/puppet/hieratool/patch.yaml
---
classes:
- nodes:
  - web01
  - web02
  - web03
  value:
  - git::install
```

### ②-3パッチファイルをHieraの定義に反映する

```
ruby hieratool.rb --mode update
```

変更後のweb01の定義。git::installがclassesに追加されています。

```/etc/puppet/hieradata/web01.yaml
---
classes:
- apache::install
- apache::config
- apache::service
- vsftpd::install
- vsftpd::service
- git::install
apache::config::server_limit: '128'
apache::config::max_limits: '128'
```

## ③web01のvsftpdのを削除する（厳密にはPuppetでの管理対象外にする）。
※Puppetを使って、実際にサーバから削除する場合、削除の定義を別途作る必要があります。

### ③-1既存定義のweb01から、vsftpdに関わる部分を取り出してパッチファイル生成

```
ruby hieratool.rb --mode select --keys vsftpd --nodes web01

```

生成されたパッチファイル
現時点で、vsftpd::install,vsftpd::serviceが入っているのでそれがでます。

```/etc/puppet/hieratool/patch.yaml
---
classes:
- nodes:
  - web01
  value:
  - vsftpd::install
  - vsftpd::service
```

### ②-2パッチファイルをエディタで編集。
　この場合、消したくないものはないため、未編集です。
　フィルターのかけ方によっては、消したくないものも残るため、その場合は編集してその記載を消してください。

### ③-3パッチファイルをHieraの定義に反映する

```
ruby hieratool.rb --mode update
```

変更後のweb01の定義。vsftpd::install,vsftpd::serviceがclassesからなくなっています。

```/etc/puppet/hieradata/web01.yaml
---
classes:
- apache::install
- apache::config
- apache::service
- git::install
apache::config::server_limit: '128'
apache::config::max_limits: '128'
```

## ④開発用DBサーバdb01に、web03と同じApache設定する定義を追加する。
DBサーバにもApache?となりますが、開発用サーバでDBとApacheを共存させた試験用サーバのイメージです。

### ④-1既存定義からweb03のApacheに関わる部分を取り出してパッチファイル生成

```
ruby hieratool.rb --mode select --keys apache --nodes web03
```

生成されたパッチファイル
ちょと冗長ですが、web03でapacheに関わる定義が全部取れます。

```/etc/puppet/hieratool/patch.yaml
---
classes:
- nodes:
  - web03
  value:
  - apache::install
  - apache::config
  - apache::service
apache::config::server_limit:
- nodes:
  - web03
  value: '512'
apache::config::max_limits:
- nodes:
  - web03
  value: '512'
```

### ④-2パッチファイルをエディタで編集。web03をすべてdb01に変更
変更箇所が多いですが、１ファイルのため簡単に置換できるはず？

```/etc/puppet/hieratool/patch.yaml
---
classes:
- nodes:
  - db01
  value:
  - apache::install
  - apache::config
  - apache::service
apache::config::server_limit:
- nodes:
  - db01
  value: '512'
apache::config::max_limits:
- nodes:
  - db01
  value: '512'
```

### ④-3パッチファイルをHieraの定義に反映する

```
ruby hieratool.rb --mode update
```

もともとdb01.yamlがなかったので、新たにファイルができます。本来であればDB関連の定義があるはずなので、既存ファイルのDB定義とマージされた状態になりますが、今回はサンプルのため変更追加したApache関連のみです。

```/etc/puppet/hieradata/db01.yaml
---
classes:
- apache::install
- apache::config
- apache::service
apache::config::server_limit: '512'
apache::config::max_limits: '512'
```

## ユースケースの総括
ここで注目して欲しいのは、Hieraの定義変更のみ限れば、ほとんど同じ手順①select②編集③update/delteでできることです。ユースケース④のようなちょっとイレギューラ？な変更もほぼ同じ手順です。
ただし、classやテンプレートの追加/変更がいるものについては、別途変更が必要なのは注意です。

あと、こういう補助ツールを使う場合大切なのは、ツールでの編集を必須としないことです。例えばユースケース①の変更などは、直接web02.yamlを編集した方が早いですし、そもそもこのツールの使い方を知らない人が変更できないような状態は好ましくないです。ツールの使い方を知らない人は、時間がかかるかもしれないけどYAMLを直接編集してもらうとかになります。基本的には、ツールが使いこなせる人にはツールで効率良く変更してもらうこともできるし、ツールが使えない人は簡単に書いているYAMLファイルを直接編集してもらくらいの柔軟な運用が楽で良いかと思います。

# あとがき
キャッチーなタイトルをつけた自作ツール紹介みたいになってしまいましたが、Puppetを使っていて前々から不便だと思っていた部分を補完するとしたらこんな機能が欲しいという提案だったりしますす。
Puppetは、個人的に構成管理（設定の一元管理やべき等性）に重きを置いてるため、設定変更ツールとして見たときに、ちょっと使いづらい部分があると思います。
例えば、特定のミドルウェアだけ設定を横並びで変更したいとか、横ぐし的な定義変更がしにくいとか。
急いで設定変更しないといけないから、簡易な定義を作って対応するとかがやりにくい。
どっちも普通にあることだけど、構成管理に重きを置いたマニフェストの書き方をすると、なかなか難しかったりします。

設定変更ツールとすれば、変更したい箇所を取り出して、ちょっと編集して、実際に反映させるくらいが、個人的にはちょうどいいんじゃないかとおもったため、補助ツールを試作してみました。
構成管理（設定の一元管理やべき等性）については、一旦Puppetマニフェストに統合して、マニフェストの変更差分をチェックしておけば担保できるので大丈夫なはずです。というか似たような編集機能がすでにあったりすれば知りたいです、Puppetに限らず。

あと、Hieraであまり言われないメリットですが、Puppetの独自DSLからの脱却もあります。YAMLで定義できる部分にシステムで変わりうるパラメータを寄せておけば、既存のパーサを使って編集や変換が簡単にできるメリットはかなり大きい気がします。よりよいConfigurarion Management Toolがもしあれば、変換スクリプトで一気に乗り換える、とかもできたらなと思うこの頃です。

長文、最後まで読んでいただきありがとうございました。