[Chef-Solo]猿でもわかるdata_bag入門

このエントリーをはてなブックマークに追加

data_bagとは?何がおいしいの?

Chef-Soloには"data_bag"という仕組みがあって、ノード間をまたがって利用するグローバルな値を管理することができます。
よくあげられるのは、管理対象の各サーバに同じ名前のユーザを作成する例です。
data_bags/users/doraemon.json
data_bags/users/nobita.json
のようなファイルを作ってユーザ情報を記述すると、レシピからそれを参照できます。

data_bagを使ってサーバのIPアドレスを管理する

さてここでは、サーバのIPアドレスを"data_bag"に登録してレシピから呼び出す例を紹介します。
サーバのIPアドレスというのは一見ノードの属性のように思えますが、そうではないのです。
サーバ同士で連携してサービスを提供する場合、同じIPアドレスをあちこちに書くはめになったりします。
例えばdbサーバのiptablesにwebサーバのIPアドレスを書いたりしますよね。
"data_bag"を使えば、このような情報をノードをまたいで一元管理することができます。

Chef-Soloの記事で、よくsolo.rbに絶対パスを書いてる例をひじょ〜〜によく見かけます。
solo.rb
cookbook_path ["/home/hoge/chef-repo/cookbooks"]
しかしこのような書き方はポータビリティがありません。
ディレクトリ名が変わったり、別のサーバで動かそうとすると、この絶対パスをいちいち書き換えなくてはならないからです。

File.expand_path()を使おう

ほとんどの場合、chef-soloコマンドを実行したときのカレントディレクトリに"cookbooks"と"site-cookbooks"が置いてあると思います。
したがってこのように書けます。
solo.rb
base = File.expand_path('..', __FILE__)

cookbook_path [base + '/cookbooks', base + '/site-cookbooks']
PHP5.5(remi-php55レポジトリのやつ)を動かしたらこんなエラーが出ました。
PHP Warning:  PHP Startup: Unable to load dynamic library '/usr/lib64/php/modules/imagick.so' - liblcms2.so.2: cannot open shared object file: No such file or directory in Unknown on line 0

解決方法

wget http://pkgrepo.linuxtech.net/el6/release/x86_64/liblcms2-2.4-1.el6.x86_64.rpm
sudo yum install liblcms2-2.4-1.el6.x86_64.rpm
composer.jsonの"require-dev"という仕組みには落とし穴があります。
使うときは十分気をつけましょう。

そもそも"require-dev"とは何か?

本番環境で必要のない開発環境用のパッケージを記述する場合に使います。
{
  "require": {
    "symfony/http-foundation": "2.4.*",
  },
  "require-dev": {
    "phpunit/phpunit": "3.7.*"
  }
}
例えばこのように記述すると、本番では"http-foundation"のみを使い、開発環境ではそれに加えて"phpunit"も使う、というようなことが可能になります。

うっかり"require-dev"パッケージを呼び出すコードを書いてしまうと事故になる。

例えばプロジェクトでYAMLファイルを扱いたいと思って"symfony/yaml"を呼び出すコードを書いたとします。
use Symfony\Component\Yaml\Yaml;

$array = Yaml::parse($file);
このコードは開発環境ではバッチリ動いてしまいます。 なぜなら、phpunitが"symfony/yaml"に依存してるので、phpunitをインストールすると芋づる式に"symfony/yaml"が入るからです。

ところがこのプロジェクトを本番環境にデプロイすると、本番では"symfony/yaml"がインストールされていないために"Fatal Error"が発生してしまいます。

対処方法

根本解決が難しいのですが、
対策1:本番環境でも"composer install --dev"してrequire-devパッケージを入れてしまう
  • メリット:上記のような事故がおきない
  • デメリット:本番環境に無駄にパッケージを入れることになる。(インストール時間が長くなる、ディスク容量の無駄、等)
対策2:本番環境で、パッケージの存在有無をチェックするテストスクリプトを走らせる
  • メリット:本番にrequire-devを入れなくて済む。
  • デメリット:テストスクリプトの作成忘れ、実行忘れを防げない
対策3:開発者が気をつける
これはダメなやつや・・

PHPプログラマのみなさんはどうされているのでしょうか?
「自分はこうしてる」などの事例があればTwitterなどで教えていただけると幸いです。
こちらの記事を参考にして、C言語からlibcurlを使ってHTTP GETするコードを書いて動かしてみました。
"cURL と libcurl を使ってインターネット経由でやりとりする"

さて取得したHTTP Bodyを変数に格納するためにコールバックを使うのですが、コールバックの中身が思ったよりも複雑になっていたので、その理由を調べてみました。

C言語+libcurlだとなぜこんな複雑になるの?

PHPだと $content = file_get_contents($url); みたいなのでいけるのに、C言語+libcurlだとなぜこんな複雑になるのだろう?
ひょっとして、このコールバックは1回のRequest内で複数呼ばれるのではないか?と思って 標準エラー出力にログを吐いてみたら(fprintfのところ)、やはりそうでした。

