Chronograf,Telegraf,Influxdbでサーバとコンテナ情報を可視化する

こんにちは。@jedipunkz です。

Influxdb が Influxdata (https://influxdata.com/) として生まれ変わり公式の メトリクス送信エージェント Telegraf と可視化ツール Chronograf をリリースしたので 使ってみました。

3つのツールの役割は下記のとおりです。

  • Chronograf : 可視化ツール, Grafana 相当のソフトウェアです
  • Telegraf : メトリクス情報を Influxdb に送信するエージェント
  • Influxdb : メトリクス情報を格納する時系列データベース

以前に cAdvisor, influxdb, grafana を使って Docker コンテナのメトリクスを可視 化する記事を書きましたが telegraf を使うとサーバ情報と合わせて Docker コンテナ のメトリクスも influxdb に送信することが出来ます。個人的にはそのコンテナ情報の 扱いもサーバ情報と同様に扱ってくれる点に期待しつつ、評価してみました。

今回の環境

今回は Ubuntu 15.04 vivid64 を使ってテストしています。

influxdb をインストールして起動

最新リリース版の deb パッケージが用意されていたのでこれを使いました。

1
2
3
wget http://influxdb.s3.amazonaws.com/influxdb_0.9.5.1_amd64.deb
sudo dpkg -i influxdb_0.9.5.1_amd64.deb
sudo service influxdb start

telegraf のインストールと起動

こちらも deb パッケージで。

1
2
wget http://get.influxdb.org/telegraf/telegraf_0.2.4_amd64.deb
sudo dpkg -i telegraf_0.2.4_amd64.deb

コンフィギュレーションですが今回は CPU, Disk, Net, Docker のメトリクス情報を送 信するようにしました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[agent]
    interval = "0.1s"

[outputs]

[outputs.influxdb]
    urls = ["http://localhost:8086"]
    database = "telegraf-test"
    user_agent = "telegraf"

[plugins]
[[plugins.cpu]]
  percpu = true
  totalcpu = false
  drop = ["cpu_time*"]

[[plugins.disk]]
  [plugins.disk.tagpass]
    fstype = [ "ext4", "xfs" ]
    #path = [ "/home*" ]

[[plugins.disk]]
  pass = [ "disk_inodes*" ]
  
[[plugins.docker]]

[[plugins.net]]
  interfaces = ["eth0"]

他にも色々メトリクス情報を取得できそうです、下記のサイトを参考にしてみてください。 https://github.com/influxdb/telegraf/blob/0.3.0/CONFIGURATION.md

telegraf を起動します。Docker コンテナのメトリクスを取得するために root ユーザ で起動する必要があります。

1
sudo telegraf -config telegraf.conf

chronograf のインストールと起動

こちらも deb でインストールします。

1
2
3
4
wget https://s3.amazonaws.com/get.influxdb.org/chronograf/chronograf_0.4.0_amd64.deb
sudo dpkg -i chronograf_0.4.0_amd64.deb
sudo /opt/chronograf/chronograf -sample-config > /opt/chronograf/config.toml
sudo service chronograf start

グラフの描画

この状態でブラウザでアクセスしてみましょう。

http://<ホストのIPアドレス>:10000/

アクセスすると簡単なガイドが走りますのでここでは設定方法は省きます。Grafana を使った場合と 同様に気をつけるポイントは下記のとおりです。

  • ‘filter by’ に描画したいリソース名を選択(CPU,Disk,Net,Dockerの各リソース)
  • Database に telegraf.conf に記した ‘telegraf-test’ を選択

すると下記のようなグラフやダッシュボードが作成されます。下記は CPU 使用率をグ ラフ化したものです。

こちらは Docker 関連のグラフ。

複数のグラフを1つのダッシュボードにまとめることもできるようです。

まとめ

個人的には Grafana の UI はとてもわかりずらかったので公式の可視化ツールが出てきて良かった と思っています。操作もとても理解しやすくなっています。Telegraf についても公式のメトリクス 情報送信エージェントということで安心感があります。また Grafana は別途 HTTP サー バが必要でしたが Chronograf は HTTP サーバも内包しているのでセットアップが簡単 でした。

ただ configuration guide がまだまだ説明不十分なので凝ったことをしようすとする とソースを読まなくてはいけないかもしれません。

いずれにしてもサーバのメトリクス情報と共に cAdvisor 等のソフトウェアを用いなく てもサーバ上で稼働しているコンテナ周りの情報も取得できたので個人的にはハッピー。 cAdvisor でしか取得できない情報もありそうですが今後、導入を検討する上で評価し ていきたいと思います。

Weave を使った Docker ネットワーク

こんにちは。@jedipunkz です。

今回は Weave というコンテナ間のネットワークを提供してくれる Docker のネットワークプラ グインを使ってみました。下記のような沢山の機能があるようです。

  • Fast Data Path
  • Docker Network Plugin
  • Security
  • Dynamic Netwrok Attachment
  • Service Binding
  • Fault Tolerance
  • etc …

この記事では上から幾つか抜粋して、Weave ってどのように動かせるのか?を解説します。 そこから Weave が一体ナニモノなのか理解できればなぁと思います。

Vagrant を使った構成

この記事では下記の構成を作って色々と試していきます。使う技術は

  • Vagrant
  • Docker
  • Weave

です。

1
2
3
4
5
6
7
+---------------------+ +---------------------+ +---------------------+
| docker container a1 | | docker container a2 | | docker container a3 |
+---------------------+ +---------------------+ +---------------------+
|    vagrant host 1   | |    vagrant host 2   | |    vagrant host 3   |
+---------------------+-+---------------------+-+---------------------+
|                          Mac or Windows                             |
+---------------------------------------------------------------------+

特徴としては

  • 作業端末(Mac or Windows or Linux)上で Vagrant を動作させる
  • 各 Vagrant VM 同士はホスト OS のネットワークインターフェース上で疎通が取れる

です。

Vagrantfile の作成と host1,2,3 の起動

上記の3台の構成を下記の Vagrantfile で構築します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Vagrant.configure(2) do |config|
  config.vm.box = "ubuntu/vivid64"

  config.vm.define "host1" do |server|
    server.vm.network "private_network", ip: "192.168.33.11"
  end

  config.vm.define "host2" do |server|
    server.vm.network "private_network", ip: "192.168.33.12"
  end

  config.vm.define "host3" do |server|
    server.vm.network "private_network", ip: "192.168.33.13"
  end

  config.vm.provision :shell, inline: <<-SHELL
apt-get update
apt-get install -y libsqlite3-dev docker.io
curl -L git.io/weave -o /usr/local/bin/weave
chmod a+x /usr/local/bin/weave
  SHELL
end

vagrant コマンドを使って host1, host2, host3 を起動します。

1
2
3
4
$ vagrant up
$ vagrant ssh host1 # <--- host1 に SSH する場合
$ vagrant ssh host2 # <--- host2 に SSH する場合
$ vagrant ssh host3 # <--- host3 に SSH する場合

物理ノードまたがったコンテナ間で通信をする

weave でまず物理ノードをまたがったコンテナ間で通信をさせてみましょう。ここでは 上図の host1, host2 を使います。通常、物理ノードまたがっていると各々のホストで 稼働する Docker コンテナは通信し合えませんが weave を使うと通信しあうことが出 来ます。

まず weave が用いる Docker コンテナを稼働します。下記のように /16 でレンジを切って 更にそこからデフォルトのレンジを指定することが出来ます。

1
2
3
4
host1# weave launch --ipalloc-range 10.2.0.0/16 --ipalloc-default-subnet 10.2.1.0/24
host1# eval $(weave env)
host2# weave launch --ipalloc-range 10.2.0.0/16 --ipalloc-default-subnet 10.2.1.0/24 192.168.33.11
host2# eval $(weave env)

この状態で下記のようなコンテナが稼働します。

1
2
3
4
host1# docker ps
CONTAINER ID        IMAGE                        COMMAND                CREATED             STATUS              PORTS               NAMES
c55e96b4bdf9        weaveworks/weaveexec:1.4.0   "/home/weave/weavepr   4 seconds ago       Up 3 seconds                            weaveproxy
394382c9c5d9        weaveworks/weave:1.4.0       "/home/weave/weaver    5 seconds ago       Up 4 seconds                            weave

host1, host2 でそれぞれテスト用コンテナを稼働させます。名前を –name オプションで付けるのを 忘れないようにしてください。

1
2
host1# docker run --name a1 -ti ubuntu
host2# docker run --name a2 -ti ubuntu

どちらか一方から ping をもう一方に打ってみましょう。下記では a2 -> a1 の流れで ping を実行しています。

1
2
3
4
5
6
7
8
9
root@a2:/# ping 10.2.1.1 -c 3
PING 10.2.1.1 (10.2.1.1) 56(84) bytes of data.
64 bytes from 10.2.1.1: icmp_seq=1 ttl=64 time=0.316 ms
64 bytes from 10.2.1.1: icmp_seq=2 ttl=64 time=0.501 ms
64 bytes from 10.2.1.1: icmp_seq=3 ttl=64 time=0.619 ms

--- 10.2.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.316/0.478/0.619/0.127 ms

また docker コンテナを起動する時に指定した –name a1, –name a2 の名前で ping を実行してみましょう。これも weave の機能の1つで dns lookup が行えます。

1
2
3
4
5
6
7
8
9
root@b2:/# ping a1 -c 3
PING b1.weave.local (10.2.1.1) 56(84) bytes of data.
64 bytes from a1.weave.local (10.2.1.1): icmp_seq=1 ttl=64 time=1.14 ms
64 bytes from a1.weave.local (10.2.1.1): icmp_seq=2 ttl=64 time=0.446 ms
64 bytes from a1.weave.local (10.2.1.1): icmp_seq=3 ttl=64 time=0.364 ms

--- b1.weave.local ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 0.364/0.653/1.149/0.352 ms

結果から、異なる物理ノード(今回は VM)上で動作させた Docker コンテナ同士が通信し合えた ことがわかります。またコンテナ名の DNS 的は名前解決も可能になりました。

ダイナミックにネットワークをアタッチ・デタッチする

次に weave のネットワークを動的(コンテナがオンラインのまま)にアタッチ・デタッ チすることが出来るので試してみます。

最初に weave のネットワークに属さない a1-1 という名前のコンテナを作ります。 docker exec で IP アドレスを確認すると eth0, lo のインターフェースしか持っていない ことが判ります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
host1# C=$(docker run --name a1-1 -e WEAVE_CIDR=none -dti ubuntu)
host1# docker exec -it a1-1 ip a # <--- docker コンテナ内でコマンド実行
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
25: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.5/16 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe11:5/64 scope link
       valid_lft forever preferred_lft forever

では weave のネットワークを a1-1 コンテナにアタッチしてみましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
host1# weave attach $C
10.2.1.1
host1# docker exec -it a1-1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
25: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.5/16 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe11:5/64 scope link
       valid_lft forever preferred_lft forever
27: ethwe: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1410 qdisc pfifo_fast state UP group default qlen 1000
    link/ether aa:15:06:51:6a:3b brd ff:ff:ff:ff:ff:ff
    inet 10.2.1.1/24 scope global ethwe
       valid_lft forever preferred_lft forever
    inet6 fe80::a815:6ff:fe51:6a3b/64 scope link
       valid_lft forever preferred_lft forever

上記のようにインターフェース ethwe が付与され最初に指定したデフォルトのサブネッ ト上の IP アドレスが付きました。

次に weave ネットワークを複数アタッチしてみましょう。default, 10.2.2.0/24, 10.2.3.0/24 のネットワーク(サブネット)をアタッチします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
host1# weave attach net:default net:10.2.2.0/24 net:10.2.3.0/24 $C
10.2.1.1 10.2.2.1 10.2.3.1
root@vagrant-ubuntu-vivid-64:~# docker exec -it b3 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
25: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.5/16 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe11:5/64 scope link
       valid_lft forever preferred_lft forever
33: ethwe: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1410 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 9a:74:73:1b:24:a9 brd ff:ff:ff:ff:ff:ff
    inet 10.2.1.1/24 scope global ethwe
       valid_lft forever preferred_lft forever
    inet 10.2.2.1/24 scope global ethwe
       valid_lft forever preferred_lft forever
    inet 10.2.3.1/24 scope global ethwe
       valid_lft forever preferred_lft forever
    inet6 fe80::9874:73ff:fe1b:24a9/64 scope link
       valid_lft forever preferred_lft forever

結果、ethwe インターフェースに3つの IP アドレスが付与されました。 この様にダイナミックにコンテナに対して weave ネットワークをアタッチすることが出来ます。

コンテナ外部から情報を取得する

下記のようにコンテナを起動しているホスト上 (Vagrant VM) からコンテナの情報を取 得する事もできます。シンプルですがオーケストレーション・自動化を行う上で重要な機能に なりそうな予感がします。

1
2
3
4
host1# weave expose
10.2.1.1
host1# weave dns-lookup a2
10.2.1.128

ダイナミックに物理ノードを追加し weave ネットワークへ

物理ノード(今回の場合 vagrant vm)を追加し上記で作成した weave ネットワークへ参 加させることも可能です。なお、今回は上記の vagrant up の時点で追加分の vm (host3) を既に稼働させています。

host1 で新しい物理ノードを接続します。

1
2
3
4
host1# weave connect 192.168.33.12
host1# weave status targets
192.168.33.13
192.168.33.12

host3 で weave コンテナ・テストコンテナを起動します。 下記で指定している 192.168.33.11 は host1 の IP アドレスです。

1
2
host3# weave launch --ipalloc-range 10.2.0.0/16 --ipalloc-default-subnet 10.2.1.0/24 192.168.33.11
host3# docker run --name a3 -ti ubuntu

host2 の a2 コンテナに ping を打ってみます。

1
2
3
4
5
roota3:/# ping a2 -c 3
PING a2.weave.local (10.2.1.128) 56(84) bytes of data.
64 bytes from a2.weave.local (10.2.1.128): icmp_seq=1 ttl=64 time=0.366 ms
64 bytes from a2.weave.local (10.2.1.128): icmp_seq=2 ttl=64 time=0.709 ms
64 bytes from a2.weave.local (10.2.1.128): icmp_seq=3 ttl=64 time=0.569 ms

host3 上の a3 コンテナが既存の weave ネットワークに参加し通信出来たことが確認 できました。

まとめと考察

コンフィギュレーションらしきモノを記述することなく Docker コンテナ間の通信 が出来ました。これは自動化する際に優位になるでしょう。また今回紹介したのは ‘weave net’ と呼ばれるモノですが他にも ‘weave scope’, ‘weave run’ といったモノ があります。

http://weave.works/product/

また Docker Swarm, Compose と組み合わせる構成も組めるようです。試してみたい方 がいましたら是非。

http://weave.works/guides/weave-and-docker-platform/compose-scalable-swarm-cluster-with-weave.html

ですが結果、まだ weave をどう自分たちのサービスに組み込めるかは検討が付いてい ません。’出来る’ と ‘運用できる’ が別物であることと、コンテナまわりのネットワー ク機能全般に理解して選定する必要がありそうです。

参考サイト

http://weave.works/docs/

CodeDeploy, S3 を併用して CircleCI により VPC にデプロイ

こんにちは。@jedipunkz です。

最近、業務で CircleCI を扱っていて、だいぶ “出来ること・出来ないこと” や “出来ないこと に対する回避方法” 等のノウハウが若干溜まってきたので共有したいなと思います。

この記事の前提…

ここでは CodeDeploy の設定方法や、CircleCIの設定方法等に関しては記述しませ ん。あくまで、Tips 的な内容にしています。また運用する上で想定できる問題点と、 それの回避方法等…についてまとめています。

CirlceCI と併用するサービスについて

CircleCI は Github と連携してレポジトリ内の制御ファイル circle.yml に従ってテ スト・ビルド・デプロイを実施してくれる CI サービスです。ただ CircleCI は自分た ちの管理しているシステム外にあるので、AWS VPC を用いていると VPC 内のプライベー トインスタンスにデプロイするのが難しいです。プロキシ挟んで・・ってことは出来そ うですがプロキシの運用もしたくない、AWS のインフラリソースに任せることが出来た らインスタンス・インスタンス上のミドルウェアを運用しなくて済むので運用コストが 省けそう。ってことで

を併用することを考えました。

S3 は皆さんご存知のオブジェクトストレージです。CircleCI 用のバケットを作って、 ビルドした結果を格納します。私の務めている会社ではプログラミング言語として Scala を用いているので SBT というツールを使ってビルドします。その結果もバージョ ニングしつつ S3 バケットに格納できれば、万が一問題が発生した時にバイナリ毎切り 戻すことが出来そうです。

また CodeDeploy は EC2 インスタンス・またオンプレのインスタンスへコードのデプ ロイが可能になるサービスです。東京リージョンでは 2015/08 から利用が可能になり ました。これの便利なところは CircleCI 等の CI サービスから簡単に叩けて、VPC 内 のインスタンスに対してもデプロイが可能なところです。

Tips 的な情報として

circle.yml という CircleCI の制御ファイルがあります。Git レポジトリ内に格納することで CircleCI の動作を制御することが出来ます。この記事では circle.yml の紹介をメインとしたい と思います。

Git push からデプロイまでを自動で行う circle.yml

Github への push, merge をトリガーとしてデプロイまでの流れを自動で行う流れを組む場合の circle.yml を紹介します。全体の流れとしては…

  • レポジトリに git push, merge ことがトリガで処理が走る
  • circle.yml を元にテスト・ビルド(場合によってはテストのみ) が走る
  • S3 バケットにビルドされた結果が格納される
  • CodeDeploy が実行され S3 バケット内のビルドされた成果物を対象のインスタンスにデプロイする

となります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
machine:
  environment:
    SBT_VERSION: 0.13.9
    SBT_OPTS: "-Xms512M -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256M"
  services:
    - docker

dependencies:
  pre:
    - (事前に実行したいコマンド・スクリプトを記述)
  cache_directories:
    - "~/.sbt"

test:
  override:
    - sbt compile

deployment:
  production:
    branch: master
    codedeploy:
      codedeploy-sample:
        application_root: /
        region: ap-northeast-1
        revision_location:
          revision_type: S3
          s3_location:
            bucket: circleci-sample-bucket
            key_pattern: filename-{CIRCLE_BRANCH}-{CIRCLE_BUILD_NUM}.zip
        deployment_group: codedeploy-sample-group

それぞれのパラメータの意味

上記 circle.yml の重要なパラメータのみ説明していきます。 私が務めている会社は Scala を使っていると冒頭に説明しましたがテスト・ビルドに SBT を使うのでこのような記述になっています。Ruby や Python でも同様に記述でき ると思いますので読み替えてください。

  • machine -> environment : 全体で適用できる環境変数を定義します
  • dependencies -> pre : 事前に実行したいコマンド等を定義できます
  • test -> overide : テストを実行するコマンドを書きます。
  • deployment -> production -> branch : 適用するブランチ名と本番環境であることを記述します。
  • ‘codedeploy-sample’ : CodeDeploy 上にサンプルで作成した ‘Application’ 名です
  • s3_location -> bucket : ビルドした成果物を S3 へ格納する際のバケット名を記します
  • s3_location -> key_pattern : S3 バケットに収めるファイル名指定です
  • deployment_group : CodeDeploy で定義する ‘Deployment-Group’ 名です

より詳細な説明を読みたい場合は下記の URL に描いてあります。

https://circleci.com/docs/configuration

S3 のみににデプロイする例

上記の circle.yml ではビルドとデプロイを一気に処理するのですが、テスト・ビルドとデプロイを別けて 実行したい場面もありそうです。流れとしては…

  • レポジトリに git push, merge ことがトリガで処理が走る
  • circle.yml を元にテスト・ビルド(場合によってはテストのみ) が走る
  • S3 バケットにビルドされた結果が格納される

です。S3 のバケットに格納されたアプリを CodeDeploy を使ってデプロイするのは CodeDeploy の API を直接叩けば出来そうです。

http://docs.aws.amazon.com/codedeploy/latest/APIReference/API_CreateDeployment.html

このリファレンスにある “CreateDeployment” については後に例をあげます。

ただ、同様のサービスとして TravisCI 等は S3 にのみデプロイを実施する仕組みが用意されているのですが CircleCI にはこの機能はありませんでした。サポートに問い合わせもしたのですが、あまり良い回答ではありませんでした。

よって、下記のように awscli をテストコンテナ起動の度にインストールして S3 にアクセスすれば 上記の流れが組めそうです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
machine:
  environment:
    SBT_VERSION: 0.13.9
    SBT_OPTS: "-Xms512M -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256M"
  services:
    - docker

dependencies:
  pre:
    - sudo pip install awscli
  cache_directories:
    - "~/.sbt"

test:
  override:
    - sbt compile

deployment:
  master:
    branch: master
    commands:
      - zip -r sample-code-${CIRCLE_BRANCH}-${CIRCLE_BUILD_NUM}.zip .
      - aws s3 cp
        sample-code-${CIRCLE_PROJECT_REPONAME}-${CIRCLE_BRANCH}-${CIRCLE_BUILD_NUM}.zip s3://<バケット名>/<ディレクトリ>/ --region ap-northeast-1

事前に awscli をインストールしているだけです。

S3 バケットに格納された成果物を CodeDeploy を使って手動でデプロイするには下記 のコマンドで実施できます。

1
2
3
4
5
6
7
8
9
$ aws deploy create-deployment \
  --application-name codedeploy-sample \
  --deployment-config-name CodeDeployDefault.OneAtATime \
  --deployment-group-name codedeploy-sample-group \
  --description "deploy test" \
  --s3-location bucket=<バケット名>,bundleType=zip,key=<ファイル名>
  {
    "deploymentId": "d-2B4OAMT0B"
   }

deploymentId は CodeDeploy 上の Application に紐付いた ID です。CodeDeploy の API を叩くか AWS コンソールで確認可能です。

CircleCI の問題点とそれの回避方法

  • production と staging

1つのブランチで管理できる circle.yml は1つです。このファイルの中で定義できる ‘本番用’, ‘開発用’ の定義は deployment -> production, staging の2種類になります。この2つで管理しきれない環境がある場合(例えば staging 以前の development 環境がある) は、レポジトリのブランチを別けて circle.yml を管理する方法があると思います。

  • 複数のデプロイ先があるレポジトリの運用

同一のレポジトリ内で管理しているコードのデプロイ先が複数ある場合は CodeDeploy 上で1つの Application に対して複数の Deployment-Group を作成することで対応できます。ただ、cirlce.yml で定義できるデプロイ先は deployment_group: の1つ( 厳密に言うと production, staging の2つ) になるので、こちらもブランチによる circle.yml の別管理で回避できそうです。

こちらの問題については CircleCI 的にはおそらく「1つのレポジトリで管理するデプロイ先は1つに」というコンセプトなのかもしれません。

AWS IAM ユーザにアタッチする Policy 作成

IAM ユーザを CircleCI に事前に設定しておくことで直接 AWS のリソースを操作出来るのですが、 そのユーザにアタッチしておくべき Policy について例をあげておきます。

特定の S3 バケットにオブジェクト Put する Policy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1444196633000",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::<S3 バケット名>/*"
            ]
        }
    ]
}

