Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

MySQL Storage 外部ASP

Slide 7

Slide 7 text

社内にk8sを運用している人 も多く知見もありました

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

負荷に応じてノードとPodのオートスケールをさせています ノードの自動プロビジョニング 必要なリソースに合わせてノードプール単位でスケーリングしてくれます spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: grpcserver minReplicas: 3 maxReplicas: 40 metrics: - type: Resource resource: name: cpu targetAverageUtilization: 80

Slide 11

Slide 11 text

annotations: cloud.google.com/neg: '{ "ingress": true }' 有効化もannotationを追加するだけ

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

Envoy   アプリA Envoy アプリB Inbound Inbound Outbound Outbound

Slide 14

Slide 14 text

可用性を高めるEnvoyの機能 Circuit Breaking 過剰なリクエストが来た時に応答不可を防ぐ Outlier Detection Podへの500系や200系の回数をみてクラスタから取り除くか制御 Health Check アプリ側でHTTPのエンドポイントをはやしてLiveness/Readiness Prove gRPCのHealthCheckはSidecarのEnvoyからのみ叩くようにした

Slide 15

Slide 15 text

アクセスログの設定 access_log: - name: envoy.access_loggers.file typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: "/dev/stdout" typed_json_format: start_time: "%START_TIME%" method: "%REQ(:METHOD)%" path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%" protocol: "%PROTOCOL%" response_code: "%RESPONSE_CODE%" response_flags: "%RESPONSE_FLAGS%" bytes_rcvd: "%BYTES_RECEIVED%" bytes_snt: "%BYTES_SENT%" duration: "%DURATION%" x-envoy-upstream-svc-time: "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%" x-forwarded-for: "%REQ(X-FORWARDED-FOR)%" useragent: "%REQ(USER-AGENT)%" x-request-id: "%REQ(X-REQUEST-ID)%" backend_address: "%UPSTREAM_HOST%" client: "%DOWNSTREAM_REMOTE_ADDRESS%" referer: "%REQ(REFERER)%" response_duration: "%RESPONSE_DURATION%" upstream_transport_failure_reason: "%UPSTREAM_TRANSPORT_FAILURE_REASON%" アプリのログの他にも、 Envoyでアクセスログを取得 しています %RESPONSE_FRAGS%で RESPONSEの情報をみるぐ らいに使っています

Slide 16

Slide 16 text

Envoyのメトリクス EnvoyのメトリクスでgRPC等 のstatusなどもとれます。 実際はDatadogAPMで取得し たメトリクスを主に使用して いますが、Envoyでのメトリ クスで監視設定している部分 もあるので、取得していま す。 template: metadata: annotations: ad.datadoghq.com/envoy.check_names: '["envoy"]' ad.datadoghq.com/envoy.init_configs: '[{}]' ad.datadoghq.com/envoy.instances: | [ { "stats_url": "http://%%host%%:8001/stats" } ]

Slide 17

Slide 17 text

共通のEnvoyイメージの用意 共通で使える設定ファイルを内包したEnvoyイメージを使用 YAMLのアンカーとエイリアスで記述量を減らすようにしています。 ゆくゆくはサービス の規模感や体制に合 わせたcontrol planeを導入したい です type: STRICT_DNS lb_policy: ROUND_ROBIN connect_timeout: 0.25s ignore_health_on_host_removal: true http2_protocol_options: {} health_checks: *egress_health_checks outlier_detection: *outlier_detection circuit_breakers: *circuit_breakers マイクロサービスが増え た際はclustersに書き足 していく運用です。 Envoyをv1.15.0にアッ プグレードして、data plane apiの設定をv3に 対応してあります。

Slide 18

Slide 18 text

実際に運用して困ったこと Pod数が増えるとヘルスチェックのgRPCアクセスが劇的に増えて、 API監視でUNKNOWNのアラートを発報してしまった。 http_filters: - name: envoy.filters.http.health_check typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.health_che ck.v3.HealthCheck pass_through_mode: false cluster_min_healthy_percentages: self-grpc: value: 100 headers: - name: ":path" exact_match: /healthz pass_through_mode: falseにし てヘルスチェックの状態を保持 し返すようにしました。 no_traffic_intervalを60sにし大 量のヘルスチェックを飛ばさな いようにしています。

Slide 19

Slide 19 text

1. アーキテクチャの紹介 2. GKEクラスタを本番運用にするためにやった工夫 3. GKEのオートプロビジョニングとスケールアップ 4. gRPCのバランシングのEnvoyの設定 5. リクエストを取りこぼさないためにやったこと 後半:DatadogAPMとOpencensusでのモニタリング

