Chef-Soloでレシピを書く前の環境について(Vagrantfile,role,node,data_bags)

こんにちは。小宮です。
いつのまにか今年の前半が終了しました。宇治金時がおいしい季節になってしまったようです。
それはともかく前回の続きです。
今回はChef-Soloのレシピを書く前の環境の定義などまでを書きます。

Chef用語などは軽く解説してる、今日から使い始めるChefを見ていただくか定番の入門Chef Soloを見ていただくのが早いんではないかと思います。
Automated infrastructure is on the menuも英語ですがわかりやすいです。

やりたいこととしては以下のとおりです。
 Vagrantfileを書いて仮想インスタンスをたてる
 chefレポジトリを作る
 chefクックブックを作る
 nodeを定義する
 roleを定義する
 data_bagsを定義する
  ★今回はここまで★
 レシピを書く
 レシピをノードに適用する
 serverspecテストを書く
 テストを実環境に実行する

テストの構成:
 chef-solo元のadmserver:10.0.0.93
 knife-solo適用クライアントwebserver:10.0.0.240
 knife-solo適用クライアントdbserver:10.0.0.241
 全てCentOS5.8です。

・AWSの環境変数を定義

AWSのVPC上なので適宜定義しておきます
[shell]# cd
# vi .ec2
export AWS_ACCESS_KEY_ID=******
export AWS_SECRET_ACCESS_KEY=*******
export AWS_KEYPAIR_NAME=xxx-key
export AWS_PRIVATE_KEY_PATH=/root/.ssh/xxx-key
# source .ec2[/shell]

・VagrantFileを書く

vagrant initを実行するとVagrantfileができますのでそれを編集し、
VPC上で仮想インスタンスを起動するための設定を書きます。

参考
プロビジョニングの設定(Chef Solo)
  ※Vagrant1.1から設定ファイルの書き方が異なる場合があるので、v2のほうのマニュアルを見る必要があります。
シェルスクリプトを実行させる方法

[shell]# vagrant init
# cp -p Vagrantfile{,.org}
# vi Vagrantfile
———————–
Vagrant.configure("2") do |config|
# 共通設定
config.vm.box = "dummy"

#—- ここからWebサーバ作る
config.vm.define :web do |web|
web.vm.box = "dummy"
web.vm.provider :aws do |aws|
aws.access_key_id = ENV[‘AWS_ACCESS_KEY_ID’]
aws.secret_access_key = ENV[‘AWS_SECRET_ACCESS_KEY’]
aws.keypair_name = ENV[‘AWS_KEYPAIR_NAME’]
aws.ssh_private_key_path = ENV[‘AWS_PRIVATE_KEY_PATH’]

#—- VPC固有の設定 —-#
aws.private_ip_address = "10.0.0.240"
aws.subnet_id = "subnet-80bxxxx9"
aws.security_groups = ["sg-6ebxxxx", "sg-fa81xxxx"]
#—- VPC固有の設定ここまで —-#

aws.ssh_username = "root"
aws.ssh_private_key_path = "~/.ssh/komi-test.pem"
aws.instance_type = "t1.micro"
aws.tags = ["xxx-web03"]
aws.region = "ap-northeast-1"
aws.ami = "ami-d7bxxxx6"
end
## プロビジョニングの設定
#web.vm.provision :chef_solo do |chef|
# chef.cookbooks_path = ["cookbooks", "site-cookbooks"]
# chef.roles_path = "roles"
# chef.add_role("webserver")
# chef.data_bags_path = "data_bags"
# chef.node_name = "xxx-web03"
# chef.log_level = "debug"
#end
end

#—- ここからDBサーバ作る
config.vm.define :db do |db|
db.vm.provider :aws do |aws|
aws.access_key_id = ENV[‘AWS_ACCESS_KEY_ID’]
aws.secret_access_key = ENV[‘AWS_SECRET_ACCESS_KEY’]
aws.keypair_name = ENV[‘AWS_KEYPAIR_NAME’]
aws.ssh_username = ‘"root’
#aws.ssh_private_key_path = ENV[‘AWS_PRIVATE_KEY_PATH’]

