そこはかとなく書くよん。 http://tdoc.info/blog/ Python、Ansible, Sphinx、golang、PostgreSQL、翻訳、などをだらだらと en-us Wed, 14 Dec 2016 00:00:00 +0900 http://tdoc.info/blog/2016/12/14/kane.html http://tdoc.info/blog/2016/12/14/kane.html <![CDATA[#kaneの話]]> #kaneの話

(この記事はpyspaアドベントカレンダー2016の14日目です)

ファイナンシャルプランナー3級のr_rudiです。3級程度じゃ意味ないので、来年はとりあえず2級を取ろうと思ってます。

今回は、普段のこのブログとは大きく異なり、ぼくの投資スタンスの話を書きます。といっても、特に面白みはないかもしれません。

経験

株を始めとする投資を始めたのは、ちょうど10年、2006年です。それから2007年のサブプライムローン、2008年のリーマンショックなどを経験してきました。あのときのお金がどんどん減る恐怖、自動損切りが発動して損が確定したときの喪失感と安堵感をはっきりと覚えています。

そんなに多くのお金を入れていたわけではないし、いい経験をしたなということで、そこから投資をむしろ増やしていきました。

0. 原則

投資をしてお金を増やすためには、時間が必要です。しかし、技術者ならば、その時間を勉強や仕事に振り分けて年収をあげたほうが断然効率が良いです。これは大原則として最初から思っています。ですので、投資そのものにはなるべく時間をかけないようにしていますし、値動きが気になって仕事が手に付かないことでは本末転倒ですので、気にしないような仕組みにしています。あくまで仕事による給与収入が一番大事であり、投資による収入はおまけです。

もともと投資でお金を増やすことそのものにはそこまで熱心ではなく、むしろ投資を通じて世界経済の動きを知ることのほうが楽しいので、今まで続けてきました。つまり、趣味です。他の人が趣味にお金と時間を費やすのと同じことだと思っています。

1. 個人拠出型年金

元々大企業にいたので、企業型年金に入っていました。その後転職して個人拠出型年金に切り替え、そのままずっと続けています。個人拠出型年金は単に将来退職時の年金に使えるということだけではなく、拠出した金額分税金の軽減ができるので、非常にお得です。特に保育園等、払った税金に応じて金額が変わるようなことがある場合は、結構な金額が変わる倍があります。

配分ですが、

  • 先進国株式 70%
  • 先進国債券 20%
  • 新興国株式 10%

ぐらいの割合で振り分けています。年に2,3回ぐらい見直して割合を変えていますが、先進国株式重視なのは変えていません。

2. 投資信託

先進国株式インデックスの積立型投資信託をやっています。ノーロードで信託報酬が低いもの、配当は年一回のものを選んでいます。

積立型投資は決まった額の投資信託を毎月買う方式です。金額固定の方式(ドルコスト平均法)は

  • 相場が下がった場合は多く株を買える
  • 相場が上がった場合はその分儲けられる

です。10年単位の超長期、かつ、サイクル理論が成り立つ限りは、どちらに転んでも最終的には利益はでます。

投資信託のメリットは自動的に買ってくれるという点につきます。残高をたまにチェックするだけであとは自動的に買ってくれるので、時間をかけなくてすみます

投資信託も年に2回ぐらい見直して、切り替えたり、売ったりしています。

../../../_images/sbi-total-return.png

SBIでは2009年以降のトータルリターンが表示できます

海外証券会社

ETFの自動積立があると良いのですが、使っている証券会社にはないのです。firstradeなどの海外証券会社ではできるものもあるので、そろそろ海外証券会社を開こうかなぁ、とも思っています。

3. 国内株式

国内株式に対しては悲観的なので優先度はかなり低めです。むしろETFがメインです。ETFもインデックスだけじゃなくて例えば原油ETFを原油価格が30円の時に買ったりしました。NISAも使っています。

「原則」で示したように選んでいる時間がもったいないため、個別株はほとんどやっていません。例外は優待で、以下のような企業を優待目当てでずっと保有しています。

  • イオン (8267)
    • キャッシュバックとお客様感謝デーの5%OFF
  • マックスバリュー (8198など)
    • 1000円ごとに使える、100円割引券。イオンのキャッシュバックと併用可能
    • マックスバリューは東海などいろいろあり、それぞれ100株でも優待がもらえるので良い
  • ビックカメラ (3048)
    • ネットでも使えます
    • 長く持ってると追加でもらえる
  • ゼンショー (7550)
    • すき家やはま寿司で使える
    • ワンオペ問題で売ろうかと思ったが、改善の姿勢があったので保持中
  • キャンドゥ (2698)
    • 100円ショップ
  • キリンHD (2503)
    • ビール

優待は投資家にとってあまり良くないものです。本来であれば企業業績を伸ばして配当で貰うほうが良いです。しかし、優待が届くとそれをきっかけにして購買行動に動くということが(出不精としては)個人的にはメリットなので、保持しています。

なお、情報通信系はいつどこでインサイダーでひっかかるか分からないため、一切投資していません。

4. 外国為替証拠金取引(FX)

ぼくの投資のメインはこのFXです。FXは一般的に危ないと思われていますが、ストップを置くだけで危険はかなり少なくなります。むしろ、以下の点でぼくの投資スタンスに合っていると思います。

  • 24時間動くので、仕事以外の時間で見やすい
  • 流動性が高いので、ストップを超えることはめったにない。株だと売るに売れないという状況が起こり得る
  • 通貨ペアはわずかしかないので、銘柄探しに費やす時間がない
  • 参加者が膨大のため、株のように仕手勢が入る余地が少ない(0ではない)
  • 値動きと世界の経済状況とが直結している

特に最後の点が重要で、「なんでこういう値動きになったんだろう」というのを調べることはとても面白いです。

ポジションを持っていると気になるので、基本的に順張りで、「ファンダメンタルズもチャートもこういう流れなので、絶対こう行く」という時しかポジションを作りません。そんなにいい機会は年に数回程度しかないので、ポジションをもっていない時時間帯は多いです。その間にがっつりと動くことは多々ありますが、「あの時買っておけばよかった」とかは気にしないことにしています。それでもトレンドをうまく捉えられればそこそこの利益を出せます。おかげで最初の投資金額はすべて回収し、出金していますので仮に全額なくなったとしてもトータルでは損はありません。

今年は円高になると予想してたので年初から売りポジションを持ってたりしたのですが、100円近辺まで下がるとは思っておらず利確が早すぎたのと、あとから追加で売ったら完全に予想外だったトランプ相場でやられてしまったのでちょっとプラスぐらいで終わりです。

自動売買

MT4というツールで自動売買をしようと思っていた時期もありました。しかし、単なる条件判定だと書いててあんまりおもしろくなく、これだったら普通に仕事してるほうがいいな、と思ってやめてしまいました。

今は、機械学習での自動売買ならば新しい技術の習得も兼ねられるのでおもしろそうだなぁ、と思っているところです。と言ってもまだなにもしていませんが。

まとめ

今回はいつもの技術系記事とは異なり、現在の投資スタンスについて説明してきました。かなり安全側に倒した何の変哲もない話ですが、誰かのなにかの参考になれば。と思います。基本はコツコツ麺です。


最近気になっているのはヨーロッパとアメリカにおける保護主義の動きです。これはこの50年の動きを否定する動きです。つまり、たかが10年程度の蓄積では対応できないレベルで世界経済が激変する可能性があります。

とはいえ、一個人にできることは、常日頃の情報収集とその時々の対応しかありません。これからも引き続き投資はしていこうと思っています。

なんせ趣味ですから。

]]>
Wed, 14 Dec 2016 00:00:00 +0900
http://tdoc.info/blog/2016/12/07/go_swagger.html http://tdoc.info/blog/2016/12/07/go_swagger.html <![CDATA[go-swaggerを使う]]> go-swaggerを使う

この記事はGo (その2) Advent Calendar 2016の7日目の記事です。

APIからのコード自動生成ツールとして、swaggerが最近流行ってます(異論は認めます)。

この分野では、goaが有名ですが、goaはあくまでGoのDSLから生成するという方式です。swagger定義がすでにある場合には使えません。

swagger定義ありきの場合、今回説明するgo-swaggerを使うと便利です。

なお、この内容はhttps://github.com/shirou/swagger_sampleにて公開しています。

go-swaggerとは

go-swaggerは、swagger定義からgoのサーバーおよびクライアントを生成してくれるツールです。

インストールはgo getでインストールしてもよいのですが、goの環境がない場合は

などが用意されています。

go-swaggerの使い方 -- サーバー編

例として、以下のようなswagger.ymlで定義したとしましょう。

produces:
  - application/json
paths:
  '/search':
    get:
      summary: get user information
      consumes:
        - application/x-www-form-urlencoded
      tags:
        - user
      parameters:
        - name: user_id
          type: integer
          in: formData
          description: user id
      responses:
        200:
          schema:
            $ref: '#/definitions/User'
        default:
          schema:
            $ref: '#/definitions/Error'
definitions:
  User:
    type: object
    required:
      - user_id
    properties:
      user_id:
        type: integer
        description: user id
      name:
        type: string
        description: name
  Error:
    type: object
    properties:
      message:
        type: string

サーバーサイドのコードを生成するには以下のコマンドを実行します。

$ swagger generate server -f swagger.yml -A swaggertest

これで、

  • cmd
  • models
  • restapi

というディレクトリが作成され、その中に必要なソースコードが生成されます。具体的にはこんな感じです。

