ファニーボーン

プログラムなどについて書いていこうと思います。

パーセプトロンの実装

概要

先日、「タコス DE 機械学習」と名打った、機械学習初学者向けの勉強会で発表する機会がありました。 勉強会の主旨は、機械学習の基本的な分類器であるパーセプトロンを好きな言語で実装しながら、タコスを食べようという、楽しげな勉強会でございました。 かくゆう私も、機械学習を学習し始めたばかり。発表内容は、Pythonでの開発環境の説明とアイリスデータを用いたパーセプトロンの実装例の紹介です。本記事では、その発表をまとめます。 connpass.com

パーセプトロンとは

勉強会でも発表された、こちらの資料が分かりやすいので是非お読みください。

www.slideshare.net

Pythonでの実装について

勉強会では、Pythonに限らず、ScalaJavaRuby等さまざまな言語で参加者はパーセプトロンを実装しました。 私はPythonでの実装に関して発表しました。 方々で語られていますが、以下の点で機械学習の初学者にとってPythonはオススメです。

  • ライブラリが豊富
  • リファレンスが豊富
  • Jupyter Notebookがイケてる

この辺のこと発表した資料です。

タコスで機械学習 Python編

で、こちら実装例。

https://github.com/yamatsuka-hiroto/ml/blob/master/perceptron_iris.ipynb

簡易のため、アイリスデータのsetosaとversicolorの額の長さ、幅をもとに分類してます。

まとめ

今回は、Pythonでの開発環境、パーセプトロンの実装を発表しました。

余談

普段、Golangを使っているので、Golangで同様のことをするにはどうするかを検討しました。

gonum/matrix

PythonにはNumPyなど、便利な行列演算ライブラリがあります。本当便利。 Golangにはないかなあと思っていたのですが、相当するものがありました。 基本的な行列演算はできそう。

GitHub - gonum/matrix: Matrix packages for the Go language.

gonum/plot

Pythonのmatplotlibに相当するのものです。

GitHub - gonum/plot: A repository for plotting and visualizing data Example plots · gonum/plot Wiki · GitHub

Jupyter Notebook

Jupyter Notebook自体は40以上の言語で使え、もちろんGolangの使用できます。 github.com

インストールして使ってみたのですが、 グラフ表示がNotebook内できなかったりで、ちょっと微妙でしたね。。

Vaultの本番導入を見据えて(2)

概要

前回の記事では、ローカル環境でvaultのbackgroundをZookeeperに設定し、基本的な操作を行いました。 なんだか、便利そうだけどよく分からないVault。 今回は、実運用を想定して、Auth Backendを App ID にして、tokenの動的な発行を試しました。

Auth Backendって何?

