概要
OS:Windows11
開発環境:VisualStudio2022(Docker使用)
プロジェクト:.NET Core Razor Pages
ソース管理:GitLab(セルフホスト版)
Visual StudioでDockerを使用する方法については、こちらの記事で説明しています。
AWSの環境はTerraFormで構築しました。
TerraFormを使ったAWS環境構築については、今回の趣旨から逸れるのでここでは説明しません。
上記の環境で、GitLabにソースをプッシュしたら、ECSに自動でデプロイされるように設定していきます。
CI/CDの処理は全て「.gitlab-ci.yml」というファイルに記述します。
プロジェクトのフォルダ構成
├─.git
├─.vs
├─bin
├─db
├─obj
│
├─RazorPageSample
│ │
│ ├─bin
│ ├─Data
│ ├─Migrations
│ ├─Models
│ ├─obj
│ ├─Pages
│ ├─Properties
│ ├─wwwroot
│ ├─appsettings.Development.json
│ ├─appsettings.json
│ ├─Dockerfile
│ ├─Program.cs
│ ├─RazorPageSample.csproj
│ └─RazorPageSample.csproj.user
│
├─RazorPageSampleTests
│ │
│ ├─bin
│ ├─obj
│ ├─CreateModelTests.cs
│ ├─DeleteModelTests.cs
│ ├─EditModelTests.cs
│ └─obj
│
├─TestResults
│
│ .dockerignore
│ .gitignore
│ .gitlab-ci.yml
│ docker-compose.dcproj
│ docker-compose.override.yml
│ docker-compose.yml
│ Dockerfile
│ RazorPageSample.sln
Dockerfileが「./Dockerfile」と「./RazorPageSample/Dockerfile」の二つ存在していますが、「./RazorPageSample/Dockerfile」を.gitlab-ci.ymlで指定して、docker buildすると、Program.csにMainメソッドが省略された関係で、ビルドエラーになってしまったので、.gitlab-ci.ymlと同じ階層にもDockerfileを配置しています。Dockerfileの中身は同じです。
./Dockerfile・・・.gitlab-ci.ymlでdocker buildする時に指定するDockerfile
./RazorPageSample/Dockerfile・・・VisualStudioでdocker buildする時に参照されるDockerfile
手順1. GitLab Runnerを設定する
GitLab CI/CDを動かすためには、GitLab Runnerが必要です。GitLab Runnerを動かすためには、Dockerが必要なので、入っていない方はインストールしてください。
今回は、自分のPCにGitLab Runnerを、公式ドキュメントに沿ってインストールしていきます。
下記のコマンドを、コマンドプロンプトで実行して、GitLab Runnerコンテナを起動します。
docker volume create gitlab-runner-config
docker run -d --name gitlab-runner --restart always -v /var/run/docker.sock:/var/run/docker.sock -v gitlab-runner-config:/etc/gitlab-runner gitlab/gitlab-runner:latest
GitLab Runnerコンテナを起動したら、下記のコマンドを使用して、プロジェクトにランナーを登録をします。
docker run --rm -it -v gitlab-runner-config:/etc/gitlab-runner gitlab/gitlab-runner:latest register
コマンドを実行すると、対話形式で登録が始まるので、設定していきます。
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com )
GitLabインスタンスのURLを入力
Please enter the gitlab-ci token for this runner
トークンを入力
Please enter the gitlab-ci description for this runner
ランナーの説明を入力(後で変更可能)
Please enter the gitlab-ci tags for this runner (comma separated):
ランナーにタグを付ける(後で変更可能)
Please enter the executor: ssh, docker+machine, docker-ssh+machine, kubernetes, docker, parallels, virtualbox, docker-ssh, shell:
docker
Please enter the Docker image (eg. ruby:2.6):
.gitlab-ci.ymlで明示的にイメージが指定されていない場合に、使用するイメージ
GitLabインスタンスのURLとトークンは、プロジェクトのページの[設定]>[CI/CD]を開いて、Runnerの「specific のRunnerのマニュアルセットアップ」にあります。
GitLab Runnerの登録が完了すると、プロジェクトに登録されているランナーが表示されます。
ここで、鉛筆マークからRunnerの設定画面を開いて、「タグのないジョブの実行」にチェックを入れておきます。
手順2. 環境変数の設定
デプロイに必要な情報です。CI/CD処理にAWSのアカウント情報などを、直接書き込むのはよくないので、環境変数に設定して、参照できるようにします。
デフォルトでは、masterブランチのみ保護ブランチになっているので、Protect variableにチェックが付いているとmasterブランチ以外のプッシュでは、環境変数の値が参照出来ません。
なので、Protect variableのチェックを外すか、パイプラインを実行したいブランチを保護する必要があります。
自分で設定した環境変数以外にも、デフォルトで参照可能な環境変数もあります。
手順3.「.gitlab-ci.yml」ファイルを作成する
プロジェクトページのリポジトリから、新規ファイルを開いて「.gitlab-ci.yml」を選択して、プロジェクトにファイルを追加します。追加した.gitlab-ci.ymlをVScodeなどで開いて、処理を書いていきます。
テスト、ビルド、ECSにデプロイする場合の処理は、下記のような処理になります。
stages:
- test
- build
- deploy
tests:
stage: test
image: mcr.microsoft.com/dotnet/sdk:6.0
script:
# 依存関係を復元して、ビルドして、テストを実行
- cd ./RazorPageSample
- dotnet restore --packages .nuget
- dotnet build --no-restore
- dotnet test ../RazorPageSampleTests
build_image_with_kaniko:
stage: build
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
# Dockerイメージを生成して、イメージのタグにコミットIDを付けてECRにプッシュ
- mkdir -p /kaniko/.docker
- echo "{\"credsStore\":\"ecr-login\"}" > /kaniko/.docker/config.json
- >-
/kaniko/executor
--context ${CI_PROJECT_DIR}
--dockerfile ${CI_PROJECT_DIR}/Dockerfile
--destination ${AWS_ECR_REPOSITORY_URL}:${CI_COMMIT_SHORT_SHA}
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
deploy_image:
stage: deploy
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
services:
- stedolan/jq:latest
script:
# AWS認証情報表示とログイン
- aws sts get-caller-identity
- aws ecr get-login-password --region ${AWS_DEFAULT_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com
# ECRにプッシュしたイメージにlatestタグを追加
- MANIFEST=$(aws ecr batch-get-image
--repository-name ${AWS_ECR_REPOSITORY_NAME}
--region ${AWS_DEFAULT_REGION}
--image-ids imageTag=${CI_COMMIT_SHORT_SHA}
--output json | jq --raw-output --join-output '.images[0].imageManifest')
- aws ecr put-image
--repository-name ${AWS_ECR_REPOSITORY_NAME}
--region ${AWS_DEFAULT_REGION}
--image-tag latest
--image-manifest "${MANIFEST}"
# タスク定義を取得して参照しているイメージを、最新のイメージに書き換える
- TASK_DEFINITION=$(aws ecs describe-task-definition
--task-definition ${AWS_ECS_TASK_DEFINITION}
--region ${AWS_DEFAULT_REGION} | jq '.taskDefinition | del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)')
- NEW_CONTAINER_DEFINTIION=$(echo ${TASK_DEFINITION} | jq --arg IMAGE "${AWS_ECR_REPOSITORY_URL}:${CI_COMMIT_SHORT_SHA}" '.containerDefinitions[0].image = $IMAGE | .')
# 書き換えたタスク定義を読み込ませて、ECSを更新する
- aws ecs register-task-definition --cli-input-json "${NEW_CONTAINER_DEFINTIION}"
- aws ecs update-service
--region ${AWS_DEFAULT_REGION}
--cluster ${AWS_ECS_CLUSTER}
--service ${AWS_ECS_SERVICE}
--task-definition ${AWS_ECS_TASK_DEFINITION}
--desired-count 1
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
test、build、deployの3段階に処理が分かれています。
複雑そうに見えますが、AWS CLIなどのコマンドを1つずつ実行しているだけなので、慣れたらそこまで難しくはないと思います。
buildとdeployはrulesを使って、developブランチがプッシュされたときのみ、ジョブを実行するように設定しています。rules以外にもonlyなどの機能がありますが、現在はrulesの使用が推奨されています。
では、testステージの処理から1つずつ見ていきます。
テスト
tests:
stage: test
image: mcr.microsoft.com/dotnet/sdk:6.0
script:
# 依存関係を復元して、ビルドして、テストを実行
- cd ./RazorPageSample
- dotnet restore --packages .nuget
- dotnet build --no-restore
- dotnet test ../RazorPageSampleTests
ここの処理では、単純にdotnetコマンドを使用して、プロジェクトの単体テストを行っています。
コマンドプロンプトを開いて、プロジェクトのあるフォルダに移動して、同じコマンドを入力すれば、動きを確認出来ます。
ビルド
build_image_with_kaniko:
stage: build
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
# Dockerイメージを生成して、イメージのタグにコミットIDを付けてECRにプッシュ
- mkdir -p /kaniko/.docker
- echo "{\"credsStore\":\"ecr-login\"}" > /kaniko/.docker/config.json
- >-
/kaniko/executor
--context ${CI_PROJECT_DIR}
--dockerfile ${CI_PROJECT_DIR}/Dockerfile
--destination ${AWS_ECR_REPOSITORY_URL}:${CI_COMMIT_SHORT_SHA}
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
GitLab RunnerはDockerコンテナで動いているため、そのままだとdocker buildが実行出来ず、Dockerイメージの生成が出来ません。
docker build出来るようにする方法として、Docker in Dockerがありますが、特権モードでGitLab Runnerを実行する必要があったりするので、今回は「kaniko」というイメージを使ってdocker build出来るようにしました。
デプロイ
deploy_image:
stage: deploy
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
services:
- stedolan/jq:latest
script:
# AWS認証情報表示とログイン
- aws sts get-caller-identity
- aws ecr get-login-password --region ${AWS_DEFAULT_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com
# ECRにプッシュしたイメージにlatestタグを追加
- MANIFEST=$(aws ecr batch-get-image
--repository-name ${AWS_ECR_REPOSITORY_NAME}
--region ${AWS_DEFAULT_REGION}
--image-ids imageTag=${CI_COMMIT_SHORT_SHA}
--output json | jq --raw-output --join-output '.images[0].imageManifest')
- aws ecr put-image
--repository-name ${AWS_ECR_REPOSITORY_NAME}
--region ${AWS_DEFAULT_REGION}
--image-tag latest
--image-manifest "${MANIFEST}"
# タスク定義を取得して参照しているイメージを、最新のイメージに書き換える
- TASK_DEFINITION=$(aws ecs describe-task-definition
--task-definition ${AWS_ECS_TASK_DEFINITION}
--region ${AWS_DEFAULT_REGION} | jq '.taskDefinition | del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)')
- NEW_CONTAINER_DEFINTIION=$(echo ${TASK_DEFINITION} | jq --arg IMAGE "${AWS_ECR_REPOSITORY_URL}:${CI_COMMIT_SHORT_SHA}" '.containerDefinitions[0].image = $IMAGE | .')
# 書き換えたタスク定義を読み込ませて、ECSを更新する
- aws ecs register-task-definition --cli-input-json "${NEW_CONTAINER_DEFINTIION}"
- aws ecs update-service
--region ${AWS_DEFAULT_REGION}
--cluster ${AWS_ECS_CLUSTER}
--service ${AWS_ECS_SERVICE}
--task-definition ${AWS_ECS_TASK_DEFINITION}
--desired-count 1
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
最後に、deployステージです。
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest でAWS CLIコマンドが使用可能になります。
services: - stedolan/jq:latest でjqコマンドも使えるようにしています。jqコマンドは、jsonデータから目的の値を抽出したり、整形したり出来るツールで、タスク定義のイメージ書き換えなどに使用します。
最初にAWS認証を行います。
その後、先ほどECRにプッシュしたイメージに、最新であることが、分かりやすいようにlatestタグを追加します。タグの追加方法はAWS公式ドキュメントを参考にしました。
次に、aws ecs describe-task-definitionでタスク定義をjsonで取得して、TASK_DEFINITIONに格納します。
タスク定義を取得する際に、del関数で「.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy」を削除しています。
この項目を削除しておかないと、後でタスク定義を読み込ませる時にエラーが出ます。
取得したタスク定義は、参照しているimage部分だけを、最新のものに書き換えて、NEW_CONTAINER_DEFINTIION に格納します。
そして、aws ecs register-task-definition --cli-input-json "${NEW_CONTAINER_DEFINTIION}" でタスク定義を読み込ませて、aws ecs update-service でECS諸々を更新して、デプロイ完了です。
参考文献