Saturday, November 22, 2014

docker用の超絶シンプルDNS

fig使って遊んでいたら、どうしても欲しくなり、Goでプログラム書きたかったこともあり、作ってみた。

実際にはこんな回りくどいことしなくても、既存のDNS使った、マシなソリューションがあると思う。参考

まあ200行くらいで済んだし、これはこれでいいかなー。

go-docker-dns

Github

原理は死ぬほど単純で、dockerのリモートAPIをひたすら見守り続けて、コンテナの作成イベントを捉えたら、そのホスト名とIPの組を登録。逆にコンテナ停止のイベントを捉えたら、その情報を削除する。これとは別のDNSのスレッドが動いていて(ほとんど流用だけどっ)、dockerコンテナからの要求に応じて、上で登録したホスト名-IPの組を見つけたら、そのIPを回答している。

逆引きは実装していない。結構簡単に実装できそうだけど、現状でも用途には応えているから作る理由もない…。

もともとfigを使って、普段使っているPC上で比較的大きなシステム(WebAPI x MQ x KVS x RDB x LB)のはりぼてみたいなのを作り、その動作を検証するといったことをやりたかった。

この時、DNSがいないとクラスタを組む類のサービス(典型的には、今回変なDNSを実装するきっかけになったRabbitMQがスケール時にまともに動かない。

RabbitMQの場合:masterノードとslaveノードを用意し、masterノードに対して複数のslaveを登録する仕組みを今回作ったが、fig scale mqslave=3としたときに、docker --linkの仕組みでmasterノードのIPアドレスは解決できるが、他のslaveノードのIPアドレスはDNSがいないと解決のしようがない。

他の人のアプローチは、もっとslaveノードが欲しかったら、

fig.yml

rabbit:
    image: bijukunjummen/rabbitmq-server
    hostname: rabbit_1
    ports:
        - "5672:5672"
        - "15672:15672"
mqnode1:
    image: bijukunjummen/rabbitmq-server
    hostname: mqnode_1
    environment:
        - CLUSTERED=true
        - CLUSTER_WITH=rabbit_1
        - RAM_NODE=true
    links:
        - rabbit
mqnode2:
    image: bijukunjummen/rabbitmq-server
    hostname: mqnode_2
    environment:
        - CLUSTERED=true
        - CLUSTER_WITH=rabbit_1
        - RAM_NODE=true
    links:
        - rabbit
        - mqnode1
...

こんな感じに増やしていけばいいじゃない!って言ってるけど、流石に「ス、スケールとは一体」…って言いたくなる。fig scale mqnode=3ってやったら1 master x 3 slaves の構成でクラスタが構築できてほしい。

コンテナ用のDNSサーバーを立てることで、わざわざfig.ymllinksを記載しなくても、scale時に名前解決してくれるようになる。

ここでもう一つ面倒な話があって、(RabbitMQでは)コンテナのホスト名がクラスタを組むときに大事になってくる。具体的にいうとfigは、scaleして増やすコンテナのホスト名をfig.ymlで記述されたものを使う。fig scale mqnode=3とやると、mqnodeというホスト名を持つコンテナが3つできるorz じゃあ、どやって名前解決してるの?っていうと、これとは別に環境変数envdocker exec <コンテナID> echo envで覗ける)と/etc/hostsに、linkのところで指定された、関係のあるコンテナのIPと外側向けホスト名みたいのを列挙している。(この辺はdocker側の実装。)

この状況だと、RabbitMQのmasterノードはslaveノードをクラスタに参加させる時に、「お前、slave_1っていう名前で参加しようとしてきたのに、実際にはslaveじゃん。」みたいな事を指摘して参加が上手くいかない。頑張って参加するときの名前とホスト名を一致させても、今度は2つめ以降のslaveが参加するときにホスト名が被る。つまり、1ノードだけが運よく参加できて、他は今参加しているノードが落ちるまで参加できない…\(^o^)/ いや、ある意味、可用性あるけど、そうじゃないいいいいいい。

fig改悪?版

という訳でfig本体にもしぶしぶ手を入れた。具体的には上に書いたようにホスト名をscaleを考慮した形で生成する。

以下のようなfig.ymlがあったとする。

mqnode:
    image: bijukunjummen/rabbitmq-server
    hostname: mqnode
    environment:
        - CLUSTERED=true
        - CLUSTER_WITH=rabbit_1
        - RAM_NODE=true

fig up -d mqnodeとやると、コンテナが1つだけ立ち上がる。ホスト名はmqnodeではなく、mqnode_1。続いてfig scale mqnode=3。これで新しいコンテナが2つ立ち上がる。ホスト名はそれぞれmqnode_2, mqnode_3。自作したDNSサーバーは、コンテナが作成されたイベントを検知すると、このホスト名とIPの組をレコードとして登録する。IPアドレスが10.10.0.1から始まってたとすると、こういうレコード。

mqnode_1 10.10.0.1
mqnode_2 10.10.0.2
mqnode_3 10.10.0.3

RabbitMQのクラスタ構築時の動きが

  1. slaveノードが起動する
  2. slaveノードがmasterノードに自身の存在を通知
  3. masterノードが参加してきたslaveノードに他のslaveの存在を教える
  4. slaveノードが教えてもらった他のslaveノードにアクセスを試みる

ってな感じになってるから、fig側の修正とDNSサーバの合わせ技で、この振る舞いが正しく機能するようになった。

オマケ

開発中は、必要に応じてコンテナを頻繁に作り直すことになると思うんだけど、fig up serviceの現在の実装がとても気に入らない。

fig up -d service
fig scale service=3

こうすると、ホスト名、およびコンテナ名がservice_1, service_2, service_3となり、これはいい。でも、この後、もう一度

fig up -d service

ってやると、service_1, service_2, service_3が削除され、service_3, service_4, service_5が新たに立ち上がる。なんで番号が増えんのよorz

別途立ち上げていたnginxコンテナにロードバランスの設定を

upstream webapp {
        server service_1;
        server service_2;
        server service_3;
    }

こんな感じに書いてるものだから、コンテナを作り直す度にnginxの設定を書き直さなきゃならなくなる。流石に気が滅入ってくるorz ので、fig up -d serviceした時に既存のサービスが立ち上がっているなら、そのホスト名で作り直すように変更。better_recreateって名前のブランチで、ここまでに書いたfig側の変更を全て反映している。

まとめ

  • Goの練習も兼ねてdocker用の超絶シンプルなDNSを作成
  • figを使ってRabbitMQクラスタを自動構築できるようにした
  • figのコードをちょっと変えてrecreate時にホスト名を引き継げるようにした

