Fargate上のGitLab起点でEC2を更新するGitOpsを実現したい

# はじめに
この記事はDevOps on AWS大全 実践編の一部です。
DevOps on AWS大全 実践編の一覧は[こちら](https://qiita.com/tech4anyone/items/c27e74f9ae569ced259f)。

この記事ではGitLab起点でEC2を更新するCICDパイプラインのアーキテクチャを決める流れを解説しています。

具体的には以下流れで説明します。

- 解決したい課題の整理
- 今回使うAWSサービスの整理
- アーキテクチャの策定
- 策定したアーキテクチャで達成できたこと

AWSの区分でいう「Level 400 : 複数のサービス、アーキテクチャによる実装でテクノロジーがどのように機能するかを解説するレベル」の内容です。

# この記事を読んでほしい人
- DevOpsエンジニアがアーキテクチャを決めるときにどのような思考フローを踏んでいるか知りたい人
- GitLab起点でEC2を更新するCICDパイプラインのアーキテクチャを知りたい人
- AWS Certified DevOps Engineer Professionalを目指している人

# 前回までの流れ
[こちら](https://qiita.com/tech4anyone/items/1f8e8b04d10d111ad00c)の記事で以下のアーキテクチャを策定しました。


# 解決したい課題の整理
現在、解決したいと思っている課題は以下になります。
- GitLabにコミットしたEC2 ImageBuilderの定義を使ってGitOpsを実現したい
- EC2 ImageBuilderを使って作成したAMIをもとにEC2を更新したい
- CICDパイプラインを実行した後、実行完了までの間はマネジメントコンソールを見たくない

ということで、これをどうやって解決するか考えていきましょう。

# 今回使うAWSサービスの整理
今回使う代表的なAWSサービスの概要とベストプラクティスは以下の通りです。

# EC2 ImageBuilder
EC2 ImageBuilderとは、AWSでカスタムAMIを作成、管理、配布するためのサービスです。
カスタムAMIは、EC2インスタンスの起動時に使用される仮想マシンのイメージで、必要なソフトウェアや設定を事前にインストールしておくことができます。

EC2 ImageBuilderでは、イメージレシピと呼ばれる定義ファイルに従って、ソースイメージからカスタムAMIを自動的に生成することができます。
イメージレシピには、ソースイメージの情報やインストールするコンポーネント、テストや検証の手順などが記述されます。
また、イメージパイプラインと呼ばれる仕組みで、定期的にイメージレシピを実行し、最新のカスタムAMIを作成することもできます。

# SSM Paramater Store
SSM Paramater Storeとは、AWSでセキュアにパラメータを保存、管理、取得するためのサービスです。
パラメータとは、アプリケーションやインフラストラクチャの設定や構成に使用されるキーと値のペアです。
例えば、データベースの接続文字列やパスワード、APIキーなどがパラメータになります。

SSM Paramater Storeでは、パラメータを階層的に整理したり、暗号化したり、バージョン管理したりすることができます。
また、AWS CLIやSDK、APIを使ってパラメータを取得したり、CloudFormationやTerraformなどのインフラストラクチャコードに組み込んだりすることもできます。

# ベストプラクティス
EC2 ImageBuilderとSSM Paramater Storeを組み合わせることで、カスタムAMIの作成や管理をより効率的かつセキュアに行うことができます。
以下の表は、そのような場合のベストプラクティスを示しています。

| ベストプラクティス | 説明 |
| --- | --- |
| ソースイメージを最新に保つ | EC2 ImageBuilderでは、ソースイメージとしてAWSが提供する公式AMIや自分で作成したカスタムAMIを指定することができます。ソースイメージは常に最新のセキュリティパッチやバグフィックスが適用されている状態に保つ必要があります。これは、イメージパイプラインを使って定期的にソースイメージを更新することで実現できます。|
| コンポーネントを再利用可能にする | EC2 ImageBuilderでは、コンポーネントと呼ばれる単位でカスタムAMIにインストールするソフトウェアや設定を定義することができます。コンポーネントは再利用可能な形式で作成し、複数のイメージレシピで共有することが推奨されます。これは、コンポーネントをSSM Paramater Storeに保存し、他のイメージレシピから参照することで実現できます。|
| パラメータをSSM Paramater Storeに保存する | カスタムAMIにインストールするソフトウェアや設定には、機密性の高い情報や環境ごとに異なる情報が含まれる場合があります。例えば、データベースの接続文字列やパスワード、APIキーなどが該当します。このような情報は、イメージレシピやコンポーネントに直接記述するのではなく、SSM Paramater Storeに保存し、暗号化し、適切な権限を設定することが必要です。これは、SSM Paramater Storeのパラメータをコンポーネントのスクリプトから参照することで実現できます。|
| テストと検証を行う | EC2 ImageBuilderでは、カスタムAMIの作成後にテストや検証を行うことができます。テストや検証は、イメージレシピに記述された手順に従って実行されます。テストや検証には、カスタムAMIが正しく起動するか、インストールしたソフトウェアが正しく動作するか、セキュリティの要件を満たしているかなどを確認することが含まれます。テストや検証に失敗した場合は、カスタムAMIの作成は中止されます。|

# アーキテクチャの策定
ここからはやりたいことを順番に考えながらアーキテクチャを策定していきます。
## GitLabにコミットしたソースコードを起点にCIを実現したい
まずビルドが必要なアプリケーションをAMIに含める、という前提でCIを考えてみたいと思います。
ビルドが不要なアプリケーションの場合はこの段階は不要です。

アプリケーションのビルドが必要な場合にはCodeBuildを用いてアプリケーションをビルドします。
なお、これまで同様Fargate上のGitLabをCodeCommitにミラーリングしているのでCodeCommit起点で考えます。
そうするとまずは以下のアーキテクチャになります。
![](https://storage.googleapis.com/zenn-user-upload/048c208c5651-20240106.png)

そしてアプリケーションのビルドが終わったらAMIを作成したいのでEC2 ImageBuilderを使いたいのですがCodePipelineから直接EC2 ImageBuilderは呼び出せません。
そのため、CodePipelineの中にEC2 ImageBuilderを組み込む際にはLambdaをキックしLambdaからEC2 ImageBuilderを起動させるという仕組みをとります。

こういった手間が面倒くさい場合にはEC2 ImageBuilderだけでイメージ作成のパイプラインを実現することも可能です。
しかし、ECSやLambdaとパイプラインの仕組みをそろえようとするとCodePipelineに寄せる必要があります。
この辺は、全体のパイプラインの管理運用にかかる手間をどのように考えるか次第なので極論はプロジェクト次第で採用する手段が変わります。

個人的にはEC2だけを扱うプロジェクトは少ないと思うので横並びでCodePipelineにしておいたほうが管理運用が楽と考えています。

話はそれましたが、LambdaからEC2 ImageBuilderを呼び出しAMIを作成し終わったらAMI IDをSSM Paramater Storeに保存しておくようにしましょう。
AMIを更新するたびにCloudFormationする、といった運用が不要になるためおすすめです。

これらをまとめると以下のようなアーキテクチャになります。
![](https://storage.googleapis.com/zenn-user-upload/dd32ee342101-20240106.png)

補足として、ゴールデンイメージを作る運用の場合には上記アーキテクチャの手前にAMI作成のパイプラインをもう1つ追加したカスケード方式が必要になります。

例えばOSのセキュリティパッチをあてた最新のAMIをまずは1つのパイプラインで作り、そのAMIをベースAMIに設定して上記のパイプラインを動かすという作り方です。

## GitLabにコミットしたソースコードを起点にCDを実現したい
AMIが作成出来たら、作成したAMIをもとにEC2を更新していきます。
まずはAMIの内容を確認して承認してほしいので、AMI作成が完了した後に承認ステージを作成します。

続いて、CloudFormation部分です。
すでにAMI IDをSSM Paramater Storeに格納してあるので、このパラメータを呼び出すつくりになっていればCloudFormationを更新する必要はありません。
しかし、ほかの変更、例えばインスタンスタイプの変更などを実行することもあると思うのでお作法としてChangeSetの作成は必ず組み込んでおきましょう。

ChageSetが作成されたら、再び承認を経てEC2の更新を行います。
EC2がAutoScalingの中に入っていればローリングアップデートで一台ずつ順次更新、といったことが可能になるのでここではローリングアップデートで組んでみます。

これらをまとめると以下のようなアーキテクチャになります。
![](https://storage.googleapis.com/zenn-user-upload/62a77c865621-20240106.png)

## GitLabにコミットしたソースコードを起点にChatOpsを実現したい
最後にいつも通り、CodePipelineをずっと見なくて済むようにマネジメントコンソールを見たくなるタイミングでチャットに通知します。
今回だと承認が必要になるタイミングとデプロイが完了したタイミングで通知を飛ばします。
![](https://storage.googleapis.com/zenn-user-upload/96b366f14873-20240106.png)

加えてCodeCommitは1つにしてクロスアカウントの運用を行いたいといういつもの要件を足してみましょう。
これらをまとめると以下のようなアーキテクチャになります。
![](https://storage.googleapis.com/zenn-user-upload/e7e87232a8ec-20240106.png)

# まとめ
本記事ではGitLab起点でLambdaを更新するGitOpsを実現する方法をまとめました。

今回策定したアーキテクチャで実現できたのは以下の項目です。
- GitLabにコミットしたソースコードを起点にAMIのビルドを実行するCI
- GitLabにコミットしたソースコードを起点にEC2をローリングアップデートするCD
- マネジメントコンソールを確認する必要があるタイミングでチャットに通知を飛ばすChatOps

次回はモニタリングとロギングを考えてみたいと思います。