#—- VPC固有の設定 —-#
aws.private_ip_address = "10.0.0.241"
aws.subnet_id = "subnet-80bxxxx9"
aws.security_groups = ["sg-6ebdxxxx", "sg-fa81xxxx"]
#—- VPC固有の設定ここまで —-#

aws.ssh_username = "root"
aws.ssh_private_key_path = "~/.ssh/komi-test.pem"
#aws.ssh_private_key_path = "~/.ssh/id_dsa"
aws.instance_type = "t1.micro"
aws.tags = ["xxx-db03"]
aws.region = "ap-northeast-1"
aws.ami = "ami-d7bxxxx6"
end
## プロビジョニングの設定
#db.vm.provision :chef_solo do |chef|
# chef.cookbooks_path = ["cookbooks", "site-cookbooks"]
# chef.roles_path = "roles"
# chef.add_role("dbserver")
# chef.data_bags_path = "data_bags"
# chef.json = {
# "mysqld" => {
# "server_id" => "103"
# }
# }
# chef.node_name = "xxx-db03"
# chef.log_level = "debug"
#end
end
end
———————–[/shell]
都合(※)によりプロビジョニングの設定はコメントインしました
(※data_bagsの鍵の都合などknifeコマンドでないと上手いかず等)

SecurityGroupの書き方がIDじゃなくて名前だったりVPCのサブネットが違ったりする以下のようなエラーがでます。

[shell][web] — Security Groups: ["default,nat-test-grp"]
There was an error talking to AWS. The error message is shown
below:

InvalidParameterCombination => The parameter groupName cannot be used with the parameter subnet

[web] — Security Groups: ["sg-6815xxxx", "sg-6ebdxxxx"]
There was an error talking to AWS. The error message is shown
below:

InvalidParameter => Security group sg-6815xxxx and subnet subnet-80bfxxxx belong to different networks.[/shell]

・AMI化の前の最低限の設定

一度起動して途中で止まるので以下の設定を行いAMIを取って指定しなおします。

[shell]# ssh 10.0.0.240
# cd /root/.ssh
# vi authorized_keys[/shell]
vagrantのrootの鍵を登録する

ログイン後のssh越しのsudo許可設定
[shell]# visudo
#Defaults requiretty[/shell]

移行期のVPC環境のため外に出られずcurlに失敗してknife solo prepareが効かずchefがインストールされないので、
stgにあたるchef-solo元をNATインスタンスにしてknife soloクライアントのデフォルトゲートウェイをNATに向ける

[shell]# vi /etc/sysconfig/network
GATEWAY=10.0.0.93[/shell]

AWSマネジメントコンソールからノードのAMIを作成して再度vagrantでインスタンスを起動する

・vagrantでインスタンスを起動する

[shell]# vagrant up –provider=aws
~略~
[db] Waiting for instance to become "ready"…
[db] Waiting for SSH to become available…
[db] Machine is booted and ready for use!
[db] Rsyncing folder: /root/ => /vagrant[/shell]

ここまででVPCインスタンスの複数起動は上手くできた様子なので、
次にレポジトリを作りrole等の環境を定義しレシピを書いていってprovisionしていきます。provisionというのはよしなにレシピをインスタンスに適用することです。

・新規Chefレポジトリを作る
[shell]# knife solo init chef-repo
# tree -L 1 chef-repo
chef-repo
|– cookbooks  #サードパーティレシピ置き場
|– data_bags  #ユーザ等データ管理機能使用時に使う
|– nodes  #各サーバ(node)用のJSONファイル置き場
|– roles  #Roleを使うとき用ディレクトリ
|– site-cookbooks  #自作レシピ置き場
-- solo.rb  #cookbookのパスなどを設定[/shell]

