Puppet
PuppetDay 1

CheferのためのPuppet

More than 3 years have passed since last update.

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

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で頑張るのが良さそう



---

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


基本的なリソース(Typeとも呼ばれる)


user/grpup

group { 'app':

gid => 501,
ensure => present;
}

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


package

package { 'dnsmasq':

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

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


file

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

service { 'nginx':

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


exec

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 に書いておくパターンが多い。

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で戸惑うぞ...)

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


アロー記法

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

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


require 宣言(クラス間)

class nginx::mysite {

require nginx::install
file { '/etc/nginx/conf.d/ore.conf': .... }
}

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


リソースに直書き

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時のコマンドを置き換える。リスタートとリロードを同じリソースでは行えなくなるが、現実的に必要ある場面ってないので...

service { 'consul':

restart => 'systemctl reload consul'
}


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

たとえばdnsmasqなら:

$ 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/ を眺めよう。


最後に

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


明日のアドカレは...

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