Puppet設定ガイドメモ

これのこと
Configuring Puppet — Documentation — Puppet Labs

Configuration Guide

初回設定の方法が書かれている

サーバとクライアントのファイアウオールポート開放

下記をサーバ/クライアントの両方で開放する

  • TCP/UDPポート: 8140

設定ファイル

  • メイン設定ファイル: /etc/puppet.conf
    • yumとかpackage baseでインストールしたなら、自動で作られてるはず

設定できるパラメータとデフォルト値を見るには、下記のコマンドを実行すると表示される

$ /usr/bin/puppet --genconfig

DNSの設定

  • puppetクライアントはサーバ名:puppetを参照する
  • localのDNSを構築しているなら、サーバにCNAMEを設定したほうがよい
puppet   IN   CNAME  crabcake.picnic.edu.

CNAMEを指定すれば、いちいちそれぞれのクライアントでサーバを指定しなくても済む


CNAMEレコードの設定は、DNS and Bind本を参考にするといいだろう

  • CNAMEレコードを追加後、nameサーバをリスタート(非DynamicDNSの場合)

DynamicDNSなら、下記のようにコマンドで設定(再起動不要)

$ nsupdate -k <tsig_key_file> -d
> server ns.picnic.edu
> zone picnic.edu
> update add puppet.picnic.edu. 3600 CNAME crabcake.picnic.edu.
> send
  • サーバとクライアントの両方の/etc/hostsにホストエントリを追加することも可能

For the server:

127.0.0.1 localhost.localdomain localhost puppet

For the clients:

192.168.1.67 crabcake.picnic.edu crabcake puppet

WARNING: もし、puppetに対してpingできても、クライアントからサーバに接続できないというログが出力されていたら、サーバでport 8140が開放されているか確認する

Puppet Lauguage設定

Site Manifest作成
  • Puppetは、宣言型システム
  • puppet独自コードで記述されているmanifestを使う
  • サイト全体の設定を中心に管理するのは、site manifest
    • manifestは、複数に分割してもよい
    • nodeごとの個々のシステムの差異は取り除かれるべき
  • Puppetは、Primaryのmanifestとして/etc/puppet/manifests/site.ppを使う
    • /etc/puppet/manifestsを作成して、そこにmanifestを追加する
    • manifestの変更trackのために、scm(git, svnとか)を使うことを強く推奨
Manifestの例

とっかかりとして、sudoersファイルの適切なパーミッションを保持する例

# site.pp
file { "/etc/sudoers":
    owner => root, group => root, mode => 440
}

Start the Central Daemon

  • 多くは一つのサーバでいいはず
  • Puppet Labsが公開しているドキュメントにスケールアウトとフェールオーバーについてのベストプラクティスがあるので、必要に応じて追加すればいい
  • サーバの決定: puppetmasterが動作
    • puppetmasterはinitスクリプトで自動起動した方がよさげ
    • 必要に応じて、daemonで使うpuppetユーザとグループを作成する

daemon起動時に作成する場合

# /usr/sbin/puppetmasterd --mkusers
  • puppet daemonを起動すれば、必要な証明書、ディレクトリ、ファイルは自動的に作成される

インストールの確認

  • Puppetは、クライアントをサーバに対してすべてが正常に動作するか確認を行うことができる
  • 最初のクライアントは、--waitforcertフラグを有効にすべき(default: 120秒)
# puppetd --server myserver.domain.com --waitforcert 60 --test
  • --testフラグは、フォアグラウンドで出力を表示でき、一回のみ動作する

クライアント動作中のメッセージ

info: Requesting certificate
warning: peer certificate won't be verified in this SSL session
notice: Did not receive certificate
  • 自動署名でなければ、上のメッセージが60秒おきにくりかえされる?
  • 署名待ちリストをサーバで確認する方法
# puppetca --list
  • クライアント名が表示されるはず
  • 手動で署名
# puppetca --sign mytestclient.domain.com

※ waitforcertはdefault:5分らしいが、CentOS5.4環境でrpmインストールしたpuppetでは2分になってる

GitのTagへ署名してみる

はじめに

今まで、ある程度まとまった機能が開発できたら、タグ付けするということを繰り返してきましたが、
今まで使用していたのは、軽量版のタグだということに気がつきちょっと調べて見ると、タグには注釈を付けることができ、さらにそれに対して署名が可能だということがわかりました。

これは、開発者であるコミッターとリリースタグを付けるリリースマネージャみたいな異なる人がいる場合に有効な仕組みだと思います。

