tkak's tech blog

This is my technological memo.

Chefを学ぶのが面倒な人のInfrastructure as Code with JSON

最近、いかにChefの学習コストを小さくしつつ、組織にインフラのコード化(Infrastructure as Code)を導入するか、について悶々と考えていたので、まとめてみた。

Chefの辛み

インフラのコード化をするにあたって、Chefは最初に覚えることが多くてなかなか辛い、という話をよく耳にする。Chefの動作概念、Chefの各種リソースの使い方、Chefの周辺ツール群の使い方、Cookbookのコーディング規約など、Chefを使い始めるために覚えるべき内容は結構多い。組織が大きくなればなるほど学習コストはバカにならないし、インフラエンジニアに日常的にコードを書いてもらうことはなかなか難しい。そんな環境でInfrastructure as Codeを広めようとしても、現実的になかなかうまくいかない。インフラをコード化して業務改善がしたいのに、いつの間にかみんなにChefを広めることが目的になり、疲弊したりする。

Infrastructure as Code with JSON

そんな中、DevOpsの流れとは逆行するが、みんなみんながみんなChefのことわかってCookbookが書ける必要はないんじゃないかとふと思った。大規模な組織では業務内容が細分化されているし、Cookbookの作成は得意な人に任せて、最低限必要な変数(attribute)をJSON ファイルで定義してchefを実行する方法だけ学べばいいのではないかと。まさにInfrastructure as Code with ChefならぬInfrastructure as Code with JSONJSON を書いてインフラを管理する。Cookbookに比べてある程度柔軟性は無くなるが、最低限の学習コストでChefを導入することができ、業務の自動化ができそう。そうすれば、少しでも手作業による運用、筋肉運用を減らすことができるんじゃないだろうか。

インフラのコード化を進めていく段階を分類するとだいたいこんな感じになる。

  1. Chefなんて使わずにマンパワーにまかせた筋肉運用をする
  2. Chefはそんなに知らないけどJSONファイルが書けてcookbookが使える
  3. Chefのことをよく知ってて自由にcookbookが書ける

理想は3の人が当たり前にいることだが、いきなり1から3にいける人はそこまで多くないと思うし、まずは1から2に持っていくのが改善効果が期待できるし大事だと思う。

Cookbookを書かずにCookbookを使う

Cookbookを使うだけであれば、Berkshelfの使い方と、attributeとrun_listを学ぶだけだ。作業としてはBerkshelf を使ってcookbookをRubyのBundlerのようにgemを手元に取ってきて、Cookbookの実行に必要なattributeとrun_listをNodeのJSON ファイルに定義してChefを実行する。Chefを実行するだけならChefのCookbookを書く必要はない。以下、使い方を紹介したい。なお、紹介のなかで使うNodeのJSONファイルなどはGithubに置いておいた。

1. BerkshelfでCookbooksを取得する

まず、使いたいCookbookを選ぶ。ChefコミュニティのCookbookを使ってもいいし、社内で使ってるCookbookでもいい。Cookbookの取得元は以下の通り。

  • コミュニティCookbook置き場 - Supermarket
  • 社内のCookbook置き場 - Private supermarketかBerkshelf APIのエンドポイント

timezone-ii Cookbookを使う場合のBerksfileはこんな感じ。

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

cookbook "timezone-ii"

社内のCookbookを使う場合は、sourceに社内のエンドポイントを指定する。

source "https://shanai.example.com/"

cookbook "hogehoge"
cookbook "fugafuga"
cookbook "piyopiyo"

Berksfileを作ったらberksコマンドでcookbookをローカルに取ってくる。まるでRubyのBundlerのように。以下の例では取得先をcookbooksディレクトリにしている。

$ berks vendor cookbooks
Resolving cookbook dependencies...
Fetching cookbook index from https://supermarket.chef.io...
Using timezone-ii (0.2.0)
Vendoring timezone-ii (0.2.0) to cookbooks/timezone-ii

$ tree -L 2
.
├── Berksfile
├── Berksfile.lock
└── cookbooks
    └── timezone-ii

2. NodeのJSONファイルを定義する

