CheferのためのPuppet

[Puppetアドベントキャレンダー](http://qiita.com/advent-calendar/2015/puppet) の1日目です。1日目ですってば。

初回ということで、社の「Chefは慣れてるんですけど〜〜」という若者にPuppetを速習していただくための、雑ですが割と頑張ったチュートリアルを公開したいと思います。みんなもHieraになろう！

## 基本概念の対応

### ファイル設定を記述する基本となるファイル

* Chef => レシピ
* Puppet => マニフェスト

### 設定のひとまとまり、特にミドルウェア単位とかでまとまっているやつ

* Chef => クックブック
* Puppet => モジュール/クラス（大体同じものを違う見方で指す。Puppet内部で使う側から見るとクラスで、クラスを構築するファイル全体をモジュールと呼んでいる印象）

Puppetには明示的なロールの概念はなく、各クラスをまとめたクラスを便宜的にロールと呼ぶことが多い。

### 実行形式

* Chef
  * クライアント実行 => chef-solo
  * サーバエージェント型 => Chef server/client
* Puppet
  * クライアント実行 => `puppet apply`
  * サーバエージェント型 => `puppet agent`

### ロードパス

* Chef => 規約で決まる
* Puppet => puppet apply 時は `--modulepath` というオプションで指定。puppetmaster(server)には設定ファイルがある
  * バージョン4からはEnvironment Directoryの規約 + Global Moduleで指定。詳細はきっとアドカレの後日で。。。

`puppet apply --modulepath ./modules:./roles:./vendor/modules manifests/site.pp`

この構成、コマンドが2014年ぐらいまでの定番だった...。まだちょっとだけ続くのじゃ

### コミュニティモジュールの利用

* Chef => Berkshelf（すごく昔はlibrarian-chef）
* Puppet => librarian-puppet

### サーバのデータ収集

* Chef => ohai
* Puppet => facter

```
[cloud-user@imageproxy001 ~]$ facter hostname
imageproxy001
[cloud-user@imageproxy001 ~]$ facter fqdn
imageproxy001.node.minne.lan
```

PuppetのfacterもRubyで拡張を書ける。

### LWRP

* Chef => LWRP
* Puppet => Defined Types

```puppet
define consul::kvs (
  $key = $name,
  $value = '',
  $consul_master_host = 'localhost:8500'
) {
  exec { "curl -X PUT -d '${value}' http://${consul_master_host}/v1/kv${key}":
    unless => "test \"$(curl http://${consul_master_host}/v1/kv${key}?raw)\" = '${value}'",
  }
}

consul::kvs {
  'Foo':
  	value => "bar bar";
}
```

って書くとConsulのkvsのレコードが登録される、しかもべき等に、みたいな。

### ホストごとに違う属性値

* Chef => nodes/roles/environmentsのJSONに書くattributes
* Puppet => Hieraがおすすめ。以下のようにenvironment別に。
  * environmentで吸収できない要素はfacterで頑張るのが良さそう

```yaml
---
:backends: yaml
:yaml:
  :datadir: "/var/lib/puppet/data/hiera/%{::environment}"
:hierarchy:
  - secret
  - common
:logger: console
```

## 基本的なリソース（Typeとも呼ばれる）

### user/grpup

```puppet
group { 'app':
  gid    => 501,
  ensure => present;
}

user { 'app':
  uid        => 501,
  gid        => 501,
  managehome => true,
  ensure     => present,
  require    => Group['app'],
}
```

### package

```puppet
package { 'dnsmasq':
  ensure => '2.66-14.el7_1',
  # installedでは行っているかどうか/latestで常に最新
}

package {
  [
    'gcc',
    'gcc-c++',
    'make',
    'patch',
  ]:
  	ensure => installed;
}
```

### file

```puppet
file { '/etc/mackerel-agent/conf.d/roles.conf':
  ensure => present
  # present かつ content/source を明示する時は普通ensureは省略する。
  # そのほかよく使うもの:
  #   ensure => anbsent 無いのを保証する場合
  #   ensure => directory ディレクトリ。Puppetは親から順に明示的に定義
  #   ensure => link Symlink
  # テンプレートを使う時(ERB)。テキストの場合、動的な箇所がなくても推奨
  # テンプレートでは、そのクラスのスコープの $foo を @foo で参照。
  content => template('mackerel/etc/mackerel-agent/conf.d/roles_template.conf'),
  # ファイルをじかに使う時
  source => 'puppet:///modules/mackerel/etc/mackerel-agent/conf.d/roles_template.conf',
  mode    => '0640', # default 0644
  owner   => 'root', # default root
  notify  => Service['mackerel-agent'],
}
```

`template()`/`puppet:///` はファイルの解決の仕方が独特なので注意。

* `template("${モジュール名}/${templates/以下の相対パス}")`
* `puppet:///modules/${モジュール名}/${files/以下の相対パス}`

### service

```puppet
service { 'nginx':
  ensure => running, # 止めたい時stopped
  enable => true, # 起動時無効はfalse
  provider => 'systemd' # Ubuntu 15.04とかでは必要...
  restart => 'systemctl reload nginx' # そのほか、startのコマンドを変えたい時は start => '...' など
}
```

### exec

```puppet
exec {
 "expand ruby-${v}":
 	# command省略時はタイトルがそのままコマンドになる、たいてい長いので明示する...
    command => "rbenv exec gem install bundler && rbenv rehash",
    # 環境変数は配列
    environment => ["RBENV_ROOT=/usr/local/rbenv", "RBENV_VERSION=${v}"],
    path => '/usr/local/rbenv/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin', # 配列も可
    # これは後述する、フックでだけ実行したいようなコマンドに使う
    refreshonly => true,
}
```

pathは明示しないといけない。しかし面倒。
以下のように、「配下のクラスでこのリソースでは全てこのオプションが有効になる」デフォルト値の記法がある。それを最初に読む `manifests/site.pp` に書いておくパターンが多い。

```puppet
Exec {
  path => ['/bin', '/sbin', '/usr/bin', '/usr/sbin', '/usr/local/bin', '/usr/local/sbin'],
}
```

### ドキュメントへのポインタ

詳細は、 https://docs.puppetlabs.com/references/latest/type.html を眺めるのが一番わかりやすい。困ったらまずこのページを眺めよう。

## これどうやるの一覧

### ノードの判定

* `node 'hostname' { include ::role ... }` で行う。
* 大規模になったら、特定のスクリプトの実行結果から使うクラスを判定可能（enc/External Node Classifiers）

### 特定のファイル更新でサービスを再起動

依存関係はPuppetのキモなので、幾つか記法がある。

* ファイルの属性で、 `notify => Service['nginx']` のようにやる
* ぐにゃぐにゃアロー記法（？

```
File['/etc/nginx/nginx.conf'] ~> Service['nginx']
```

### AをインストールしてからBをやってくれ

* 重要なことなので何度かいうが、 **Puppetは、上から適用するとは限らなくて、実行時に依存関係をコンパイルして決める** （そもそも上とは？という感じなのでPuppetに慣れるとChefやAnsibleで戸惑うぞ...）

なので幾つか記法をサポートしている。

#### アロー記法

```puppet
Package['nginx'] -> File['/etc/nginx/conf.d/ore.conf']
```

`init.pp` でこれを書くやり方が多い。

#### require 宣言（クラス間）

```puppet
class nginx::mysite {
  require nginx::install
  file { '/etc/nginx/conf.d/ore.conf': .... }
}
```

マニフェストを書いてる時の脳みその動き（Bを作る前にまずA, Cが必要だな〜よーし）に近いので、個人的によくやる。

#### リソースに直書き

```puppet
file { '/etc/nginx/conf.d/ore.conf':
  content => ....,
  require => Package['nginx'], # Class['nginx::install']
}
```

どうしても動かない時、これをやるとうまくいくこともある。

### 特定のリソース更新の時のみ何かをフックして実行

Chefで`action :nothing`でどうこうのPuppet版

* execリソース側で `notifyonly => true`
* fileなどの側で、 `notify => Exec['foo...']` を明示

### リスタートじゃなくてリロードしてくれ

* ハッキーだけど、現状以下のように書くしかない。restart時のコマンドを置き換える。リスタートとリロードを同じリソースでは行えなくなるが、現実的に必要ある場面ってないので...

```puppet
service { 'consul':
  restart => 'systemctl reload consul'
}
```

### ミドルウェアをドカンと一つ入れたい、定番構成は？

たとえばdnsmasqなら:

```console
$ tree modules/dnsmasq                                                                               
modules/dnsmasq
├── manifests
│   ├── config.pp
│   ├── consul_cache.pp
│   ├── init.pp
│   ├── install.pp
│   └── service.pp
└── templates
    ├── 001-consul.conf
    ├── 200-api-proxy.conf
    ├── dnsmasq.conf
    └── libexec
        └── generate-consul-dns-cache.sh
## 場合により
##└── files
##    ├── hoge.tgz
##    └── usr/local/src/...
```

* `{init,install,service,config}.pp` の分割は基本パターン。
* それ以外のパターンはそれぞれいい感じの粒度を探して定義。適宜Defined Typesを作っても良い
* templates/files 配下は、ちゃんと `templates/etc/dnsmasq.d/001-consul.conf` ってするほうがいいと思う（例のように直下に置く流儀もあるが、やっぱりちゃんとディレクトリ切ったほうがいいという気持ちになっている）

### ドキュメントへのポインタ

やや変なクックブックもあるが、 http://www.puppetcookbook.com/ を眺めよう。

## 最後に

ペパボコーディング規約を一度は眺める。そうすると一緒に標準のスタイルガイドも見ることになるので、眺める。

* http://pepabo.github.io/docs/puppet/style-guide.html

----

明日のアドカレは...

~~明日の風が吹きます~~ @buty4649 パイセンが名乗り上げてくれました！やったぜ〜！