自己紹介
はじめましてSREとして従事しているもいもいと申します。
私はインフラエンジニアとして約7年ほど金融系のSIerとして業務に従事してきました。
割とガチガチな運用要件やセキュリティ要件の対応を主務としており、前職では書物(設計書)などを書いたりすることが多かったです。
ずっとSIerとして業務に従事してきて自社開発やSaasビジネスをしている企業で
看板を背負って働きたいという思いから現職に転職しました。
今ではSREとしてコンテナ開発のリプレイスや運用などを担当しています。
SREと言っても私自身全然フルスタックではなくインフラ部分を担当しています。
(現在アプリ領域も勉強中!!)
この記事の目的
今回のPRJでEKSでの開発からECSへの基盤リプレイスを実施しました。
その中でCI/CD周りも一新することになり、
CI/CDの設計をする中で学んだことや考慮したことを残しておきたいと思います。
この記事が初めてCI/CDを設計・実装する人にとって有益な情報になるように善処します。
なぜEKSからECSに??
当社が主要システムの1つでEKSをやめることにした大きな理由としては、以下の3つです。
- アプリとインフラ役割分担の明確化
- 既存のEC2環境の統一化を考えた時に、統一的なアーキテクチャへの移行の際に、シンプルなECSにしたい
- アップデートに対する知識の追従コストが高かった点の改善
この辺にの詳細については同僚が書いた記事があるため、そちらをご参照ください!
https://qiita.com/numa-numa/items/3662bb3a7827f2385c48
CI/CDって?
CI/CDとは
「Continuous Integration/Continuous Delivery」の略で、日本語では継続的インティグレーション/継続的デリバリーといいます。CI/CDは1つの技術を指すものでなく、ソフトウェアの変更を常にテストして自動で本番環境にリリース可能な状態にしておく、ソフトウェア開発の手法を意味します。CI/CDを取り入れると、バグを素早く発見したり、変更を自動でリリースしたりできるようになります。
CI/CDの運用イメージと要件を整理
まずはCI/CDを設計するに当たり、GitとCI/CDの関係性を決めなければなりませんでした。
要件としては以下の感じでした。
(本当はもう少し要件がありましたが、ここでは2つだけ抜粋)
- CIとCDは権限を分離したい
- CIとCDで役割分担を明確にし、責任分界点を設ける
- タスク定義はインフラで管理したい
- タスク定義やインフラ管理はIaCで管理したい
実装方針をきめる
まずは基本方針としてCIOpsで進めることにしました。
※単純なCIOpsではないのでもしかしたら当社の実装方法はCIOpsでは無いかもしれないです
CIOpsとは
CIOps とは GitHub Actions や CircleCI などの CI ツールを使用してデプロイまで行う方法です。
これはアプリケーションリポジトリ側にデプロイに関する情報を持ち、アプリケーションコードの git push をトリガとして、インフラ操作を伴うデプロイ処理まで行うものです。
CIOps のメリット・デメリットを以下に示します。
メリット
- 一つの CI ツールの中でデプロイのパイプラインまで管理できるので、単純
- 開発者にとっては、デプロイのタイミングが図りやすい
デメリット
- CI ツールにインフラに対する強めの権限が必要となることが多い
- 場合によっては CI ツールからの WebHook を通すためにインフラへ穴あけが必要になる
- デプロイに関する設定変更のたびに、インフラ管理者が各アプリケーションリポジトリに設定を撒いて行く必要が出る
CI と CD の分離
CIOps はそのシンプルさがメリットですが、アプリケーション側にデプロイコードが存在することで、
- 予期しないタイミングで設定が壊されてしまったり
- 運用するアプリケーションが多くなると設定追加や変更が大変
というように、インフラ面での負担が徐々に増加してしまうことがデメリットとなっていきます。
加えて、最小権限の原則の面からも、性質の異なるワークフローは分離して管理するほうが安全ということになります。
このため、当社ではECS へのアプリケーションのデプロイでは CIOps を行わず、CI と CD を明確に分離しました。
じゃあ具体的にどんなふうに分離したの?
まずはCIとCDでツールを分離しました。
CIはCircleCI、CDはcodepipelineとしました。
CI の役割
アプリケーションのインテグレーションを役割とします。
責任の主体は開発チームであり、ツール選定の権利も開発チームにあります。
ただし、選定基準については費用対効果を考慮したものである必要があります。
コードの push をトリガーとしてブランチ戦略に沿った形で CI を実行し、以下の成果物を特定の場所に配置するまでを責務とします。
- ビルドしたコンテナイメージ
- デプロイ対象環境に対応した AWS アカウントの ECR へ push
- デプロイに必要なメタ情報(imagedefinitions.json)
- デプロイ対象環境に対応した AWS アカウントの S3 へ PutObject
このため、CI ツールが AWS に対して必要な権限は以下のみとなります。
- ECR リポジトリ認証とコンテナイメージのアップロード
- 特定の S3 バケットへのオブジェクトのアップロード
CD の役割
CI にて生成された成果物のデプロイを役割とします。
責任の主体はインフラチームであり、デプロイ対象によらず極力同一のツール・仕組みで動作するように構成します。
CI での成果物の生成をトリガーとして、メタ情報に従い ECS クラスター上の対象サービスにアプリケーションをデプロイするまでを責務とします。
CI と連携している点は CIOps と同様ですが、メタ情報の生成をトリガーとすることで間接的な連携を実現します。
何らかの不具合でリランやロールバックを行う場合にはメタ情報の修正のみで済み、CI まで遡る必要はありません。
AWS リソースの変更は CD と分離し、すべて Terraform を用いて運用管理します。
CD(CodePipeline)の処理フロー
CI ツールに CircleCI を用い、2つのコンテナが存在する ECS タスクへのデプロイを例に、CD の処理フローについて記載します。
(1) 特定 branch/tag push をトリガーに CircleCI ビルドが起動
特定 branch/tag が push されることにより、アプリケーションリポジトリのブランチ戦略に則りビルドが起動します。
※以下はサンプルブランチと環境が1対1となるようなブランチ戦略の場合
CI 動作の標準的な想定は以下のとおりです。
| イベント | lint/テスト | 成果物 | デプロイ先env |
| --- | --- | --- | --- | --- |
| git push (dev branch) | ○ | ○ | dev |
| git push (dev1 branch) | ○ | ○ | dev1 |
| git push (dev2 branch) | ○ | ○ | dev2 |
| git push (dev3 branch) | ○ | ○ | dev3 |
| git push (stg branch) | ○ | ○ | stg |
| git push (semantic version tag) | ○ | ○ | prod |
成果物は以下の2つに規定します。
-
ビルドしたコンテナイメージ
- タグ形式は
${env}-${commit hash}
- タグ形式は
-
デプロイに必要なメタ情報(imagedefinitions.json)
[ { "${AWS_ECR_ACCOUNT_URL}/${ACCOUNT_ID}/api:${IMAGE_TAG}", "name": "app-container" }, { "${AWS_ECR_ACCOUNT_URL}/${ACCOUNT_ID}/front:${IMAGE_TAG}", "name": "nginx-container" } ]
(2) CI からの成果物を push
CircleCI ビルドによる成果物の push 先は、ブランチ戦略に則りインフラの各環境にルーティングされます。
成果物 | push 先 |
---|---|
ビルドしたコンテナイメージ | https://${aws_account_id}.dkr.ecr.ap-northeast-1.amazonaws.com/${account_id}/${service_name} |
デプロイに必要なメタ情報(imagedefinitions.json) | s3://${env}-ecsdef-${aws_account_id}/${service_id}/imagedefinitions.json.zip |
※ zip 圧縮 |
(3) 成果物の push で CodePipelineがトリガー
具体的には、2つの成果物のうち「デプロイに必要なメタ情報」の push をトリガーとします。
もし、ECR へのコンテナイメージ push をトリガーにすると、ECS タスクに複数の複数のコンテナイメージが存在する場合、同時に複数の CodePipeline パイプラインが起動してしまうことが想定されるためです。
メタ情報は ECS タスク単位で生成されるため、デプロイの単位と合致します。
S3 への書き込みをトリガーに CodePipeline を起動するには、こちらのチュートリアルと同様、以下の AWS リソースの設定が必要となります。
- CloudTrail により、特定の S3 の書き込みデータイベントをログ記録する証跡を作成
- EventBridge(CloudWatch Events) により、当該証跡から CodePipeline をフックするイベントルールを作成
(4) CodePipelineからECSサービスに対してデプロイ
CodePipeline では以下の2つ(or 3つ)のアクショングループを実行します。
ソース:Amazon S3
imagedefinitions.json の中身を出力アーティファクトに設定します。
承認:手動承認
デプロイ対象が正しいかどうかレビューする手動承認のタイミングをはさみます。
prod 環境のみ設定する想定です。
デプロイ:Amazon ECS
ECS サービススケジューラによりローリングアップデート方式でデプロイを行います。
まとめ
- CIとCDは開発者に過剰な権限を渡さないためにも分離すべし!
- CIツールおよびCDツールは特性に合わせて変動・選定をする!
最後に個人的な感想
インフラエンジニア視点で少し感想を書きます。
昨今ではコンテナ開発が盛んに行われ、CI/CDの設計から携わる機会が増えてきたと思います。
しかしながらインフラエンジニアとして経験してきた私にはCIの部分はさっぱりわかない、というつまづきがありました。
CIはアプリケーションコードのビルドやテストを行う工程でもあり、インフラエンジニアではかけない部分が多く存在するからです。
ただし、コンテナ開発の場合はDockerファイルからイメージのプッシュ時に環境特有のコンフィグファイルをどのように渡すのか?や、
環境変数をどのタイミングでどのように渡すのかというのを意識する必要があるためインフラも設計のタイミングから入る必要があります。
そのため、自分はCIはわからないから、と投げ出すのではなく理解できるよう努力しつつ、
不明な点はアプリとしっかり連携して設計することが非常に重要であると感じました。
コンテナオーケストレーションによって実施方法が様々あり、どの選択をするかというのが重要であり、大変でしたが、
実装によってリリースが改善されたりすることは達成感があり、貴重な経験になったと思います。