GRPC と GKE で Microservices を構築してみる (執筆完了・レビュー待ち)

こんにちは。オープンプラットフォーム事業本部のむらた (@yuichi1004) です。

オープンプラットフォーム事業部ではかねてから Microservices アーキテクチャを採用おります。先日公開された AndApp においても、Microservices 構成が取られています。

DeNAでのGCP活用事例とGCP NEXTでの事例紹介

この事例においては App Engine をフル活用することによって、Restful API をベースとした Microservices 構成を効率よく立ち上げることができました。一方で、GRPC や Google Container Engine (GKE) といった Microservices 向けの技術も次々に世の中に浸透しつつあります。

本稿では、これらの技術を用いてイマドキっぽい Microservices を構築してみます。

システム構成

ここではフィボナッチ数列を計算して返すという非常にシンプルな API の構築を考えてみます。ただし、Microservices っぽいことをするために、Token サービスにトークンを払い出してもらい、そのトークンを用いて処理を行うというケースを考えてみます。

gke_grpc_composition.png

ソースコードはこちらに公開してあります。

RPC Server

RPC Server のプロトコルには GRPC を用います。GRPC は Google の開発している RPC フレームワークです。以下の特徴があります。

  • HTTP/2 をベースにしたプロトコル
  • Protocol Buffer を用いた静的型付け・バイナリペイロードのやり取り
  • リクエスト・レスポンス型/単方向・双方向のストリーミング型通信をサポート
  • Client/Server の TLS による認証・暗号化
  • Metadata による認証のサポート

今回では TLS による暗号化と Metadata による認証のサポートを考えてみます。 なお GRPC ではまず、ペイロードとサービスを Protocol Buffer で定義して Client Stub と Server Interface に変換する作業が必要になりますが、このあたりはすでに良質な記事が見受けられますのでそちらに譲ります。

GRPC は Channel と呼ばれるホストとポートの組み合わせによってコネクションを管理します。この Channel ごとに認証・暗号化方法を指定することができます。例としてまず、トークンを取得するための Token Service への接続を考えてみましょう。


ca, err := credentials.NewClientTLSFromFile(CAPath, "")
if err != nil {
    panic(err)
}
tokenConn, err := grpc.Dial(TokenAddress,
    grpc.WithTransportCredentials(ca),
)
if err != nil {
    log.Fatalf("did not connect: %v", err)
}
defer tokenConn.Close()
tokenClient := token.NewTokenClient(tokenConn)
resp, err := tokenClient.GetToken(ctx, &token.TokenRequest{Subject: "me", Scope: []string{"fibo"}})
if err != nil {
    panic(err)
}

コード

このように WithTransportCredentials() を指定すると、単純な TLS の接続のみを Channel に要求します。 ここでは自己認証局である RootCA のみを指定して TransportCredential を設定しましたが、クライアント証明書をつければクライアント認証付きで TLS のコネクションを貼ることも可能です。限定されたクラアイアントのみが使うサービスであれば、こうした構成もありですね。

一方で、フィボナッチ数列を計算する Fibo Service への接続を見てみましょう。


// Connect to fibo server
fiboConn, err := grpc.Dial(FiboAddress,
    grpc.WithTransportCredentials(ca),
    grpc.WithPerRPCCredentials(TokenCreds{resp.Token}),
)
if err != nil {
    log.Fatalf("did not connect: %v", err)
}
defer fiboConn.Close()
fiboClient := fibo.NewFibonacciClient(fiboConn)

// Calc fibonacci series
for i := 0; i < 20; i++ {
    r, err := fiboClient.GetN(ctx, &fibo.FibonacciRequest{N: int64(i)})
    if err != nil {
        log.Fatalf("could not get fibo: %v", err)
    }
    log.Printf("Result: %d, %d", i, r.Result)
}

コード

ここでは先程の WithTransportCredentials() に加えて WithPerRPCCredentials() による認証を加えています。ここで指定している TokenCreds は Token Service から払い出されたトークンを Metadata として RPC 呼び出しに付与します。これによって、Fibo Service はメタデータを参照してリクエストの認可処理を行うことができます。

Fibo Server の認可処理を見てみましょう。


md, ok := metadata.FromContext(ctx)
if !ok {
    return nil, fmt.Errorf("unauthorized")
}
token, ok := md["authorization"]
if !ok {
    return nil, fmt.Errorf("unauthorized")
}

tokenPayload := []byte(token[0])
var claims Claims
if err := json.Unmarshal(tokenPayload, &claims); err != nil {
    return nil, fmt.Errorf("invalid token")
}

for _, scope := range claims.Scopes {
    if scope == "fibo" {
        return &fibo.FibonacciReply{Result: Fibo(in.N)}, nil
    }
}
return nil, fmt.Errorf("unauthorized (require fibo scope)")

コード

go の場合 Metadata はコンテキストから簡単に取り出すことができます。あとはこのメタデータからトークンを取り出し、scope の中に必要な権限が含まれているか見るだけです。ここでは横着してただの JSON をトークンとしちゃってますが、本来は JWT とかにして署名するべきですね...。そちらはまたの機会に。