作成した注釈+署名付きタグを、git showコマンドで確認すると、コミットの情報の上に、タグの情報と署名が表示されます。

{タグ作成者の情報}
{注釈メッセージ}
{署名}
{コミットの情報}

環境

MacOSX v10.6

GnuPGやGitは導入済みとします。

前準備

Gitに署名用のGPG秘密鍵を登録します。$HOME/.gitconfigに直接記述することもできるのですが、コマンドで登録した方が、Syntaxエラー等を防げるのでコマンドで登録します。

$ git config --global user.signingkey 1234FEDC

署名付きのタグを作成

下記のコマンドで署名付きタグを作成できます。(既に作成済みのタグへ署名する場合は、-fオプションを追加します)

$ git tag -s v0.0.2 -m 'Interactive shell support'

次のユーザーの秘密鍵のロックを解除するには
パスフレーズがいります:“Gonbe Nanashi (hoge) <hoge@gmail.com>”
2048ビットRSA鍵, ID 1234FEDC作成日付は2010-09-23

Updated tag 'v0.0.2' (was 4c5f5b8)

署名されたか確認します。

いつものgit showコマンドを実行すると、GPG署名が一緒に表示されます。

    $ git show v0.0.2
    tag v0.0.2
    Tagger: Gonbe Nanashi <hoge@gmail.com>
    Date:   Thu Sep 23 18:58:42 2010 +0900

    Interactive shell support
    -----BEGIN PGP SIGNATURE-----
    Version: GnuPG v1.4.10 (Darwin)

    iQEcBAABCAAGBQJMmyTSAAoJEFWFhZJ7f/uc/AQIAJDjM/PiwixNgIrtFIuTglzM
    pG6YVFML5O63Rf1bmX8W9WAY7jWDufHZ5q+dY9PnzffM3Tz8HTiJMzKGjg66cZ89
    vmuvaJ5nM/1hq1fbXdAjpXXxVPin/6jscpAjBefSQFckegXUCKe5Sl8K1M2c0ohY
    Qe7hViY9BlJXfn/xFgGBO9oWDcpz+hCcsb0SjyrsAirtPvX5axJEW/Eq3yKijXM4
    QiR+q0B7/Sz9ymZJUB6wWEK/pdhf6w64pN+xEvaIHmrsqoPG3kXzhYOYCxQgeAms
    lEyd5caIKteKzQqAejtWW2y0Q4W7v+aeWr7lJfwMym+7f6iHwqsjVp87lzUaC+M=
    =QFab
    -----END PGP SIGNATURE-----

    commit e57c1135027592d6f25a444fe04b2aff8ebf0c39
    Author: Gonbe Nanashi <hoge@gmail.com>
    Date:   Thu Sep 23 17:13:19 2010 +0900

        Add running interactive shell argument and parameters

    diff --git a/.gitignore b/.gitignore
    new file mode 100644
    index 0000000..6e92f57
    --- /dev/null
    +++ b/.gitignore
    (...)

タグの検証

署名者の公開鍵を使用して、検証できます。

$ git tag -v v0.0.2
object e57c1135027592d6f25a444fe04b2aff8ebf0c39
type commit
tag v0.0.2
tagger Gonbe Nanashi <hoge@gmail.com> 1285235922 +0900

Interactive shell support
gpg: 木  9/23 18:58:42 2010 JSTにRSA鍵ID 1234FEDCで施された署名
gpg: “Gonbe Nanashi (hoge) <hoge@gmail.com>”からの正しい署名

参考

Git

おまけ

タグをgithub等のリモートサーバへ送る

git push origin masterでやってるように、git push origin v0.0.2って実行してやるだけです。

$ git push origin v0.0.2

複数のタグを一度にpushしたい場合は、下記のコマンドを実行するといいらしいです。

$ git push origin --tags
リモートリポジトリからタグ一覧を取得する

まず、下記のコマンドで、自分のmasterブランチに最新リビジョンをマージします。

$ git pull origin master

タグはダウンロードされないので、下記のコマンドでタグを取得します。

