Quantcast
Channel: fukuoka.ex Elixir/Phoenix Advent Calendarの記事 - Qiita
Viewing all articles
Browse latest Browse all 25

grpc-elixirでGoと通信してみる #1

$
0
0

(この記事は、「fukuoka.ex Elixir/Phoenix Advent Calendar Advent Calendar 2018」の11日目です)

昨日は @curry_on_a_rice さんのElixirでニューラルネットを実装しようとした話でした。

grpc-elixirについて

gRPCはマイクロサービス用の通信プロトコルとして脚光を浴びていますが、今回はそのElixir実装である、gRPC Elixirをご紹介します。C++, Node.js, Python, Ruby, Objective-C, PHP, C#, Java, Goと、様々な言語サポートがある一方で、Elixirは蚊帳の外だったのですが、Tony Han氏によりgRPCのElixir実装が進んでいます。

image.png

gRPCに関してはマイクロサービスバックエンドAPIのためのRESTとgRPCをご覧ください。

2018年12月現在はα版ですが、( ⇒ 12/11α版表記取れました ! )
他言語との連携を確認する分には問題ないので、今回ご紹介することにしました。ロードマップを見る限りは、圧縮・ロギング・外部エンコーディング以外の実装は済んでます。

gRPC公式には単純な接続を確認するHello Worldと、双方向ストリーミングを行うRoute Guideというサンプルがあります。elixir-grpcでは両方とも実装されているので、2つのサンプルを使った通信をPlay with Dockerを使用して行います。

いわゆる「体裁」面はほぼ実装済みで、β版への移行は内部で使用しているgunなどのhttp2の関連ライブラリの安定待ちではないかと、私は推測しています。
本コラム投稿直前に正式版となりました!

以降の手順で必用とされるスキル

vimでファイルの編集ができること
Dockerコンテナで中と外の居場所の区別がつくこと

一般的な実装手順

今回は単項目の呼び出しに限定して、解説します。
導入手順に関しては、grpc-elixirのリポジトリに丁寧に書いてあるので、ここではその手順を軽く辿るのみとします。

0. プロジェクトへのインストール

mix.exsにパッケージを追加します

mix.exs
def deps do
  [{:grpc, github: "tony612/grpc-elixir"}]
end
mix deps.get

1. protocol buffer elixirプラグインのインストール

gRPCでは、.proto拡張子が付くIDL(インターフェス定義言語)を定義します。protocツールを使って、各言語用のスキーマーや構造体を作成します。各言語用のprotocプラグインがあるのですが、なんと、elixirプラグインであるelixir-protobufもtony氏が作られたものです。

以下のコマンドを使ってprotocのプラグインをインストールします。(protocコマンドはOSのパッケージマネージャーでインストールしてください)

mix escript.install hex protobuf --force

インストール後は~/.mix/escriptsにパスを通す必要があります。

PATH=~/.mix/escripts:$PATH

2. protoファイルから、構造体ファイル(.pb.ex)を作成

protoc --elixir_out=plugins=grpc:./lib/ helloworld.proto

IDLのhellowold.protoからは、.pb.exという拡張子で次のようなRequestとReply用のモジュールが生成されます。

hellowolrd.pb.ex
defmodule Helloworld.HelloRequest do
  use Protobuf, syntax: :proto3

  @type t :: %__MODULE__{
    name: String.t
  }
  defstruct [:name]

  field :name, 1, type: :string
end

defmodule Helloworld.HelloReply do
  use Protobuf, syntax: :proto3

  @type t :: %__MODULE__{
    message: String.t
  }
  defstruct [:message]

  field :message, 1, type: :string
end

defmodule Helloworld.Greeter.Service do
  @moduledoc false
  use GRPC.Service, name: "helloworld.Greeter"

  rpc :SayHello, Helloworld.HelloRequest, Helloworld.HelloReply
end

defmodule Helloworld.Greeter.Stub do
  @moduledoc false
  use GRPC.Stub, service: Helloworld.Greeter.Service
end

3.クライアント側コード

クライアント側のコードを対話環境で実行すると以下の通りとなります。

iex> GRPC.Server.start(Helloworld.Greeter.Server, 50051)
iex> {:ok, channel} = GRPC.Stub.connect("localhost:50051")
iex> request = Helloworld.HelloRequest.new(name: "grpc-elixir")
iex> {:ok, reply} = channel |> Helloworld.Greeter.Stub.say_hello(request)

4.サーバー側コード

また、サーバー側のコードは以下の通りです。
上記で定義したモジュールを使いながら記述していきます。

