More than 1 year has passed since last update.

以下はChefDKハンズオン with すごい広島用の資料です。

準備

本ハンズオンはTest Kitchenのdriverにdocker_cliを利用します。
準備物としてダウンロードをするものが多いので事前に準備をしておくとよりスムーズに進みます。

MacOSX

  • ChefDK 0.4.0
  • Virtualbox
  • Boot2docker 1.5.0

Homebrew Caskをインストールしていれば簡単です。

$ brew cask update
$ brew cask install chefdk virtualbox boot2docker

Windows

サポートできない恐れがあります。
仮想環境でDockerの動作するLinux環境を用意しておくと安全です。

  • ChefDK 0.4.0
  • Virtualbox
  • Boot2docker 1.5.0

各ファイルは下記よりダウンロードしてください。

Linux

  • ChefDK 0.4.0
  • docker 1.5.0

多様なのでUbuntuのものを代わりに貼っておきます。

Debianで試した時の環境設定メモもあります。

その他インストールしておくと良いかもしれないもの

  • vagrant
  • git

Test Kitchenのデフォルトのdriverはvagrantなので、vagrant使用したい場合はvagrantをインストールしてください。最後にvagrantのプロビジョンを行う例もあります。
gitはなくても良いですが、各時点でコミットしておくと変化がよくわかって便利です。

共通

今回はubunutu-14.04を使っています。
事前にイメージをダウンロードしておくとスムーズに参加できます。

Test Kitchenでは複数のOSを同時にテストすることもできます。
好みに応じてダウンロードしてください。

$ docker pull ubuntu:14.04
$ docker pull centos:6.6

ChefDKに付いているもの

ChefDKはChef Development Kitの略で、RubyとChefとCookbookの開発に使えるサブツールで構成されています。

今回はVersion0.4.0を想定しています。

添付されているツールは以下の通りです。

  • chef
  • chefコマンド ChefDKのためのコマンド。ジェネレータの実行やChefDKに添付されているRubyを操作したりするのに使う。
  • Test Kitchien サーバの統合テストのためのツール。Chef以外でも使える
  • ChefSpec Chefのレシピを単体テストするためのツール
  • Foodcritic Chefのレシピの書き方をチェックしてくれる
  • Berkshelf cookbookの依存関係を解決してくれるツール。bundlerみたいなもの。

目標

今回の目標は以下の通りです。

  • ChefDKに添付されているツールを一通り使う
  • Webサーバを立てる

特にTest KitchenはChef以外の構成管理ツールでも使えて有用です。

ChefDK添付のRubyを使う

ChefDKにはRubyが添付されています。
このRubyを利用するようにシェルを設定します。

$ eval "$(chef shell-init `basename $SHELL`)"

Powershellの場合は以下を使用します。

$ chef shell-init powershell | Invoke-Expression

rubyコマンドがchefdkに添付されているものになっているはずです。

$ which ruby
/opt/chefdk/embedded/bin/ruby

chefコマンドについて

chefコマンドはChefDKに添付されているコマンドです。
Chef本体に添付されているものではありません。
引数なしでchefコマンドを実行するとヘルプを見ることができます。

ChefDKに添付されているRubyに対して操作ができたり、雛形を生成するgeneraterを実行できたりします。

クックブックの作成

ChefではCookbookという単位で開発していきます。Rubygemsで言うgemがcookbookにあたります。
今回はmywebというクックブックを作成します。

$ chef generate cookbook myweb
$ cd myweb

chefコマンドにはgenerateサブコマンドがあり、cookbookの雛形を生成できます。
cookbookだけでなくrecipeやtemplateなども雛形が作成できます。

cookbookを生成すると下記のようなディレクトリ構成になります。

.
├── .gitignore
├── .kitchen.yml
├── Berksfile
├── README.md
├── chefignore
├── metadata.rb
├── recipes
│   └── default.rb
├── spec
│   ├── spec_helper.rb
│   └── unit
│       └── recipes
│           └── default_spec.rb
└── test
    └── integration
        └── default
            └── serverspec
                ├── default_spec.rb
                └── spec_helper.rb