$ git fetch origin --tags
最新のタグから、tarballを作成する
$ git describe
v0.0.2
$ APP_VER=$(git describe)
$ git archive --prefix=nanashi-${APP_VER#v}/ $APP_VER | gzip >nanashi-${APP_VER#v}.tar.gz
$ tar tvf ~/src/nanashi-0.0.2.tar.gz
-rw-rw-r--  0 root   root        5  9 23 23:41 nanashi-0.0.2/.gitignore
-rw-rw-r--  0 root   root        0  9 23 23:41 nanashi-0.0.2/LICENSE
-rw-rw-r--  0 root   root       27  9 23 23:41 nanashi-0.0.2/README
-rwxrwxr-x  0 root   root     4779  9 23 23:41 nanashi-0.0.2/nanashiCommand.py

ビルド用のディレクトリにtarballを送ってRPMを作成するとか、Webアプリだったら配信用ディレクトリに送ってrsync叩くとか。これで簡単にリリース管理ができますね。

GNU Privacy Guard(GPG)鍵を作ってみた

はじめに

GNU Privacy Guard(GPG)は、データの暗号化と署名に使用します。
使用例としては、下記のものがあります。

  1. 暗号化メール
  2. 電子署名
    • RPMパッケージの署名
    • Git Tagの署名

かつて、Gmailでのメール暗号化のため、Firegpgとともに導入しようとしましたが、日本語が文字化けする・ブラウザが突然落ちるという問題で断念していましたが、今回、GitのTag署名のため、MacOSXで再挑戦したため、その記録を残しておきます。

環境

MacOSX v10.6
CentOS v5.5

導入

MacOSXであれば、下記のコマンドで導入します。

$ brew install gnupg

CentOSであれば、下記のコマンドで導入します。

$ yum install gnupg

鍵生成

    $ gpg --gen-key
    gpg (GnuPG) 1.4.10; Copyright (C) 2008 Free Software Foundation, Inc.
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.

    gpg: ディレクトリー「/Users/hoge/.gnupg」ができました
    gpg: 新しい構成ファイル「/Users/hoge/.gnupg/gpg.conf」ができました
    gpg: 警告: 「/Users/hoge/.gnupg/gpg.conf」のオプションは起動している間、有効になりません
    gpg: 鍵輪「/Users/hoge/.gnupg/secring.gpg」ができました
    gpg: 鍵輪「/Users/hoge/.gnupg/pubring.gpg」ができました
    ご希望の鍵の種類を選択してください:
       (1) RSA and RSA (default)
       (2) DSA and Elgamal
       (3) DSA (署名のみ)
       (4) RSA (署名のみ)
    選択は? 1
    RSA keys may be between 1024 and 4096 bits long.
    What keysize do you want? (2048) 
    要求された鍵長は2048ビット
    鍵の有効期限を指定してください。
             0 = 鍵は無期限
          <n>  = 鍵は n 日間で満了
          <n>w = 鍵は n 週間で満了
          <n>m = 鍵は n か月間で満了
          <n>y = 鍵は n 年間で満了
    鍵の有効期間は? (0)
    Key does not expire at all
    これで正しいですか? (y/N) y

    あなたの鍵を同定するためにユーザーIDが必要です。
    このソフトは本名、コメント、電子メール・アドレスから
    次の書式でユーザーIDを構成します:
        "Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"

    本名: Gonbe Nanashi
    電子メール・アドレス: hoge@gmail.com
    コメント: gpg key
    次のユーザーIDを選択しました:
        “Gonbe Nanashi (hoge) <hoge@gmail.com>”

    名前(N)、コメント(C)、電子メール(E)の変更、またはOK(O)か終了(Q)? O
    秘密鍵を保護するためにパスフレーズがいります。

    今から長い乱数を生成します。キーボードを打つとか、マウスを動かす
    とか、ディスクにアクセスするとかの他のことをすると、乱数生成子で
    乱雑さの大きないい乱数を生成しやすくなるので、お勧めいたします。
    +++++
    ....+++++
    今から長い乱数を生成します。キーボードを打つとか、マウスを動かす
    とか、ディスクにアクセスするとかの他のことをすると、乱数生成子で
    乱雑さの大きないい乱数を生成しやすくなるので、お勧めいたします。
    +++++
    .......+++++
    gpg: /Users/hoge/.gnupg/trustdb.gpg: 信用データベースができました
    gpg: 鍵1234FEDCを絶対的に信用するよう記録しました
    公開鍵と秘密鍵を作成し、署名しました。

    gpg: 信用データベースの検査
    gpg: 最小の「ある程度の信用」3、最小の「全面的信用」1、PGP信用モデル
    gpg: 深さ: 0  有効性:   1  署名:   0  信用: 0-, 0q, 0n, 0m, 0f, 1u
    pub   2048R/1234FEDC 2010-09-23
                     指紋 = A1C4 89E8 47BD E506 15BF  B6DA 5585 8592 7B7F FB9C
    uid                  Gonbe Nanashi (hoge) <hoge@gmail.com>
    sub   2048R/ABCD9876 2010-09-23

公開鍵サーバへ自分の公開鍵を登録

下記のサイトにて、公開鍵の登録と検索が可能ですが、今回はコマンドラインから公開鍵サーバへ鍵を登録してみます。
Public Key Server Commands


公開鍵の送信

$ gpg --keyserver pgp.nic.ad.jp --send-keys 1234FEDC
gpg: 鍵1234FEDCをhkpサーバーpgp.nic.ad.jpへ送信


公開鍵の検索と取得をするには、下記のようにします。

公開鍵の検索

    $ gpg --keyserver pgp.nic.ad.jp --search-keys hoge
    gpg: “hoge”をhkpサーバーpgp.nic.ad.jpから検索
    (1)	Gonbe Nanashi (hoge) <hoge@gmail.com>
          2048 bit RSA key 1234FEDC, 作成: 2010-09-23
    番号(s)、N)次、またはQ)中止を入力してください >1    #<=番号を入力すると該当の公開鍵を入手できる
    gpg: 鍵1234FEDCをhkpからサーバーpgp.nic.ad.jpに要求
    gpg: 鍵1234FEDC:“Gonbe Nanashi (hoge) <hoge@gmail.com>”変更なし
    gpg:     処理数の合計: 1
    gpg:         変更なし: 1