vagrantfileをchefリポジトリ直下に移動しておきます(cookbookのpathが通るように)
[shell]# mv Vagrantfile chef-repo/[/shell]

・必要なcookbookを作成する
knife cookbook create cookbook-name -o create-directory-path で作成できます。
[shell]# cd chef-repo
# for i in base_setting login-users httpd mysqld munin zabbix ;do knife cookbook create $i -o site-cookbooks;done
# tree -L 1 site-cookbooks/munin
site-cookbooks/munin
|-- CHANGELOG.md
|-- README.md
|-- attributes  #Attributesはtemplateで指定した変数のデフォルト値置き場
|-- definitions
|-- files  #定数のみの設定ファイルやパッケージファイル置き場
|-- libraries
|-- metadata.rb
|-- providers
|-- recipes  #レシピ置き場
|-- resources
— templates  #変数を用いる設定ファイル置き場[/shell]

・Roleを作成する

Roleを定義することで各サーバへの適用時(node用のjsonファイル編集時)に
レシピ名やattributeをつど列挙する必要がなくなりRole名だけを定義できます
webserverとかdbserverとか役割を定義しておいて、役割に応じたレシピをrole内で列挙しとく感じです。nodeにはroleを定義します。

 template,attributeもroleやnodeで定義できる。
  templateリソースを用いてrecipeを書き、templatesディレクトリに変数を指定してある設定ファイルを置き、
  attributesディレクトリにデフォルト値を書いたrecipeを置き、roleかnodeにて固有の値を上書きする。
 ※Ohaiが収集した情報を用いる場合はattributesディレクトリにデフォルト値置いたりnodeやroleに値かかなくても大丈夫。

書く予定のレシピをそれぞれのロールに定義しておく
[shell]# cd chef-repo/roles
# vi webserver.json
{
"name":"webserver",
"chef_type": "role",
"json_class":"Chef::Role",
"default_attributes":{
"base_setting": {
"swappiness": "30",
"tcp_tw_reuse": "0",
"tcp_tw_recycle": "0",
"tcp_fin_timeout": "60",
"tcp_max_syn_backlog": "4096",
"somaxconn": "4096"
}
},
"override_attributes":{},
"description":"webserver’s role",
"run_list": [
"recipe[base_setting::bkup_dir]",
"recipe[login_users]",
"recipe[base_setting::hosts]",
"recipe[base_setting::sysctl]",
"recipe[base_setting::disable]",
"recipe[base_setting::ntpdate]",
"recipe[base_setting::mail-client]",
"recipe[base_setting::logrotate]",
"recipe[httpd::httpd-server]",
"recipe[httpd::basic_auth]",
"recipe[httpd::wordpress]",
"recipe[httpd::s3mount]",
"recipe[munin::munin-node]",
"recipe[munin::munin-node-web]",
"recipe[zabbix::zabbix-agent]"
]
}

# vi dbserver.json
{
"name":"dbserver",
"json_class":"Chef::Role",
"chef_type": "role",
"description":"",
"default_attributes":{
"base_setting": {
"swappiness": "0",
"tcp_tw_reuse": "1",
"tcp_tw_recycle": "1",
"tcp_fin_timeout": "10",
"tcp_max_syn_backlog": "8192",
"somaxconn": "8192"
}
},
"override_attributes":{},
"run_list": [
"recipe[base_setting::bkup_dir]",
"recipe[login_users]",
"recipe[base_setting::hosts]",
"recipe[base_setting::sysctl]",
"recipe[base_setting::disable]",
"recipe[base_setting::ntpdate]",
"recipe[base_setting::mail-client]",
"recipe[base_setting::logrotate]",
"recipe[mysqld::mysqld-server]",
"recipe[mysqld::mysql-users]",
"recipe[munin::munin-node]",
"recipe[munin::munin-node-db]",
"recipe[zabbix::zabbix-agent]"
]
}

