チーム開発の視点が変わる アジャイル開発の新常識 第2回 アーキテクチャから考える大規模アジャイルの最適化

アジャイル/DevOps

2021.07.28

  

*本コラムは、技術評論社「Software Design」2021年1月号に寄稿したコラムを掲載しています。

近年のアジャイル開発をとりまく技術

個人との対話などを重視するアジャイルはもともと、10人程度の小規模開発に適していると言われてきました。しかしながら、近年は大規模開発でもアジャイルを採用するケースが増えてきています。それに伴い、大規模開発によるアジャイルの課題を解決するためにマイクロサービスアーキテクチャを採用し、それを支えるためにDocker、Kubernetes といった技術が使われるようになってきました。

今回の記事では、マイクロサービスというアーキテクチャがなぜ大規模なアジャイル開発に適しているのかを見ていきつつ、マイクロサービスを支えるための技術を紹介していきます。

そのアーキテクチャはアジャイル開発に向く?向かない?

ある会社でWebアプリケーション開発を行うケースを考えてみます(図1)。この開発は納期も短めで、要件や実装の優先度も変更される可能性があったため、柔軟に対応できるスクラムで開発を行うことになりました。そしてプロダクトオーナー、スクラムマスター、そして開発メンバーとして5人がアサインされました。
また、短い納期を考慮し、要求される機能をすべて単一のアプリケーションで提供することを決めました。単一のアプリケーションのため、テストやデプロイが容易であり、扱うデータすべてを管理できることから、「非機能要件などの考慮が減り、開発スピードを向上させられる」と考えました。

図1 小規模でのアジャイル開発

図1 小規模でのアジャイル開発

そして実際に開発が始まり、スプリントを回していき、無事リリースができました。ここまでは、スクラムのよくある事例だと思います。

アジャイルチームの肥大化で起こる課題

リリースしたプロダクトはクライアントにも好評だったため開発の継続が決定し、新機能の要求がどんどん生まれていきました。そして当初の開発メンバー5人だけでは手が回らなくなってきたので、新たにメンバーを増員することが決定しました。
しかしスクラム開発においては、単純に人員を増加しただけでは生産性は向上しないことがあります。なぜならシステムやアジャイルチームの規模拡大に伴って、さまざまな課題が発生するからです。

コミュニケーションの課題

●個人との対話が難しくなる

アジャイルソフトウェア開発宣言注1の中でも挙げられている「個人との対話」、これがアジャイルチームの規模が拡大することにより難しくなります。
作業の関係者が少ない状態では、対面やZoom、Cisco Webex上での対話を気軽に行えますが、アジャイルチームの規模が拡大すると1つの作業でも関係者が増えていきます。するとこれらの関係者すべてが都合のつく場を確保するのが難しくなり、アジャイルの利点であるスピード感が薄れていきます。また、スクラム開発においてのデイリースクラムやスプリントレビューも人員が増えることによって時間がかかり、打ち合わせばかりで作業が進められないという状況が容易に発生します。

●特定のメンバーに負荷が偏る

人数が増えると現メンバーと同一レベルの知識や技術を持つ人員を確保できなかったり、自社だけで人員を確保できなかったりといった事情から社外のビジネスパートナーと契約することが発生すると思います。これに伴い知識が豊富なメンバーへのコードレビューや意思決定の負担が増えたり、ビジネスパートナーが権限的に実施できない作業が増えたりして、自社メンバーへの負荷が高まるのが想像できます。

技術的な課題

コミュニケーションによる課題だけでなく、モノリシックなシステムでは規模が拡大するに伴い、技術的な課題も発生します。それは、アプリケーションを特定機能のみ変更することが難しくなるということです(図2)。

図2 モノリシックなアプリケーションにおける技術的弊害

図2 モノリシックなアプリケーションにおける技術的弊害

●プログラミング言語やフレームワークのバージョンアップが難しい

ある機能で利用しているプログラミング言語やフレームワークの新しいバージョンの機能が使いたくなったとします。その際、バージョンアップがアプリケーション全体に影響してしまうため、変更を行いたい機能以外の箇所の修正も必要になり、修正コストが非常に大きくなってしまいます。