metadata.rbにはcookbookのメタ情報を記述します。

chefignoreにはcookbookを配布する際に含めないファイルを記述します。

.kitchen.ymlはTest Kitchenの設定ファイルです。

他にもレシピファイル、インテグレーションテストのファイルやユニットテストファイルが生成されています。

git initされている状態なのでコミットをしておきましょう。

$ git add .
$ git commit -m 'initial commit'

Dockerの動作確認

今回はDockerの中でChefを実行します。
Vagrantを使うと時間がかかってしまうので、今回はDockerを使うことにしました。

では、Dockerが使えることを確認しておきましょう。

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED         STATUS              PORTS               NAMES

正常に動作していればこのような出力になります。

boot2dockerを使用していて、動作していないのであれば、boot2dockerのstatusを確認しておきましょう。

$ boot2docker status
running

boot2dockerが動作しているのに失敗する場合は環境変数の設定が漏れている恐れがあります。

$ $(boot2docker shellinit)

で、そのシェルだけは一時的に環境変数を設定できます。

Test Kitchenの設定

折角なので、テスト駆動開発風に進めていきます。
まず、統合テストをしましょう。

統合テストをするにはTest Kitchenを利用します。
Test Kitchenがchefを実行するマシンを作成し、ServerSpecなどで記述したテストを実行してくれます。

Test Kitchenの設定は.kitchen.ymlに記述します。
.kitchen.ymlを少し書き換えます。

kitchen.yml
---
driver:
  name: docker_cli
  require_chef_omnibus: false
  run_command:
    - curl -L https://www.chef.io/chef/install.sh | bash
  publish:
    - 8100:80
#  no_cache: true

provisioner:
  name: chef_zero

platforms:
  - name: ubuntu-14.04
#  - name: centos-6.6

suites:
  - name: default
    run_list:
      - recipe[myweb::default]
    attributes:

今回はdriverにはdocker_cliを使います。
driverをインストールする必要があります。

$ bundle init
$ echo "gem 'kitchen-docker_cli'" >> Gemfile
$ bundle install

driverにはデフォルトでvagrantが使われます。
AWSのec2やGoogle Cloud Platformのgceなども使うこともできます。
production環境になるべく近い環境を使うのがオススメです。

問題がないようであればコミットしておきます。

$ git add .
$ git commit -m 'setup .kitchen.yml'

.kitchen.ymlの中身について補足します。

require_chef_omnibusはchefを自動でインストールするかどうかです。
falseにしてrun_commandで手動でインストールしているためfalseにしています。
run_commandはインスタンスを作成する際の追加コマンドを記述できます。

今回このようにしているのはDockerのキャッシュが効いて、一回のサイクルが短かく済むからです。

またno_cachetrue にするとdockerのキャッシュを使わないようにもできます。
最初から作りなおしたいときに利用してください。

publishは、今回webサーバを立てるので、80番ポートを8100でアクセスできるようにしています。

Test Kitchenを使うと同時に複数のOSのテストができます。
今回はひとつだけにしています。
同時にやってみたい場合はcentosのコメントアウトを外してみてください。

provisionerにはchef-zeroが指定されています。
他のprovisonerを使用することもできます。
SaltStackやAnsibleも使用することができるそうです。

Test Kitchenを使う

Test Kitchenを使用してみましょう。

テストを実行するには下記のtestサブコマンドを使用します。
実行に時間がかかる場合もあります。

$ kitchen test default-ubuntu-1404

第2引数のdefault-ubuntu-1404はインスタンス名です。
突然登場しましたが、インスタンス名を確認するにはlistサブコマンドを使用します。

$ kitchen list
Instance             Driver     Provisioner  Last Action
default-ubuntu-1404  DockerCli  ChefZero     <Not Created>