Auth Backendとは、ユーザーの認証やアクセス制限をを行うVaultのコンポーネントです。 (https://www.vaultproject.io/docs/auth/index.html) 現状は以下のAuth Backendを使用して認証を行うことができます。 GitHub・AppID・LDAP・MFA・TLS Certificates・Tokens・Username&Password

この中でも、今回、App ID 注目しました。 その理由としては、App ID のbackendは 、個々のサーバー/コンテナに動的に Vaultとの認証を行うことを想定した機構であり、クラウド環境と相性が良さそうだからです。

準備

前回同様、ZookeeperをBackendにして、Vaultを起動し、unsealの状態にしておきます。

* 完全に余談ですが、前回に馬鹿なことをやってしまっていたことに気付きました。 前回、発行したrootのtokenを書き止めておくのを忘れいました。 そのため、seal状態のVaultさんはうんともすんとも言ってくれず、 結果、前回作成したZookeeperのvaultデータを全て消さなければならなくなりました。。 皆さんも、まあないでしょうけど、気をつけて下さいね。

auth のAPIからapp_iduser_id を登録します。

app_id の登録 app_id: hogehoge

$ curl \
    -X POST \
    -H "X-Vault-Token:$VAULT_TOKEN" \
    -d '{"value":"root", "display_name":"demo"}' \
    http://localhost:8200/v1/auth/app-id/map/app-id/hogehoge

user_id の登録 user_id: fugafuga

$ curl \
    -X POST \
    -H "X-Vault-Token:$VAULT_TOKEN" \
    -d '{"value":"hogehoge"}' \
    http://localhost:8200/v1/auth/app-id/map/user-id/fugafuga

app_iduser_id から token を発行

$ curl \
    -X POST \
    -d '{"app_id":"hogehoge", "user_id": "fugafuga"}' \
    "http://127.0.0.1:8200/v1/auth/app-id/login"

レスポンス

{
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": null,
  "warnings": null,
  "auth": {
    "client_token": "10921cd8-001c-25cd-2e5c-2a9c2fa43e16",
    "policies": [
      "root"
    ],
    "metadata": {
      "app-id": "sha1:3b2c6c10d0e78072d14e02cc4c587814d0f10f3a",
      "user-id": "sha1:39975bb0ba31825c4fdd3de775dd468081b3522b"
    },
    "lease_duration": 0,
    "renewable": true
  }
}

client-token としてtokenが発行されました。 簡単ですね。

いつtokenを発行するか

VaultのサイトAuth Backend: App ID - Vault by HashiCorp に想定される使い方があったので、手順まとめてみます。

  1. appIDを登録し、権限を与える。(上記①。例ではroot権限)
  2. configuration management service にappIDを保存する。
  3. cloud-init system がマシン毎の user_idを登録する。 (上記②)
  4. マシンを起動。マシンはconfiguration management serviceから appID を取得。 割り振られたuser_id でvaultと認証を行う。(上記③)

ん。。。なんか分かりそうでわからない。 クラウド環境でいろいろ試してみます。

今回のまとめ

前回、今回とローカルで試しましたが、Vault自体がクラウド環境を想定して設計されているぽいので、 クラウド環境で実環境に即した使い方を模索します。

Vaultの本番導入を見据えて

概要

タイトルは今にもVaultを本番導入する勢いのようですが、触ってみた系の記事です。
最近、遅ればせながらDevOps界の雄 Hashicorpさんが開発しているVaultが気になっていました。
今回は、業務で実際導入するにあたって、試しにローカル環境でZookeeperをbackendに設定し、基本的な操作を行いました。

動機

例えば、ソースの中にDBのuser/passwordやAPIキーなどをベタ書きしちゃってることありません?
しかも、それぞれのプロジェクト毎にいちいち、その情報書いたりしてません?
ぼくは、めちゃくちゃあります。むしろ、それしかない。
心苦しくはあったのですが、良さそうな手もなかったので目をつむっていました。
Hashicorpが開発するVaultを導入することで、そういった情報を管理しちゃおうっていうのが動機です。

Vault とは

機密情報を安全に管理するツールです。
2015年4月末にリリースされまして、最近 、0.5 がリリースされました。
基本的な機能としては、以下です。(参考:Introduction - Vault by HashiCorp)
・ Secure Secret Storage(安全に機密情報を保持)
・ Dynamic Secrets(動的に機密情報を管理)
・ Leasing and Renewal(機密情報の貸与と更新)
・ Data Encryption(データの暗号化)
・ Revocation(無効化)

入門

以下のチュートリアルでVaultの基本操作が大体わかりました。
Vaultのバージョンが上がって、若干異なるところもありますが、
日本語訳とても助かりました。
pocketstudio.jp

backendをZookeeperに

さて、入門が終わったところでとりあえず、backendをZookeeperに設定してみます。
なぜ、Zookeeperかと言いますと、業務でVaultを導入するにあたり、
既存のシステムで実績のあるZookeeperで使い始めようかなという構想。
Hashicorp的にはやはり、自社製品のConsulをbackendに使用するがmost recomendedだそうです。(Server Configuration - Vault by HashiCorp)

サンプルとしては、Consulの場合しか載っていなかったのでZookeeperでは以下のようにbackendを設定しました。
Jsonとも互換性のあるHCL(HashiCorp Configuration Language)で記述します。

example.hcl

# backendを"zookeeper"を指定
backend "zookeeper" {
      # localのZookeeperのアドレス
      address = "192.168.12.11:2181"
      # ZookeeperのNodeを指定
      path = "vault"
}

# Vaultサーバー
listener "tcp" {
   address = "127.0.0.1:8200"
    tls_disable = 1
}

さて、これで以下のコマンドを実行。

$ vault server -config=example.hcl

が、しかし、下記のようなえらーが発生。

Error initializing core: missing advertisement address

advertisementのaddressがないとのこと。
`VAULT_ADVERTISE_ADDR`の設定が必要でした。

$ export VAULT_ADVERTISE_ADDR='http://192.168.12.11:2181'

もう一度、以下を実行するとVaultサーバーが起動しました。

$ vault server -config=example.hcl

次は、実際にVaultで設定した値がZookeeperに書き込もれるかの確認。
チュートリアルでの同様に`vault init`を実行し、アンシールも実行。
vault authで初期化時のtokenを入力すればrootユーザとして操作できます。

試しに以下を実行します。

$ vault write secret/hello value=world

Zookeeperを確認すると、/vault/logical/{暗号化されたパス}/_hello のChildrenに
暗号化されたデータを確認することができました。
もちろん、以下で暗号化した値を取得できます。

$ vault read secret/hello
Key           	Value
lease_duration	2592000
value

所感

backendをZookeeperに設定し、ローカル環境でVaultの基本操作を行いました。
あとは、本番導入するにあたって、各サーバーとVaultの認証をどうしようとかも考えなきゃですね。
触ってみて感じたのですが、Vaultはかなり柔軟なので 、これからどんどん導入の場面が増えるのかなと思っています。

gRPC x Go言語 x Unixドメインソケット

概要

最近の業務でのこと。
Go言語で書かれたサービスが色々なロジックを持つようなってきたので、俗にいうマイクロサービスへの分割を行いました。
分割したサービス同士の通信には、速い速いと噂のgRPC(http://www.grpc.io/)を適用しました。分割後のサービスは同じサーバー内で稼働させることに。
それなら、通信はデフォルトのTCPでなく、Unixドメインソケットでプロセス間通信をしたほうが速いんじゃね?ということで、今回はGo言語でgRPC使う際に、Unixドメインソケットでプロセス間通信する実装方法、また、TCPとのパフォーマンス比較などを書きます。

実装方法

Unixドメインソケットを使用は、コネクションの取得時に指定します。
何も指定しない場合は、以下のような感じ。

// デフォルトでTCP.
conn, err := grpc.Dial("127.0.0.1:9000", grpc.WithInsecure())

grcp.Dialは、DialOptionを指定してコネクションを設定することができます(grpc - GoDoc)。
例えば、WithTimeoutでタイムアウトを、WithCodecでメッセージのコーデックを設定することができます。Unixドメインソケットを使用する場合は以下のように、WithDialerを用いてnet.dialerを指定します。

// dialer を作成して grpc.WithDialer(dialer)でDialオプションを追加する.
dialer := func(a string, t time.Duration) (net.Conn, error) {
	return net.Dial("unix", a)
}
conn, err := grpc.Dial("/tmp/test.sock", grpc.WithInsecure(), grpc.WithDialer(dialer))

パフォーマンス

ローカル環境(MBP15, 2015)で簡単なサーバとクライアントを実装して、10,000回リクエストした場合のレスポンスタイムを比較しました。ソースは末尾。
比較のために、1コネクション、1クライアントで固定。
結果から言うと、案の定、Unixドメインソケットの方が速かったです。TCP: 115 μs / req に対し、Unixドメインソケット: 90 μs /req。

所感

現在、gRPCを用いてサービスを分割したものが、本番環境で動いています。リリース前には負荷試験などを行い、分割前と後で性能を比較しました。
結果は、サービスを分割することにより、ほとんど性能の劣化はなかったです。
gRPCを調べていると、etcdでも使われている様子。関連の記事も面白かったので、etcdのソースを読んでみようと思います。

検証で使ったソース

protoファイル

syntax = "proto3";

package hello;

service Hello {
	rpc SayHello (HelloRequest) returns (HelloReply){}
}

message HelloRequest{
	string name = 1;
}

message HelloReply {
	string message = 1;
}

server側

var network = "unix"
var address = "/tmp/grpc_test.sock"

type server struct{}

// リクエスト(Name)を受け取り、レスポンス(Message)を返す
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	return &pb.HelloReply{Message: fmt.Sprintf("Hello, %s.", in.Name)}, nil
}

func main() {
	lis, err := net.Listen(network, address)
	if err != nil {
		panic(err)
	}
	defer func(){
		if network == "unix" {
			_, err := os.Stat(address)
			if err == nil {
				if err := os.Remove(address); err != nil {
					panic(err)
				}
			}
		}
	}()


	s := grpc.NewServer()
	pb.RegisterHelloServer(s, &server{})
	s.Serve(lis)
}

client側

var network = "unix"
var address = "/tmp/grpc_test.sock"

var requestNum = 100000

func main() {
	// コネクション数は1
	conn, err := genConn(network, address)
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	// クライアント数も1
	client := pb.NewHelloClient(conn)

	// 名前の作成(name_i)
	names := make([]string, requestNum)
	for i := 0; i < requestNum; i++ {
		names[i] = fmt.Sprintf("name_%d", i)
	}

	st := time.Now()
	for i := 0; i < requestNum; i++ {
		_, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: names[i]})
		if err != nil {
			panic(err)
		}
	}
	tt := time.Since(st)
	pt := tt / time.Duration(requestNum)
	fmt.Println(pt)
}

