サーバーレスにおける安全なCI/CD

こんにちは、中山( @k1nakayama ) です。

この記事は、AWS LambdaとServerless Advent Calendar 2021 の13日目です。

サーバーレスアプリケーションは、AWS LambdaのようなFaaS以外にも様々なサービスと連携し構成されています。
そのため、開発時にローカル環境上で十分なユニットテスト等が行われていた場合でも、デプロイしてみてから様々な問題に遭遇するということは、珍しくありません。
開発環境へのマージである場合でも、複数人で開発を進めている場合、問題のあるデプロイが行われた際には、開発が中断する可能性もありますし、出来る限りそのような問題がある状態でマージしたくはありません。

この記事では、このような問題に対処し、安心してデプロイを進めていくためのCI/CDについて紹介していきます。

  • チームでサーバーレスアプリケーションの開発を行っている方
  • サーバーレス開発を進める上でのCI/CDについて興味のある方
  • サーバーレス開発を効率的に進めていきたい方
  • PRマージしてデプロイしてから問題に気づいた、という経験をお持ちの方

一般的なCI/CDパイプライン

私が様々な開発現場やDevOps関連のセミナー等で見てきたCI/CDパイプラインは、下記のようなものでした。

Gitリポジトリにコミットされ、mainブランチ(またはmasterブランチなど)などの特定のブランチにプッシュ(①)されたことをトリガーに、自動的なビルドやテストが実行され(②)、ステージング環境へのデプロイが実行される(③)。その後、ステージング環境を確認した後、手動承認を経て(④)、プロダクション環境へデプロイが実行される(⑤)、といった流れです。

そして、プロダクトの開発を継続的に安定して続けていくために、多くの場合、開発環境、ステージング環境、本番環境(プロダクション環境)の3つのステージを設けているケースが多く、この場合、開発環境へのデリバリーのためのCI/CDパイプラインは、①②③のフェーズで構成されているものが多い印象です。

上記の例のように開発環境が用意されている際に、開発環境に直接変更を加えることは禁止され、フィーチャーブランチを都度作成し開発を進めていった上で、開発環境用のブランチ(developブランチなど)にマージすることで開発環境用のCI/CDパイプラインによりデリバリーされる流れとなります。

安全でないデプロイの例

上記のような流れでデリバリーを進めていった際、フィーチャーブランチで開発している内容について検証することは、一般的にローカル環境で行います。
ローカル環境での検証方法は、一般的にモックの使用や、ローカル環境に構築したクラウドサービスの代替え環境を使用したユニットテストで行っていくことになります。

サーバーレスアプリケーションを開発していく際には、AWS Lambdaを中心としてコードの開発を進めていくことが多いものの、Lambda上で動くアプリケーションは、様々なサービスからLambda Functionが呼び出されたり、Lambda Functionから様々なサービスを利用したりしていることが多いです。
そのため、このようなローカル環境上でのテストには、下記のような点が十分に検証できていないケースがあります。

  • モックや代替え環境の挙動が実際と異なっている
  • 実行権限等の設定が不足している
  • レートリミット等、様々なクオータ制約が考慮されていない
  • 関連しているリソースの指定を行う際の、リソース情報の設定内容に誤りがある(タイポなど)
  • IaCにより構成したリソースの設定に抜け漏れがある
  • CloudFormationの実行時に問題があり、正常にデプロイが完了しない

いかがでしょうか?
サーバーレス開発をこれまでに経験されてきた方の場合、上記に1つ2つは心当たりあるのではないでしょうか?

弊社で開発していく際にも、原則としてTDD(テスト駆動型開発)で進めていき、Pythonの場合はmotoというモッキングライブラリを使用してユニットテストを実装していますが、とても良くできているライブラリではあるものの、やはり実際のSDKの挙動とは異なっていることもあり、デプロイ後に問題に気づくケースなどは、過去に多々ありました。
また、実行権限の不足などはユニットテストの実行に関わらず遭遇しやすい問題です。

このような問題がある状態で、フィーチャーブランチがプッシュされ、プルリクエスト(マージリクエスト)が行われて、あなたがレビュアーとしてアサインされた際、これらの問題があることをしっかり気付ける自信はありますか?
正直なところ、私は気付けず、何度となく開発環境へのデプロイ後に問題発覚といったケースを多く経験してきました。このような問題が注入された際、これが開発環境であったとしても時期リリース対象の環境であることに変わりはなく、問題の大きさによっては一時的に開発チーム全体に迷惑を掛ける可能性もあります。
更に言えば、上記でお伝えしたようなCI/CDパイプラインで進める場合、developブランチにマージがされてしまったあとで、テストやデプロイが実行されるため、実際にデプロイがエラーになるようなコードも、マージ済みの状態となってしまうので、コード管理を行う上でこれらのコードはマージ前に気付ける状態にしたいですよね。

安全なCI/CDパイプラインの例

上記のような安全ではないデプロイを回避するためのCI/CDパイプラインは、どうあるべきでしょうか?

フィーチャーブランチへコミット&プッシュされると(①)、自動的にパイプラインが実行されます。
最初にビルドやテストが実行され(②)、フィーチャーブランチのデプロイが実行されます(③)、コード開発者はデプロイされた環境を実際に確認し、意図したとおりに動作していることが確認できたらプルリクエスト(マージリクエスト)を行います。レビュアーは③でデプロイされた環境の実物と、ソースコードを確認し問題がなければマージします(④)。再度ビルド&テストを実行し(⑤)、開発環境へデプロイを行います。