これで、RPMパッケージの署名とか、GitのTag署名とかできますね。また、他の人の公開鍵を使って署名を検証するとか、暗号化メッセージを複合化するとかできますよね。(なお、他人の公開鍵を使用する場合、警告が出ますが、ローカルの信用データベースの更新とかの追加のタスクを実施することにより出なくなります。)

MacOSXにGnu indentをインストール

はじめに

MacOSX標準のindentコマンドに、K&Rスタイルがないよー

とあるオープンソースのプロジェクトのソースコードを、ローカルのMacBookにチェックアウトしてしばらく眺めていたところ、スペースやらインデントやらが非常に適当な見づらいソースコードがあったため、indentコマンドでソースコードをフォーマットしてやれ!!と思ったのが、気づいたきっかけ。

前提

ローカルの環境

  • UTF8
  • タブ幅は4
  • タブはスペースに変換しない

調査

MacOSX標準でインストールされているindentコマンドは、manを見る限りBSD由来のものでオプションにK&Rスタイルが用意されていないことがわかった。

$ man indent
INDENT(1)                 BSD General Commands Manual                INDENT(1)

NAME
     indent -- indent and format C program source

SYNOPSIS
     indent [input_file [output_file]] [-bacc | -nbacc] [-bad | -nbad] [-bap | -nbap] [-bbb | -nbbb] [-bc | -nbc] [-bl] [-br] [-cn]
            [-cdn] [-cdb | -ncdb] [-ce | -nce] [-cin] [-clin] [-dn] [-din] [-fc1 | -nfc1] [-in] [-ip | -nip] [-ln] [-lcn]
            [-lp | -nlp] [-npro] [-pcs | -npcs] [-psl | -npsl] [-sc | -nsc] [-sob | -nsob] [-st] [-troff] [-v | -nv]
(...)
HISTORY
     The indent command appeared in 4.2BSD.

BUGS
     indent has even more switches than ls(1).

     A common mistake that often causes grief is typing:

           indent *.c

     to the shell in an attempt to indent all the C programs in a directory.  This is probably a bug, not a feature.

BSD                              June 29, 2004                             BSD

何となく、MacPortsでindentを探してみる

$ port search indent
indent @2.2.10 (devel)
    C language source code formatting program
$ port info indent
indent @2.2.10 (devel)
Variants:             universal

Description:          GNU indent changes the appearance of a C program by inserting or deleting whitespace according to a plethora of options. Some
                      canned styles of formatting are supported as well. GNU indent is a descendant of BSD indent. GNU indent does NOT work for C++,
                      only C.
Homepage:             http://www.gnu.org/software/indent/

Library Dependencies: gettext, libiconv
Platforms:            darwin
License:              unknown
Maintainers:          nomaintainer@macports.org

インストール

Gnu indentなら間違いなくK&Rスタイルは用意されているはずと思ってインストール

port install indent

インストールされたものがどこにあるかは下記のコマンドで確認できる