defmodule Helloworld.Greeter.Server do
  use GRPC.Server, service: Helloworld.Greeter.Service

  @spec say_hello(Helloworld.HelloRequest.t(), GRPC.Server.Stream.t()) ::
          Helloworld.HelloReply.t()
  def say_hello(request, _stream) do
    Helloworld.HelloReply.new(message: "Hello #{request.name}")
  end
end

以下のようにApplicationモジュールのSupervisorツリーにhelloworldサーバーを登録します

defmodule YourApp do
  use Application
  def start(_type, _args) do
    import Supervisor.Spec
    children = [
      # ...
      supervisor(GRPC.Server.Supervisor, 
    [{Helloworld.Greeter.Server, 50051}])
    ]

    opts = [strategy: :one_for_one, name: YourApp]
    Supervisor.start_link(children, opts)
  end
end

mix.exsにアプリケーションモジュールを登録します

mix.exs
  def application do
    [
      mod: {YourApp, []}, # これを登録
      extra_applications: [:logger]
    ]
  end

config.exsにアプリケーション設定するか、mixコマンドで待ち受けが可能です。

mix grpc.server

Docker-composeで双方向通信環境を作る

ここからは、docker環境を作り相互通信をするフェーズです。

2つ以上の言語を使った通信なので、環境まわりを楽にすべくDocker-composeを使います。といっても、ファイルを3種コピペすれば通常のDocker環境で実行できるので、気軽に読み進めて下さい。

ここでは2種類のコンテナを一気に立ち上げます
- Elixirコンテナ elixir-node
- Golangコンテナ go-node

Play with Dockerについて

無料で4時間Docker環境が使えるサービスです。Docker Hubに登録していれば誰でも使用できます。ホストVMのメモリも4GBでvCPU8コアなので、速度はかなり速いです(個人の感想です)。Docker Hubに登録すればすぐ使用できるので、ご登録をお勧めします。

詳しくはDocker 入門にはインストールなしで使える「Play with Docker」がいいと思うをご覧ください。

以下の説明はPlay with Dockerにて進めますが、ご自分のPCにDockerをインストールされている方も、ほぼ変わらない手順で動作します。(touch等必用ない分そちらが楽です)

コンテナ構築

Play with Dockerで行う場合は、内臓のEditorを使うためにtouchコマンドでまず空ファイルを3種作ります。(Vimも使えますが、コピペに難があります)

コンテナには、elixir-nodeとgo-nodeという名前を付けています。docker-composeだとホスト名として扱えるので生IPアドレスを扱わなくて済むので楽です。

まずは「ADD NEWINSTANCE」ボタンを押して下さい。ブラウザ内にLinuxコンソールが立ち上がります。そこで以下を入力します。

$ touch docker-compose.yml
$ touch Dockerfile-go
$ touch Dockerfile-elixir

image.png

Editorボタンを押すと、数秒後にフォルダ構成が出て来ます。

image.png

それぞれのファイルに以下をそれぞれコピペしてSaveボタンを押してください。

Dockerfile-elixir

公式のaplineコンテナを使用して、grpc-elixirのリポジトリをクローン。protobuf関連の準備をするコンテナです。

Dockerfile-elixir
FROM elixir:1.7.4-alpine

RUN apk -U update && apk --update --no-cache add protobuf git vim && \
    cd ~ && \
    git clone https://github.com/tony612/grpc-elixir.git && \
    mix local.hex --force && \
    mix local.rebar --force && \
    mix hex.info && \
    mix escript.install hex protobuf --force

ENV PATH /root/.mix/escripts:$PATH

CMD ["ash"]

Dockerfile-go

gRPCの公式コンテナを引用して、Goのパッケージを準備を行います。
各種サンプルを事前コンパイルして、helloworldのサーバーを実行して待機する内容です。

Dockerfile-go
FROM grpc/go:latest

RUN go get -u golang.org/x/net/http2 golang.org/x/net/context && \
    cd /go/src/google.golang.org/grpc/examples/helloworld/greeter_server && \
    go build main.go && \
    cd /go/src/google.golang.org/grpc/examples/helloworld/greeter_client && \
    go build main.go && \
    cd /go/src/google.golang.org/grpc/examples/route_guide/server && \
    go build server.go && \
    cd /go/src/google.golang.org/grpc/examples/route_guide/client && \
    go build client.go

CMD ["/go/src/google.golang.org/grpc/examples/helloworld/greeter_server/main"]

EXPOSE 50051 10000