次にChefを実行するために必要なNodeのJSONファイルを作成する。Nodeの設定に必要な可変値(attribute)や、どのrecipeを実行するか(run_list)などを記載する。その際、きちんと使いたいCookbookのドキュメントを読んで、使えるAttributeやrecipeを確認する。

supermarket.chef.io

timezone-iiのcookbookを実行する例だと以下のようになる。

{
    "name": "my_node",
    "tz": "Asia/Tokyo",
    "run_list": [
        "recipe[timezone-ii]"
    ]
}

nodesディレクトリを掘ってそこにJSONファイルをおく。

$ tree -L 2
.
├── Berksfile
├── Berksfile.lock
├── cookbooks
│   └── timezone-ii
└── nodes
    └── my_node.json

3. Chefを実行する

あとはChefを実行するだけ。Chefの実行はchef-solo、chef-client、knife-soloなど、いろんな方法があるが、ここではVagrantで作ったVM上で、chef-clientのlocalモードを実行する例を示す。

まず、以下のようなchefを実行するのに必要な設定ファイルclient.rbを用意する。

cookbook_path '/vagrant/cookbooks'

ちなみに、社内環境とかProxy配下の環境で試す場合は以下のようなファイルになる。chef-zeroでproxyを使う場合はno_proxylocalhostを入れることがポイント。

http_proxy 'http://proxy.example.coml:1234'
no_proxy 'localhost'
cookbook_path '/vagrant/cookbooks'

最後にvagrantVMを立ててchefを実行する。

$ vagrant box add tkak/centos-6.6-x86_64-chef-dk
$ vagrant init tkak/centos-6.6-x86_64-chef-dk
$ vagrant up
$ vagrant ssh

[vagrant@localhost ~]$ date
Sun Apr  5 04:19:24 UTC 2015
[vagrant@localhost ~]$ sudo chef-client -z -j /vagrant/nodes/my_node.json -c /vagrant/conf/client.rb
Starting Chef Client, version 12.0.3
resolving cookbooks for run list: ["timezone-ii"]
Synchronizing Cookbooks:
  - timezone-ii
Compiling Cookbooks...
Converging 4 resources
Recipe: timezone-ii::default
  * yum_package[tzdata] action install (up to date)
  * log[Linux platform 'centos' is unknown to this recipe; using generic Linux method] action write (skipped due to not_if)
Recipe: timezone-ii::linux-generic
  * ruby_block[confirm timezone] action run
    - execute the ruby block confirm timezone
  * file[/etc/localtime] action create
    - update content in file /etc/localtime from ab1ddb to 0bc4b3
    (current file is binary, diff output suppressed)
    - restore selinux security context

Running handlers:
Running handlers complete
Chef Client finished, 2/3 resources updated in 10.584156048 seconds
[vagrant@localhost ~]$ date
Sun Apr  5 13:21:12 JST 2015
[vagrant@localhost ~]$ 

まとめ

ChefのCookbookを書かずにJSONとBerkshelf だけでChefを実行する方法について書いた。もちろんChefをきちんと理解してCookbookが書ける方が望ましいが、Infrastructure as Codeに馴染みがない人やこれから学びたいという人は、とりあえずChefのcookbook使ってみるところからはじめるのがいいと思う。大事なのはChefを使うことではなく業務を改善することなので、まずはCookbookを使ってみて少しでも手作業をなくすことが大切だと思う。

TerraformのProviderを作った

Terraform v2.0から、オリジナルProviderを作るためのフレームワーク機能が追加されている。今回この機能を使ってオレオレProviderを作ったので、やったことをまとめておく。

Go API library

まず、TerraformのProviderを作る前に、自分が使いたいクラウドサービスのGo API libraryを探す。なければ自分で作る。

今回はGMO ConoHaクラウド用のAPI libraryを作った。ConoHaクラウドは今のところオブジェクトストレージ(OpenStack Swift)APIしか提供していなかったので、オブジェクトストレージを操作する機能だけのものになっている。

また、他のクラウドサービス向けのプラグインを作る時の参考用に、簡単なAPI client libraryの型みたいな物も作ってみた。