# vi admserver.json
{
"name":"admserver",
"json_class":"Chef::Role",
"description":"",
"chef_type": "role",
"default_attributes":{
"base_setting": {
"swappiness": "0",
"tcp_tw_reuse": "0",
"tcp_tw_recycle": "0",
"tcp_fin_timeout": "60",
"tcp_max_syn_backlog": "8192",
"somaxconn": "8192"
}
},
"override_attributes":{},
"run_list": [
"recipe[base_setting::bkup_dir]",
"recipe[login_users]",
"recipe[base_setting::hosts]",
"recipe[base_setting::sysctl]",
"recipe[base_setting::disable]",
"recipe[base_setting::ntpd]",
"recipe[base_setting::ntpdate]",
"recipe[base_setting::mail-postfix]",
"recipe[base_setting::mail-dovecot]",
"recipe[base_setting::logrotate]",
"recipe[httpd::httpd-server]",
"recipe[httpd::wordpress]",
"recipe[mysqld::mysqld-server]",
"recipe[mysqld::mysql-users]",
"recipe[munin::munin-node]",
"recipe[munin::munin-node-web]",
"recipe[munin::munin-node-db]",
"recipe[munin::munin-server]",
"recipe[zabbix::zabbix-agent]",
"recipe[zabbix::zabbix-proxy]"
]
}[/shell]

初心者Chefアンチパターンによるとroleはバージョン管理できないので
 runlistをroleで管理するべきでないようです(リファクタリング対象)

※sysctl.confはrole毎に異なる値にしたくてtemplateにおいて変数埋め込んでattribute使って上書きすることにしました。
templateやattributeは次回以降詳述しますが[chef] attributeの理解。がわかりやすかったです。
chefとは関係ない話で恐縮ですが、こちらに書いてあるように、
「net.ipv4.tcp_tw_recycle」を有効にするのは(場合によっては)やめた方が。
公衆無線LAN環境からスマートフォン等で同じ回線を用いる
複数の異なる端末から同時にアクセスする場合などに通信ができないことがあるため。
つまりフロントのWEBやMTAサーバなどでは0が推奨値。
必ず異なるIPから接続されるはずのバックエンドのDBなら1でも問題ないはず。たぶん。

・node用JSONファイルを作成する
[shell]# cd chef-repo/nodes
# vi localhost.json
{
"run_list":[
"role[admserver]"
]
}

# vi 10.0.0.240.json
{
"run_list":[
"role[webserver]"
]
}

# vi 10.0.0.241.json
{
"mysqld" : {
"server_id" : 103
},
"run_list":[
"role[dbserver]"
]
}[/shell]

※roleとrecipeは併記することが可能です。
※roleを使わない場合、nodeで全部書かなくてはならないので、webサーバ30台あった場合わりと面倒なことに。
※node毎に分ける必要があるmysqlのserver_idパラメータのattributeを指定しています
※vagrantでprovisionする場合はnodeのJSONファイルはあまり要らなかったりします。

・data_bagsでユーザ管理の用意をする

data_bagsとはldapのような機能をもったデータ検索など管理ができるもの。
chef-server使う場合はサーバと通信してデータ取ってきて反映することが可能。
今回はChef-soloなので、ローカルのファイルにデータを用意して反映させる方法をとります。

参考:
もしユーザ数が多くて頻繁にアカウントが追加変更される場合、以下リンクのようにdata_bagsで有効無効も管理するのもよさそうです。
chef-data-bag活用法

[shell]# cd ;cd chef-repo/data_bags
# mkdir users;cd users
# vi xxx-op.json
// xxx-op.json
{
"id" : "xxx-op",
"groups": [ "xxx-op","wheel" ],
"uid": 1000,
"username" : "xxx-op",
"home" : "/home/xxx-op",
"shell" : "/bin/bash",
"password" : "$1$Ka.Mw69U$TT5HRfSe7xxxxx"
}

# vi yyy-op.json
// yyy-op.json
{
"id" : "yyy-op",
"groups": [ "yyy-op","wheel" ],
"uid": 500,
"username" : "yyy-op",
"home" : "/home/yyy-op",
"shell" : "/bin/bash",
"password" : "$1$Ka.Mw69U$TT5HRfSe78Pxxxxx"
}

