私とPuppet 基本編 その1 (Resource,Ordering,Class,Define,Node)

# 本シリーズの目次

* [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)

# Resource - Puppetによる構成管理の最小単位

Puppetでは構成管理する対象をResourceと呼ぶ。
Resourceの種類(Resource Type)としては、

* file(ファイル、ディレクトリ、シンボリックリンク)
* user(rootや一般ユーザアカウントなどのUID情報)
* service(OSが管理している初期起動サービス候補)

等様々なものがある。([Docs: Type Reference](https://docs.puppetlabs.com/references/stable/type.html))

## puppet resource による現行システムのリソース定義の確認

puppetがインストールされたサーバでは、各リソースの現在の状態を`puppet resouce`コマンドで表示することが可能である。

```puppet:/etc/motdファイルのリソース定義確認
$ sudo puppet resource file /etc/motd
file { '/etc/motd':
  ensure  => 'file',
  content => '{md5}d41d8cd98f00b204e9800998ecf8427e',
  ctime   => 'Thu Jan 16 20:06:18 +0000 2014',
  group   => '0',
  mode    => '644',
  mtime   => 'Tue Jan 12 13:28:22 +0000 2010',
  owner   => '0',
  type    => 'file',
}
```

```puppet:vagrantユーザ情報のリソース定義確認
$ sudo puppet resource user vagrant
user { 'vagrant':
  ensure           => 'present',
  gid              => '500',
  groups           => ['wheel'],
  home             => '/home/vagrant',
  password         => 'masked by takeuchikzm',  
  password_max_age => '99999',
  password_min_age => '0',
  shell            => '/bin/bash',
  uid              => '500',
}
```

```puppet:crondサービスのリソース定義確認
$ sudo puppet resource service crond
service { 'crond':
  ensure => 'running',
  enable => 'true',
}
```

なお、Resource定義の各部分には以下の名称がある。

* **Resource Type**: `service`, `user`, `file` 等の`{`の前に来る部分。
* **title**: そのResourceのタイトル。全てのmanifest中で一意である必要がある。
  なおtitleがResource適用時のパラメタとして使われる場合がある。
  (たとえば、`user`Resourceのtitleは作成するユーザ名として使われる。)
* **parameter**: 定義中の`ensure`や`uid`等。Resource定義の反映時に必要となる各種情報を渡すのに使われる。Resource毎に必須のparameterとそうでないものがある。
* **value**: Resource定義中の`=>`から`,`の間の値。`=>`の左辺のparameterに設定される。

特にResource TypeとParameterで同じ名前のものがあることが多いので正しく言い訳られるようになっておくと良い。

## puppet apply によるResource定義の反映

前節では現在のシステムのResource定義を表示したが、逆にあるべきResource定義を記述し、`puppet apply` コマンドを実行することでシステムの状態を変化させることができる。

例として、testuserというuser Resource定義を作成し、適用してみる。
まず、以下のようなtestuserのリソース定義もつファイルを作成する。

```puppet:testuser.pp 
user { 'testuser':
  ensure           => 'present',
  gid              => 'puppet',
  groups           => ['wheel'],
  home             => '/home/testuser',
  password         => '!!',  
  password_max_age => '99999',
  password_min_age => '0',
  shell            => '/bin/bash',
  uid              => '1500',
  managehome       => true,
}
```

(なお、Resource定義を書いたファイルのことをPuppetでは**マニフェスト**と呼ぶ。)

次に、`puppet apply`コマンドによりマニフェストをシステムに状態を反映する。


```bash:リソース定義の反映と確認
$ sudo puppet apply testuser.pp
Notice: Compiled catalog for vagrant-centos65 in environment production in 0.61 seconds
Notice: /Stage[main]/Main/User[testuser]/ensure: created
Notice: Finished catalog run in 0.22 seconds
```

これでtestuserがpuppetにより作成された。idコマンドで確認してみるとちゃんとユーザができていることが確認できる。

```
$ id testuser
uid=1500(testuser) gid=52(puppet) groups=52(puppet),10(wheel)
```

### Resourceのもっと細かい話

さて、ここで反映用のResource定義中に`mangehome   => true,`という見慣れない行があることに気がついただろうか。

実は、`puppet resource`が出力以外に様々なパラメータを指定して、Resource定義をシステムに反映する際の挙動を調整することが可能である。
（むしろ、`puppet resource`定義の出力はあくまで参考程度としたほうが良い。）

この`managehome`は`user` Resource特有のparameterであり、ユーザ作成時やユーザ削除時にそのユーザのホームディレクトリの作成や削除をすべきかどうかを制御するものである。

puppetのResource Typeについての詳細は、`puppet doc`コマンドもしくはPuppet公式の[Docs: Type Reference](https://docs.puppetlabs.com/references/stable/type.html)で詳細を確認できる。

## システムとはすなわちResourceの集合である

勘の良い方は気づいたと思うが、Puppetによるシステムの構成管理とは前節のようなResource定義を多数持つマニフェストを記述し、システム全体の**あるべき状態**を定義することである。
システム全体の**あるべき状態**を定義することである。

もちろん、Resourceをひたすら1つのファイルに列記するのではあんまりなので、それらを意味的にmoduleやClass等に分割して管理する事がPuppetでは可能である。

# Ordering - Resourceの適用順序

Resourceをひたすら記述していく前に、Puppetの思想とResourceの適用順序について述べておく必要がある。

Puppetとは**あるべき状態**を記述するものであるため、マニフェスト中の複数のResourceがどのような順序で適用されるかは基本的には不定である。

構成管理をしていると、

* 全てのパッケージインストールを実行するまえに、yumのリポジトリ設定をしたい。
* `httpd`パッケージをインストールする前に、`nobody`ユーザを特定のUIDで作っておきたい。
* `/etc/httpd/conf/httpd.conf`を編集したら、`httpd`サービスを再起動したい。

のように順序関係を定義したいことが良く出てくる。
このような場合、Puppetではリソースの適用順序を明確に記述する必要がある。

Puppetを使い始めた頃は「何でよ？書いた順番に適用してよ」と思ったのだが、もし書いたとおりの順番で適用が進むとしたら、それは暗黙に順序関係を定義しているに過ぎないのだ、と最近は思えるようになった。

むしろ、書かれたとおりに実行していたのでタマタマうまく行っていたということが無くなるため、順序の意図が明確に記述されることになりマニフェストの設計意図が汲み易くなるのだ。

## Resource参照による順序関係の定義

Resource定義間の順序関係は`before`,`require`パラメータで指定できる。

見るは言うより易し。

```puppet:requireパラメータによる順序の指定
group { 'testgroup':
  ensure           => 'present',
  gid              => '1500',
}
user { 'testuser':
  ensure           => 'present',
  gid              => 'testgroup',
  home             => '/home/testuser',
  shell            => '/bin/bash',
  uid              => '1500',
  require          => Group['testgroup']
}
```

上記のように指定するとtestgroupのResourceがtestuserのResourceより先に適用されるようになる。

```puppet:beforeパラメータによる順序の指定
group { 'testgroup':
  ensure           => 'present',
  gid              => '1500',
  before           => User['testuser'],
}
user { 'testuser':
  ensure           => 'present',
  gid              => 'testgroup',
  home             => '/home/testuser',
  shell            => '/bin/bash',
  uid              => '1500',
}
```

この書き方でも意味は同じである。
なお、複数のResourceに対して順序関係をつけたいときは以下のように書く。

```puppet:複数Resourceへの順序関係
  before           => [User['testuser'],File['testfile']]
```

なお、Resource参照Rは先頭を大文字にしたResourceType名に続く四角カッコ内にtitleを入れることで行う。(ex, `User['testuser']`、`File['testfile']`)

## arrowオペレーター

Resource定義の外部でarrowオペレータを使って順序関係を定義することもできる。

```puppet:arrowオペレータによる順序関係の定義
Group['testgroup'] -> User['testuser']
```
arrowオペレータを使うと、Resourceの順序関係を各Resource定義とは別のファイルに記述することができるため、複数のファイルに分かれたResourceの定義を後から調整する際などの威力を発揮する。

また、arrowオペレータを使う[Resource collecter](https://docs.puppetlabs.com/puppet/latest/reference/lang_collectors.html)という特殊記法を使うことで、複数のResource間の関係を効率的に記述することも可能である。

## Resourceのリフレッシュ動作の実行

「`/etc/httpd/conf/httpd.conf`を編集したら、`httpd`サービスを再起動したい。」という例を先に述べたが、リフレッシュ動作をすることが可能な以下のようなResourceTypeが存在する。

* **service**: 各種デーモンなどの起動サービスの状態を管理
* **exec**: 任意のコマンドを実行
* **mount**: ファイルシステムのマウント状態を管理

これらのサービスは`notify`や`subscribe`パラメータを用いることで別のResourceに変化が発生したらリフレッシュ動作をさせるように関係を持たせることができる。

```puppet:subscribeによるリフレッシュ動作関係の登録
service { 'httpd':
  ensure    => 'running',
  enable    => 'true',
  subscribe => File['/etc/httpd/conf/httpd.conf']
}
file { '/etc/httpd/conf/httpd.conf':
  ensure    => 'file',
  mode      => '644',
  owner     => 'root',
  group     => 'root',
}
```

上記のようにしておくと、もし`/etc/httpd/conf/httpd.conf`の`mode`、`owner`、`group`に変更が起きるとhttpdサービスが再起動されるようになる。

## 自動的な適用順序付け

ある程度暗黙的に適用順序が自動的に付与されるパターンが存在する。

たとえば、入れ子関係にあるディレクトリ/ファイルのfile Resourceが存在する場合、必ず上側のディレクトリのfile Resourceが先に適用される。

自動的な順序付けはfile Resourceの例の以外にもあるらしいが、ユーザが細かく意識しないとならないような複雑だったり副作用があるようなもの無いらしい。

### どうしてもマニフェスト記述順序どおりに実行したい人は..

`ordering = manifest`とPuppet設定ファイル`puppet.conf`に記述すると、明確に順序関係を書いていない部分は必ずマニフェスト記述順に実行される。

# Class - 複数Resourceをまとめて管理

Puppetでは、Resourceを複数まとめてClassとして管理ができる。

たとえば、先に書いたGroup['testgroup']とUser['testuser']リソースをClass['testaccount']にまとめて呼び出す際の記法は以下となる。

```puppet:ClassによるResourceのまとめ上げ
class testaccount {
  group { 'testgroup':
    ensure           => 'present',
    gid              => '1500',
    before           => User['testuser'],
  }
  user { 'testuser':
    ensure           => 'present',
    gid              => 'testgroup',
    home             => '/home/testuser',
    shell            => '/bin/bash',
    uid              => '1500',
  }
}
include testaccount
```

一点、class定義をするだけでは、Class内のResource定義は有効とならない点に注意が必要である。
`include testaccout`もしくは`class { 'testaccout': }`と記述してClassを**宣言(declare)**して始めてClassに書かれたResource定義が適用される。

今後、**定義**と**宣言**を明確に区別して書くので覚えよう。

## Parameterized Class

Class定義を変数を取るように設計し、Classの宣言時にその変数を渡すことでClassの振る舞いを変更させることができる。

このようにすることで、Class定義時に決定できない部分をClass宣言時に渡すことができるようになるため、Classの再利用性を高めることができる。

```puppet:ParameterizedClassの利用(testdirectory.pp)
class testdirectory (
  $testdir = '/tmp/testdir',
){
  file { $testdir:
    ensure => directory,
    mode   => '755',
    owner  => 'root',
    group  => 'root',
  }
}
class { 'testdirectory':
  testdir => '/tmp/testdir2',
}
```

上記の例では、`$testdir`という変数(デフォルト値は`/tmp/testdir`)を取れるように`testdirectory` Classを設計している。
この例では宣言時に`$testdir`を設定することで、`/tmp/testdir2`というディレクトリを作成している。
もし、デフォルト値をそのまま使うのであれば、`include testdirectory`と書くこともできる。

## ClassとDefined Type

ここで、先ほどのParameterized Classの利用例の最後に以下を足して実行してみよう。

```puppet:testdirectory.pp追加分
class { 'testdirectory':
  testdir => '/tmp/testdir3',
}
```

こうすれば、/tmp/testdir2と/tmp/testdir3が両方作れるだろうか?残念ながらそうはならず、以下のようなエラーが帰るはずである。

```bash::
$ sudo puppet apply testdirectory.pp
Error: Duplicate declaration: Class[Testdirectory] is already declared in file testdirectory,pp:13; cannot redeclare at /tmp/testdirectory,pp:16 on node vagrant-centos65
```

これは、Class Resourceのtitleである`testdirectory`がマニフェスト中で重複してしまったためである。
Classはそもそもシステムで重複させたくないようなリソース定義をまとめる際に使うものなので、これはPuppetが間違えて重複して宣言されてしまったクラス宣言を検出してくれたとも言える。

同一マニフェストに何度も登場する似たようなResourceをまとめたい場合は、ClassではなくDefined Typeを使ってResourceをまとめるのがPuppe的に正しい方法である。

```puppet:define定義とその利用(/tmp/definedirectory.pp)
define definedirectory (
  $testdir = '/tmp/testdir',
){
  file { $testdir:
    ensure => directory,
    mode   => '755',
    owner  => 'root',
    group  => 'root',
  }
}
definedirectory { 'define_testdir2':
  testdir => '/tmp/testdir2',
}
definedirectory { 'define_testdir3':
  testdir => '/tmp/testdir3',
}
```

Defined TypeもClassと同様に定義しただけではResourceは有効にならず、宣言をして始めて有効になる。
Defined Typeの定義はClassとほぼ同じであるが、宣言の仕方が若干違う点に注意が必要である。

また、Defined Type宣言および定義内のResourceのtitleはマニフェスト中で一意でないとエラーとなる。

# Node - サーバ単位でResource定義をまとめる

ここまでの例では、puppet apply に渡したマニフェストの中身は全て実行されていた。
マニフェスト中にnode定義をすると、node定義にマッチしたホストのみしか実行しないResourceを定義することができる。

ホスト名`vagrant-centos65`用の設定とそれ以外のホストのものを分けて書く例を以下に記述する。

```puppet:node単位でのResource定義のまとめこみ(nodes.pp)
class testdirectory (
  $testdir = '/tmp/testdir',
){
  file { $testdir:
    ensure => directory,
    mode   => '755',
    owner  => 'root',
    group  => 'root',
  }
}

node 'vagrant-centos65' {  
# このシングルクォートの中に自ホスト名を入れる
 class { 'testdirectory':
   testdir => '/tmp/testdir_mynode',
 }
}

node default {
# クォート無しのdefaultは予約語でこれ以外のnode定義に該当しない全てのnodeに対応する。
 class { 'testdirectory':
   testdir => '/tmp/testdir_othernode',
 }
}
```

ここで、`vagrant-centos65` NodeとデフォルトNodeで`testdirectory` Classが重複しているが、node定義の`vagrant-centos65`側のみしかマニフェストが読み込まれないため、先ほどのようなエラーは起きない。

node定義でのホスト名部分には正規表現を使うこともできる。なお、複数のnode定義がホスト名に適合した場合、以下の上位から優先される。
(ホスト名のFQDNが`www01.example.com`と仮定。)

* 正規表現ではなく、FQDNで一致するnode定義(ex. `node 'www01.example.com' {`)
* 正規表現で、FQDNで一致するnode定義(ex. `node /^www01.example.com$/ {`)
* 正規表現ではなく、FQDNの先頭側のサブドメインに一致するnode定義(ex. `node 'www01.example' {`)
* 正規表現で、FQDNの先頭側のサブドメインに一致するnode定義(ex. `node /^www01.example$/ {`)
* 正規表現ではなく、hostnameが一致するnode定義(ex. `node 'www01' {`)
* 正規表現で、hostnameが一致するnode定義(ex. `node /^www01$/ {`)
* クォート無しの`default` node定義(ex. `node default {`)

詳しくは[PuppetLabs - Docs: Language: Node Definitions](https://docs.puppetlabs.com/puppet/latest/reference/lang_node_definitions.html)を見ると良い。

# Puppetの動作モード

Puppetには、

* 構成管理をマスターサーバが一元的に管理するMaster/Agentモード
* ローカルのみで動作させるApplyモード

が存在する。

まずは、Puppetの基本的な使い方やコンセプトの理解を目的とするため、利用コストの低いApplyモードを前提とする。
(Applyモードでは、マスターサーバに全Agentの情報を集約して設定を決定するタイプのいくつかの機能が動作しない。)

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

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

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