きみはねこみたいなにゃんにゃんなまほう

ねこもスクリプトをかくなり

gRPC で go-proto-validators を .proto に import したら grpc_cli がうまく使えない

(=˘ ꒳ ˘=) 最近 gRPC を始めたけどパス解決周りが Protocol Buffer 層も相まって複雑で混乱する...

hello.proto に簡単なバリデーションを組み込んで Go で吐き出して grpc_cli で動作確認しようとしたらハマって、そしてそのまま解決していない件になります。バリデーション処理ではなく主に Protocol Buffer の import 周りの試行錯誤のお話になります。

素朴に go-proto-validators を import したところ...

今回対象にする hello.proto は以下のようなものです。 go-proto-validators/validator.protoimport しています。

syntax = "proto3";
package hello;

import "github.com/mwitkow/go-proto-validators/validator.proto";

service GreetingService {
  rpc Hello(GreetingRequest) returns (GreetingResponse);
}

message GreetingRequest {
  string name = 1 [(validator.field) = {string_not_empty: true}];
}

message GreetingResponse {
  string message = 1 [(validator.field) = {string_not_empty: true}];
}

これを protoc して Go ライブラリを吐き出してサーバを実装して grpc_cli で叩いたところ、症状としては以下のようになりました。

  • grpc_cli ls は動く
  • grpc_cli ls -l は動かない
  • grpc_cli call ... も動かない
$ grpc_cli ls localhost:10000
grpc.reflection.v1alpha.ServerReflection
hello.GreetingService

$ grpc_cli ls -l localhost:10000
[libprotobuf ERROR google/protobuf/descriptor.cc:3592] Invalid proto descriptor for file "api/hello.proto":
[libprotobuf ERROR google/protobuf/descriptor.cc:3595]   api/hello.proto: Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors.
filename: grpc_reflection_v1alpha/reflection.proto
package: grpc.reflection.v1alpha;
service ServerReflection {
  rpc ServerReflectionInfo(stream grpc.reflection.v1alpha.ServerReflectionRequest) returns (stream grpc.reflection.v1alpha.ServerReflectionResponse) {}
}

$ grpc_cli call localhost:10000 Hello "name: 'test'"
[libprotobuf ERROR google/protobuf/descriptor.cc:3592] Invalid proto descriptor for file "api/hello.proto":
[libprotobuf ERROR google/protobuf/descriptor.cc:3595]   api/hello.proto: Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors.
Method name not found

どれも github.com/mwitkow/go-proto-validators/validator.proto が見つからないと言われていますね。

gRPC や Protocol Buffer 周りのパス解決をよく理解しておらず、さらにリフレクション経由というローカルとはいえネットワークを噛ませたパス解決がどうなるかも相まって二重三重で混乱します。

とりあえずリフレクションを無効化して試行錯誤する

問題を簡略化したいので、とりあえずリフレクションはコメントアウトして grcp_cli --protofile を直接指定するやり方で実行してみます。

https://github.com/grpc/grpc/blob/master/doc/command_line_tool.md#call-a-remote-method の「User local proto file」のやり方です。

grpc_cli にも --proto_path を指定できるみたいですが protoc のように複数指定してもうまく行かず、結局 .proto のパスを同一起点から解決できればいいんだろうということで、プロジェクトルートから依存パッケージを辿れるような symlink を張ったら動くことには動きました。

$ echo $GOPATH
/Users/asa-taka

$ pwd
/Users/asa-taka/src/github.com/asa-taka/hello-validated-grpc

$ tree
.
├── Makefile
├── api
│   ├── hello.pb.go
│   ├── hello.proto
│   └── hello.validator.pb.go
├── github.com -> /Users/asa-taka/src/github.com/
├── google -> /Users/asa-taka/src/github.com/google/protobuf/src/google
└── server.go

$ grpc call localhost:10000 Hello "name: 'test'" --protofiles=api/hello.proto connecting to localhost:10000
message: "Hello, test."

Rpc succeeded with OK status

流石に不便なのでもっといい方法が用意されているか、何かしら自分が間違えているのを期待しています。