Slide 20

Slide 20 text

Ⅰ リクエストを取りこぼさないためにやったこと① アプリのGraceful Shutdown // Stop datadog exporter cleanly defer a.datadog.Stop() go func() { _ = a.server.Serve(listenPort) }() // Wait for signal sigCh := make(chan os.Signal, 1) signal.Notify( sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) log.Printf("SIGNAL %d received, then shutting down...\n", <-sigCh) a.server.GracefulStop() gRPCのGraceful Shutdownを実 装しました。 GracefulStop()を使っています。 またDatadog Exporterにメトリ クスを投げているためdeferで止 めています。 terminationGracePeriodSecon dsも指定しています。

Slide 21

Slide 21 text

リクエストを取りこぼさないためにやったこと② SidecarのEnvoyの起動順番 1. ignore_health_on_host_removalをtrueにして、 HealthCheckの失敗を待たずにサービスディスカバリから除外 2. アプリの起動前にEnvoyを起動 3. Envoyが終了する前にアプリを終了 #!/bin/sh wget -qO- --post-data='' http://127.0.0.1:8001/healthcheck/fail sleep 30 curl -s -o /dev/null -w '%{http_code}' http://localhost:8001/ready シェル芸でいい感じにしています

Slide 22

Slide 22 text

定期的なクラスタアップグレードや機能追加 定期的・継続的にアップグレードや機能追加をすることで システムの価値を維持・向上し続けることができます。 そのためのリクエストを取りこぼさない工夫でした。 GKEは3つのマイナーバージョンをサポート EnvoyのDataplane APIのLifecycleは最大3つのメジャーバージョン 開発スピードが早い

Slide 23

Slide 23 text

アーキテクチャの紹介 GKEのオートプロビジョニングとスケールアップ gRPCのバランシングのEnvoyの設定 リクエストを取りこぼさないためにやったこと DatadogAPMとOpencensusでのモニタリング 後半:アプリやマイクロサービスについて

Slide 24

Slide 24 text

Kubernatesの監視体制 外形監視: Datadog Synthetic 内形監視: Datadog DeploymentのDesired/Available, PodのRestart回数 GSLBと各APIのレイテンシやStatusの監視, NodeのStatus,ノードのDisk使用量etc ログ監視: Stackdriver Logging トレース/APM: DatadogAPM/OpenCensus 通知先: Slack/メーリングリスト ドキュメント管理: esa Datadogでの監視の知見が多いの で使用していますDashboardを なるべく統一したいのもあります

Slide 25

Slide 25 text

DatadogAPMとOpenCensusでのモニタリング APM(Application Performance Monitoring)でアプリの稼働状況を監視 - DatadogのDashboardに出力したいので設定はAPMの有効化ぐらい - エンドポイント毎にLATENCYとERROR RATEが表示できる # Enable APM and Distributed Tracing apmEnabled: true ◆Datadogはhelmfileで管理しています エンドポイント名

Slide 26

Slide 26 text

DatadogAPMとOpenCensusでのモニタリング OpenCensusでTrace,Stats情報を取得 Datadog Exporterへ送信 - StatsはPrometheus Exporterへ変更を対応中 - Agentのリソース負担を減らすため - DogStatsD経由でメトリクスを送らないため - OpenTelemetryに移行させたい 当初DatadogAPMを有効化するか、自前 でJaegerをたてるか悩み、DatadogAPM のライブラリを使わずにOpencensusで取 得している背景があります

Slide 27

Slide 27 text

OpenCensusでのStats/Tracingでとっているもの gRPC Server/Client - StatsHandlerにocgrpcのhandlerを設定するだけ HTTP Server MySQLドライバー - driverNameの設定時にocsqlでwrapするだけ 他にどんなものをとっ ているか教えて下さい # Server grpc.StatsHandler(&ocgrpc.ServerHandler{}) # Client grpc.WithStatsHandler(&ocgrpc.ClientHandler{}) return &http.Server{ Addr: host + port, Handler: &ochttp.Handler{ Handler: mux, }, }, listenPort ochttp.Handlerでhttp.Handlerを wrapするだけ ochttpにはHTTPヘッダやStatusを取 得してくれる機能があるので、 stats/viewなどがなくても表示可能 // OpenCensus driverName, err := ocsql.Register("mysql")

Slide 28

Slide 28 text

次はアプリ側 のおはなし

Slide 29

Slide 29 text

荻野陽太 CyberAgent, Inc. Ameba事業本部 AmebaPickDiv サーバサイドエンジニア 新卒2年目 Ameba Pickの開発をしています

Slide 30

Slide 30 text

