hacomono TECH BLOG

フィットネスクラブ・スクールなど施設・店舗のための会員管理・予約・決済システム「hacomono」 開発チームの技術ブログ

Jenkinsによるデプロイ時間を1/2に短縮した話

皆さんこんにちは。SREグループの大塚(@sh_otk0)です。

スーパーマリオワンダーにドハマリしており寝不足が続いています。

hacomonoのSREは主に以下の業務を担当し、サービスの安定稼働を実現するために日々奮闘しています。

  • hacomonoサービスの運用・監視整備
  • パブリッククラウドのガバナンス管理
  • インフラリソースのコスト管理
  • システム運用業務のトイル削減

はじめに

以前の記事で触れた様に hacomono では去年アーキテクチャの刷新を行いました。新しいマルチテナントアーキテクチャは新規環境の格納に利用しており、旧来からご利用頂いているお客様は旧来のシングルテナントアーキテクチャ上で動作しております。

今回の記事ではシングルテナントアーキテクチャのリリース作業を対象にお話します。

背景

hacomono では 2021年から Jenkins を用いた継続デリバリーを運用しており、隔週でリリースを行なっています。

Jenkinsを使用した継続デリバリーの運用は、最初はスムーズで問題がなかったものの、hacomonoサービスの利用者数と環境の複雑さが増加するにつれて、ジョブの実行に約300分かかるような状況となってしまいました。

Jenkinsジョブの構造

所要時間の肥大化の原因を説明する前に、Jenkinsジョブの構造について説明します。Jenkinsジョブは入れ子になった構造であり、直列および並列処理が複雑に絡み合っています。

パイプラインは以下のようになっています。

  1. 実行環境はマスターノード1台のみです。
  2. 複数の個別環境をグループ化し、これらのグループ単位でステージが順番に実行されます。
  3. 各ステージに所属するターゲットは並列に処理されます。
  4. 各タスク内でターゲットのサーバ群に対してAnsible Playbook(以下、デプロイ処理)が実行されます。
node('master') {           <--1
  stage(groups) {          <--2
    parallel(
      target : tasks(      <--3
        ansible-playbook() <--4
      ) ,
      …
    )
  },
  …
}  

※ ターゲットはシングルテナントの個別環境を指し、個別環境は冗長化された複数台のサーバで構成されます。

原因の特定

Jenkinsジョブの所要時間の肥大化の原因は以下の2つです。いずれもサービス拡大に起因していました。

①ステージの直列処理の増加

各ステージのデプロイ処理を順番に実行する必要があり、ステージの数が増えたことでこの直列処理がJenkinsジョブの実行時間を伸ばしていました。

②タスクの増加

各ターゲットのサーバ冗長化台数が増えると、各ターゲットのタスク所要時間も増加します。Jenkinsジョブはステージ内の全てのターゲットの処理が完了するまで次のステージに進めず、これが各ステージの所要時間引き上げていました。

Jenkinsジョブの所要時間を図示すると以下のようになります。

対策①

原因①に対する対策を行います。

①ステージの直列処理の増加

Jenkinsのジョブは、複数のステージから成り立っており、各ステージは順番に実行されます。各ステージには、一定数のターゲットがグループ化されており、これらのターゲットは並列に処理されます。

ステージの数を削減するために、各ステージに関連づけられたターゲットの数を増やす対策を実施しました。その結果、ステージの数を 7 から 4 に削減できました。

# 対策前
Jenkinsジョブ
├── stage1
|   ├── target1
|   ├── …
|   └── target15
├── …
└── stage7
    ├── …
# 対策後
Jenkinsジョブ
├── stage1
|   ├── target1
|   ├── …
|   └── target30
├── …
└── stage4
    ├── …

対策②

原因②に対する対策を行います。

②タスクの増加

まず、タスクの構成について説明します。タスクは複数台のサーバで構成されるターゲットに対してデプロイ処理を実行します。このデプロイ処理はAnsibleの並列設定値である「forks」に基づいて並列で実行されます。ただし、forksの値はターゲットのサーバ数よりも低いため、全てのサーバでデプロイ処理を完了するには複数回のループが必要です。

例えば、20台のサーバから成るターゲットに対してforksの値が5の場合、タスクは4回ループすることになります。

タスクのループ回数を減らすために、Ansibleの並列度を増やし、1回のループでより多くのサーバに対してデプロイ処理を行うようにしました。その結果、タスクの最大ループ回数を 5 から 2 に削減できました。

# 対策前
forks = 5
# 対策後
forks = 15

対策③

対策①および②は、処理の並列数を向上させる修正をであり、サーバ負荷が増加する可能性があります。これに対処するため①②を段階的に適用しながら、サーバの負荷量を確認しつつ、サーバスペックを検討していきました。

単純計算で処理の並列度が6倍(対策①は2倍 * 対策②は3倍 = 6倍)になる想定でしたが実績値ベースで約2倍の負荷量に収まったことから、サーバスペックを1段階アップする対応としました。

# 対策前
インスタンスタイプ : m5.xlarge
vCPU : 4
メモリ : 16GiB
# 対策後
インスタンスタイプ : m5.xlarge
vCPU : 8
メモリ : 32GiB

結果の比較

最終的にJenkinsジョブ及びAnsibleの並列度を調整したことで、Jenkinsジョブの所要時間は 1/2 になりました!!!

最後に

今回は、弊社のシングルテナントアーキテクチャにおけるJenkinsジョブの構造と、それに伴うパフォーマンスチューニングについてお話ししました。Jenkinsジョブの処理時間が長くなった問題に対処するため、ボトルネックが生じていた箇所の並列度を調整し、処理時間の短縮を実現しました。

今後の展望として、サービスの更なる拡大を考慮し、AMI(Amazon Machine Image)ベースのデプロイ方式への移行を検討しています。インフラコスト削減、ビルドの安定化、デプロイ時間の短縮(予測値20分)、そして将来的なサービスの拡大にもスムーズに対応できる体制整備を目指しています。

今後のテックブログで、この移行プロセスや得られた成果について続報をお知らせできればと考えています。

お楽しみにしていただければ幸いです。

Fin.


株式会社hacomonoでは一緒に働く仲間を募集しています!
採用情報や採用ウィッシュリストも是非ご覧ください!