Vagrantでknife-zeroを使うためのベストプラクティス

Vagrantでknife-zeroを試そうとして挫折する人が多いらしいので、自分の考えるベストプラクティスを書いてみます。

追記:記事の一部を更新しました。詳細は末尾の更新履歴でご確認ください。

以下、Vagrantを開発環境で利用することを想定しています。

下記のドキュメントを参考にしました。

TL;DR

プライベートネットワークモードで立ち上げた2つのVMに、knife-zeroでレシピを適用する具体的な手順を説明します。

基本方針は下記の通り。

  • コマンドラインでのパラメータを極力減らす。
  • IPアドレスの記述は.envrcに集約して、その他の設定ファイルやコマンドラインでは環境変数を使う。

適切に設定ファイルを書けば、下記のような一連のコマンドでレシピの適用まで実行することができます。

% cd knife_zero_example
% direnv allow
% vagrant up
% bundle install --path=vendor/bundle --binstubs
% ./bin/berks vendor cookbooks
% ./bin/knife zero bootstrap $VAGRANT_HOST001
% ./bin/knife zero bootstrap $VAGRANT_HOST002
% ./bin/knife node run_list set host001.example build-essential
% ./bin/knife node run_list set host002.example build-essential
% ./bin/knife zero converge 'name:*.example' -a knife_zero.host

概要

動作確認環境

  • Yosemite 10.10.5
  • Vagrant 1.7.4
  • VirtualBox 5.0.0
  • ruby 2.2.3
  • chef 12.4.1
  • knife-zero 1.8.0
  • berkshelf 3.3.0
  • direnv 2.6.0
  • CentOS 7.1(VM on VirtualBox)

目標

  • vagrantで2台のVM(CentOS 7.1)をセットアップ
  • プライベートネットワークで互いに疎通
  • 2台のVMにコミュニティクックブックの build-essential を適用

VMのホスト名とIPアドレスは下記とします。

hostname ip address
host001.example 192.168.33.10
host002.example 192.168.33.11

ネットワークアドレスは、VBoxManage list hostonlyifs コマンドでVirtualBoxのホストオンリーネットワークのアドレスを確認して、そのレンジのアドレスを設定するようにしてください。 普通にVirtualBoxをインストールすると下記のような出力になると思います。

% VBoxManage list hostonlyifs

Name:            vboxnet0
GUID:            786f6276-656e-4074-8000-0a0027000000
DHCP:            Disabled
IPAddress:       192.168.33.1
NetworkMask:     255.255.255.0
IPV6Address:
IPV6NetworkMaskPrefixLength: 0
HardwareAddress: 0a:00:27:00:00:00
MediumType:      Ethernet
Status:          Up
VBoxNetworkName: HostInterfaceNetworking-vboxnet0

この場合、192.168.33.0/24のレンジでアドレスを設定する必要があります。

手順

VMのアドレスを環境変数に設定(.envrc)

VMのIPアドレスを環境変数に設定します。 これらの環境変数はdirenvで自動的にロード・アンロードするようにします。

% mkdir knife_zero_example 
% cd knife_zero_example
% cat <<EOF > .envrc
export VAGRANT_HOST001=192.168.33.10
export VAGRANT_HOST002=192.168.33.11
EOF

% direnv allow

direnvがない場合は直接読み込んでもよいです。

% source ~/.envrc

Vagrantfileの作成

% cat <<EOF> Vagrantfile
Vagrant.configure(2) do |config|
  config.vm.box = "bento/centos-7.1"

  config.vm.define :host001 do |host|
    host.vm.hostname = "host001.example"
    host.vm.network "private_network", ip: ENV['VAGRANT_HOST001']
  end

  config.vm.define :host002 do |host|
    host.vm.hostname = "host002.example"
    host.vm.network "private_network", ip: ENV['VAGRANT_HOST002']
  end

  # VMにログインする鍵を固定
  config.ssh.insert_key = false
end
EOF

~/.ssh/config の設定変更

~/.ssh/configに下記のエントリを追加します。

% cat <<EOF>> ~/.ssh/config

# Vagrant private network ip
Host 192.168.33.*
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  LogLevel FATAL
  User vagrant
  IdentityFile ~/.vagrant.d/insecure_private_key
EOF

vagrantで使うプライベートネットワークでのデフォルトのログインユーザをvagrantにし、鍵をデフォルトのものに固定します。

IdentityFileは、もしもVAGRANT_HOME環境変数を設定している場合には、その下のinsecure_private_keyファイルのパスに変更してください。

VMの立ち上げと疎通確認

VMを立ち上げて、ログインできるか確認して下さい。

% vagrant up

% ssh $VAGRANT_HOST001 hostname
host001.example

% ssh $VAGRANT_HOST002 hostname
host002.example

knife-zeroとberkshelfのインストール

knife-zeroとberkshelfをインストールします。

% cat <<EOF> Gemfile
source "https://rubygems.org"

gem "knife-zero"
gem "berkshelf"
EOF

% bundle install --path=vendor/bundle --binstubs

knifeコマンドのオプション引数を省略するために、.chef/knife.rbを作成します。

% mkdir -p .chef

% cat <<EOF> .chef/knife.rb
# -z, --local-mode
local_mode true

# -x vagrant, --ssh-user vagrant
knife[:ssh_user] = "vagrant"

# --sudo
knife[:use_sudo] = true
EOF