# vi dev.json
// dev.json
{
"id" : "dev",
"groups": [ "dev","wheel" ],
"uid": 501,
"username" : "dev",
"home" : "\/home\/dev",
"shell" : "\/bin\/bash",
"password" : "$1$S/q25RbR$oO7pCoAjBWxxxxx"
}[/shell]

password は下記のコマンドにて作成する
[shell]# openssl passwd -1[/shell]

[shell]# knife solo data bag show users[/shell]
データが表示されることを確認する

・mysqlのユーザの定義を暗号化して行う

※nodeやroleのJSONファイルに平文でパスワード定義するのはセキュアでないしnode毎に記録するのもスマートでない
 ので鍵を使って暗号化できるdatabagsを使う方向とします
 knife-soloだけだと暗号化つかえないようですが、gem install knife-solo_data_bagすると使えるようになります。

[shell]# openssl rand -base64 512 | tr -d ‘\r\n’ > /etc/chef/encrypted_data_bag_secret
# chmod 400 encrypted_data_bag_secret
# cd /root/chef-repo
# knife solo data bag create mysqlusers root –secret-file ./encrypted_data_bag_secret
エディタが開くので以下を入力する
{
"id": "root",
"user": "root",
"pass": "xxxxxxx",
"host": "localhost",
"privileges": "all"
}

# knife solo data bag create mysqlusers repl –secret-file ./encrypted_data_bag_secret
エディタが開くので以下を入力する
{
"id": "repl",
"user": "repl",
"pass":"xxxxxxx",
"host":"10.0.0.%",
"privileges": ["\\:replication slave", "\\:replication client"]
}

# knife solo data bag create mysqlusers xxx_admin –secret-file ./encrypted_data_bag_secret
エディタが開くので以下を入力する
{
"id": "xxx_admin",
"user": "xxx_admin",
"pass": "xxxxxxx",
"host":"10.0.0.%"
"privileges": "all"
}

# knife solo data bag create mysqlusers zabbix –secret-file ./encrypted_data_bag_secret
エディタが開くので以下を入力する
{
"id": "zabbix",
"user": "zabbix",
"pass": "xxxxxxx",
"host": "localhost",
"privileges": "all"
}

確認
# knife solo data bag show mysqlusers
# knife solo data bag show mysqlusers repl –secret-file ./encrypted_data_bag_secret
# knife solo data bag show mysqlusers root –secret-file ./encrypted_data_bag_secret
# knife solo data bag show mysqlusers xxx_admin –secret-file ./encrypted_data_bag_secret
# knife solo data bag show mysqlusers zabbix –secret-file ./encrypted_data_bag_secret[/shell]
データが表示されることを確認する
他のコマンドは以下で確認可能
[shell]# knife solo data bag –help[/shell]

chef-serverだとdatabags用の鍵をbootstrapファイルでクライアントホストに渡すということが可能なようですが、
knife-solo_data_bagだからなのかknife solo cook hostnameする時にbootstrapファイル指定するという必要はないようでした。
vagrantのほうでどうやるのかはよく分かっていません。

レシピからの呼び出し方などは追ってレシピ載せますが以下が参考になると思われます。
参考:
Chefで公開したくないJSONデータを暗号化するためにDataBagsを利用してみた記録
ChefでのMySQLパスワードの扱い

Chef-Serverだと本番環境と試験環境をEnvironmentで分けるとかできるらしいのですが、その機能はChef-Soloについてなくてちょっと残念。
もう少し規模の大きい環境(20台以上とか)の場合にはChef-Serverがいいらしいのでそういう話があったら使うことになるかも、、、
と思ってたのですが一応検索したら11.6から使えるっぽいです。あら嬉しい。追ってためしてみます。

今回はここまで。見ていただいてありがとうございました。
つづきはレシピについてです。

おすすめ記事