Microservicesにジョインするには知らない技術が多すぎたので一通り触ってみた話

Microservicesにジョインするには知らない技術が多すぎたので一通り触ってみた話:

Mercari Advent Calendar 2018 の2日目はCrossUXチームの@mkazutaka(twitterは@makazutaka)がお送りします。

昨日のアドベントカレンダーに@stanakaさんが取り上げているようにメルカリではMicroservices化に向けて開発が進んでおります。その流れに乗るように前QまでPHPを使って開発していた自分も今QからMicroservicesで実現されているサービスでの開発を行っております。

tech.mercari.com

メルカリではMicroservicesの実現にあたってGoogleCloudPlatform(GCP)、Terraform、Docker、Kubernetes、Halyard、gRPC、Goといったさまざまなサービスからフレームワーク、言語を利用しています。

メルカリでのMicroservices上での開発をする以上、これらに対して多少なりとも理解が必要です。

本記事ではメルカリでのMicroservices上で使われてるサービスを理解するため自分が行ったTerraformによるGCPプロジェクトの構築からSpinnakerによる自動デプロイまでの方法を紹介します。

Spinnakerについては下記の記事もご参照ください!

tech.mercari.com

tech.mercari.com

本記事では、下記のすべてが実現しているプロジェクトを作ることをゴールとします

  • Terraformを用いてGCP上にKubernetes clusterが作成されている
  • Terraformを用いてGCP上に必要なService Accountが作成されている
  • Kubernetes Cluster上でSpinnakerが動作している
  • Kubernetes Cluster上でgRPCプロジェクトが1つ動いている
  • Kubernetes Cluster上でgRPCプロジェクトにリクエストを投げることができる、またレスポンスを受け取ることができる
  • Gitのmasterブランチのレポジトリ内容を更新するとSpinnaker上で自動デプロイ・ビルドが起こる
また本記事で取り扱わないことは下記とします。

  • 各アプリケーションの説明
  • 各アプリケーションのインストール方法
  • 各アプリケーションのコードの詳細な解説
本記事で使う書くソフトウェアのバージョンは下記のようになっております。

||*'-') <  gcloud --version 
Google Cloud SDK 226.0.0 
cloud-build-local 
kubectl 2018.09.17 
 
||*'-') <  hal --version 
1.12.0-20181024113436 
 
||*'-') <  terraform --version 
Terraform v0.11.10 
始める前に以下の準備が必要です。
- GoogleCloudPlatform上でProjectの作成
- Kubernetes Engine APICompute Engine APIを有効化
- ServiceAccountのcredentialファイル(JSON形式)を作成・ダウンロード
- gcloudのconfigurationsの設定

# 自分は下記状態で始めます。Project名等は適宜自分が設定したものに変えてください。 
||*'-') <  gcloud config list 
[compute] 
region = asia-northeast1 
zone = asia-northeast1-a 
[core] 
account = ***@gmail.com 
disable_usage_reporting = True 
project = advent20181202
はじめにGoogle Kubernetes Engine上にKubernetes ClusterをTerraformで作成します。
基本的に下記の公式のGetting Startedを参考に、作成するのをKubernetes ClusterとしLocal Valueを使っています。

locals { 
  project = "advent20181202" 
  region = "asia-east1" 
  zone = "asia-northeast1-a" 
  kubernetes = { 
    name = "terraform-cluster" 
  } 
  network = { 
    name = "terraform-network" 
  } 
} 
 
provider "google" { 
  project = "${local.project}" 
  region = "${local.region}" 
  zone = "${local.zone}" 
  credentials = "${file("account.json")}" 
} 
 
resource "google_container_cluster" "cluster" { 
  name = "${local.kubernetes["name"]}" 
  initial_node_count = 3 
 
  network = "${google_compute_network.vpc_network.self_link}" 
  enable_legacy_abac = true 
} 
 
resource "google_compute_network" "vpc_network" { 
  name = "${local.network["name"]}" 
  auto_create_subnetworks = "true" 
}
作成後、terraform applyをしてKubernetes Clusterを作成することができます。

||*'-') <  terraform init 
||*'-') <  terraform plan  # check 
||*'-') <  terraform apply
つぎに、作成したKubernetes上にHalyardを使いSpinnakerをデプロイします。
公式ドキュメントのリンクを載せているので適宜参考にしてください。

ちなみに自分はAccess Private Docker Registryの設定等をやるのを忘れておりハマったりしてました

#!/usr/bin/env bash 
 
KUBERNETES_CLUSTER=terraform-cluster 
 
########## 1.Download Credential 
gcloud container clusters get-credentials ${KUBERNETES_CLUSTER} 
 