// コネクションを生成
func genConn(network, address string) (*grpc.ClientConn, error) {
	switch network {
	case "tcp":
		// Dial を指定しない場合はでデフォルトでTCP.
		return grpc.Dial(address, grpc.WithInsecure())

	case "unix":
		// dialer を作成して grpc.WithDialer(dialer)でDialオプションを追加する.
		dialer := func(a string, t time.Duration) (net.Conn, error) {
			return net.Dial(network, a)
		}
		return grpc.Dial(address, grpc.WithInsecure(), grpc.WithDialer(dialer))
	default:
		panic("invalid network")
	}
}

メディアサーバーKurento+Node.jsを動かしてみる

概要

オープンソースのWebRTCメディアサーバー・KurentoをGCE上に構築し、チュートリアルを試してみました。 構築手順等を書いていきます。

動機

昨年末、社内のキャンペーンで使用する 生放送配信システムを自前で作ろうという話になりました。 当初、WebRTCを用いた1対多数の配信システムを手元のMBPで完結させて配信していた。 しかし、接続数が20を超えてくると配信しているMBPにかなりの負荷がかかった。 配信に駆り出された使用されていない老MBPはお死にになられました。 ということで、メディアサーバーを立てて、MCUを実現したいなと思ったのでとりあえず、勉強がてらKurentoを触ってみました。