test-kitchenでは「platformsの数」*「suitesの数」だけ同時にテストを行うことができますが、インスタンス名を指定することで、特定のインスタンスだけテストを実行できます。
省略した場合はすべてのインスタンスでテストを実行できます。

インスタンス名はplaftformとsuiteを組み合わせて生成されます。

また、正確には第2引数がマッチするものだけをテストします。

例えば以下のように複数のplatformがあるとします。

$ kitchen list
Instance             Driver     Provisioner  Last Action
default-ubuntu-1404  DockerCli  ChefZero     <Not Created>
default-centos-66    DockerCli  ChefZero     <Not Created>

ubuntuのみテストしたい場合は

$ kitchen test ubuntu

とします。何も指定しない場合は両方実行されます。

Test Kitchenのもっと詳しい使い方は

などを読んでみてください。

特にテストの章は大切です。

Serverspec

それではServerspecをかいていきましょう。

期待することはポート80でListenしていることですね。
一応、nginxが走っていること、自動で起動することも確認しておきます。

test/integration/default/serverspec/default_spec.rb
require 'spec_helper'

describe 'myweb::default' do
  describe service('nginx') do
    it { should be_enabled }
    it { should be_running }
  end

  describe port('80') do
    it { should be_listening.with('tcp') }
  end
end

テストの実行はkitchen testです。

このテストは失敗します。

出力は長いですがよくみると下記のような出力があると思います。

Finished in 0.27516 seconds (files took 0.39046 seconds to load)
3 examples, 3 failures

このテストを成功するためにはなにをすればいいでしょうか?

test-kitchenは失敗するとインスタンスが作成されたままで残っています。
kitchen loginでサーバにログインできます。

テストを通るようにしてみましょう。

$ kitchen login
remote$ apt-get -y install nginx
remote$ service nginx start
remote$ exit

この状態で再度テストのみを実行してみます。

$ kitchen verify

成功したでしょうか。

kitchen testはインスタンスをつくりなおしてテストを実行しますが、kitchen verifyはインスタンスはそのままでテストの実行だけできます。

この状態で再度kitchen testを実行するとテストは失敗します。
インスタンスが削除されて、nginxのインストールがなかったことになるからです。

ChefSpec

今度は単体テストフレームワークのChefSpecに挑戦してみましょう。
さきほど手動で行った作業は

  • nginxのインストール
  • nginxの起動

でした。
ChefSpec化してみましょう。

spec/unit/recipes/default_spec.rb
require 'spec_helper'

describe 'myweb::default' do
  let(:chef_run) { ChefSpec::SoloRunner.converge(described_recipe) }

  it { expect(chef_run).to install_package('nginx') }
  it { expect(chef_run).to start_service('nginx') }
end

itの行に注目してください。

  • install_package('nginx')
  • start_service('nginx')

で、作業していることを確認できます。

このテストの実行にはrspecコマンドを使います。

$ rspec

ChefSpecの使い方を学ぶにはexampleを見るのがよいです。

ChefSpecの良いところはtest-kitchenの実行に比べて早いことです。
chefを実行するよりも早いと思うので文法チェックなどに使えます。
無理して書く必要はないですが、メタプログラミングしたり、リファクタリングする際にあると有効な場合もあります。

レシピの作成

今度はChefSpecが成功するように実際にレシピを書いていきます。

recipes/default.rb
package 'nginx'

service 'nginx' do
  action [:start]
end

Chefではレシピにリソースを記述して、サーバの設定をします。
packageやserviceはリソースを作成する命令です。
作成するリソースのプロパティを設定する場合はdoendで囲んだ部分で行います。

packkage 'ngixn'ではpackage[nginx]リソースを作成して、
service 'nginx'ではservice[nginx]リソースをaction:startになるように設定しています。

レシピを作成したらrspecを再度実行してみましょう。
テストに成功するはずです。

test-kitchenでのchefの実行

kitchen testが失敗している状態なので、インスタンスが残っています。
そのままのインスタンスを使ってchefを実行したい場合はkitchen convergeが使えます。