$ port contents indent
Port indent contains:
  /opt/local/bin/gnuindent
  /opt/local/bin/gnutexinfo2man
  /opt/local/share/doc/indent/indent.html
  /opt/local/share/info/indent.info
  /opt/local/share/locale/ca/LC_MESSAGES/indent.mo
  /opt/local/share/locale/da/LC_MESSAGES/indent.mo
  /opt/local/share/locale/de/LC_MESSAGES/indent.mo
  /opt/local/share/locale/eo/LC_MESSAGES/indent.mo
  /opt/local/share/locale/et/LC_MESSAGES/indent.mo
  /opt/local/share/locale/fi/LC_MESSAGES/indent.mo
  /opt/local/share/locale/fr/LC_MESSAGES/indent.mo
  /opt/local/share/locale/gl/LC_MESSAGES/indent.mo
  /opt/local/share/locale/hu/LC_MESSAGES/indent.mo
  /opt/local/share/locale/it/LC_MESSAGES/indent.mo
  /opt/local/share/locale/ja/LC_MESSAGES/indent.mo
  /opt/local/share/locale/ko/LC_MESSAGES/indent.mo
  /opt/local/share/locale/nl/LC_MESSAGES/indent.mo
  /opt/local/share/locale/pl/LC_MESSAGES/indent.mo
  /opt/local/share/locale/pt_BR/LC_MESSAGES/indent.mo
  /opt/local/share/locale/ru/LC_MESSAGES/indent.mo
  /opt/local/share/locale/sk/LC_MESSAGES/indent.mo
  /opt/local/share/locale/sv/LC_MESSAGES/indent.mo
  /opt/local/share/locale/tr/LC_MESSAGES/indent.mo
  /opt/local/share/locale/zh_TW.Big5/LC_MESSAGES/indent.mo
  /opt/local/share/man/man1/gnuindent.1.gz

MacPortsでインストールされたコマンドはgnuindentなので、manでオプションを確認してみる

$ man gnuindent
INDENT(1L)                                                                                                                 INDENT(1L)

NAME
       indent - changes the appearance of a C program by inserting or deleting whitespace.

SYNOPSIS
       indent [options] [input-files]

       indent [options] [single-input-file] [-o output-file]

       indent --version
(...)
       -ipn, --parameter-indentationn
           Indent parameter types in old-style function definitions by n spaces.
           See  INDENTATION.

       -kr, --k-and-r-style                       #<= 目的のオプションはこれ
           Use Kernighan & Ritchie coding style.
           See  COMMON STYLES.

       -ln, --line-lengthn
           Set maximum line length for non-comment lines to n.
           See  BREAKING LONG LINES.
(...)

試す

下記にこれはひどいというソースコードを用意してみました。

#include <stdio.h>
#include <stdlib.h>

typedef enum {
	SUN,
	MON,
	TUE,
WED,
	THU,
FRI,
SAT
	} Week_t;


typedef struct {
	int year;
int month;
	int day;
 Week_t w;
}   Date_t;


void setdate(Date_t * dt, int y, int m,int d) {
	dt->year=y, dt->month = m, dt->day =d;
}

Week_t dayofweek(int y, int m,int d) {
	if(m <3) {
		y--;
m+=12;
}
	return (Week_t)((y + y/4-y/100+y/400+(13*m+8)/5+d)%7);
}

int main(int argc,char** argv) {
	int i =0;
	Date_t dt[3];
	int ymd[][3] = {
		{2010,1,1,},
	{1999,12,31,},
{2004,8,1,},
	};

	char wstr[][12] = {
	"Sunday","Monday","Tuesday",
	"Wednesday", "Thursday", "Friday",
"Saturday",
	};

	for (i=0; i<3;i++) setdate(&dt[i], ymd[i][0], ymd[i][1], ymd[i][2]);
	for(i=0;i<3;i++) 
	dt[i].w =dayofweek(dt[i].year,dt[i].month, dt[i].day);


	i=0;
	do {
	printf("It's %s (%d.%d.%d).\n", wstr[dt[i].w], dt[i].year, dt[i].month,dt[i].day);
	i++;
} while (i<3);
	return EXIT_SUCCESS;
}

こんなひどいソースコードを、Gnu indentはいとも簡単に読みやすくフォーマットしてくれます。

K&Rスタイルにフォーマット
$ gnuindent -kr -ts4 hoge.c    #<= K&Rスタイルで、タブ幅4を指定
#include <stdio.h>
#include <stdlib.h>

typedef enum {
	SUN,
	MON,
	TUE,
	WED,
	THU,
	FRI,
	SAT
} Week_t;


typedef struct {
	int year;
	int month;
	int day;
	Week_t w;
} Date_t;


void setdate(Date_t * dt, int y, int m, int d)
{
	dt->year = y, dt->month = m, dt->day = d;
}