一応どの symlink を張ったか解説しておくと

ちなみに grpc_cli ls は動作しませんでした。リフレクション専用のコマンドみたいですね。

$ grpc ls -l localhost:10000 --protofiles=api/hello.proto
Received an error when querying services endpoint.

この状態からリフレクションのコメントアウトを外してみます。初めの状態と特に症状は変わりませんでした。依然として github.com/mwitkow/go-proto-validators/validator.proto が見つからないと言われています。

$ grpc ls localhost:10000
grpc.reflection.v1alpha.ServerReflection
hello.GreetingService

$ grpc ls -l localhost:10000
[libprotobuf ERROR google/protobuf/descriptor.cc:3592] Invalid proto descriptor for file "api/hello.proto":
[libprotobuf ERROR google/protobuf/descriptor.cc:3595]   api/hello.proto: Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors.
filename: grpc_reflection_v1alpha/reflection.proto
package: grpc.reflection.v1alpha;
service ServerReflection {
  rpc ServerReflectionInfo(stream grpc.reflection.v1alpha.ServerReflectionRequest) returns (stream grpc.reflection.v1alpha.ServerReflectionResponse) {}
}

$ grpc call localhost:10000 Hello "name: 'test'"
[libprotobuf ERROR google/protobuf/descriptor.cc:3592] Invalid proto descriptor for file "api/hello.proto":
[libprotobuf ERROR google/protobuf/descriptor.cc:3595]   api/hello.proto: Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors.
Method name not found

苦し紛れに --proto_path=. を指定してみても改善せず。

$ grpc ls -l localhost:10000 --proto_path=.
[libprotobuf ERROR google/protobuf/descriptor.cc:3592] Invalid proto descriptor for file "api/hello.proto":
[libprotobuf ERROR google/protobuf/descriptor.cc:3595]   api/hello.proto: Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors.
filename: grpc_reflection_v1alpha/reflection.proto
package: grpc.reflection.v1alpha;
service ServerReflection {
  rpc ServerReflectionInfo(stream grpc.reflection.v1alpha.ServerReflectionRequest) returns (stream grpc.reflection.v1alpha.ServerReflectionResponse) {}
}

$ grpc call localhost:10000 Hello "name: 'test'" --proto_path=.
[libprotobuf ERROR google/protobuf/descriptor.cc:3592] Invalid proto descriptor for file "api/hello.proto":
[libprotobuf ERROR google/protobuf/descriptor.cc:3595]   api/hello.proto: Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors.
Method name not found

リフレクションサービスは動いているけれど、import が解決できず GreetingService は動かないという...

リフレクション経由で .proto の import を解決する方法ってないんでしょうか。今のところ symlink を張るくらいの解決策しか見つかっておらず、このままだとちょっと不便なんですよね...何かあるはず、というかみんなどうやってるのか...

Issue を漁ってみる...

コマンドのヘルプもドキュメントもあまり充実してはいないので、諦めて Issue を辿っていきます。

ざっと見たところ google.protobuf.Timestamp というよく使われる type/message を使った時も面倒なことになっているらしいですが、詳しいことは読み取る気力がわきませんでした。こちらの方が事象としてはよりシンプルな気もするので、こちら側から攻めてみてもいいかもしれませんね。どうせ日付型も使うことになるでしょうし。

...で、別記事で日付型の import を試してみたところ普通に動いてしまったというわけで、本件に関しては継続して探っていきたいと思います...

それにしても Issue を辿っていると protobuf と grpc と grpc-go をたらい回しになって、なんか、その、疲れます...

GraphQL のため息

GraphQL とは一体何だったのか、半年ほど触れ続けていまだ掴みあぐねています。

やりたいことは分かる、が、どこに使えばいいのかわからない。

サービスに導入するとして、GraphQL で実装したい場所が見つからない。

夢は見せてくれたが、壮大な社会実験だった気がしなくはない、そんな不安。

GraphQL は夢だけ見せて立ち去っていった。

いや、距離をとったのは自分の方だったのだけれど。

とりあえず、しばらくは別の何かを追いかけてみたい。

