サーバー設定ツール「Chef」のデータ管理機構「Data Bag」を使う

図2 設定内容をData Bagに分離した場合

 サーバー管理ツール「Chef(シェフ)」は、ファイルに記述した設定内容に応じて自動的にユーザーの作成やパッケージのインストール、設定ファイルの編集などを行うツールだ。今回はChefの応用編として、設定のためのデータなどを一元管理できる「Data Bag」という仕組みを紹介する。

さまざまな管理用データを格納できるChefの機能「Data Bag」

 Chefは「Cookbook(クックブック)」や「Recipe(レシピ)」と呼ばれる設定ファイルに従って、ユーザーの作成やソフトウェアのインストール、ネットワークやファイアウォール、データベースなどの設定など、さまざまな設定作業を自動で実行するツールだ。利用するメリットとしてサーバーの設定を一元的に管理できる、実行する設定内容をチェックしやすくミスを防ぎやすいなどがあり、近年注目を集めている。

 Chefの基本的な使い方については以前の『サーバー設定ツール「Chef」の概要と基礎的な使い方』という記事で解説しているが、今回はChefの応用編として、設定のためのデータを一元管理するための機構である「DataBag」について紹介する。

Data Bagとは

 ChefのCookbookにはさまざまな情報を記述できるが、その中身は作業内容と設定内容におおまかに分けることができる。たとえば「taroというユーザーを作成し、そのuidは1000」というCookbookであれば、作業内容は「ユーザーを作成する」、設定内容は「taro」や「1000」となる。今まではこれらを1つのRecipe内に記述していたが、この場合、設定内容が変わるとRecipeやCookbookを書き換えなければならなかった。たとえば、「taro」だけでなく「john」というユーザーも作成したいと思った場合、Recipeを書き換えてこのユーザーを作成するための記述を追加しなければならない(図1)。

図1 Cookbook内に作業内容と設定内容を記述した場合
図1 Cookbook内に作業内容と設定内容を記述した場合

 しかし、taroというユーザーを作成する処理も、johnというユーザーを作成する処理もその作業内容自体は同じであるため、似たような記述がRecipe内に並ぶこととなる。このような場合、Chefに用意されている「Data Bag」という仕組みを利用することで、作業内容と設定内容を分離することが可能だ(図2)。

図2 設定内容をData Bagに分離した場合
図2 設定内容をData Bagに分離した場合

 Data Bagは任意のデータを格納できるデータベースのようなもので、Recipeからその内容を参照できる。たとえばユーザーを作成する例の場合、Data Bagに作成するユーザーの情報を格納し、Recipe側ではData Bagからデータを順次読み出してユーザーを作成するという処理を記述すれば良い。

 Data BagはChef Serverを使う構成でも、Chef Soloを使う構成でも利用可能だ。Chef Serverを利用する場合、Data Bagに格納したデータはChef Server内に格納され、そのChef Server内に保存されているすべてのCookbookからアクセスできる。データはJSON形式で格納され、RecipeからはRubyのハッシュとしてアクセスできる。

 いっぽう、chef-soloを使う構成では専用のデータベースは使われないが、代わりに指定したディレクトリ以下に格納するデータをJSON形式で記述したファイルを配置するという形でData Bagを利用できる。

 以下では、Chef Serverを使う構成と、Chef SOloを使う場合で、以下のような、「taro」というグループおよびユーザーを作成するというCookbookを、Data Bagを使って作成し使用する流れを紹介する。

group "taro" do
  gid 1000
  action :create
end

user "taro" do
  home "/home/taro"
  password '$6$OmC3KootOURrqOaP$63rwQ2bSE8op8wXa.ZWzgxm/iGvePTzEL5lOntmkPyYh5Qwh4lWs2DtyoEHcvsbYV5Q6a2ezzrZueb2ydrkhz0'
  shell "/bin/bash"
  uid 1000
  gid "taro"
  supports :manage_home => true
  action :create
end

 このCookbookの内容については以前の記事で紹介しているので、詳しくはそちらを参照してほしい。

Chef Server構成でData Bagを使う

 それでは、まずChef Serverを使う構成でData Bagにデータを格納する方法を説明しよう。Data Bagは「knife data bag create」コマンドで作成できる。たとえば、「initial_accounts」という名前のData Bagを作成するには以下のようにする。

$ knife data bag create initial_accounts

 続いて、data bagに格納するデータを用意する。今回は作成するユーザー名やグループ名、関連プロパティなどを記述した「default.json」というJSONファイルを用意した。