1回のリクエストで17回もコールバックが呼ばれてしました。
LTSVのログビューワーltsview (http://d.hatena.ne.jp/naoya/20130207/1360229220) のC言語版を作りました。

ソースコードはGithubにおいてあります。
https://github.com/DQNEO/c-ltsview

やる前は難しそうだと思っていたのですが、書いてみたら意外と簡単で、100行ほどでできました。
こんな感じです。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define BUF_MAX 10240
#define KEYS_MAX 100

struct item {
    char *key;
    char *value;
};

void parse_item(struct item *item, char *key_value);
int in_array(char *s, char **strings);

int main(int argc, char **argv)
{
    char *keys[KEYS_MAX];
    memset(keys, 0, sizeof(keys));
    int i;
    char *concate_keys;

    /* parse -k options */
    if (argc >= 2 && strcmp(argv[1],"-k") == 0) {
	if (argc == 2) {
	    fprintf(stderr, "no argument for -k option\n");
	    exit(1);
	}

	concate_keys = argv[2];

	keys[0] = strtok(concate_keys, ",");
	if (keys[0] != NULL) {
	    i = 1;
	    while((keys[i] = strtok(NULL, ",")) != NULL) {
		i++;
	    }
	}
    }

    char buf[BUF_MAX];
    struct item items[KEYS_MAX];
    char *tab;
    char *tmp;
    char *newline;

    while (fgets(buf, BUF_MAX, stdin) != NULL) {
	printf("=========\n");

	if (buf[BUF_MAX -2] != '\0') {
	    fprintf(stderr, "buffer over run!\n");
	    return 1;
	}

	tmp = buf;
	i = 0;
	memset(items,0, sizeof(items)); // is this right?
	while ((tab = strchr(tmp, '\t')) != NULL ) {
	    *tab = '\0';
	    parse_item(&items[i++], tmp);
	    tmp = tab + 1;
	}
	newline = strchr(tmp, '\n');
	*newline = '\0';
	parse_item(&items[i], tmp);

	for (i = 0;items[i].key != NULL;i++) {
	    if (keys[0] != NULL && ! in_array(items[i].key, keys)) {
		continue;
	    }
	    printf("%s: %s\n", items[i].key, items[i].value);
	}
    }

    return 0;
}

void parse_item(struct item *item, char *key_value)
{
    char *colon;
    colon = strchr(key_value, ':');
    if (colon == NULL) {
	fprintf(stderr, "invalid format:[%s]", key_value);
	exit(1);
    }
    *colon = '\0';
    item->key = key_value;
    item->value = colon + 1;
}

int in_array(char *s, char **strings)
{
    int j;
    for (j = 0; strings[j] != NULL; j++) {
	if (strcmp(s, strings[j]) == 0) {
	    return 1;  // found
	}
    }

    return 0; // not found
}
C言語はまだほんのド素人なので、この書き方はおかしいなどありましたらPull Requestをお待ちしております。
Githubでレポジトリを作って.mdファイルベースでドキュメント管理する場合、文書間のリンクで相対パス指定ができます。

これを知らずに絶対パスを書いてしまってるケースが非常に多いです。
身の回りでみかけたら教えてあげましょう。

どういうこと?

例えば、
https://github.com/DQNEO/memo
というレポジトリがあったとして、
README.md から AboutMe.md にリンクを貼りたいとします。

このときREADME.mdにリンクを書くわけですが、やり方が2つあります。
絶対パス指定
[AboutMe](https://github.com/DQNEO/memo/blob/master/AboutMe.md)
相対パス指定
[AboutMe](/AboutMe.md)

絶対パスで書いたらなぜダメなのか

ウェブサイトを運営したことがある人ならすぐわかるかと思います。
  • レポジトリの名前が変わったとき
  • forkして所有者の名前が変わったとき
に、URLを全部書きなおさないといけなくなります。

よいみほん

https://github.com/DQNEO/memo/blob/master/README.md
相対パスの方が短いし、これを使わない手はないでしょう。

C言語でlsを実装してみた

このエントリーをはてなブックマークに追加
C言語で何か書きたいと思って、lsコマンドの簡易版を実装してみました。

今回新しい試みとして一切ぐぐらずにやってみたら、意外とあっさりできました。
man 3 opendir
man 3 readdir
とmanを見るだけで必要な情報は得られました。

ソースコードはGithubにも置いておきますのでご自由にお使いください。
https://github.com/DQNEO/c-ls
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>

/**
 * 実行方法: gcc ls.c && ./a.out /tmp
 */
int main(int argc, char *argv[])
{
    DIR *dir;
    struct dirent *ent;
    char dirname[256];

    if (argc == 1) {
        strcpy(dirname, ".");
    } else {
        strcpy(dirname, argv[1]);
    }

    dir = opendir(dirname);
    if (dir == NULL) {
        fprintf(stderr, "unable to opendir %s\n", dirname);
        return 1;
    }

    while ((ent = readdir(dir)) != NULL) {
        if (ent->d_name[0] == '.') {
            continue;
        }

        printf("%s  ", ent->d_name);
    }
    printf("\n");

    closedir(dir);
    return 0;
}
エラー処理とかがちょと甘いかもしれません。
ご利用は自己責任でお願いします。
Mac OSX (mavericks) でPerlを使うとこんな警告が出ました。
$ perl -v
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
        LC_ALL = "ja_JP.utf8",
        LC_CTYPE = "ja_JP.utf8",
        LANG = "ja_JP.utf8"
    are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").

This is perl 5, version 20, subversion 0 (v5.20.0) built for darwin-2level

原因と解決法

どうも、"ja_JP.utf8"という文字列が正しくなかったようです。
正しくは"ja_JP.UTF-8"のようです。
export LANG=ja_JP.UTF-8
export LC_ALL=ja_JP.UTF-8
と設定したら警告が消えました。

"utf8"だとなぜダメなの?

こちらの記事に詳しい解説があります。
ja_JP.UTF-8 vs ja_JP.utf8
brew install awscli
べんり!!

最新の開発版をインストルしたい場合はこう
brew install awscli --HEAD
参考
購読する

最近の人気記事

人気記事