Skip to main content
Puppetのススメhbstudy#8(株)paperboy&co. 宮下 剛輔2010/02/23
自己紹介宮下 剛輔mizzy, id:MIZZYgosukenator(twitter, gmail)http://mizzy.org/ペパボで技術責任者というのをやってますインフラからウェブ開発まで広く見てます子だくさん2009/11/24に4人目が生まれました
自己紹介最近はまってることWiiFitPlusで筋トレホームベーカリーでパンを焼くアサシンクリード2 シーケンス13プレイ中ホットドクターペッパー牛乳
Puppetのススメ
アジェンダPuppetの概要Puppetの動作概要マニフェストの基礎Puppetを動かしてみるPuppetの使いどころPuppetを利用する上で知っておくべきことPuppetの微妙なところ
Puppetの概要
Puppet情報リソースhttp://reductivelabs.com/products/puppet/http://gihyo.jp/admin/serial/01/puppethttp://trac.mizzy.org/puppetSoftware Design 2007年12月号Software Design 総集編 【2000~2009】
Puppetとは?Ruby製のシステム管理ツールでも、使う上ではRubyかどうかはあまり関係ないFacter(後述)の拡張時にはRubyの知識必要
システム管理ツール?Puppetはサーバの状態を定義し、維持するためのツール以下はPuppetの対象外ハードウェアの設置ネットワーク接続OSインストール/初期設定ネットワークの設定Puppet自身のインストール
Puppetの対象範囲OSインストール/ネットワーク設定/Puppetインストール後のフェーズがPuppetの対象範囲パッケージインストール/設定ユーザ追加cron設定パッケージアップデートなどなど
なぜPuppetが必要か?手動作業に伴う問題手作業は時間がかかる手を動かしてる時間はもちろん待ち時間も大幅に効率下げる要因並行して別作業するのも大変あるべき状態との乖離正しい手順書があっても、作業漏れ、ミスなどが出る手順書自体が間違ってたり、古かったりすることもある
なぜPuppetが必要か?他の自動化手法に伴う問題カスタムスクリプトでの自動化解読、メンテが大変自由度が高すぎて、書く人のスキルの差によるばらつきが大きいHDDイメージ複製、PXEブート+NFS、rsyncといった手法複数OS、複数の役割を持つサーバが混在した環境では、一括管理が難しいサーバによって異なる部分をどう吸収するか?Apache の ServerName、MySQLの server-id など
Puppet導入のメリットシステム構築/アップデートの自動化システムの状態を目に見える形で記録できるマニフェストと呼ばれる独自言語による記述システムのあるべき状態と実際の状態の乖離を防ぐマニフェストで状態を定義し、システムへ適用するマニフェストの独自言語は、スクリプト言語より自由度が低いが、その分属人的なスキルに依存しにくいOS、役割、設定の異なるサーバ群の集中管理ができるマニフェストはテキストファイルなので、バージョン管理ツールによるトラッキングが可能
Puppetの動作概要
Puppetのネットワーク構成puppetサーバ(puppetmasterd)実行結果レポートマニフェストの取得と実行puppetクライアント(puppetd)puppetクライアント(puppetd)puppetクライアント(puppetd)プロトコルは XMLRPC over HTTPS
Puppet用語マニフェストシステムのあるべき状態が記述されたファイルPuppetサーバで一括して維持管理するPuppetクライアントで取得し、システムに適用リソースPuppetマニフェスト内で扱うオブジェクトユーザ、ファイル、パッケージなど
Puppetの動作概要パターン1puppetサーバ(puppetmasterd)③ 適用結果をレポート① マニフェストを取りに行く(デフォルトは30分に1回)puppetクライアント(puppetd)② 取得したマニフェストを適用する
Puppetの動作概要パターン2puppetサーバ(puppetmasterd)④ 適用結果をレポート② マニフェストを取りに行く① サーバ上でpuppetrunを実行してpuppetdをキックするpuppetクライアント(puppetd)③ 取得したマニフェストを適用
マニフェストの基礎
リソースの定義リソースタイプリソースのタイトルリソースの属性file { '/etc/hosts':    owner  => $default_owner,    group  => $default_group,    mode   => 644,    source => 'puppet://server/module/hosts',}/etc/hosts を puppetサーバから取得し、オーナー、グループ、モードを適切に設定するためのリソース定義これ全体が「リソース」
クラスでリソースをまとめる「sudo」クラスを定義class sudo{package { ‘sudo’:        ensure => latest,        alias  => sudo    }    file { ‘/etc/sudoers’:        source => 'puppet://server/module/sudoers',        mode   => 440,        owner  => $default_owner,        group  => $default_group,        alias  => sudoers    }}node ‘mail.hoge.com’ { include sudo }package リソースfile リソースクラスをノードに適用
クラスを継承するbase クラスを定義class base {     file { "/my/file":        content => template("base.erb")    }}class sub inherits base { # override the content       File["/my/file"] {content => template("other.erb")    }} base クラスを継承した sub クラスを定義base クラスの /my/file リソースの属性を上書き
リソースの依存関係file { '/etc/ssh':    source  => puppet://server/module/ssh,recurse => true,notify  => Service[ssh]}service { 'ssh':    name      => sshd,    ensure    => running,subscribe => File[‘/etc/ssh’]}/etc/ssh以下のファイルに変更があれば、sshサービスリソースに再起動を促す/etc/sshリソースに変更があれば、サービスを再起動するnotifyかsubscribeのどちらか一方だけ定義すればOK
Puppetを動かしてみる
インストール詳細は略。gihyo.jp 見てください。(といっても、古いのでそのまま利用できないかもですが。)ちなみに、CentOSだとEPELリポジトリに割と新しめのパッケージがあります。依存するパッケージもそれほど多くありません。(augeas-libs, facter, ruby, ruby-augeas, ruby-libs, ruby-shadow)
デフォルトの設定ファイルを削除基本的に、設定は最低限必要なところだけすれば良いので、デフォルトの設定ファイル群は消してしまうのが吉自分がタッチしてない余分なファイルがあると、その挙動に悩むこともあるしというわけで、インストールしたらおもむろに rm –rf /etc/puppet/* してしまう
マニフェストを書く簡単なマニフェストを書いてみるまずはディレクトリ作成# mkdir /etc/puppet/manifets
マニフェストを書く/etc/puppet/manifests/site.ppfile{ '/etc/passwd':    owner => 'root',    group => 'root',    mode  => 644,}
マニフェストを適用(スタンドアロン版)# puppet /etc/puppet/manifests/site.ppnotice: //File[/etc/passwd]/mode: mode changed '777' to '644'
マニフェスト適用(クライアント/サーバ版)サーバ起動# /usr/sbin/puppetmasterd --verbose \  --no-daemonize --nonodesinfo: Starting server for Puppet version 0.24.8info: Listening on port 8140notice: Starting Puppet server version 0.24.8
マニフェストを適用(クライアント/サーバ版)クライアントで適用# /usr/sbin/puppetd --server puppet.hoge.com \--no-daemonize --verbose --onetimeinfo: No classes to storeinfo: Caching catalog at /var/puppet/state/localconfig.yamlnotice: Starting catalog runnotice: //File[/etc/passwd]/mode: mode changed '777' to '644'notice: Finished catalog run in 0.03 seconds
以上簡単ですよね?サーバとクライアントが別の場合は、証明書の署名が手順に入ります/etc/puppet/autosign.confを設定すれば、署名手順は自動化できます実運用では、デーモンとして常駐させたり、puppetrunでクライアントをキックしたりします
Puppetの使いどころ
Puppetの使いどころ大量にサーバがある異なるOSや環境のサーバが混在台数少なくても、同じような手順を何度も実行する可能性がある開発環境、検証環境等の、ベースになる部分自分以外の人も同じ手順を実行するかもしれないシステムの状態を何らかの形で記録しておきたい、バージョン管理したいインクリメンタル/テスト駆動サーバ構築
バージョン管理ツールによるトラッキングなぜこんな設定してるんだろう?という瞬間ありませんか?こんな時でも、バージョン管理ツールで誰がどんな意図で設定、あるいは修正したのかが記録されていると安心ですデプロイツールと組み合わせて、サーバのアップデートもできます(ペパボではArcherつかってます)ただし、適切なバージョン管理ツールの利用が必要です。特にコメントをしっかり残す、という習慣が大事システム管理者にバージョン管理ツールを根付かせるのは難しい?
インクリメンタル/テスト駆動サーバ構築テスト環境では、Puppetマニフェストを少しづつ書き足し、修正、テストしながら、構築すべき内容を詰めていくPuppet化しておくと、一からつくりなおすのも簡単マニフェストは、テスト環境、本番環境どちらでも使えるように作成。環境の違いはFacter変数などで吸収本番環境では、テスト環境で作り上げたマニフェストを適用するだけテスト環境できっちりテストしておけば、本番でも大丈夫と自信が持てる
Puppetを利用する上で知っておくべきこと
Puppetのコンセプトに馴染むマニフェストでは、リソースとリソース間の関係を記述する静的な状態を記述するPuppetでどうシステムを管理するか、を考える場合、リソースとリソースの関係にブレイクダウンすることシステム上で何を実行するのか、ではなく、どういう状態にするのか、を考える
リソースとその関係へのブレイクダウン例えばApacheの場合、以下のようなリソースへとブレイクダウンし、各リソースとその関係を記述するパッケージサービス設定ファイル群バーチャルホストApacheモジュール
Apacheマニフェストpackage { ‘httpd’: ensure => installed }service { ‘httpd’:   enable => true,   ensure => running,   require => Package[‘httpd’],}
Apacheマニフェスト(つづき)file {   ‘/etc/httpd/conf.d/hoge.conf’:       source => “puppet://$server/hoge.conf”,       notify  => Service[‘httpd’];‘/etc/httpd/conf.d/fuga.conf’:        source  => “puppet://$server/fuga.conf”,       notify   => Service[‘httpd’];}
Apacheマニフェスト(つづき)defineapache_module() {   ....}apache_module { ‘rewrite’:config => “puppet://$server/rewrite.conf”,}
Apacheマニフェスト(つづき)definevirtual_host() {    ...}virtual_host { ‘www.example.jp’:docroot => ‘/var/www/html’ ,}
マニフェストは宣言型言語マニフェストは設定ではなく、言語である手続型言語ではなく宣言型言語「何を実行するか」ではなく最終的な「状態」を定義する通常のシステム管理タスクは手続型なので、発想の転換が必要
手続型と宣言型手続型OpenSSHをインストールする設定ファイルを修正するサービスを起動する宣言型OpenSSHがインストールされた状態にする設定ファイルの内容を決められた通りにするサービスが起動した状態とする
手続型と宣言型同じことをしているように見えるが、実は異なる手続型はプロセスを記述する同じことを何度も繰り返すと、違った状態になることがあり得る宣言型は最終的な状態を記述するマニフェストを何度適用しても、最終的な状態は同じ
defined typedefineは関数/機能を定義しているように見えるけど、実際はリソースタイプを定義しているので注意definevirtual_host() {    ...}virtual_host { ‘www.example.jp’:docroot => ‘/var/www/html’ ,}
クラスと継承マニフェストにはクラスと継承があるけど、オブジェクト指向言語として十分な機能を備えてるわけではないクラスはあくまでもリソースのコレクションにすぎない
以上はPuppetBest Practices 3.0からhttp://plathrop.tertiusfamily.net/puppet/best-practice-draft.html
モジュールの活用モジュールは、よく利用するマニフェストを汎用的に利用できるようにまとめたものよくある処理や、複雑な処理はモジュールとしてまとめるとすっきりでも、本当に汎用的なモジュールを作成するのは難しい…コピペになりがちモジュールをうまく共有する仕組みがないのもイマイチでも、できるだけ早い段階から活用した方がいい汎用的とか共有とか考慮しなくても、メンテナンス性は向上する
iptablesモジュールの利用例include'iptables'file {'/etc/iptables.d/filter':        content => template('filter'),        notify  => Exec["rebuild_iptables"];'/etc/iptables.d/nat':        content => template('nat'),        notify  => Exec["rebuild_iptables"];}
iptablesモジュールでやってることiptablesパッケージのインストール必要なファイルの配布全サーバ共通ルールファイルrebuild用スクリプトルールファイルをがっちゃんこして /etc/sysconfig/iptables に書き込むサーバ個別ルールファイルの配布ルールファイルが変更された場合にrebuildスクリプト実行
モジュール公開してるところhttp://reductivelabs.com/trac/puppet/wiki/PuppetModules外部へのリンクもありhttp://github.com/n0ts/puppet
エラー通知Puppetにはエラーだけ通知する仕組みがない?tagmailは特定のクラスに関するログの通知先アドレスを指定できるが、エラーのみ通知、ということができないという話をクックパッドテックライフLTで話したけど、実は0.24.6からは、tagでログレベルも指定できるようになってたことに気づいた/etc/puppet/tagmail.conferr: error@example.jp
syslogでエラー通知を分離puppet.conf[puppetd]syslogfacility = local0syslog.conflocal0.err \/var/log/puppet/error.log
ノード情報の管理シンプルなやりかたは、ノード情報をマニフェストに記述するnode‘www.hoge.com’ {include‘base’include‘www’}
ノード情報の管理ExternalNode機能を使えば、既存で持ってるノード情報を使いまわせる具体的には、ノード名を与えると、既存のノード情報を変換してYAML形式で出力するようなスクリプトを用意して、puppetに設定する
External Nodeの設定例/etc/puppet/puppet.confでの設定[puppetmasterd]external_nodes = \ /usr/bin/cobbler-ext-nodesnode_terminus  = exec
cobbler-ext-nodesの実行結果$ cobbler-ext-nodes web-proxy.example.jpclasses: [base, web-proxy]parameters: {from_cobbler: 1}
Cobblerでノード情報管理最近はCobblerを利用しているので、こいつのノード情報をマスターとしてますcobbler-ext-nodesコマンドが付属してるので、Puppetと即連携可Cobblerはdnsmasqと連携できるので、DNS/DHCPのノード情報としても利用できる
LDAPによるノード情報管理puppetrun –all とか –class オプションの利用には、LDAPでノード情報を管理する必要があるでも、あまりお勧めしない。めんどうだけどあまりメリットない。puppetrun –all や –class 相当のことがしたければ、スクリプト組むのがよい
execは最小限にPuppetマニフェストは基本的に、最終的な「状態」を定義するものなので、何度適用しても、最終的な状態は同じになるただしexecリソースは例外で、何を実行するかを定義する副作用のあるコマンドを実行すると、何度実行しても同じ結果になるという保証がないので危険execを濫用するとマニフェストのメンテもしづらくなるPuppetの考え方に馴染んでない人は、何でもexecで実行してしまおうとするので注意
execを濫用したマニフェストexec { 'sysctl add':command => 'echo "net.ipv4.netfilter.ip_conntrack_max = 131072" >> /etc/sysctl.conf \    && /sbin/sysctl -p',onlyif => "test -f /etc/sysctl.conf",unless => "grep 'net.ipv4.netfilter.ip_conntrack_max' /etc/sysctl.conf 2>/dev/null"}
execを適切に利用したマニフェストfile {'/etc/sysctl.conf':ensure  => present,source  => "puppet://$server/sysctl.conf",}exec { '/sbin/sysctl -p':subscribe   => File['/etc/sysctl.conf'],refreshonly=> true,}
Facterの活用FacterというライブラリをPuppetは利用しているシステムに関する情報(プロセッサアーキテクチャ,利用OSとそのバージョン,ドメイン名,FQDN,IPアドレスなど)を収集するための,クロスプラットフォームなRubyライブラリFacterによってFacter変数が定義される
Facter変数$ facterarchitecture => x86_64domain => hoge.comfacterversion => 1.5.7fqdn => puppet.hoge.comhardwareisa => x86_64hardwaremodel => x86_64hostname => h026id => miyainterfaces => eth0,eth1,sit0ipaddress => 192.168.10.26ipaddress_eth0 => 192.168.10.26ipaddress_eth1 => 192.168.122.106is_virtual => false...
Facter変数を利用したマニフェスト例$ntp_package= $operatingsystem? { freebsd => 'openntpd',   default => 'ntp',}package { $ntp_package: ensure => present }
カスタムFactFacter.add(:serverid) dosetcodedo      /\d+$/.match(Facter.hostname)endend
カスタムFactを利用したテンプレート/etc/my.cnfのテンプレ[mysqld]server-id=<%= serverid %>
0.25.xの利用推奨いろいろなところで正規表現使えるようになってるので、0.25.xの利用がお勧めです
Puppetの微妙なところ
変数のスコープとオーバーライドこの関係がわかりづらく、仕様がいまいち意図通りのオーバーライドができない柔軟なオーバーライドができない
想定通りに動かない例classbase_class {$myvar = 'bob'file {"/tmp/testvar":         content => "$myvar",    }}classchild_classinheritsbase_class {$myvar= 'fred'}
想定通りにするには$myvar= ‘bob‘classbase_class {    file {"/tmp/testvar":         content => "$myvar",    }}classchild_class {$myvar= 'fred‘includebase_class}ただし、$myvarがグローバルになる
想定通りにするには(別パターン)classbase_class {$myvar = 'bob'file {"/tmp/testvar":         content => "$myvar",    }}classchild_classinheritsbase_class {$myvar= 'fred‘File[‘/tmp/testvar’]{       content => "$myvar“    }}ただし、記述が冗長
といった感じで…変数がグローバルになる、記述が冗長になるなど、いずれにしてもいまいち
理想的な例classfile {$var= ‘hoge’file {       ‘/tmp/file’: content => $var}}classfile_fugainherits file {$var= ‘fuga’}
もうひとつの理想的な例継承ではなく委譲classfile {$var= ‘hoge’file { ‘/tmp/file’: content => $var}}classfile_fuga {file.new( var => ‘fuga’ ).apply}
理想的な例(ノードの場合)ノード定義でもこんな風にできればいいのにnode‘www.example.com’ {file.new( var => ‘fuga’ ).apply}
変数オーバーライドとモジュールモジュールも結局はクラスなので、同じ問題が起こる汎用的なモジュールつくるのが難しい一因無理やり実現する方法はあるけど、ちょっと面倒
モジュールでの変数オーバーライド詳しくはPuppetBestPractices? at Cookpadの資料参照node"sample1.example.com"{classapache_configinheritsapache_defaults {$ssl = false    }includeapache}
External/LDAPNodeと変数オーバーライド前ページのモジュール変数オーバーライド手法は、LDAPNodeやExternalNodeでは使えないExternal/LDAPNodeではクラスのincludeとパラメータ設定ぐらいしかできない
柔軟性とメンテナンス性のバランス以上のような感じで、マニフェスト言語については柔軟性不足な感があるそもそも、クラスはリソースのコレクションでしかない柔軟性が低いのは、誰が書いても同じになりやすく、メンテしやすい、というメリットはあるバランスが難しい
内部DSL実装マニフェストは外部DSL内部DSL実装も実はあるRuby DSLhttp://reductivelabs.com/trac/puppet/wiki/RubyDSLあまり使われてないって書いてあるShadowPuppethttp://reductivelabs.com/trac/puppet/wiki/ShadowPuppet
ShadowPuppetclass ManifestExample< ShadowShadow::Manifest  recipe :sample  recipe :mysql, {:root_password=> 'OMGSEKRET'}defsample    exec :foo, :command => '/bin/echo "foo" > /tmp/foo.txt'    package :foo, :ensure => :installed    file '/tmp/example.txt', :ensure => :present, :contents => Facter.to_hash_inspectenddef mysql(options)  ...endend
変数オーバーライド以外のモジュールに関する問題CPAN的な仕組みがない。PRM(Puppet Recipe Manager)というのがあったけど、現在はウェブサイトは残ってるが、ソースが見当たらないPRMのウェブサイトから辿れなかったけど、 http://people.redhat.com/~alikins/prm/にはあった1年ほど更新されてない
クラス設計が難しいこれも言語の柔軟性が低いゆえ?特に、リソースの重複が悩ましい複数クラスにまたがるリソースの定義が難しい仮想リソースという仕組みはあるが、これはこれで面倒ではあるでも、柔軟性が増すと別の悩みが出てきそう
ドキュメントが読みづらくなった例えば TypeReference現行ページ http://docs.reductivelabs.com/references/stable/type.html旧ページ http://reductivelabs.com/trac/puppet/wiki/TypeReference.
現行ページ
旧ページ
リソースタイプに微妙なものがリソースタイプには、file, user, group, package, service, cron といったものがある上の例は、割りとOSに普遍的なものだけど、中にはnagios_command, nagios_contact, nagios_hostといった、Nagios固有のものがある。(Nagios固有のタイプで14もあり、リファレンスの中でも目立ってる)アプリケーション固有のものは、コアに入れないでモジュールにした方がいいのでは?
パッケージ利用がほぼ必須Puppetを利用する場合は、RPMなどパッケージ利用がほぼ必須execでmakeは各サーバにコンパイラとかいれないといけないし、時間もかかるPuppetでファイル配布できるけど、大量にファイルがあるとかなり遅いrsyncをPuppetで実行するのも、微妙な感じでもパッケージングめんどくさいとは言っても、パッケージングのメリットもあるソースRPMつかってれば、異なるディストリビューション/アーキテクチャでもパッケージリビルドするだけでOK
--noopのdiffが読みづらいpuppetdに—noopオプションをつけると、ファイルリの内容が変更された場合、実際にファイルは書き変えないけど、diff出力してくれるだけど、ノーマルな形式のdiffで、ユニファイド形式じゃないので、読みづらい
ロールバックできない「トランザクショナルレイヤー」というのがドキュメントを見るとあるんだけど、ロールバックはできない
puppetrunの微妙なところLDAPNodeつかわないと、--allや--classが使えない
サーバ間の依存関係が記述できないリソース間の依存関係は記述できるこのサービスデーモンはこの設定ファイルに依存する、などサーバ間の依存関係は記述できないこのサーバは、データベースサーバがセットアップされてからマニフェストを適用する、といったことができない
パッケージ削除が微妙パッケージの追加は、依存関係勝手に解決してくれる(これはPuppetではなく、利用しているパッケージプロバイダ依存)パッケージ削除は解決してくれないRedHat系の場合、追加はyumコマンドでやるけど、削除はrpmコマンドでやってるっぽい他のパッケージシステムは不明余計なパッケージを最初から入れないのが吉
とはいうものの色々微妙な点を挙げてみたけど、Puppet以上にシンプルで使いやすいものはなさそうというわけで、当分はPuppet使い続けることになりそうだけどChefの今後の動きにも要注目内部DSLなので言語の自由度は高そう