{
  "id": "default",
  "groups": [
    {
      "name": "taro",
      "gid": 1000
    }
  ],
  "users": [
    {
      "name": "taro",
      "home": "/home/taro",
      "password": "$6$OmC3KootOURrqOaP$63rwQ2bSE8op8wXa.ZWzgxm/iGvePTzEL5lOntmkPyYh5Qwh4lWs2DtyoEHcvsbYV5Q6a2ezzrZueb2ydrkhz0",
      "shell": "/bin/bash",
      "uid": 1000,
      "gid": "taro"
    }
  ]
}

 ここで、注目したいのが「”id”: “default”」という項目だ。この「id」はData Bagに格納するデータに必須の項目で、Data Bagに格納するデータ(ハッシュ)は、この項目で指定した名前で管理される。たとえばこのデータは、Recipe側からは「default」という名前でアクセスできる。

 JSONファイルをData Bagに格納するには、「knife data bag from file」コマンドを利用する。たとえば、先に作成した「initial_accounts」というData Bagに「defaoult.json」ファイルに記述されたデータを格納するには、以下のようにする。

$ knife data bag from file initial_accounts default.json

 格納したデータは、「knife data bag show」コマンドで確認できる。

$ knife data bag show initial_accounts default
groups:
  gid:  1000
  name: taro
id:     default
users:
  gid:      taro
  home:     /home/taro
  name:     taro
  password: $6$OmC3KootOURrqOaP$63rwQ2bSE8op8wXa.ZWzgxm/iGvePTzEL5lOntmkPyYh5Qwh4lWs2DtyoEHcvsbYV5Q6a2ezzrZueb2ydrkhz0
  shell:    /bin/bash
  uid:      1000

 なお、Data Bagに格納したデータは、実際にはChef Serverに含まれるPostgreSQLデータベース内に格納されている。このデータは、Chef Serverを稼動させているマシン上で「opscode-pgsql」ユーザーを使ってPostgreSQLデータベースにアクセスすることで確認可能だ。

Data Bagを使ったRecipeを作成する

 続いて、RecipeからData Bagに格納したデータにアクセスする方法を説明しよう。今回は「setup-user」というCookbookを作成し、そこから先ほどのData Bagにアクセスしてユーザーやグループを作成することにする。

$ cd chef-repo/cookbooks/
$ knife cookbook create setup-user -o .
** Creating cookbook setup-user
** Creating README for cookbook: setup-user
** Creating CHANGELOG for cookbook: setup-user
** Creating metadata for cookbook: setup-user
$ vi setup-user/recipes/default.rb

 Recipe(default.rb)の内容は、以下のようになる。

accounts = data_bag('initial_accounts')
accounts.each do |id|
  item = data_bag_item('initial_accounts', id)

  item['groups'].each do |g|
    group g['name'] do
      gid g['gid']
      action :create
    end
  end

  item['users'].each do |u|
    user u['name'] do
      home u['home']
      password u['password']
      shell u['shell']
      uid u['uid']
      gid u['gid']
      supports :manage_home => true
      action :create
    end
  end
end

 Recipe内からData Bagのデータを取得するには、data_bag関数を使用する。引数には対象のData Bag名を文字列で指定する。たとえば「initial_accounts」というData Bagを取得するには、以下のようになる。

accounts = data_bag('initial_accounts')

 data_bag関数の戻り値は、data_bagに含まれるデータの「id」を格納した配列となる。続いて、Data Bag名とidを引数に与えてdata_bag_item関数を実行することで、データをハッシュとして取得できる。今回の例では、以下のように配列のeachメソッドを使ってidを順番に取り出し、それを引数としてdata_bag_item関数を呼び出すことでデータを取り出している。

accounts.each do |id|
  item = data_bag_item('initial_accounts', id)

 data_bag_item関数の戻り値は、JSONで指定したものと同じデータ構造を持つハッシュとなっており、以下のように[‘<キー名>’]という書式で各項目を取得可能だ。

  item['groups'].each do |g|
    group g['name'] do
      gid g['gid']
      action :create
    end
  end

 作成したCookbookは前回解説したとおり「knife cookbook upload」コマンドでアップロードできる。

$ knife cookbook upload setup-user

 このCookbookをクライアント側で実行すると、以下のようにユーザーおよびグループが作成される。