Terraform Provider

Client libraryが準備できたら、次に、Terraform Providerを作る。terraform-provider-xxxっていう名前のリポジトリを作って、main.goprovider.goconfig.goresource_xxx.goのファイルを用意する。Providerは基本的にこの4種類のファイルで構成されている。(詳細はGitHubを参照)

以下、ちょっとした解説。

main.go

package main
import (
    "github.com/hashicorp/terraform/plugin"
    "github.com/tkak/terraform-provider-conoha/conoha"
)
func main() {
    plugin.Serve(&plugin.ServeOpts{
        ProviderFunc: conoha.Provider,
    })
}

main.goファイルには、plugin.Serve関数を呼ぶだけの処理を書く。オリジナルProviderをプラグインとしてTerraformに組み込むためのもの。

provider.go

// Provider returns a terraform.ResourceProvider.
func Provider() terraform.ResourceProvider {
    return &schema.Provider{
        Schema: map[string]*schema.Schema{
            "tenant": &schema.Schema{
                Type:     schema.TypeString,
                Required: true,
            },
            "user": &schema.Schema{
                Type:     schema.TypeString,
                Required: true,
            },
            "password": &schema.Schema{
                Type:     schema.TypeString,
                Required: true,
            },
        },
        ResourcesMap: map[string]*schema.Resource{
            "conoha_container": resourceConohaContainer(),
        },
        ConfigureFunc: providerConfigure,
    }
}

provider.goファイルには、helper/schemaライブラリを使って、Provider関数を定義する。ここでは、.tfファイル内で使う、provider用の変数を定義したり、conoha_containerなど、独自のresourceの宣言を行う。例えば、上のProvider関数の定義だとこんな.tfファイルになる。

provider "conoha" {
    user = "hoge"
    password = "hoge123"
    tenant = "hoge"
}

簡単なバリデーションであればhelper/schemaのTypeやRequiredなどを使ってシンプルに書くことができる。

config.go

type Config struct {
    Tenant   string `mapstructure:"tenant"`
    User     string `mapstructure:"user"`
    Password string `mapstructure:"password"`
    Token    string `mapstructure:"token"`
    Endpoint string `mapstructure:"endpoint"`
}

func (c *Config) Client() (*conoha.Client, error) {
...
}

config.goファイルでは、client libraryを使うためのConfig structを定義したり、Client関数を定義したりする。変数の詳細なバリデーションも必要あればここに書いたりする。

resource_conoha_container.go

func resourceConohaContainer() *schema.Resource {
    return &schema.Resource{
        Create: resourceConohaContainerCreate,
        Read:   resourceConohaContainerRead,
        Delete: resourceConohaContainerDelete,

        Schema: map[string]*schema.Schema{
            "name": &schema.Schema{
                Type:     schema.TypeString,
                Required: true,
                ForceNew: true,
            },
        },
    }
}

func resourceConohaContainerCreate(d *schema.ResourceData, meta interface{}) error {

    client := meta.(*conoha.Client)

    c := &conoha.Container{
        Name: d.Get("name").(string),
    }

    err := client.CreateContainer(c)
    if err != nil {
        return fmt.Errorf("Error creating container: %s", err)
    }

    d.Set("name", c.Name)
    d.SetId(c.Name)

    return nil
}

...

resource_xxx.goファイルには、resourceで扱う変数の定義やAPI周りのメイン処理を書く。上のresourceConohaContainer()だと、.tfファイルのresource定義はこのようになる。

resource "conoha_container" "example" {
    name = "fuga"
}

また、*schema.ResourceDataSet関数やSetId関数を使うことで、Terraform内で扱う任意の変数やリソースIDなどを設定することができる。

Usage

オリジナルProviderの動かし方の説明も書いておく。

Install

TerraformのProviderは、terraformのバイナリファイルに組み込まれているのではなく、Providerごと別なバイナリファイルになっている。インストールはgo buildしたあとterraform-provider-<provider名>という名前でTerraformの実行ファイルと同じディレクトリ配下($GOPATH/bin配下)に配置する。要は直接go installを叩くのと一緒。そうすると、Terraformコマンドを実行した時にProviderのバイナリファイルが読み込まれる。インストール手順はこんな感じ。