knife.rb のその他の設定は下記のドキュメントを参照してください。

knife.rb Optional Settings — Chef Docs

今回はknife-zeroの利用例として、コミュニティクックブックのbuild-essentialを2つのVMにインストールします。

このコミュニティクックブックを使うための設定をします。

% cat <<EOF> Berksfile
source "https://supermarket.chef.io"

cookbook "build-essential"
EOF

% ./bin/berks vendor cookbooks

ノードの初期設定

knife-zeroを使って、VMにchefをインストールするとともに、ノードとしてchef-zeroサーバに登録します。

% ./bin/knife zero bootstrap $VAGRANT_HOST001
% ./bin/knife zero bootstrap $VAGRANT_HOST002

nodes/ディレクトリとclients/ディレクトリ以下にそれぞれjsonファイルができていることを確認して下さい。

また、knife node listコマンドを実行して、登録したVMのホスト名が表示されることを確認して下さい。

% ./bin/knife node list
host001.example
host002.example

レシピの適用

各ノードのrun_listにbuild-essentialレシピを追加します。

% ./bin/knife node run_list set host001.example build-essential
host001.example:
  run_list: recipe[build-essential]

% ./bin/knife node run_list set host002.example build-essential
host002.example:
  run_list: recipe[build-essential]

次に各ノードへレシピを適用します。

knife-soloではノードにレシピを適用するとき、knife solo cook FQDNでホストを一つ指定します。
knife-zeroでは、knife solo cook FQDNに相当するコマンドはknife zero converge QUERYです。
QUERYで指定した条件にマッチするすべてのホストに対して並列にsshログインし、chef-clientを実行してレシピを適用します。

knife zero converge QUERYでは、対象のノードにsshでログインするためのIPアドレスをノード情報のAttribute名で指定する必要があります(-aオプション)。 このAttributeは通常ipaddressで良く、その場合は省略できます。 ただし、Vagrant + VirtualBoxのprivate_networkでIPアドレスを固定したVMにknife zero bootstrapした場合、 ノードのAttributeとして記録されるipaddressは、Vagrantfileで指定したアドレス192.168.33.*ではなく、 10.0.2.*のように異なるネットワークのアドレスになっているはずです。(さらにおそらくアドレス自体が同じ)

% ./bin/knife search node -a ipaddress
2 items found

host001.example:
  ipaddress: 10.0.2.15

host002.example:
  ipaddress: 10.0.2.15

knife-zeroのバージョン1.8.0以降であれば、bootstrap時にknife_zero.hostというAttributeに接続時のアドレスが記録されます。

% ./bin/knife search node -a knife_zero.host
2 items found

host001.example:
  knife_zero.host: 192.168.33.10

host002.example:
  knife_zero.host: 192.168.33.11

なので下記のようにknife_zero.hostを指定すればレシピを適用できます。

% ./bin/knife zero converge 'name:*' -a knife_zero.host

'name:*'はクエリ文字列で、この例ではすべてのノードを対象にしています。

クエリの構造は knife search と同様です。

# host001だけを指定
% ./bin/knife search node 'name:host001.example'

# exampleドメインに一致するホストだけを指定
% ./bin/knife search node 'name:*.example'

また、クエリ指定でsshログインして任意のコマンドを実行することもできます。

% ./bin/knife ssh 'name:*' -a knife_zero.host 'sudo yum update -y'

補足

ノード情報の保存先

knife execknife node editで変更した情報は nodes/*.json に保存されています。

これは本質的にはknife-solo(chef-solo)で書くものと同じなのですが、knife-zero(chef-zero)では非常に分量が多いです。

これについては、.chef/knife.rbにAttributeのホワイトリストを書くことで制御できます。

chef-zero - Knife-Zeroで管理するnodeオブジェクトを任意のattributesに限定する - Qiita

Attributeのタイプ

knife execでnormalを指定していますが、chefのAttributeには他にもdefaultやoverrideなどの種類があります。 defaultで記録してしまうと、次にconvergeしたタイミングで消えるので注意してください。normalであればconvergeでは消えません。 優先順位は複雑なので公式のドキュメントを参照してください。

About Attributes — Chef Docs

サンプルコード

設定ファイル一式まとめたものを下記に置きました。

https://github.com/kwhrtsk/knife_zero_example

knife-zero 1.7.1以前(蛇足)

knife_zero.host にbootstrapで接続した時のIPアドレスが記録されるようになったのはknife-zero 1.8.0からです。

それ以前は knife node edit でjsonファイルを編集するか、下記のようにknife execで接続用のアドレスを追加する必要がありました。

% ./bin/knife exec -E "search(:node, 'hostname:host001'){|n| n.normal['chef_ip'] = ENV['VAGRANT_HOST001']; n.save}"
% ./bin/knife exec -E "search(:node, 'hostname:host002'){|n| n.normal['chef_ip'] = ENV['VAGRANT_HOST002']; n.save}"

# 設定されているか確認
% ./bin/knife search node 'hostname:*' -a chef_ip
2 items found

host001.example:
  chef_ip: 192.168.33.10

host002.example:
  chef_ip: 192.168.33.11

knife execでどのようなことができるかは、下記のドキュメントが参考になります。

更新履歴

  • 2015-08-27
  • 2015-08-28
    • サンプルのVagrantfileに書いたboxを変更(跡地) chef/centos-7.1 => bento/centos-7.1