# chef-client -o setup-user
Starting Chef Client, version 11.6.2
[2013-10-24T19:58:40+09:00] WARN: Run List override has been provided.
[2013-10-24T19:58:40+09:00] WARN: Original Run List: []
[2013-10-24T19:58:40+09:00] WARN: Overridden Run List: [recipe[setup-user]]
resolving cookbooks for run list: ["setup-user"]
Synchronizing Cookbooks:
  - setup-user
Compiling Cookbooks...
Converging 2 resources
Recipe: setup-user::default
  * group[taro] action create (up to date)
  * user[taro] action create
    - alter user user[taro]

Chef Client finished, 1 resources updated

 このように作成されたCookbookでは、Cookbook側を変更することなく、Data Bagのみを変更することで作成するユーザーを追加したり、その情報を変更することが可能だ。たとえば、新たに「jiro」というユーザーを追加したい場合、JSONファイルを以下のように修正すれば良い。

{
  "id": "default",
  "groups": [
    {
      "name": "hylom",
      "gid": 1000
    },
    {
      "name": "jiro",
      "gid": 1001
    }
  ],
  "users": [
    {
      "name": "hylom",
      "home": "/home/hylom",
      "password": "$6$OmC3KootOURrqOaP$63rwQ2bSE8op8wXa.ZWzgxm/iGvePTzEL5lOntmkPyYh5Qwh4lWs2DtyoEHcvsbYV5Q6a2ezzrZueb2ydrkhz0",
      "shell": "/bin/bash",
      "uid": 1000,
      "gid": "hylom"
    },
    {
      "name": "jiro",
      "home": "/home/jiro",
      "password": "$6$OmC3KootOURrqOaP$63rwQ2bSE8op8wXa.ZWzgxm/iGvePTzEL5lOntmkPyYh5Qwh4lWs2DtyoEHcvsbYV5Q6a2ezzrZueb2ydrkhz0",
      "shell": "/bin/bash",
      "uid": 1001,
      "gid": "jiro"
    }
  ]
}

 このJSONデータをData Bagに格納し、再度Cookbookを実行すると新たにユーザー「jiro」が作成される。

$ knife data bag from_file initial_accounts default.json
# chef-client -o setup-user
Starting Chef Client, version 11.6.2
[2013-10-24T20:08:31+09:00] WARN: Run List override has been provided.
[2013-10-24T20:08:31+09:00] WARN: Original Run List: [recipe[setup-user]]
[2013-10-24T20:08:31+09:00] WARN: Overridden Run List: [recipe[setup-user]]
resolving cookbooks for run list: ["setup-user"]
Synchronizing Cookbooks:
  - setup-user
Compiling Cookbooks...
Converging 4 resources
Recipe: setup-user::default
  * group[taro] action create (up to date)
  * group[jiro] action create
    - create group[jiro]

  * user[taro] action create (up to date)
  * user[jiro] action create
    - create user user[jiro]

Chef Client finished, 2 resources updated

データを暗号化する

 Data Bagではデータを暗号化した上で格納することも可能だ。パスワードやそのハッシュなど、閲覧されると問題が発生する可能性のあるデータを格納する場合などに利用できる。なお、暗号化にはopensslコマンドで作成した鍵ファイルを使用し、この鍵ファイルは設定対象のマシンそれぞれに格納しておく必要がある。公開鍵方式ではなく、1つの鍵ファイルで暗号化も復号もできるため、この鍵ファイルの管理には注意してほしい。

 暗号化に使用する鍵ファイルは、以下のようにして作成できる。

$ openssl rand -base64 512 > secret_key

 これで、secret_keyというファイルに鍵情報が格納される。

 データを暗号化してData Bagに格納するには、「knife data bag from file」コマンドを「–secret-file <鍵ファイル>」オプション付きで実行する。

$ knife data bag create initial_accounts_secret
Created data_bag[initial_accounts_secret]
$ knife data bag from file initial_accounts_secret default.json --secret-file secret_key
Updated data_bag_item[initial_accounts_secret::default]

 このように鍵ファイルを指定して格納したデータは、showコマンドで表示させてもその中身は分からない。

$ knife data bag show initial_accounts_secret default
groups:
  cipher:         aes-256-cbc
  encrypted_data: TLa/BNAc26FSpOABekf7ALvd3KhQ/iXZzggP/hSAiqBn3WYk63h7hX+Aguta
  0zMW

  iv:             v6fzk6WYzYayr6IGj7j38A==

  version:        1