$ go get github.com/tkak/conoha
$ go get github.com/tkak/terraform-provider-conoha
$ go install github.com/tkak/terraform-provider-conoha

terraform apply

あとは、.tfを作って、実行するだけ。今回作ったProviderだと以下のような.tfファイルを作成し、terraform applyコマンドを実行する。

$ vi conoha.tf
---
variable "conoha_user" {}
variable "conoha_password" {}
variable "conoha_tenant" {}

provider "conoha" {
    user = "${var.conoha_user}"
    password = "${var.conoha_password}"
    tenant = "${var.conoha_tenant}"
}

resource "conoha_container" "example" {
    name = "hoge"
}
---
$ terraform plan \
-var "conoha_user=${CONOHA_USER}" \
-var "conoha_password=${CONOHA_PASSWORD}" \
-var "conoha_tenant=${CONOHA_TENANT}"

パスワードなどのパラメータはファイルとしてバージョン管理したくないのでCONOHA_*環境変数で設定して実行する。

ちなみに、環境変数TF_LOG=1を設定すると実行時にデバッグログをはくようにすることもできる。

まとめ

Goは最近勉強しはじめたばかりで、今回作ったライブラリやProviderはまだまだ改良する点があるが、ひとまず動く物を作ることができた。実際作るのにかかった時間はそんなにはなくて、APIの調査やTerraform のコードを読んだりした時間の方がかかった。Mitchell Hashimoto先生曰くGoogle CloudのProviderは8時間しかかからなかったらしい。慣れたらかなり簡単にオリジナルProviderが作れると思う。

プラグインごとの細かい作り込みは必要だけど、いろんなクラウドサービスのProviderを用意すれば、それぞれを共通のファイル形式で一元管理できる。 そんなツールは今までありそうでなかったし、Infrastructure as Codeの痒いところに手が届くツールとして、Terraformは中々優れているものなんじゃないかなと思う。

References

TerraformのGoogle Cloud providerを試してみた。

先日参加したgcp ja night #28 - connpassで、 Google Cloud Platformを$500分無料で使えるクーポンをいただいたので、 Terraform by HashiCorpGoogle Cloud providerを試してみた。

Terraformは、Vagrantとかインフラ便利ツールを作っているHashicoap社の新作。 インフラの状態をDSL形式で定義できて(Infrastructure as Code)、CLIからインフラの状態を変更することができる。

ChefやPuppetとは守備範囲が違って、VM作ったり、バランサの設定したり、DNSを登録したりする作業を自動化する。 守備範囲的には、Chef-metalとかAWS CloudFormationとかとかぶる感じ。

いろんなクラウドサービスを、同じフォーマットのファイルで管理することができるのが大きな強み。 今巷でバズってるMicroservices的な考えで、数多あるクラウドサービスを一元管理できるってなんて夢のツールだろう。

Terraformは、v0.2.2を使用。

すでに試している方がいるので、それも参考に。

Terraform 0.2 で Google Compute Engine を試してみた (初級編) - Qiita

試してみたファイルはGithubに。

tkak/terraform-demo · GitHub

準備

Google Developers Consoleからaccount.jsonとclient_secrets.jsonをダウンロードする。初めて使うので、どこからファイルをダウンロードすればいいかわからなくて少しはまった。古いDashboardに画面遷移する必要があった。

account.json

Google Cloudのアカウント証明書。

"APIs & auth" -> "Credentials"のページ、"OAuth"の"Generate new JSON key"ボタンからダウンロード。もしまだOAuth Client IDを作ってなかったら、"Create new Client ID"ボタンから新しく作成してからJSONファイルを作成する。

client_secrets.json

Google Cloud APIsを利用するためのクライアント情報。

"APIs & auth" -> "APIs"のページ、歯車マークのボタンから別ページに遷移して、"API Access"のページ、"Download JSON"からダウンロードする。

2つのファイルは適当に作業ディレクトリ配下に。

$ ls
account.json client_secrets.json

instanceの作成。

