LoginSignup
2
4

More than 1 year has passed since last update.

.NET CoreのプロジェクトをGitLab CI/CDでECSに自動デプロイさせる

Last updated at Posted at 2022-08-19

概要

画像1.png
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コンテナを起動します。
volume作成
docker volume create gitlab-runner-config
GitLab Runnerコンテナを起動
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コンテナを起動したら、下記のコマンドを使用して、プロジェクトにランナーを登録をします。
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のマニュアルセットアップ」にあります。

画像2.png

GitLab Runnerの登録が完了すると、プロジェクトに登録されているランナーが表示されます。
ここで、鉛筆マークからRunnerの設定画面を開いて、「タグのないジョブの実行」にチェックを入れておきます。
画像3.png
画像4.png

手順2. 環境変数の設定

デプロイに必要な情報です。
CI/CD処理にAWSのアカウント情報などを、直接書き込むのはよくないので、環境変数に設定して、参照できるようにします。

画像7.png

デフォルトでは、masterブランチのみ保護ブランチになっているので、Protect variableにチェックが付いているとmasterブランチ以外のプッシュでは、環境変数の値が参照出来ません。
なので、Protect variableのチェックを外すか、パイプラインを実行したいブランチを保護する必要があります。
画像8.png

自分で設定した環境変数以外にも、デフォルトで参照可能な環境変数もあります。

手順3.「.gitlab-ci.yml」ファイルを作成する

プロジェクトページのリポジトリから、新規ファイルを開いて「.gitlab-ci.yml」を選択して、プロジェクトにファイルを追加します。

画像6.png

追加した.gitlab-ci.ymlをVScodeなどで開いて、処理を書いていきます。
テスト、ビルド、ECSにデプロイする場合の処理は、下記のような処理になります。

.gitlab-ci.yml
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つずつ見ていきます。

テスト

.gitlab-ci.yml
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コマンドを使用して、プロジェクトの単体テストを行っています。
コマンドプロンプトを開いて、プロジェクトのあるフォルダに移動して、同じコマンドを入力すれば、動きを確認出来ます。

ビルド

.gitlab-ci.yml
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出来るようにしました。

デプロイ

.gitlab-ci.yml
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諸々を更新して、デプロイ完了です。

画像9.png

参考文献

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4