SDPFクラウド/サーバー ESIチームにおけるCI改善の取り組み

この記事は、 NTT Communications Advent Calendar 2022 10日目の記事です。 こんにちは! SDPF クラウド/サーバー ESI チーム入社1年目の飯國 (@guni1192) です。 普段は SDPF クラウド/サーバーにおけるネットワークコントローラ ESI (Elastic Service Infrastructure) を開発しています。

今回は ESI チームにおける CI 改善の取り組みについて紹介します。 CI/CD をセルフホストしている方向けに、CI の Workflow や実行基盤の改善の一例として参考になればと思います。

今までの ESI チームの CI 基盤の課題

ESI チームでは Jenkins を運用していました。 ESIの開発当初(6、7年前)から大きく構成は変わっておらず、チーム内から以下のような問題点があげられました。

  • 課題1: CIのJob内容が Jenkinsfile でコード管理されておらずブランチ単位でのJobの設定変更が不可能
    • 変更する際は管理者となっている社員に依頼しなければならない
  • 課題2: Jenkins Agent に Jobの実行に必要な依存関係(言語, ライブラリ, ミドルウェア)が溜まる
    • サーバのストレージ容量が逼迫する (ストレージ容量を空けるためのオペレーションコスト)
    • ソフトウェアの依存関係を正しくテストできない。 CI の実行環境は毎回作って壊してクリーンな状態にしたい
  • 課題3: Flaky Test が多く、CI の Job が2つ以上溜まっているとほぼFailする
  • 課題4: CI がパスするまでの時間が長い (3-4時間)

この様な状態だと、1営業日で PR をマージすることはほぼ不可能な状態です。

ESIチームのCI基盤の要件

これまでの Jenkins の課題を踏まえ、CI基盤及びパイプラインを再設計することにしました。 以下の要件達成を目指します。

  • 要件1: 開発者自身が必要なワークフローを定義し、バージョン管理できる → 課題1
  • 要件2: コンテナやVMなどの使い捨ての環境でCIを実行したい → 課題2
  • 要件3: 環境要因で Fail しないキャパシティ → 課題3
  • 要件4: CI が長くとも1時間程度で終了する → 課題4

GitHub Actions への移行

GitHub Actions で前述した要件を達成可能か検証しました。 GitHub Actions は GitHub が提供する CI/CD プラットフォームです。 開発者自身がWorkflowをYAML形式で定義し、リポジトリにPRを送ることでCI/CDパイプラインを作成できます(要件1達成)。

GitHub Actions に移行するにあたって、CI の Job を実行するサーバ(Runner)は Self-Hosted Runnerを用います。 Self-Hosted Runner は名前の通り、自分でベアメタルサーバ、VM、コンテナを用意し、その上で CI の Job を実行する Runner です。

Self-Hosted Runner についての社内事例を2つほど紹介します。

engineers.ntt.com engineers.ntt.com

actions-runner-controller + Google Kubernetes Engine

Self-Hosted Runner のオーケストレーションには actions-runner-controller を用います。 actions-runner-controller は Runner を Kubernetes の Custom Resource として扱うことができる Custom Controller です。 actions-runner-controller は Runner を Pod として提供し、使用されたら破棄する Ephemeral Runnerを標準で採用しています(要件2達成)。 また、Runnerの オートスケーリングや Prometheus Metrics の出力などが可能です。

今回は Kubernetesクラスタの管理に Google Kubernetes Engine(GKE) を使用しました。

とりあえず GitHub Actions へ移行してみた

GKEに手っ取り早く環境構築して、Jenkinsのときと同じ様なフローをGitHub ActionsのWorkflowに作って検証してみました。 この時点で要件3、要件4は未達成で、WorkflowとGKEの構成の両方に手を入れる必要がありました。

Flaky Test の解消

Jenkinsを使っていたときから「何もしてないのにCIがFailする」という事象が頻発し、人がCIを利用していない時間帯を見計らってジョブを流すということをしていました。 CI 基盤が開発する上で大きなブロッカーになってしまっていました。

まずは原因を調査するために Grafana, Prometheus, Node Exporter, kube-state-metrics を導入し、Runner のパフォーマンスを計測しました。 原因は、Node の Disk I/O が過負荷になることで、Integration Test内でDBへの書き込みがタイムアウトになることでした。 対策としては大きく2つあげられます。

  • クラスタ全体のDisk I/Oのスループットを上げる
  • タイムアウト値の緩和