うまくいったらそのままServerspecの実行をしてみましょう。
kitchen verifyで実行します。

boot2dockerを使用している場合はまずIPを確認します。

$ boot2docker ip
192.168.59.103

http://192.168.59.103:8100 にアクセスするとウェブサーバが動いていることが確認できると思います。8100なのは.kitchen.ymlのpublishで設定をしたからです。

スクリーンショット 2015-02-23 19.30.17.png

インスタンスを作りなおしてもうまくいくのか確認してみましょう。
kitchen test -d neverを使います。

-d neverを使用するとテストに成功してもインスタンスを残すことができます。
さて、ウェブサーバーが動いているのを確認してみましょう。

うまくいったらコミットしておきましょう。

$ git add .
$ git commit -m 'install nginx'

Chefのテンプレートを使う

index.htmlを自分好みのものに変えてみたいと思います。
index.htmlの作成にはテンプレート機能を使ってみます。

テンプレートはcookbookに用意したファイルのテンプレートに変数を埋めてファイルを作成する機能です。

テンプレートの雛形を作成するのにchefコマンドが使えます。

$ chef generate template index.html

そうすると、templates/default/index.html.erbが作成されます。

自由にファイルを作成しましょう。
折角なので変数を参照してみます。

templates/default/index.html.erb
Hello <%= node.name %>

node.nameにはTest Kitchenの

作成したテンプレートのリソースを作る必要があります。
レシピに内容を追加しましょう。

recipes/default.rb
template '/usr/share/nginx/html/index.html' do
  owner 'www-data'
  group 'www-data'
  mode 644
end

Test Kitchenを実行して反映してみましょう。
kitchen convergeをつかうのが良いでしょう。

実行に成功したら http://192.168.59.103:8100 にアクセスしてみましょう。
作成したhtmlが表示されるでしょうか。

スクリーンショット 2015-02-23 20.24.38.png

されてませんね。

Foodcritic

Foodcriticを使うとrecipeの中の良くない書き方をみつけて教えてくれます。

$ foodcritic .
FC006: Mode should be quoted or fully specified when setting file permissions: ./recipes/default.rb:13

recipes/default.rbの13行目に良くない書き方があるようです。
先頭にあるFC006が問題の種類で、 http://acrmp.github.io/foodcritic/#FC006 を見ると詳しい解説が書いてあります。

modeは文字列で "644"とかくか頭に00をつけて00644で書けとあります。

recipes/default.rb
template '/usr/share/nginx/html/index.html' do
  owner 'www-data'
  group 'www-data'
  mode '644'
end

と、なおしてkitchen convergeとして再度確認してみましょう。

スクリーンショット 2015-02-23 20.41.59.png

うまくうごいたら、コミットしておきましょう。

$ git add .
$ git commit -m 'create default index.html'

node

テンプレートの中で<%= node.name %>としました。
nodeの中にはohaiというツールがサーバ情報を取得して保存されています。
/tmp/kitchen/nodes/に保存されています。

中身をみてみましょう。

$ kitchen login
remote $ cat /tmp/kitchen/nodes/default-ubuntu-1404.json

OSの種類、CPUの情報、実行したrun_listの情報、Chefの情報…様々な情報が記録されていることがわかると思います。これの情報はテンプレートやレシピの中でも利用できます。

閲覧したら exit しましょう。

remote $ exit

Berkshelf

Berkshelfは他のクックブックを取得するのに使われます。
nginxのコミュニティクックブックを利用してみましょう。

mywebクックブックがnginxクックブックに依存していることをmetadata.rbに記述して上げます。

metadata.rb
name             'myweb'
maintainer       'The Authors'
maintainer_email 'you@example.com'
license          'all_rights'
description      'Installs/Configures myweb'
long_description 'Installs/Configures myweb'
version          '0.1.0'
depends          'nginx'

追加したのは最後のdepends 'nginx'のみです。

BerkshelfはBerksfileに利用するクックブックを記述しますが、
metadataと記述してあるため、このmetadata.rbの情報を利用してくれます。

