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を使ったほうが良いかと思います。

書評: 初めての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を初めて使う人だけでなく、今実際に使っている人にもオススメの一冊です。ぜひ買いましょう。

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のコードを改変しないかぎりその必要はない、という例外条項です。ライセンスを確認していただけると分かるように、商用プログラムに対して適用できると明確に書かれています。

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

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は随時お待ちしております。

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イメージを作ろうという記事を書いており、必要な最小限のファイルだけ置くのが理想です。

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

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を意識する必要はありません。

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年以上前にやっていたのです。それがここまで広がったのか、とつい嬉しくなり、勢いに任せて書いた次第です)

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についての記事も載っていますのでぜひ買ってください。

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に関するお仕事を募集しております。お気軽にお問い合わせください。