########## 2.Adding Account to Halyard 
########## ref: https://www.spinnaker.io/setup/install/providers/kubernetes-v2/#adding-an-account 
ACCOUNT=my-k8s-v2-account 
hal config provider kubernetes enable 
hal config provider kubernetes account add my-k8s-v2-account \ 
    --provider-version v2 \ 
    --context $(kubectl config current-context) 
hal config features edit --artifacts true 
 
########## 3. Distribute installation 
########## ref: https://www.spinnaker.io/setup/install/environment/#distributed-installation 
hal config deploy edit --type distributed --account-name ${ACCOUNT} 
hal shutdown 
 
########## 4. Choose Storage Service 
########## ref: https://www.spinnaker.io/setup/install/storage/gcs/ 
SERVICE_ACCOUNT_NAME=spinnaker-gcs-account 
SERVICE_ACCOUNT_DEST=~/.gcp/gcs-account.json 
SA_EMAIL=$(gcloud iam service-accounts list \ 
    --filter="displayName:$SERVICE_ACCOUNT_NAME" \ 
    --format='value(email)') 
PROJECT=$(gcloud info --format='value(config.project)') 
 
#-- create service account 
gcloud iam service-accounts create \ha 
    ${SERVICE_ACCOUNT_NAME} \ 
    --display-name ${SERVICE_ACCOUNT_NAME} 
 
#-- add iam=policy to serviceAccount 
gcloud projects add-iam-policy-binding ${PROJECT} \ 
    --role roles/storage.admin --member serviceAccount:${SA_EMAIL} 
 
#-- create service-account key 
mkdir -p $(dirname ${SERVICE_ACCOUNT_DEST}) 
gcloud iam service-accounts keys create ${SERVICE_ACCOUNT_DEST} \ 
    --iam-account ${SA_EMAIL} 
 
#-- create storage 
BUCKET_LOCATION=us 
hal config storage gcs edit --project ${PROJECT} \ 
    --bucket-location ${BUCKET_LOCATION} \ 
    --json-path ${SERVICE_ACCOUNT_DEST} 
hal config storage edit --type gcs 
 
########## 5. Container Registry 
########## ref. https://www.spinnaker.io/setup/install/providers/docker-registry/#google-container-registry 
ADDRESS=gcr.io 
SERVICE_ACCOUNT_NAME=spinnaker-gcr-account 
SERVICE_ACCOUNT_DEST=~/.gcp/gcr-account.json 
 
gcloud iam service-accounts create \ 
    ${SERVICE_ACCOUNT_NAME} \ 
    --display-name ${SERVICE_ACCOUNT_NAME} 
 
SA_EMAIL=$(gcloud iam service-accounts list \ 
    --filter="displayName:$SERVICE_ACCOUNT_NAME" \ 
    --format='value(email)') 
 
PROJECT=$(gcloud info --format='value(config.project)') 
 
gcloud projects add-iam-policy-binding ${PROJECT} \ 
    --member serviceAccount:${SA_EMAIL} \ 
    --role roles/browser 
 
gcloud projects add-iam-policy-binding ${PROJECT} \ 
    --member serviceAccount:${SA_EMAIL} \ 
    --role roles/storage.admin 
 
mkdir -p $(dirname ${SERVICE_ACCOUNT_DEST}) 
 
gcloud iam service-accounts keys create ${SERVICE_ACCOUNT_DEST} \ 
    --iam-account ${SA_EMAIL} 
 
PASSWORD_FILE=${SERVICE_ACCOUNT_DEST} 
 
hal config provider docker-registry account add my-docker-registry \ 
 --address ${ADDRESS} \ 
 --username _json_key \ 
 --password-file ${PASSWORD_FILE} 
 
########## 6. Access Private Docker Registry 
########## ref. https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ 
SECRETNAME=reg-secret 
SERVICE_ACCOUNT_DEST=~/.gcp/gcr-account.json 
kubectl create secret docker-registry ${SECRETNAME} \ 
  --docker-server=https://gcr.io \ 
  --docker-username=_json_key \ 
  --docker-email=user@example.com \ 
  --docker-password="$(cat ${SERVICE_ACCOUNT_DEST})" 
 
########## 7. Deploy 
########## ref: https://www.spinnaker.io/setup/install/deploy/ 
VERSION=1.10.1 
hal config version edit --version ${VERSION} 
hal deploy apply 
 
# 確認 
kubectl get pods --namespace spinnaker
ここまで来たらSpinnakerの環境構築は終わりです。
ちなみにHalyardを使わなくてもGoogle Cloud Platform Marketplaceから1クリックでSpinnaker環境を構築できたりもします。