●言語仕様上満たせない要求をカバーできない

たとえば、非常に重い処理(C/C++などの高速な言語でないと非常に時間がかかってしまう処理)が要求される機能を実装する際、現在利用しているプログラミング言語で要求が満たせないと、実装が不可になってしまいます。

●スケーリングがアプリケーション単位になってしまう

アプリケーションの特定の機能がリソースを大量に消費してしまう場合でも、特定機能だけをスケールさせるということができず、アプリケーション単位でのスケールとなってしまいリソースの利用効率が低下します。

アーキテクチャから対策を考えてみる

これらの課題に対して、システムアーキテクチャの観点から解決できないかを考えてみます。スケールやアップデート、プログラミング言語の選択も自由にできるようにアプリケーションを機能ごとに分割してみるのはどうでしょうか。機能ごとにアプリケーションを作成し、これらの分割したアプリケーションが通信を行うことで大きな業務を達成するのです。

また、1つのチームで分割したアプリケーション全体を管理するのではなく、上記で分割したアプリケーションごとにチームを割り当ててみるのはどうでしょうか。これによりチームの規模を小規模に抑えることができ、コミュニケーションの課題も解決できそうです。
それらの特徴を満たすものが、次節で紹介するマイクロサービスアーキテクチャになります。

マイクロサービスとは

マイクロサービスは2014年にThoughtWorks社のJames Lewis、Martin Fowlerによって提唱されたアーキテクチャ注2です。何をもってマイクロサービスと呼ぶかという厳密な定義はありませんが、図3のような特徴があります。

図3 マイクロサービスの特徴

図3 マイクロサービスの特徴

単独でビジネス遂行が可能な複数サービスから成る

巨大なシステムをマイクロサービスとして分割しようとすると、技術的レイヤで分割をしてしまいがちです。フロントエンド、サーバサイド、データベースごとにチームを分割するなどがその一例です。このような分割を行うと単純な変更(たとえば画面上で表示する項目を1つ増やすなど)でさえ、チームをまたいだコミュニケーションが発生し非常に時間がかかってしまいます。

そのため、マイクロサービスではひとつひとつが単独でビジネス遂行が可能なサービスへ分割することが好まれます。これはMelvin Conwayが提唱したコンウェイの法則注3に従ったチーム分割となります(図4)。この分割方法では各ビジネス領域が要求する技術スタックすべてをカバーできるようにチームを構成します。これによってアーキテクチャは組織構造を反映させたものになり、ビジネス領域内の機能変更であれば、ほかのサービスへ影響を与えずに独立して行うことが可能になります。

図4 ビジネスレイヤでのチーム分割の例

図4 ビジネスレイヤでのチーム分割の例

分割された各サービスの挙動

●分割された各サービスはネットワーク経由でやりとりを行う

複数のサービスを利用しての業務を行いたい場合は、各サービスはネットワーク越しに各サービスの利用技術に影響を受けないプロトコル(REST、gRPCなど)で通信を行い、業務を達成します。

●各サービスは独立して、開発・更新・デプロイ・スケールが可能である

前述のとおりマイクロサービスは単独でビジネス遂行が可能な複数のサービスとするのが好ましいです。
各サービスはそれぞれ単独のプロセスで動作するため、ほかのサービスに影響を与えない更新であれば、機能の更新やデプロイが独立して行えます。サービスのリソースが不足している場合はサービス単位でのスケールアップやスケールアウトが可能となります。また、各サービス開発における技術選択(実装言語、ツール)も独立して決めることができます注4

プロジェクトではなくプロダクトである

ここで言うプロジェクトとは、アプリケーションを開発したあとは運用チームへ引き渡し、開発チームは解散するようなものを指します。そうではなく、マイクロサービスではプロダクトのライフサイクル全体に責任を持つことが好まれます。ですのでユーザーの反応を受け、継続的にサービスを改善させていくことになります。
このような進め方は継続的な改善を特徴とするアジャイル開発と非常に相性が良いことがわかると思います。

