# 今年の技術選定まとめ この記事はPTA Advent Calendar 2020 19日目の記事になります。 今年は技術的に色々挑戦した年となった気がするので、その備忘録的なものを書いていこうと思います。 ## 技術選定について エンジニアにとって技術選定は重要なトピックです。より良い技術を採用し、導入することにより、パフォーマンスの向上、メンテナンスの容易性、エンジニアのモチベーションアップなどの様々な効果が期待できますが、同時にジレンマを伴う作業でもあります。個人で開発する際はあまり気にする部分ではないかもしれませんが、やはりビジネスとして技術を使っていく以上工数やコストなど何かしらの制約や選択を強いられることとなります。 この記事においては、今年触れた色々な(主にCloudNative系の)技術について、早めに知っておけば良かったこと、触って気づいたことについてを軽くまとめました。 --- # アーキテクチャ編 あまりOSSや特定の技術についてではないですが、決めておいてよかったことや少し変えてみたことについていくつかあげてみました。 ## 設計思想 チームでの開発においてわりと重要になってくるのが設計思想です。 コードのレビューや共同作業を行う際にはある程度の共通認識があるとコードレビューや実際にコードを書く際に効率が良く、大体こんな感じで書こうみたいな方向を決めて進めました。とはいえ、あまりガチガチにコーディング規約みたいなのを定めることはせず、あくまでもPRレビューの指針として程度で導入しました。 ### マイクロサービスアーキテクチャ 以前よりチームではマイクロサービスアーキテクチャを採用しており、Kubernetes上である程度の単位でコンポーネントを分割し、管理出来るような設計をしていました。 マイクロサービスの組み合わせでシステムを構築する上でやはり一番難しいポイントとなるのはコンポーネントの分け方です。 コンポーネントを細かい単位で分けてシステムを構築することによって、疎結合でスケーリングやデプロイが容易になったりする利点を得る反面、ネットワーク部分のレイテンシや通信障害が増したりコンポーネント間通信のセキュリティ、管理コストが増すなどのトレードオフも発生します。 特に、ネットワークのコストは馬鹿に出来ず、HTTPなりgRPCなりで通信した部分についてはそれ相応のコストが掛かるため、ある一定のレイテンシ要求やSLAがあるようなシステムにおいては考慮をする必要があります。 レイテンシを下げるためにGoのReflectionやループ数を減らしてパフォーマンスチューニングしたりするよりもコンポーネント間の通信コストの方が全然高かったりします。また、通信障害を考慮してクライアントにCircuit BreakerやExponential Backoffなどのretry実装を入れる必要が発生したりします。 このようなマイクロサービスにすることによって発生する通信周りの実装コストを節約する方法も複数選択肢あり、コンポーネント間の通信をgRPCにすることにより、[grpc-ecosystem/go-grpc-middleware](https://godoc.org/github.com/grpc-ecosystem/go-grpc-middleware/retry)のようなライブラリや、後述のIstioなどのサービスメッシュなどを用いてCircuit Breakerなどを導入することが可能です。 - コンポーネントの分割方法については責務を意識して分ける - コンポーネント間の通信コストを侮らない ### ドメイン駆動設計/クリーンアーキテクチャ 上記の各マイクロサービスをどのように実装していくかについては、最近話題のドメイン駆動設計やクリーンアーキテクチャなどの書籍を参考に進めました。 以前チームではDDDを導入しようとした経緯があり、その際コンポーネントごとに実装者が異なり、実装者によってレイヤーの責務の考え方やレイヤーの命名規則、ディレクトリ構成などにバラつきがあったりという状況になり、コンポーネントのコードを把握するまでに実装者の思想を理解するといった状態がしばしば発生しておりました。 今回はその反省を多少活かしつつ、新規で作成する全コンポーネントのレイヤー構成の雛形を自分がある程度書いた状態で進める形にしました。 レイヤー構成もあまり厳格なクリーンアーキテクチャを採用はしておらず、分かりやすく主に以下の部分の解決に重点に置いたなんちゃってクリーンアーキテクチャなレイヤー構成にしました。 - 外部コンポーネントと通信を行う部分や副作用の伴うコード部分のテストが容易な状態 - メインロジックだけを分けてテスト出来る状態 - ユースケースだけ見ればそのコンポーネントが何をしているか大体分かる状態 - なるべくレイヤーごとに構造体を用意する。 ## レポジトリ構成 思い切ってモノレポにしてみました。 以前までチームでは各マイクロサービスのコンポーネントごとにレポジトリを作成・管理していましたが、コンポーネントを跨ぐような大きな機能追加の場合には複数レポジトリにPRを出す必要があり、複数のコンポーネント間で共通して使われるライブラリなども別のレポジトリで管理していたため、依存範囲を見つけるのも難しい状態でした。 モノレポにすることでKubernetesのmanifestやterraform、protobufの修正とコードの修正などを一つのPRで確認することが出来るようになったので、コードレビューなどの効率が良くなりました。 ただ、一般的なgoのモノレポ構成にしきってしまうのは少し不安があったため、最悪モノレポの運用が辛くなってきた際にマルチレポに戻せるよう、以下の様な構成でcomponentsディレクトリ以下にコンポーネント単位にディレクトリを切って管理するようにしました。 ``` - components (各マイクロサービスコンポーネント) - component1 - component2 - dataflow - terraform - schema - avro - proto - bq - spanner - kubernetes - dev - stg - prd - pkg (コンポーネント間で共通で使用するコード) ``` 先述のコンポーネント実装のレイヤーごとに構造体を用意する部分について、クリーンアーキテクチャにおいては構造体の責務を明確にさせたり依存を分けたりする目的でレイヤーごとの構造体を用意していましたが、コンポーネント間で引き回すような構造体の変換処理を何度も書くみたいなことが大変だったので、pkg以下に共通entityとしてまとめて扱い、同じ変換処理を何度も書かないようにしました。 --- # Service Mesh編 先程述べたように、マイクロサービスを採用したことによりコンポーネント間の通信を考慮する必要が出てきました。マイクロサービスの基盤としてKubernetesを使用している場合はクラスタ内のDNSなどの機能を提供はしてくれますが、コンポーネント間通信のロードバランシングなどは別途考慮する必要があります。都度envoyなどのproxyをsidecarとして入れることも可能ですが、Service Meshを導入することでそのあたりの設定をシンプルに管理することが可能となります。また、サービスメッシュならではの機能を使用することも可能です。 ## Service Mesh > オープンソース・プロジェクトの Istio のようなサービスメッシュは、アプリケーションのさまざまな部分が互いにデータをどのように共有するかを制御する方法です。 > [サービスメッシュとは](https://www.redhat.com/ja/topics/microservices/what-is-a-service-mesh) 以前よりチームでは最近Service Meshというものが流行っているらしい、いつか取り入れてみたいという話が出ていました。そのときの理解としては、Istioを入れると色々便利らしい、レイテンシがわりとかかる、GKEのクラスタバージョンを上げないといけないから今はやめておこうみたいな状態でした。 前述の通り、既存サービスでもマイクロサービスアーキテクチャを採用していましたが、Istioなどのサービスメッシュは導入しておらず、各マイクロサービスのコンポーネントごとにenvoyのconfigを記述していました。しかし、envoyの設定がバージョンごとに書き方が異なる、jsonで書かれていたりyamlで書かれたり、など管理が大変になっていたので、この際新しい部分ではIstioを採用したいなみたいなモチベーションもありました。 --- サービスメッシュ導入と言っても、Istio以外にも以下のように様々な選択肢があります。 - Istio( ~~envoy~~ IstioProxy) - Google/IBM/Lyftの共同開発のメッシュフレームワーク - 環境非依存(k8s/nomad/consul/eureka/pcf/vm) - Sidecar(Envoy)にデプロイするためコード変更の必要がない - knative - GKE使ってるならわりとこれになりそう - Kong Kuma - Sidecar: envoy - SMI準拠ではない - Kubernetes/VMで動作 - Kong Integration for Ingress - Enterprise仕様? - Linkerd(~~conduit~~ Linkerd v2) - Service Meshという言葉を生み出したプロジェクト - Scala、Finagle、Netty、JVM(v1.x) -> Rust/Go(v2.x) - k8sで動作 - Telemetry and Monitoring Built-in - https://linkerd.io/2/features/telemetry/ - ハイパフォーマンス - シンプル/設定少なくて楽 - Maesh - 他とは少し異なったサイドカーを使わないパターンのサービスメッシュ - Hashicorp Consul Service Mesh - Managed Controll Plane - AWS App Mesh - Anthos Service Mesh(Google Cloud) - Red Hat Openshift - Tetrate ### OPA(Open Policy Agent) 認証でOpen Policy Agentを使ってみました。 --- # 環境編 ## CI/CD/BuildTool/DeployTool - Bazel - Flagger - kpt - skaffold ## Bazel - 構成管理ツール - goの依存解決を行う場合はgazelleを用いる - goのsub package構成が少し難しい ### gazelle - build file generator - Architecture - `.go` / `.s`・`.h` (cgo用) とかのファイルを探して、package名・ライブラリ・タグなどの情報を抽出する - `// +build` - ほとんどの場合はファイルの先頭のみが抽出される(`go/parser`パッケージを利用する - Architecture of Gazelle - https://github.com/bazelbuild/bazel-gazelle/blob/master/Design.rst ### Flagger - Istioを使ったCanaria --- # GCP編 - Cloud Profiler - Error Reporting - Cloud Trace - Cloud Logging(StackDriver Logging --- # 働き方編 ## (ほぼ)フルリモート開発