CodeDeploy の各 Action を実行する Policy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "codedeploy:RegisterApplicationRevision",
                "codedeploy:GetApplicationRevision"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "codedeploy:CreateDeployment",
                "codedeploy:GetDeployment"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "codedeploy:GetDeploymentConfig"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

まとめ

CodeDeploy, S3 を併用することで CircleCI を使っても VPC 内のプライベートインス タンスにデプロイできることが判りました。もし EC2 インスタンスを使っている場合 は他の方法も取れることが判っています。circle.yml 内の pre: で指定出来るコマン ド・スクリプトで EC2 インスタンスに紐付いているセキュリティグループに穴あけ処 理を記述すれば良さそうです。デプロイが終わったら穴を塞げばいいですね。この辺の 例については国内でもブログ記事にされている方がいらっしゃいますので参考にしてくだ さい。

cAdvisor/influxDB/GrafanaでDockerリソース監視

こんにちは。@jedipunkz です。

今回は Docker ネタです。Docker 導入するにしても監視はどうする?という話になる と思うのですが、各 Monitoring as a Service を使うにしてもエージェント入れない といけないしお金掛かることもあるし..で、調べていたら cAdvisor というキーワード が出てきました。今回は cAdvisor を使ってコンテナの監視が出来ないか、について書 いていきたいと想います。

  • cAdvisor とは ?