コミュニティクックブックを使えばnginxの設定はいろいろ楽ができます。
ohaiプラグインがはいっていたり、attributesを与えるだけで設定を変更できたりします。

recipe/default.rbを書き換えてみます。

packgeリソースとserviceリソースを削除して、nginxクックブックのdefalutレシピを使うように変更してみます。

recipe/default.rb
include_recipe 'nginx::default'

template '/usr/share/nginx/html/index.html' do
  owner 'www-data'
  group 'www-data'
  mode '644'
end

http://192.168.59.103:8100 にアクセスすると403になります。

スクリーンショット 2015-02-23 20.58.30.png

コミュニティクックブックによってドキュメントルートが代わってしまっているようです。
attributesを使うことでドキュメントルートを変更することができます。
.kitchen.ymlで変更してみます。

kitchen.yml
---
driver:
  name: docker_cli
  require_chef_omnibus: false
  run_command:
    - curl -L https://www.chef.io/chef/install.sh | bash
  publish:
    - 8100:80
#  no_cache: true

provisioner:
  name: chef_zero

platforms:
  - name: ubuntu-14.04
#  - name: centos-6.6

suites:
  - name: default
    run_list:
      - recipe[myweb::default]
    attributes:
      nginx:
        default_root: /usr/share/nginx/html

以下が追加されています。

      nginx:
        default_root: /usr/share/nginx/html

kitchen convergeを実行して、
http://192.168.59.103:8100 にアクセスするとindex.htmlが表示されるでしょうか。

スクリーンショット 2015-02-23 20.41.59.png

こちらでも稀に見れないことがあったので、もしダメだったら kitchen test -d neverを試してみてください。

nginxクックブックではどんなattributesが使えるのかなどは下記を参照してください。
必要があればソースコードも見ると勉強になります。

そういえば、include_recipeを使うとChefSpecが動いてくれないので、つらいです。
include_recipeは使い方を考えたほうが良いかもしれないとおもってたりします。

宿題

つくったcookbookでvagrantのプロビジョンをしてみよう

Vagrantfileを作ります。

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config|
  config.vm.box = "opscode-ubuntu-14.04"
  config.vm.box_url = "http://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_ubuntu-14.04_chef-provisionerless.box"
  config.vm.provision "shell", inline: <<-SHELL
    curl -L https://www.chef.io/chef/install.sh | bash
  SHELL
  config.vm.provision "chef_zero" do |chef|
    chef.cookbooks_path = "cookbooks"
    chef.add_recipe "myweb"
    chef.json = {
      "nginx" => {
        "default_root" => "/usr/share/nginx/"
      }
    }
  end
end

次にBerksfileをつくります。

source "https://supermarket.chef.io"

cookbook 'myweb', git: '../myweb' # mywebのディレクトリを指定する
$ berks
$ berks vendor cookbooks
$ vagrant up

attributesをcookbook側で指定しちゃう手もあるにはありますが、もにゅもにゅ。(よりよい方法を調査中)

ここからの発展

  • packerでディスクイメージを作成する
  • CIで実行する
  • どこかのサーバで使用する

どこかのサーバを使用する場合はknife-zeroやknife-soloといった選択肢もありますし、
cookbookをアップロードしてchef-zeroやchfe-soloのオプションでrun-listやattributesを指定できます。

説明してないこと

  • chef install
  • chef update
  • chef push
  • chef verify
  • chef gem
  • chef exec

まとめ

ChefをつかっているかどうかはさておきTest Kitchenはとても便利です。

  • 最初から実行しなおしたい
    • kitchen test -d never
  • Chef(プロビジョナ)をもう一度実行したい
    • kitchen converge
  • Serverspecを実行したい
    • kitchen verify
  • インスタンスを削除したい
    • kitchen destroy
  • インスタンスにログインしたい
    • kitchen login

とりえあず、上記ぐらいを最初は覚えておけば良いと思います。

参考文献