Week_t dayofweek(int y, int m, int d)
{
	if (m < 3) {
		y--;
		m += 12;
	}
	return (Week_t) ((y + y / 4 - y / 100 + y / 400 + (13 * m + 8) / 5 +
					  d) % 7);
}

int main(int argc, char **argv)
{
	int i = 0;
	Date_t dt[3];
	int ymd[][3] = {
		{2010, 1, 1,},
		{1999, 12, 31,},
		{2004, 8, 1,},
	};

	char wstr[][12] = {
		"Sunday", "Monday", "Tuesday",
		"Wednesday", "Thursday", "Friday",
		"Saturday",
	};

	for (i = 0; i < 3; i++)
		setdate(&dt[i], ymd[i][0], ymd[i][1], ymd[i][2]);
	for (i = 0; i < 3; i++)
		dt[i].w = dayofweek(dt[i].year, dt[i].month, dt[i].day);


	i = 0;
	do {
		printf("It's %s (%d.%d.%d).\n", wstr[dt[i].w], dt[i].year,
			   dt[i].month, dt[i].day);
		i++;
	} while (i < 3);
	return EXIT_SUCCESS;
}

自分でコードをできるだけ素早く書きたいという時でも、indentコマンドが威力を発揮します。

2つのリストからdictionaryを作る

超個人的メモ

ループを使用しなくても作れるのか...

下記のようにしたい場合
list1 = [key1, key2, ..., keyN]
list2 = [value1, value2, ..., valueN]

dict = {key1: value1, key2: value2, ..., keyN: valueN}

$ipython-2.6                                                                                  [~/work]
Python 2.6.5 (r265:79063, Jul 24 2010, 18:10:03) 
Type "copyright", "credits" or "license" for more information.

IPython 0.10 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object'. ?object also works, ?? prints more.

In [1]: 

In [2]: questions = ['name', 'quest', 'favorite color']

In [3]: answers = ['lancelot', 'the holy grail', 'blue']

In [4]: dict(zip(questions,answers))
Out[4]: {'favorite color': 'blue', 'name': 'lancelot', 'quest': 'the holy grail'}

FreeDNSに登録しているDNSレコードを更新するDynamicDNSクライアントを作成_Second

はじめに

DynamicDNSサービスに元々MyDNS.JPを使用していましたが、なぜか1レコード分以外のレコードがTXTレコードになってしまい、外部から自宅サーバへアクセスできなくなっていたので、FreeDNSに乗り換えてみました。

追記:エキスパートPythonPython v2.6.2のマニュアルにも目を通し、おかしなところとか、Pythonは、GIL(Global Intepreter Lock)の問題とかで、同時実行できるスレッドが一つに限定されてしまうので、マルチスレッドの代わりに、マルチプロセスをよく利用するとなっているので、それも変更しました。

またDNS更新クライアントとして使用していた機器が故障し、普通のPCから更新することにしました。

FreeDNSの特徴
  • MXレコード、Aレコード、CNAMEレコード、AAAAレコード等ほぼすべて対応している
  • DNSレコードのバックアップサーバとして自分でたてたDNSサーバを指定できる
  • wget等HTTPアクセスでDNSレコードを更新できる
  • XML APIが用意されており、自分でDynamicDNSクライアントを作成できる

手順

前提
  • 外部公開とかメンテとかで外部からサーバにアクセスしたいが、固定グローバルIPなんぞは不要っていう人
  • FreeDNSサービスにユーザ登録し、DNSのレコードを登録していること(Aレコード,MXレコード, AAAAレコードでも)
DynamicDNSクライアント環境
  • sheevaplug(電源プラグ型LinuxPC: FlashROM) 常時電源ONにするとデーモンがダウンしたりするため除外
  • DesktopPC(2002年くらいのノーブランド)
  • Ubuntu10.04
  • python v2.6
XML API取得

下記のURLにアクセスすると、XML形式でレコード更新用URLが取得できます。
(shaの部分は、登録ユーザ名+パスワードを使用してSHA-1で暗号化した文字列が入ります。)

URL: http://freedns.afraid.org/api/?action=getdyndns&sha=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&style=xml

SHA-1暗号化

暗号化文字列は、username + pipe + passwordをSHA-1で暗号化すればよいようなので、
下記のような関数で暗号化文字列を作成します。

def gethashstr(username, password):

    import hashlib

    hashobj = hashlib.sha1()
    encstr = '|'.join([username, password])
    hashobj.update(encstr)
    hashstring = hashobj.hexdigest()
    return hashstring
更新用URLの取得

下記のように必要なパラメータを用意して