cAdvisor は Kubernates で用いられているコンポーネントで単体でも利用可能とのこ と。Google が開発しています。また Docker コンテナの監視においてこの cAdvisor は一般化しつつあるようです。

https://github.com/google/cadvisor

  • 収集したメトリクスの保存

cAdvisor 自体も Docker で起動して、同ホスト上に起動している Docker コンテナの リソースをモニタリングしてくれます。そのメトリクスデータは幾つかの DB に保存出 来るのですが、そのうちの一つが influxDB です。influxDB は時系列データベースで す。システムのメトリクスデータを収めるのにちょうどいいデータベースになります。

https://influxdb.com/

  • DB に収めたメトリクスの可視化

influxDB に収めたメトリクスデータを可視化するのが Grafana です。Grafana のデー タソースは influxDB の他にも幾つかあり Elasticsearch, KairosDB, Graphite 等が それです。

http://grafana.org/

では早速試してみましょう。

前提の環境

今回は Vagrant を使います。また Vagrant 上で上記の3つのソフトウェアを Docker で稼働します。またどうせなので docker-compose を使って3つのコンテナを一斉に立 ち上げてみましょう。

VagrantFile の準備

下記のような VagrantFile を作成します。各ソフトウェアはそれぞれ WebUI を持って いて、そこに手元のコンピュータから接続するため forwarded_port しています。

1
2
3
4
5
6
7
8
9
10
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config|
    config.vm.box = "ubuntu/trusty64"
    config.vm.network "forwarded_port", guest: 8080, host: 8080
    config.vm.network "forwarded_port", guest: 8083, host: 8083
    config.vm.network "forwarded_port", guest: 3000, host: 3000
    config.vm.network "private_network", ip: "192.168.33.10"
end

Docker コンテナの起動と docker-compose.yml の準備

Vagrant を起動し docker, docker-compose のインストールを行います。

1
2
3
4
5
6
7
8
$ vagrant up
$ vagrant ssh
vagrant$ sudo apt-get update ; sudo apt-get -y install curl
vagrant$ curl -sSL https://get.docker.com/ | sh
vagrant$ sudo -i
vagrant# export VERSION_NUM=1.4.0
vagrant# curl -L https://github.com/docker/compose/releases/download/VERSION_NUM/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
vagrant# chmod +x /usr/local/bin/docker-compose

次に docker-compose.yml を作成します。上記3つのソフトウェアが稼働するコンテナ を起動するため下記のように記述しましょう。カレントディレクトリに作成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
InfluxSrv:
    image: "tutum/influxdb:0.8.8"
    ports:
        - "8083:8083"
        - "8086:8086"
    expose:
        - "8090"
        - "8099"
    environment:
        - PRE_CREATE_DB=cadvisor
cadvisor:
    image: "google/cadvisor:0.16.0"
    volumes:
        - "/:/rootfs:ro"
        - "/var/run:/var/run:rw"
        - "/sys:/sys:ro"
        - "/var/lib/docker/:/var/lib/docker:ro"
    links:
        - "InfluxSrv:influxsrv"
    ports:
        - "8080:8080"
    command: "-storage_driver=influxdb -storage_driver_db=cadvisor -storage_driver_host=influxsrv:8086 -storage_driver_user=root -storage_driver_password=root -storage_driver_secure=False"
grafana:
    image: "grafana/grafana:2.1.3"
    ports:
        - "3000:3000"
    environment:
        - INFLUXDB_HOST=localhost
        - INFLUXDB_PORT=8086
        - INFLUXDB_NAME=cadvisor
        - INFLUXDB_USER=root
        - INFLUXDB_PASS=root
    links:
        - "InfluxSrv:influxsrv"

コンテナの起動

docker コンテナを立ち上げます。

1
vagrant$ docker-compose -d

influxDB の WebUI に接続する

それでは起動したコンテナのうち一つ influxDB の WebUI に接続していましょう。 上記の VagrantFile では IP アドレスを 192.168.33.10 と指定しました。

URL : http://192.168.33.10:8083

データベースに接続します。

ユーザ名 : root パスワード : root

接続するとデータベース作成画面に飛びますので Database Datails 枠に “cadvisor” と入力、その他の項目はデフォルトのままで “Create Database” をクリックします。

cAdvisor の WebUI に接続する

続いて cAdvisor の WebUI に接続してみましょう。

URL : http://192.168.33.10:8080

ここでは特に作業の必要はありません。コンテナの監視が行われグラフが描画されてい ることを確認します。

Grafana の WebUI に接続する

最後に Grafana の WebUI です。

URL : http://192.168.33.10:3000 ユーザ名 : admin パスワード : admin

まずデータソースの設定を行います。左上のアイコンをクリックし “Data Sources” を 選択します。次に “Add New Data Source” ボタンをクリックします。

下記の情報を入力しましょう。

  • Name : influxdb
  • Type : influxDB 0.8.x
  • Url : http://influxsrv:8086
  • Access : proxy
  • Basic Auth User admin
  • Basic Auth Password admin
  • Database : cadvisor
  • User : root
  • Password : root

さて最後にグラフを作成していきます。左メニューの “Dashboard” を選択し上部の “Home” ボランを押し “+New” を押します。

下記の画面を参考にし値に入力していきます。

Metrics を選択しネットワークの受信転送量をグラフにしています。

  • series : ‘stats’
  • alias : RX Bytes
  • select mean(rx_bytes)

同じく送信転送量もグラフにします。Add Query を押すと追加できます。

  • series : ‘stats’
  • alias : TX Bytes
  • select mean(tx_bytes)

時間が経過すると下記のようにグラフが描画されます。

まとめと考察

3つのソフトウェア共に開発が活発であり、cAdvisor は特に Docker コンテナの監視と して一般化しつつあるよう。Kubernates の一部ということもありそう簡単には廃れな いと想います。コンテナの中にエージェント等を入れることもなく、これで Docker コ ンテナのリソース監視が出来そう。ただサービス監視は別途考えなくてはいけないなぁ という印象です。また、今回 docker-compose に記した各コンテナのバージョンは Docker Hub を確認すると別バージョンもあるので時期が経ってこのブログ記事をご覧 になった方は修正すると良いと想います。ただこの記事を書いている時点では influxDB の 0.9.x 系では動作しませんでした。よって latest ではなくバージョン指 定で記してあります。

参考にしたサイト

Knife-ZeroでOpenStack Kiloデプロイ(複数台編)

こんにちは。@jedipunkz です。

前回 OpenStack Kilo のオールインワン構成を Chef-Zero を使ってデプロイする方法 を書きましたが、複数台構成についても調べたので結果をまとめていきます。

使うのは openstack/openstack-chef-repo です。下記の URL にあります。

https://github.com/openstack/openstack-chef-repo

この中に Vagrant を使ったファイルが存在しますが、実機でのデプロイには全く役に 立ちません。自分で Environment ファイルを作成する必要があります。今回は前提の 構成を作って、それに合わせた Environment ファイルを記します。ほぼスタンダード な構成にしましたので、自分の環境に合わせて修正するのも比較的簡単だと想います。 参考にしてください。

今回は knife-zero を使ってデプロイします。Chef サーバが必要なく、knife-zero を 使うホスト上のオンメモリで Chef サーバが稼働するので準備がほとんど必要ありません。

