Puppet4.xではじめるサーバ設定自動化 3（パッケージ管理）


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

# パッケージインストール

## 基本

パッケージをインストールする場合、Puppetのマニフェストでpackageリソースを使います。
例としてhttpdをインストールする場合、次のようなマニフェストを書きます。裏の処理はOSによって異なりますが、yumかapt-getでのインストールになります。

```puppet:httpd.pp 
package { "httpd" :
  ensure => "installed",
}

```

packageの横に、インストールするパッケージ名。
ensureにinstalledを指定します。

## アップデートする

installedの場合、puppet実行時点でインストールされていないパッケージをインストールします。すでにインストールされている場合は、アップデートがあってもバージョンは変わりません。
アップデートがあった場合に、Puppet実行時に最新バージョンにするには、ensureをlatestにします。Puppet実行時に、**不用意にアップデートしたくない場合はinstalled、常に最新にしたい場合はlatestです。**

```puppet:httpd.pp 
package { "httpd" :
  ensure => "latest",
}

```

## バージョン指定

バージョン指定もできます。ただ、バージョンが古くなると公開が終わってインストールできなくなる場合もあるため、明確にバージョンを指定したいとき以外は使わないかと思います。

```puppet:httpd.pp
package { "httpd":
  ensure => "2.4.6-40.el7.centos",
}

```

## 複数パッケージをインストールする場合
属性が同じパッケージであれば、パッケージを配列に列挙して、まとめて書くとコンパクトに書けます。

```puppet:require_packag.pp
$require_package = [
  "python-setuptools",
  "gcc",
  "python-devel"
]

package { $require_package :
  ensure   => "installed",
}
```

## OSごとにパッケージ名が異なる場合

OSでパッケージ名が異なる場合、次のようにOSを判定してパッケージ名を使い分けることができます。

```puppet:httpd.pp
$httpd = $operatingsystem ? {
  "CentOS" => "httpd",
  "Ubuntu" => "apache2",
}

package { "httpd" :
  name    => $httpd,
  ensure  => "installed",
}

```

最初のブロックで、httpdという名前の変数に、各OSのパッケージ名を入れています。operatingsystemは、facterを使って取得できる、OSの種類です。operatingsystemが、CentOSの場合はhttpd、Ubuntuの場合apache2になるようになっています。

次のブロックでhttpdのパッケージをインストールしますが、インストールするパッケージ名をnameで上書きして指定しています。

なお、operatingsystemの結果は、下記コマンドで確認できます。
CentOSの場合

```
# /opt/puppetlabs/bin/facter operatingsystem
CentOS
```

Ubuntuの場合

```
# /opt/puppetlabs/bin/facter operatingsystem
Ubuntu
```

## gemを使う場合

gemを使ってインストールするパッケージの場合、providerにgemを指定します。なお、gemコマンドが使いないとエラーになるため、rubygemsをインストールする定義も書いておきます。
(※puppet-agentでインストールした場合、かつpuppet側のgemにインストールする場合はproviderはpuppet_gemになります。普段はgemで、puppet用にgemを追加する場合puppet_gem？)

### Serverspecを例に

Serverspecをインストールするマニフェストの例を下記に記載しています。最初のブロックでrubygemsをインストール、次のブロックでgemを使ってServerspecをインストールするようになっています。
**なお、Puppet4.xからは、マニフェストに記載されている順に処理されてるようになっています。**3.x以前だと処理順序が、（デフォルトでは）マニフェスト順でないため注意です。

```puppet:gem_sample.pp
package { "rubygems" :
  ensure   => "installed", 
}

package { "serverspec" :
  ensure   => "installed",
  provider => "gem",
}

```

### gemコマンドが使えない場合のnoop

なお、noopオプションで実行した場合、その時点でrubygemsがインストールされていない場合、次のようなエラーがでます。

```
Provider gem is not functional on this host
```

ただ、rubygemsインストール後に、serverspecをインストールするように定義できていれば、実際に実行するとエラーはでないです。

```
# /opt/puppetlabs/bin/puppet apply gem_sample.pp -t --noop
Notice: Compiled catalog for 8fd260a416a1 in environment production in 0.66 seconds
Info: Applying configuration version '1460782127'
Notice: /Stage[main]/Main/Package[rubygems]/ensure: current_value purged, should be present (noop)
Error: /Stage[main]/Main/Package[serverspec]: Provider gem is not functional on this host
Info: Class[Main]: Unscheduling all events on Class[Main]
Notice: Applied catalog in 0.14 seconds

# /opt/puppetlabs/bin/puppet apply gem_sample.pp -t       
Notice: Compiled catalog for 8fd260a416a1 in environment production in 0.70 seconds
Info: Applying configuration version '1460782150'
Notice: /Stage[main]/Main/Package[rubygems]/ensure: created
Notice: /Stage[main]/Main/Package[serverspec]/ensure: created
Notice: Applied catalog in 103.53 seconds
```


## pipを使う場合

pipを使ってインストールするパッケージのの場合、providerにpipを指定します。なお、pipコマンドが使えないとエラーになるため、pipをインストールする定義も書いています。

### ansibleを例に