HASH_ARGO = "sha"
TARGET_PARAMS = {'action': "getdyndns", HASH_ARGO: None, 'style': "xml"}
TARGET_API = "http://freedns.afraid.org/api/"
TIMEOUT = 300 

urllib2を使ってDNSレコード更新用URLが記述されているXMLを取得しました。

def request(url, timeout, **params):

    query = ''
    for k, v in params.items():
        query += '?' if query == '' else '&' 
        query += '{key}={value}'.format(key=k, value=v)

    url += query

    import urllib2
    print 'Connecting URL: {url} ...'.format(url=url)
    res = urllib2.urlopen(url=url, timeout=timeout)
    print 'URL: {url} contents download finished.'.format(url=url)
    responsetext = res.read()
    return responsetext

TARGET_PARAMS[HASH_ARGO] = gethashstr(hoge, hogepass) 
response = request(TARGET_API, TIMEOUT, TARGET_PARAMS)

XMLからレコード更新用URL抽出

responsetextに取得したXMLが格納されているとして、XMLから更新用URLを抽出します。
XMLの解析モジュールは、dom,saxとかいろいろあったのですが、xml.etree.ElementTreeが一番簡単でしたので、それを使用しました。

利点

  • xpath風に目的のノードを取得できる
    • 下記のXMLから複数のurlのテキストを抽出する場合["item/url"](イテレータオブジェクトが返却される)

欠点

  • XML文字列をそのまま渡すと例外が発生する(ファイルオブジェクトを渡す必要あり)
    • しかし、StringIOで渡せば問題なく対処できる。
    • 通常のPCサーバであればXMLをtempfileにリダイレクトして渡せばよいと思います。
<root>
  <item>
    <host>www.hoge.com</host>
    <address>71.1.1.1</address>
    <url>http://freedns.afraid.org/dynamic/update.php?T3faGkljdax==</url>
  </item>
  <item>
    {...}
    <url>http://freedns.afraid.org/dynamic/update.php?jklIIUadaaa==</url>
  </item>
</root>
def update(responsetext, timeout):
    from xml.etree.ElementTree import ElementTree
    from StringIO import StringIO

    try:
        # xml.etree.ElementTree requires FileObject
        output = StringIO(responsetext)
        tree = ElementTree()
        tree.parse(output)
        urls = tree.findall("item/url")
DNSレコード更新

XMLから抽出した更新用URLにアクセスするだけでレコードの更新ができます。

for url in urls:
    res = urllib2.urlopen(url)

あとは、これをcrontabに1時間に1回動作するように設定しておけばいいのではないかと。
$ crontab -l
0 * * * * python /var/cron/freedns/freednsupdate.py

追記:

マルチプロセスを扱うワーカークラスを作成

#!/usr/bin/env python
# -*= coding:utf-8 -*-
from multiprocessing import Process
import urllib2


class AsyncWorker(Process):
    def __init__(self, url, timeout):
        Process.__init__(self)
        self.url = url 
        self.timeout = timeout

    def run(self):
        try:
            print 'Connecting URL: {url} ...'.format(url=self.url)
            res = urllib2.urlopen(url=self.url, timeout=self.timeout)
        except Exception, e:
            raise e
        else:
            print 'URL: {url} update successfully.'.format(url=self.url)
            return True

ワーカーを使用して処理を行う関数

# Update the dynamic dns records using API
def update(responsetext, timeout):
    from xml.etree.ElementTree import ElementTree
    from cStringIO import StringIO

    try:
        # xml.etree.ElementTree requires FileObject
        output = StringIO(responsetext)
        tree = ElementTree()
        tree.parse(output)
        urls = tree.findall("item/url")

        # Record update
        from worker import AsyncWorker
        tasks = [AsyncWorker(u.text, timeout) for u in urls]
        for t in tasks:
            t.start()

        # wait for the background tasks to finish
        for t in tasks:
            t.join()
        print 'Dynamic dns update tasks has finished.'
    except Exception, e:
        raise e
    finally:
        output.close()

リスト内包表記とか使用しているものの、ワーカーの書き方とかSJC-P(Java)とかで勉強したものそのままひっぱてきた表現になってしまいました。

設定ファイルを読み込むクラスのメソッドの返却値も自分自身(self)を返却するようにして、メソッドチェーンっぽく続けて処理できるようにしてみました。

作成したもの

今回作成したものは下記にあります。ここ変だよとかありましたら突っ込みとかお願いします。

404 · GitHub

参考にしたもの

404 Not Found

pythonまじめに覚えようと思って、注文してみました。
ISBN:4048686291