ここまでやって、メモリ4G程度のゲストマシン(CoreOS)上に計18のサービス(Rails App 2種類, KVS, RDB, Node.js App, Nginx)が協調動作する環境を構築できた。全部動いていると、だいたい3.8Gくらいメモリを消費。

なんやかんや問題も多いわけだけど、figみたいのを使うと、これまで鯖を何台も用意、provisonerなんかを使って構築していた比較的大きいシステムを、(ハリボテだけど)各開発者が自分のマシン上に持てるようになる、これで開発効率上がるといいな、とか思ったり思わなかったり。ここまでできると、本番環境へのデプロイを同じ設定ファイル使いまわしで実現したくなるね。

Friday, November 14, 2014

Docker remote API

とある事情からDockerデーモン内で発生したイベントをストリームで取得したいと思ったら、remote APIとかいう便利なものがあったので触ってみた。

ホストはOSXでboot2dockerを使っている。

いつからか分からないが、最新版のboot2dockerだとゲストマシンで起動するdockerデーモンがremote APIを受け付けるオプション付きで動いている模様。

 1055 root     /usr/local/bin/docker -d -D -g /var/lib/docker -H unix:// -H tcp://0.0.0.0:2376 --tlsverify --tlscacert=/var/lib/boot2docker/tls/ca.pem --tlscert=/var/lib/boot2docker/tls/server.pem --tlskey=/var/lib/boot2docker/tls/serverkey.pem

curlを使ってアクセスできるようなので、ホストマシン側からテストがてらGET images/jsonを叩いてみる。

yukaarybox:~ yukaary$ curl --insecure --cert $DOCKER_CERT_PATH/cert.pem --key $DOCKER_CERT_PATH/key.pem https://192.168.59.103:2376/images/json
curl: (35) Unknown SSL protocol error in connection to 192.168.59.103:-9825

なんと…。繋がらないので色々調べてたらOSX版curlのバグじゃない?って話題が上がっている。Certificate Authentication Fails

ちなみにboot2docker sshして同じ意味のコマンドを打つと、結果が返ってくる。