このようなパイプラインにすることで、上記で挙げたこれまでのCI/CDにおける問題点を解消しやすくなります。更には、ローカル環境で確認しにくいような動作を検証しつつ開発を進めていきたい場合にも、フィーチャーブランチにプッシュするだけでデプロイが実行されるため、とても開発効率高く進めることができます。

レビュアーにとっても、実際にデプロイされた結果をみて判断することが出来るため、ソースコードの書き方等のチェックを中心にコードレビューを行い、実際の動きについての確認は実物で行うということができ、レビューに掛ける時間も最小限に抑えることが可能になります。

GitLabを使用したCI/CDパイプラインの構築

上記のようなCI/CDパイプラインを構成していくためには、原則として全てのブランチへのプッシュに対しCI/CDパイプラインが実行できる必要があります。
AWSにもCodePipelineというCI/CDを実行するためのサービスがありますが、予めトリガーとなるブランチを設定する必要があるなど、今回のような構成を行うための柔軟なパイプラインを構築することは難しいです。

そこで、弊社では少し前からGitLabを使用したCI/CDを構築しています。
GitLabを使用した場合、リポジトリのルートに.gitlab-ci.ymlといったファイルを置き、その中で様々なCI/CDパイプライン上の動作を定義することで柔軟なパイプラインを構築することが可能です。

上記は、GitLab上のマージリクエストの画面ですが、フィーチャーブランチにプッシュされた時点で既にテスト・デプロイを行うためのパイプラインが実行され、その結果がこの画面上で確認できます。
レビューを行う方は、その結果を見てコードレビュー&デプロイ結果のレビューを行いマージを行います。

弊社の上記のプロジェクトの場合、UIの確認も行えるように、イシュー番号が入ったサブドメインのサイトをデプロイする構成になっており、フィーチャーブランチ毎にサイトを構築できる仕組みにしています。

バックエンドの機能を構築する場合でも、ユニットテストの他にAPIテスト(インテグレーションテスト)などを、フィーチャーブランチデプロイ前のテストフェーズで実行することにより、権限不足のチェックや実際の挙動が正しいかなどの確認までを自動で行うことができます。

もちろん、フロントエンドについても同様で、CypressやAutifyといったUIテスト(E2Eテスト)を自動実行することで、マージする前に十分な確認が行える状態を作ることが可能です。

更に、GitLabを使用した場合、SAST(Static Application Security Testing)を自動的に実行する機能が無料プランから使用可能なため、セキュリティを考慮したテストまでを実行することができます。

フィーチャーブランチのデプロイをする上で考慮すべき点

上記で紹介したようなフィーチャーブランチのデプロイを行うCI/CDパイプラインを構成すると、開発効率が向上し、安全に開発を進めていくことが出来る事がわかったかと思います。

このようなCI/CDパイプラインを構築していく上で、考慮しておくべき点は下記のとおりです。

  • フィーチャーブランチとしてデプロイしやすくするために、ブランチ名の命名規則を考えておく
    • 動的なURLやスタック名等に使用しやすくするために、フィーチャー毎に変わる数値を含めておくと良い
  • フィーチャーブランチでのデプロイ時には、スタック名がフィーチャー毎に変化するようなルールでスタック名を構成する
  • デプロイする各リソース(Lambda Function、S3バケット、DynamoDB、IAM Roleなどなど)は、原則としてリソース名を指定することが必須でないリソースは名前を指定しない形で定義する。
    • リソース名の指定が必須のものは、スタック名と同様にフィーチャー毎に変化する名前になるように考慮して設定する
  • バックエンド、フロントエンド共に手動設定を行うことなく全体の構成を自動デプロイできるように、IaCで構成する
  • ブランチがマージされた際に、フィーチャーブランチ用のデプロイ済み環境を削除するようにする
  • S3やデータベース等の削除ポリシーがデフォルトで保持になっているリソースがあるので、フィーチャーブランチのデプロイの際は、削除ポリシーを削除に設定する
  • インスタンス系のリソースなど、デプロイ後削除するまでの時間が課金対象となるリソースについては、デプロイされた数の環境分だけコストが発生する
  • リソースによって、リージョン毎に作成できるリソース数に制限があるため、並行して開発を進めているフィーチャー数分の環境がデプロイされても問題が無いように、制限緩和等を行っておく

上記のいくつかは、フィーチャーブランチのデプロイに関わらず考慮するべき点も含まれますが、いざフィーチャーブランチ毎にデプロイを行おうとすると、上記のような点で躓きやすくなるかと思います。

まとめ

今回は、サーバーレスアプリケーションの開発における、安全で開発効率のよいCI/CDの構築について紹介してきました。
このような環境を1度作るだけで、その後の生産性を大きく向上させられる可能性もありますので、是非検討してみてはいかがでしょうか?

弊社では、サーバーレスアプリケーションの開発について、お客様と共に開発を進めていき、お客様社内でのサーバーレス開発の内製化を支援することも提供しております。(内製化支援
今回ご紹介したCI/CD等の構築の仕方などについても、このような内製化支援でお客様にご提案していくことも行っています。
AWSを活用したサーバーレス開発やDevOps等について、課題がございましたら是非ご相談ください。

無料相談実施中
AWSを使用したサーバーレスアプリケーションの構築
サーバーレス開発内製化、AWSへの移行等
様々な課題について無料でご相談お受けいたします。
最新情報をチェックしよう!