SaltStack を使ってみる

(=˘ ꒳ ˘=) ちょっと興味が湧いたので塩を舐めてみる...

https://docs.saltstack.com/en/latest/ に従ってチュートリアルを一通り触ってみました。

  • 基本的な動作編: https://docs.saltstack.com/en/getstarted/fundamentals/
    • master + minion x 2 という構成でリモートな複数ホスト上でコマンドを一斉実行したりします
    • HTTP Proxy 環境下では vagrant up した後、無言で止まることがあるので適宜 vagrant-proxyconf あたりのプラグインで設定してやるとうまく進むようになります
  • 設定ファイルのマネジメント編: https://docs.saltstack.com/en/getstarted/config/
    • Pillar や Jinja テンプレートなど、設定ファイルやそこで使えるパラメータの管理方法についてです
    • mysqld をインストールする場面で mysql-server を指定しないと動きませんでした
  • イベント管理編: https://docs.saltstack.com/en/getstarted/event/
    • イベント処理の概要や Reactor などイベントをトリガに発火する処理の記述についてです

ざっと触ってみた感じは宣言的定義が主体であるプロビジョニングツールといった感じでした。 設定ファイルに Jinja テンプレート処理を書き込めるあたり、かなり凝った処理も行えそうです。

宣言的定義に関連してひとつ気になったのですが、チュートリアルの途中まで install vim のように命令的な Session ID が振られていたのが、途中から vim installed のように宣言的な Session ID に変わったところです。 SaltStack の中でもブレがあるのでしょうか、どちらが SaltStack 流なのか気になるところですね。 個人的には pkg.installed という命名から伺える意向を汲んでいるように見える宣言的な Session ID の方が好みです。

命令を定義するのではなく、あくまで状態の定義を行い、SaltStack はホストの状態と定義された理想状態を比較して適宜処理を行う。 install という処理が走るのはあくまで結果であって、大元にあるのはあくまで状態定義である、...と Kubernetes 的な思考を流用するとこうなるでしょうか。

それにしても Vagrant 様々です。VMにアレルギーを持っていた頃が遥か昔のことのようです。

『プロフェッショナルSSL/TLS』を読む - 「第1章 SSL/TLSと暗号技術」から読む

(=˘ ꒳ ˘=) とりあえず Alice と Bob と Mallory だけ覚えておこう...

最近 Kubernetes Dashboard など、HTTPSが有効化されたツールをセットアップする機会が増えてきました。 なのでそろそろきちんと証明書などその辺の技術基盤を理解しないといけない... というか理解した方がそういったツールのトラブルシュートも捗るだろうという目論見でSSL/TLSについて興味を持ち始めております。

というわけで、今読んでいるのは『プロフェッショナルSSL/TLS』という本です。

「はじめに」で実用的な所を知りたければ8、9章を読みましょうとありますが、理論的な所に興味があるのでおとなしく冒頭から順に読んでいきます。

1.3 までは暗号技術の必須要件や歴史なので、とりあえず今は簡単に目を通して飛ばします。 ある程度読み進めてから戻って来たいと思います。

今回は「1.4 暗号技術」の「1.4.1 技術要素」までを読みました。

まず Alice など登場人物の凡例を押さえておく

初めは順序もバラバラに斜め読みしたせいで Alice と Bob と Mallory という登場人物の解釈で迷いましたが、 1.4 の冒頭に

Alice と Bob は暗号技術をわかりやすくせつめいするためによく使われている名前です。

と書かれていました。これだけ押さえたら途端に読みやすくなりました。

1.4 暗号技術

1.4 節の冒頭に暗号技術で解決したいセキュリティにおける重要な問題として

  • 機密性(confidentiality): 秘密が守られること
  • 真正性(authenticity): 本人であることの検証ができること
  • 完全性(integrity): データが改ざんされることなく転送されること

が挙げられていますが、こうやって並べられているだけでは「へぇ、そうなんだ」で通り過ぎてしまいますが、 続く「1.4.1 要素技術」でこれらの言葉がどう使われているかを見ることで、だんだんと具体的なイメージがつかめてきます。