docker@boot2docker:~$ curl --insecure --cert ~/.docker/cert.pem --key ~/.docker/
key.pem https://boot2docker:2376/images/json
[{"Created":1414247260,"Id":"806930947ad909768835b8311a061e630ee95e7cb0d3420ec2c815abd21182f1","ParentId":"4271258c1b3fc4f96e8d7ddbf2893ff7f9dfbfcf5d97b5dcae25685930c8645a","RepoTags":["dockerfile/redis:latest"],"Size":0,"VirtualSize":434170370}

解決方法が泣けるけど、セキュリティリスクを承知でTLSオプション無しでdockerd起動すればいいじゃない!というもの。ゲストマシンにTLSオプションを無効にする設定を書く。

docker@boot2docker:~$ cat /var/lib/boot2docker/profile 
DOCKER_TLS=no
docker@boot2docker:~$ sudo /etc/init.d/docker restart

ホストマシンからremote APIを叩いてみる。

yukaarybox:boot2docker-vm yukaary$ curl http://192.168.59.103:2375/images/json
[{"Created":1414247260,"Id":"806930947ad909768835b8311a061e630ee95e7cb0d3420ec2c815abd21182f1","ParentId":"4271258c1b3fc4f96e8d7ddbf2893ff7f9dfbfcf5d97b5dcae25685930c8645a","RepoTags":["dockerfile/redis:latest"],"Size":0,"VirtualSize":434170370}

イメージの一覧は取れた。が、ストリーム関係はだめぽ…。OSXのcurlが駄目なんじゃないかって気がしてきた。

yukaary$ curl -N http://192.168.59.103:2375/events?since1415972290
シーン...

ゲストマシンからだとストリームAPIも正常に動作する。

docker@boot2docker:~$ curl http://boot2docker:2375/events?since=1415972290
{"status":"die","id":"688f24305b55bb8587635e5392e664e1ec50591c5d13be66e5040a6d6a36d2de","from":"dockerfile/redis:latest","time":1415972323}{"status":"stop","id":"688f24305b55bb8587635e5392e664e1ec50591c5d13be66e5040a6d6a36d2de","from":"dockerfile/redis:latest","time":1415972323}{"status":"die","id":"1811fbe97840253a797c0fac6a33d84bf74eeac084793c3d68f157a36e892199","from":"dockerfile/redis:latest","time":1415972339}{"status":"stop","id":"1811fbe97840253a797c0fac6a33d84bf74eeac084793c3d68f157a36e892199","from":"dockerfile/redis:latest","time":1415972339}{"status":"destroy","id":"688f24305b55bb8587635e5392e
...

追記

mac側に入れていたファイアウォールソフトが全力で邪魔していたっぽい。

yukaarybox:CoreServices yukaary$ curl -N http://192.168.59.103:2375/images/json
[{"Created":1414247260,"Id":"806930947ad909768835b8311a061e630ee95e7cb0d3420ec2c815abd21182f1","ParentId":"4271258c1b3fc4f96e8d7ddbf2893ff7f9dfbfcf5d97b5dcae25685930c8645a","RepoTags":["dockerfile/redis:latest"],"Size":0,"VirtualSize":434170370}
yukaarybox:CoreServices yukaary$ curl -N http://192.168.59.103:2375/events?since=1415972290
{"status":"create","id":"d0d362d2d7b9a1fb61aec059007b7aa902148cb67796ffb01e98840cdf7ba7f5","from":"dockerfile/redis:latest","time":1415975558}{"status":"start","id":"d0d362d2d7b9a1fb61aec059007b7aa902148cb67796ffb01e98840cdf7ba7f5","from":"dockerfile/redis:latest","time":1415975558}

繋がったからよしとしよう…。

Sunday, November 9, 2014

Python part2

思うところあってPython(3.4.2)を勉強中。公式サイトのドキュメントを見よう真似で

4. More Control Flow Tools

4.1 if Statements

最もよく知られたステートメント型はifで間違いない。

x = int(input("Please enter an interger: "))

if x < 0:
    x = 0
    print('Negative changed to zero')
elif x == 0:
    print('Zero')
elif x == 1:
    print('Single')
else:
    print('More')

実行結果。

$ py ifstmt.py
Please enter an interger: 100
More
[c:python]
$ py ifstmt.py
Please enter an interger: 1
Single
[c:python]
$ py ifstmt.py
Please enter an interger: 0
Zero
[c:python]
$ py ifstmt.py
Please enter an interger: -19
Negative changed to zero

elifは0あるいは複数あっていい。elseはオプションだ。if, elif,elif…のシークエンスはswitch, caseの代替手段になっている。

4.2 for Statement

Pythonでのfor文は、CPascalとは若干異なる。Pascalのような数学的な数の繰り返し、あるいはCのような繰り返しのステップとその停止条件の定義というよりは、あらゆるシークエンス(ListやString)内の要素の繰り返しをする。

words = ['yukari', 'maki', 'zunko']
for w in words:
    print(w, len(w))

実行結果。

$ py iterate.py
yukari 6
maki 4
zunko 5

ループの内側でシークエンスに対する改変を行いたい場合、まずコピーを作ることをお勧めする。シークエンス上での繰り返しはcopyを暗黙的に作るわけではない。スライス表記がこの目的で使えるだろう。

words = ['yukari', 'maki', 'zunko']
for w in words:
    print(w, len(w))

for w in words[:]: # Loop over slice copy of the entire list.
    if len(w) > 5:
        words.insert(0, w)
print('words', words)

実行結果。

$ py iterate.py
yukari 6
maki 4
zunko 5
words ['yukari', 'yukari', 'maki', 'zunko']

4.3 The range() Function

数列上(0,1,2,…)での繰り返しを実装したい場合は、ビルトイン関数range()を使うといい。数学的なprogression(直訳で増殖。訳すのメンドイ)を生成してくれる。

>>> for i in range(5):
...     print(i)
...
0
1
2
3
4

与えた終点は生成されたシーケンスに含まれない。range(10)は10個の値を生成する。レンジの開始点を別の値に設定することができるほか、増分(減産分)を指定することもできる。(ステップということもある)

>>> for i in range(5, 10):
...     print(i)
...
5
6
7
8
9
>>> for i in range(0, 10, 3):
...     print(i)
...
0
3
6
9
>>> for i in range(-10, -100, -30):
...     print(i)
...
-10
-40
-70

シーケンスのインデックスを使った繰り返しを実現したい場合はrange()len()を組み合わせることができる。

words = ['yukari', 'maki', 'zunko']
for w in words:
    print(w, len(w))

for w in words[:]: # Loop over slice copy of the entire list.
    if len(w) > 5:
        words.insert(0, w)
print('words', words)

for i in range(len(words)):
    print(i, words[i])

実行結果。

$ py iterate.py
yukari 6
maki 4
zunko 5
words ['yukari', 'yukari', 'maki', 'zunko']
0 yukari
1 yukari
2 maki
3 zunko

大半のこういうケースでは、enumerate()を使うと便利だ。ループのテクニックを参照してほしい。(for i, v in enumerate(['tic', 'tac', 'toe']):。なるほど理解。)

単にrangeを出力するとおかしなことになるはずだ。

>>> print(range(10))
range(0, 10)

range()によって返されるオブジェクトはリストのように振る舞うが、実際のところは違う。このオブジェクトは、繰り返しの中で期待するシークエンスの次のアイテムを返すものだ。リストを作っているわけではない。つまりスペースを削減している。

こういったオブジェクトをiterableと呼び、供給が可能な限り継続的に次のアイテムを生産する。for文は繰り返しであることを見てきたが、関数listは別だ。これはiterableオブジェクトからリストを作り出す。

>>> list(range(5))
[0, 1, 2, 3, 4]

後ほど、iterableオブジェクトを返す関数や、iterableを引数に取る関数を見ていく。

4.4 break and continue Statements, and else Caluses on Loops

break文はCのようにforwhileループの処理部から脱出する。

ループ文はelseの節を持つことがある。この節は、ループの中でリストが枯渇(for文)、または条件がfalseになる(while文)時に実行される。ただし、ループがbreakにより中断したときは実行されない。下記の例では、この特性を使って素数をピックアップしている。

primes.py

for n in range(2,10):
    for x in range(2, n):
        if n % x == 0:
            print(n, 'equals', x, '*', n//x)
            break
    else:
        print(n, 'is a prime number.')

実行結果。

$ py primes.py
2 is a prime number.
3 is a prime number.
4 equals 2 * 2
5 is a prime number.
6 equals 2 * 3
7 is a prime number.
8 equals 2 * 4
9 equals 3 * 3

よく見てほしい。elseは、ifではなくforに属している。

ループと一緒に使うとき、elseは、if文とセットで使用するより、try文のelse部として利用するのがより一般的だ。try文のelse部は、例外が発生しなかった時に実行される。または、ループ文のelse部は、breakが発生しなかったときに実行される。try文に関する詳細はHandling Exceptionsで見ていく。

continue文は、C同様、ループ内の次の繰り返しを実行する。

even.py

for num in range(2, 10):
        if num % 2 == 0:
            print("Found an even number", num)
            continue
        print("Found a number", num)

実行結果。

$ py even.py
Found an even number 2
Found a number 3
Found an even number 4
Found a number 5
Found an even number 6
Found a number 7
Found an even number 8
Found a number 9

3.4 pass Statements

pass文は何もしない。これはステートメントが構文的に必要だが、プログラムは何もする必要が無い場合に使える。たとえば、

>>> while True:
...     pass # Busy-wait for keyboard interrupt (Ctrl+C)
...

passは、一般に最少のクラスを作るために使われている。

>>> class MyEmptyClass:
...     pass
...
>>>

passが使われている他の場所は、関数のプレースホルダ、あるいは新しいコードを書いているときの条件付きの本体だ。これにより、もっと抽象的なレベルで検討を続けることができる。passは特にわーわー言われることなく無視される。

>>> def initlog(*args):
...     pass # remember to implement this!!
...
>>> initlog
<function initlog at 0x000000000296CBF8>
>>>
>>> initlog('a')
>>>

4.6 Defining Functions

任意の上限までフィボナッチ数列を生成する関数を作ってみよう。

def fib(n):
    """Print a Fibonacci series up to n."""
    a, b = 0, 1
    while b < n:
        print(b, end=' ')
        a, b = b, a + b
    print()

fib(2000)

$ py fibonacchi.py
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

キーワードdefで関数を定義する。続いて関数名と丸括弧内にパラメータのリストが続かなければならない。関数の処理本体は次の行から始まる。インデントされている必要がある。

関数の最初の行はオプションであり文章だ。この文章は関数のドキュメント(docstring)だ。docstringに関する詳細はDocumentation Stringsで扱う。docstringで、オンラインまたは印刷用文書を自動的に生成するツールがある。また、利用者にコードを通して情報を与える。書いたコードに対してdocstringを付与しておくようにしよう。

関数の実行は関数のローカル変数用にシンボルテーブルを導入するところから始まる。もう少し正確にいうと、関数内で割り当てられた全ての変数はローカルシンボルテーブル内に値を格納する。一方で変数の参照はローカルシンボルテーブル内に最初に見つかる。続いて上位関数のローカルシンボルテーブル、グローバルシンボルテーブル、そして最後にビルトイン名のテーブル。従ってグローバル変数は関数の中では、(global文無しでは)直接、値を割り当てることはできない(関数内で参照できるシンボルテーブルにはないってことか)。

関数コールでの実際のパラメータ(引数)は呼び出された関数のローカルシンボルテーブル内に、それがコールされた時に導入される。引数はcall by value(値は常にオブジェクトへの参照であり、オブジェクトの値ではない)で渡される。関数が別の関数を呼び出す場合、新たなローカルシンボルテーブルが呼び出し時に作られる。

関数定義は現在のシンボルテーブル内に関数名を導入する。関数名の値はユーザ定義関数としてインタプリタによって認識される型を持っている。この値には別の名前を割り当てることができ。これも関数として使える。一般的なリネームの仕組みだ。

>>> def fib(n):
...     pass
...
>>> fib
<function fib at 0x0000000002AAC9D8>
>>> f = fib
>>> f(100)

シンボルテーブルってのは現在のコンテキストが持つオブジェクトを格納しているテーブルのことか。locals(), globals()で中身を覗けた。

>>> locals()
{'f': <function fib at 0x0000000002AAC9D8>, '__doc__': None, '__name__': '__main__', '__spec__': None, '__package__': None, '__builtins__': <module 'builtins' (built-in)>, 'MyEmptyClass': <class '__main__.MyEmptyClass'>, 'initlog': <function initlog at 0x000000000296CBF8>, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, 'fib': <function fib at 0x0000000002AAC9D8>, 'fin': <function fin at 0x0000000002AACAE8>}
>>> globals()
{'f': <function fib at 0x0000000002AAC9D8>, '__doc__': None, '__name__': '__main__', '__spec__': None, '__package__': None, '__builtins__': <module 'builtins' (built-in)>, 'MyEmptyClass': <class '__main__.MyEmptyClass'>, 'initlog': <function initlog at 0x000000000296CBF8>, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, 'fib': <function fib at 0x0000000002AAC9D8>, 'fin': <function fin at 0x0000000002AACAE8>}

他の言語から来た人にとって、fibは関数ではなく、それは値を返さないので手続きのように見えるだろう。実際は、return文を持たない関数も値を返す。この値はNone(これはビルトインの名前だ)と呼ばれている。Noneの書き出しはインタプリタによって抑止されているが、print()を使うと確認できる。

>>> print(fib(0))
None

フィボナッチ数列のリストを返すよう関数を直すのは単純だ。

def fib2(n):
    """Return a list containing the FIbonacci series up to n. """
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a+b
    return result

print(fib2(100))

実行結果。

$ py fibonacchi.py
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

この例では次のPythonの特性を紹介した。

  • return文で関数から値を返す。単にreturnとした場合、または、returnの指定がない場合はNoneが返る。
  • result.append(a)はリストオブジェクトresultのメソッドを呼び出す。メソッドはオブジェクトに属する関数であり、obj.methodnameの形で呼び出す。objは何らかのオブジェクト、methodnameはオブジェクトの型によって定義されたメソッドの名前だ。異なる型は、異なるメソッドを定義する。違った型のメソッドは曖昧性をもたらすことなく同じ名前を持てる(classesを使って、独自のオブジェクト型とメソッドを定義することが可能だ。Classesのセクションを参照。) appendメソッドはリストオブジェクトで定義されている。これはリストの終端に新たな要素を追加する。この例ではresult = result + [a]と等価であるが、より効率的だ。

4.7. More on Defining Functions

引数の数が可変である関数を定義することもできる。同時に利用できる3つの形態がある。

4.7.1 Default Argument Values

最も使いやすい形態は引数のデフォルト値を指定するものだ。より少ない引数で関数を呼び出すことができる。(そりゃそうだ)

def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
    while True:
        ok = input(prompt)
        if ok in ('y', 'ye', 'yes'):
            return True
        if ok in ('n', 'no', 'nop', 'nope'):
            return False
        retries = retries - 1
        if retries < 0:
            raise OSError('uncooperative user')
        print(complaint)

ask_ok('Yukarisan maji tenshi')

実行結果。

$ py askok.py
Yukarisan maji tenshia
Yes or no, please!
Yukarisan maji tenshib
Yes or no, please!
Yukarisan maji tenshid
Yes or no, please!
Yukarisan maji tenshie
Yes or no, please!
Yukarisan maji tenshif
Traceback (most recent call last):
  File "askok.py", line 13, in <module>
    ask_ok('Yukarisan maji tenshi')
  File "askok.py", line 10, in ask_ok
    raise OSError('uncooperative user')
OSError: uncooperative user

この関数は幾つかの方法で呼び出すことができる。

  • 最低限、必要な引数だけ:ask_ok('Yukarisan maji tenshi:')
  • オプション引数を1つだけ: ask_ok('Yukarisan maji tenshi:', 2)
  • オプション全部指定: ask_ok('Yukarisan maji tenshi:', 2, 'use yes or no, plz.')

inキーワードは、シークエンスが正しい値を含むかチェックする。

デフォルト値は、definingのスコープで関数の定義位置で評価される。つまり

i = 5

def f(arg=i):
    print(arg)
i = 6
f()

この結果は5だ。

重大な警告: デフォルト値は1回のみ評価される。つまり、デフォルト値がlist, dictionary, クラスインスタンスのような可変オブジェクトの時に違いが発生する。以下の例を見てみよう。

def f(a, L=[]):
    L.append(a)
    return L

print(f(1))
print(f(2))
print(f(3))

$ py default.py
[1]
[1, 2]
[1, 2, 3]

まさかのデフォルト値を設定したリストが共有されてしまう。このようにしたくない場合、以下のようにする。

def g(a, L=None):
    if(L is None):
        L = []
    L.append(a)
    return L

$ py default.py
[1]
[2]
[3]

うわ、めんどくさ…。

4.7.2. Keyword Arguments

関数はkwarg=valueという形式を持つkeyword argumentを使って呼び出すことができる。以下の例で説明する。

def parrot(voltage, state='a stif', action='voom', type='Norwegian Blue'):
    print("-- This parrot wouldn't", action, end=' ')
    print("If you put", voltage, "volts though it.")
    print("-- Lovely plumage, the", type)
    print("It's", state, "!")

# These calls are valid
parrot(1000)
parrot(voltage=1000)
parrot(voltage=10000000, action="VOOOOOOOM")
parrot(action="VOOOOOOOM", voltage=10000000)
parrot('a million', 'bereft of life', 'jump')
parrot('a thousand', state='pushing up the daisies')

# Invalid calls
parrot() # required argument missing
parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument
parrot(110, voltage=220) # duplicate value for the same argument
parrot(actor='Makimaki') # unknown keyword argument

関数parrotは1つの必須引数と3つのオプション数を取る。関数を呼び出す際、キーワード引数は実際に定義されている引数に従わなければならない。ただ、その順序を気にする必要はない。同時に同じキーワードに対して複数の値を設定することはできない。この制約は簡単にチェックできる。

>>> def func(a):
...     pass
...
>>> func(0, a=0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() got multiple values for argument 'a'

**nameという形の形式パラメータがあるとき、それは形式パラメータに関連するものを除いた全てのキーワード引数が含まれている辞書を受け取る。これは、*nameという形をした形式パラメータ(こっちは形式パラメータリストの上位の引数を格納したタプルだ)と一緒に使うこともあるだろう。*nameは、**nameの前に置かなければならない。関数を例に示す。

def cheeeshop(kind, *arguments, **keywards):
    print("-- Do you have any", kind, "?")
    print("-- I'm sorry, we're all out of", kind)
    for arg in arguments:
        print(arg)
    print("-" * 40)
    keys = sorted(keywards.keys())
    for kw in keys:
        print(kw, ":", keywards[kw])


cheeeshop("Limburger", "It's very runny, sir",
        "It's very very runny, sir.",
        shopkeeper="Maki Tsurumaki",
        client="Yukari Yuzuki",
        sketch="Cheese Shop Sketch")

実行結果。

$ py formalparam.py
-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir
It's very very runny, sir.
----------------------------------------
client : Yukari Yuzuki
shopkeeper : Maki Tsurumaki
sketch : Cheese Shop Sketch

キーワード引数の名前のリストは、それの中身を書き出す前にkeywards dictionaryのkeys()メソッドの出力をソートしている点に注意してほしい。もしこれをしない場合、引数の書き出し順序は確定しない。

4.7.4 Arbitary Argument Lists

最後に、任意の数の引数をもって関数を呼び出すことができる最も頻度が低い方法について触れておく。これらの引数はタプル内にラップされる。可変の引数の前に、0個ないし通常の引数の引数が並ぶ。

def write_multiple_items(file, separator, *args):
    file.write(separator.join(args))

通常、これらのvariadic(可変な)な引数は形式パラメータのリストの最後に置かれる。関数に渡される残りの引数全てが、この可変パラメータとして扱われるので、まあ仕方がない。*argsの後ろに置かれるどんな形式パラメータも、keyward-onlyな引数だ。つまり、位置が確定した引数というより、キーワードでのみ利用する。

>>> def concat(*args, sep="/"):
...     return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'

4.7.4. Unpacking Argument Lists

引数がリストやタプルに含まれているときは逆の必要が生じるだろう。つまり、それらをばらして、個別の引数を必要とする関数呼び出しに対応させる必要性だ。例えばビルトイン関数range()は、別々にstartstopの引数を必要とする。これらの情報が分かれていない場合は、*オペレータを用いてリスト/タプルの外に引数を引き出すことができる。

>>> list(range(3,6))
[3, 4, 5]
>>> args = [3,6]
>>> list(range(*args))
[3, 4, 5]

同じように、dictionaryも**オペレータを使って引数を引き出すことができる。


d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
parrot(**d)

4.7.5. Lambda Expressions

小さな無名関数をキーワードlamdaを使って作成できる。lambda a, b: a +b、この関数は2つの引数a,bの合計値を返すものだ。ラムダ関数は関数オブジェクトを使えるところなら、どこでも使える。単一の表現しか使えないという文法上の制約がある。意味的には、ラムダ関数は通常の関数定義のシンタックスシュガーだ。ネストされた関数定義のように、ラムダ関数はそのスコープから変数を参照できる。

def make_incrementor(n):
    return lambda x: x + n

f = make_incrementor(42)
print(f(0))
print(f(1))

この例は関数を返り値で返す時にラムダ表現を使っている。次の例は引数にラムダ式を与える。

pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
pairs.sort(key=lambda pair: pair[1])
print(pairs)

4.7.5. Documentation Strings

ドキュメントのお作法の話。関数の宣言の直後に"""で初めて複数行に渡って色々書いたら"""で閉じる。

def my_function():
    """Do nothing, but document it.

    No really, it doen't do anything.
    """
    pass

print(my_function.__doc__)

__doc__で参照できるのが面白いw

4.7.7. Function Annotations

これは完全にオプションだ。ユーザ定義関数についての任意のメタ情報を含めることができる。サードパーティ製のプロジェクトではアノテーションをドキュメント、型チェック、その他の用途、何に使っても自由だ。

アノテーションは関数の__annotations__属性にdictionaryとして格納されている。関数の他の部分に何の影響も与えない。パラメータアノテーションはパラメータ名の後ろにコロンを付けて定義する。この後、アノテーションの値を評価する文が続く。リターンアノテーションはリテラル->で定義する。下記の例は、positional argumentとkeyward argument、および返り値に特に意味のないアノテーションを付けたものだ。

def f(ham: 42, eggs: int = 'spam') -> "Nothing to see here":
    print("Annotations:", f.__annotations__)
    print("Arguments:", ham, eggs)

f("wonderful")

$ py annotation.py
Annotations: {'return': 'Nothing to see here', 'ham': 42, 'eggs': <class 'int'>}
Arguments: wonderful spam

4.8. Intermezzo: Coding Style

タブ使うな、とかそんな話。

Written with StackEdit.

Saturday, November 8, 2014

DockerとFig クラスタ対応の話

figをぽちぽち触っていると、こいつDBなんかのサービスを含むアプリケーション全体を自動構築できるのに、なぜデプロイターゲットはDOCKER_HOST一台だけなのん?と疑問に思う。

少し調べてみたら、当然、外人さん達も同じことを思うらしく、githubでぼちぼち議論されていた。

中身を覗いてみると単純にhostを複数管理できるようにしているだけらしい。そんでfig.ymlにどのホストにデプロイするかの情報を付与する。

web1:
  build: .
  command: python app.py
  docker_host: tcp://192.168.0.101
  docker_cert_path: /path/to/your/cert1/directory
  docker_tls_verify: 1
  ports:
   - "8000:8000"

web2:
  build: .
  command: python app.py
  docker_host: tcp://192.168.0.102
  docker_cert_path: /path/to/your/cert2/directory
  docker_tls_verify: 1
  ports:
   - "8000:8000"

これ、ホスト決め打ちだから、そのホスト落ちたら(^o^)/なんじゃないかな…。

Dockerクラスタ内のどこかにデプロイされるが、それがどこかはリクエスト側は知る必要が無い/仮にクラスタを構成するインスタンスがクラッシュした場合、そのインスタンスが実行していたDockerプロセスは別の健全なインスタンスに引き継がれる。無理言ってる気もするけど、このくらいの機能がないとマルチホスト構成をサポートする意味がないように思える。

上に挙げた特性はkubernetesがそこそこ対応しているように思えるけど、googleにべったりなのもなぁ…docker本体でなんとかしないのかなーと期待している。

この話、figdockerに取り込まれたこともあり、内部ではdockerfigどっちに持たせる?って話になっているように見える。

docker本体側の議論。

  • Docker Clustering: Design proposal
  • Proposal: Host management

  • 8859 dockerのクラスタ対応、master x 1 slave x Nのモデル?

    • 詳しく読んでないけど「masterが死んだときにslaveのどれかがmasterになる/masterが定期的に交代する」くらいの特性が欲しいな!それなんてcoreosって話だけども。
  • 8681 dockerホスト立てるのメンドイ。dockerクライアントにdockerホスト立てる機能も持たせようぜ!みたいな話

という感じで、上の機能が盛り込まれたら今までのdockerとは位置づけが異なった
ものになりそう。figをアプリケーションの構成を管理する環境構築ツールと位置付けて、クラスタ管理はdockerが担うっていうのが住み分けとしてはいいのかな。

クラスタ機能がサポートされるのを待つのもあれだし、PR中のブランチ引っ張ってきて自分で試せないか調べてみようかな…。

Written with StackEdit.

Monday, September 22, 2014

Vim環境設定中

Unite の細々した設定

tabの作成をショートカット登録中。悩むな。

  • ss 画面を横分割
  • sv 画面を縦分割
  • ,ub unite bufferを開く
  • ,ud unite fileを開く
  • ,uf unite refisterを開く

vim-ref

vim上で各種言語のリファレンスを参照できるとな?

これ調べてたらいつの間にかjdei-vim。また後日。

jdei-vim

Python用のリファレンスライブラリ及び補完機能を提供する。

今使っているKaoriyaさんのVimはPython2python3両方使えるようになっている。これに関係するか分からないけど、インストール後にvim hello.pyとすると謎のエラーで落ち続けて涙目になった。

エラー:

  1. No module named site と言われて落ちる
  2. site.pyを実行しようとして落ちる

原因:

  • 1.は環境変数PYTHONPATHを適切に設定していないから。なりふり構わず以下のようにしたら通った。
PYTHONPATH=C:\Python34;C:\Python34\Lib;C:\Python34\DLLs;C:\Python34\libs;

要らないパスもあると思う。

  • 2.はjdei-vimのデフォルトの設定がpython2を見にいくため。インストールしていないpython2を使ってpython3で書かれたライブラリを実行しようとするからこける、って事なんだと思う。

.vimrcにこの一行を書けば直る。

let g:jedi#force_py_version = 3

たったこれだけのために1時間近くかかった。要領悪いなぁorz。

テキストブラウザ

Lynxw3mの2つがあるらしい。ちょっと調べた感じ

  • w3m … 日本人作者, 細かな便利機能豊富, 正式版がない. 2011年最終更新.
  • Lynx … 多分デファクト. インストール楽, 2007年が最終更新か…

という状況。どっち入れるかな。どっちもかな。

Written with StackEdit.

Friday, September 12, 2014

Eucalyputus

2014/09/12(金)にHPが買収すると発表したので知った。OSS版プライベートクラウドの古参っぽい何か。
HPはEucalyptusの社長さんをクラウド部門のトップにするとか。

サイト: https://www.eucalyptus.com

ちょっと調べてみたら、これ、Open Stackと同じIaaSに分類されるのかな。ただしOpen Stackに比べると構造がずっとシンプル。その分、インストールも楽そう。(CentOS 6.5 minimize上でcurlコマンド打つだけと公式サイトでは言っている)

だけどもOpen Stackのインターフェースが標準になりそうな今、魅力は少し薄いな…。その辺の余ったPCをかき集めて簡単なクラスタを作るくらいはできそうだが。

参考情報: http://www.slideshare.net/daisukekikuchi906/eucalyptus-23505115

  • Amazon EC2, Amazon S3互換らしい。
  • 動作環境はZenKVM

メモ

SCALRとかいう名前で複数のIaaS(Public/Private)をGUIで操作できるアプリケーションをAXLBITとかいう日本の?会社が出してるなー。みんな考えることは同じか…この辺は芽がなさそうorz

Written with StackEdit.

Monday, September 8, 2014

proxy環境下でcoreos

はまると涙目になるので一応メモ。

proxy環境下でcoreos、更にはdockerを使おうとすると、docker hub (repository)に既存のイメージー例えばredisーを取りにいくところで間違いなく失敗する。

gituhubで公開されているプログラムは、親切な人だとREADME.mdにUsage under proxyみたいな感じで対応を書いてくれるんだけど、正直あまり期待できない。外人さんたちは、あんまりproxyの下にはいないのかね。

愚痴はこれまでにしてproxyを突破する方法。

proxy回避方法

docker.serviceははじめからにcoreosに組み込まれていて、こいつの設定ファイルは以下の場所にある。

/usr/lib64/systemd/system/docker.service
## 中身 ##
Unit]
Description=Docker Application Container Engine 
Documentation=http://docs.docker.io
Requires=docker.socket

[Service]
Environment="TMPDIR=/var/tmp/"
ExecStartPre=/bin/mount --make-rprivate /
LimitNOFILE=1048576
LimitNPROC=1048576
# Run docker but don't have docker automatically restart
# containers. This is a job for systemd and unit files.
ExecStart=/usr/bin/docker -d -s=btrfs -r=false -H fd://

[Install]
WantedBy=multi-user.target

当然ながらproxyなんか考慮しているはずもない。ので、/etc/systemd/system/の下にこのdocker.serviceをコピーして、proxyの設定を付け加える。幸い、coreos(systemd)さんは/etc/systemd/system/の下に同名の.serviceファイルがあると、こっちを優先してくれる模様。

さあ、[Service]の直下に以下の呪文を書き足すのです。

Environment="http_proxy=http://proxy.xxx.yyy.com:1234"

なんだかなあ…。

Written with StackEdit.

kubernetesとcoreos (船頭を連れてくる)

前回、vagrant x virtualboxの上でcoreos x 2を連結することに成功したので、今回はcore-01のほうにkubernetesをインストールしてみる。(本来ならkubernetesにcore-02もデプロイ先として認識させる必要があると思う。)

参考にしたのはcoreos本家サイトのkubernetsインストール手順part1

インストール

基本はサイトに書いてある通りでいいんだけど、微妙にタイポ?してるっぽいから改めて書き出す。

core@core-01 ~ $ sudo mkdir -p /opt/kubernetes
core@core-01 ~ $ sudo mkdir -p /opt/bin
core@core-01 ~ $ sudo chown -R core: /opt/kubernetes
core@core-01 ~ $ sudo chown -R core: /opt/bin
core@core-01 ~ $ cd /opt/kuberbetes

そんでもってkubernetsのバイナリを取得、/opt/binの下に置く。

core@core-01 /opt/kubernates $ wget https://github.com/kelseyhightower/kubernetes-coreos/releases/download/v0.0.1/kubernetes-coreos.tar.gz
...(snip)...
core@core-01 /opt/kubernates $ cd ../  
core@core-01 /opt $ tar -C bin/ -xvf kubernates/kubernetes-coreos.tar.gz
apiserver
controller-manager
kubecfg
kubelet
proxy

続いてunitの設定ファイルをgithubから引っ張ってくる。unitsディレクトリの中から必要なserviceファイルだけ/etc/systemd/system/ディレクトリ下にコピー。今回はdocker.serviceはコピーしない。動かなくなる。

core@core-01 /opt $ cd kubernates/
kubernetes-coreos.tar.gz
core@core-01 /opt/kubernates $ git clone https://github.com/kelseyhightower/kubernetes-coreos.git
...(snip)...
core@core-01 /opt/kubernates $ sudo cp kubernetes-coreos/units/apiserver.service /etc/systemd/system/
core@core-01 /opt/kubernates $ sudo cp kubernetes-coreos/units/controller-manager.service /etc/systemd/system/
core@core-01 /opt/kubernates $ sudo cp kubernetes-coreos/units/kubelet.service /etc/systemd/system/
core@core-01 /opt/kubernates $ sudo cp kubernetes-coreos/units/proxy.service /etc/systemd/system/

更にcontroller-manager.serviceがそのままで動かないので、手を加える。

変更箇所のみ抜粋。

ExecStart=/opt/bin/controller-manager \
-etcd_servers http://127.0.0.1:4001 \
-master 127.0.0.1:8080 \
--logtostderr=true

etcd_serversの設定はとりあえず動けばいいと割り切り中。もしかしたらこれhttp://172.17.8.1:4001(ホストマシン)にすべきかもしれない。

ここで一応、etcdサービス、dockerサービスの状態を確認。

core@core-01 /opt/kubernates $ sudo systemctl status etcd
 etcd.service - etcd
   Loaded: loaded (/usr/lib64/systemd/system/etcd.service; disabled)
  Drop-In: /run/systemd/system/etcd.service.d
           └─20-cloudinit.conf
   Active: active (running) since Mon 2014-09-08 11:05:07 UTC; 15min ago
 Main PID: 554 (etcd)
 ...(snip)...
 core@core-01 /opt/kubernates $ sudo systemctl status docker
 docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib64/systemd/system/docker.service; disabled)
   Active: inactive (dead)
     Docs: http://docs.docker.io

よし、動いているぞ。先ほどコピーしたkubernetesのサービスを順次起動していく。

core@core-01 /opt/kubernates $ sudo systemctl daemon-reload
core@core-01 /opt/kubernates $ sudo systemctl start apiserver
core@core-01 /opt/kubernates $ sudo systemctl start controller-manager
core@core-01 /opt/kubernates $ sudo systemctl start kubelet
core@core-01 /opt/kubernates $ sudo systemctl start controller-proxy

各サービスのステータスを確認。

core@core-01 /opt/kubernates $ sudo systemctl status apiserver
core@core-01 /opt/kubernates $ sudo systemctl status controller-manager
core@core-01 /opt/kubernates $ sudo systemctl status kubelet
core@core-01 /opt/kubernates $ sudo systemctl status controller-proxy

全部「Active: active (running)」にはなった。

それではさっそくredisのサービスを立ち上げちゃうんだからね!

core@core-01 /opt/kubernates $ mkdir kubernetes-coreos/pods
core@core-01 /opt/kubernates $ vim kubernetes-coreos/pods/redis.json
# redis.jsonをコピペ
core@core-01 /opt/kubernates $ kubecfg -h http://127.0.0.1:8080 -c kubernetes-coreos/pods/redis.json create /pods

Name                Image(s)            Host                Labels
----------          ----------          ----------          ----------
redis               dockerfile/redis    /                   name=redis

redis.jsonは公式サイトの例をまるまるコピー。dockerへのデプロイが終わるまで、ちょっと時間がかかる。なんか動いたっぽい…。

起動したコンテナのIPアドレスを確認。

core@core-01 /opt/kubernates $ ip a show docker0
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP 
    link/ether 56:84:7a:fe:97:99 brd ff:ff:ff:ff:ff:ff
    inet 10.1.42.1/16 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::5484:7aff:fefe:9799/64 scope link 
       valid_lft forever preferred_lft forever

redis-cliで10.1.42.1にアクセスできれば動いてるってことになりそうだな。

core@core-01 /opt/kubernates $ docker run -t -i dockerfile/redis /usr/local/bin/redis-cli -h 10.1.42.1
10.1.42.1:6379> set yukaary craft
OK
10.1.42.1:6379> get yukaary
"craft"
10.1.42.1:6379> quit

やったぜ。最後に立ち上げたpodsっていうのか?を消して今回はここでおしまい。

core@core-01 /opt/kubernates $ kubecfg -h http://127.0.0.1:8080 delete /pods/redisStatus
----------
success

まとめ

  • 次回はcore-02も認識させてマルチホストにしたいな。
    • 公式サイトのブログのpart2がそれにあたると期待したい。
  • jourtnalctlみるとetcdが警告とかエラーとかが結構あるんだが、大丈夫なのか…?
  • この環境にfigも入れたい。入るのかなあ。

Sunday, September 7, 2014

Go tour #48

#48 Cube rootの計算。

package main

import (
    "fmt"
    "math/cmplx"
)

func Cbrt(x complex128) complex128 {
    var z complex128 = x

    // なんとz,xが参照できる件w
    f := func() complex128 {
        //fmt.Println("z=", z, "x=", x)
        for i := 0; i < 20; i++ {
            z = z - ((z*z*z - x)/(3*z*z))
        }
        return z
    }
    return f()
}

func main() {
    fmt.Println(Cbrt(2))
    fmt.Println(cmplx.Pow(2, 1.0/3))
}

なんとなく関数の中に関数を定義してみた。理由はない。

Written with StackEdit.

Go tour #41 #44

気が向いた時にぼちぼち進めているけど、進みが遅いんじゃ~。なんか変なウイルスにかかって調子悪いし仕方ないね。(まさかデングなんとかじゃないよね…;ω;)

tour #41

Word Count関数。mapの使い方のおさらいだね。しかし、このmapの定義、違和感が凄いんだけど…いや、いいけどさ。

package main

import (
    "strings"
    "code.google.com/p/go-tour/wc"
)

func WordCount(s string) map[string]int {
    m := make(map[string]int)

    for _, value := range strings.Fields(s) {
        m[value] = m[value] + 1
    }

    return m
}

func main() {
    wc.Test(WordCount)
}

toue #44

フィボナッチ数列~。関数はクロージャだってことのおさらい。

package main

import "fmt"

func fibonacci() func() int {
    var f0, f1 int = 0, 1
    return func() int {
        fn := f0 + f1
        f0 = f1
        f1 = fn
        return fn
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

関数を返り値として返す。返される関数は関数外の変数(上のf0, f1)とバインドしている。新たにfibonacci()により関数を取得しても既存の関数にバインドした変数とは被らない。

…よく出来てるなと思った。確かjavascriptでもこんな書き方したな。あっちは変数の扱いどうだったかな…。

Friday, September 5, 2014

coreos x fleet x etcd (艦隊を組むぞ!)

みんな、丸太は持ったか!?
あのホラー系コメディ漫画、おわっちゃった件。

3〜4番煎じネタだけど、くだらないところで大いにはまったので書き残しておくよ。激しく時間を浪費した(;_;

coreos-vagrant

google、pivotal、vmware、dockerと蒼々たる巨人達がコンテナ技術で提携を発表したここ最近、相変わらずdockerが盛り上がってるので、てくてく後ろを着いていこうと思った。

googleはkubernetesとかいうコンテナ用クラスタ管理システムを公開していて、こちらも興味があるんだけど、自分がこの分野に疎すぎるので、今回はもう少し基本を学べるように見えるcoreosに付属するfleetというクラスタ管理プログラムを試してみる。

fleet

公式サイトを見て、更に自分でもちょっと触ってみた感じ、etcdというKey, Valueストアを介して複数の(仮想)マシンを連結、1のマシンとして扱うことができる。

外部からデプロイコマンドを打ったらクラスタを構成するマシンのどれか一つでdockerコンテナが立ち上がるって理解でいいんだろうか。死活管理も入っているように見えるが、この辺はまだ不勉強。

ともかくクラスタを構成できないと話にならない。手元のへぼいiMacでも大丈夫そうなcoreos-vagrantで試してみる。

yukaarybox:project yukaary$ git clone https://github.com/coreos/coreos-vagrant.git
yukaarybox:project yukaary$ cd coreos-vagrant

ここでconfig.rb.sampleuser-data.sampleからコピーして、config.rbuser-dataを作る。続いて編集。

変更箇所:config.rb

$num_instances=2
$update_channel='stable'
$expose_docker_tcp=2375
$vb_gui = false
$vb_memory = 512 
$vb_cpus = 1

仮想マシンを2つ立ち上げる、coreosはstableを使用、ホストマシンからdockerコマンドを打てるようにする、仮想マシンのスペックを調整、以上。

変更箇所:user-data

discovery: http://172.17.8.1:4001/v2/keys/machines

クラスタを構成する時に照会するetcdのキー名。デフォルトでは、coreos側がご親切に照会用のetcdサーバー(https://discovery.etcd.i
o/)を立ち上げてくれており、キーを発行してそちらを見ればOK!となっているが、封権的なこの日本社会では、なかなかこれが利用できる環境にない…。

仕方がないのでvirtualboxで立ち上げるcoreosより絶対先に存在しており、かつ、ゲストマシンから間違いなくアクセス可能なホストマシン(yukaarybox/iMAC)にdiscoverly役を担ってもらうことにする。上の172.17.8.1はゲストマシンから見たホストのIPアドレス。

このetcd、coreosの構成するsystemdなどと並ぶ一大要素らしい。

公式サイト(英語)を読むと、プロセスの起動順序を制御するためにunittargetという2つの概念を利用する、この発想は既存のcloudなんとかから得ていて、coreosにとって必要な要素だけを取り出してきたとか色々書いてある。

goで実装されているのでOSXにgoをまずはインストール。インストール先。etcdをインストールするときに環境変数GOPATHがないと怒られるので、適当なディレクトリに設定しておく。

~/.bash_profile

export GOPATH=/Users/yukaary/go

etcdをインストール。

$ go get github.com/coreos/etcd

コマンドが終了するとGOPATH/bin/etcdが出来ている。そのまま./etcdとすると起動しちゃうから騙されたんだけど、まともに動いてなかったorz。これでゲストマシンx2が連結してくれず数時間はまった黒歴史。

(これだからgithubのmasterは嫌なんじゃ〜〜〜〜。)

go getしたソースコードはGOPATH/srcの中にあるので、そこでまともに動いてくれそうなブランチかタグを調べてみる。

yukaarybox:etcd yukaary$ git tag
0
v0.1.0
v0.1.1
v0.1.2
v0.2.0
v0.2.0-rc0
v0.2.0-rc1
v0.2.0-rc2
v0.2.0-rc3
v0.2.0-rc4
v0.3.0
v0.4.0
v0.4.1
v0.4.2
v0.4.3
v0.4.4
v0.4.5
v0.4.6

v0.4.6。多分これだな…。

yukaarybox:etcd yukaary$ git checkout v0.4.6
yukaarybox:etcd yukaary$ go install $ go buildもいるのかな?ちょと分からない。

これでまともに動きそうなetcdが出来た。IPアドレス:ポートを指定して起動する。

./etcd -addr 172.17.8.1:4001

別ターミナルでVagrantを起動。

yukaarybox:coreos-vagrant yukaary$ vagrant up
...

ゲストマシン起動完了後、core-01, core-02でそれぞれ。

core@core-01 ~ $ fleetctl list-machines -l
MACHINE                 IP      METADATA
b29eb20a7acb4b6fac7dd7839d15ee08    172.17.8.101    -
b2aff8b7ea5d4663a70900f560376c23    172.17.8.102    -
core@core-02 ~ $ fleetctl list-machines -l
MACHINE                 IP      METADATA
b29eb20a7acb4b6fac7dd7839d15ee08    172.17.8.101    -
b2aff8b7ea5d4663a70900f560376c23    172.17.8.102    -

やったぜ。

Written with StackEdit.