restapi/
|-- configure_swaggertest.go
|-- doc.go
|-- embedded_spec.go
|-- operations
|    |-- swaggertest_api.go
|    `-- user
|         |-- get_search.go
|         |-- get_search_parameters.go
|         |-- get_search_responses.go
|         `-- get_search_urlbuilder.go
`-- server.go

-A swaggertestはアプリ名です。指定すると、いろいろなところで使われるようになります。

operationsの下のuserはtagから取ってきています。複数のtagを設定していると複数のディレクトリに同じ内容が生成されてしまうので気をつけてください。

以降は、後ほど説明するconfigure_swaggertest.go以外のファイルは触りません。

Handlerの実装

生成されたファイルには触らず、パッケージ配下に別のファイルを作成しましょう。restapi/operations/user/search.goというファイルを作成し、以下のようなコードを実装します。

package user

import (
    middleware "github.com/go-openapi/runtime/middleware"
    "github.com/go-openapi/swag"

    "github.com/shirou/swagger_sample/models"
)

func Search(params GetSearchParams) middleware.Responder {
    payload := &models.User{
            UserID: params.UserID,
            Name:    fmt.Sprintf("name_%d", swag.Int64Value(params.UserID)),
    }

    return NewGetSearchOK().WithPayload(payload)
}

paramsが渡ってくるパラメータです。swagger定義に従って事前にvalidateされていますので、安心して使えます。

NewGetSearchOKはswagger定義での200の場合に返す構造体を作成します。payloadとして、Userを定義して渡しておきます。

swagパッケージはポインタと実体とを変換したり、pathを検索したりといった便利関数を提供している便利ライブラリです。goの言語上の制限として、値なしとデフォルト値を区別するためにポインタにする必要があり、そのためにswagでの変換を使う必要があります。

Handlerの登録

restapi/configure_swaggertest.goに今実装したHandlerを付け加えます。 最初はmiddleware.NotImplementedが書かれているので、先程実装したHandlerに置き換えます。

api.UserGetSearchHandler = user.GetSearchHandlerFunc(user.Search)

configure.goは一度生成するとその後自動では変更されないので、swagger.ymlを更新して、Handlerを追加したりするときには注意が必要です。ださいですが、一旦renameして、再度生成されたファイルからコピーしたりしています。

なお、configure.goにはsetupのコードも含まれているので、必要に応じて事前設定を追加したり、ミドルウェアを追加したりするとよいでしょう。

実行

これで準備は整いました。cmd/swaggertest-server以下でbuildしましょう。

$ cd cmd/swaggertest-server && go build -o ../../server

$ ./server -port=8000

デフォルトではランダムなportで起動するので、port引数で指定しています。

あとは普通にアクセスできるようになります。

$ curl "http://localhost:8080/search?user_id=10"
{"name":"name_10","user_id":10}

簡単ですね。

生Request

paramsの中にはhttp.Requestがありますので、Requestそのものを得たい場合はここから得られます。contextもここから得ればよいかと思います。

go-swaggerの使い方: クライアント編

go-swaggerはサーバーだけではなく、以下のコマンドでクライアントを生成出来ます。

$ swagger generate client -f swagger.yml -A swaggertest

あとは以下のようなコードを書けば、clientとして実行できます。

package main

import (
    "fmt"
    "log"
    "time"

    "github.com/go-openapi/swag"

    apiclient "github.com/shirou/swagger_sample/client"
    "github.com/shirou/swagger_sample/client/user"
)

func main() {

    // make the request to get all items
    p := &user.GetSearchParams{
            UserID: swag.Int64(10),
    }

    resp, err := apiclient.Default.User.GetSearch(p.WithTimeout(10 * time.Second))
    if err != nil {
            log.Fatal(err)
    }
    fmt.Println(resp.Payload.Name)
}

この例ではパラメータ固定で叩くだけですが、引数とかをちゃんと設定していけば、CLIツールが簡単に実装できます。

model

swaggerではmodelを定義して、それを返答に含めたり出来ます。definitions以下ですね。

今回使っている例のUserモデルはmodels/user.goで以下のように生成されます。

// User user
// swagger:model User
type User struct {
     // name
     Name string `json:"name,omitempty"`

     // user id
     // Required: true
     UserID *int64 `json:"user_id"`
}

models/user.go そのものは再生成すると書きつぶされてしまうので、models以下に user_hoge.go などを作ってそちらでいろいろと関数を追加していくと良いと思います。

テスト

サーバー実装のテストはHandlerに対して指定されたパラメータを渡してあげれば良いです。ただし、http.Requestを入れる必要があるので、そのときにはhttptest.NewRecorder()を使って返り値を記録するようにします。あとは、各Handlerは単なる関数なので、その関数を実行するだけでよくて、httptest.Serverを立ち上げる必要はありません。

func TestSearch(t *testing.T) {
    req, err := http.NewRequest("GET", "", nil)
    if err != nil {
            t.Fatal(err)
    }

    params := GetSearchParams{
            HTTPRequest: req,
            UserID:      swag.Int64(10),
    }
    r := Search(params)
    w := httptest.NewRecorder()
    r.WriteResponse(w, runtime.JSONProducer())
    if w.Code != 200 {
            t.Error("status code")
    }
    var a models.User
    err = json.Unmarshal(w.Body.Bytes(), &a)
    if err != nil {
            t.Error("unmarshal")
    }
    if swag.Int64Value(a.UserID) != 10 {
            t.Error("wrong user id")
    }
}

まとめ

go-swaggerを使ってswagger定義ファイルからサーバーとクライアントのコードを生成する例を示しました。

swaggerはswagger uiがcurlのコマンドが出てくるなどかなり使いやすく、定義ファイルも慣れれば難しくありません(ファイル分割が出来ないという問題はありますが)。

クライアントとサーバーとで別れて開発しているときなど、swaggerとswagger uiを使ってプロトコル定義を確認しつつ開発を進めていくと、理解の不整合が起きにくくなります。

Goaから生成するのも良いですが、swagger定義ファイルから生成する場合はgo-swaggerも検討してみると良いのではないでしょうか。

]]>
Wed, 07 Dec 2016 00:00:00 +0900
http://tdoc.info/blog/2016/11/02/eawsy_lambda.html http://tdoc.info/blog/2016/11/02/eawsy_lambda.html <![CDATA[eawsyのaws-lambda-goを使ってみる]]> eawsyのaws-lambda-goを使ってみる

AWS lambdaは昨今人気ですね。しかし、実行環境がpython、java、nodeだけです。ぼくはpythonも結構やってきましたが、近頃ずっとgoを使っており、goで実行できると良いなと思っていました。

ということで、昨日eawsy/aws-lambda-goというgoでlambdaを記述できるライブラリを知ったので、試してみました。 (以下eawsyと呼びます)

apexとの比較

lambdaをgoで書きたいなと言う気持ちは過去にはAWS Lambdaで効率的にgoバイナリを実行するという記事を書いてました。このときは、lambda_procというライブラリを使っていたのですが、その後いろいろでてきて、最近だとapexが有名です。

では、eawsyとapexとの違いはなんでしょうか。eawsyはPythonのC拡張を用いて実行します。

  • apex
    • lambdaはruntimeとしてnodeを呼び出し、さらにnodeがgoのバイナリをspawnで呼び出す。
    • 実行するのは普通のバイナリ。Linuxでそのまま実行できる
  • eawsy
    • goを-buildmode=c-sharedでshared libraryとしてbuild
    • lambdaはpythonを実行。pythonはgoをC拡張として読み込んで実行

つまり、apexでは二回のプロセス生成が入るのに比べて、eawsyでは一回だけとなります。その代わり、buildにcgo環境が必要になります。しかし、eawsyはDockerコンテナを提供していますので、cgo環境を用意する必要はありません。

使ってみる

maing.goはこんな感じで書きます。

package main

import (
      "encoding/json"
      "log"
      "time"

      "github.com/eawsy/aws-lambda-go/service/lambda/runtime"
)

func handle(evt json.RawMessage, ctx *runtime.Context) (interface{}, error) {
      ret := make(map[string]string)
      ret["now"] = time.Now().UTC().Format(time.RFC3339)

      return ret, nil
}

func init() {
      runtime.HandleFunc(handle)
}

func main() {}

mainは空で、initでhandleを登録します。json.RawMessageにはパラメータが入って渡ってきます。

retはinterface{}なので任意の型を返せます。これがJSONになって返答されます。

ベンチマーク

apexでほぼおなじコードを書いてみました。

package main

import (
     "encoding/json"
     "time"
     apex "github.com/apex/go-apex"
)

type message struct {
   Time string `json:"time"`
}

func main() {
     apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) {
             var m message
             if err := json.Unmarshal(event, &m); err != nil {
                     return nil, err
             }
             m.Time = time.Now().Format(time.RFC3339)
             return m, nil
     })
}

以下のように直接実行して、CloudWatchのログで実行時間を見てみます。

eawsy
$ aws lambda invoke --function-name preview-go output.txt
apex
$ apex invoke hello
ベンチマーク
回数 eawsy apex
一回目 16.38 ms 41.11 ms
二回目 0.48 ms 1.21 ms
三回目 0.50 ms 0.64 ms

lambdaは一回目はコンテナ(かどうか不明ですが)を起動するので時間がかかります。二回目以降は起動済みなので早いです。ということで大事な一回目なのですが、apexが40msecに比べてeawsyが16msecと、約半分になっています。めんどいので一回だけの結果しか載せませんが、数回やって基本的な傾向は同じでした。

一度起動が完了したら、1msec以下になり、これは両者とも同じです。



しかし、ぶっちゃけ、40msecが16msecになったところで一回だけです。これが重要なワークロードもあるでしょうが、あんまり意味があるとは思えません。そもそもlambdaの実行時間が不安定なので、数msec速くなったところで、それがどうしたよ、という感じです。

eawsyの利点はベンチマークではなく、ログ出力やruntimeの関数を呼べることです。(ということを、redditのFinally vanilla Go on AWS Lambda (no serverless!) startup <5msで作者が述べています。)

eawsyの利点

ログ出力

apexはnodeのruntimeを通している関係上、ログ出力はstdoutに出したものだけとなります。そのため、一手間必要になってしまっていました。それに対して、eawsyは標準のlogパッケージが使えます。

log.Printf("Log stream name: %s", ctx.LogStreamName)
log.Printf("Log group name: %s", ctx.LogGroupName)
log.Printf("Request ID: %s", ctx.AWSRequestID)
log.Printf("Mem. limits(MB): %d", ctx.MemoryLimitInMB)
log.Printf("RemainingTime: %d", ctx.RemainingTimeInMillis)

このように普通のlog出力と同じようにしておくと、CloudWatchのログに以下のように出ます。

13:19:55 START RequestId: 9bf7d852-a0b3-11e6-b64b-7dec169bb683 Version: $LATEST
13:19:55 2016-11-02T04:19:55.919Z     9bf7d852-a0b3-11e6-b64b-7dec169bb683    Log stream name: 2016/11/02/[$LATEST]1e58f3ef77894283988110ea452dc931
13:19:55 2016-11-02T04:19:55.919Z     9bf7d852-a0b3-11e6-b64b-7dec169bb683    Log group name: /aws/lambda/preview-go
13:19:55 2016-11-02T04:19:55.919Z     9bf7d852-a0b3-11e6-b64b-7dec169bb683    Request ID: 9bf7d852-a0b3-11e6-b64b-7dec169bb683
13:19:55 2016-11-02T04:19:55.919Z     9bf7d852-a0b3-11e6-b64b-7dec169bb683    Mem. limits(MB): 128
13:19:55 END RequestId: 9bf7d852-a0b3-11e6-b64b-7dec169bb683
13:19:55 REPORT RequestId: 9bf7d852-a0b3-11e6-b64b-7dec169bb683
Duration: 16.38 ms
Billed Duration: 100 ms Memory Size: 128 MB   Max Memory Used: 8 MB

Fatalfだとエラーに、Panicだとstacktraceも表示されます。

エラー処理

handleの返り値にerrorを入れると

return ret, fmt.Errorf("Oops")

以下のようなログがCloudWatchに書き出されます。

Oops: error
Traceback (most recent call last):
File "/var/runtime/awslambda/bootstrap.py", line 204, in handle_event_request
result = request_handler(json_input, context)
error: Oops

runtimeの関数を呼べる

apexのアプローチでは、あくまで実行はgoのため、nodeに提供されている情報を得ることは出来ませんでした。しかし、 eawsyではruntimeに提供されている情報をgoから得ることが出来ます。

上のログ出力の例にctx.RemainingTimeInMillisという残り時間を得る箇所があります。これがpython runtimeに提供されている情報を利用できている証拠です。

まとめ

PythonからC拡張経由でgoを呼び出すというアプローチが面白かったので使ってみました。

といっても、ベンチマーク的には(元から速いぶん)決定的な違いではなく、runtimeの関数を呼べたり標準logパッケージが使えるのはちょっとうれしいかもしれませんが、プログラミングモデルとしてもそこまで大きな差はありません。

apexはgolangに限らないことと、apex deployというように管理ツールとしても優れていることから、現時点ではapexの方に軍配が上がると思います。

おまけ

proxy.cがruntimeの実体ですね。handleでgoを呼んでます。

普通にC拡張なので、rustやC++などでも同じアプローチが出来ます。rustで書いてみるのも面白いかもしれません。

]]>
Wed, 02 Nov 2016 00:00:00 +0900
http://tdoc.info/blog/2016/08/16/testing_docker_swarm.html http://tdoc.info/blog/2016/08/16/testing_docker_swarm.html <![CDATA[Docker swarmを試してみた]]> Docker swarmを試してみた

TD; LR

docker swarmは気軽に使えてとても良いが、実運用は自分でちゃんと試してからの方がよさげ。

まえがき

Dockerの1.12.0からDocker swarmがdockerと統合され、クラスタ環境でのDockerが簡単に使えるようになりました。この記事は夏休みの自由研究として、このDocker swarm modeについて調べてみた記録です。

長いです。細かいです。前提をいろいろ飛ばしています。全部読むことはおすすめしません。

間違っている点は指摘していただけると助かります。

リポジトリ

この文章に関するリポジトリ
https://github.com/shirou/docker-swarm-test
docker engine: docker engineのCLI的なやつ。
https://github.com/docker/docker
swarmkit: この中にswarmの実際の中身が入っている
https://github.com/docker/swarmkit
swarm: 1.12までのswarm実装。今回は対象外
https://github.com/docker/swarm

docker swarm mode

docker swarm modeは、複数のdocker hostを束ねて使えるようになるモードです。従来はdocker swarmとして別物でしたが、1.12からdocker engineに統合されました。

とかいう概要はばさっと飛ばします。以下は用語やコマンドのメモです。

登場人物

manager node
管理ノード。3〜7がmanager nodeの最適個数
worker node
task実行ノード
node
一つのdocker engineが動いているサーバー
service
複数のdocker engineが協調して、一つのportでサービスを提供する一つのswarm clusterは複数のserviceを持てる
https://docs.docker.com/engine/swarm/images/swarm-diagram.png

使いかた

  • swarm
    • docker swarm init
      • swarm clustrを初期化します。つまり、このコマンドを実行したdocker hostが一番最初のmanager nodeとなります。
    • docker swarm join
      • 指定したmanager nodeが管理するswarm clusterにjoinします。--tokenでswarm cluster tokenを指定します。manager tokenを指定すればmanagerとして、worker tokenを指定すればworkerとしてjoinします。あるいは--managerで明示的にmanagerとしてjoinさせることもできます。
    • docker swarm leave
      • clusterから抜けます
  • node
    • docker node ls
      • nodeの状態を見ます
    • docker node ps
      • taskの状態を見ます
    • docker node update
      • nodeをupdate
    • docker node demote/docker node promote
      • workerに降格(demote) / managerに昇格(promote)
  • service
    • docker service create
      • serviceを作成
    • docker service ls
      • serviceの状態を見ます
    • docker service ps
      • taskの状態を見ます
    • docker service update
      • rolling updateをします
  • network
    • docker network create
      • overray networkを作成

今回使うプロセス

今回実行するプロセスは以下の通り。コードはこちら

package main

import (
     "fmt"
     "net/http"
     "strings"
     "time"
)

var wait = time.Duration(1 * time.Second)

func handler(w http.ResponseWriter, r *http.Request) {
     rec := time.Now()

     time.Sleep(wait)
     rep := time.Now()

     s := []string{
             rec.Format(time.RFC3339Nano),
             rep.Format(time.RFC3339Nano),
     }

     fmt.Fprintf(w, strings.Join(s, ","))
}

func main() {
     http.HandleFunc("/", handler)
     http.ListenAndServe(":8080", nil) // fixed port num
}

単に1秒待ってからリクエスト時とリプライ時の時刻をCSVを8080ポートで返しているだけ。1秒をblockingして待つという最悪な処理なのです。

今回buildはCircleCIに任せてあって、tar.gzを作ってあるので、各nodeでは以下のようにimportすればいい。tagは適当。

$ docker import https://<circleci artifact URL>/docker-swarm-test.tar.gz docker-swarm-test:1

ヒント

golangはglibcなどが必要なく1バイナリで実行できるので、Dockerfileとか別に要らなくてtar.gzで十分。linuxでnetを使うとdynamic linkになる件はGo 1.4でstatic binaryを作成するgolangで書いたアプリケーションのstatic link化をみてください。

$ docker service create --name web --replicas 3 --publish 8080:8080 docker-swarm-test:1 "/docker-swarm-test"
$ docker service ps web
ID                         NAME   IMAGE                NODE       DESIRED STATE  CURRENT STATE          ERROR
18c1hxqoy3gkaavwun43hyczw  web.1  docker-swarm-test:1  worker-2   Running        Running 3 minutes ago
827sjn1t4nrj7r4c0eujix2it  web.2  docker-swarm-test:1  manager-1  Running        Running 3 minutes ago
2xqzzwf2bte3ibj2ekccrt6nv  web.3  docker-swarm-test:1  worker-3   Running        Running 3 minutes ago

この状態で、コンテナが動いている worker-2,3, manager-1に対して、curlでGETすると返してくれます。それ以外に、コンテナが動いていないworker-1に対してcurlで聞いても、きちんと答えてくれます。これは、中でリクエストが転送されているからです。

rolling update

swarm clusterで--publishを付けてserviceを提供すると、一つのportで複数のnodeに対してリクエストを割り振るload balancingをやってくれます。従って、今までdockerでportが動的に変わったり、一つのnodeで同じport番号を使ってしまったりするのが問題になる場合がありましたが、その問題は生じません。また、swarm cluster内でLoad balancingを行ってくれるので、rolling updateも容易です。

% sudo docker service update --image "docker-swarm-test:1" web

というわけで、試してみました。先ほどのプロセスは1秒待つので、下手に切るとリクエストを取りこぼすはずです。

ツールにはabを使います。今回は処理能力ではなく、リクエストを取りこぼさないかどうかのテストなので、abで十分と判断しました。

% ab -rid -c 10 -n 500 http://45.76.98.219:8080/

Concurrency Level:      10
Time taken for tests:   50.146 seconds
Complete requests:      500
Failed requests:        8
   (Connect: 0, Receive: 4, Length: 0, Exceptions: 4)

ということで、取りこぼしてしまいました。残念ですね。--update-delayは次のコンテナを起動するまでの時間なので関係がなさそうです。--restart-delayも組み合わせてみましたがだめでした。nodeのステータスをdrainに手動で変更していけばうまく動くかもしれませんが、手間がかかるので試してません。

調べてみると、この辺りが関連してそうです。

次のpatch releaseで修正されるとのこと。ちょっとまだlibnetworkまで調査が足りてないのでこれで本当に直るのかは分かりませんが、今はまだ本番環境上で使うのは早そうです。

というより、nginxとか使え

というより、そもそもingress overlay networkは内部で使う用途で、外部サービスに公開する用途ではない、ということのようです。外部に公開する場合は、nginxなりを使って後述のDNS service discoveryに従って使うコンテナを決定しろ、とのことのようです。

この辺りはこれからもうちょっと調べないといけない感じです。

network

docker network createでnetworkを作成します。あとからdocker service update --network-addでnetworkを追加しようとしたら

Error response from daemon: rpc error: code = 2 desc = changing network in service is not supported

と怒られたのでserviceを作り直します。

docker service create --replicas 3 --name web --network webnet ...

その後、shellとしてalpineを立ち上げます。

$ docker service create --name shell --network webnet alpine sleep 3000
$ sudo docker service ls
ID            NAME        REPLICAS  IMAGE                COMMAND
1f9jj2izi9gr  web         3/3       docker-swarm-test:1  /docker-swarm-test
expgfyb6yadu  my-busybox  1/1       busybox              sleep 3000

execで中に入ってnslookupで同じnetworkに属しているwebserviceをDNSで探します。

$ docker exec -it shell.1.3x69i44r6elwtu02f1nukdm2v /bin/sh
/ # nslookup web

Name:      web
Address 1: 10.0.0.2

/ # nslookup tasks.web

Name:      tasks.web
Address 1: 10.0.0.5 web.3.8y9qbba8eknegorxxpqchve76.webnet
Address 2: 10.0.0.4 web.2.ccia90n3f4d2sr96m2mqa27v3.webnet
Address 3: 10.0.0.3 web.1.44s7lqtil2mk4g47ls5974iwp.webnet

webつまりservice名を聞けばVIPを、tasks.webを聞けば各nodeを直接DNS RoundRobinで答えてくれます。

このように、同じnetworkに属しさえすれば、他のServiceを名前で引くことができるので、コンテナ間の連携がしやすいと思います。

プロトコル

raft

docker swarmでは、複数のmanager node間でのLeader選出にはRaft consensusを使っています。raftの実装はetcdのraft libraryです。docker node lsでどのManagerがLeaderかが分かります。

$ docker node ls
ID                           HOSTNAME   STATUS  AVAILABILITY  MANAGER STATUS
5g8it81ysdb3lu4d9jhghyay3    worker-3   Ready   Active
6td07yz5uioon7jycd15wf0e8 *  manager-1  Ready   Active        Leader
91t9bc366rrne19j37d0uto0x    worker-1   Ready   Active
b6em4f475u884jpoyrbeubm45    worker-2   Ready   Active

raftですので、きちんとした耐故障性を持つためにはmanager nodeは最低限3台必要ですし、3台中2台が落ちたらLeader選出ができなくなります。この場合、docker swarmでは、新規のtaskを受け付けられない状態になります。

heat beat

swarm node間はheatbeatで死活監視をしています。heatbeatは通常5秒間隔ですが、docker swarm init時に--dispatcher-heartbeat durationで指定することも出来ます。死活監視の結果はgossipで分配されます。

疑問

serviceを消したらcontainerはどうなるの?

docker service rmでserviceを消すと、containerも全部消えます。消えるまでは時間がかかるので注意

worker node以上のtaskを振られたら?

nodeが3つしかない場合に、 docker swarm scale web=10とかしたらどうなるか?

答えは一つのnodeに複数のコンテナが立ち上がります。

podの概念

なさそう。 serviceを作成するときに--constraintで配置制約としてaffinityを使うとかなのかな。

あとがき

もはやコンテナ技術そのものはどうでも良くて、Kubernetesなどの複数Node管理が重要だと個人的には思っています。Docker swarm自体は今までもあったけれども、それをDocker Engineに統合することで、コンテナだけではなくその上の管理も事実上の標準を取っていくぞという意気込みが感じられます。しかも、kubernetesは使いはじめるまでが大変ですが、その手間がほぼないため、優位と感じます。

swarm clusterを組むのは簡単ですし、cluster自体はとても安定しているように思いました。もちろん、split brainなど凝ったテストをしていないのでなんともですが、raft関係はetcdのを使っていますので安定しているのではないかと思っています。

ただ、networkについてはまだ不安定なようで、serviceを作ったり消したりnetworkを作ったり消したりしていると、名前が引けなくなったりしました(詳細は追っていません)。

networkやgracefulなど、まだこなれていない点はありますが、今後Docker swarmは普及していくのではないかな、と思います。

]]>
Tue, 16 Aug 2016 00:00:00 +0900
http://tdoc.info/blog/2016/07/08/ansible_to_docker.html http://tdoc.info/blog/2016/07/08/ansible_to_docker.html <![CDATA[DockerコンテナでAnsibleをテストする]]> DockerコンテナでAnsibleをテストする

Ansible 2.0になり、Docker connection pluginが標準で入りました。これにより、Docker内にsshdを立てることなくAnsibleを直接実行できるようになりました。

すでに導入されている方も多く、かなり今更ではありますが、Dockerコンテナに対してAnsibleを実行してテストする方法についてここに記します。

Dockerに対する場合の制限

まず最初にAnsibleをDockerコンテナに対して実行する際の制限についてです。

基本的にはすべての機能が使えます。ただ、以下の制限があります。

  • /etc/hosts, /etc/resolv.conf, /etc/hostnameは書き換えできない

    これらのファイルはDockerがbind mountしており、書き換えられるが、置き換えることは出来ないため。/etc/hostnameが変更できないため、hostnameモジュールでの変更もできない。

また、実行するイメージによっては少なくとも以下の問題があります。他にもいろいろあるかもしれません。このあたりはDocker自体に関する問題で、Ansible特有の問題ではないので、なんとか解決して下さい。

  • systemdのserviceが起動できない

    D-busがないため、Failed to connect to bus: No such file or directoryと言われる。upstartやrc initは起動できる。CAP_SYS_ADMINcapabilityが必要

  • sudoがない場合がある

付け加えるならば、まっさらなイメージからテストをしていくとダウンロードなどに時間がかかってしまいますので、適宜設定を施したイメージを事前に用意しておくとテストの時間が減ると思います。

Inventory

さて、本題です。Ansibleの docker connection pluginを使うには

web ansible_connection=docker ansible_host=<コンテナID>

というように、ansible_connection=dockerとするだけですぐに使えます。しかし、ansible_hostにはコンテナIDを指定しなければいけません。DockerのコンテナIDは本当に一時的なものなのでここに書くのはデバッグ時だけです。

これを回避するためにはdockermoduleを使ってコンテナを立ち上げ、add_hostでグループを生成することも可能ですが、playbookをテスト用に編集する必要が出てきます。それでもいい場合もありますが、せっかくですからDocker dynamic inventoryを使いましょう。

Docker dynamic inventory

GitHubのansibleのリポジトリからdocker.pyを取得し実行権限を付与しておきます。docker.ymlはなくても構いません。

# docker containerを立ち上げる
$ docker run --name <hostsでの指定対象> ubuntu:16.04 /bin/sleep 3600

# 立ち上げたdocker containerに対してansibleを実行する
$ ansible-playbook -i docker.py something.yml

で現在起動しているコンテナのnameからコンテナIDを取得して、使ってくれます。docker.pyで取得できる情報の一部を以下に示します。name以外にもimageやコンテナIDでグループが作成されていることが分かります。しかし、今回はテストなので、通常のグループと同じ名前を使いたいためnameを使います。

"web": [
  "web"
],
"image_ubuntu:16.04": [
  "web"
],
"zzzzzd5bed36e033fac72d52ae115a2da12f3e08b995325182398aae2a95a": [
  "web"
],
"zzzzz114d5bed": [
  "web"
],
"running": [
  "web"
],
"docker_hosts": [
  "unix://var/run/docker.sock"
],
"unix://var/run/docker.sock": [
  "web"
],

Inventory Directory

dynamic inventoryをつかうと、inventoryファイルで指定してあるgroup_varsが使えなくなってしまうのではないか、と思うかもしれません。

その場合ディレクトリを分けてdocker.pyと静的なファイルを入れておきます。そうしておいてinventoryファイルとしてディレクトリを指定すると、静的なファイルとdynamic inventoryの両方から情報をとってくれます。CIでのみ使う場合はCI用のInventoryとして、ディレクトリを分けておくと扱いやすくなると思います。

CircleCI

では、CIを通してみましょう。CircleCIを試します。circle.ymlはこんな感じです。

machine:
  services:
    - docker
  environment:
    DOCKER_API_VERSION: "1.20"

dependencies:
  pre:
    - docker pull ubuntu:16.04
    - sudo pip install ansible ansible-lint docker-py

test:
  override:
    - docker run -d --name web ubuntu:16.04 /bin/sleep 3600
    - ansible-playbook -i inventory_docker web.yml test.yml --syntax-check
    - ansible-lint test.yml
    - ansible-playbook -i inventory_docker web.yml test.yml -l running

--syntax-checkやansible-lintもついでに行っています。DOCKER_API_VERSIONはCircleCIのdockerが古いために設定しています。また、docker runで--name webとしています。これは、通常使うplaybookではweb グループに対して実行しており、そのplaybookを変えたくないからです。

これでpushすると、

fatal: [web]: FAILED! => {"changed": false, "failed": true, "rc": 1, "stderr": "Error response from daemon: Unsupported: Exec is not supported by the lxc driver\n", "stdout": "", "stdout_lines": []}

と怒られてしまいました。そうです。CircleCIはlxc driverを使っており、Docker connection pluginが使うdocker execが使えないのです。

ということで諦めました。

他のCIサービスはwerckerとかdrone.ioありますが、これらはそもそもCIでDockerを使っており、Docker in Dockerになってしまいいろいろ大変です。

別解: 自前Dockerホストを用意する

あるいは、circle.ymlのenvironmentDOCKER_HOSTを設定することで、CircleCI外に立てたDockerホストに対して実行することもできます。次に説明するGitLabを使うよりも手軽かもしれませんが、セキュリティ設定をしっかりする点は特に気をつけて下さい。

GitLab

GitLabが最近流行りですね。最近CIも付いたのでこれを使う場合も紹介します。

gitlabやgitlab CI runner自体のインストールは省略します。Docker内で実行するCI Runnerもありますが、それではDocker in Dockerになってしまいますので、今回の用途ではshell runnerにすることを忘れないで下さい。

結論からいうと、runnerの設定がしっかりしてあれば、このような.gitlab-ci.ymlで動きます。ほとんどCircleCIと変わらず、after_scriptによるコンテナの削除が入っているぐらいです。

before_script:
  - pip install ansible ansible-lint docker-py

stages:
  - build

build_job:
  stage: build
  script:
    - docker run -d --name web ubuntu:16.04 /bin/sleep 3600
    - ansible-playbook -i inventory_docker web.yml test.yml --syntax-check
    - ansible-lint test.yml
    - ansible-playbook -i inventory_docker web.yml test.yml -l running

after_script:
  - docker kill `docker ps -aq`
  - docker rm `docker ps -aq`

runnerの設定としてはsudo gpasswd -a $USER dockerをしてsudoをしなくてもdockerを使えるようにしておくとよいと思います。

追記: Travis CI

@auchidaさんからTravis CIならば使える、ということをお聞きしました。auchidaさんのリポジトリを参考にしました。

ポイントはsudo: requiredを入れることのようです。

しかし、たぶんvirtualenvとsystemとがなにかおかしいようで、docker dynamic inventoryを実行時に以下のエラーが出ました。そのうち直したいと思います。

class AnsibleDockerClient(Client):
    NameError: name 'Client' is not defined

ありがとうございました。

まとめ

この記事では、Dockerコンテナを利用してAnsibleのテストを行う方法についてご紹介しました。

  • Ansible2.0からDockerコンテナに対して直接ansibleを実行できる
  • 一部の制限はあるが、Docker上でも問題なく動く
  • CircleCIでは動かないので以下の三つの方法を紹介
    • 自前でDockerホストを用意する
    • GitLabなどを立てる
    • Travis CIを使う

おまけ: ansible-lintのルール

最近弊社内でPlaybookの書き方を統一するためにansible-lintのルールの制定を始めました。ansible-lint-rulesにて公開しています。

Long descriptionがないなど、まだ途中ではありますが、使ってみてIssueやPRをいただけると大変ありがたく思います。

]]>
Fri, 08 Jul 2016 00:00:00 +0900
http://tdoc.info/blog/2016/07/06/xo.html http://tdoc.info/blog/2016/07/06/xo.html <![CDATA[DBから直接golangのモデルを生成するxoのご紹介]]> DBから直接golangのモデルを生成するxoのご紹介

Webアプリを開発している時に、DBのモデル定義の方法にはいろいろなやり方があると思います。

xoは、DBから直接golangのモデル定義を自動生成するツールです。

  • PostgreSQL
  • MySQL
  • Oracle
  • Microsoft SQL Server
  • SQLite

に対応しており、良く使われるRDBをほぼカバーしていると思います。

インストール

goのツールですので、go getでインストールできます。

$ go get -u golang.org/x/tools/cmd/goimports (依存性のため)
$ go get -u github.com/knq/xo

これでxoというコマンドがインストールされたと思います。

使い方

ではさっそく使ってみましょう。使用するDBはPostgreSQLです。

CREATE TABLE users (
    id   BIGSERIAL PRIMARY KEY,
    name TEXT,
    age  INT NOT NULL,
    weight INT,
    created_at timestamptz NOT NULL,
    updated_at timestamptz
);

CREATE INDEX users_name_idx ON users(name);

このようなテーブルとインデックスがあったとしましょう。

xoを実行します。

$ mkdir -p models  # 事前にディレクトリを作っておく
$ xo pgsql://localhost/example -o models

そうすると、models以下に

  • user.xo.go
  • xo_db.xo.go

という二つのファイルが作成されます。xoで生成したファイルは*.xo.goとなるので分かりやすいですね。

user.xo.goには以下のような内容が生成されています。NOT NULLをつけたか付けないかで型が違っているところに注目して下さい。また、jsonタグも生成されているので、そのままJSONとして出力もできます。

// User represents a row from 'public.users'.
type User struct {
        ID        int64          `json:"id"`         // id
        Name      sql.NullString `json:"name"`       // name
        Age       int            `json:"age"`        // age
        Weight    sql.NullInt64  `json:"weight"`     // weight
        CreatedAt *time.Time     `json:"created_at"` // created_at
        UpdatedAt pq.NullTime    `json:"updated_at"` // updated_at

        // xo fields
        _exists, _deleted bool
}

この生成されたUser型に対して、以下の関数が生成されています。

  • func (u*User) Exists() bool
  • func (u*User) Deleted() bool
  • func (u*User) Insert(db XODB) error
  • func (u*User) Update(db XODB) error
  • func (u*User) Save(db XODB) error
  • func (u*User) Delete(db XODB) error
  • func (u*User) Upsert(db XODB) error (PostgreSQL 9.5+以上の場合)

XODB型は xo_db.xo.go で定義されているdbに対するinterfaceです。

IDはPrimary Keyですし、nameに対してindexを貼っています。ということで、以下の二つの関数も生成されています。

  • func UserByID(db XODB, id int64) (*User, error)
  • func UsersByName(db XODB, name sql.NullString) ([]*User, error)

これらの関数を使ってSELECTする、という流れです。UsersByNameの方は、返り値がSliceということもポイントですね。

実装

ここまで自動生成されていればあとは簡単です。以下のような実装がすぐにできます。

db, err := sql.Open("postgres", "dbname=example sslmode=disable")
if err != nil {
   panic(err)
}

now := time.Now()
u := &User{
   Age:       18,
   CreatedAt: &now,
}
err = u.Insert(db)
if err != nil {
   panic(err)
}

user, err := UserByID(db, u.ID)  // Insertでu.IDがセットされている
if err != nil {
   panic(err)
}
fmt.Println(user.Age)  // -> 18が返される

SQL

InsertUpdateなどの関数の中身はどうなってるかというと、

// sql query
const sqlstr = `INSERT INTO public.users (` +
        `name, age, weight, created_at, updated_at` +
        `) VALUES (` +
        `$1, $2, $3, $4, $5` +
        `) RETURNING id`

// run query
XOLog(sqlstr, u.Name, u.Age, u.Weight, u.CreatedAt, u.UpdatedAt)
err = db.QueryRow(sqlstr, u.Name, u.Age, u.Weight, u.CreatedAt, u.UpdatedAt).Scan(&u.ID)
if err != nil {
        return err
}

というように、SQLがそのまま生成されています。挙動が分かりやすくてぼくはこの方が好きですね。

関数

xoが扱うのはテーブル定義だけではありません。関数も扱ってくれます。

CREATE FUNCTION say_hello(text) RETURNS text AS $$
BEGIN
    RETURN CONCAT('hello ' || $1);
END;
$$ LANGUAGE plpgsql;

という関数を定義したとしましょう。こうしておくと、sp_sayhello.xo.goというファイルが生成されます。Stored Procedureですね。

この中にはSayHelloというgolangの関数が定義されています。

  • func SayHello(db XODB, v0 string) (string, error)

これ、中身は

// sql query
const sqlstr = `SELECT public.say_hello($1)`

// run query
var ret string
XOLog(sqlstr, v0)
err = db.QueryRow(sqlstr, v0).Scan(&ret)
if err != nil {
        return "", err
}

というように、定義した say_hello 関数をSQLで呼ぶようになっています。ですから、

SayHello(db, "hoge")

というように、golangから呼べるようになります。

まとめ

DBのメタデータからgolangのコードを生成してくれる、xoを紹介しました。

この他、PostgreSQLで定義した型をgolangの型に変換してくれるなど、かなりいたれりつくせりです。また、コードはtemplateで作られており、このtemplateは自分で定義することもできるので、SQL文を変えたり、関数を追加したりなども自由にできます。

ちょうど同じようなものを作ったのですが、xoの方が断然高機能なので、xoを使ったほうが良いかと思います。

]]>
Wed, 06 Jul 2016 00:00:00 +0900
http://tdoc.info/blog/2016/04/18/ansible_up_and_running.html http://tdoc.info/blog/2016/04/18/ansible_up_and_running.html <![CDATA[書評: 初めてのAnsible]]> 書評: 初めてのAnsible

「初めてのAnsible」 という本がオライリージャパンから発売されました。その本を頂いたので、読んでみました。

結論

結論から述べますと、この本は「初めての」と付きますが、これから使いたい人だけでなく、今現在も使っている人にとっても買うべき本だと思います。

Ansibleの実行方法、Playbook、Task、InventoryといったAnsibleを使う上での重要なところが一から順序良く書かれており、すぐに理解できるようになると思います。そういう点で初心者向けです。

それでいて、かなり注釈が多く、初心者向け、ということだけではなく、YAMLの引っかかりやすい文法上の問題や、localhostが暗黙的にinventoryに追加されるというような細かいところまできちんと書いており、現在使っている人に取っても有意義だと思います。特に、筆者はこう考えてこういうルールを適用してplaybookを書いている、といった設計に関することが書かれているのはとてもありがたいです。

また、翻訳もこなれており、読んで引っかかるところはありません。

2.0対応

2.0への対応状況というのが気になるところだと思います。この本は、Ansible: Up and Runningという本の翻訳です。原著が執筆された時にはAnsible 2.0はまだ出ていませんでしたので、当然内容は1.9対応です。

しかし、日本語訳された時に、書かれているPlaybookがすべて2.0.0.2で動作確認をしたとのことです。あまり時間もなかったでしょうに、素晴らしい。ということで、1.9互換の箇所については何の問題もありません。

さらに、付録として「Ansible 2.0」が付いており、ブロックなどの新機能について説明されています。

各章のみどころ

ここからは、各章について独断と偏見で述べていきます。これだけ読んでもよく分からないと思いますので、気になる方はぜひ買って読んでみてください。

1章: イントロダクション

Ansibleをなぜ選択するか、といったことやインストール方法などが書かれています。

Inventoryファイルはやっぱりリポジトリ内にあったほうがいいですよね。なんでデフォルトが/etc/ansible/hostsなんだろう。

2章: Playbook: はじめてみよう

Playbookの書き方と、タスクやPlayといったAnsible用語の定義です。特にYAMLの引用符が必要になるところは、ハマることが結構あるので読んでおくと良いですね。

惜しむらくはcowsayについてあんまり書いてないことです。しかも無効化する方法を書くなんて!

ハンドラについては、ぼくも必須とまでは思っていなかったので、同意ですね。あったほうがtask自体はシンプルになるので良いのですが、確かにハマるところでもあります。ちなみに、--force-handlersオプションを付けることで強制的にハンドラーを起動させることが出来ますが、ハマるときはそういうことに気が付かないものです。

3章: インベントリ: サーバーの記述

インベントリファイルです。Vagrantを使って説明しているのは良いですね。

特に、動的なインベントリについて例を上げて記述されているのが良いです。動的なインベントリは10台以上の場合は使ったほうがいいとぼくも思っています。group_byは…ぼくも使ったことが無いですねぇ…。Ansible Towerのインストールなどには使われているので、「このディストリビューションの時はまとめてこれ」とかではいいのかもしれません。

4章: 変数とファクト

変数とファクトです。とても重要です。

ローカルファクトがちゃんと書いてあるのはいいですね。これ、設置に一手間必要なのであんまり使われてないかもしれませんが、かなり便利ですよ。あと、優先順位「このリストに載っていないすべての方法」とざっくりまとめられてちょっと吹き出しましたが、正しいですね。

5章: Mezzanineの紹介

かなり純粋にMezzanineの紹介です。すいません、これ知りませんでした。

6章: Mezzanineのデプロイ

Vagrantを使った実際のデプロイです。この章を読めば、実本番環境でも対応できるようになると思います。まあ、djangoアプリに寄ってますので、その他の場合はいろいろ変更が必要だとは思いますが、基本的な流れは十分に分かります。

xip.ioは知りませんでした。便利なサービスですね。今度使おう。

7章: 複雑なplaybook

6章で説明しなかった機能の解説です。local_actionrun_oncechange_when/failed_whenなど、重要な機能がいっぱいです。とはいえ、6章と7章を読めば一通りなんでもできるようになりますね。

この章の見どころは「独自のフィルタの作成」と「独自のルックアッププラグインの作成」ですね。さっくりと飛ばしていますが、言及があるのが良いです。自分でフィルタやルックアップを作れるようになると、Ansibleでできることが飛躍的に大きくなります。「YAMLプログライミングで複雑だ」と思っている人は一度自分でフィルタを作ってみると良いと思います。

ちなみに、この章ではルックアップの次に複雑なループが書いてあります。これ、実はwith_hogeなどのループはルックアッププラグインとして実装されているからなのですが、きちんと書いてあるのが良いですね。

8章: ロール

ロールです。ロールはAnsibleの一番大事な機能です。ロールがきっちりと理解できていると、複雑な構成に対しても簡潔なplaybookで対応できるようになります。

varsとdefaultsの差は、結構悩みどころです。著者は上書きされる可能性があるものはdefaultsで、通常の変数はvarsで、という話を書いています。それは正しいのですが、往々にして後から上書きしたくなることが出てくるものです。個人的にはすべてdefaultsでいいのではないか、と思っています。

ちなみに、Ansible Galaxyについてちょっと触れていますが、ansible-galaxyコマンドはAnsible Galaxyサイト以外も指定できるようになったので、共有ロールをプライベートなgit repositoryにおいておき、組織内でansible-galaxyコマンドで共有する、というのができるようになりました。

さらに、-rオプションで、どこからどのロールを入れるかということをテキストファイルで指定できるようになったので、リポジトリに一個そのファイルを置いておくだけで、初期設定ができるようになっています。この点から考えても、ロールに分けることの重要性が分かるかと思います。

9章: Ansibleの高速化

ssh multiplexingとかです。EC2等の場合に、too long for Unix domain socketというエラーが返されることがある点について解説しているのは良いですね。pipeliningについても書いてあるのは良いです。

ファクトのキャッシュは、あんまり使ったことないですね。ファクトの収集は、通常のマシンだと(他の処理と比例して)そんなに時間がかからないので、キャッシュの不整合の方を気にしています。ただ、本当にちょっとしたtaskしか実行しない、あるいは遅いマシンの場合だと劇的に効く場合がありますので、「Ansible遅いんだけど」という方はためしてみるのもいいかと思います。

10章: カスタムモジュール

はい、カスタムモジュールです。Ansibleをちょっと深く使おうとするならば、モジュールの自作が一番です。YAMLでなんとかしようと頑張るのはナンセンスです。

この章では、Pythonでのモジュールの作り方に加えて、少しですがbashでのモジュールの作成方法も記載されています。Pythonでのモジュール作成はかなり充実したヘルパー関数が用意されているので、多くのことに対応できるため、本格的に作るならばpython一択となります。ただし、ちょっとした操作をモジュール化するのであれば、使い慣れた言語の方が良いですので、bashでの作成方法が書かれているのはとても便利だと思います。

11章: Vagrant

Vagrantとansibleプロビジョナの説明です。Vagrantで並列プロビジョンをやるやり方は知らなかったので勉強になりました。

12章: Amazon EC2

EC2です。とはいえ、AWSに限らずセキュリティグループやAMIの取得まで記載されています。この章のうち、tagとグループの関係については、頭に入れておいたほうが良いでしょう。あと、packerについても記載があるので、使ってみるのもいいかもしれません。

メモ: P.230の脚注がなにか編集段階のが残ったままになっている?

13章: Docker

Dockerです。この章では、AnsibleとDockerの関わりについて以下の二点があると書かれています。

  1. 複数のDockerを指定した順番で確実に起動するために使う
  2. Docker imageを作成する時にAnsibleを使う

このうち、1.に関しては、docker-composeのほうが良い気がします。2.についてですが、Ansibleを入れたコンテナを使う方法が記載されています。しかし、Ansible 2.0では、docker connection pluginが標準で添付されていることから、直接dockerのイメージを扱ったほうが良いでしょう。しかし、13.5.5で書かれているように、筆者はdockerイメージ作成はビルドプロセスの一部だという考えがあり、AnsibleでDocker Imageを構築はしていません。ぼくもこの考えに賛成です。

14章: Playbookのデバッグ

playbookのデバッグに役立つtipsが書かれています。ちなみに、ぼくが以前Software DesignでAnsibleに関する記事を書かせて頂いた時に、debugモジュールを真っ先に挙げました。これは、debugモジュールはデバグに必ず使うからです。

まとめ

本書はAnsibleを初めて使う人だけでなく、今実際に使っている人にもオススメの一冊です。ぜひ買いましょう。

]]>
Mon, 18 Apr 2016 00:00:00 +0900
http://tdoc.info/blog/2016/03/01/go_diet.html http://tdoc.info/blog/2016/03/01/go_diet.html <![CDATA[Goのバイナリサイズを削減する]]> Goのバイナリサイズを削減する

Goは1バイナリになってとても良いのですが、その1バイナリが結構大きいのがちょっとネックになる場合もあります。

The Go binary dietという記事を読み、Goのバイナリをダイエットすることにしてみました。

ldflags="-w"

最初に示されていたのは-ldflags="-w"によってDWARFのシンボルテーブルを生成しないようにする方法です。

% go build -ldflags="-w"

ldflags="-s"

次に、-ldflags="-s"によってデバッグのために使われるシンボルテーブルを全部生成しないようにする方法です。

% go build -ldflags="-s"

ここまではよくあるのですが、次に示されていたのはThe Ultimate Packer for eXecutablesを使う方法でした。

UPX

Wikipedia

寡聞にして存じませんでしたが、UPXは1998年からあるソフトウェアのようです。Windows/Linux(i386/ARM/MIPS)/Mac/*BSDとほぼすべての主要プラットフォームで動作します。ライセンスはGPL([注1])です。UPXは実行形式を保ったまま、バイナリを圧縮します。

実際の動作としては、7-Zipなどで使われるLZMAで圧縮しておき、実行時に伸長してメモリ上に直接書き出してから実行します。メモリ上の置き換えが出来ない場合は一時ファイルに書きだしてそれを実行する形式となります。

伸長に必要なコードはわずか数百バイトです。もちろん、伸長時にはCPUやメモリは必要となりますが、一回だけですしGoのバイナリ程度の大きさであれば問題になることはさほどないかと思います。

結果

手元のそこそこ大きいプロジェクトのコードを対象に試してみました。OSはMac OSで、go 1.6を使いました。

対象 バイト数
26MB (27090756)
"-w" 19MB (20414276)
"-s" 26MB (27090756)
"-w" + UPX(-9時) 5.2M (5443584)
"-w" + UPX(-1時) 6.4M (6684672)

あれ、 "-s"では変わってないですね…darwin環境ではでないのかななld周りのなにかだと思うのでそれはあとで追うとして、元々が26MBだったのが、5.2MBまで減りました。

圧縮にupx -9を使った場合、かかった時間は15.70秒でそこそこ時間がかかりますね。3回ほど実行してだいたい同じぐらいでした。伸長時は0.10秒ほどでした。もちろんメモリなどにも依存しますので、この結果は鵜呑みには出来ませんが、あくまで目安として。

さらにいうと、upx -1で圧縮した場合は 0.78秒しかかかりません。それでいて、6.4MBと充分な圧縮効率となりました。この辺りはターゲットとする環境に合わせて決めればいいと思いますが、-1で十分な気もします。

まとめ

Goのバイナリが大きい問題は、ldflagsとUPXを使うことである程度解決できるのではないか、という話でした。UPX知らなかったですけど、コード見るとかなりすごい感じですね。

[注1]ライセンスについて。UPXはGPLに例外事項をつけたライセンスをとっています。これは、GPLであるUPXがバイナリにリンクする形となるため、圧縮対象となるバイナリも通常はGPLになる必要があるのですが、リンクするUPXのコードを改変しないかぎりその必要はない、という例外条項です。ライセンスを確認していただけると分かるように、商用プログラムに対して適用できると明確に書かれています。
]]>
Tue, 01 Mar 2016 00:00:00 +0900
http://tdoc.info/blog/2016/01/07/lambda.html http://tdoc.info/blog/2016/01/07/lambda.html <![CDATA[AWS Lambdaで効率的にgoバイナリを実行する]]> AWS Lambdaで効率的にgoバイナリを実行する

最近LambdaとAPI Gatewayを結構使ってて、これはいいな、と思っていたところ、AWS LambdaでJavaとNode.jsとGoの簡易ベンチマークをしてみたという記事を見かけたので、関連した記事を書いてみます。

前提条件

AWS Lambdaではnodejsなどが使えますが、golangは(今のところ)使えません。従って、golangで書きたい場合は、上記記事にある通り、

nodejsを起動 -> nodejsがgolangのバイナリをchild_process.spawn で起動

というやりかたとなります。

そのため、一回一回のリクエストのたびにプロセスを立ち上げるコストが掛かり、毎回500msecほどかかってしまう、という事になってしまいます。

この問題点を解消するライブラリがlambda_procです。

lambda_proc

Lambdaは独自のコンテナで起動されています。起動したコンテナは一定時間存続し続け、リクエストのたびにそのコンテナが使いまわされます。では、一回一回goのプロセスを起動し直すのではなく、起動しっぱなしにしておけばgoの起動コストはなくなるのではないか、というやり方がlambda_procです。

nodeとgoのやりとりはstdin/stdoutを使います。具体的にはこのような感じです。

リクエスト
client -> node --(標準入力)--> go

リプライ
go --(標準出力)--> node -> client

lambda_procでは、node側でJSONを整形し、goに対して一行のJSONとして渡します (Line Delimited JSON)。go側のヘルパーライブラリがJSONを整形し、goのメイン関数に渡します。

返答は、適当なstructを返すとlambda_procのヘルパーライブラリがJSONに整形してnodeに返してくれます。

実際にベンチマークにgoのソースコードは以下となります。標準出力に書いてしまうとnode側に渡ってしまうので、ログは標準エラー出力に書く必要があります。

package main

import (
     "encoding/json"
     "log"
     "os"

     "github.com/aws/aws-sdk-go/aws"
     "github.com/aws/aws-sdk-go/aws/session"
     "github.com/aws/aws-sdk-go/service/dynamodb"
     "github.com/bitly/go-simplejson"
     "github.com/jasonmoo/lambda_proc"
)

// 標準エラー出力に書き出す
var logObj = log.New(os.Stderr, "", 0)

// 元記事のメイン部分の関数
func parse(jsonStr string) {
     js, _ := simplejson.NewJson([]byte(jsonStr))
     records := js.Get("Records")
     size := len(records.MustArray())

     for i := 0; i < size; i++ {
             record := records.GetIndex(i)

             logLn(record.Get("eventName").MustString())  // fmt.Printlnは使えない
             logLn(record.Get("eventId").MustString())
             logLn(record.Get("dynamodb").MustMap())
     }

     ddb := dynamodb.New(session.New(), aws.NewConfig().WithRegion("ap-northeast-1"))
     tableName := "mytest"
     keyValue := "test"
     attribute := dynamodb.AttributeValue{S: &keyValue}
     query := map[string]*dynamodb.AttributeValue{"id": &attribute}

     getItemInput := dynamodb.GetItemInput{
             TableName: &tableName,
             Key:       query,
     }

     obj, _ := ddb.GetItem(&getItemInput)
     logLn(obj)
}

// fmt.Printlnをして標準出力に書き込むと、js側でparseしてしまうので、標準エラー出力に書き出す
func logLn(a ...interface{}) {
     logObj.Println(a...)
}

// なにかstructを返さなければいけないのでダミーの構造体を作成。普通に書くと、むしろstructを返せたほうがいいでしょう
type Return struct {
     Id    string
     Value string
}

// メインとなる関数
func handlerFunc(context *lambda_proc.Context, eventJSON json.RawMessage) (interface{}, error) {
     parse(string(eventJSON))
     return Return{Id: "test", Value: "somevalue"}, nil
}

// mainではlambda_procに登録する
func main() {
     lambda_proc.Run(handlerFunc)
}

ベンチマーク

Lambdaには標準でテスト用のJSONが用意されています。今回はDynamoDB Updateのテスト用JSONを手元に保存しました。curlから叩ける用にLambdaのAPI Endpointを用意し、以下のスクリプトで0.5秒おきにcurlを10回起動しました。

for I in `seq 1 10`
do
curl -X POST -H "Content-type: application/json" --data @body.json https://hoge.execute-api.ap-northeast-1.amazonaws.com/prod/benchmark
sleep 0.5
done

これを実行すると、

Duration Billed Duration Used Memory
367.42 ms 400 ms 14 MB
36.92 ms 100 ms 14 MB
44.00 ms 100 ms 14 MB
46.05 ms 100 ms 14 MB
61.44 ms 100 ms 15 MB
50.48 ms 100 ms 15 MB

Duration Billed Duration Used Memory
393.30 ms 400 ms 14 MB
44.13 ms 100 ms 14 MB
47.99 ms 100 ms 14 MB
52.30 ms 100 ms 14 MB

という二つのログストリームがCloudWatchに出てきました。

このログから2つのコンテナが使われていることが分かります。また、初回は400msecと時間がかかりますが、以降のリクエストに対しては40msec程度しかかかっていません。もちろんメモリも最小限です。今回は10回しか呼び出しをしていませんが、もっと大量でも構いません。また、一回の実行制限時間を長くすればもっと長生きになり、起動コストはかなり無視できる範囲に収まると思います。

ログ出力には注意

上記コード中にも記載していますが、ログ出力にfmt.Printlnを使ってしまうと標準出力に書きだされてしまい、node側に伝わってしまいます。そのため、標準エラー出力に書き出すようにしています。これで解決はできますが、loggingライブラリを使う時は気をつけたほうがいいでしょう。

goのプログラムがシンプルになった

なお、今回lambda_procを使った副次的な効果として、goのプログラムがシンプルになりました。

普通のアプリケーションサーバーだと、HTTPを意識してcontextやらいろいろなことを扱う必要がありました。しかし、この方式では、標準入力/出力しか関係ありません。HTTPに関連することはすべてAWS Lambda (およびAPI Gateway)がまかないます。goは標準入出力だけを見ればよく、しかもその形式は標準的となったJSONです。

この制限により、実装する範囲を狭めることになり、非常に処理が書きやすくなりました。

また、テストもしやすくなりました。従来であれば、"net/http/httptest"を立てて、とか考える必要がありますが、標準入出力だけですみます。

まとめ

lambda_procを使うことで、AWS Lambda上でgoのプログラムを効率的に呼び出せることを示しました。これにより、たまに行う処理だけでなく、がんがんくるリクエストをさばく用途にもgoが使えるのではないか、と思います。

lambdaは無料でかなりのコンピュータ資源を使えるので、うまく使って費用を節約していきたいですね。

]]>
Thu, 07 Jan 2016 00:00:00 +0900
http://tdoc.info/blog/2015/12/16/gopsutil.html http://tdoc.info/blog/2015/12/16/gopsutil.html <![CDATA[CPUやメモリなどの情報を取得するgopsutilのご紹介]]> CPUやメモリなどの情報を取得するgopsutilのご紹介

Go Advent Calendar 2015の16日目です。

Pythonにはpsutilという、CPUやメモリなどの情報を取得するライブラリがあります。拙作gopsutilはこのpsutilをgolangに移植しようと始まりました。

gopsutilは、以下の特徴があります。

  • Linux/Darwin/FreeBSD/Windowsで動作します
    • もちろん、対応状況はかなり違います
  • (ほぼ) pure golangで実装されています。そのため、クロスコンパイルが容易です
    • ほぼ、というのはdarwinのCPU利用率だけcgoを使っています。cgoを使わない場合は単にnot implementedが返ってきます。
  • psutilにない情報も取れます
    • docker(cgroup)の情報だったり、仮想化状況だったり、好き勝手に機能を追加しています

gopsutilは1年半以上前からこつこつと開発を続けており、おかげさまで今ではgithubのスターが800以上を超えました。

また、

といったソフトウェアからライブラリとして使用されています。

使い方

使い方はREADMEに書いてありますが、以下の通りです。github.com/shirou/gopsutil/memなどをimportして、パッケージに置いてあるメソッドを呼び出すだけです。

import (
    "fmt"

    "github.com/shirou/gopsutil/mem"
)

func main() {
    v, _ := mem.VirtualMemory()

 // structが返ってきます。
    fmt.Printf("Total: %v, Free:%v, UsedPercent:%f%%\n", v.Total, v.Free, v.UsedPercent)

 // PrintするとJSON形式の結果が返ってきます
    fmt.Println(v)
}

これを実行するとこんな感じになります。

Total: 3179569152, Free:284233728, UsedPercent:84.508194%

{"total":3179569152,"available":492572672,"used":2895335424,"usedPercent":84.50819439828305, (以下省略)}

structとして取得できるので、あとは好きな様にいじって下さい。あるいは、PrintしてあげればJSONとして扱えますよ、という感じです。

取得できる情報

結構いろいろな情報が取れるのですが、その一部を紹介します。

  • CPU
    • CPU使用率、CPUのハードウェア情報
  • memory
    • メモリ使用率、スワップ使用率
  • disk
    • パーティション情報、I/O、ディスク使用率、ディスクのシリアル番号
  • host
    • ホスト名、起動時刻、OSや仮想化方式、
    • ログインユーザー情報
  • load
    • Load1, 5, 15
  • Process
    • 個々のプロセスのPIDや状態、起動プロセス名、メモリやCPU使用率など
  • Docker
    • コンテナ内部のCPU使用率やメモリ使用率など

要望があれば既存APIを壊さない範囲であればどんどん増やしていこうかな、と思っています。

中身

gopsutilは非常に泥臭いことをたくさんやっています。まず、pure goでいく、という大原則を立てているため、cgoは使えません。また、Linux/BSD/Windowsでは方式が大きく異なります。

Linux
procファイルシステムなど、ファイルベース
FreeBSD/Darwin
sysctl
Windows
DLL及びWMI

これらはcpu_darwin.goなどのようにファイル名で分けています。

Linux

基本的にテキストファイルベースなので結構楽ですね。

と思いきや、Linuxのバージョンによって取れる情報が違ったり、コンテナ内部では/sysが使えないのでパスを入れ替えられるようにする必要があるなど、細かな点が異なります。

また、ユーザー情報は/var/run/utmpでバイナリ(utmp構造体)で格納されていますので、ちゃんとparseしてあげる必要があります。このあたりは2015年6月のGoConで公開しました(発表はしてません)。

FreeBSD/Darwin

BSD系はsysctlコマンドで各種の情報が取得できます。sysctl vm.stats.vm.v_page_sizeでページサイズが取れたりですね。

ただし、sysctlコマンドで取得できるのはテキスト形式の情報だけです。Proc構造体の情報などはコマンドからは叩けないので、syscall.Syscall6などを使って叩きます。 (余談ですが、godocで出てくるのはLinuxのコードだけですので、Linux以外を知りたい場合はソースコードを読む必要があります)

mib := []int32{CTLKern, KernProc, KernProcProc, 0}
miblen := uint64(len(mib))

// まずlengthを0にして叩き、必要となるバッファ量を得る
length := uint64(0)
_, _, err := syscall.Syscall6(
    syscall.SYS___SYSCTL,
    uintptr(unsafe.Pointer(&mib[0])),
    uintptr(miblen),
    0,
    uintptr(unsafe.Pointer(&length)),
    0,
    0)

// 必要な情報を得る
buf := make([]byte, length)
_, _, err = syscall.Syscall6(
    syscall.SYS___SYSCTL,
    uintptr(unsafe.Pointer(&mib[0])),
    uintptr(miblen),
    uintptr(unsafe.Pointer(&buf[0])),
    uintptr(unsafe.Pointer(&length)),
    0,
    0)

ただし、Darwinはsysctlで取れる情報はFreeBSDに比べてかなり少ないので諦めたところもあります。

Windows

DLLを呼び出して情報を取得しています。

procGetDiskFreeSpaceExW := modkernel32.NewProc("GetDiskFreeSpaceExW")

diskret, _, err := procGetDiskFreeSpaceExW.Call(
     uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))),
     uintptr(unsafe.Pointer(&lpFreeBytesAvailable)),
     uintptr(unsafe.Pointer(&lpTotalNumberOfBytes)),
     uintptr(unsafe.Pointer(&lpTotalNumberOfFreeBytes)))

という感じですね。ただし、さすがにこれはいろいろツライので、github.com/StackExchange/wmiを使ってWMIを叩くようにしています。

type Win32_Processor struct {
    LoadPercentage            *uint16
    Family                    uint16
    Manufacturer              string
    Name                      string
    NumberOfLogicalProcessors uint32
    ProcessorId               *string
    Stepping                  *string
    MaxClockSpeed             uint32
}

func get() {
    var dst []Win32_Processor
    q := wmi.CreateQuery(&dst, "")
    err := wmi.Query(q, &dst)
    if err != nil {
        return ret, err
    }
    fmt.Println(dst)
}

性能

測ってはいませんが、外部コマンドを呼んだりなどを気軽にしているため、そんなに性能はでないはずです。ものすごい高頻度で実行するとホスト側に負荷がかかるでしょう。その点は使う側で適宜キャッシュするなどをして頂ければと思います。

まとめ

ホストのCPUやメモリなどの情報を取得するgopsutilの紹介をしました。

作り始めたのがgoを使い始めて間もないころであり、さらにいろいろなプラットフォームに対する知見は後から得たりしていたので、統一感がなかったりします。そのうちちゃんとしたいと思ってはいるのですが…

もしもgoでシステムの情報を得たいと思った場合には、gopsutilのことを思い出していただけるとありがたく思います。また、PRは随時お待ちしております。

]]>
Wed, 16 Dec 2015 00:00:00 +0900
http://tdoc.info/blog/2015/12/03/docker_connection_plugin.html http://tdoc.info/blog/2015/12/03/docker_connection_plugin.html <![CDATA[Ansible Docker Connection Pluginを使う]]> Ansible Docker Connection Pluginを使う

1年以上前の2014年4月にこんな記事を書きましたdocker containerに対して直接ansibleを実行するそれからいろいろあって、Ansible 2.0では標準でDocker Connection Pluginが入っています。(といってもぼくの実装ではありませんが)

Docker Connection Pluginとは

まず、Connection Pluginについて説明します。Ansibleでは通常SSHを使って対象となるホストに接続します。しかし、 Connection Plugin を使うことで、接続方式を切り替えることができます。

代表的なものはlocal connectionです。以下のように書くと、sshではなく、localhostでそのまま実行されます。sshのlocalhostと異なる点は、sshはまったく使わずに、そのままのユーザーがのまま実行する、という点です。開発時に便利ですね。

- hosts: all
  connection: local
  tasks:
    - file: path=/tmp/this_is_local state=directory

その他、以下のconnection pluginが用意されています。paramikoやwinrmは使ったことがある方も多いかと思います。

accelerate
accelaretモード(過去の遺産なので覚える必要なし)
chroot
chroot
funcd
Func: Fedora Unified Network Controller 経由
zone
SolarisのZone
jail
FreeBSDのJail
libvirt_lxc
virtのLXC
paramiko
sshのpython実装
winrm
Windows

この中の一つがdocker connection pluginです。

Docker connection pluginの利点

Docker Connection Pluginを使うことで、Dockerコンテナに対して直接Ansibleを実行できます。具体的にはdocker execでコマンド実行を、ファイルのコピーはdocker cpを実行します。Dockerコンテナの中にsshdを建てる必要はありません。

DockerfileによるBuildが一番シンプルであることは確かです。しかし、

  • Layerを増やさないために\で何行も増やしていく場合がある
  • templateがないため、複数種類のimageを作り分けるのがめんどう
  • 他はAnsibleで管理しているのに、ここだけDockerfileになると管理が分断されてしまう

などの理由で、Ansibleを使いたくなる場合がありますので、そういう場合に有用です。

なお、私はDockerfileで済ませられるのであればその方が良いと思います。Ansibleをわざわざ使う必要はないでしょう。しかし、複雑な構成になってくると、Ansibleの方が便利な場合もあると考えられるため、ここで紹介している次第です。

Docker connection pluginを使う

能書きはこのあたりにして、早速使ってみましょう。ほとんどの人はAnsible 2.0RC1を使っていると思いますので、新たなインストールは必要ないですが、万が一 1.9.4を使っている人はこちらからdocker.pyをダウンロードし、connection_pluginsというディレクトリを作成してその中に置きましょう。以下のような構成になります。

.
|-- connection_plugins
|   `-- docker.py
|-- hosts
`-- site.yml

また、 pipでdocker-pyをインストールしておきましょう。(ansible v2.0では必要ないです)

あとは以下のようにplaybookを書きます。

- name: Dockerコンテナを起動
  hosts: localhost
  connection: local
  vars:
    base_image: ubuntu:latest
    docker_hostname: test

  tasks:
    - name: Dockerコンテナを起動
      local_action: docker image={{ base_image }} name={{ docker_hostname }} detach=yes tty=yes command=bash
    - name: ホストを追加
      add_host: name={{ docker_hostname }}

- name: Dockerコンテナ内を構成
  hosts: test
  connection: docker   # ここで docker connectionを指定
  tasks:  # 好きなように書きます
    - file: path=/tmp/docker state=directory
    - file: path=/tmp/ansible state=directory
    - group: name=admin state=present
    - user: name=johnd comment="John Doe" uid=1040 group=admin
    - copy: src=site.yml dest=/tmp/ansible/

  post_tasks:
    - local_action: shell /usr/local/bin/docker commit {{ inventory_hostname }} ubuntu:ansible

この例のplaybookは以下の二つから構成されています。

  1. Dockerコンテナを起動
  2. 起動したDockerコンテナ内部を構成管理

1の方では、dockerモジュールを使って起動します。ここは普通にlocal接続です。2の方がDocker接続を使用しています。

重要なのは、connection: dockerという行だけが異なっており、そのほかは通常のPlaybookと何ら変わりないという点です。

最後にdokcer commitを実行して、イメージとして保存しています。それまでの箇所はすべて docker execなどで行われているため、保存されず、layerは全体で最後にdocker commitを実行した時に出来る一つだけとなります。これにより、Dockerfileで何行も続けたり、ということをしなくてすみます。

commitも自動化

先ほどの例ではpost_tasksとして、docker commitを実行しています。しかし、Ansible を使って Docker コンテナーをプロビジョニングするという記事では、callback pluginを使って毎回のtask実行毎にcommitをする例を示しています。

この方式はDockerfileによる方式と同じくlayerがたくさんできることになります。その代わり、キャッシュされるため、次回は高速になるという利点もあります。

RemoteのDockerホストを使う

Dockerホストは手元だけでなく、リモートでも構いません。

export DOCKER_HOST=tcp://192.168.0.10:4243

DOCKER_HOSTを環境変数で設定すれば、そのホストを経由してDockerコンテナにアクセスします。試していませんが、Swarmなどでもきちんと動くと思います。

これで、

  • インスタンス起動などのクラウドサービスの利用
  • dockerホスト自体の構築
  • dockerコンテナ/イメージの構築
  • ELBの付け外しなど、デプロイに必要な機能

のすべてがAnsibleで可能となります。

まとめ

この記事ではAnsibleからDockerコンテナを直接触れる Docker Connection Pluginを紹介しました。一つのpythonファイルを置くだけで、Dockerコンテナにたいして通常のsshホストと同じことができるようになります。また、Dockerホストはローカルだけでなく、リモートに対しても可能です。

最後に。

最初の方に述べましたが、Dockerfileで済ませられるのであればその方が良いでしょう。なんでもAnsibleでやりたくなる気持ちも分かりますが、Ansibleを使わなくてはならない理由はありません。適材適所、不要な苦労をしないために、もう一度考えましょう。

そして、そもそもDockerコンテナ内部が複雑な時点で間違っていると思います。golangをDockerでデプロイするで示したように、golangであれば、1バイナリだけ置けば動くため、「プロビジョニング」なんてものは存在しなくなります。現在はGoogleに転職したIanさんも(より)小さいDockerイメージを作ろうという記事を書いており、必要な最小限のファイルだけ置くのが理想です。

自動化をするまえに、「そもそも本当に必要なのか?」を考えましょう。

]]>
Thu, 03 Dec 2015 00:00:00 +0900
http://tdoc.info/blog/2015/10/09/aws_iot_mqttcli.html http://tdoc.info/blog/2015/10/09/aws_iot_mqttcli.html <![CDATA[AWS IoTにmqttcliからつなぐ]]> AWS IoTにmqttcliからつなぐ

mqttcliという、CLIで動くMQTTクライアントを開発し、公開しています。

AWS IoTに対して、このmqttcliから接続してみましょう。

mqttcliのダウンロード

mqttcli filesから、

  • Linux (arm/amd64)
  • FreeBSD (arm/amd64)
  • darwin (amd64)
  • Windows (amd64)

が用意してありますので自身のアーキテクチャにあったバイナリをダウンロードして下さい。その後、chmod u+x mqttcliと実行権限を付与して下さい。

AWS IoTでthingsを作る

  1. AWSコンソールからAWS IoTを開きます。
  2. Create ResourceからCreate Thingを選びます
  3. Nameを入力して、Createを押します
  4. 下の一覧に先ほど入力した名前のThingが出てくるので、選択し、右のタグからConnect a Deviceをクリックします。
  5. Connect a Deviceという画面が出てくるので、NodeJSを押し、Generate Certificate and Policyを押します
  6. 10秒ぐらい経つと、
  • Download Public Key
  • Download Private Key
  • Download Certificate

という三つのファイルをダウンロードするように指示されるので、全部ダウンロードしておきます。

  1. Confirm & Start Connectingを押します。すると、以下の様なJSONが表示されるので、コピーして、ファイルに保存しておきます。

    {
      "host": "A3HVHEAALED.iot.ap-northeast-1.amazonaws.com",
      "port": 8883,
      "clientId": "something",
      "thingName": "something",
      "caCert": "root-CA.crt",
      "clientCert": "2a338xx2xxf-certificate.pem.crt",
      "privateKey": "aad380efffx-private.pem.key"
    }
    
  2. root-CA.crtというファイルをAWS IoT SDKのここに書いてあるとおり、こちらのSymantecのページから取得してください。

  3. 先ほどダウンロードした三つのファイルとJSONファイル、そして、 root-CA.crt を同じディレクトリに置いて下さい。

以上で準備は終わりです。

AWS IoTに接続

ファイルを入れたディレクトリに移動し、 以下のようにmqttcliを起動します。-tでtopicを指定しますが、$をエスケープする必要があるかもしれません。--confで指定するのは先ほど保存したJSONファイルです。-dはデバッグ用です。

$ mqttcli sub -t "\$aws/things/something/shadow/update" --conf something.json -d
INFO[0000] Broker URI: ssl://A3HVHEAALED.iot.ap-northeast-1.amazonaws.com:8883
INFO[0000] Topic: $aws/things/something/shadow/update
INFO[0000] connecting...
INFO[0000] client connected

と出れば成功です。MQTTで接続できています。

Thing Shadowを更新

Thing Shadowを更新するには以下の様なJSONを送ります。

{
  "state": {
    "reported": {
      "from": "mqttcli"
    }
  }
}

mqttcliで送ってみましょう

echo '{"state": {"reported": {"from": "mqttcli"} } }"' | mqttcli pub -t "\$aws/things/something/shadow/update" --conf something.json -d -s

これで、AWS Consoleから見ると、stateが書き換わっているはずです。

このように、mqttcliとを使って、AWS IoTを触ることが出来ました。同様のことがmosquitto_subでもできることを確認しています。

さいごに

MQTTを直に叩くのではなく、AWS IoTのSDKを使いましょう。そうすればこんなふうにMQTTを意識する必要はありません。

]]>
Fri, 09 Oct 2015 00:00:00 +0900
http://tdoc.info/blog/2015/10/09/thing_shadows.html http://tdoc.info/blog/2015/10/09/thing_shadows.html <![CDATA[AWS IoTおよびThing Shadowsに関する雑感]]> AWS IoTおよびThing Shadowsに関する雑感

注意: この文章には思い込みが含まれています。

AWS IoTが発表されました。AWS IoTはManagedなMQTT Serverを用意し、運用が大変となるMQTT Serverを面倒見てくれる、というMQTTに対する評価がちらほら聞こえます。

しかし、違います。AWS IoTの本質は、Thing Shadowsの仕組みです。

とはいえ、まだちゃんと使ったわけではないですし、間違いがあったらご指摘をお願いします。

Thing Shadows

AWS IoTでは、Thingsというものが定義されています。このThingsには二種類が存在します。

Things
実際の物理的なモノ。デバイス
Thing Shadow
Thingsの状態をネットワーク(AWS)上に持たせたもの

Thingsはそのままですね。新しいのはThing Shadowです。これは、「物理的なデバイスを、仮想的にネットワーク上に写した(写像)もの」です。

ThingsとThing Shadowは1対1で結びついています。Thingsになにか変更があれば、Thing Shadowsにも変更が起こります。逆もまたしかり。Thing Shadowsに変更を起こした場合、それはThingsにも変更を起こすのです。

これはすなわち、

  • 物理空間
  • 仮想空間

とが一体化したことにほかなりません。

Thing Shadowの情報

Thing Shadowの実装は、単なるJSONです。:

{
    "state" : {
        "desired" : {
          "color" : "RED",
          "sequence" : [ "RED", "GREEN", "BLUE" ]
        },
        "reported" : {
          "color" : "GREEN"
        }
    },
    "metadata" : {
        "desired" : {
            "color" : {
                "timestamp" : 12345
            },
            "sequence" : {
                "timestamp" : 12345
            }
        },
        "reported" : {
            "color" : {
                "timestamp" : 12345
            }
        }
    },
    "version" : 10,
    "clientToken" : "UniqueClientToken",
    "timestamp": 123456789
}

ここ重要なのは stateとmetadataそれぞれが持つ、desiredreportedです。

1. Thingが更新された場合

物理的なものである、Thingが更新された場合、その情報はMQTTやHTTPSでThing Shadowに通知されます。

これに伴い、reportedの状態が更新されます。

2. Thing Shadowが更新された場合

Thing Shadowは、MQTTやHTTPによって、仮想空間上で更新できます。その場合、desiredの情報が更新されます。

この時、desiredreportedに差があった場合、 このThing ShadowをsubscribeしているThing(一つとは限らない)に対してメッセージが送られます。Thingはこれを受け取り、自分自身の情報を更新することができるのです。そして、更新出来たら、reportedとしてThing Shadowを更新します。


これらの動作により、ThingとThing Shadowsは同期が取れます。もしも、repoteddesiredが異なっている場合は同期がとれていない、ということになります。

さらに言うと、APIとして、 update/get/deleteが用意されており、それぞれに対してacceptedrejectedが用意されています。このため、Things Shadowsを更新しようとしたが、できなかった、ということも分かるようになっています。

MQTTとの違い

ここまで、ThingsとThing Shadowsについて説明してきました。ところで、AWS IoTでは、MQTTの以下の機能がありません。

  • Retain
  • Will
  • QoS2

なぜでしょうか。それはThing Shadowがあるからです。

  • RetainはShadowそのものです
  • Wllはそもそもオフラインという状態が存在しないので必要ありません
  • QoS2による、同期はShadowのdesired/reportedを使えば実現できます

AWS IoTはMQTTというメッセージプロトコルではなく 「状態」を扱うためのものである、という点を重要視するといいのではないでしょうか。

まとめ

AWS IoTを単なるManagedなMQTT Serverとだけ捕らえてしまうと、本質を見誤る気が致します。仮想空間と物理空間の融合、Internet of Things、とはなにか、というのをもう一度見つめなおしてみると面白いかもしれません。

また、今回はまだRuleに関して踏み込んでいません。Thing Shadows/Ruleを組み合わせることにより、人間が介在しない、Machine to Machine、 Things to Things の世界を作ることが出来るはずです。

という、あまりこのblogには書いていない、ちょっとエモーショナルな話でした。(実はこういう研究を10年以上前にやっていたのです。それがここまで広がったのか、とつい嬉しくなり、勢いに任せて書いた次第です)

]]>
Fri, 09 Oct 2015 00:00:00 +0900
http://tdoc.info/blog/2015/08/21/webdbpress.html http://tdoc.info/blog/2015/08/21/webdbpress.html <![CDATA[WEB+DB PRESS vol88にMQTTの記事を書きました]]> WEB+DB PRESS vol88にMQTTの記事を書きました

2015年8月22日発売のWEB+DB PRESS vol88速習 MQTTというタイトルでMQTTに関する記事を書かせていただきました。

内容は以下の通りです。

  • MQTTとは
  • MQTTの利用場面
  • MQTTの特長
  • その他のプロトコルとの比較
  • MQTTを使ってみる

速習、というタイトルの通り、利用場面や特長の説明がメインの、MQTTという聞きなれないプロトコルの内容を一通り説明する内容になっています。一応MQTTを使ってみる、という箇所でMQTTを使うアプリをpythonで実装してみていますが、eclipse pahoのおかげもあり、非常に短い行で実装が完了しており、分量は正直あんまりです。

最近すっかりバズワード化してしまい、「MQTTはIoT時代の主役となるプロトコルだ」「MQTTで全部できる」というような言説をたまに目にするようになってきました。今回の内容は、MQTTの実情をなるべく正確に伝えることを目指しました。MQTTの使い道は限られたものであり、もちろん完全に適した利用形態もありますが、他のプロトコルを使う方がいい場合も多々あると考えています。その辺りを過不足なく伝えられたらと思います。

Sphinx InDesign Builder

さて、本題です。

今回、reStructuredText(rst)形式で執筆しました。しかし、rst形式ではいろいろそのあとの作業が発生することは分かっていたので、Sphinx Indesign BuilderというSpinx拡張を作成しました。

% pip install sphinxcontrib_indesignbuilder

でインストールした後、conf.pyにて

extensions = ['sphinxcontrib.indesignbuilder']

と書けば準備は完了です。

あとは、

% sphinx-build -b singlewebdb -d build/doctrees source build/singlewebdb

とすれば、build/singlewebdbにWEB+DB PRESSで使用するためのInDesignのXMLファイルが作成されます。singleと付いているのは一つのxmlファイルにまとめる場合で、一つにまとめないことも可能です。WEB+DB PRESSの編集さんはこのXMLをInDesignに流しこみ、図などのフロート要素を配置すればPDFが作成できる状態になります。md2inaoを使うのとほぼ変わらない手間となったとのことです。(ただし、md2inaoで実現できる機能のうち、現時点では今回の原稿に必要であった機能のみ実装している点に注意)

実際には、フロート要素の配置には人力が必要なため、rst形式ファイルからPDFまで一貫したフローにはなっていません。そのためにはInDesgin力がかなりいろいろ必要そうです。

なお、webdbと付いているように、今回作成したのはWEB+DB PRESS用のsphinx拡張です。他の本や雑誌ではスタイル命名規則が異なるため、そのままでは適用できません。しかし、今回作成した拡張を参考にすれば、他のスタイル命名規則に沿った別の拡張を実装するのはかなり簡単だと思います。もしかしたら、ある設定ファイルを読みこませればそれだけでそのスタイル命名規則に沿ったXMLを書き出すことが出来るかもしれません。そうなれば実装は必要無くなります。

reStructuredText(rst)形式は、そのままの形式でも可読性が高いという点も重要ですが、拡張ポイントが決まっていることによる高い拡張性が魅力です。例えば「この文章のこの部分は特別にこういう表現にしたい」という要望に対しても柔軟に応えられます。これにより、表現力は他の記述形式に比べて非常に高くなっています。

まとめ

  • WEB+DB PRESS vol88にMQTTの記事を書きました
    • MQTTのできること、できないことを一通り解説する内容です
  • Sphinx InDesign BuilderというSphinx拡張を実装しました
    • reStructuredText(rst)形式 で脱稿しました

WEB+DB PRESS vol88はモバイル開発やLINEの開発、Elixirについての記事も載っていますのでぜひ買ってください。

]]>
Fri, 21 Aug 2015 00:00:00 +0900
http://tdoc.info/blog/2015/08/02/edison_golang_ble.html http://tdoc.info/blog/2015/08/02/edison_golang_ble.html <![CDATA[EdisonでgolangからBLEを扱う]]> EdisonでgolangからBLEを扱う

Intel Edisonが人気です。特に、WiFiとBLEが両方とも使えるのは非常にIoT向きといえるでしょう。

Edisonには標準でNodeJSを使った開発環境が搭載されていますが、やはりここはgolangで扱いたいところです。

paypal/gatt

GolangでBLEを扱うには、github.com/paypal/gattが最適です。このライブラリはBLEのperipheralの動作などからすべてgolangで実装しており、pure golangで動作します。

以下にサンプルプログラムを記載します。この例では、 mainでgatt.NewDeviceでdeviceを作り、onStateChangedonPeriphDiscoveredという二つの関数をハンドラに登録するだけで、BLE機器の探索が行えます。

func onStateChanged(d gatt.Device, s gatt.State) {
     fmt.Println("State:", s)
     switch s {
     case gatt.StatePoweredOn:
             fmt.Println("scanning...")
             d.Scan([]gatt.UUID{}, false)
             return
     default:
             d.StopScanning()
     }
}
func onPeriphDiscovered(p gatt.Peripheral, a *gatt.Advertisement, rssi int) {
     fmt.Printf("\nPeripheral ID:%s, NAME:(%s)\n", p.ID(), p.Name())
     fmt.Println("  Local Name        =", a.LocalName)
     fmt.Println("  TX Power Level    =", a.TxPowerLevel)
     fmt.Println("  Manufacturer Data =", a.ManufacturerData)
     fmt.Println("  Service Data      =", a.ServiceData)
}
func main() {
     d, err := gatt.NewDevice(option.DefaultClientOptions...)
     if err != nil {
             log.Fatalf("Failed to open device, err: %s\n", err)
     }
     // Register handlers.
     d.Handle(gatt.PeripheralDiscovered(onPeriphDiscovered))
     d.Init(onStateChanged)
     select {}
}

paypal/gattを使うことで、golangから簡単にBLEを扱えるのですが、一つ困ったことがあります。

paypal/gattは、HCI USER CHANNELを前提としており、これはLinux 3.14以降でのみ搭載されています。つまり、Linux 3.10で動いているEdisonでは、paypal/gattは動作しない、ということになります。

noble

しかし、 NodeJSで動作するnobleは動くよな、と思い調べていくとnobleは以下の二つの小さなヘルパープロセスをインストール時にbuildし、実行時に使っていることが分かりました。

nobleをインストールすると、以下の二つのバイナリができあがっています。

  • node_modules/noble/build/Release/hci-ble
  • node_modules/noble/build/Release/l2cap-ble

その名前の通り、hci-bleがHCIを、l2cap-bleがL2CAPを扱っています。この二つはBluezのライブラリを直接linkしているため、kernel 3.10でも動作します。

noblechild

ここまで調べたところで、思いつきます。

「nobleのヘルパープロセスを使えばPure golangでもkernel 3.10でBLE扱えるんじゃね」

ということで作ったのがnoblechildです。

noblechildは、nobleのヘルパープロセスを起動し、nobleと同じようにヘルパープロセスを扱うことで、Pure golangからkernel 3.10でもBLEを扱えるようになります。

使い方

  1. nobleをインストールします。
  2. NOBLE_TOPDIR環境変数を、nobleをインストールしたディレクトリのトップに設定します。

以上です。あとは、ほぼpaypal/gattと同じインタフェースを提供していますので、そのまま使えます。

func main() {
     d, err := noblechild.NewDevice(DefaultClientOptions...)
     if err != nil {
             log.Fatalf("Failed to open device, err: %s\n", err)
     }
     d.Handle(noblechild.PeripheralDiscovered(onPeriphDiscovered))
     d.Init(onStateChanged)
}

まとめ

Intel EdisonでgolangからBLEを扱えるようにするnoblechildというライブラリを作成しました。

ぶっちゃけハックなので、筋は良くありません。Edisonが3.14に上がれば直接paypal/gattを使えるので、それまでの間のつなぎ、という位置づけと考えています。

golangでBLEを扱えると便利ですね。弊社も関連しているMQTT ゲートウェイのfujiにも搭載し、問題なく動作していますので、そのうち公開したいと思います。

お仕事募集中

ツキノワ株式会社では、BLEやMQTT、Golangに関するお仕事を募集しております。お気軽にお問い合わせください。

]]>
Sun, 02 Aug 2015 00:00:00 +0900
http://tdoc.info/blog/2015/06/22/gocon_psutil.html http://tdoc.info/blog/2015/06/22/gocon_psutil.html <![CDATA[CのヘッダファイルからGoの構造体を生成する]]> CのヘッダファイルからGoの構造体を生成する

Gocon 2015で発表しませんでした。