マイクロサービス導入時に考慮すること

実際にマイクロサービスを導入しようとすると、マイクロサービスの各サービスを独立して更新・デプロイ・スケールできるようなプラットフォームが必要となります。導入において考慮すべき点も数多くあります。

●サービスをどのように分割するか

各サービスをどのような単位で分割するか、1つのサービスをどのくらいの粒度にするかは難しい課題です。サービスの粒度を小さくし過ぎると、1つの業務で大量のサービスを経由するため、機能変更がほかのサービスに与える影響が大きくなります。
それとは逆に、サービスの粒度を大きくし過ぎてもサービス肥大化につながり、どちらにしてもマイクロサービスの利点が損なわれてしまいます。マイクロサービスの利点を受けられるように、ある程度互いに関係が疎になるようなサービス分割を考える必要があります。

● マイクロサービスの導入条件

最初からマイクロサービスでシステム開発を始めることもできますが、前述のプラットフォーム検討や適切なサービス分割の課題から、マイクロサービスの導入は簡単なものではありません。対象のシステムが継続的に開発されるものでなかったり、小規模なものであったりした場合は、マイクロサービスを導入するのはコストに見合っていないと言えるかもしれません。
今回の例のように、単一のアプリケーションを作成後にマイクロサービスへ変更していく方法も考えられます。この場合も運用を続けつつ少しずつサービスを切り出していくか、一気にマイクロサービスとしてリプレースするかという方法を検討しないといけません。

マイクロサービスを支えるコンテナ技術

ここまでは、大規模アジャイル開発にマイクロサービスが適しているという流れを見てきました。ここからは実際にマイクロサービスをどのように動かすかを見ていきます。

仮想サーバにおける課題

典型的な方法として、仮想サーバに直接サービスのデプロイを行い運用する場合があります。しかし、仮想サーバに直接デプロイする方法だといくつか課題が発生します。
1つの仮想サーバに複数サービスを載せる場合、サービスが利用するプログラミング言語やライブラリは共有されます。このため、サービスが要求するライブラリのバージョンなどが異なるとき、うまく動作しない可能性があります(図5左)。1つの仮想サーバに複数サービスを載せる場合、どれか1つのサービスを更新する際も、一緒に載せているサービスが影響を受けてしまいます。

また、サービスごとに仮想サーバを分ける場合、サービスごとに仮想サーバのテンプレートを管理・運用することが必要になり、インフラチームの負荷が高まります。開発チームが各自のローカル環境での情報を正確にインフラチームに連携できないと環境差分が発生し、うまく動作しないことが容易に発生し得ます。

図5 仮想サーバ/コンテナのアーキテクチャの差によるライブラリへの影響

図5 仮想サーバ/コンテナのアーキテクチャの差によるライブラリへの影響

マイクロサービスに欠かせないコンテナ技術

これらの課題はコンテナ技術を採用することで解決できます。図5右のようにコンテナはホストOS部分だけを共有するため、OSさえそろっていれば基本的にどの環境でも動作させることが可能で、環境差分が発生しづらいです。
コンテナに閉じられた環境はほかのコンテナの影響を受けないため、ほかのサービスやチームと共有ライブラリをそろえるといったコミュニケーションをとる必要もなくなります。

また、一般的にコンテナの作成はインフラチームでなく開発チームが管理することが多いです。インフラチームとしてはコンテナの動作が可能なサーバの用意をして、開発チームからコンテナイメージを受け取るだけで済み、インフラチームと開発チームで環境の調整を行う際のコミュニケーションも減らせます。

Kubernetesで多数のコンテナを多数のサーバで扱う

1台のサーバで固定台数のコンテナを動かすだけであれば、Docker Composeなどを利用すれば実現できます。しかしながらマイクロサービスの実運用を考えると、次のような要求が発生するでしょう。

  • 複数のサーバ上で複数のコンテナを動かしたい
  • 複数のサーバにまたがるコンテナ間で通信を行いたい
  • コンテナに割り当てるリソースを制御したい
  • コンテナを動的にスケールさせたい
  • コンテナが止まらないように運用したい