docker-composer.yml

elixir-nodeとgo-nodeを定義します。2つのコンテナの由来や関連付けを行います。コメント化された一行は次回で使用します。

docker-compose.yml
version: '3'
services:
  elixir-node:
    build:
      context: .
      dockerfile: Dockerfile-elixir
    command: ash
    tty: true
    environment:
      - MIX_ENV=dev

  go-node:
    build:
      context: .
      dockerfile: Dockerfile-go
    # command: sh -c "cd /go/src/google.golang.org/grpc/examples/route_guide/ && server/server"

エディタを閉じコンソールに戻ります。
以下を起動すると、ビルドが始まりますのでしばらくお待ちください。

docker-compose.ymlを見てわかるように、elixir-nodego-nodeという2つのコンテナを同時に起動します。

docker-compose up -d

HelloWorld gRPC サンプル

Elixirのコンテナにashを起動します。

HelloWorld : Elixir -> Golang

$ docker-compose exec elixir-node ash

以下elixir-nodeのコンテナ内での作業です。フォルダ移動と依存関係の取得、コンパイルを行います。

# cd ~/grpc-elixir/examples/helloworld
# mix deps.get && mix compile

まずはクライアントを実行してみます。
priv/client.exsにクライアント用のコードが用意されているのですが、サーバーがlocalhost固定ですので、ここは動きを理解するためにも、手動で実行します。

# iex -S mix

まずはGRPC.Stub.connect関数でコンテナgo-nodeのgrpcサーバーとの接続を確立します。

iex(1)> {:ok, channel} = GRPC.Stub.connect("go-node:50051")
{:ok,
 %GRPC.Channel{
   adapter: GRPC.Adapter.Gun,
   adapter_payload: %{conn_pid: #PID<0.216.0>},
   cred: nil,
   host: "go-node",
   port: 50051,
   scheme: "http"
 }}

無事接続できました。protoファイルで定義されているsay_hello関数を実行します。

iex(2)> {:ok, reply} = channel |> Helloworld.Greeter.Stub.say_hello(Helloworld.HelloRequest.new(name: "grpc-elixir"))
{:ok, %Helloworld.HelloReply{message: "Hello grpc-elixir"}}

構造体にHello grpc-elixirという文字列が返ってくることが確認できました。

順調に行き過ぎてるように見えるので、接続先を変えてみます。

iex(3)> {:ok, channel2} = GRPC.Stub.connect("localhost:50051")
** (MatchError) no match of right hand side value: {:error, "Error when opening connection: :timeout"}

きちんと、タイムアウトでエラーとなりましたね。

ping代わりのテストなので大したことをやってるわけではないのですが、priv/client.exsを見てわかるとおり、スタブに対してsay_hello関数を呼び出すだけでRPCが実行できる簡単な流れとなっているのがおわかり頂けると思います。

Ctrl+C を2回押してiexから抜けてください。

HelloWorld : Golang -> Elixir

こんどは、Elixir側を待ち受けにしてGolang側からAPIを叩いてみます。

以下でgrpcサーバーが待ち受けとなります。

# mix grpc.server

Ctrl+p, Ctrl+q でコンテナを抜けて、go-nodeのコンテナに入ります。

$ docker-compose exec go-node bash

helloworld/greeter_clientフォルダーに移動します

cd /go/src/google.golang.org/grpc/examples/helloworld/greeter_client

サンプルプログラムはLocalhostのサーバーに対して、リクエストを送るようになっているので宛先をelixir-nodeに変えます。このコンテナにはvimが入ってないので、sedでlocalhostを強制変換して再コンパイルします。

# sed -i -e s/localhost/elixir-node/ main.go
# go build main.go

実行ファイルを起動します。

# ./main
2018/12/10 05:11:17 Greeting: Hello world

無事にElixirとの接続が確認できました。

コンテナを抜け、コンテナを終了させましょう。

# exit
$ docker-compose down

まとめ

protcのプラグインのインストールと、Stubの作成を乗り越えてしまえば、簡単に多言語と通信できることが理解頂けたのではないかと思います。今回は、HelloWorldのみでしたが、次回は1接続でストリーミングを行うコードを含んだRoute Guildサンプルを試してみます。お楽しみに。


 明日のfukuoka.ex Elixir/Phoenix Advent Calendar 2018 12日目の記事は, @tuchiro さんの「BehaviorとMix.Configで切り替え可能なStubを実装する」です。こちらもお楽しみに!


Viewing all articles
Browse latest Browse all 25

Trending Articles