Upgrade to Pro — share decks privately, control downloads, hide ads and more …

MonorepoとOneTeamでMicroservicesの課題に挑む【DeNA TechCon 2021】/techcon2021-4

MonorepoとOneTeamでMicroservicesの課題に挑む【DeNA TechCon 2021】/techcon2021-4

2019年6月にリリースしたkencom×ほけん。アーキテクチャにマイクロサービスを導入しクラウドはGCP、コンテナオーケストレーションにGKEを採用しリリースまで走り切りました。
リリース後しばらくは新機能や機能改善に伴う開発も順調に進んでいましたが、徐々に開発がスムーズに進捗しない課題に直面しました。
細分化されすぎたマイクロサービスと呼応するように増えていくレポジトリの数、レポジトリ毎に異なる開発スタイル、サーバとインフラチームのコミュニケーション不足によるオペレーションの分断...など、マイクロサービスの採用経験がある方なら共感いただける課題はあるのではないでしょうか?
これらの課題にkencom×ほけんチームはどのように立ち向かっていったのか、課題解決の施策を中心にお話させていただきます。

8a84268593355816432ceaf78777d585?s=128

DeNA_Tech

March 03, 2021
Tweet

Transcript

  1. 王岳宏 and 野澤聡史

  2. 2019年6月 入社 DeSC ヘルスケア システム部所属 SRE・インフラ サーバサイド 2018年12月 入社 DeSC

    ヘルスケア システム部所属 Tech Lead サーバサイド
  3. None
  4. • 保険契約者向けの健康増進サービス • 楽しみながら、健康に。 • 2019年6月に公開

  5. • ハイブリッドアプリ • フロントエンド・バックエンド分離 • バックエンドはGo言語+マイクロサービス採用 • 複数組織で複数マイクロサービスを運用ではなく • 1つの組織で複数サービスをメンテナンス

  6. None
  7. None
  8. • サーバー開発とインフラが別チーム • インフラ作業は都度依頼 • • 開発とインフラ間の

  9. • Polyrepoでサービス毎にリポジトリ作成 • 一年間でリポジトリ数が20弱に到達 • 担当者毎にアーキテクチャが異なる • ( ) •

    • 言語とライブラリのバージョンアップ • 新規作成時のCI/CDのセットアップ、etc...
  10. None
  11. • チームが推進した と • Monorepoでマイクロサービス開発 • 開発とインフラをOneTeamに • 施策の背景や施策から得たもの

  12. • 開発とインフラを に • Polyrepoを に

  13. • Monorepo • Project Layout • 起動とデプロイ • 設計方針と開発ルール •

    OneTeam • チームのあるべき姿 • 実現に向けた施策
  14. Polyrepoで進むチームから生まれるレポジトリが増 大の一途を辿り、コードのメンテナンスには してい く状況の中で進めたMonorepoの経緯や施策の話。

  15. • Monorepo = 1つのリポジトリ • マイクロサービスを並行開発 • 開発効率向上 • 起動コマンド等の共通化

    • 統一性の担保 • サービス作成方法を統一(自由度は許容)
  16. None
  17. alpide ├─ cmd/ ├─ eiger │ ├─ config/ │ ├─

    e2e/ │ ├─ ... │ └─ internal/ ├─ manaslu │ ├─ e2e/ │ ├─ ... │ ├─ internal/ │ └─ pkg/ ├─ pkg │ ├─ log/ │ └─ ... ├─ .env.example ├─ ... ├─ Dockerfile ├─ Makefile ├─ go.mod ├─ go.sum └─ main.go alpide ├─ cmd/ ├─ config │ ├─ eiger/ │ └─ ... ├─ e2e │ ├─ eiger/ │ ├─ ... │ └─ manaslu/ ├─ internal │ ├─ eiger/ │ ├─ ... │ └─ manaslu/ ├─ pkg │ ├─ log/ │ └─ ... ├─ .env.example ├─ ... ├─ Dockerfile ├─ Makefile ├─ go.mod ├─ go.sum └─ main.go alpide ├─ cmd/ ├─ internal │ ├─ eiger │ │ ├─ config/ │ │ ├─ e2e/ │ │ └─ ... │ └─ manaslu │ ├─ e2e/ │ ├─ ... │ └─ pkg/ ├─ pkg │ ├─ log/ │ └─ ... ├─ .env.example ├─ ... ├─ Dockerfile ├─ Makefile ├─ go.mod ├─ go.sum └─ main.go
  18. • 簡易に一括作業が可能 # Dockerfile COPY --from=builder /config /config alpide ├─

    cmd/ ├─ config │ ├─ eiger/ │ └─ ... ├─ e2e │ ├─ eiger/ │ ├─ ... │ └─ manaslu/ ├─ internal │ ├─ eiger/ │ ├─ ... │ └─ manaslu/ ├─ pkg │ ├─ log/ │ └─ ... ├─ .env.example ├─ ... ├─ Dockerfile ├─ Makefile ├─ go.mod ├─ go.sum └─ main.go # Makefile unit-test: go test ./cmd/... ./internal/... ./pkg/... e2e-test: go test ./e2e/...
  19. • cmd: 共通起動コマンド • config: 設定ファイル • e2e: E2Eテスト •

    internal: 各マイクロサービス • pkg: 共通ライブラリ alpide ├─ cmd/ ├─ config │ ├─ eiger/ │ └─ ... ├─ e2e │ ├─ eiger/ │ ├─ ... │ └─ manaslu/ ├─ internal │ ├─ eiger/ │ ├─ ... │ └─ manaslu/ ├─ pkg │ ├─ log/ │ └─ ... ├─ .env.example ├─ ... ├─ Dockerfile ├─ Makefile ├─ go.mod ├─ go.sum └─ main.go
  20. • サービス開発しながら追加とリファクタを継続 • internal/*/pkg -> pkg に移管 alpide ├─ internal

    │ └─ himalaya │ └─ pkg │ └─ lock/ └─ pkg ├─ cache/ ├─ crypto/ ├─ logger/ ├─ ... └─ validation/ alpide ├─ internal │ └─ himalaya └─ pkg ├─ cache/ ├─ crypto/ ├─ lock/ ├─ logger/ ├─ ... └─ validation/
  21. None
  22. • 共通コマンドでサービスを起動 • 共通Docker Imageでサービスをデプロイ • モノリシックとして起動する選択肢も可能

  23. • 入り口は main.go • サービス登録 • main()で起動 // main.go package

    main import ( "***/alpide/cmd" // register services _ "***/alpide/internal/eiger/cmd" _ "***/alpide/internal/manaslu/cmd" _ ... ) func main() { cmd.Execute() }
  24. • main.goで、1つの実行ファイルをビルド • Dockerfileで、1つのDocker Imageをビルド # Dockerfile # ... RUN

    CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ -ldflags='-w -s -extldflags "-static"' -a \ -o /app ./main.go # ... COPY --from=builder /app /app # ... ENTRYPOINT ["/app"]
  25. • サービス名でサービスを独自に起動 # start service manaslu $ /app manaslu

  26. • サービス名+コンポーネント名でサービスの一部 を起動 # start only web server $ /app

    manaslu web-server
  27. • 全サービスの起動が可能 # start all services as monolithic $ /app

    all
  28. None
  29. • サービス名 • 起動時の初期化処理 • 零 OR 複数のサーバー • 零

    OR 複数のバッチ • サーバー数 + バッチ数 > 1 type Service struct { Name string InitFunc func(context.Context) error Servers []Server Batches []Batch }
  30. • サービス名で全サーバーを起動 # start all servers of service himalaya $

    /app himalaya # k8s manifest containers: - name: himalaya command: ["/app"] args: ["himalaya"]
  31. • サーバー名 • 起動時の初期化 • 起動と停止が可能 type Server struct {

    Name string InitFunc func(context.Context) error IServer IServer } type IServer interface { Start(context.Context) error Shutdown(context.Context) error }
  32. • サービス+サーバー名で起動 # start web server of service himalaya $

    /app himalaya web-server # start worker of service himalaya $ /app himalaya worker # k8s manifest kind: Deployment spec: replicas: 5 template: spec: containers: - name: himalaya-web args: ["himalaya", "web-server"] --- kind: Deployment spec: replicas: 3 template: spec: containers: - name: himalaya-worker args: ["himalaya", "worker"]
  33. • バッチ名 • 起動時の初期化 • 起動と中断が可能 • CLIパラメーター対応 type Batch

    struct { Name string InitFunc func(context.Context) error IBatch IBatch } type IBatch interface { Start(context.Context, *cobra.Command) error Interrupt(context.Context) Customize(*cobra.Command) // cli args }
  34. • サービス名+バッチ名+CLIパラメーターで起動 # start batch export of himalaya $ /app

    himalaya export --opt whatever # k8s manifest kind: CronJob # ... spec: jobTemplate: # ... spec: containers: - name: himalaya-batch-export args: ["himalaya", "export", "--opt whatever"]
  35. None
  36. • ドメイン駆動設計(DDD)のBounded Contextでマ イクロサービスの境界を決める • 依存性逆転原則(DIP)でレイヤー化アーキテク チャを採用 • アーキテクチャの多様性・実装の自由度を持たせ たい

    • 厳格なルールは強制しない
  37. • 四層Layer • 依存逆転 • 共通処理利用 の最大化

  38. • コードレビュー必須 • E2Eテスト必須 • カバレージ原則70%以上 baker: 77.6% chiroro: 83.9%

    denali: 63.1% eiger: 55.5% hiyori: 79.5% ibuki: 81.3% kailas: 78.2% manaslu: 74.0% meru: 77.8% otake: 76.8% pumori: 79.0% rainier: 74.5%
  39. None
  40. • 初めからマイクロサービスの選択で疲弊 • 初期はモノリシックのデプロイが望ましい • 要件やドメイン理解の成熟に応じてマイクロ サービスの分割を検討

  41. • 開発時には複数の開発メンバーが並行してマイク ロサービスを作成 • デプロイ時に要件・状況に応じてモノリシックに デプロイするか選択が可能

  42. None
  43. • • マイクロサービスとして効率良く並行開発 • メンテナンスコストが下がる • モノリシックなデプロイの選択が可能 • 共通認識と理解を得た •

  44. • デグレードの可能性 • 自動化テストで保証 • ブランチ・リリースバージョン管理の混乱 • 複数ライン開発でバージョンがバッティング • 稀に発生するが現状は運用でカバー

  45. • monorepoでマイクロサービスの開発実践 • 開発効率向上、統一性と多様性の両立 • マイクロサービス ANDOR モノリシック

  46. Monorepoに舵を切ったチームがマイクロサービス の課題解決を ために分断されていた チームを にするまでの経緯や施策の話。

  47. None
  48. None
  49. • GKE • Istio(OSS) • CircleCI, Flux • GitOps •

    IAM Role for Service Accounts
  50. None
  51. • サーバ開発とインフラ・SREのチームが分断 • 分断により様々な弊害が生じていた

  52. None
  53. • デプロイオペレーションに対する • 過度な運用作業の分担による • 互いの領域は • や が頻出

  54. • コミュニケーションの不足 • オーナシップの欠如 • 心理的障壁

  55. • 、 されている • サーバー開発とSRE・インフラが • 互いの が自然発生している • あるべき姿から

  56. None
  57. • 掲げたVisionは • 担当技術領域の分断を • ◦◦担当や☓☓担当の廃止

  58. • 主体的にサービスを開発できる • 安全にインフラリソースをセットアップできる • 特殊な操作を必要とせず本番デプロイできる • これら ことが理想

  59. • オーナーシップを持つ( ) • を担保したコミュニケーション • 技術領域を超えた成果( ) • を体現

    DeNA QualityはDeNAのミッション・ビジョンを参照 https://dena.com/jp/company/policy/
  60. None
  61. • インフラ領域に苦手意識がある開発メンバーが技 術領域を広げられる環境を整える必要があり迎え 入れるオーナーとしての心構え • 自身もサーバ開発に入り対面の課題を知る • 技術領域を超える際の • メンバーが領域を超える際のストレスを軽減し、

  62. • ストレスの軽減 • 反復なオペレーションの廃止 • 自動化の仕組みの導入 • 心理的障壁を下げる • 苦手意識の排除

    • チームの開発状況の検知を容易にする
  63. None
  64. • デプロイ手順の属人排除 • リリースオペレーションの自動化 • Pull Requestのストレス軽減と簡略化 • ローカル開発環境の共有化 •

    インフラリソース管理の品質担保
  65. None
  66. • Slack BotでChatOps • • GitOpsのサポート • コンテナイメージ差替のPullRequest作成 • リリースとデプロイのオペレーションのサポート

    • リリースバージョンの成果物作成 • コンテナイメージとリポジトリのタグ切り
  67. None
  68. • 運用オペレーションの効率改善に寄与 • • 履歴の蓄積 • 開発状況が検知可能 •

  69. None
  70. • Monorepoの成長 • Bounded Contextの増加 • Contextのオーナーが不明瞭 •

  71. • CI/CDシステム • KubernetesやIstioのレポジトリなどで導入 • 主にPRにおいて自動化機能を提供 • chat-opsを用いたオペレーション • 自動マージ

    • ◦
  72. • OWNERS filesを整理 • • PR作成時に自動でアサイン • アサイン機能以外にも便利機能がある • ex)

    ラベル付与、サイズ通知 • カスタマイズ可能
  73. None
  74. • MonorepoのPRストレス解消 • OWNERS files定義による効果 • • Context理解が進むとReviewerにアサイン • •

  75. None
  76. • • 複数サービス起動のストレス • サービス間通信を確保する難易度 • サーバ開発メンバーの心理的障壁 •

  77. • Docker DesktopのKubernetes clusterを導入 • Deploymentsフォルダにリソースを配置 • デプロイスクリプトを整備 • Skaffoldでローカルクラスターにデプロイ

    • サンドボックスのGCP環境を配備
  78. • • 開発環境と差異のないサービス間通信を実現 • Kubernetesリソースの • ローカル開発からリソース整備を意識 • ローカルからDev環境へのデプロイがスムーズ •

    サンドボックスのGCP環境で知見を貯められる
  79. None
  80. • 設定したリソースの過不足 • ex) CPU/Memory limitの不足 • レビュー時の確認漏れ • ex)

    trueのクォーテーション付け漏れ • デプロイ後にエラー発生 • リソース不備が発覚 •
  81. • Kube-scoreとKubevalのlinterを導入 • zegl/kube-score, instrumenta/kubeval • CIのstepにlintを追加 • デプロイ前にリソース不備が検知可能

  82. • 品質担保に寄与 • 運用の心理的障壁を下げる効果

  83. None
  84. • OneTeam体制の過渡期 • 人員とプロダクト規模から最適判断と評価 • チームの指針が明確になり • 人材の入れ替えに対応できる柔軟性を担保 •

  85. • がチームを成長させる • 相互理解・信頼を得てアクションを実行 • • チーム力の底上げには不可欠 • ことが大事 •

    下げる方法は • 技術的アプローチ以外の施策も多かった
  86. • OneTeamの実現には技術アプ ローチ以外の施策も多い • コロナ禍も重なり が必要に • チームビルドの詳細はメン バー執筆のブログを参照 https://engineer.dena.com/posts/2021.01/kencom-h

    oken-team-building/
  87. None
  88. • 初期のPolyrepoやチーム分離は我々のサービスに は合わず課題に向き合うことでMonorepoと OneTeamの最適な選択ができた • 課題解決から得たものは大きい • 開発と運用効率 • 品質とコストのバランス

    • 継続すべきチームの指針
  89. None