id:     default
users:
  cipher:         aes-256-cbc
  encrypted_data: x98kLVBQr0k3YoDmOtyRWtbgpOMAEeZFGJJ2kAGeEz1JHc92a3fb/O+Ky/j1
  9kJRbgFDkrXBkN7UDLpXbAAx0YPbOhhXQdOs/kDXOyVsjztBRgzdJB/I/6ZO
  +LytQpz7px6M8Jk8sd2V1lGYgUFm7Fq+DQjXlo3SLKMPqGVLxjJjanHLtm2f
  zZa/op2kAFWlGDamhBoWwrXbPGNcWiBeArRPvMzJe4BT52/czse36WU5OyGz
  TcOKpWVhl03lnh5vZ/v+cthDLl+hxsWGuPUvGY3suIrxd2SKHDGKl+X+HWc=

  iv:             aYvs9pPh5wRPAoBGu+DDSQ==

  version:        1

 もし暗号化されたデータの内容を確認したい場合は、「–secret-file <鍵ファイル>で暗号化に使用した鍵ファイルを指定すれば良い。

$ knife data bag show --secret-file secret_key initial_accounts_secret default
groups:
  gid:  1000
  name: taro
id:     default
users:
  gid:      taro
  home:     /home/taro
  name:     taro
  password: $6$OmC3KootOURrqOaP$63rwQ2bSE8op8wXa.ZWzgxm/iGvePTzEL5lOntmkPyYh5Qwh4lWs2DtyoEHcvsbYV5Q6a2ezzrZueb2ydrkhz0
  shell:    /bin/bash
  uid:      1000

 いっぽう、暗号化されたData Bag項目にアクセスしたい設定対象サーバーには、あらかじめ鍵ファイルを配置しておく必要がある。鍵ファイルのデフォルトの保存先は、/etc/chef/encrypted_data_bag_secretだ。scpコマンドなどを利用してサーバーにコピーし、配置しておこう。

# cp secret_key /etc/chef/encrypted_data_bag_secret

 Recipeから暗号化されたData Bag項目にアクセスするには、data_bag_item関数の代わりにChef::EncryptedDataBagItem.load関数を使用する。以下の例は、先に紹介したRecipeを、暗号化されたData Bag項目を使用するよう書き換えたものだ。

accounts = data_bag('initial_accounts_secret')
accounts.each do |id|
  # item = data_bag_item('initial_accounts_secret', id)
  item = Chef::EncryptedDataBagItem.load('initial_accounts_secret', id)

  item['groups'].each do |g|
    group g['name'] do
      gid g['gid']
      action :create
    end
  end

  item['users'].each do |u|
    user u['name'] do
      home u['home']
      password u['password']
      shell u['shell']
      uid u['uid']
      gid u['gid']
      supports :manage_home => true
      action :create
    end
  end
end

Chef Solo環境でData Bagを使う

 Chef Serverを使用しないChef Solo環境ではデータを一元管理するデータベースがあるわけではなく、knifeコマンドも利用できない。その代わりとして、任意のディレクトリにJSONファイルを配置することでData Bagとして利用できるようになっている。

 Data Bagとして利用するディレクトリは、Chef Soloの設定ファイルである/etc/chef/solo.rb内で指定できる。たとえば「/root/chef-repo/data_bags」ディレクトリをData Bagの格納先として利用する場合、以下の行をsolo.rbファイルに追加しておけば良い。

data_bag_path "/root/chef-repo/data_bags"

 Chef Solo環境では、ここで指定したディレクトリ内にサブディレクトリを作成し、そこにJSONファイルを配置する。作成したサブディレクトリData Bag名として認識される。

 たとえば、/root/chef-repoというディレクトリをChefリポジトリとして使っている場合、「initial_accounts」というData Bagを作成するには以下のようにする。

# mkdir -p /root/chef-repo/data_bags/initial_accounts

 このディレクトリにJSONファイルを配置することで、Data Bag項目としてそこに記述されたデータにアクセスできる。

$ cp default.json /root/chef-repo/data_bags/initial_accounts/

Chef Server構築の手間なしに利用できるknife-soloは有力な選択肢に

 Data Bagを利用することで、作業内容と設定内容を分離して記述することが可能になる。これは、一般的なプログラミングにおけるプログラムとデータの分離に相当すると考えれば分かりやすいだろう。Data Bagを使うことでより保守性の高いCookbookを作成でき、また再利用も容易になる。やや導入は面倒であるが、うまく活用すると良いだろう。