こんにちは。
本記事では、GitLabとAWS ECS/FargateのCI/CD環境の構築する方法を紹介します。
今回の記事を書く動機は、GitLabの目玉機能であるGitLab CI/CDとAWSのCode4兄弟と言われているAWS CodeCommit, AWS CodePipeline, AWS CodeDeployやAWS CodeBuildの協調がいまいち最初は分からなかったからです。これからGitLabとAWSのCode4兄弟でCI/CDをしていく人の参考になれば幸いです。
注意
用語について
本記事では、GitHubではなくGitLabを使用しているため用語がGitHubの場合と異なる場合があります(例: GitHubのPull Request = GitLabのMerge Request)。 GitLabにまだ馴染みがない方は、適宜、読み替えてください。
作成したCI/CDのシステム図
以下のシステムを構築しました。

GitLabに関する情報がどうしても少ないため、よくある採用パターンなのかは分かりませんがあまり違和感のない実装になっていると思っています。
詳細
細かく解説していこうと思います。まず、全体のPipelineの定義は以下のようになっています。
ソースを指定するstage
、ビルドを指定するstage
とデプロイを指定するstage
の3つがあります。
resource "aws_codepipeline" "pipeline" {
name = "my-pipeline"
role_arn = aws_iam_role.codepipeline.arn
artifact_store {
location = aws_s3_bucket.pipeline_bucket.bucket
type = "S3"
}
stage {
name = "Source"
action {
name = "Source"
category = "Source"
owner = "AWS"
provider = "CodeCommit"
version = 1
output_artifacts = ["source"]
configuration = {
BranchName = "develop"
RepositoryName = aws_codecommit_repository.my_repository.repository_name
}
}
}
stage {
name = "Build"
action {
name = "Build"
category = "Build"
owner = "AWS"
provider = "CodeBuild"
version = "1"
run_order = 2
input_artifacts = [
"source"]
output_artifacts = [
"build"]
configuration = {
ProjectName = aws_codebuild_project.my_project.name
}
}
}
stage {
name = "Deploy"
action {
name = "Deploy"
category = "Deploy"
owner = "AWS"
provider = "ECS"
version = 1
run_order = 1
input_artifacts = ["Build"]
configuration {
ClusterName = aws_ecs_cluster.my_clustername
ServiceName = aws_ecs_service.my_service.name
FileName = "${var.file_name}"
}
}
}
}
① DeveloperがGitLabに変更をPushしMerge Requestを出す
② GitLab CI/CDのPipelineがトリガーされ、テストが実行される
ここでは、Go言語で書かれたアプリケーションのユニットテストを実行していたり、リントをしています。例えば、テストだと以下にパイプラインを.gitlab-ci.yml
に定義すれば簡単にテストをすることができます。
image: golang:1.15
variables:
REPO_NAME: gitlab.com/xxxxxxx/microservice
before_script:
- mkdir -p $GOPATH/src/$(dirname $REPO_NAME)
- ln -svf $CI_PROJECT_DIR $GOPATH/src/$REPO_NAME
- cd $GOPATH/src/$REPO_NAME
stages:
- test
test:
stage: test
script:
make test
GitLabプロジェクトごとに一つずつPipelineを定義しています。
③ AWS CodeCommitにミラーリングされる
GitLabのプロジェクトに対して一つのAWS CodeCommitのリポジトリを作成しています。
resource "aws_codecommit_repository" "repository" {
repository_name = "my-repo"
}
Merge RequestをマージするとAWS CodeCommitはこのイベントを受け取って、ミラーリングをします。このミラーリングはPush
を選択します。GitLabはAWS CodeCommitとのSSH接続はサポートされておらず(参考: Impossible mirroring to AWS CodeCommit, GitLab)、パスワード認証のみサポートされています。こちらはTerrafromのAWS Provider、またはGitLab Providerで設定することができないので、GUI上から設定する必要があります。これによって、設定したGitLabプロジェクトの全ての変更がAWS CodeCommitのリポジトリへミラーリングされます。
④ AWS CodeCommitの変更を検知してAWS CodeBuildをトリガーする
AWS CodeCommitの指定したブランチに変更があった場合に、AWS CodeBuildをトリガーすることができます。
⑤ AWS CodeBuildでDockerイメージがビルドされる
次に、AWS CodeCommitにミラーリングされたソースコードをもとにAWS CodeBuildでアプリケーションのDockerイメージをビルドします。そして、その生成物をAmazon ECRへプッシュします。 事前に、ECRのリポジトリを作成しておかなければなりません。
resource "aws_ecr_repository" "repository" {
name = "repository"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
}
⑥ AWS CodeDeployがトリガーされ、AWS ECS/Fargateにデプロイされる
AWS CodeDeployがトリガーされ、AWS ECS/Fargateのタスク定義が更新され、新しいDockerイメージのアプリケーションがデプロイされます。
その他
これらの一連のフローの中で以下のようなことを行っています。
- CloudWatch LoggingでAWS CodePipeline全体のログを収集しています
- Amazon S3を使って生成物をやり取りしています
考えられる選択肢
この手法以外にも、いくつか選択肢はあります。感想戦的に振り返ってみようと思います。
1. GitLab CI/CDでCI/CDをやりきる
GitLab CI/CDの名前の通り、CI/CDをGitLabだけで完結できないのかを考えました。以下の3つの点から今回のGitLab CI/CDとAWSの両方を使ったシステムを採用しました。
1.1 Developerフィードバックの高速化
今回は、GitLab CI/CDはCIの部分について使っていました。それはAWS CodeBuildでテストを行ってしまうと、Developerへのフィードバックは遅くなってしまうからです。 GitLabの変更はAWS CodeBuildを直接トリガーすることができず、AWS CodeCommitのミラーリングを介してトリガーされます。そのため時間がかかりますし、その結果をもとにMergeをブロックすることなどが難しくなります。なるべくDeveloperに近い場所でテストなどを使用とするとGitLab CI/CDが最適でした。
また、AWS CodeBuildのテストレポート機能が2020年5月にGAになりました(参考: 「自動テストがより便利に!!CodeBuildのテストレポート機能がGAされました!!」, Developers.IO)。しかし、現在サポートされているテストフレームワークは以下の通りで、たとえテストレポート機能を使うと思ってもGo言語のアプリケーション開発をしているので採用するには至りませんでした。
- JUnit
- Ccumber
- TestNG
- TRX
やはりDeveloperフィードバックが遅ければ意味が薄れてしまいます。そのためにもGitLab CI/CDでユニットテストを実行したり、リントをしたりすることは良い選択だと思っています。テストレポートなどは別途、テストレポートツールなどを採用するべきだと思います。
1.2 GitLab Runnerの運用問題
GitLab CI/CD内でDockerイメージをビルドすることはできず、GitLab Runnerを用いる必要があります。
しかし、小さなチームで開発していて、インフラ周辺の運用に携わっているメンバーが少ない中で、新たにGitLab Runnerを管理するのは面倒でした。AWS CodeBuildのマネージド・サービスでビルドをしたほうが管理コストを考えると良かったです。
1.3 Registryの選択、AWS ECR or GitLab Docker Registry
GitLab Docker RegistryにDockerイメージを置く案が出ていましたが、AWS ECRへ置くようにしました。
理由としては以下の通りです。
- 依存性のサイクル
1.2
で解説したように、GitLab Runnerの採用を見送ってAWS CodeBuildを採用しました。仮に、GitLab Docker Registryを採用するとGitLab → AWS CodeCommit・CodeBuild → GitLabのような流れになり、プロバイダを行ったり来たりします。- AWS ECS/FargateがGitLab Docker RegistryにDockerイメージを引っ張ってくるようになると、さらに問題が悪化します。クレデンシャル情報を双方に置く必要があり、良いことがないです。そのため、AWS CodeBuildを採用したのでAWS ECRを採用することは必然でした。
- イメージスキャンニング
- AWS ECRはイメージスキャニングをすることができます(参考: 「イメージスキャン」、AWS)。GitLabにも同様の機能はありますが、GitLab Runnerを使う必要があります(参考: 「Container Scanning」、AWS)。今回はGitLab Runnerは使わないため、採用できませんでした。
- イメージのライフサイクル
- AWS ECRはイメージのライフサイクルポリシーを定義してイメージのライフサイクル管理を細かく定義することができます(参考: 「ライフサイクル」、AWS)。そのため、不要になったイメージを簡単に、手間かけることなく削除することができて、コストの削減をすることができます。
2. AWS CodeCommitではなくAmazon S3を使う
GitLabはGitHubと違って、AWS CodeBuildのソースとして直接指定することはできず、今回はAWS CodeCommitを使用していました。しかし、その方法はAWS CodeCommitだけに限られず、Amazon S3に圧縮したソースコードをアップロードする方法もあります。 しかし、GitLab CI/CDの中でアップロードをしなければならないこと、S3バケットを管理しなければならないことは他の用途でもS3バケットがたくさんある中では面倒でした。そのため、ミラーリングという、簡単にソースコードをAWS側へ渡すことのできる方法を選択しました。
Amazon S3はブランチのような概念はないため、AWS CodeBuildのソースとする場合、開発フローを整えることが難しいのではないかと感じました。 チーム内のDeveloperは今まで通りGitLabを使った開発にフォーカスし続けられて、新たな学習コストを払うことなく、CI/CDを組み込むことができました。
これから
私のチームはSlackを使っているので、これらのCI/CDからSlack通知など、よりフィードバックやフィードを改善していきたいと思っています。 Developerが爆速で開発できるための基盤づくりにフォーカスしてやっていきたいです。