トレタ開発者ブログ

飲食店向け予約/顧客台帳サービス「トレタ」、モバイルオーダー「トレタO/X」などを運営するトレタの開発メンバーによるブログです。

gRPCとGoでサーバサイド開発を始めるために用意したこと

トレタ 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が回ってくれます。

f:id:rkmathii:20191207092148p:plain

開発を進めているとどうしてもウッカリで抜け漏れが出てしまうことがあるので、早めにCIを用意しておくのは正解だったなと改めて感じました🧹

3 ECS

クライアントとE2Eで疎通確認ができる環境が欲しかったので、 SREチーム と相談して、とりあえずの環境としてAmazon ECS(using Fargate)に乗せてみることにしました。

このような構成です↓

f:id:rkmathii:20191207061052p:plain

とりあえず開発するための環境なので、前段には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の開発もポケモンバトルも頑張ります💪

© Toreta, Inc.

Powered by Hatena Blog