1. マイクロサービスを採用した理由 2. マイクロサービスの設計 3. システム構成について 4. マイクロサービス間でのエラーハンドリング 5. マイクロサービスのデプロイ

Slide 31

Slide 31 text

1. マイクロサービスを採用した理由 2. マイクロサービスの設計 3. システム構成について 4. マイクロサービス間でのエラーハンドリング 5. マイクロサービスのデプロイ

Slide 32

Slide 32 text

Ⅰ 開発初期のチーム状況 サーバーチームのメンバー: 8人 クラウドネイティブを使った開発経験があった人:2人 ほとんどのメンバーがクラウドネイティブを使った開発経験がなかった! 手探りで開発を進めて いきました

Slide 33

Slide 33 text

Ⅰ マイクロサービス採用した理由 システム要件を洗い出したらモノリスだと辛そうだったから 必要なシステム要件 ● アフィリエイト広告の計測 ● 成果報酬の支払い ● 成果通知の受け取り ● アフィリエイト案件の管理 ● 報酬レポート作成 ● アフェリエイターの管理 社内に採用事例があ り知見もあったので 採用しやすかったで す 外部のシステム との連携が必要

Slide 34

Slide 34 text

1. マイクロサービスを採用した理由 2. マイクロサービスの設計 3. システム構成について 4. マイクロサービス間でのエラーハンドリング 5. マイクロサービスのデプロイ

Slide 35

Slide 35 text

マイクロサービスの設計 ① コンテナオーケストレーションにはKubernatesを採用 ● デファクトスタンダードなので採用しない理由がなかった マイクロサービス 間の通信にはgRPCを採用 ● HTTP/2を使うので低レイテンシでマイクロサービス間の通信が行える ● Protoocol Buffersを使ったIDLでクライアント・サーバー間でスキーマを共有 できる ● IDLからクライアント・サーバーコードの自動生成ができる ● エコシステムが充実している

Slide 36

Slide 36 text

マイクロサービスの設計 ② アーキテクチャにはレイヤードアーキテクチャを採用 開発手法にはDDDを採用(しようとした) ● 人員追加や要件追加があり途中からおざなりになった DDDの境界付けられたコンテキストによってマイクロサービスを分割 開発言語にはGolangを採用 ● Kotlinという案もあったが、社内での導入事例や人員採用のしやすさを踏まえ て採用 ● Golangを採用したことで結果的にかなりインフラコストを削減できた

Slide 37

Slide 37 text

1. マイクロサービスを採用した理由 2. マイクロサービスの設計 3. システム構成について 4. マイクロサービス間でのエラーハンドリング 5. マイクロサービスのデプロイ

Slide 38

Slide 38 text

Ⅰ バックエンドアーキテクチャ ユーザ Private Cloud ブログシステム Services(Pods):gRPC GKE Ingress:HTTP MySQL Storage 外部 ASP NAT Rest API gRPC喋れない

Slide 39

Slide 39 text

Ⅰ システム構成 ブロガー Private Cloud ブログシステム API Gateway Micro Service A Micro Service B Micro Service C Routing SSL終端・API認証 Micro Service内でREST to gRPCの変換をしてます REST(JSON) REST(JSON)

Slide 40

Slide 40 text

REST to RPCの仕組み grpc-gatewayを利用している ● .protoからREST to gRPCのリバースプロキシをするコードを自動生成してく れるもの ● https://github.com/grpc-ecosystem/grpc-gateway ● 自動生成されたコードが各マイクロサービスに取り込まれている rpc SearchItems (SearchItemsRequest) returns (SearchItemsResponse) { option (google.api.http) = { post: "/api/affiliate_item/search_items" body: "*" }; } RESTとgRPCのマッピングをちゃんと考 えると面倒なので以下のルールで統一 ● POSTのみ ● パス名は/api/マイクロサービス 名/mathod名のスネークケース

Slide 41

Slide 41 text

Ⅰ 再掲: システム構成 ブロガー Private Cloud ブログシステム REST(JSON) API Gateway Micro Service A Micro Service B Micro Service C Routing SSL終端・API認証 当初はここにREST to gRPCの変換を持たせよう としていました REST(JSON)

Slide 42

Slide 42 text

REST to RPCの仕組み 当初はAPI-GatewayにREST to gRPCの変換機能を持たせようとしていた この方法だとマイクロサービスのデプロイの度にAPI-Gatewayのデプロイ が必要になり辛いのでやめた

Slide 43

Slide 43 text

1. マイクロサービスを採用した理由 2. マイクロサービスの設計 3. システム構成について 4. マイクロサービス間でのエラーハンドリング 5. マイクロサービスのデプロイ

