私とPuppet ベストプラクティス編 その3 (Hieraで世界が変わる?)

Puppetマニフェストを高度かつ複数人で安全に使うための規約例やテクニックおよびすべからず等について記述する。元々Hieraの使い方のロクな資料がなかったので何とかしたくて始めたこのシリーズもやっとここまできたかという感じ。

ここまで読んでくれた方であればHieraを使った構成のスッキリ感と見通しの良さに感動するはず。
なお、hieraの機能の中で最も簡略にマニフェストが書けるようになる`hiera_include`の利用のみを焦点として書いている。

**Hieraを使ってModuleをスッキリ書く例を紹介していましたが、params Classの内部ブロックで新たにローカル変数を環境に応じて定義するような場合にうまくいかないので消しました。**

# 本シリーズの目次

* [Infrastructure as Code 概念の理解](http://qiita.com/takeuchikzm/items/fb9d1701ea19be337131)
* [Puppet環境の準備](http://qiita.com/takeuchikzm/items/eb37a918b39b069f22b3)
* [Puppetの基本の理解その1 Resource,Ordering,Class,Define,Node](http://qiita.com/takeuchikzm/items/cfa012571fdedcf0792b)
* [Puppetの基本の理解その2 Module,File,Templates,Variable](http://qiita.com/takeuchikzm/items/6b65af9e0cf8d317c9b3)
* [Puppetベストプラクティスの理解その1 ディレクトリ構成とその役割](http://qiita.com/takeuchikzm/items/5e303e03a48ab2f0db1a)j
* [Puppetベストプラクティスの理解その2 多種多様なサーバの構成管理](http://qiita.com/takeuchikzm/items/8dd82540a8cceb7b850f)
* Puppetベストプラクティスの理解その3 Hieraで世界が変わる? (本ページの内容)

# Hieraとは?

公式サイトによるとHieraとは、

* Hiera以前のマニフェストの冗長さを無くして、クラスタのnodeやrole差分を管理可能とする。

ものである。

もうちょっと噛み砕くと、

* `node`定義で構成差分を記載しようとすると冗長になるので、node単位のYAML形式でClass宣言もParameter調整も書けるようにする
* さらにそのYAMLは階層構造をもたせるようにして環境差分を書きやすくする

ものである。

うーん、ダメだ。文字だけだと全然うまく書けないわ。

(完全に余談だが、後発の構成管理ツールのAnsibleでは全てのroleやパラメタの設定をYAML形式で書くようになっている。きっとPuppetのHieraやENCを参考としたに違いないと勝手に思っている。)

# Hieraを使うと何が嬉しいのか?

## node定義がコンパクトになる。

Hieraを使うと、`node`定義には以下の情報しか必要なくなる。

* Moduleを跨いだClass間の依存関係([Ordering](http://qiita.com/takeuchikzm/items/cfa012571fdedcf0792b#1-3))しか書かなくて良くなる。
* (もしロールを識別して設定を変えたい場合は、)ロール識別用の変数

もし依存関係が無いnode定義の場合、以下のように書くだけである。

```puppet:
node web01 {
  $myrole = "web" #ロール識別用の変数
  hiera_include('classes')
}
```

## node定義のClass宣言やParameter調整がスッキリ書ける。

Hieraを使うと、以前はnode定義でしていたClass宣言やParameter変更の記述は全てYAMLでマニフェストとは分離して記載できる。

例えば、

```puppet:web01.pp
node web01 {
  class { 'os':
    hosts_tmpl   => "os/hosts.web01",
    sysctl_addon => ["net.core.somaxconn = 512", "vm.swappiness = 0"],
  }
}
```

という`node`定義は、

```yaml:/etc/puppet/hieradata/hosts/web01.yaml
---
classes:
  - os

os::params::hosts_tmpl: "os/hosts.web01"
os::params::sysctl_addon: 
  - "net.core.somaxconn = 512"
  - "vm.swappiness = 0"
```

と書けるようになる。


<a name="hiera_sukkiri"></a>
## 同一ホスト名の商用・試験環境での構成差分をスッキリ書ける。

facterやTopレベル変数、Nodeレベル変数等使って優先度をつけてYAMLを適用できる。

* **hosts/(ホスト名)-(環境名).yaml**: 環境名(商用=`production`,試験=`test`)でのみホスト名単位で適用されるクラス宣言やParameter調整を記述。
* **hosts/(ホスト名).yaml**: ホスト名に応じたクラス宣言やParameter調整を記述。
* **roles/(ロール名).yaml**: WebやDB等のロールに応じたクラス宣言やParameter調整を記述。
* **common.yaml**: 全てのサーバが行うクラス宣言やParameter調整を記述。

というような任意の階層構造を定義し、上に行くほど優先的な設定となるよう重複なく記述できる。

例えばHiera以前の以下のような`node`定義群を考える。

```puppet:hiera以前のnode定義群
# /etc/puppet/manifests/site.pp
$env = "test"

# /etc/puppet/manifests/web.pp
node web01 {
  class { 'common':
    label => "this is web01"
  } 
  class { 'os':
    hosts_tmpl   => "os/hosts.web01",
    sysctl_addon => ["net.core.somaxconn = 512", "vm.swappiness = 0"],
  }
  if $env == "production" {
    class { 'apache':
      document_root => "/opt",
    }
  } elsif $env == "test" {
    class { 'apache':
      thread_num    => 10,
      document_root => "/opt",
    }
  }
}

node /^web[0-9]{4}.*$/ {
  include common
  include os
  include apache
}

# /etc/puppet/manifests/web.pp
node monitor01 {
  class { 'common':
    label => "this is monitor01"
  } 
  class { 'os':
    hosts_tmpl   => "os/hosts.monitor01",
  }
  include nagios
}
```

各`node`定義でのosやcommon等の共通のClass宣言等かなり冗長に感じるのではないだろうか？
更に商用環境と試験環境でのClass宣言挙動を切り替えるためのenv変数が加わり更に見通しが悪い。そもそも{}が邪魔くさい。

これと同じ構成管理をHieraで記述すると以下のようになる。

```puppet:hieraのマニフェスト定義群
# /etc/puppet/manifests/site.pp
$env = "test"

# /etc/puppet/manifests/web.pp
node web01 {
  $myrole = "web"
  hiera_include('classes')
}
node /^web[0-9]{4}.*$/ {
  $myrole = "web"
  hiera_include('classes')
}

# /etc/puppet/manifests/monitor.pp
node monitor01 {
  $myrole = "monitor"
  hiera_include('classes')
}
```

```yaml:hieraのYAML定義群
# /etc/puppet/hieradata/common.yaml
---
classes:
  - common
  - os

# /etc/puppet/hieradata/roles/web.yaml
---
classes:
  - apache

# /etc/puppet/hieradata/hosts/web01.yaml
---
common::params::label: "this is web01"
os::params::hosts_tmpl: "os/hosts.web01"
os::params::sysctl_addon: 
  - "net.core.somaxconn = 512"
  - "vm.swappiness = 0"
apache::params::document_root: "/opt"

# /etc/puppet/hieradata/hosts/web01-test.yaml
---
apache::params::thread_num: 10


# /etc/puppet/hieradata/hosts/monitor01.yaml
---
classes:
  - nagios

common::params::label: "this is monitor01"
os::params::hosts_tmpl: "os/hosts.monitor01"
```   

となる。

下層のレイヤでのClass宣言やParameter調整を有効としたままに、更に上層レイヤで上書きや追加ができるのでHieraのほうがシンプルに構成を見通せるということがなんとなく伝わったであろうか。

<a name="apache_best_hiera"></a>

# Hieraの使い方

puppet 3系以降ではhiera機能はデフォルトで使える。

## hiera.yamlの作成

Hieraを使うためには、

* HieraによるシステムのYAML設定の置き場所
* Hieraの階層構造

などが記載された`/etc/puppet/hiera.yaml`ファイルを準備する必要がある。

以下のファイルを作成する。

```yaml:hiera.yaml
---
:backends:
  - yaml
:yaml:       #ここがシステムのYAML設定の置き場所
  :datadir: /etc/puppet/hieradata
:hierarchy:  # ここが階層構造を指定する部分。
  - hosts/%{::hostname}-%{::env}
  - hosts/%{::hostname}
  - roles/%{myrole}
  - common
```

この設定は[先の例](#hiera_sukkiri)で動くように意識したものとなっている。

## 階層構造の設計

Hieraでは、hiera.yamlの`:datadir:`(この例では`/etc/puppet/hieradata`)の末尾に`:hierarchy:`を下から順に適用したYAMLファイルを読み込んで行うべきクラス宣言やParameter設定を決定する。

`:hierarchy:`で出てくる`"%{::hostname}"`はpuppetにおける`$::hostname`変数の参照と同じである。
ただし、`:hierarchy:`の定義に使える変数はTopレベルもしくはNodeレベルでの変数のみである。

nodeレベルの変数の参照をフルパスで指定する方法は（多分）無いため、動的な名前解決に頼っている。
(ちゃんとNodeレベルの変数を拾ってくれるのか不安なのでModuleであまり使わなそうな`myrole`というnodeレベル変数を今回は使ってみた。)

例えば、`monitor01`サーバの場合、

1. /etc/puppet/hieradata/common.yaml
1. /etc/puppet/hieradata/roles/monitor.yaml
1. /etc/puppet/hieradata/hosts/monitor01.yaml
1. /etc/puppet/hieradata/hosts/monitor01-test.yaml

の順序でYAMLファイルを探して適用していく。ファイルがなくても問題はない。

# Puppetの学習に便利なリンク

* [PuppetLabs - Docs: Hiera Overview](https://docs.puppetlabs.com/hiera/1/)

* [PuppetLabs - Docs: Puppet 3.N Reference Manual] (https://docs.puppetlabs.com/puppet/latest/reference/)
  
  + より細かい言語仕様について書かれたページ基本編を越えた状態であれば読み解けるはず。
  + このマニュアルを一通り理解したメンバのみでPuppetマニフェストを作れれば相当幸せになれる。

* [PuppetLabs - Lerning Puppet](https://docs.puppetlabs.com/puppet/#learning-puppet)

  + 順を追って書かれており初学者が体系的な学習にお勧め。
  + ぶっちゃけこのページはこのリンク先を圧縮したようなものである。
  + このチュートリアル用のVMも[無料ダウンロード](http://puppetlabs.com/download-learning-vm)できる。(ただし登録が必要)