Deployment

さてシステムのデプロイには GKE (Google Container Engine) を使ってみましょう。基本的な使い方は公式のドキュメントに譲るとして、ここでは具体的に 2 つのサービスをデプロイしてみましょう。

例として Token Service のデプロイを見てみます。Replication Controller (または Replica Set) を用いて Token Service の Docker イメージをデプロイします。以下が Token Service の Replication Controller の定義ファイルです。


apiVersion: v1
kind: ReplicationController
metadata:
  name: token
spec:
  replicas: 2
  template:
    spec:
      volumes:
      - name: "creds"
        secret:
          secretName: "creds"
          items:
          - key: "tokencert.pem"
            path: "tokencert.pem"
          - key: "tokenkey.pem"
            path: "tokenkey.pem"
      containers:
      - name: token
        image: gcr.io/hoge-jp/token
        env:
        - name: TOKEN_SERVER_PORT
          value: ":443"
        - name: TOKEN_CERT_PATH
          value: "/etc/creds/tokencert.pem"
        - name: TOKEN_KEY_PATH
          value: "/etc/creds/tokenkey.pem"
        ports:
        - containerPort: 443
        volumeMounts:
        - name: "creds"
          mountPath: "/etc/creds"
          readOnly: true

コード

Replication Controller は指定された Docker イメージをコンテナクラスタ内で指定された個数のコンテナで実行します。実行対象となるイメージや、渡される環境変数などを指定します。

ここで便利なのが Secret です。事前にコンテナクラスターに登録しておいた各種シークレットを tmpfs としてマウントすることができます!tmpfs としてマウントされるため、メモリ上に展開されます。そして、ファイルとしては各実行ノードには残りません。これによって安全に秘密鍵などの情報を配置することができます。GRPC では TLS を使う場合、サーバーの証明書や秘密鍵を必要とするのでこれは便利です。

Secret のクラスターへの登録もとても簡単です。Kubectl コマンドを用いて Secret 名とストアする secret file 名を指定するだけです。


kubectl create secret generic creds --from-file=fibocert.pem --from-file=fibokey.pem --from-file=tokencert.pem --from-file=tokenkey.pem

ここまできたらあとは Replication Controller を作成し、外部 IP を与えて疎通するだけです。同様にして作成した Fibo Replication Controller も作成してしまいましょう。


$ kubectl create -f ./token_rc.yaml
$ kubectl create -f ./fibo_rc.yaml
$ kubectl expose rc token --port=443 --target-port=443 --type=LoadBalancer
$ kubectl expose rc fibo --port=443 --target-port=443 --type=LoadBalancer

これで外部 IP が自動的に割り当てられます。


$ kubectl get service
NAME         CLUSTER-IP     EXTERNAL-IP       PORT(S)   AGE
fibo         10.3.248.140   104.xxx.xx.xxx    443/TCP   3h
kubernetes   10.3.240.1                 443/TCP   5h
token        10.3.241.174   104.xxx.xxx.xxx   443/TCP   3h

あとは、この外部 IP に DNS を割り当てるなり、hosts でホスト名を与えるなりして、Client を実行してみましょう!


$ TOKEN_SERVER_ADDR=token.example.com:443 FIBO_SERVER_ADDR=fibo.example.com:443 go run main.go
2016/12/08 01:23:43 Result: 0, 0
2016/12/08 01:23:43 Result: 1, 1
2016/12/08 01:23:43 Result: 2, 1
2016/12/08 01:23:43 Result: 3, 2
2016/12/08 01:23:43 Result: 4, 3
2016/12/08 01:23:43 Result: 5, 5
2016/12/08 01:23:43 Result: 6, 8
2016/12/08 01:23:43 Result: 7, 13
2016/12/08 01:23:43 Result: 8, 21
2016/12/08 01:23:43 Result: 9, 34
2016/12/08 01:23:43 Result: 10, 55
2016/12/08 01:23:43 Result: 11, 89
2016/12/08 01:23:43 Result: 12, 144
2016/12/08 01:23:43 Result: 13, 233
2016/12/08 01:23:43 Result: 14, 377
2016/12/08 01:23:43 Result: 15, 610
2016/12/08 01:23:43 Result: 16, 987
2016/12/08 01:23:43 Result: 17, 1597
2016/12/08 01:23:43 Result: 18, 2584
2016/12/08 01:23:43 Result: 19, 4181

完成です!

まとめ

GRPC を使うと、型づけのしっかりした効率の良い RPC を簡単に実装することができます。そして、Transport Credentials や Per RPC Credentials を組み合わせ、柔軟な認証・認可・暗号化ができます。

GKE を組み合わせると、簡単に GRPC サービスをデプロイ・公開できます。Secret を活用することによって、安全かつ簡単に GRPC に必要な鍵情報をデプロイすることが可能です。

参考リンク

  • http://www.grpc.io/docs/
  • http://kubernetes.io/docs/
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る