まず試しにinstanceを一つ作ってみる。

作業ディレクトリにtfファイルを作成する。

$ vim google_compute.tf
provider "google" {
    account_file = "./account.json"
    client_secrets_file = "./client_secrets.json"
    project = "tkakfrkw"
    region = "asia-east1-a"
}

resource "google_compute_instance" "default" {
    name = "test"
    machine_type = "f1-micro"
    zone = "asia-east1-b"

    disk {
        image = "debian-7-wheezy-v20140814"
    }

    network {
        source = "default"
    }
}

planでこれから行う作業の確認。

$ terraform plan
Refreshing Terraform state prior to plan...


The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ google_compute_instance.default
    disk.#:                     "" => "1"
    disk.0.image:               "" => "debian-7-wheezy-v20140814"
    machine_type:               "" => "f1-micro"
    metadata_fingerprint:       "" => "<computed>"
    name:                       "" => "test"
    network.#:                  "" => "1"
    network.0.internal_address: "" => "<computed>"
    network.0.name:             "" => "<computed>"
    network.0.source:           "" => "default"
    tags_fingerprint:           "" => "<computed>"
    zone:                       "" => "asia-east1-b"

applyで実際の処理を実行。

$ terraform apply
google_compute_instance.default: Creating...
  disk.#:                     "" => "1"
  disk.0.image:               "" => "debian-7-wheezy-v20140814"
  machine_type:               "" => "f1-micro"
  metadata_fingerprint:       "" => "<computed>"
  name:                       "" => "test"
  network.#:                  "" => "1"
  network.0.internal_address: "" => "<computed>"
  network.0.name:             "" => "<computed>"
  network.0.source:           "" => "default"
  tags_fingerprint:           "" => "<computed>"
  zone:                       "" => "asia-east1-b"
google_compute_instance.default: Creation complete

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

showでインフラの状態を確認。

$ terraform show terraform.tfstate
google_compute_instance.default:
  id = test
  disk.# = 1
  disk.0.image = debian-7-wheezy-v20140814
  machine_type = f1-micro
  metadata_fingerprint = iyTlDGmC25M=
  name = test
  network.# = 1
  network.0.internal_address = 10.240.150.255
  network.0.name = nic0
  network.0.source = default
  tags_fingerprint = 42WmSpB8rSM=
  zone = asia-east1-b

f:id:keepkeptkept:20141005150747p:plain

できてる、できてる。

ちなみに、terraform.tfstateファイルはバイナリファイル。v0.3.0からはjsonになるっぽい。

$ file terraform.tfstate
terraform.tfstate: data

instanceの削除

instanceを削除してみる。

instanceの部分をコメントアウト

$ vim google_compute.tf
provider "google" {
    account_file = "./account.json"
    client_secrets_file = "./client_secrets.json"
    project = "tkakfrkw"
    region = "asia-east1-a"
}

/*
resource "google_compute_instance" "default" {
    name = "test"
    machine_type = "f1-micro"
    zone = "asia-east1-b"

    disk {
        image = "debian-7-wheezy-v20140814"
    }

    network {
        source = "default"
    }
}
*/

plan, apply, show。

$ terraform plan
efreshing Terraform state prior to plan...

google_compute_instance.default: Refreshing state... (ID: test)

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

- google_compute_instance.default

$ terraform apply
google_compute_instance.default: Refreshing state... (ID: test)
google_compute_instance.default: Destroying...
google_compute_instance.default: Destruction complete

Apply complete! Resources: 0 added, 0 changed, 1 destroyed.

$ terraform show
The state file is empty. No resources are represented.

JSON形式で試す

tfファイルはJSON形式でも定義できるので、拡張子.tf.jsonのファイルを用意する。上のtfファイルと同じ処理をするJSONは以下の通り。

$ vim google_compute.tf.json
{
    "provider": {
        "google": {
            "account_file": "./account.json",
            "client_secrets_file": "./client_secrets.json",
            "project": "tkakfrkw",
            "region": "asia-east1-a"
        }
    },

    "resource": {
        "google_compute_instance": {
            "default": {
                "name": "test",
                "machine_type": "f1-micro",
                "zone": "asia-east1-b",

                "disk": {
                    "image": "debian-7-wheezy-v20140814"
                },

                "network": {
                    "source": "default"
                }
            }
        }
    }
}