早速ですが、構成と準備・そしてデプロイ作業を記していきます。

前提の構成

   +------------+
   | GW Router  |
+--+------------+
|  |
|  +--------------+--------------+---------------------------- public network
|  | eth0         | eth0
|  +------------+ +------------+ +------------+ +------------+
|  | Controller | |  Network   | |  Compute   | | Knife-Zero | 
|  +------------+ +-------+----+ +------+-----+ +------------+
|  | eth1         | eth1  |      | eth1 |       | eth1 
+--+--------------+-------)------+------)-------+------------- api/management network
                          | eth2        | eth2
                          +-------------+--------------------- guest network

特徴としては…

  • public, api/management, guest の3つのネットワークに接続された OpenStack ホスト
  • Controller, Network, Compute の最小複数台構成
  • knife-zero を実行する ‘Knife-Zero’ ホスト
  • Knife-zero ホストは api/management network のみに接続で可
  • デプロイは api/management network を介して行う
  • public, api/management network はインターネットへの疎通が必須
  • OS は Ubuntu 14.04 amd64

とくに api/management network がインターネットへの疎通が必要なところに注意して ください。デプロイは knife-zero ホストで実行しますが、各ノードへログインしデプ ロイする際にインターネット上からパッケージの取得を試みます。

また api/management network を2つに分離するのも一般的ですが、ここでは一本にま とめています。

IP アドレス

IP アドレスは下記を前提にします。

interface IP addr
Controller eth0 10.0.1.10
Controller eth1 10.0.2.10
Network eth0 10.0.1.11
Network eth1 10.0.2.11
Network eth2 10.0.3.11
Compute eth1 10.0.2.12
Compute eth2 10.0.3.12
Knife-Zero eth1 10.0.2.13

ネットワークインターフェース設定

それぞれのホストで下記のようにネットワークインターフェースを設定します。

  • Controller ホスト

eth0, 1 を使用します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
auto eth0
iface eth0 inet static
    address 10.0.1.10
    netmask 255.255.255.0
    gateway 10.0.1.254
    dns-nameservers 8.8.8.8
    dns-search jedihub.com

auto eth1
iface eth1 inet static
    address 10.0.2.10
    netmask 255.255.255.0

auto eth2
iface eth2 inet manual
  • Network ホスト

eth0, 1, 2 全てを使用します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
auto eth0
iface eth0 inet static
        up ifconfig $IFACE 0.0.0.0 up
        up ip link set $IFACE promisc on
        down ip link set $IFACE promisc off
        down ifconfig $IFACE down
        address 10.0.1.11
        netmask 255.255.255.0

auto eth1
iface eth1 inet static
        address 10.0.2.11
        netmask 255.255.255.0
        gateway 10.0.2.248
        dns-nameservers 8.8.8.8
        dns-search jedihub.com

auto eth2
iface eth2 inet static
        address 10.0.3.11
        netmask 255.255.255.0
  • Compute ホスト

eth1, 2 を使用します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
auto eth0
iface eth0 inet manual

auto eth1
iface eth1 inet static
        address 10.0.2.12
        netmask 255.255.255.0
        gateway 10.0.2.248
        dns-nameservers 8.8.8.8
        dns-search jedihub.com

auto eth2
iface eth2 inet static
        address 10.0.3.12
        netmask 255.255.255.0

これらの作業は knife-zero からログインし eth1 を介して行ってください。でないと 接続が切断される可能性があります。

準備

knife-zero ホストに chef, knife-zero, berkshelf が入っている必要があるので、こ こでインストールしていきます。

knife-zero ホストに chef をインストールします。Omnibus パッケージを使って手っ 取り早く環境を整えます。

1
2
sudo -i
curl -L https://www.opscode.com/chef/install.sh | bash

Berkshelf をインストールするのに必要なソフトウェアをインストールします。

1
apt-get -y install build-essential zlib1g-dev libssl-dev libreadline-dev ruby-dev libxml2-dev libxslt-dev g++

Berkshelf をインストールします。

1
/opt/chef/embedded/bin/gem install berkshelf --no-ri --no-rdoc

最後に knife-zero をインストールします。

1
/opt/chef/embedded/bin/gem install knife-zero --no-ri --no-rdoc

デプロイ作業

それでは openstack-chef-repo を取得してデプロイの準備を行います。 ブランチの指定は行わず master ブランチを取得します。Kilo は master ブランチで 管理されています。次のバージョンの開発が始まるタイミングで ‘stable/kilo’ ブラ ンチに管理が移されます。

1
2
3
sudo -i
cd ~/
git clone https://github.com/openstack/openstack-chef-repo.git

次に Berkshelf を使って必要な Cookbooks をダウンロードします。

1
2
cd ~/openstack-chef-repo
/opt/chef/embedded/bin/berks vendor ./cookbooks

Environment を作成します。これは各環境に合わせた設定ファイルのようなもので、各 Cookbooks の Attributes を上書きする仕組みになっています。下記の内容を

1
openstack-chef-repo/environments/multi-neutron-kilo.json

というファイル名で保存してください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
{
  "name": "multi-neutron-kilo",
  "description": "test",
  "cookbook_versions": {
  },
  "json_class": "Chef::Environment",
  "chef_type": "environment",
  "default_attributes": {
  },
  "override_attributes": {
    "mysql": {
      "bind_address": "0.0.0.0",
      "server_root_password": "mysqlroot",
      "server_debian_password": "mysqlroot",
      "server_repl_password": "mysqlroot",
      "allow_remote_root": true,
      "root_network_acl": ["10.0.0.0/8"]
    },
    "rabbitmq": {
      "address": "0.0.0.0",
      "port": "5672",
      "loopback_users": []
    },
    "openstack": {
      "auth": {
        "validate_certs": false
      },
      "dashboard": {
        "session_backend": "file"
      },
      "block-storage": {
        "syslog": {
          "use": false
        },
        "api": {
          "ratelimit": "False"
        },
        "debug": true,
        "image_api_chef_role": "os-image",
        "identity_service_chef_role": "os-identity",
        "rabbit_server_chef_role": "os-ops-messaging"
      },
      "compute": {
        "rabbit": {
          "host": "10.0.2.10"
        },
        "novnc_proxy": {
          "bind_interface": "eth1"
        },
        "libvirt": {
          "virt_type": "qemu",
          "bind_interface": "eth1"
        },
        "novnc_proxy": {
          "bind_interface": "eth1"
        },
        "xvpvnc_proxy": {
          "bind_interface": "eth1"
        },
        "image_api_chef_role": "os-image",
        "identity_service_chef_role": "os-identity",
        "nova_setup_chef_role": "os-compute-api",
        "rabbit_server_chef_role": "os-ops-messaging",
        "network": {
          "public_interface": "eth1",
          "service_type": "neutron"
        }
      },
      "network": {
        "debug": "True",
        "dhcp": {
          "enable_isolated_metadata": "True"
        },
        "metadata": {
          "nova_metadata_ip": "10.0.2.10"
        },
        "openvswitch": {
          "tunnel_id_ranges": "1:1000",
          "enable_tunneling": "True",
          "tenant_network_type": "gre",
          "tunnel_types": "gre",
          "tunnel_type": "gre",
          "bridge_mappings": "physnet1:br-eth2",
          "bridge_mapping_interface": "br-eth2:eth2"
        },
        "ml2": {
          "tenant_network_types": "gre",
          "mechanism_drivers": "openvswitch",
          "tunnel_id_ranges": "1:1000",
          "enable_security_group": "True"
        },
        "api": {
          "bind_interface": "eth1"
        },
        "l3": {
          "external_network_bridge_interface": "eth0"
        },
        "service_plugins": ["neutron.services.l3_router.l3_router_plugin.L3RouterPlugin"]
      },
      "db": {
        "bind_interface": "eth1",
        "compute": {
          "host": "10.0.2.10"
        },
        "identity": {
          "host": "10.0.2.10"
        },
        "image": {
          "host": "10.0.2.10"
        },
        "network": {
          "host": "10.0.2.10"
        },
        "volume": {
          "host": "10.0.2.10"
        },
        "dashboard": {
          "host": "10.0.2.10"
        },
        "telemetry": {
          "host": "10.0.2.10"
        },
        "orchestration": {
          "host": "10.0.2.10"
        }
      },
      "developer_mode": true,
      "endpoints": {
        "network-openvswitch": {
          "bind_interface": "eth1"
        },
        "compute-api-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8774"
        },
        "compute-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8774"
        },
        "compute-ec2-admin-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8773"
        },
        "compute-ec2-admin": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8773"
        },
       "compute-ec2-api-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8773"
        },
        "compute-ec2-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8773"
        },
        "compute-xvpvnc": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "6081"
        },
        "compute-novnc-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "6080"
        },
        "compute-novnc": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "6080"
        },
        "compute-vnc": {
          "host": "0.0.0.0",
          "scheme": "http",
          "port": "6080"
        },
        "image-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "9292"
        },
        "image-api-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "9292"
        },
        "image-registry": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "9191"
        },
        "image-registry-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "9191"
        },
        "identity-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "5000"
        },
        "identity-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "5000"
        },
        "identity-admin": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "35357"
        },
        "identity-internal": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "35357"
        },
        "volume-api-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8776"
        },
        "volume-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8776"
        },
        "telemetry-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8777"
        },
        "network-api-bind": {
          "host": "10.0.2.11",
          "scheme": "http",
          "port": "9696"
        },
        "network-api": {
          "host": "10.0.2.11,
          "scheme": "http",
          "port": "9696"
        },
        "block-storage-api-bind": {
          "host": "10.0.2.10",
          "port": "8776",
          "bind_interface": "eth1"
        },
        "block-storage-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8776"
        },
        "orchestration-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8004"
        },
        "orchestration-api-cfn": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8000"
        },
        "db": {
          "host": "0.0.0.0",
          "port": "3306"
        },
        "bind-host": "0.0.0.0"
      },
      "identity": {
        "admin_user": "admin",
        "bind_interface": "eth1",
        "debug": true
      },
      "image": {
        "api": {
          "bind_interface": "eth1"
        },
        "debug": true,
        "identity_service_chef_role": "os-identity",
        "rabbit_server_chef_role": "os-ops-messaging",
        "registry": {
          "bind_interface": "eth1"
        },
        "syslog": {
          "use": false
        }
      },
      "mq": {
        "bind_interface": "eth1",
        "host": "10.0.2.10",
        "user": "guest",
        "vhost": "/nova",
        "network": {
          "rabbit": {
             "host": "10.0.2.10",
             "port": "5672"
          }
        },
        "compute": {
           "service_type": "rabbitmq",
          "rabbit": {
            "host": "10.0.2.10",
            "port": "5672"
          }
        },
        "block-storage": {
          "service_type": "rabbitmq",
          "rabbit": {
            "host": "10.0.2.10",
            "port": "5672"
          }
        }
      }
    },
    "queue": {
      "host": "10.0.2.10",
      "user": "guest",
      "vhost": "/nova"
    }
  }
}