今回は、できるだけテストコードを修正して仕様を変えたくなかったため、タイムアウト値の緩和は見送りました。 クラスタ全体のDisk I/O性能をあげるためにクラスタの構成や Kubernetes の設定を修正して、Job を処理できるようにしました。

Disk I/O の負荷分散

まず、Nodeの台数(=local storageの台数)を増やし、負荷を分散させます。 Node数を増やしただけでは、Kubernetes が Pod (Runner) を Disk I/O の多い Node にスケジュールして Disk I/O が偏る可能性があります。 そのため、 Pod Topology Spread Constraints などの仕組みを使って Pod を Node に分散配置します。

しかし、従来の Workflow では1つの Job を実行する Pod の Disk I/O が非常に高いため、1つの Job を複数の Pod に分散させるように Workflow を改修する必要があります(後述)。

ストレージ単体の性能を上げる (SSD化)

Node のローカルストレージを HDD から SSD に変更しました。

クラスタ全体のDisk I/Oのスループットを上げた結果

数ヶ月運用し、今まで Disk I/O 要因で起こっていたと思われる Flaky Test が報告されなくなくなりました(要件3達成)。 それはそれとして、定量的に Flaky Test を検知する仕組みが必要だと考えたので今後の課題にしたいと思います。

CIの実行時間の短縮

次にCIの実行時間の短縮を図ります。 これまで ESI の Integration Test は Docker Compose で API, DB, 外部サービスの mock を含む複数のコンテナを構築し、APIのテストを行っていました。 Jenkins のときは Jenkins Agent はベアメタルサーバで容易にスケールアウトできる環境ではなかったため、 1つの Jenkins Agent 内で 複数の Docker Compose 環境を作ってテストしていました。 この構成だと、並列数(==テスト環境数)が大きく制限されてしまいます。 なぜなら、1つ Runner (Pod) 内で複数の環境を作ってしまうと、1つのNodeのlocal storageに負荷が集中するためです。 Disk I/O の負荷分散について前述しましたが、従来の構成ではほぼ機能しない仕組みとなっているのはこのためです。

Disk I/O の負荷分散及び、並列数をスケーラブルにするため、複数台の Runner によってテスト項目ごとにテストを分散実行するように Workflow を再設計しました。 ESIチームでは大きく分けて14種類のテスト項目がありました。 つまり、14並列でテストを実行すれば、CIの待機時間は 14種類のうち、一番長い実行時間のテスト項目となります。力技ですね。

今回はそれぞれテスト項目のチューニングを行った結果、実行時間が最長のテスト項目は1時間だと言うことがわかりました。 これにより、並列数次第ですが最短1時間程度でCIをパスできます。

Workflowの改修結果

Runnerを10並列で動かした結果、実行時間の大幅な短縮に成功しました(要件4達成)。

まとめ

今回は SDPF クラウド/サーバー ESI チームにおける CI の改善に関する取り組みについて紹介させていただきました。 CIはソフトウェアが常に動くことを保証するために欠かせないものです。 Workflow や基盤の両方から改善することは開発者体験の向上にも繋がります。 GitHub Actions on GKEに移行することで、開発者主体でワークフローを設計し、CIの実行時間の短縮、Flaky Testの解消ができました。

今後は、Self-Hosted Runner の信頼性の計測 (Prometheus Exporter の実装) や、Disk I/O のレイテンシを考慮した Kubernetes Scheduler の実装なども考えていきたいと思っています。 また、GitHub のロードマップに Self-Hosted Runner の Kubernetes への対応が入っているので注目です。

github.com

ここからは宣伝です。 SDPF クラウド/サーバーは国内最大級の IaaS です。ESI チームはネットワーク機器のオーケストレーションを行うためのソフトウェアを開発しています。

ESI チームはインターンシップの募集も行っているため、クラウドサービスのソフトウェア開発に興味がある学生さんはぜひご応募ください。 ESI チームのポストは、エンタープライズ向け大規模クラウド/ネットワークサービスを支えるコントローラ開発内のクラウドネットワーク/仮想アプライアンスです。 締切(12/14)が迫っているため、ご応募はお早めに!

information.nttdocomo-fresh.jp

© NTT Communications Corporation All Rights Reserved.