次に実際のアプリケーション部分の構築に入ります。
messageにGoと入力したらレスポンスのメッセージにBoldと返すアプリケーションを作りましょう。

最終的なディレクトリ構成は下記のようにしています。

||*'-') <  tree 
. 
├── client 
│   └── main.go 
├── proto 
│   └── value.proto 
├── server 
│   └── main.go 
└── value 
    ├── server.go 
    └── value.pb.go
最初にprotoファイルを定義します。
Service名は、valueServiceとします。その中でSayRequestとSayResonseを作成します。

// vim proto/value.proto 
syntax = "proto3"; 
package value; 
 
service valueService { 
    rpc Say (SayRequest) returns (SayResponse); 
} 
 
message SayRequest { 
    string message = 1; 
} 
 
message SayResponse { 
    string message = 1; 
}
上記のファイルを完成させるとprotoファイルをコンパイルします。

||*'-') <  protoc --proto_path=proto --go_out=plugins=grpc:value value.proto
そしてmainロジックを実装します

// vim value/server.go 
package value 
 
import ( 
    "context" 
) 
 
type Server interface { 
    Say(ctx context.Context, in *SayRequest) (*SayResponse, error) 
} 
 
func New() (Server, error) { 
    return &server{}, nil 
} 
 
type server struct{} 
 
func (s *server) Say(ctx context.Context, in *SayRequest) (*SayResponse, error) { 
    if in.Message == "Go" { 
        return &SayResponse{ 
            Message: "Bold!", 
        }, nil 
    } 
 
    return &SayResponse{ 
        Message: "Mercari", 
    }, nil 
}
ここまででロジックは完成です。
実際に動かすためにserveするためのコマンドを実装します

// vim server/main.go 
package main 
 
import ( 
    "google.golang.org/grpc" 
    "log" 
    "net" 
 
    "github.com/mkazutaka/value-service/value" 
) 
 
const ( 
    port       = ":50001" 
) 
 
func main() { 
    lis, err := net.Listen("tcp", port) 
    if err != nil { 
        log.Fatalf("failed to listen: %v", err) 
    } 
 
    s := grpc.NewServer() 
    server, nil := value.New() 
 
    value.RegisterValueServiceServer(s, server) 
 
    // Register reflection service on gRPC server. 
    if err := s.Serve(lis); err != nil { 
        log.Fatalf("failed to serve: %v", err) 
    } 
}
次にserverにアクセスするためのクライアント側を作成します

// vim client/main.go 
package main 
 
import ( 
    "golang.org/x/net/context" 
    "google.golang.org/grpc" 
    "log" 
    "time" 
 
    pb "github.com/mkazutaka/value-service/value" 
) 
 
const ( 
    address     = "localhost:50001" 
    defaultName = "world" 
) 
 
func main() { 
    // Set up a connection to the server. 
    conn, err := grpc.Dial(address, grpc.WithInsecure()) 
    if err != nil { 
        log.Fatalf("did not connect: %v", err) 
    } 
    defer conn.Close() 
 
    c := pb.NewValueServiceClient(conn) 
 
    // Contact the server and print out its response. 
    ctx, cancel := context.WithTimeout(context.Background(), time.Second) 
    defer cancel() 
 
    req := &pb.SayRequest{Message: "Go"} 
 
    log.Printf("Say: %s", req.Message) 
    r, err := c.Say(ctx, req) 
    if err != nil { 
        log.Fatalf("could not: %v", err) 
    } 
    log.Printf("Ans: %s", r.Message) 
}
||*'-') <  go run server/main.go 
||*'-') <  go run  client/main.go 
2018/11/29 16:55:48 Say: Go 
2018/11/29 16:55:48 Ans: Bold!

アプリケーション側は終わりです

最後に、Spinnakerを使った自動デプロイを設定しようとしたのですが、ちょっと長いのでまた今度にします!(こっそり更新する予定です)

今日の記事は、アプリケーションそれぞれの表面的な部分しかなぞっていません。もちろん本番環境で使うなどできないようなものです(実際は監視ツール群などが必要でさらにMicroservices同士でやりとりも必要...)
それでも新しい技術に触れ動かすことができるというのは楽しいものであり、またこれを実際に深い部分まで理解してメルカリ上で動かしているチームがいるというのも非常に尊敬するところであります。

弊社では、新しい技術を学ぶことを楽しめるエンジニアを引き続き募集しています。
最近は東京のみならず福岡にも支社できてるので興味ある方よかったら応募してみてください。

careers.mercari.com

最後まで読んでいただきありがとうございました。

明日 3日目の執筆担当は @shoe116 です。引き続きお楽しみください

オリジナルのエンクロージャ:


コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)