上記ファイルでは virt_type : qemu に設定していますが、KVM リソースを利用出来る 環境であればここを削除してください。デフォルトの ‘kvm’ が適用されます。また気 をつけることは IP アドレスとネットワークインターフェース名です。環境に合わせて 設定していきましょう。今回は前提構成に合わせて environemnt ファイルを作ってい ます。

次に openstack-chef-repo/.chef/encrypted_data_bag_secret というファイルが knife-zero ホストにあるはずです。これをデプロイ対象の3ノードに事前に転送してお く必要があります。

1
2
3
scp openstack-chef-repo/.chef/encrypted_data_bag_secret 10.0.2.10:/tmp/
scp openstack-chef-repo/.chef/encrypted_data_bag_secret 10.0.2.11:/tmp/
scp openstack-chef-repo/.chef/encrypted_data_bag_secret 10.0.2.12:/tmp/

対象ホストにて

1
2
mkdir /etc/chef
mv /tmp/encrypted_data_bag_secret /etc/chef/openstack_data_bag_secret

ではいよいよデプロイです。

Controller ホストへのデプロイ

1
knife zero bootstrap 10.0.2.10 -N kilo01 -r 'role[os-compute-single-controller-no-network]' -E multi-neutron-kilo -x <USERNAME> --sudo

Network ホストへのデプロイ

1
knife zero bootstrap 10.0.2.11 -N kilo02 -r 'role[os-client]','role[os-network]' -E multi-neutron-kilo -x <USERNAME> --sudo

Compute ノードへのデプロイ

1
knife zero bootstrap 10.0.2.12 -N kilo03 -r 'role[os-compute-worker]' -E multi-neutron-kilo -x <USERNAME> --sudo

これで完了です。admin/mypass というユーザ・パスワードでログインが可能です。

まとめ

openstack-chef-repo を使って OpenStack Kilo の複数台構成をデプロイ出来ました。重要なのは Environment をどうやって作るか?ですが、 私は 作成 -> デプロイ -> 修正 -> デプロイ ->…. を繰り返して作成しています。何度実行しても不具合は発生しない設計なクックブックに なっていますので、このような作業が可能になります。また、「ここの設定を追加したい」という時は…

  • 該当の template を探す
  • 該当のパラメータを確認する
  • recipe 内で template にどうパラメータを渡しているか確認する
  • attribute なり、変数なりを修正するための方法を探す

と行います。比較的難しい作業になるのですが、自らの環境に合わせた Environment を作成するにはこれらの作業が必須となってきます。

以上、複数台構成のデプロイ方法についてでした。

Chef-ZeroでOpenStack Kiloデプロイ(オールインワン編)

こんにちは。@jedipunkz です。

久々に openstack-chef-repo を覗いてみたら ‘openstack/openstack-chef-repo’ とし て公開されていました。今まで stackforge 側で管理されていましたが ‘openstack’ の方に移動したようです。

https://github.com/openstack/openstack-chef-repo

結構安定してきているのかな?と想い、ちらっと試したのですが案の定、簡単に動作さ せることが出来ました。

今回はこのレポジトリを使ってオールインワン構成の OpenStack Kilo を作る方法をま とめていきます。

前提の構成

このレポジトリは Vagrant で OpenStack を作るための環境一式が最初から用意されて いますが、Vagrant では本番環境を作ることは出来ないため、Ubuntu ホストを前提と した記述に差し替えて説明していきます。前提にする構成は下記のとおりです。

  • Uuntu Linux 14.04 x 1 台
  • ネットワークインターフェース x 3 つ
  • eth0 : External ネットワーク用
  • eth1 : Internal (API, Manage) ネットワーク用
  • eth2 : Guest ネットワーク用

特徴としては上記なのですが、eth2 に関してはオールインワンなので必ずしも必要と いうわけではありません。複数台構成を考慮した設定になっています。

前提のIP アドレス

この記事では下記の IP アドレスを前提にします。お手持ちの環境の IP アドレスが違 い場合はそれに合わせて後に示す json ファイルを変更してください。

  • 10.0.1.10 (eth0) : external ネットワーク
  • 10.0.2.10 (eth1) : api/management ネットワーク
  • 10.0.3.10 (eth2) : Guest ネットワーク

事前の準備

事前に対象ホスト (OpenStack ホスト) に chef, berkshelf をインストールします。

1
2
sudo -i
curl -L https://www.opscode.com/chef/install.sh | bash

Berkshelf をインストールするのに必要なソフトウェアをインストールします。

1
apt-get -y install build-essential zlib1g-dev libssl-dev libreadline-dev ruby-dev libxml2-dev libxslt-dev g++

Berkshelf をインストールします。

1
/opt/chef/embedded/bin/gem install berkshelf --no-ri --no-rdoc

デプロイ作業

それでは openstack-chef-repo を取得してデプロイの準備を行います。 ブランチの指定は行わず master ブランチを取得します。Kilo は master ブランチで 管理されています。次のバージョンの開発が始まるタイミングで ‘stable/kilo’ ブラ ンチに管理が移されます。

1
2
3
sudo -i
cd ~/
git clone https://github.com/openstack/openstack-chef-repo.git

次に Berkshelf を使って必要な Cookbooks をダウンロードします。

1
2
cd ~/openstack-chef-repo
/opt/chef/embedded/bin/berks vendor ./cookbooks

Environment を作成します。これは各環境に合わせた設定ファイルのようなもので、各 Cookbooks の Attributes を上書きする仕組みになっています。下記の内容を

1
openstack-chef-repo/environments/aio-neutron-kilo.json

というファイル名で保存してください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
{
  "name": "aio-neutron-kilo",
  "description": "test",
  "cookbook_versions": {
  },
  "json_class": "Chef::Environment",
  "chef_type": "environment",
  "default_attributes": {
  },
  "override_attributes": {
    "mysql": {
      "bind_address": "0.0.0.0",
      "server_root_password": "mysqlroot",
      "server_debian_password": "mysqlroot",
      "server_repl_password": "mysqlroot",
      "allow_remote_root": true,
      "root_network_acl": ["10.0.0.0/8"]
    },
    "rabbitmq": {
      "address": "0.0.0.0",
      "port": "5672",
      "loopback_users": []
    },
    "openstack": {
      "auth": {
        "validate_certs": false
      },
      "dashboard": {
        "session_backend": "file"
      },
      "block-storage": {
        "syslog": {
          "use": false
        },
        "api": {
          "ratelimit": "False"
        },
        "debug": true,
        "image_api_chef_role": "os-image",
        "identity_service_chef_role": "os-identity",
        "rabbit_server_chef_role": "os-ops-messaging"
      },
      "compute": {
        "rabbit": {
          "host": "10.0.2.10"
        },
        "novnc_proxy": {
          "bind_interface": "eth1"
        },
        "libvirt": {
          "virt_type": "qemu",
          "bind_interface": "eth1"
        },
        "novnc_proxy": {
          "bind_interface": "eth1"
        },
        "xvpvnc_proxy": {
          "bind_interface": "eth1"
        },
        "image_api_chef_role": "os-image",
        "identity_service_chef_role": "os-identity",
        "nova_setup_chef_role": "os-compute-api",
        "rabbit_server_chef_role": "os-ops-messaging",
        "network": {
          "public_interface": "eth1",
          "service_type": "neutron"
        }
      },
      "network": {
        "debug": "True",
        "dhcp": {
          "enable_isolated_metadata": "True"
        },
        "metadata": {
          "nova_metadata_ip": "10.0.2.10"
        },
        "openvswitch": {
          "tunnel_id_ranges": "1:1000",
          "enable_tunneling": "True",
          "tenant_network_type": "gre",
          "local_ip_interface": "eth2"
        },
        "api": {
          "bind_interface": "eth1"
        },
        "l3": {
          "external_network_bridge_interface": "eth0"
        },
        "service_plugins": ["neutron.services.l3_router.l3_router_plugin.L3RouterPlugin"]
      },
      "db": {
        "bind_interface": "eth1",
        "compute": {
          "host": "10.0.2.10"
        },
        "identity": {
          "host": "10.0.2.10"
        },
        "image": {
          "host": "10.0.2.10"
        },
        "network": {
          "host": "10.0.2.10"
        },
        "volume": {
          "host": "10.0.2.10"
        },
        "dashboard": {
          "host": "10.0.2.10"
        },
        "telemetry": {
          "host": "10.0.2.10"
        },
        "orchestration": {
          "host": "10.0.2.10"
        }
      },
      "developer_mode": true,
      "endpoints": {
        "compute-api-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8774"
        },
        "compute-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8774"
        },
        "compute-ec2-admin-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8773"
        },
        "compute-ec2-admin": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8773"
        },
       "compute-ec2-api-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8773"
        },
        "compute-ec2-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8773"
        },
        "compute-xvpvnc": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "6081"
        },
        "compute-novnc-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "6080"
        },
        "compute-novnc": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "6080"
        },
        "compute-vnc": {
          "host": "0.0.0.0",
          "scheme": "http",
          "port": "6080"
        },
        "image-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "9292"
        },
        "image-api-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "9292"
        },
        "image-registry": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "9191"
        },
        "image-registry-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "9191"
        },
        "identity-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "5000"
        },
        "identity-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "5000"
        },
        "identity-admin": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "35357"
        },
        "identity-internal": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "35357"
        },
        "volume-api-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8776"
        },
        "volume-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8776"
        },
        "telemetry-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8777"
        },
        "network-api-bind": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "9696"
        },
        "network-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "9696"
        },
        "orchestration-api": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8004"
        },
        "orchestration-api-cfn": {
          "host": "10.0.2.10",
          "scheme": "http",
          "port": "8000"
        },
        "db": {
          "host": "0.0.0.0",
          "port": "3306"
        },
        "bind-host": "0.0.0.0"
      },
      "identity": {
        "admin_user": "admin",
        "bind_interface": "eth1",
        "debug": true
      },
      "image": {
        "api": {
          "bind_interface": "eth1"
        },
        "debug": true,
        "identity_service_chef_role": "os-identity",
        "rabbit_server_chef_role": "os-ops-messaging",
        "registry": {
          "bind_interface": "eth1"
        },
        "syslog": {
          "use": false
        }
      },
      "mq": {
        "bind_interface": "eth1",
        "host": "10.0.2.10",
        "user": "guest",
        "vhost": "/nova",
        "network": {
          "rabbit": {
             "host": "10.0.2.10",
             "port": "5672"
          }
        },
        "compute": {
           "service_type": "rabbitmq",
          "rabbit": {
            "host": "10.0.2.10",
            "port": "5672"
          }
        },
        "block-storage": {
          "service_type": "rabbitmq",
          "rabbit": {
            "host": "10.0.2.10",
            "port": "5672"
          }
        }
      }
    },
    "queue": {
      "host": "10.0.2.10",
      "user": "guest",
      "vhost": "/nova"
    }
  }
}

