私とPuppet 基本編 その2 (Module,File,Templates,Variable)

Puppetの基本その2について記載する。

# 本シリーズの目次

* [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)
* [Puppetベストプラクティスの理解その2 多種多様なサーバの構成管理](http://qiita.com/takeuchikzm/items/8dd82540a8cceb7b850f)
* [Puppetベストプラクティスの理解その3 Hieraで世界が変わる?] (http://qiita.com/takeuchikzm/items/e04e6cbe84a185eb1f26)

# Module - マニフェストの機能単位での分割管理

[Puppetの基本の理解その1](http://qiita.com/takeuchikzm/items/cfa012571fdedcf0792b)にて説明した、

* **Resource** - 構成管理するResource状態の記述
* **Ordering** - Resourceの適用順序の指定
* **Class/Define** - Resourceのまとめ込みによる再利用性の向上
* **Node** - サーバ単位でのResoure定義のまとめ込み

を使えば複数のサーバに適用できるマニフェストは一通り書く事ができるようになった。
しかし、ここまででは全てのマニフェストを1つのファイルに全て詰め込む方法しか紹介していなかった。
VCSによるバージョン管理や共同開発等を考えると不便である。

Puppetでは、意味的にマニフェストファイルを分割して管理するために**Module**機能を用意している。

* Moduleは、任意の機能(mysqlやapache等)毎の構成管理に必要なマニフェストや配布用のファイル等をのディレクトリにまとめられる。
* `puppet.conf`で設定できる`modulepath`にModuleを置いておけば、どのマニフェストからでも宣言してModuleは利用できる。

  + またModule内部のResourceやファイル等を外部から参照する際に省略記法を使うことができる。

* 世の中の人々が作ったModuleを利用することも可能である。

## modulepathの設定

modulepathの設定は`puppet config`コマンドで確認できる。

```bash::modulepathの確認
$ sudo puppet config print
```

`:(コロン)`で区切られたディレクトリのどれかにModuleを置けば、その環境のマニフェストから参照することができる。

## Moduleのディレクトリ構成

Moduleの標準的なディレクトリ構成は以下となる。

* **manifests/**: マニフェストを置く。
* **templates/**: そのModuleの設定ファイルの雛形ファイルを置く。(変数等による内容変更が必要なものを置く。)
* **files/**: そのModuleの動作に必要なファイルを置く。(変数等による内容変更が必要のないものを置く。)
* **lib/**: Custom facter(システム状態等から自律的に決定される変数)やCustom Resource Typeの定義を置く。
* **tests/** or **exapmles/**: Moduleのマニフェストの利用方法の説明などを置く。
* **spec/**: rspec-puppetで書かれたテストファイルを置く。

中身が空なディレクトリは作成しなくても動作上問題は無い。

### manifests/の書き方

Moduleのmanifestsディレクトリの中には複数のマニフェストファイルを用意したり、サブディレクトリを作ったりすることができる。
ただし、Module内のマニフェストのclass名付けにはいくつかのルールがある。

**init.ppの書き方のルール**

* init.ppを必ず用意し、Module名と同名のclassを用意しなくてはならない。また、`<Module名>::init`というclassは持てない。

  + `${modulepath}/apache/manifests/init.pp`に`class apache { ..; }`が存在する必要がある。

* init.pp中には`<Module名>::<サブクラス名>`という名前のclassが定義できる。

  + `${modulepath}/apache/manifests/init.pp`には`class apache::class1 { ..; }`や`class apache::class2 { ..; }`等を定義する。

**init.pp以外のマニフェストの書き方のルール**

* init.pp以外のマニフェストファイルを作成したい場合、`<モジュール名>::<ファイル名>`という名のclassを用意しなくてはならない。

  + `${modulepath}/apache/manifests/install.pp`に`class apache::install { ..; }`が存在する必要がある。

* init.pp以外のマニフェストファイルには、`<Module名>::<ファイル名>::<サブクラス名>`という名前のclassが定義できる。

  + `${modulepath}/apache/manifests/install.pp`には`class apache::install::class1 { ..; }`や`class apache::install::class2 { ..; }`等を定義する。

**manifestsディレクトリにサブディレクトリを作る場合のルール**

* manifessディレクトリにサブディレクトリを作る場合、その中のマニフェストファイルには`<Module名>::<サブディレクトリ名>::<ファイル名>`というclassを用意しなくてはならない。

  + `${modulepath}/apache/manifests/site1/install.pp`には`class apache::site1::install { ..; }`が存在する必要がある。

* manifestsディレクトリのサブディレクトリのマニフェストファイルには`<Module名>::<サブディレクトリ名>::<ファイル名>::<サブクラス名>`という名前のclassが定義できる。

  + `${modulepath}/apache/manifests/site1/install.pp`には`class apache::site1::install::class1 { ..; }`や`class apache::site1::install::class2 { ..; }`等を定義する。

基本的にclass名をModule名やマニフェストファイル/サブディレクトリ名とマッピングさせるように記載する必要がある。
面倒であるが、class定義の探しやすさのために必要と言える。(perlやruby等のプログラム経験があるとさほど違和感を感じないのではないだろうか。)

上記のルールにしたがって記述したModuleを`modulepath`に配備すれば、そのマシンのどのマニフェストからも、

```puppet:Module内のclass宣言(include)
include apache::install
include apache::install::class1
include apache::install::class2
include apache::site1::install
include apache::site1::install::class1
include apache::site1::install::class2
```

もしくは、

```puppet:Module内のclass宣言(Resource)
class { 'apache::install': }
class { 'apache::install::class1': }
class { 'apache::install::class2': }
class { 'apache::site1::install': }
class { 'apache::site1::install::class1': }
class { 'apache::site1::install::class2': }
```

のようにすることで呼び出しが可能である。

より詳細な解説は、[PuppetLabs - Language: Namespaces and Autoloading](https://docs.puppetlabs.com/puppet/latest/reference/lang_namespaces.html)を参考されたい。

# Files - 静的ファイルの配備

さて、ここまで騙し騙し説明をしてきたが、Puppetでの構成管理のほとんどはシステムへのファイルの配備と言える。
Modulesの中の`files/`ディレクトリは配布用のファイルを置くために利用するものである。

file Resourceの`source` parameterを使えば、`files`以下のファイルを実システムに配備することができる。

`testmodule_file`モジュールを使った例を示す。(なお、例では`modulepath`としては`/etc/puppet/modules`を利用する。)
まず、配布したいファイル`file1`をモジュールディレクトリに以下のように配備する。

```bash:testmodule_fileの構造(/etc/puppet/modules/testmodule_file)
$ tree /etc/puppet/modules/testmodule_file/
/etc/puppet/modules/testmodule_file/
|-- files
|   `-- file1
`-- manifests
    `-- init.pp
```

file1の中には適当な内容を入れておく。

```bash:
$ cat /etc/puppet/modules/testmodule_file/files/file1 
testmodule_file_file1
```

init.ppの記述例は以下。


```puppet::sourceによるfiles以下ファイルの指定(/etc/puppet/modules/testmodule_file/manifests/init.pp)
class testmodule_file {
  file { '/tmp/testmodule_file_file1':
    ensure  => file,
    mode    => '644',
    owner   => 'root',
    group   => 'root',
    source  => "puppet:///modules/testmodule_file/file1",
  }
}
```

## source parameterのvalueのファイルPATHの指定

`source` parameterのvalueでのModuleの`files`化のファイルの指定は若干特殊で`puppet:///modules`というprefixをつける必要がある。

具体的には、

```puppet:sourceのvalueの書式
puppet:///modules/<Module名>/<filesディレクトリからのファイルの相対Path>
```

となる。`files`以下にサブディレクトリを用意することも可能である。

# Templates - 動的ファイルの配備

file Resourceの`content` parameterと`template`関数を組み合わせると、雛形ファイルの中身を配布時に変数を使って変更して配布することができる。
説明の都合上、`files`を使った静的ファイルの配布から説明したが、設定ファイルの配備等ではほとんどの場合`template`を使う。

具体的には、Module配下の`templates`ディレクトリは雛形ファイルを置き、`file` Resourceの`content` parameterのvalueでtemplate関数を使い雛形ファイルの場所を指定することで行う。

以下、`testmodule_temlate`を使った例を示す。

```bash:testmodule_templateの構造(/etc/puppet/modules/testmodule_template)
$ tree /etc/puppet/modules/testmodule_template/
/etc/puppet/modules/testmodule_template/
|-- manifests
|   `-- init.pp
`-- templates
    `-- template1
```

雛形ファイルであるtemplate1の中は以下のようにしておく。
ファイル中の`<%= @var1 %>`は雛形ファイルの特殊表記でvar1変数を参照した内容を入れるという意味である。

```bash:
$ cat /etc/puppet/modules/testmodule_template/templates/template1 
This template file test.

<%= @var1 %>
```

init.ppには以下のように記述して、雛形ファイルを指定する。


```puppet::contents及びtemplateによるtemplates/以下ファイルの指定(/etc/puppet/modules/testmodule_template/manifests/init.pp)
class testmodule_template (
  $var1 = "default var1"
) {
  file { '/tmp/file_from_testmodule_template':
    ensure  => file,
    mode    => '644',
    owner   => 'root',
    group   => 'root',
    content => template('testmodule_template/template1'),
  }
}
```

試しに以下のような宣言を用意して実行してみる。雛形ファイル中の変数も変更してみる。

```puppet:/tmp/puppet_and_me/testmodule_template.pp
class { 'testmodule_template':
  var1 => 'set by class declare var1',
}
```

```bash:applyと確認
$ sudo puppet apply /tmp/puppet_and_me/testmodule_template.pp
Notice: Compiled catalog for vagrant-centos65 in environment production in 0.25 seconds
Notice: /Stage[main]/Testmodule_template/File[/tmp/file_from_testmodule_template]/content: content changed '{md5}60a685a6103212f8347d88031cbd7867' to '{md5}e8915467f3fbc34cb902d6bab4ef0609'
Notice: Finished catalog run in 0.04 seconds

$ cat /tmp/file_from_testmodule_template 
This template file test.

set by class declare var1
```

## template関数による雛形ファイルの指定

template関数の引数にはファイルPATHのようなものを書くが、絶対PATHで書いた場合と相対PATH指定で記述した場合でルックアップの挙動が変わる。

* 絶対PATHの場合: ファイルシステムの絶対PATHから雛形ファイルを探す。
* 相対PATHの場合: 1つ目の`/`(スラッシュまではModule名、1つ目のスラッシュより後ろは対応するModule名のtemplatesディレクトリ直下からの相対PATHとして雛形ファイルを探す。

```puppet:絶対PATH
  content => template('/tmp/puppet_and_me/templates/template1'),
```

と書くと文字通り`/tmp/puppet_and_me/templates/template1`が雛形ファイルとなり、

```puppet:相対PATH
  content => template('testmodule_template/template1'),
```

と書くと、`/etc/puppet/modules/testmodule_template/templates/template1`が雛形ファイルとなる。

## 雛形ファイルの書式

雛形ファイルはRuby on Rails等で使われているERB記法で記述する。

ERBでは`<% %>`というTag内部でほとんどのRuby言語の記載が可能である。

```rb:varのvalueを挿入
<%= @var %>
```

```rb:varが定義されているときのみ挿入
<% if @var -%>
var is <%= @var %>
<% end -%>
```

```rb:配列変数の中身で繰り返してvalueを挿入
<% @array_var.each do |v| -%>
<%= v %>
<% end -%>
```

等がよく使われる。

というか、ERBのタグ内では上記パターン以外の複雑な事をしないほうが良い。
極力雛形ファイルをシンプルかつ実際に適用された際の内容が想像しやすくしないと再利用性が下がる。

詳細なERB表記に関しては、[PuppetLabs - Docs: Using Puppet Templates](https://docs.puppetlabs.com/guides/templating.html#erb-template-syntax)を参照されたい。

# Variable 

Puppetでは、通常のプログラミング言語のように変数を使うことができる。

変数の参照、代入部ともに文字列の先頭に`$`(ドルマーク)をつけることでその文字列が変数として認識される。

```puppet:変数の定義
$var = "this is a pen"
```

ただし、Parameterized Classの宣言部分({}で囲まれる内部)でのparameter代入式の左辺のparameter名には`$`はつけない点に注意されたい。

```puppet:ParameterizedClass宣言での代入
class param_class (
  $param_var = '',
) {
  file { '/tmp/param_class':
    ensure  => file,
    content => inline_template($param_var),
  }
}

$var = "This is a pen"

class { 'param_class':
  param_var => $var,
}
```

また、Templateファイル外で定義された変数にアクセスする際は、`<%= @var %>`のように変数名の先頭に@をつける。

## Array, Map

整数値や文字列等のprimitiveな型以外にも配列(array)や連想配列(map)型も使うことができる。

```puppet:array変数の定義と参照
$array_var = ["apple", "banana"]
$var = ${array_var[0]} # apple
```

```puppet:map変数の定義
$map_var = {"name" => "takuechikzm", "address" => "Japan"}
$var = ${map_var['name']} # takekuchikzm
```

主にarrayやmapはTemplateファイルのERBに対しての値渡しに使う場合が多い。

## Facter変数

Puppetでは特に自分で指定しなくても、自動的に設定される変数が存在する。それらの変数はfacter変数(もしくはfacter)と呼ばれる。

例えば

* hostname: その環境のホスト名が代入される。
* virtual: 仮想マシンの場合`kvm`等のハイパーバイザ名が、物理マシンの場合`physical`が代入される。
* operatingsystem: OSの種類(RedHat, CentOs, Debian等)が代入される。

等がある。

現在の環境で利用可能なfacter変数は、

```bash:facter変数の確認
$ facter
```

と実行することで確認できる。

Puppetマニフェスト内でfacter変数を参照する場合は、`$::operatingsystem`のように$と変数名の間に`::`を入れることが推奨される。
(これはローカルスコープのfacterと同名の変数を参照しないようにするため。)

## 変数のスコープ

classから特にスコープを指定せずに変数を参照した場合の、変数名の解決は以下の順に行われる。

* Localスコープ(同一class定義内)からの解決
* 親クラススコープからの解決

  + (あまり使わないので詳しく説明しないがpuppetではクラスの継承をしてクラスの共通部分の記述を削減することができる。)

* Nodeスコープからの解決
* Topスコープからの解決

classで定義されいてる変数に関しては、Puppetマニフェスト中では`<class名>::<変数名>`というフルネームで参照することもできる。

```puppet:フルネームによる変数参照(fullname_refer.pp)
class other_class (
  $other_param = "other class var",
) {
}

include other_class

file { '/tmp/other_class_var':
  ensure  => file,
  content => "$other_class::other_param",
}
```

この場合、`/tmp/other_class_var`の内容は`other class var`になる。

また、Resource定義と違い、変数は同一スコープ内では先に変数定義が無いとならないので注意。
(これは、状態記述言語であるPuppetとしてはイマイチな制約であるが、まだ残存してしまっている問題とのこと。)

Puppetマニフェストでは同一スコープ内で同一変数に再代入することができない点にも注意が必要である。

```puppet:間違った例(変数の再代入)
$var = "first file content"
file { '/tmp/first_file':
  ensure => file,
  content => "$var",
}
$var = "second file content"
file { '/tmp/second_file':
  ensure => file,
  content => "$var"
}
#上記を実行すると、Error: Cannot reassign variable in .. というエラーが出る。
```

## 変数の使い方のコツ

変数はとても便利なのであるが使い方を間違えると動かすとどのような構成になるのかがわからない**恐怖のマニフェスト**が出来上がるため注意が必要である。

万人が読んでわかりやすいマニフェストを書く際は以下に留意すると良い。

* Nodeスコープの変数をNodeスコープ以外から使わない。
* スコープを指定しない変数参照の場合、必ずローカルスコープからの参照となるようにする。

  + 同一モジュール配下の変数管理用のクラスを継承する場合のみ親クラススコープから解決は可。
  + トップスコープやfacter変数の参照の場合は必ず`$::top_scope_var`のようにトップ or facter変数からの参照とわかるようにする。

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

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

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