1.4.1 要素技術

1.4.1項で挙げられている要素技術を並べてみると

  • 共通鍵暗号化方式
    • ストリーム暗号化方式、ブロック暗号化方式、パディング
  • ハッシュ関数
    • 原像計算困難性、第二原像計算困難性、衝突耐性
  • MAC(Message Authentication Code、メッセージ認証コード)
  • 暗号化利用モード
    • ECB(Electronic Codebook)モード、CBC(Cipher Block Chaining)モード
  • 公開鍵暗号化方式
  • ディジタル署名
  • 乱数生成器

となりますが、見た感じ機密性と完全性を提供するものに見えますね。 あとは暗号強度を改善する技術でしょうか。 真正性の例は、次の「1.4.2 プロトコル」で Alice と Bob が互いの認証を行う過程で触れられていました。

1.4.1項、1.4.2項の冒頭で繰り返し述べられていることですが

要素技術は、単体ではそれほど便利ではありませんが、 それらをスキームやプロトコルとして組み合わせることで強固なセキュリティを実現できます。

ということで、ここから先はここまで読んできた要素技術の応用編、ということになりそうです。 おそらく度々ここに戻ってきて「公開鍵暗号化方式って具体的にどうだっけ」と読み返すことになるんでしょうね。

一点、機密性、完全性は要素技術で提供されるけれど、真正性はプロトコルで提供される、という違いが気になりました。 節の冒頭で機密性、完全性、真正性と並列に並べられていたのと比較して、ちょっと興味深かったです。 この認識で正しいのかも含めて、この先の内容が楽しみです。

とりあえず読み始めてみて

...と、ここまで読むのに4時間くらいかかりました。

こう言った本にありがちですが、流し読みで理解できる内容ではないんですよね。 ちゃんと読もうとすると一文一文がとにかく重く、行間に気を配っていないとすぐに認識ズレを起こしそうで気力を持って行かれます。 そしてどう読んだところで認識ズレは起こすんだろうなという諦めもあります。

反面、それが楽しくもあるのですけどね。 各項が授業ひとコマ分くらいの密度を持っていてとても読み応えがあります。

みたいな感じで、当サイトでは個々の技術についてはまとめず、どこまで読んでどう思ったかの感想をふわっと書いていこうと思います。 各用語については Wikipedia を見た方がよほど詳しいですしね。

vagrant-hosts を使って互いに名前引き可能なVMをさっくり立てる

(=˘ ꒳ ˘=) らくちんなのである...

Kubernetes のクラスタVMで組んでみたくて Vagrant のお勉強中です。だんだん Ruby が読めるようになってきました。

クラスタを組みたいので当然互いにアドレスなり名前解決可能なホスト名なりで相互接続したいですね。 Vagrant にはDNS的な仕組みはなさそうなので名前解決をしたければ /etc/hosts にレコードを登録する必要があります。

自前でも生成できそうですが、上手いこと自動設定してくれるプラグインnetworking - Can Multiple Vagrant VMs communicate by VM hostname? - Stack Overflow で紹介されていたので使ってみます(4年前ですか、結構昔ですね...)。

GitHub - oscar-stack/vagrant-hosts: Manage static DNS on vagrant guests

これを利用して master 1台と node 3台のVMを立ててみます。

Vagrant.configure("2") do |config|

  config.vm.box = "centos/7"

  config.vm.define "master" do |m|
    m.vm.network "private_network", ip: "192.168.0.1"
    m.vm.provision :hosts, sync_hosts: true
  end

  (1..3).each do |i|
    config.vm.define "node-#{i}" do |n|
      n.vm.network "private_network", ip: "192.168.1.#{i}"
      n.vm.provision :hosts, sync_hosts: true
    end
  end
end

vagrant ssh master して /etc/hosts を見てみると

[vagrant@master ~]$ cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 master
192.168.0.1 master
192.168.1.1 node-1
192.168.1.2 node-2
192.168.1.3 node-3

作成したVMとアドレスが問題なく登録されています。ping も問題なく通りました。

環境メモ

