トレタ Advent Calendar 2019 の7日目の記事です。
こんにちは!前回の対策として、からをやぶるパルシェンを採用したところ徐々に勝てるケースが増えてきたサーバサイドエンジニアの川又です。
ミミッキュ・ドラパルトに対しては優位に立てることが多くなった(気がする)のですが、最近増えてきた(気がする)バンギラスが完全に止まるようになってしまったので、そちらの対策を考えないとツラい感じになってきました。
この記事では、自分が開発に携わっているgRPCとGoを使ったサーバサイドのコンポーネントの開発をするにあたって用意したことについて書いてみます。
(注: まだ開発中のフェーズなので、運用に乗せるところまでは考えられていません><)
トレタのサーバサイドはRuby on Railsを採用することが多かったのですが、Toreta nowのサーバサイドなど一部のコンポーネントではGoが採用されていました。(トレタのプロダクト開発 / toreta tech talk#2 より)
いま自分が開発をしているコンポーネントもGoを採用しており、ざっくり通信の流れを書くと
--1. gRPC--> (2. gRPC to JSON) --3. JSON--> [gRPCクライアント] [★コンポーネント★] [別のAPIサーバ] <--6. gRPC-- (5. JSON to gRPC) <--4. JSON--
というように、クライアントからはgRPCでリクエストを受け、別のAPIサーバに対してはREST API(JSON)で通信し、クライアントに対してgRPCでレスポンスを返す、というようなものになっています。
自分はgRPCもGoも初めてだったのでまだ手探り状態ではありますが、次に紹介するモノたちを活用して開発を行っています💪
1 GoLand
JetBrains社が提供しているIDEです。
Railsのコンポーネントを開発していた頃からRubyMineを使っていた(以前C++で開発してたときはCLionも使っていた)ので慣れが大きいかもですが、とても使いやすいと思います!
単にコードジャンプだけでなく、今開いているソースファイルに対応するテストファイルを「Cmd + Shift + T」で開けたり、デバッグ実行するときの変数一覧がコマンドラインよりも見やすかったりと、イロイロな点で捗るポイントがあります。
プラグインは、(ターミナルではneovimを使っているので)Vimキーバインドで操作できるようになる IdeaVim だったり、「Cmd + ;」を入力してから文字を入力すると、画面内に表示されている位置に移動できる AceJump とかを使ってます 🙆♀️
2 CircleCI
他のコンポーネントもほとんどCircleCIを使っていたので、今回のコンポーネントも同じようにCircleCIを使うことにしました。
CIでは、
- go mod tidy, go vet ./..., goimportsの実行漏れのチェック
- protoファイルへのclang-format, protocの実行漏れのチェック
- テスト実行
などをやっています。
開発してるコンポーネントのディレクトリ構成を抜粋すると、このような感じになっていまして↓
. ├── .circleci │ └── config.yml # CircleCIの設定ファイル ├── Dockerfile.sandbox-envoy # envoy用のDockerfile ├── Dockerfile.sandbox-server # 今回のコンポーネント用のDockerfile ├── Makefile ├── bin │ └── .gitkeep ├── cmd │ └── grpc-health-probe │ └── main.go # grpc-health-probeのmain │ └── server │ └── main.go # 今回のコンポーネントのmain ├── config │ ├── envoy.yml # envoyの設定ファイル │ └── sandbox.yml # 今回のコンポーネントの設定ファイル ├── internal │ └── (内部ロジックなど) ├── proto │ └── (protoファイルの置き場) ├── proto-generated │ └── (protocで生成されたファイルの置き場) ├── test └── (テスト)
.circleci/config.yml
は次のような内容になっています。
version: 2.1 executors: golang: docker: - image: golang:1.13-buster commands: check_git_status_empty: steps: - run: command: | changed_files=$(git status -s) if [ -n "$changed_files" ]; then echo "git-diff found in these files! Maybe you forgot lint." echo "$changed_files" exit 1 fi install_clang_format_and_protoc: steps: - run: apt-get update - run: apt-get install -y clang-format protobuf-compiler - run: GO111MODULE=off go get -u github.com/golang/protobuf/protoc-gen-go install_goimports: steps: - run: GO111MODULE=off go get -u golang.org/x/tools/cmd/goimports run_clang_format: steps: - run: find ./proto -regex ".*\.proto" -type f | xargs clang-format -style=google -i run_go_mod_tidy: steps: - run: go mod tidy run_goimports: steps: - run: goimports -w cmd/**/*.go internal/**/*.go test/**/*.go run_protoc: steps: - run: find ./proto -regex ".*\.proto" -type f | xargs protoc -I./proto --go_out=plugins=grpc:./proto-generated/go run_go_vet: steps: - run: go vet ./... jobs: # go mod tidy を実行して、 # gitの差分が出た場合はエラーにするjob check_go_mod_tidy: executor: name: golang steps: - checkout - run_go_mod_tidy - check_git_status_empty # go vet ./... を実行して、 # gitの差分が出た場合はエラーにするjob check_go_vet: executor: name: golang steps: - checkout - run_go_vet - check_git_status_empty # goimports を実行して、 # gitの差分が出た場合はエラーにするjob check_goimports: executor: name: golang steps: - checkout - install_goimports - run_goimports - check_git_status_empty # protoファイルに対してclang-formatとprotocを実行して、 # gitの差分が出た場合はエラーにするjob check_proto: executor: name: golang steps: - checkout - install_clang_format_and_protoc - run_clang_format - run_protoc - check_git_status_empty # テストを実行して、 # gitの差分が出た場合はエラーにするjob run_test: executor: name: golang steps: - checkout - # テスト実行の command - check_git_status_empty workflows: version: 2 ci: jobs: - check_go_mod_tidy - check_go_vet - check_goimports - check_proto - run_test: requires: - check_go_mod_tidy - check_go_vet - check_goimports - check_proto
これを実行すると、↓のような順序・依存関係でCIが回ってくれます。
開発を進めているとどうしてもウッカリで抜け漏れが出てしまうことがあるので、早めにCIを用意しておくのは正解だったなと改めて感じました🧹
3 ECS
クライアントとE2Eで疎通確認ができる環境が欲しかったので、 SREチーム と相談して、とりあえずの環境としてAmazon ECS(using Fargate)に乗せてみることにしました。
このような構成です↓
とりあえず開発するための環境なので、前段にはALBを置いて、ECRにenvoyとコンポーネントのDockerイメージをアップロードし、ECSでそのイメージを使って起動させる、という簡単なものになっています。
自分はECSを使った経験はなかったのですが、ドキュメントを読んだりググったりしながら試してみたところ、割と簡単に立ち上げることができたし、サービスの更新作業も簡単であるため便利だと思いました✨
Dockerfile, Makefileはこんな感じで用意しています↓
# Dockerfile.sandbox-envoy FROM envoyproxy/envoy:v1.12.1 WORKDIR / COPY ./config/envoy.yml /etc/envoy/envoy.yaml # expose envoy & envoy-admin EXPOSE 9080 9081 CMD ["/usr/local/bin/envoy", "--config-path", "/etc/envoy/envoy.yaml"]
# Dockerfile.sandbox-server ### バイナリをビルドするイメージ FROM golang:1.13-buster AS build_image WORKDIR /go/src/github.com/hogehoge/fugafuga COPY . /go/src/github.com/hogehoge/fugafuga RUN make build-for-docker ### バイナリを実行するイメージ FROM alpine:3.10 RUN apk --update add --no-cache ca-certificates WORKDIR /app COPY --from=build_image /go/src/github.com/hogehoge/fugafuga/bin/server /app COPY --from=build_image /go/src/github.com/hogehoge/fugafuga/bin/grpc-health-probe /app COPY --from=build_image /go/src/github.com/hogehoge/fugafuga/config /app/config EXPOSE 8800 CMD ["./server", "-c", "./config/sandbox.yml"]
# Makefile .PHONY: build-on-docker build-for-docker: GOARCH=amd64 GOOS=linux CGO_ENABLED=0 go build -a -tags netgo -installsuffix netgo $(LDFLAGS) -o ./bin/server ./cmd/server/main.go GOARCH=amd64 GOOS=linux CGO_ENABLED=0 go build -a -tags netgo -installsuffix netgo $(LDFLAGS) -o ./bin/grpc-health-probe ./cmd/grpc-health-probe/main.go (あといろいろ)
特に、コンポーネント用の Dockerfile.sandbox-server
ではmulti stage buildを取り入れているため、ECRにアップロードするイメージサイズが60MB程度で済んでいます🏋
かなりざっくりな紹介になってしまいましたが、以上になります。
引き続き、gRPCとGoの開発もポケモンバトルも頑張ります💪