Go
golang
ProtocolBuffers
gRPC
ChaosEngineering

Unix時間がNの倍数のときだけサービスを不安定にするGoパッケージを作成した


概要

マイクロサービスアーキテクチャでは場合にもよりますが機能ごとにサービスが別れます。

その時、クリティカルじゃないサービスへのアクセスで、クリティカルなサービスも影響を受ける、といったことは避けたいです。

クリティカルは、イメージとしては、Qiitaでは記事の投稿や閲覧です。逆にクリティカルじゃないものはいいねやストック機能だと思います。

クリティカルなサービスに影響を出さないよう、クリティカルでないサービスにリクエストを送る側でうまいことハンドリングしておいてほしいと思います。

それを確実にするため、あえて四六時中サービスを不安定にしようと思いunstableを作成しました。


どういったときに使うか

基本的にはサービス全体でクリティカルではないものに対し、意図的にサービスを不安定にすることで、過度にそのサービスを信頼させないようにするためのものです。

HttpやgRPCのサーバで使える様になっています。


不安定とは

サービスの不安定の定義はいくつかあると思います。Netflixのカオスエンジニアリングについてまとめられた、Chaos Engineering を読んでみると、Netflixでは意図的に注入する不安定さには次があるそうです。


Some examples of inputs we use at Netflix in our experiments:

- terminate virtual machine instances

- inject latency into requests between services

- fail requests between services

- fail an internal service

- make an entire Amazon region unavailable


と書かれています。

アプリケーション側で可能なことは次の2つだと思います。


  • inject latency into requests between services

  • fail an internal service

なので、上の2つの条件をunstableでは採用してます。


どう使うか

必要なことは2つで、

1. 設定ファイル(unstable.json)を書く

1. ミドルウエアを挟み込む


設定ファイルを書く


unstable.json

{

"interval": 1,
"slow_response_option": {
"enable": true,
"time": 5
},
"server_error_option": {
"enable": true
}
}

こんな感じで書きます。

interval: ここで設定した秒数でUnix時間が割り切れるとき、不安定になる。ただし、0の場合不安定にはならない。

slow_response_option: enableでオンオフを切り替えられる。timeでレスポンスに何秒かけるか決める

server_error_option: エラーを返すかどうか。httpなら500番を、gRPCならcodes.Internalを返す

https://github.com/rerost/unstable/blob/master/common/internal/proto/config.proto


ミドルウエアを挟み込む(http)

httpの場合は以下のようにミドルウエアをはさみます。

uhttp.WithUnstable(handler)

サンプル実装が次になります。


http.go

package main

import (
"fmt"
"net/http"

"github.com/rerost/unstable/uhttp"
)

func main() {
http.Handle("/", uhttp.WithUnstable(handler))
if err := http.ListenAndServe(":3000", nil); err != nil {
fmt.Println(err)
}
}

func handler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Sample"))
}


curlで試してみると以下のようになります。

$ curl http://localhost:3000

Fail by unstable%


ミドルウエアを挟み込む(gRPC)

gRPCの場合は以下のようにInterceptorをはさみます。Unaryにしか対応しておらず、Streamには対応していません。

grpc.UnaryInterceptor(ugrpc.UnstableUnaryServerInterceptor()),

サンプル実装が次になります。


grpc.go

package main

import (
"context"
"log"
"net"

"github.com/golang/protobuf/ptypes/empty"
api_pb "github.com/rerost/unstable/example/grpc/server/api"
"github.com/rerost/unstable/ugrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)

type server struct{}

func (s *server) GetSample(ctx context.Context, req *empty.Empty) (*api_pb.GetSampleResponse, error) {
return &api_pb.GetSampleResponse{Message: "Sample"}, nil
}

func main() {
l, err := net.Listen("tcp", ":5000")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
s := grpc.NewServer(
grpc.UnaryInterceptor(ugrpc.UnstableUnaryServerInterceptor()),
)
api_pb.RegisterSampleServiceServer(s, &server{})
reflection.Register(s)
if err := s.Serve(l); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}


grpc_cliから試すと以下のようになります。

$ grpc_cli call localhost:5000 com.github.rerost.unstable.example.api.SampleService.GetSample

reading request message from stdin...
connecting to localhost:5000
Rpc failed with status code 13, error message: Fail by unstable


まとめ


  • サービスを変に信頼されないために、わざとサービスを不安定にするようにした

  • ミドルウエアとして、httpとgRPCで使えるようにした