plan, apply, showしてみたが特に問題なく実行できた。JSON形式対応してくれるのはかなりいい機能だと思う。 他のサービスからJSON出力させて、botにterraformを実行させるみたいなこともできそう。

まとめ

ここ最近Terraformっぽいツールが欲しいと思っていて自作しようとしてたところに、彗星のごとくMichell Hashimoto先生の新作が降ってきた。最近ではAWS以外にもGoogleMicrosoft、Degital Oceanなどのクラウドサービスがよくなってきていて、ハイブリッドにクラウドを利用する時代がきているのかもしれない。そんな時代にあった良いツールを出してくるHashimoto先生はさすがだと思った。

Packer + ChefDK + Docker(kitchen-docker)で、Chef Cookbookの開発環境整備を楽にする。

ChefのCookbookを書き始めるには結構準備することが多くて、Cookbookの開発環境を作るのがなかなかつらい。手元のPCにVirtualBoxVagrantを用意し、rbenvやBundlerの使い方を学び、必要なgemをインストールする。Chefだけでも学習コストが高いのに、周辺のツールを使いこなすのにさらに学習コストがかかる。しかも、社内にはWindowsユーザとMacユーザが混在してるので、その辺を仮想環境を使ってうまく抽象化したい。

そんな動機から、Packer + ChefDK + Docker(kitchen-docker)で、気軽にChefのCookbook開発を始められる環境を作ってみた。これでCookbook開発が捗る!(詳細はgithubを参照。)

今更Chef Cookbookベストプラクティス

去年Chefが大流行して今更Chefの話を書くのも恥ずかしい気がするけど、@kajikenからリクエストがあったので、僕が思うChefのCookbookのベストプラクティスを書いてみる。

Berkshelf way

まずはBerkshelf について。BerkshelfはCookbookの依存関係を解決してくれる便利ツール。基本的にCookbookはプロジェクトとは別なリポジトリで管理して、プロジェクトごとに必要なCookbookをBerkshelfで取ってきて使うのがいいと思う。Cookbookのバージョン管理を厳密にできるので、毎回同じサーバ環境が構築できる。あと、人が作ったCookbookを使い回せるので、車輪の再発明しなくてすむ。素晴らしい。

ただ、Berkshelfはバージョンが3系になって、Berkshelf用のAPIサーバを立てないといけないので、僕は未だに2系を使ってる。Chefを使う人や管理するCookbookが多くなってきたらAPIサーバをたてたほうがいいと思う。

あと、これはちょっとしたTipsになるけど、Berksfileはrubyのファイルになってるので、cookbookごとにgitのブランチを指定するよりも、こんな感じできれいにかくことができる。

# before
cookbook 'mysql', git: 'https://github.com/tkak/hoge.git'
cookbook 'nginx', git: 'https://github.com/tkak/fuga.git'
cookbook 'vim', git: 'https://github.com/tkak/piyo.git'


# after
def oreno_cookbook(name, options = {})
  cookbook(name, {
    git: "https://github.com/tkak/#{name}.git"
  }.merge(options))
end

oreno_cookbook 'hoge'
oreno_cookbook 'fuga'
oreno_cookbook 'piyo'

Cookbookの粒度

基本的に一つのCookbookは一つのリポジトリで管理するべき。馬鹿でかいCookbookを作ったりするとメンテナンスが大変になることが多いので避けた方がいい。ただ、一つのCookbookにどこまで処理を盛り込めばいいのか、Cookbookの粒度について時々迷うことがある。とりあえず、最初から大きなCookbookにするのではなく、最低限の機能を持つCookbookを何個か作って、そのあとそれらをまとめたCookbookを作ればいいと思う。

Cookbookをタイプ別にわけるとこんな感じになる。

Element Cookbook

最低限の機能、それ自体で完結するCookbook。

Library Cookbook

recipeが存在しない、Definitions, LWRPs, Librariesで構成されたCookbook。