上記ファイルは KVM が使えない環境用に virt_type : qemu にしていますが、KVM が 利用できる環境をご利用であれば該当行を削除してください。デフォルト値の ‘kvm’ が入るはずです。

次にデプロイ前に databag 関連の事前操作を行います。Vagrant 用に作成されたファ イルを除くと…

1
2
3
4
5
6
7
8
9
machine 'controller' do
  add_machine_options vagrant_config: controller_config
  role 'allinone-compute'
  role 'os-image-upload'
  chef_environment env
  file('/etc/chef/openstack_data_bag_secret',
       "#{File.dirname(__FILE__)}/.chef/encrypted_data_bag_secret")
  converge true
end

となっていて /etc/chef/openstack_data_bag_secret というファイルを事前にコピー する必要がありそうです。下記のように操作します。

1
cp .chef/encrypted_data_bag_secret /etc/chef/openstack_data_bag_secret

デプロイを実行します。

この openstack-chef-repo には .chef ディレクトリが存在していてノード名が記され ています。’nodienode’ というノード名です。これを利用してそのままデプロイを実行 します。

1
2
3
chef-client -z
knife node -z run_list add nodienode 'role[allinone-compute]'
chef-client -z -E aio-neutron-kilo

上記の説明を行います。 1行目 chef-client -z で Chef-Zero サーバをメモリ上に起動し、2行目で自ノードへ run_list を追加しています。最後、3行目でデプロイ実行、となります。

数分待つと OpenStack Kilo が構成されているはずです。

まとめ

Chef-Zero を用いることで Chef サーバを利用せずに楽に構築が行えました。ですが、 OpenStack の複数台構成となるとそれぞれのノードのパラメータを連携させる必要が出 てくるので Chef サーバを用いたほうが良さそうです。今度、時間を見つけて Kilo の 複数台構成についても調べておきます。

また、master ブランチを使用していますので、まだ openstack-chef-repo 自体が流動 的な状態とも言えます。が launchpad で管理されている Bug リストを見ると、ステー タス Critical, High の Bug が見つからなかったので Kilo に関しては、大きな問題 無く安定してきている感があります。

https://bugs.launchpad.net/openstack-chef

オブジェクトストレージ Minio を使ってみる

こんにちは、@jedipunkz です。

久々にブログ更新になりましたが、ウォーミングアップで minio というオブジェクト ストレージを使ってみたメモを記事にしたいと想います。

minio は Minimal Object Storage の名の通り、最小限の小さなオブジェクトストレー ジになります。公式サイトは下記のとおりです。

http://minio.io/

Golang で記述されていて Apache License v2 の元に公開されています。

最近、資金調達の話も挙がっていたので、これから一般的になってくるのかもしれません。

早速ですが、minio を動かしてみます。

Minio を起動する

方法は mithub.com/minio/minio の README に書かれていますが、バイナリを持ってき て実行権限を与えるだけのシンプルな手順になります。

Linux でも Mac でも動作しますが、今回私は Mac 上で動作させました。

1
2
3
4
5
% wget https://dl.minio.io:9000/updates/2015/Jun/darwin-amd64/minio
% chmod +x minio
% ./minio mode memory limit 512MB
Starting minio server on: http://127.0.0.1:9000
Starting minio server on: http://192.168.1.123:9000

起動すると Listening Port と共に EndPoint の URL が表示されます。

次に mc という minio client を使って動作確認します。

Mc を使ってアクセスする

mc は下記の URL にあります。

https://github.com/minio/mc

こちらもダウンロードして実行権限を付与するのみです。mc は minio だけではなく、 Amazon S3 とも互換性がありアクセス出来ますが、せっかくなので上記で起動した minio にアクセスします。

1
2
3
4
5
% wget https://dl.minio.io:9000/updates/2015/Jun/darwin-amd64/mc
% chmod +x mc
% ./mc config generate
/mc ls  http://127.0.0.1:9000/bucket01
[2015-06-25 16:21:37 JST]     0B testfile

上記では予め作っておいた bucket01 という名前のバケットの中身を表示しています。 作り方はこれから minio の Golang ライブラリである minio-go を使って作りました。 これから説明します。

また ls コマンドの他にも Usage を確認すると幾つかのサブコマンドが見つかります。

Minio の Golang ライブラリ minio-go を使ってアクセスする

さて、せっかくのオブジェクトストレージも手作業でファイルやバケットのアクセスを 行うのはもったいないです。ソフトウェアを使って操作してす。

minio のサンプルのコードを参考にして、下記のコードを作成してみました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package main

import (
    "log"
    "os"

    "github.com/minio/minio-go"
)

func main() {
    config := minio.Config{
        // AccessKeyID:     "YOUR-ACCESS-KEY-HERE",
        // SecretAccessKey: "YOUR-PASSWORD-HERE",
        Endpoint:        "http://127.0.0.1:9000",
    }

    s3Client, err := minio.New(config)
    if err != nil {
        log.Fatalln(err)
    }

    err = s3Client.MakeBucket("bucket01", minio.BucketACL("public-read-write"))
    if err != nil {
        log.Fatalln(err)
    }
    log.Println("Success: I made a bucket.")

    object, err := os.Open("testfile")
    if err != nil {
        log.Fatalln(err)
    }
    defer object.Close()
    objectInfo, err := object.Stat()
    if err != nil {
        object.Close()
        log.Fatalln(err)
    }

    err = s3Client.PutObject("bucket01", "testfile", "application/octet-stream", objectInfo.Size(), object)
    if err != nil {
        log.Fatalln(err)
    }

    for bucket := range s3Client.ListBuckets() {
        if bucket.Err != nil {
            log.Fatalln(bucket.Err)
        }
        log.Println(bucket.Stat)
    }

    for object := range s3Client.ListObjects("bucket01", "", true) {
        if object.Err != nil {
            log.Fatalln(object.Err)
        }
        log.Println(object.Stat)
    }

}

簡単ですがコードの説明をします。

  • 11行目で config の上書きをします。先ほど起動した minio の EndPoint を記します。
  • 17行目で minio にセッションを張り接続を行っています。
  • 22行目で ‘bucket01’ というバケットを生成しています。その際にACLも設定
  • 28行目から42行目で ‘testfile’ というローカルファイルをストレージにPUTしています。
  • 44行目でバケット一覧を表示しています。
  • 51行目で上記で作成したバケットの中のオブジェクト一覧を表示しています。

実行結果は下記のとおりです。

1
2
3
4
2015/06/25 16:56:21 Success: I made a bucket.
2015/06/25 16:56:21 {bucket01 2015-06-25 07:56:21.155 +0000 UTC}
2015/06/25 16:56:21 {"d41d8cd98f00b204e9800998ecf8427e" testfile 2015-06-25
07:56:21.158 +0000 UTC 0 {minio minio} STANDARD}

バケットの作成とオブジェクトの PUT が正常に行えたことをログから確認できます。

まとめ

上記の通り、今現在出来ることは少ないですが冒頭にも記したとおり資金調達の話も挙 がってきていますので、これからどのような方向に向かうか楽しみでもあります。また 最初から Golang, Python 等のライブラリが用意されているところが今どきだなぁと想 いました。オブジェクトストレージを手作業で操作するケースは現場では殆ど無いと想 いますので、その辺は現在では当たり前になりつつあるかもしれません。ちなみに Python のライブラリは下記の URL にあります。

https://github.com/minio/minio-py

以上です。

VyOS で VXLAN を使ってみる

こんにちは。@jedipunkz です。

VyOS に VXLAN が実装されたと聞いて少し触ってみました。この情報を知ったきっかけ は @upaa さんの下記の資料です。

参考資料 : http://www.slideshare.net/upaa/vyos-users-meeting-2-vyosvxlan

VyOS は御存知の通り実体は Debian Gnu/Linux 系の OS でその上に OSS なミドル ウェアが搭載されていて CLI でミドルウェアのコンフィギュレーション等が行えるモ ノになっています。Linux で VXLAN といえば OVS を使ったモノがよく知られています が VyOS の VXLAN 機能は Linux Kernel の実装を使っているようです。

要件

  • トンネルを張るためのセグメントを用意
  • VyOS 1.1.1 (現在最新ステーブルバージョン) が必要
  • Ubuntu Server 14.04 LTS (同じく Linux VXLAN 搭載バージョン)

構成

特徴

  • マネージメント用セグメント 10.0.1.0/24 を用意
  • GRE と同じくトンネル終端が必要なのでそのためのセグメント 10.0.2.0/24 を用意
  • 各 eth1 は IP reachable である必要があるので予め IP アドレスの設定と疎通を確認
  • VXLAN を喋れる Ubuntu 14.04 LTS x 1 台と VyOS 1.1.1 x 2 台で相互に疎通確認
1
2
3
4
5
6
7
8
9
10
11
12
+-------------+-------------+------------ Management 10.0.1.0/24
|10.0.0.254   |10.0.0.253   |10.0.0.1
|eth0         |eth0         |eth0
+----------+  +----------+  +----------+ 
|  vyos01  |  |  vyos02  |  |  ubuntu  |
+-+--------+  +----------+  +----------+ 
| |eth1       | |eth1       | |eth1
| |10.0.2.254 | |10.0.2.253 | |10.0.2.1
| +-----------)-+-----------)-+---------- Tunneling 10.0.2.0/24
|             |             |
+-------------+-------------+------------ VXLAN(eth1にlink) 10.0.1.0/24
10.0.1.254     10.0.1.253    10.0.1.1

設定を投入

vyos01 の設定を行う。VXLAN の設定に必要なものは…

  • VNI (VXLAN Network Ideintity)という識別子
  • Multicast Group Address
  • 互いに IP reachable なトンネルを張るためのインターフェース

です。これらを意識して下記の設定を vyos01 に投入します。

1
2
3
4
5
6
$ configure
% set interfaces vxlan vxlan0
% set interfaces vxlan vxlan0 group 239.1.1.1
% set interfaces vxlan vxlan0 vni 42
% set interfaces vxlan vxlan0 address '10.0.1.254/24'
% set interfaces vxlan vxlan0 link eth1