FreeDNSに登録しているDNSレコードを更新するDynamicDNSクライアントを作成

はじめに

DynamicDNSサービスに元々MyDNS.JPを使用していましたが、なぜか1レコード分以外のレコードがTXTレコードになってしまい、外部から自宅サーバへアクセスできなくなっていたので、FreeDNSに乗り換えてみました。

FreeDNSの特徴
  • MXレコード、Aレコード、CNAMEレコード、AAAAレコード等ほぼすべて対応している
  • DNSレコードのバックアップサーバとして自分でたてたDNSサーバを指定できる
  • wget等HTTPアクセスでDNSレコードを更新できる
  • XML APIが用意されており、自分でDynamicDNSクライアントを作成できる

手順

前提
  • 外部公開とかメンテとかで外部からサーバにアクセスしたいが、固定グローバルIPなんぞは不要っていう人
  • FreeDNSサービスにユーザ登録し、DNSのレコードを登録していること(Aレコード,MXレコード, AAAAレコードでも)
DynamicDNSクライアント環境
XML API取得

下記のURLにアクセスすると、XML形式でレコード更新用URLが取得できます。
(shaの部分は、登録ユーザ名+パスワードを使用してSHA-1で暗号化した文字列が入ります。)

URL: http://freedns.afraid.org/api/?action=getdyndns&sha=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&style=xml

SHA-1暗号化

暗号化文字列は、username + pipe + passwordをSHA-1で暗号化すればよいようなので、
下記のような関数で暗号化文字列を作成します。

def gethashstr(username, password):

    import hashlib

    hashobj = hashlib.sha1()
    encstr = '|'.join([username, password])
    hashobj.update(encstr)
    hashstring = hashobj.hexdigest()
    return hashstring
更新用URLの取得

下記のように必要なパラメータを用意して

HASH_ARGO = "sha"
TARGET_PARAMS = {'action': "getdyndns", HASH_ARGO: None, 'style': "xml"}
TARGET_API = "http://freedns.afraid.org/api/"
TIMEOUT = 300 

urllib2を使ってDNSレコード更新用URLが記述されているXMLを取得しました。

def request(url, timeout, **params):

    query = ''
    for k, v in params.items():
        query += '?' if query == '' else '&' 
        query += '{key}={value}'.format(key=k, value=v)

    url += query

    import urllib2
    print 'Connecting URL: {url} ...'.format(url=url)
    res = urllib2.urlopen(url=url, timeout=timeout)
    print 'URL: {url} contents download finished.'.format(url=url)
    responsetext = res.read()
    return responsetext

TARGET_PARAMS[HASH_ARGO] = gethashstr(hoge, hogepass) 
response = request(TARGET_API, TIMEOUT, TARGET_PARAMS)

XMLからレコード更新用URL抽出

responsetextに取得したXMLが格納されているとして、XMLから更新用URLを抽出します。
XMLの解析モジュールは、dom,saxとかいろいろあったのですが、xml.etree.ElementTreeが一番簡単でしたので、それを使用しました。

利点

  • xpath風に目的のノードを取得できる
    • 下記のXMLから複数のurlのテキストを抽出する場合["item/url"](イテレータオブジェクトが返却される)

欠点

  • XML文字列をそのまま渡すと例外が発生する(ファイルオブジェクトを渡す必要あり)
    • しかし、StringIOで渡せば問題なく対処できる。
    • 通常のPCサーバであればXMLをtempfileにリダイレクトして渡せばよいと思います。
<root>
  <item>
    <host>www.hoge.com</host>
    <address>71.1.1.1</address>
    <url>http://freedns.afraid.org/dynamic/update.php?T3faGkljdax==</url>
  </item>
  <item>
    {...}
    <url>http://freedns.afraid.org/dynamic/update.php?jklIIUadaaa==</url>
  </item>
</root>
def update(responsetext, timeout):
    from xml.etree.ElementTree import ElementTree
    from StringIO import StringIO

    try:
        # xml.etree.ElementTree requires FileObject
        output = StringIO(responsetext)
        tree = ElementTree()
        tree.parse(output)
        urls = tree.findall("item/url")
DNSレコード更新

XMLから抽出した更新用URLにアクセスするだけでレコードの更新ができます。

for url in urls:
    res = urllib2.urlopen(url)

あとは、これをcrontabに1時間に1回動作するように設定しておけばいいのではないかと。
$ crontab -l
0 * * * * python /var/cron/freedns/freednsupdate.py

参考にしたもの

404 Not Found

pythonまじめに覚えようと思って、注文してみました。
ISBN:4048686291