Wrapper Cookbook

他のCookbookをラッピングするCookbook。

Role Cookbooks

Roleを定義するためのCookbook。基本的にRoleは使わない、バージョン管理ができないから。代わりにRole Cookbookを使う。

# oreno_app/recipes/base.rb
include_recipe "hoge"
include_recipe "fuga"
include_recipe "piyo"

# oreno_app/metadata.rb
name             'oreno_app'
maintainer       'tkak'
maintainer_email 'hoge@mail.com'
license          'All rights reserved'
description      'role cookbook'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version          '0.0.1'
depends          'hoge'
depends          'fuga'
depends          'piyo'

Cookbookの名前

Cookbookの名前は、-(ハイフン)を使わない。-だとLWRPがうまく動かないっていう地雷があるので、代わりに_(アンダースコア)を使ってる。あと、名前は動詞ではなく名詞になるようにしてる。

chef-repoの構成

oreno-chef-repo/
├── Berksfile
├── Gemfile
├── README.md
├── conf
├── cookbooks
│   ├── fuga
│   ├── hoge
│   └── piyo
├── data_bags
├── environments
└── site-cookbooks
    ├── oreno_app
    └── oreno_db

http://shibayu36.hatenablog.com/entry/2014/08/04/073000chefリポジトリ構成はこんな感じ。特にcookbookssite-cookbooksは、Berkshelfで取ってきたCookbookかそうでないかの違いで分けてる。cookbooksにはBerkshelfで取ってきたcookbooks、site-cookbooksには自分のアプリ用のcookbooksを配置する。Berkshelfのデフォルトでは、.cookbooks配下にCookbookが置かれるが、cookbookの場所を明確にしておきたいのであえてberks install --path cookbooksでインストールしている。

CookbookのTestについて

CookbookのTestはServerspecを使ってる。深淵な理由でTest-kitchenは使ってない。ProxyとかProxyとかProxyとか。CIは、Jenkins + Vagrant + Chef + Foodcritic + Serverspecでまわしてる。よくある感じ。

アプリケーションレイヤーのCookbookはDockerを使って開発するのがいいと思ってはいるが、まだVMを多用している。毎回VMの起動に時間がかかるので早くコンテナを導入したい反面、OSのカーネルに依存するような部分のCookbookに関しては地道にVMを使うしかないのかなぁ、なんて感じてる。適材適所で使うのは全然ありだと思う。

まとめ

2、3年前くらいからChefを使い初めたんだけど、何度Cookbookを書き直したか分からない…w 試行錯誤してようやく今の形に落ち着いた感じ。これからChefを導入する人やCookbookを書き直す人の参加になれば幸いです。もう少し知見ある気がするけど、疲れたので今日はこの辺で。でわでわ。

あ、Chefの使い方とかかなり詳しく知りたい時は、「Chef活用ガイド」がオススメです。かなり細かくChefについて書いてあります。ちょっとした国語辞典並みの厚さですがw よかったらぜひ手に取ってみてください。

Chef活用ガイド コードではじめる構成管理

Chef活用ガイド コードではじめる構成管理

VagrantでVMware vSphere(ESXi)上のVMを操作する「vagrant-vsphere」を試してみた。

Vagrantのproviderは、VirtualBoxAWSなど様々ありますが、VMware vSphere(ESXi)用のvagrant-vsphereを試してみたので備忘録として残しておきます。

Installation

まずは、vagrant-vsphere pluginのインストールから。githubのREADMEに書いてますが一応。

$ vagrant plugin install vagrant-vsphere

Prepare dummy box

次にダミー用のboxイメージを作成します。vsphereを使う場合は、virtualboxとは違ってローカルにboxイメージは置きません。

$ cd ~/.vagrant.d/gems/gems/vagrant-vsphere-0.8.2/example_box/

example_boxディレクトリ配下にmetadata.jsonファイルがあるので、それをtarコマンドで固めます。

$ cat ./metadata.json
{
  "provider": "vSphere"
}

$ tar cvzf dummy.box ./metadata.json