Slide 44

Slide 44 text

Ⅰ マイクロサービス間でのエラーハンドリング 共通のエラーコードを定義して、エラーコードを使ってエラーハンドリン グをしている type ApplicationError struct { code string message string gRPCStatusCode codes.Code } var ( UnknownError = newApplicationError("CM500003", "unknown error", codes.Internal) UnavailableError = newApplicationError("CM503001", "unavailable", codes.Unavailable) ) 共通のエラーコードの定義

Slide 45

Slide 45 text

Ⅰ マイクロサービス間でのエラーの伝播① 1. カスタムエラーを定義 2. カスタムエラーをProtocol Buffersで定義 3. エラー時アプリでカスタムエラーを返す message ApplicationError { string code = 1; string message = 2; } type ApplicationError struct { code string message string gRPCStatusCode codes.Code } var ( UnknownError = newApplicationError("CM500003", "unknown error", codes.Internal) UnavailableError = newApplicationError("CM503001", "unavailable", codes.Unavailable) ) - エラーメッセージ - 共通のエラーコード - エラーに対応するgRPC のステータスコード を定義

Slide 46

Slide 46 text

Ⅰ マイクロサービス間でのエラーの伝播② 4. Server InterceptorでError Detailsにカスタムエラーを突っ込む 5. func ApplicationErrorUnaryServerInterceptor() grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { res, err := handler(ctx, req) if err == nil { return res, nil } ae, ok := err.(*ApplicationError) if !ok { ae = UnknownError.Wrap(err).(*ApplicationError) } st := status.New(ae.gRPCStatusCode, ae.Message()) if st, _ := st.WithDetails(&commonpb.ApplicationError{ Message: ae.message, Code: ae.code, }); st != nil { return nil, st.Err() } return nil, st.Err() } } エラーが起きたらエラーをProtoで シリアライズして詰めている エラーに対応するgRPCステータス コードを設定している

Slide 47

Slide 47 text

Ⅰ マイクロサービス間でのエラーの伝播③ 5. Client InterceptorでError Detailsからカスタムエラーを取り出す func ApplicationErrorUnaryClientInterceptor() grpc.UnaryClientInterceptor { return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { err := invoker(ctx, method, req, reply, cc, opts...) if err == nil { return nil } st := status.Convert(err) for _, d := range st.Details() { switch t := d.(type) { case *commonpb.ApplicationError: return newApplicationError(t.GetCode(), t.GetMessage(), st.Code()) } } //不明なエラー return UnknownError.Wrap(err) } } Error DetailからProtoでシリアラ イズされたエラーを取り出してデシ リアライズしている

Slide 48

Slide 48 text

1. マイクロサービスを採用した理由 2. マイクロサービスの設計 3. システム構成について 4. マイクロサービス間でのエラーハンドリング 5. マイクロサービスのデプロイ

Slide 49

Slide 49 text

マイクロサービスのデプロイ ブランチモデルにはGithub-Flowを採用 CIOpsを採用 ● CI/CDに時間をかけたくなかった ● Manifestとアプリケーションコードを同一リポジトリで管理している ● CIにK8sの強い権限を渡たす必要があったり、Manifestの軽微な変更でもイ メージのビルドが走るなどの課題もある... 将来的にはGitOps に移行していきたい と考えています

Slide 50

Slide 50 text

CI/CDパイプライン GCR 本番クラスタ ステージング クラスタ ステージング 本番 PRマージ 反映 反映 イメージ作成 タグ追加 gitタグ作成 パラメータに作成したイメージのタ グ名が渡される

Slide 51

Slide 51 text

Manifestの管理 最初はKustomizeを使っていた... ● yaml差分を書くのが面倒臭い ● 使い回しがしづらい ● Command Lineからパラメータを渡せないのが辛い 結局Helmを採用した ● Kustomizeに比べて学習コストが高いが一度Chartを作れば使いまわせる! ● Command Lineからパラメータを渡せる!

Slide 52

Slide 52 text

Secretsの管理 最初はkubesecを利用 ● Secret の構造を保ったまま値だけ暗号化してくれるもの ● https://github.com/shyiko/kubesec 現在はhelm-secretsを採用 ● HelmのPlugin ● HelmのValuesファイルを暗号化してくれるもの ● デプロイも簡単! helm install が helm secrets installになるだけ ● https://github.com/zendesk/helm-secrets External Secretsを採用するのもアリだったかも...

Slide 53

Slide 53 text

ご清聴ありがとう ございました

Slide 54

Slide 54 text

CAではSRE・ バックエンドエンジニ アを募集しています https://www.cyberagent.co.jp/careers/