- 投稿日:2019-11-15T21:39:46+09:00
Go (Golang) リリース・サポート期限
リリース・サポート期限
メジャーリリースは、2つ後のメジャーリリースが出るまでサポートされます。
Version Release Support 1.13 2019/09/03 Yes 1.12 2019/02/25 Yes 1.11 2018/08/24 No 1.10 2018/02/16 No 1.9 2017/08/24 No 1.8 2017/02/16 No リリースノート
1.13
Go 1.13 Release Notes - The Go Programming Language
https://golang.org/doc/go1.13
- Go Module Proxy
1.12
Go 1.12 Release Notes - The Go Programming Language
https://golang.org/doc/go1.121.11
Go 1.11 Release Notes - The Go Programming Language
https://golang.org/doc/go1.11
- Go Modules
- 投稿日:2019-11-15T16:11:18+09:00
【Go】sqlmockでupdated_atやcreated_atなどのtimestampにマッチさせる方法
前提
ORMにgormを使っていて、sqlmockでテストを書いている。
概要
gormはレコードの更新時にupdated_atカラムを自動で更新してくれる。
sqlmockでテストを書くときに期待するクエリを正規表現で書くが、updated_atはどのような値で更新されるかあらかじめ知ることができないため、期待するクエリと実行されたクエリが不一致となりテストに失敗してしまう。エラー出力does not match actual [time.Time - 2019-11-15 06:29:27.8173492 +0000 UTC m=+0.006968701]"}↑の例はupdated_atの期待する戻り値として、time.Now()と指定した結果。
方法
example.gotype AnyTime struct{} func (a AnyTime) Match(v driver.Value) bool { _, ok := v.(time.Time) return ok } func TestExample(t *testing.T) { db, mock, err := New() if err != nil { // error } mock.ExpectExec(regexp.QuoteMeta("UPDATE `users` SET `name` = ? `updated_at` = ? WHERE `users`.`id` = ?")). WithArgs("田中太郎", AnyTime{}, 1). WillReturnResult(NewResult(1, 1)) }↑の例は諸々省略しているものの、伝えたいことは、以下の通り。
- AnyTime{}を定義する
- Matchを定義する
- WithArgsのupdated_atでAnyTime{}を指定する
作成の際も同様。
参考記事
- 投稿日:2019-11-15T15:46:22+09:00
goを間違えてogと入力してしまった時に、逆さのGopher君が通り過ぎるコマンド
概要
go ... って打ちたくて og ... って間違えちゃうことごく稀にありますよね?よね?
そんな時にいい感じにtypoを注意してくれる og コマンドを作りました!!???
github.com/ramenjuniti/og
???slコマンドみたいなやつです笑
挙動(2019/11/16にgif修正)
なんと!逆さまなGopher君が通り過ぎます!ただただ通り過ぎます!
og run main.go
みたいな場合でも容赦無くGopher君は通り過ぎます!Gopher君かわいい!
インストール(2019/11/15に追加)
Goの環境があれば
go get
でインストール可能です。
実行するには$GOPATH/bin
にPATHが通っている必要があります。(2019/11/16に追加)go get github.com/ramenjuniti/og作り方
画像を用意する
元画像はここから頂きましたhttps://golang.org/doc/gopher/frontpage.png
AA変換(アスキーアート生成)でAAを生成
D;;:;;:;;:;;:;:;:;;:;:;:;:;;:;;:;;:;;:;z $;:;:;::;::;:;;:;;:;;:;;:;;::;;::;;:;;;I .I;;:;;:;;:;;::;;:;:;:;;:;:;;:;:;:;::;;:I ,<;;::;:;;::;;:;;;;;;;;:;::;;:;;:;;;:;;;I ,(;;;:;::;:;;::;;j=1J71<;;;:;:;;::;:;:;:I J;;:;;;:;;::;;;;:r ] .>;;;:;:;:;;:;:;;;r z;;::;:;;:;;:;;j=<?75?7~?I;;:;;:;;;:;:;<] (<;;;;;;:;;;;;;?+~(J-J-_(3;;;;;;::;;:;;+\ ,(;:;:;j/7!''??1+?MMMMM1+?7771+<;;;:;;:j .P;;;;J!.. 4;<<iJ .4<;;:;;2 .3;J<;;j\(M#Q D;<2.MM5. 1:;;;j73, $;jMN<;?|,WH3 $;:t.MM# ,(;;jP;;?| 4<;T9TJ;?. .J;;;?& .t;;jM@:;+% (1++++Y+;?C+...J7<;;;:;;?i.. ..J>;jv<;;;j= .71+<;;;;;;;:;;;;;;;;;;<+J= ?77! '_?771+++++++++?77!コードを書く
もちろんGoで!
main.gopackage main import ( "fmt" "os" "strings" "syscall" "time" "github.com/gosuri/uilive" "golang.org/x/crypto/ssh/terminal" ) func main() { og := []string{ " D;;:;;:;;:;;:;:;:;;:;:;:;:;;:;;:;;:;;:;z ", " $;:;:;::;::;:;;:;;:;;:;;:;;::;;::;;:;;;I ", " .I;;:;;:;;:;;::;;:;:;:;;:;:;;:;:;:;::;;:I ", " ,<;;::;:;;::;;:;;;;;;;;:;::;;:;;:;;;:;;;I ", " ,(;;;:;::;:;;::;;j=1J71<;;;:;:;;::;:;:;:I ", " J;;:;;;:;;::;;;;:r ] .>;;;:;:;:;;:;:;;;r ", " z;;::;:;;:;;:;;j=<?75?7~?I;;:;;:;;;:;:;<] ", " (<;;;;;;:;;;;;;?+~(J-J-_(3;;;;;;::;;:;;+| ", " ,(;:;:;j/7!''??1+?MMMMM1+?7771+<;;;:;;:j ", " .P;;;;J!.. 4;<<iJ .4<;;:;;2 ", ".3;J<;;j|(M#Q D;<2.MM5. 1:;;;j73, ", "$;jMN<;?|,WH3 $;:t.MM# ,(;;jP;;?|", "4<;T9TJ;?. .J;;;?& .t;;jM@:;+%", " (1++++Y+;?C+...J7<;;;:;;?i.. ..J>;jv<;;;j= ", " .71+<;;;;;;;:;;;;;;;;;;<+J= ?77! ", " '_?771+++++++++?77! ", } ogw := len(og[0]) tw, _, err := terminal.GetSize(syscall.Stdin) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } for i := range og { og[i] = strings.Repeat(" ", tw) + og[i] } writer := uilive.New() writer.Start() for i := 0; i <= tw+ogw; i++ { var out string for _, v := range og { if i < ogw { out += v[i:tw+i] + "\n" } else if i < len(v) { out += v[i:] + "\n" } else { out += "\n" } } fmt.Fprint(writer, out) time.Sleep(10 * time.Millisecond) } writer.Stop() }コードはこれだけ!簡単!
terminalのサイズを取得するためにgolang.org/x/crypto/ssh/terminalを使用
terminalの出力の更新をリアルタイムに行うためにgithub.com/gosuri/uiliveを使用まとめ
研究室にいるときにふと思いついて深夜テンションで作ったんですが、可愛く出来たので満足しています笑
今後時間があったら動きなんかも追加したいですね
参考
- 投稿日:2019-11-15T13:55:28+09:00
ECS+Fargateの環境でgRPCサーバのヘルスチェックをするために色々頑張った話
はじめに
ECS on Fargateの環境でgRPCサーバのヘルスチェックをする際に少し詰まったのですが、参考になる記事が少なかったため、ノウハウを残しておきます。
EKSやGKEとかだとどうなのかなどはコメント頂けるとありがたいです。gRPCの基本部分で自信が無い方は、よろしければ、いまさらだけどgRPCに入門したので分かりやすくまとめてみたをご覧ください。
前提
- ECS on Fargate
- LBはALBかNLBの二択
- 対象システムはRESTとgRPCの両方のAPIを持っており、Nginxをリバースプロキシとして利用している。
- REST-APIサーバのヘルスチェックはALBがヘルスチェック用のREST-APIを実行することで実現している。
- gRPCクライアントとgRPCサーバの中継はNLB(L4)、REST-APIクライアントとREST-APIサーバの中継はALB(L7)を利用している。
問題背景
- gRPCはHTTP2である。
- ALBは外側はHTTP2対応しているが、内側はHTTP1.1しか対応していない。
- つまり、ALBがgRPCサーバのヘルスチェック用メソッドを直接実行することができない。(REST-APIと同じようにはいかない)
解決案
いくつかあると思います。
解決案① NLBのポートヘルスチェック
NLBがサーバのポート監視する方法です。
やることはAWSの設定だけなので、比較的簡単に実現できます。
しかし、あくまでポート監視までしかできないので、実際にアプリが正常に動作しているかを確認するには少し弱いです。解決案② ECSのサービスディスカバリ
参考記事:
ECS + Fargate + gRPCを使ったマイクロサービス構成こちらも、やることはAWSの設定だけなので、比較的簡単に実現できます。
しかし、上記のポートヘルスチェックと似たような問題を抱えています。解決案③ grpc-gatewayを使う
grpc-gatewayはHTTP1.1で受けてHTTP2に変換することができるため、ALBとgRPCサーバの間に配置することで、間接的ではありますが、ALBがgRPCサーバのヘルスチェック用メソッドを実行することができそうです。
ヘルスチェック用メソッドの実装
gRPC公式でヘルスチェック用のprotoファイルが公開されていました。
https://github.com/grpc/grpc/blob/master/src/proto/grpc/health/v1/health.proto下記のprotoファイルは、grpc-gateway用にCheckメソッドがREST-APIで受けられるように設定を追記しています。
syntax = "proto3"; package grpc.health.v1; import "google/api/annotations.proto"; option csharp_namespace = "Grpc.Health.V1"; option go_package = "google.golang.org/grpc/health/grpc_health_v1"; option java_multiple_files = true; option java_outer_classname = "HealthProto"; option java_package = "io.grpc.health.v1"; message HealthCheckRequest { string service = 1; } message HealthCheckResponse { enum ServingStatus { UNKNOWN = 0; SERVING = 1; NOT_SERVING = 2; SERVICE_UNKNOWN = 3; // Used only by the Watch method. } ServingStatus status = 1; } service Health { // If the requested service is unknown, the call will fail with status // NOT_FOUND. rpc Check(HealthCheckRequest) returns (HealthCheckResponse) { option (google.api.http) = { get: "/grpc/health" }; } rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse); }protocコマンドで、
health.pb.go
を自動生成します。
開発言語はGoです。$ protoc -I grpc/health/ grpc/health/health.proto --go_out=plugins=grpc:grpc/healthヘルスチェック用のメソッドの実装はこんな感じです。
interceptorの認証チェックをスキップするための実装が組み込まれていますが、それに関してはこちらの記事で解説しています。type SkipAuthHealthServer struct { health.HealthServer } var _ grpc_auth.ServiceAuthFuncOverride = (*SkipAuthHealthServer)(nil) func (*SkipAuthHealthServer) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) { return ctx, nil } func (h *SkipAuthHealthServer) Check(context.Context, *health.HealthCheckRequest) (*health.HealthCheckResponse, error) { return &health.HealthCheckResponse{ Status: health.HealthCheckResponse_SERVING, }, nil } func (h *SkipAuthHealthServer) Watch(*health.HealthCheckRequest, health.Health_WatchServer) error { return status.Error(codes.Unimplemented, "service watch is not implemented current version.") }ひとまず、HTTP2でこのメソッドが正常に動作するかを確認してみましょう。
gRPC確認クライアントツールはいくつか存在します。
grpcurl
を使った例です。$ grpcurl -plaintext localhost:50051 grpc.health.v1.Health.Check { "status": "SERVING" }上記protoファイル専用のヘルスチェッククライアントツールを使った例です。
grpc-ecosystemのものなので信頼性も高そうです。
https://github.com/grpc-ecosystem/grpc-health-probe$ grpc-health-probe -addr=localhost:50051 status: SERVING以上より、ヘルスチェック用メソッド単体は正常に動作することがわかりました。
grpc-gatewayの実装
grpc-gatewayもprotocコマンドでpb.goを自動生成します。
grpc-gatewayサーバの実装はこんな感じです。
grpc-gatewayのポート番号は50052にしてみました。
ローカルの場合にホスト名がgrpc
で通信できるのは、docker-compose.yamlでコンテナ名をそのように指定しているからです。
ステージングあるいはプロダクション環境の場合にホスト名がlocalhost
で通信できるのは、ECSのawsvpcネットワークモードを使用しているためです。awsvpc ネットワークモードを使用してタスクが開始されると、タスク定義内のコンテナが開始される前に、各タスクに Amazon ECS コンテナエージェントによって追加の pause コンテナが作成されます。次に、amazon-ecs-cni-plugins CNI プラグインを実行して pause コンテナのネットワーク名前空間が設定されます。その後、エージェントによってタスク内の残りのコンテナが開始されます。こうすることで pause コンテナのネットワークスタックが共有されます。つまり、タスク内のすべてのコンテナは ENI の IP アドレスによってアドレス可能であり、localhost インターフェイス経由で相互に通信できます。
出典: https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task-networking.htmlpackage main import ( "context" "flag" "net/http" "os" "github.com/st-tech/go-zozoerp-po/app/common" "github.com/golang/glog" "github.com/grpc-ecosystem/grpc-gateway/runtime" "google.golang.org/grpc" log "github.com/sirupsen/logrus" gw "github.com/st-tech/go-zozoerp-po/grpc/health/google.golang.org/grpc/health/grpc_health_v1" ) var ( runserver = "RUNSERVER" staging = "STAGING" production = "PRODUCTION" grpcServerEndpoint = flag.String("grpc-server-endpoint", "grpc:50051", "gRPC server endpoint") // LOCALの設定 ) func run() error { if os.Getenv(runserver) == staging || os.Getenv(runserver) == production { // ステージングあるいはプロダクション環境の場合はlocalhostとする grpcServerEndpoint = flag.String("grpc-server-endpoint", "localhost:50051", "gRPC server endpoint") } ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() mux := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithInsecure()} err := gw.RegisterHealthHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts) if err != nil { return err } return http.ListenAndServe(":50052", mux) } func main() { flag.Parse() defer glog.Flush() common.LogInit() log.Info("grpc-gateway Server Started!!!") if err := run(); err != nil { log.Fatal(err) } }あとは、ローカルとECSでgrpc-gateway用のコンテナを追加します。
ここは特別なことはないので割愛します。ローカルでgRPCサーバとgrpc-gatewayのコンテナとアプリケーションを起動し、
http://localhost:50052/grpc/health
すると、正常に動作しました。これをALBが定期実行することでヘルスチェックをうまく実現できそうです。
しかし、これ使っていません。
結構頑張ったのですが、結果的にはこの方法でのヘルスチェックは現在導入していません。
理由は以下です。
- grpc-gatewayコンテナ追加に伴い、AWS利用料金が増加しそうだったから。
- ヘルスチェックをgrpc-gateway経由で行うと、たかだかヘルスチェックしか仕事していないgrpc-gatewayコンテナがなんらかの不具合で停止した場合、それに引きづられてタスクの入れ替えが発生するから。また、それを心配しなければいけないこと自体が嫌だったから。
- Nginxを経由しないためgRPCサーバのヘルスチェックだけがNginxのアクセスログ収集からは確認できない。これだとdatadogで見づらい。(他gRPCのAPIアクセルはNginx経由のためログとれる)
gRPCサーバと直接HTTP1.1で通信したくなったら、その時は移行するかもしれません。
また、上記の理由から、ローカルまでは動作確認したのですが、AWS環境上では未確認です。もし試されたり、実際に同じような運用をされているかたがいらっしゃればコメントお願いします!
解決案④ NginxコンテナにHTTP2でヘルスチェックさせる
現状、この方法を採用しています。
ECSのHEALTCHECKオプションを活用し、Nginxコンテナが自分自身にgrpc-health-probeを実行する方法です。
流れは以下です。これを10秒間隔で実行しています。
- Nginxコンテナ上で自分自身に対してヘルスチェックリクエスト
- NginxがリバースプロキシとしてgRPCサーバへ転送
- gRPCサーバのヘルスチェックメソッドが実行される
注意点として、あらかじめ、Nginxコンテナにgrpc-health-probeをインストールしておく必要があります。
Nginxコンテナが300MBほど増えてしまったのですが、新規でgrpc-gatewayコンテナ立てるよりはトータルで少ないです。詳細は弊社SREチームのいっちーさんの別日のアドベントカレンダーの記事を確認ください。
(明日公開予定)解決案⑤ AppMesh
この方法があると聞きましたが、未調査です。
最後に
gRPCを使った開発が初めてだったので色々悩むことが多かったです。
また、少し踏み込むと途端に日本語ドキュメントが少なかったので書いてみました。
わからないけど、Envoyとか使えばもっと楽なのかもしれない。そっちはまた今度勉強します。