Puppet4.xではじめるサーバ設定自動化 8（ファイルの穴埋めERB）

Puppet4.xではじめるサーバ設定自動化の、ファイルの穴埋めERB編です。
全体的な目次は、[ここ](http://qiita.com/kijibato/items/ae510dfa91884bdd9f61)を参照してください。
主にCentOS 7を例に紹介しているので、その他OSについては読み替えてください。

# Embedded Ruby (ERB) テンプレート

**PuppetとERB形式のテンプレートファイルを組み合わせると、共通のテンプレートファイルで、サーバごとに設定ファイルを変えることができます。**
まず、配布する設定ファイルのテンプレートに、サーバごとに変える部分をRubyコードを埋め込んでおきます。サーバに設定ファイルが配布される際に、Rubyの部分が処理されて設定ファイルが作られます。

ERBを使えば、テンプレートを共通にしつつも、次のことができるようになります。

- 特定のパラメータを変数にして、サーバごとに値を指定する。
- if文で特定のサーバのみだけの設定が記載できる。
- 配列、ハッシュをループさせて、行数の異なる設定ファイルが作れる。

ちなみにERB単独ではなく、テンプレートファイルに適切な変数を渡すのは、Puppet側で対応する必要があります。もしくはfacterでの値取得も使えますが、facterについては別の機会に紹介します。

# ERBのタグ

テンプレートにRubyコードを埋め込むためのタグです。わかりにくいので、後で書くサンプルも参考にしてください。

```erb
<%= EXPRESSION %>  : 変数の値を埋め込む
<%= EXPRESSION -%> : 変数の値を埋め込む。直後の改行を無視する
```

```erb
<% CODE %>   : ifやループを書く場合に使用。
<%- CODE %>  : ifやループを書く場合に使用。（タグの前の）行頭の空白文字を、ファイルに出力しない。インデントをそろえたいときに。
<% CODE -%>  : ifやループを書く場合に使用。直後の改行を無視する
<%- CODE -%> : ifやループを書く場合に使用。行頭の空白文字、直後の改行をファイルに出力しない
```

```erb
<%# COMMENT %>  : テンプレートファイルにコメントを書く。ファイルには出力しない
<%# COMMENT -%> : コメントを書きつつ、直後の改行を無視する
```

```
<%% : 設定ファイルに<%を出力したい場合、テンプレートファイルには<%%と書く
%%> : 設定ファイルに%>を出力したい場合、テンプレートファイルには%%>と書く
```

# サンプル

サンプルの前に、サンプルのファイル構成と実行方法です。
puppetのマニフェストファイルとerb01.ppと、テンプレートファイルpuppet_erb_sample.txt.erbを次のようにおきます。
puppet_erb_sample.txt.erbについては、今回日本語（２バイト文字）を含んでいるので、UTF8で保存してください。また、実行するサーバの言語設定をきちんとしておかないと動かない場合があります。ふつう設定ファイルに日本語をあまり書かないので、とりあえず適当な英数字に書き換えて試してみるでもよいです。

```
.
|-- erb01.pp
`-- modules
    `-- sample
        `-- templates
            `-- puppet_erb_sample.txt.erb
```

applyで実行する場合なので、オプションにmodulepathを指定します。

```
/opt/puppetlabs/bin/puppet apply erb01.pp --modulepath=modules
```

以降、サンプルです。サンプルでは、Puppetのマニフェストファイルで、ホスト名ごとに変数の値を設定しています。例では、ホスト名がfrontwebとそれ以外のサーバで、設定される値を変えています。**ERB形式のテンプレートファイルから、設定する変数を使うには変数名の前に@をつけます。**

## 変数にする

### マニフェストファイル

```puppet:erb01.pp
$myparameter = $hostname ? {
  "frontweb" => 100,
  default    => 20,
}

file { "/tmp/puppet_erb_sample.txt":
  ensure  => "file",
  owner   => "root",
  group   => "root",
  mode    => "0644",
  content => template("sample/puppet_erb_sample.txt.erb"),
}
```

### テンプレートファイル

```erb:puppet_erb_sample.txt.erb
# パラメータの置き換えの例です。タグの閉じ方で、直後の改行を無視するしないがあります
MaxClients <%= @myparameter %>
MaxClients <%= @myparameter %>
 # comment
MaxClients <%= @myparameter -%>
 # comment
```

### 実行結果例(ホスト名がfrontweb)

```/tmp/puppet_erb_sample.txt
# パラメータの置き換えの例です。タグの閉じ方で、直後の改行を無視するしないがあります
MaxClients 100
MaxClients 100
 # comment
MaxClients 100 # comment
```

### 実行結果例(ホスト名がfrontweb以外)

```/tmp/puppet_erb_sample.txt
# パラメータの置き換えの例です。タグの閉じ方で、直後の改行を無視するしないがあります
MaxClients 20
MaxClients 20
 # comment
MaxClients 20 # comment
```

## ハッシュを渡す

### マニフェストファイル

```puppet:erb01.pp
$myhash = $hostname ? {
  "frontweb" => { "server_limit" => 100, "max_clients" => 100 },
  default    => { "server_limit" =>  20, "max_clients" =>  20 },
}

file { "/tmp/puppet_erb_sample.txt":
  ensure  => "file",
  owner   => "root",
  group   => "root",
  mode    => "0644",
  content => template("sample/puppet_erb_sample.txt.erb"),
}
```

### テンプレートファイル

```erb:puppet_erb_sample.txt.erb
# パラメータの置き換えの例です。ハッシュも使えます
ServerLimit <%= @myhash["server_limit"] %>
MaxClients <%= @myhash["max_clients"] %>
```

### 実行結果例(ホスト名がfrontweb)

```/tmp/puppet_erb_sample.txt
# パラメータの置き換えの例です。ハッシュも使えます
ServerLimit 100
MaxClients 100
```

### 実行結果例(ホスト名がfrontweb以外)

```/tmp/puppet_erb_sample.txt
# パラメータの置き換えの例です。ハッシュも使えます
ServerLimit 20
MaxClients 20
```

## サーバごとに出力するしない

### マニフェストファイル

```puppet:erb01.pp
$mycondition = $hostname ? {
  "frontweb" => true,
  default    => false,
}

file { "/tmp/puppet_erb_sample.txt":
  ensure  => "file",
  owner   => "root",
  group   => "root",
  mode    => "0644",
  content => template("sample/puppet_erb_sample.txt.erb"),
}
```

### テンプレートファイル

```erb:puppet_erb_sample.txt.erb
# Rubyコードを埋め込む例です
# if sample 1
<% if @mycondition == true %>
if文を満たす場合、出力されます。タグの閉じ方でif,endの行で改行するパターン。
<% end %>

# if sample 2
<% if @mycondition == true -%>
if文を満たす場合、出力されます。タグの閉じ方でif,endの行で改行しないパターン。
<% end -%>

# if sample 3
<% if @mycondition == true %>１行でも書けます<% end %>

# if sample 4
<% if @mycondition == true -%>
if文を満たす場合、出力されます。
<% else -%>
if文を満たすさない場合、出力されます。
<% end -%>
```

### 実行結果例(ホスト名がfrontweb)

```/tmp/puppet_erb_sample.txt
# Rubyコードを埋め込む例です
# if sample 1

if文を満たす場合、出力されます。タグの閉じ方でif,endの行で改行するパターン。


# if sample 2
if文を満たす場合、出力されます。タグの閉じ方でif,endの行で改行しないパターン。

# if sample 3
１行でも書けます

# if sample 4
if文を満たす場合、出力されます。
```

### 実行結果例(ホスト名がfrontweb以外)

```/tmp/puppet_erb_sample.txt
# Rubyコードを埋め込む例です
# if sample 1


# if sample 2

# if sample 3


# if sample 4
if文を満たすさない場合、出力されます。
```

## ループ

### マニフェストファイル

```puppet:erb01.pp
$myarry = $hostname ? {
  "frontweb" => ["192.168.10.0/24", "192.168.20.0/24", "192.168.30.0/24"],
  default    => ["192.168.1.0/24"],
}

file { "/tmp/puppet_erb_sample.txt":
  ensure  => "file",
  owner   => "root",
  group   => "root",
  mode    => "0644",
  content => template("sample/puppet_erb_sample.txt.erb"),
}
```

### テンプレートファイル

ちなみに、eachで配列の各要素でループしていますが、そのとき各要素の値をitemという名前の変数に入れています(名前はitemでなくてもよいです)。itemは**テンプレート内で使っている変数なので、@をつけないのに注意してください。**

```erb:puppet_erb_sample.txt.erb
# loop sample 1
# myarryの要素数分繰り返す例
<% @myarry.each do |item| -%>
allow from <%= item %>
<% end -%>

# loop sample 2
# myarryの要素数分繰り返す例(入れ子の場合もキレイに書きたい場合)
<Directory /usr/local/www/data>
  order deny,allow
  deny from all　
# 読みにくい...
<% @myarry.each do |item| -%>
  allow from <%= item %>
<% end -%>

  # こうすると無駄な空白ができてしまう
  <% @myarry.each do |item| -%>
  allow from <%= item %>
  <% end -%>

  # こうすると無駄な空白ができない
  <%- @myarry.each do |item| -%>
  allow from <%= item %>
  <%- end -%>
</Directory>
```

### 実行結果例(ホスト名がfrontweb)

```/tmp/puppet_erb_sample.txt
# loop sample 1
# myarryの要素数分繰り返す例
allow from 192.168.10.0/24
allow from 192.168.20.0/24
allow from 192.168.30.0/24

# loop sample 2
# myarryの要素数分繰り返す例(入れ子の場合もキレイに書きたい場合)
<Directory /usr/local/www/data>
  order deny,allow
  deny from all　
# 読みにくい...
  allow from 192.168.10.0/24
  allow from 192.168.20.0/24
  allow from 192.168.30.0/24

  # こうすると無駄な空白ができてしまう
    allow from 192.168.10.0/24
    allow from 192.168.20.0/24
    allow from 192.168.30.0/24
  
  # こうすると無駄な空白ができない
  allow from 192.168.10.0/24
  allow from 192.168.20.0/24
  allow from 192.168.30.0/24
</Directory>
```

### 実行結果例(ホスト名がfrontweb以外)

```/tmp/puppet_erb_sample.txt
# loop sample 1
# myarryの要素数分繰り返す例
allow from 192.168.1.0/24

# loop sample 2
# myarryの要素数分繰り返す例(入れ子の場合もキレイに書きたい場合)
<Directory /usr/local/www/data>
  order deny,allow
  deny from all　
# 読みにくい...
  allow from 192.168.1.0/24

  # こうすると無駄な空白ができてしまう
    allow from 192.168.1.0/24
  
  # こうすると無駄な空白ができない
  allow from 192.168.1.0/24
</Directory>
```

## コメントとかエスケープ

### マニフェストファイル

```puppet:erb01.pp
file { "/tmp/puppet_erb_sample.txt":
  ensure  => "file",
  owner   => "root",
  group   => "root",
  mode    => "0644",
  content => template("sample/puppet_erb_sample.txt.erb"),
}
```

### テンプレートファイル

```erb:puppet_erb_sample.txt.erb
# コメント例です。タグの閉じ方で、直後の改行を無視するしないがあります
# comment
<%# これはコメントです %>
直後の改行が有効
<%# これはコメントです -%>
直後に改行が無視される

# タグのエスケープ例です
<%%
%%>
<%= "<%%" %>
<%= "%%>" %>
```

### 実行結果例

エスケープについてはわかりにくですが、タグの外で書く場合と、タグの中で書く場合でエスケープされるされないがあります。

```/tmp/puppet_erb_sample.txt
# コメント例です。タグの閉じ方で、直後の改行を無視するしないがあります
# comment

直後の改行が有効
直後に改行が無視される

# タグのエスケープ例です
<%
%%>
<%%
%>
```


# 穴埋めするべきか、穴埋めしないべきか
本題からそれた内容です。
ERBの動的ファイル作成はうまく使えば便利ですが、デメリットもあります。動的にファイルを作る場合の、メリットとデメリットは次の通りです。

## メリット

- **サーバ構成が動的に変わる場合でも対応できる**（オートスケール、スケールアウトなど）
- 似たような設定ファイルを減らせるため、変更漏れが起きにくい
- サーバごとに変わる部分と、そうでない部分がきわだつ

## デメリット

- **期待する設定が実際に入っているか確認（テスト）が必要**
- ERBとPuppetを理解していないと、どんな設定がされているか分からない
- 設定変更が多い場合は、テンプレートを丸ごと入れ替えた方が楽な場合がある
- ifやループを埋め込みすぎると、(個人的に)かなり見づらい。

なので、バランスよく取り入れるくらいがよいかと。少なくとも、１つのテンプレートでどのサーバの設定もカバーするようなことはやらない方がよいです。まずは、サーバごとに配布するテンプレートを分けつつ、**ここをサーバーごとに変更できれば、テンプレートが減らせそうだと思ったところから穴埋めを使っていく**のがベターです。また、**設定ファイルを他の人に用意してもらうような運用の場合、穴埋めは最小限にしないとメンテが大変です。**

# 参考
fileリソースの詳しい情報は、下記参照です。

- https://docs.puppet.com/puppet/latest/reference/type.html#file

ERB形式のテンプレートファイルのシンタックスについては、下記参照です。
変数スコープとか、他のテンプレートを読み込むとか興味のある方はぜひ。

- https://docs.puppet.com/puppet/latest/reference/lang_template_erb.html

ERBについて日本語。Puppetでの文法と違う場合もあるかもしれないで、参考程度に。

- http://magazine.rubyist.net/?0017-BundledLibraries