$ vagrant version
Installed Version: 2.0.3
Latest Version: 2.0.3
$ vagrant plugin list
vagrant-hosts (2.8.0)

Rubyist じゃないけど Vagrantifile を読みたい

(=˘ ꒳ ˘=) kubernetes を試そうと Vagrant のリハビリしようとしたら Ruby の表記を思い出すのに手間取ったの巻...

Multi-Machine - Vagrant by HashiCorpVagrantfile を例に、初見で読み方に困ったところを思い出しながら書き留めておきます。

Vagrant.configure("2") do |config|
  config.vm.provision "shell", inline: "echo Hello"

  config.vm.define "web" do |web|
    web.vm.box = "apache"
  end

  config.vm.define "db" do |db|
    db.vm.box = "mysql"
  end
end

ブロック(do..end)

Vagrant.configure("2") do |config|
  # ...
end

は以下の表現と等価ですではありませんでした。

Vagrant.configure("2") { |config|
  # ...
}

どちらの表記も見かけるので迷いますね。複数行の場合は do..end を使う...とかでしょうか。

引数のカッコを省略

  config.vm.provision "shell", inline: "echo Hello"

は以下の表現と等価です。

  config.vm.provision("shell", { inline: "echo Hello" })

他の言語から来ると悪魔のようにも見える表記ですが、慣れるとだんだん括弧が見えてくるようになりますね。 この表記のおかげで、私の中では「Rubyは設定ファイル言語」という先入観があります。

DSL-like 表記の面目躍如といった所ですね。

参考

Bundler の bundler/setup と bundle exec

(=˘ ꒳ ˘=) Ruby のコードちんぷんかんぷんで読みづらい... import やら require やら load やら色々読み込みの構文があるのに加えて、どのライブラリから来た定義なのかが追いづらかったり...

...という愚痴は置いておいて、Ruby のモジュールシステムを別の方面からややこしくしている Bundler の振る舞いについて整理したいと思います。ややこしいと書きましたが、プロジェクト下に依存ライブラリを置けるという、Node.js から来た身からすると勝手知ったるベンダリング手法が取れるありがたいツールです。

Bundler がインストールしたパッケージを使うためには bundle exec したり require bundler/setup したりと、いくつかお作法が必要そうなのですが、それぞれどう作用するかを実際に動かしながら整理していきます。

公式の記述は How to use Bundler with Ruby にあります。しかし情報がパラパラしていて追いづらかったので実際に試してみることにしたのでした。

試してみる

├── Gemfile
├── bin
│   ├── with-bundler-setup.rb
│   └── without-bundler-setup.rb
├── lib
│   └── using-bundled-packages.rb
└── vendor

というプロジェクトを用意します。 https://github.com/asa-taka/try-module-on-bundler に実際のファイルを置いておきました。

フロントのスクリプトをふた通り用意します。

# bin/with-bundler-setup.rb
require 'rubygems'
require 'bundler/setup'
require './lib/using-bundled-packages'
# bin/without-bundler-setup.rb
require './lib/using-bundled-packages'

require の振る舞いを見たかったので一段 lib/using-bundled-packages.rb というスクリプトを挟んでいます。

# lib/using-bundled-packages.rb
require 'awesome_print'

として awesome_print を呼んでいます。vendor 下にインストールされた awesome_print には print 'awesome_print by bundler loaded' と仕込んで、Bundler によりインストールされたパッケージが実行された場合にテキストがプリントされるようにしました。

試した結果

bundle exec ruby ./bin/with-bundler-setup のように各組み合わせを試したところ以下のような結果になりました。

実行コマンド bundler/setup なし bundler/setup あり
ruby .... global のパッケージ Bundler によるパッケージ
bundle exec ruby ... Bundler によるパッケージ Bundler によるパッケージ

つまり bundle exec を使う、もしくはエントリポイントで require bundler/setup をすると孫 require でも Bundler によるパッケージが使われることになりますね。

毎回 bundle exec するのも面倒なので require bundler/setup の方が楽そうですね。

どういう処理でこうなっているのかはまた掘り下げてみたいところです。