Kurentoとは

KurentoオープンソースのWebRTCメディアサーバーです。多人数でのテレビ会議や、生放送の配信などに使え、録画の機能もあるようです。JavaJavaScriptをクライアントとして使用できます。内部はCで書かれているようです。 今回は、Node.jsでチュートリアルを動かしてみました。

KurentoとWebRTCに関して、こちらの資料が勉強になりました。

www.slideshare.net

環境

GCE環境

マシンタイプ:n1-standard-1(vCPU x 1、メモリ 3.75 GB) → メディアサーバーと使用するにはスペック的にかなりショボいかと。。。

OS: Ubuntu 14.04 LTS (64 bits) → Kurento Media Serverを動かす必須環境のようです。

Kurento Media Server のインストール

 echo "deb http://ubuntu.kurento.org trusty kms6" | sudo tee /etc/apt/sources.list.d/kurento.list
wget -O - http://ubuntu.kurento.org/kurento.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install kurento-media-server-6.0

kurento-media-serverが起動しています。

STUNサーバーの設定

STUNとは、異なるNAT環境下どうしでもWebRTCを実現する仕組みです。STUNサーバーはNATの外側から見える端末の情報(グローバルIPUDPのポート)を教えてくれます。取得した自らの情報をシグナリングサーバーを用いて、端末間で交換します。今回、シグナリングサーバーは、後ほど出てくるNode.jsのサーバに含まれています。

STUNサーバに関しては、チュートリアルのページに利用可能なサーバーのIPとポートが掲載されていたので 、(http://www.kurento.org/docs/6.1.0/installation_guide.html)そちらを使用させていただきました。 下記ファイルを修正。 /etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini

stunServerAddress=54.172.47.69
stunServerPort=3478

TURNサーバーに関しては、設定していません。

Tutorial: Hello Worldを動かす

チュートリアルそのまま http://www.kurento.org/docs/6.1.0/tutorials/node/tutorial-1-helloworld.html

Node.jsとBowerのインストール

curl -sL https://deb.nodesource.com/setup | sudo bash -
sudo apt-get install -y nodejs
sudo npm install -g bower

チュートリアルのソースを落としてくる

git clone https://github.com/Kurento/kurento-tutorial-node.git
cd kurento-tutorial-node/kurento-hello-world
npm install
npm start

この際、server.jsでは下記のようになっているため、 GCEのインスタンスのポートを開けておく必要があります。 今回は、そのまま8443と8888を解放しました。

var argv = minimist(process.argv.slice(2), {
    default: {
        as_uri: 'https://localhost:8443/',  // アプリケーション用
        ws_uri: 'ws://localhost:8888/kurento' // シグナリング用
    }
});
https://<インスタンスのIP>:8443/

上記にアクセスし、startをクリックすると、ウェブカメラからの映像とKurento Media Serverと通して配信された映像が映し出されるかと思います。startを押しても映像が映らない場合は、一度stopしてもう一度試すと、映し出されました。

感想

その他のチュートリアルも触ってみたのですが、1対1、1対多数、多数対多数など、拡張性が容易そうです。 また、画像処理を含むチュートリアルを動かしてみると、配信の時間のズレがかなりあったので、サーバーのスペックがある程度必要そうです。