作ったboxは、適当なディレクトリに配置します。例えばプロジェクトのrootに置く場合。

$ ls
Vagrantfile
Vagrantfile.virtualbox
Vagrantfile.vsphere
dummy.box
cookbooks/
nodes/
roles/
conf/
...
...

Prepare a template in vShpere

次に、VMのひな形になるboxを用意します。以下のリンクを参考に、vSphere上にクローン用のテンプレートイメージとCustomization Specificationを作成します。

Vagrant up

準備が終わったのでVMを作成します。 Vagrantfileはこんな感じで用意します。

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

Vagrant.configure("2") do |config|
  config.vm.box = 'dummy'
  config.vm.box_url = './dummy.box'
  config.vm.synced_folder ENV["SYNCED_FOLDER"], "/vagrant"
  config.vm.network 'private_network', ip: 'x.x.x.x'
  config.ssh.host = 'x.x.x.x'

  config.vm.provider :vsphere do |vsphere|
    vsphere.host = 'HOST NAME OF YOUR VSPHERE INSTANCE'
    vsphere.data_center_name = 'YOUR DATACENTER'
    vsphere.compute_resource_name = 'YOUR COMPUTE RESOURCE'
    vsphere.resource_pool_name = 'YOUR RESOURCE POOL'
    vsphere.data_store_name = 'YOUR DATA STORE'
    vsphere.template_name = 'YOUR VM TEMPLATE'
    vsphere.name = 'NEW VM NAME'
    vsphere.user = 'YOUR VMWARE USER'
    vsphere.password = ENV["VSPHERE_PASSWORD"]
    vsphere.insecure = true
    vsphere.template_name = 'YOUR TEMPLATE NAME'
    vsphere.customization_spec_name = 'YOUR CUSTOMIZATION SPEC'
  end

end

ENV["SYNCED_FOLDER"]とENV["VSPHERE_PASSWORD"]は、Vagrantfileに直接書きたくないので、環境変数にしてます。 VMを作成する時に指定する感じです。

$ export SYNCED_FOLDER=/your/path
$ VSPHERE_PASSWORD=**** vagrant up --provider=vsphere
$ vagrant ssh

あとは普通に、vagrant sshvagrant destroyとか動きます。vagrant自体の細かい使い方は省略します。

Wrap up

vagrant-vsphereを試してみました。vSphere上のVM作成をコマンドラインからやりたいときに便利です。 (fogのvsphere providerも試してみたんですが、うまく動かなかったのでこっちにしました。)

今では、Chef cookbookのCI環境用に使ってます。 このplugin自体あんまり使ってる事例とかがなくて、試行錯誤しながらとりあえず動くところまではできたのでよかったです。 同じように困っている人がこのブログがお役にたてれば幸いです。ではでは。

References

Proxy環境下でDockerを使う。

Proxy環境下でDockerを使ってみたのでメモ。

使った環境はこんな感じ。

Dockerデーモンを起動するときに、環境変数でproxyの設定をする。

# http_proxy=http://hoge:1234 docker -d &

起動スクリプトからDockerを起動するときは、/etc/sysconfig/dockerファイルでproxyの設定を行う。

# cat > /etc/sysconfig/docker
export http_proxy="http://hoge:1234/"
# service docker start

追記2 (Jul 10, 2014)

CentOS 7.0編

  • OS: CentOS 7.0 1406 x86_64
  • Kernel: 3.10.0-123
  • Docker: 1.0.0-1
# cp /usr/lib/systemd/system/docker.service /etc/systemd/system/
# vi /etc/systemd/system/docker.service
...
...
Environment=‘http_proxy=http://hoge:1234/’  ## ExecStartの上に追記
ExecStart=/usr/bin/docker -d --selinux-enabled -H fd://
…
...
# systemctl daemon-reload
# systemctl restart docker

追記 (Jul 5, 2014)

Ubuntu 14.04編

  • OS: Ubuntu 14.04 amd64
  • Kernel: 3.13.0-24-generic
  • Docker: 1.1.0
$ sudo sh -c "echo 'export http_proxy=http://hoge:1234/' >> /etc/default/docker"
$ sudo service docker restart