これらを解決するための方法としてコンテナオーケストレーションと呼ばれる技術があります(図6)。

図6 コンテナオーケストレーションの役割

図6 コンテナオーケストレーションの役割

コンテナオーケストレーションとは技術の総称でありいくつかのツールが存在しますが、その中でもとくに人気なのがKubernetesです。KubernetesはもともとGoogleが開発した技術でしたが、現在はCloud Native Computing Foundation注5でOSSとして開発されています。
AWS、GCP、Azure といった主要なクラウドプロバイダがKubernetesのマネージドサービスを提供しており、実質コンテナオーケストレーションツールのデファクトスタンダードとして扱われています。

ここからは、Kubernetesの特徴的な機能を紹介します。

●多彩なリソースで多様なワークフローに対応できる

Kubernetesは多くのリソースを持ちます。これらのリソースはKubernetesクラスタを構築後、その中で論理的なリソースとして扱えます。これらの論理リソースを作成すると、Kubernetesは対応するコンテナ、ボリューム、ロードバランサなどの物理的なリソースを作成します。
これらのリソースを正しく選択、設定して起動させることでマイクロサービスで求められるワークフローのほとんどに対応できます(図7)。

図7 Kubernetesリソースの割り振り例

図7 Kubernetesリソースの割り振り例

●宣言的なリソース生成ができる

Kubernetesではマニフェストと呼ばれる構成ファイルを書き、それをクラスタに渡すことで前述の論理リソースを生成できます。マニフェストは「リソースをN個起動しろ」という命令的な定義ではなく、「リソースをN個起動している状態にする」という宣言的設定(Declarative Configuration)になります。たとえば、リスト1は「nginxコンテナを3つ起動する」というマニフェストになります。

マニフェストをクラスタに渡すと、クラスタ内での現在のリソースの状態と、マニフェストのリソース定義を比較し、リソース定義の状態になるように調整を行います。これを繰り返すことを突き合わせループ(Reconciliation loop)と言います。

リスト1 nginxコンテナを3つ起動するKubernetesマニフェスト

リスト1 nginxコンテナを3つ起動するKubernetesマニフェスト

●宣言的設定と突き合わせループで自律的動作が可能

宣言的なリソース定義と突き合わせループにより、Kubernetesが自律的に動作するようになります。たとえば3つのコンテナを動かして、過負荷により1つのコンテナが落ちた場合でも、自律的に復帰します。このときKubernetesは外部からのリソース再作成リクエストなどを受けず、自律的に現在の状態とマニフェストの情報を比較します。そして不足しているコンテナを1つ立ち上げ、3つのコンテナが動いている状態に戻します。このようなしくみのため、障害に対して強いシステムを構成しやすくなります。

●ほかのチームに影響を受けずにデプロイ・スケールが可能

Kubernetesでは多様なリソースが宣言的・自律的に動作していますが、これらのリソースは基本的に特定のサーバや別のサービスなどに影響されることはありません。そのためマイクロサービスアーキテクチャにおいて、各サービスの開発チームが、ほかのチームとのコミュニケーションを極力行わずにサービスのデプロイ・スケールを行うことができます。

まとめ

アジャイル開発の規模増加に伴いコミュニケーションが取りづらくなる、特定の機能だけのスケールや更新が難しいといった課題が発生することを見てきました。
コミュニケーションの課題を解決するための方法として、チームを小規模に抑えるために機能ごとにサービスやチームを分割するマイクロサービスと呼ばれるアーキテクチャを紹介しました。またマイクロサービスで各サービスが独立して技術選択やスケール・デプロイが行えるための方法としてDockerやKubernetesなどのコンテナ技術を紹介しました。

今回の記事ではマイクロサービス導入のメリットを見てきましたが、マイクロサービスの採用に伴い、トレードオフとしていくつか課題もあります。以降の連載ではこれらの課題に対する対応策なども紹介する予定です。

※図ならびにリストは技術評論社の許諾を得て掲載しています。