設定を確認します

1
2
3
4
5
6
7
8
9
% exit
$ show int
...<省略>...
    vxlan vxlan0 {
     address 10.0.1.254/24
     group 239.1.1.1
     link eth1
     vni 42
}

VyOS の CLI を介さず直 Linux の設定を iproute2 で確認してみましょう。 VNI, Multicast Group Address と共に ‘link eth1’ で設定したトンネルを終端するための物理 NIC が確認できます。

1
2
3
4
vyos@vyos01# ip -d link show vxlan0
5: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
    link/ether 86:24:26:b2:11:5c brd ff:ff:ff:ff:ff:ff promiscuity 0
    vxlan id 42 group 239.1.1.1 dev eth1 port 32768 61000 ttl 16 ageing 300

vyos02 の設定を同様に行います。

1
2
3
4
5
$ congigure
% set interfaces vxlan vxlan0 address '10.0.1.253/24'
% set interfaces vxlan vxlan0 vni 42
% set interfaces vxlan vxlan0 group 239.1.1.1
% set interfaces vxlan vxlan0 link eth1

設定の確認を行います。

1
2
3
4
5
6
7
... 省略 ...
vxlan vxlan0 {
     address 10.0.1.254/24
     group 239.1.1.1
     link eth1
     vni 42
}

同じく Linux の iproute2 で確認します。

1
2
3
4
vyos@vyos01# ip -d link show vxlan0
5: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
    link/ether 86:24:26:b2:11:5c brd ff:ff:ff:ff:ff:ff promiscuity 0
    vxlan id 42 group 239.1.1.1 dev eth1 port 32768 61000 ttl 16 ageing 300

ubuntu ホストの設定を行っていきます。

Ubuntu Server 14.04 LTS であればパッチを当てること無く Linux Kernel の VXLAN 機能を使うことができます。 設定内容は VyOS と同等です。VyOS がこの Linux の実装を使っているのがよく分かります。

1
2
3
4
sudo modprobe vxlan
sudo ip link add vxlan0 type vxlan id 42 group 239.1.1.1 dev eth1
sudo ip link set up vxlan0
sudo ip a add 10.0.1.1/24 dev vxlan0

同じく Linux iproute2 で確認を行います。

1
2
3
4
 ip -d link show vxlan0
5: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
    link/ether d6:ff:c1:27:69:a0 brd ff:ff:ff:ff:ff:ff promiscuity 0
    vxlan id 42 group 239.1.1.1 dev eth1 port 32768 61000 ageing 300

疎通確認

疎通確認を行います。

ubuntu -> vyos01 の疎通確認です。ICMP で疎通が取れることを確認できます。

1
2
3
4
5
6
7
8
9
thirai@ubuntu:~$ ping 10.0.1.254 -c 3
PING 10.0.1.254 (10.0.1.254) 56(84) bytes of data.
64 bytes from 10.0.1.254: icmp_seq=1 ttl=64 time=0.272 ms
64 bytes from 10.0.1.254: icmp_seq=2 ttl=64 time=0.336 ms
64 bytes from 10.0.1.254: icmp_seq=3 ttl=64 time=0.490 ms

--- 10.0.1.254 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.272/0.366/0.490/0.091 ms

次に ubuntu -> vyos02 の疎通確認です。

1
2
3
4
5
6
7
8
9
thirai@ubuntu:~$ ping 10.0.1.253 -c 3
PING 10.0.1.253 (10.0.1.253) 56(84) bytes of data.
64 bytes from 10.0.1.253: icmp_seq=1 ttl=64 time=0.272 ms
64 bytes from 10.0.1.253: icmp_seq=2 ttl=64 time=0.418 ms
64 bytes from 10.0.1.253: icmp_seq=3 ttl=64 time=0.451 ms

--- 10.0.1.253 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1998ms
rtt min/avg/max/mdev = 0.272/0.380/0.451/0.079 ms

この時点で ubuntu ホストの fdb (forwarding db) の内容を確認します。

1
2
3
4
$ bridge fdb show dev vxlan0
00:00:00:00:00:00 dst 239.1.1.1 via eth1 self permanent
4e:69:a4:a7:ef:1c dst 10.0.2.253 self
86:24:26:b2:11:5c dst 10.0.2.254 self

vyos01, vyos02 のトンネル終端 IP アドレスと Mac アドレスが確認できます。ubuntu ホストから見ると 送信先は vyos0[12] の VXLAN インターフェースではなく、あくまでもトンネル終端を行っているインターフェース になることがわかります。

まとめ

VyOS ver 1.1.0 には VXLAN を物理インターフェースに link する機能に不具合がありそうなので今ら ver 1.1.1 を使うしか なさそう。とは言え、ver 1.1.1 なら普通に動作しました。

VyOS は仮想ルータという位置付けなので今回紹介したようにインターフェースを VXLAN ネットワークに所属させる 機能があるのみです。VXLAN Trunk を行うような設定はありません。これはハイパーバイザ上で動作させることを前提 に設計されているので仕方ないです..というかスイッチで行うべき機能ですよね..。VM を接続して云々するには OVS のようなソフトウェアスイッチを使えばできます。

また fdb は時間が経つと情報が消えます。これは VXLAN のメッシュ構造なトンネルがその都度張られているのかどうか 気になるところです。ICMP の送信で一発目のみマルチキャストでその後ユニキャストになることを確認しましたが、その 一発目のマルチキャストでトンネリングがされるものなのでしょうか…。あとで調べてみます。OVS のように CLI で トンネルがどのように張られているか確認する手段があれば良いのですが。

以上です。

Aviator でモダンに OpenStack を操作する

こんにちは。@jedipunkz です。

自分は Ruby を普段使うのでいつも Fog というライブラリを使って OpenStack, AWS を操作していました。Fog を使うとクラウドの操作が Ruby のネイティブコードで行え るのでシステムコマンド打つよりミス無く済みます。

Fog より後発で Aviator というライブラリが登場してきたので少し使ってみたのです がまだ未完成なところがあるものの便利な点もあって今後に期待だったので紹介します。

認証情報を yaml ファイルに記す

接続に必要な認証情報を yaml ファイルで記述します。名前を ‘aviator.yml’ として 保存。この時に下記のように環境毎に認証情報を別けて書くことができます。こうする ことでコードの中で開発用・サービス用等と使い分けられます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
production:
  provider: openstack
  auth_service:
    name: identity
    host_uri: <Auth URL>
    request: create_token
    validator: list_tenants
  auth_credentials:
    username: <User Name>
    password: <Password>
    tenant_name: <Tenant Name>

development:
  provider: openstack
  auth_service:
    name: identity
    host_uri: <Auth URL>
    request: create_token
    validator: list_tenants
  auth_credentials:
    username: <User Name>
    password: <Password>
    tenant_name: <Tenant Name>

シンタックス確認

次に aviator のシンタックスを確認します。Fog に無い機能で、コマンドラインでシ ンタックスを確認できてしかも指定可能はパラメータと必須なパラメータと共にサンプ ルコードまで提供してくれます。公式サイトに’サーバ作成’のメソッドが掲載されてい るので、ここでは仮想ディスクを作るシンタックスを確認してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
% gem install aviator
% aviator describe openstack volume # <-- 利用可能な機能を確認
Available requests for openstack volume_service:
v1 public list_volume_types
v1 public list_volumes
v1 public delete_volume
v1 public create_volume
v1 public get_volume
v1 public update_volume
  v1 public root
% aviator describe openstack volume v1 public create_volume # <-- シンタックスを確認
:Request => create_volume

Parameters:
 +---------------------+-----------+
 | NAME                | REQUIRED? |
 +---------------------+-----------+
 | availability_zone   |     N     |
 | display_description |     Y     |
 | display_name        |     Y     |
 | metadata            |     N     |
 | size                |     Y     |
 | snapshot_id         |     N     |
 | volume_type         |     N     |
 +---------------------+-----------+

Sample Code:
  session.volume_service.request(:create_volume) do |params|
    params.volume_type = value
    params.availability_zone = value
    params.snapshot_id = value
    params.metadata = value
    params.display_name = value
    params.display_description = value
    params.size = value
  end

このように create_volume というメソッドが用意されていて、指定出来るパラメータ・ 必須なパラメータが確認できます。必須なモノには “Y” が REQUIRED に付いています。 またサンプルコードが出力されるので、めちゃ便利です。

では create_volume のシンタックスがわかったので、コードを書いてみましょう。

コードを書いてみる

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env ruby

require 'aviator'
require 'json'

volume_session = Aviator::Session.new(
              :config_file => '/home/thirai/aviator/aviator.yml',
              :environment => :production,
              :log_file    => '/home/thirai/aviator/aviator.log'
            )

volume_session.authenticate

volume_session.volume_service.request(:create_volume) do |params|
  params.display_description = 'testvol'
  params.display_name = 'testvol01'
  params.size = 1
end
puts volume_session.volume_service.request(:list_volumes).body

6行目で先ほど作成した認証情報ファイル aviator.yml とログ出力ファイル aviator.log を指定します。12行目で実際に OpenStack にログインしています。

14-18行目はサンプルコードそのままです。必須パラメータの display_description, display_name, size のみを指定し仮想ディスクを作成しました。最後の puts … は 実際に作成した仮想ディスク一覧を出力しています。

結果は下記のとおりです。

1
2
3
4
5
{ volumes: [{ status: 'available', display_name: 'testvol01', attachments: [],
availability_zone: 'az3', bootable: 'false', created_at:
'2014-12-13T00:41:53.000000', display_description: 'testvol', volume_type:
'standard', snapshot_id: nil, source_volid: nil, metadata:  }, id:
'3a5f616e-a732-4442-a419-10369111bd4c', size: 1 }] }

まとめ

サンプルコードやパラメータ一覧等がひと目でわかる aviator はとても便利です。ま だ利用できるクラウドプラットフォームが OpenStack しかないのと、Neutron の機能 がスッポリ抜けているので、まだ利用するには早いかもです…。逆に言えばコントリ ビューションするチャンスなので、もし気になった方がいたら開発に参加してみるのも いいかもしれません。

Chef-Zero でお手軽に OpenStack Icehouse を作る

こんにちは。@jedipunkz です。

OpenStack Juno がリリースされましたが、今日は Icehouse ネタです。

icehouse 以降、自分の中で OpenStack を自動で作る仕組みが無くなりつつあり、気軽 に OpenStack を作って色々試したい!ッていう時に手段が無く困っていました。例え ば仕事でちょっと OpenStack 弄りたい!って時に DevStack, RDO しかなく。DevStack は御存知の通り動かない可能性が結構あるし RDO は Ubuntu/Debian Gnu Linux ベース じゃないし。