下記にansibleをインストールするマニフェストの例を、記載しています。(1)(2)がpipをインストールするための定義、(3)-(6)がpipでansibleをインストールするのに必要なパッケージのインストール定義。(7)がansibleをpipでインストールする定義です。**重要なのはproviderにpipを指定しているところです。**ちなみに(2)は、/usr/bin/pipがない場合に、easy_install pipを実行する定義です。
**なお、Puppet4.xからは、マニフェストに記載されている順に処理されてるようになっています。**

```puppet:sample_pip.pp
#(1)
package { "python-setuptools" :
  ensure   => "installed",
}

#(2)
exec { "easy_install pip" :
  command  => "easy_install pip",
  creates  => "/usr/bin/pip",
  path     => "/usr/bin",
}

#(3)
package { "gcc" :
  ensure   => "installed",
}

#(4)
package { "python-devel" :
  ensure   => "installed",
}

#(5)
package { "libffi-devel" :
  ensure   => "installed",
}

#(6)
package { "openssl-devel" :
  ensure   => "installed",
}

#(7)
package { "ansible" :
  ensure   => "installed",
  provider => "pip",
}

```

ちょっと長いですが、やりたいことをコマンドで書くとこんな感じです。

```
yum install python-setuptools
easy_install pip
yum install gcc
yum install python-devel
yum install libffi-devel
yum install openssl-devel
pip install ansible
```

Puppetの場合、インストールに順序性がある場合は、やや定義が冗長になりがちです。ただ、コマンドだともう一度実行すると再度処理が走りますが、Puppetでは再度処理が走らないようにするという違いもあるので。

noopオプションでドライランです。pipの場合は、gemと違いpipが使えなくてもエラーが出ないようです。

```
# /opt/puppetlabs/bin/puppet apply sample_pip.pp -t --noop
Notice: Compiled catalog for 533d27424fd5 in environment production in 0.95 seconds
Info: Applying configuration version '1462511173'
Notice: /Stage[main]/Main/Package[python-setuptools]/ensure: current_value purged, should be present (noop)
Notice: /Stage[main]/Main/Exec[easy_install pip]/returns: current_value notrun, should be 0 (noop)
Notice: /Stage[main]/Main/Package[gcc]/ensure: current_value purged, should be present (noop)
Notice: /Stage[main]/Main/Package[python-devel]/ensure: current_value purged, should be present (noop)
Notice: /Stage[main]/Main/Package[libffi-devel]/ensure: current_value purged, should be present (noop)
Notice: /Stage[main]/Main/Package[openssl-devel]/ensure: current_value purged, should be present (noop)
Notice: /Stage[main]/Main/Package[ansible]/ensure: current_value absent, should be present (noop)
Notice: Class[Main]: Would have triggered 'refresh' from 7 events
Notice: Stage[main]: Would have triggered 'refresh' from 1 events
Info: Creating state file /opt/puppetlabs/puppet/cache/state/state.yaml
Notice: Applied catalog in 0.52 seconds
```

実際に反映。

```
# /opt/puppetlabs/bin/puppet apply sample_pip.pp -t       
Notice: Compiled catalog for 533d27424fd5 in environment production in 0.98 seconds
Info: Applying configuration version '1462511200'
Notice: /Stage[main]/Main/Package[python-setuptools]/ensure: created
Notice: /Stage[main]/Main/Exec[easy_install pip]/returns: executed successfully
Notice: /Stage[main]/Main/Package[gcc]/ensure: created
Notice: /Stage[main]/Main/Package[python-devel]/ensure: created
Notice: /Stage[main]/Main/Package[libffi-devel]/ensure: created
Notice: /Stage[main]/Main/Package[openssl-devel]/ensure: created
Notice: /Stage[main]/Main/Package[ansible]/ensure: created
Notice: Applied catalog in 438.47 seconds
```

何度実行しても大丈夫なように定義を書いたので、もう一度実行すると何も変わらないです。

```
# /opt/puppetlabs/bin/puppet apply sample_pip.pp -t
Notice: Compiled catalog for f757d6349865 in environment production in 0.77 seconds
Info: Applying configuration version '1460808983'
Notice: Applied catalog in 0.39 seconds
```

ちなみに単純にansibleをインストールしたい場合は、EPELのrpmをインストールした方が楽です。

※上記(3)-(6)の依存パッケージは、2016/5時点です。もし、次のようなエラーでansibleのインストールが失敗する場合は、事前にインストール必要なパッケージが変わっている場合があります。

```
Error: /Stage[main]/Main/Package[ansible]/ensure: change from absent to present
```

## まとめて記載する
先ほどの、ansibleをpipでインストールするマニフェストを、属性が同じパッケージでまとめて書くとこんな感じです。

```puppet:sample_pip_02.pp
$require_package = [
  "python-setuptools",
  "gcc",
  "python-devel",
  "libffi-devel",
  "openssl-devel",
]
package { $require_package :
  ensure   => "installed",
}

exec { "easy_install pip" :
  command  => "easy_install pip",
  creates  => "/usr/bin/pip",
  path     => "/usr/bin",
}

package { "ansible" :
  ensure   => "installed",
  provider => "pip",
}
```

# 参考

パッケージリソースの詳しい情報は、下記参照です。

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