ってことで、以前にも紹介した stackforge 管理の openstack-chef-repo と Chef-Zero を使って OpenStack Icehouse (Neutron) のオールインワン構成を作る方法 を書きます。ちなみに最近 Chef-Solo が Chef-Zero に置き換わりつつあるらしいです。 Chef-Zero はオンメモリで Chef サーバを起動する仕組みです。Chef-Solo と違って Chef サーバを扱う時と何も変更無く操作が出来るのでとても楽です。また、Chef サーバを 別途構、構築・管理しなくて良いので、気軽に OpenStack が作れます。

ちなみに stackforge/openstack-chef-repo の README.md に Chef-Zero での構築方法 が書いてありますが、沢山の問題があります。

  • nova-network 構成
  • API の Endpoint が全て localhost に向いてしまうため外部から操作不可能
  • 各コンポーネントの bind_address が localhost を向いてしまう
  • berkshelf がそのままでは入らない

よって、今回はこれらの問題を解決しつつ “オールインワンな Neutron 構成の Icehouse OpenStack を作る方法” を書いていきます。

構成

+----------------- 10.0.0.0/24 (api/management network)
|
+----------------+
| OpenStack Node |
|   Controller   |
|    Compute     |
+----------------+
|  |
+--(-------------- 10.0.1.0/24 (external network)
   |
   +-------------- 10.0.2.0/24 (guest vm network)

IP address 達

  • 10.0.0.10 (api/manageent network) : eth0
  • 10.0.1.10 (external network) : eth1
  • 10.0.2.10 (guest vm network) : eth2

注意 : 操作は全て eth0 経由で行う

前提の環境

stackforge/openstack-chef-repo の依存している Cookbooks の関係上、upstart 周り がうまく制御できていないので Ubuntu Server 12.04.x を使います。

インストール方法

上記のように3つのネットワークインターフェースが付いたサーバを1台用意します。 KVM が利用出来たほうがいいですが使えないくても構いません。KVM リソースが使えな い場合の修正方法を後に記します。

サーバにログインし root ユーザになります。その後 Chef をオムニバスインストーラ でインストールします。

1
2
% sudo -i
# curl -L https://www.opscode.com/chef/install.sh | bash

次に stable/icehose ブランチを指定して openstack-chef-repo をクローンします。

1
2
3
# cd ~
# git clone -b stable/icehouse https://github.com/stackforge/openstack-chef-repo
# 

berkshelf をインストールするのですが依存パッケージが足らないのでここでインストー ルします。

1
2
# apt-get -y install build-essential zlib1g-dev libssl-dev libreadline-dev \
  ruby-dev libxml2-dev libxslt-dev g++

berkshelf をインストールします。

1
# /opt/chef/embedded/bin/gem install berkshelf --no-ri --no-rdoc

次に openstack-chef-repo に依存する Cookbooks を取得します。

1
2
# cd ~/openstack-chef-repo
# /opt/chef/embedded/bin/berks vendor ./cookbooks

~/openstack-chef-repo/environments ディレクトリ配下に neutron-allinone.json と いうファイル名で作成します。内容は下記の通りです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
{                                                                                                                                                      [0/215]
  "name": "neutron-allinone",
  "description": "test",
  "cookbook_versions": {
  },
  "json_class": "Chef::Environment",
  "chef_type": "environment",
  "default_attributes": {
  },
  "override_attributes": {
    "mysql": {
      "bind_address": "0.0.0.0",
      "server_root_password": "root",
      "server_debian_password": "root",
      "server_repl_password": "root",
      "allow_remote_root": true,
      "root_network_acl": ["10.0.0.0/8"]
    },
    "rabbitmq": {
      "address": "10.0.1.10",
      "port": "5672"
    },
    "openstack": {
      "auth": {
        "validate_certs": false
      },
      "dashboard": {
        "session_backend": "file"
      },
      "block-storage": {
        "syslog": {
          "use": false
        },
        "api": {
          "ratelimit": "False"
        },
        "debug": true,
        "image_api_chef_role": "os-image",
        "identity_service_chef_role": "os-identity",
        "rabbit_server_chef_role": "os-ops-messaging"
      },
      "compute": {
        "rabbit": {
          "host": "10.0.1.10"
        },
        "novnc_proxy": {
          "bind_interface": "eth0"
        },
        "libvirt": {
          "bind_interface": "eth0",
        },
        "novnc_proxy": {
          "bind_interface": "eth0"
        },
        "xvpvnc_proxy": {
          "bind_interface": "eth0"
        },
        "image_api_chef_role": "os-image",
        "identity_service_chef_role": "os-identity",
        "nova_setup_chef_role": "os-compute-api",
        "rabbit_server_chef_role": "os-ops-messaging",
        "network": {
          "public_interface": "eth0",
          "service_type": "neutron"
        }
      },
      "network": {
        "debug": "True",
        "dhcp": {
          "enable_isolated_metadata": "True"
        },
        "metadata": {
          "nova_metadata_ip": "10.0.1.10"
        },
        "openvswitch": {
          "tunnel_id_ranges": "1:1000",
          "enable_tunneling": "True",
          "tenant_network_type": "gre",
          "local_ip_interface": "eth2"
        },
        "api": {
          "bind_interface": "eth0"
        },
        "l3": {
          "external_network_bridge_interface": "eth1"
        },
        "service_plugins": ["neutron.services.l3_router.l3_router_plugin.L3RouterPlugin"]
      },
      "db": {
        "bind_interface": "eth0",
        "compute": {
          "host": "10.0.1.10"
        },
        "identity": {
          "host": "10.0.1.10"
        },
        "image": {
          "host": "10.0.1.10"
        },
        "network": {
          "host": "10.0.1.10"
        },
        "volume": {
          "host": "10.0.1.10"
        },
        "dashboard": {
          "host": "10.0.1.10"
        },
        "telemetry": {
          "host": "10.0.1.10"
        },
        "orchestration": {
          "host": "10.0.1.10"
        }
      },
      "developer_mode": true,
      "endpoints": {
        "compute-api-bind": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "8774"
        },
        "compute-api": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "8774"
        },
        "compute-ec2-admin-bind": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "8773"
        },
        "compute-ec2-admin": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "8773"
        },
       "compute-ec2-api-bind": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "8773"
        },
        "compute-ec2-api": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "8773"
        },
        "compute-xvpvnc": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "6081"
        },
        "compute-novnc-bind": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "6080"
        },
        "compute-novnc": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "6080"
        },
        "compute-vnc": {
          "host": "0.0.0.0",
          "scheme": "http",
          "port": "6080"
        },
        "image-api": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "9292"
        },
        "image-api-bind": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "9292"
        },
        "image-registry": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "9191"
        },
        "image-registry-bind": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "9191"
        },
        "identity-api": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "5000"
        },
        "identity-bind": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "5000"
        },
        "identity-admin": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "35357"
        },
        "volume-api-bind": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "8776"
        },
        "volume-api": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "8776"
        },
        "telemetry-api": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "8777"
        },
        "network-api-bind": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "9696"
        },
        "network-api": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "9696"
        },
        "orchestration-api": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "8004"
        },
        "orchestration-api-cfn": {
          "host": "10.0.1.10",
          "scheme": "http",
          "port": "8000"
        }
      },
      "identity": {
        "admin_user": "admin",
        "bind_interface": "eth0",
        "debug": true
      },
      "image": {
        "api": {
          "bind_interface": "eth0"
        },
        "debug": true,
        "identity_service_chef_role": "os-identity",
        "rabbit_server_chef_role": "os-ops-messaging",
        "registry": {
          "bind_interface": "eth0"
        },
        "syslog": {
          "use": false
        },
        "upload_images": [
          "precise"
        ]
      },
      "mq": {
        "bind_interface": "eth0",
        "host": "10.0.1.10",
        "user": "guest",
        "vhost": "/nova",
        "network": {
          "rabbit": {
             "host": "10.0.1.10",
             "port": "5672"
          }
        },
        "compute": {
           "service_type": "rabbitmq",
          "rabbit": {
            "host": "10.0.1.10",
            "port": "5672"
          }
        },
        "block-storage": {
          "service_type": "rabbitmq",
          "rabbit": {
            "host": "10.0.1.10",
            "port": "5672"
          }
        }
      }
    },
    "queue": {
      "host": "10.0.1.10",
      "user": "guest",
      "vhost": "/nova"
    }
  }
}

内容について全て説明するのは難しいですが、このファイルを作成するのが今回一番苦 労した点です。と言うのは、構成を作りつつそれぞれのコンポーネントのコンフィギュ レーション、エンドポイントのアドレス、バインドアドレス、リスンポート等など、全 てが正常な値になるように Cookbooks を読みつつ作業するからです。この json ファ イルが完成してしまえば、あとは簡単なのですが。

前述しましたが KVM リソースが使えない環境の場合 Qemu で仮想マシンを稼働するこ とができます。その場合、下記のように “libvirt” の項目に “virt_type” を追記して ください。

1
2
3
4
    "libvirt": {
      "bind_interface": "eth0",
      "virt_type": "qemu" # <------ 追記
    },

それではデプロイしていきます。

ここで ‘allinone’ はホスト名、’allinone-compute’ は Role 名、neutron-allinone は先ほど作成した json で指定している environment 名です。

1
2
3
# chef-client -z
# knife node -z run_list add allinone 'role[allinone-compute]'
# chef-client -z -E neutron-allinone

環境にもよりますが、数分でオールインワンな OpenStack Icehouse が完成します。

まとめ

Chef サーバを使わなくて良いのでお手軽に OpenStack が構築出来ました。この json ファイルは実は他にも応用出来ると思っています。複数台構成の OpenStack も指定 Role を工夫すれば構築出来るでしょう。が、その場合は chef-zero は使えません。 Chef サーバ構成にする必要が出てきます。

ちなみに OpenStack Paris Summit 2014 で「OpenStack のデプロイに何を使っている か?」という調査結果が下記になります。Chef が2位ですが Pueppet に大きく離され ている感があります。Juno 版の openstack-chef-repo も開発が進んでいますので、頑 張って広めていきたいです。

  • 1位 Puppet
  • 2位 Chef
  • 3位 Ansible
  • 4位 DevStack
  • 5位 PackStack
  • 6位 Salt
  • 7位 Juju
  • 8位 Crowbar
  • 9位 CFEngine

参考 URL : http://superuser.openstack.org/articles/openstack-user-survey-insights-november-2014

ちなみに、Puppet を使った OpenStack デプロイも個人的に色々試しています。