.NET5.0製のREST APIサーバーを、AWS CodePipelineでビルドしてコンテナ化してECSでデプロイする。


先日、個人で開発している.NET5.0製のREST APIサーバーをAWS上で動かすにあたり、コンテナ化してECSで管理することにしました。AWSが用意してくれている仕組みに頼ることで、高い品質と高い作業効率を両立できるためです。

この記事では、その際行った設定の概要と設定について記載します。

概要

構成と作業の流れ

APIサーバーの構成は下記のようになります。

 Internet → CloudFront → EC2(ECS管理) → RDS

参照専用のAPIのため、CloudFrontでキャッシュが利くようにしています。それ以外はAPIサーバーでよく用いられる構成と思います。

このうちの"EC2(ECS管理)"とある個所で、.NET5.0製のコードを動かす。それが今回の目的です。

作業の流れは次のようになります。

  1. GitHub上のコードを取得する
  2. 取得したコードをビルドし、さらにコンテナ化する
  3. 作成したコンテナをデプロイする

この3点をAWS CodePipelineで実現しました。

設定の実際

CodePipelineについて

前置きとして、CodePipelineについて少し触れます。

GithubにPushしたソースコードをビルドしてデプロイするまでの全作業は、CodePipelineで行えます。ですが、実際はビルドをCodeBuildが、デプロイをECSが行っています。

CodePipeline <>--- CodeBuild
             <>--- ECS

先に記載した作業も、動いているサービスはそれぞれ次のようになります。

  1. GitHub上のコードを取得する ← CodePipeline
  2. 取得したコードをビルドし、さらにコンテナ化する ← CodeBuild
  3. 作成したコンテナをデプロイする ← ECS

したがってCodePipelineでコンテナを用いた自動ビルドを行うためには、CodeBuildとECSも扱える必要があります。初めて扱う場合、いきなりCodePipelineをフルで動かそうとすると大変なため、まずCodeBuild単体、ECS単体を扱えるようになってから挑戦するほうが良いと思います。

なお、今回の記事ではECSやECR、S3やEC2等の設定について記載しませんが、CodePipelineの設定を開始した時点でそれらはすべて用意済の状態です。

1. GitHub上のコードを取得する

まず、CodePipelineの名前・Role・CodePipelineが作業用に使うS3バケットを指定した後、ソースコードの取得方法について設定します。Githubを使っている場合はOAuth経由で簡単に設定できます。手なりに進めるだけのため、ここで詰まることはそうそうないと思います。

ちなみに、CodePipelineでは、インプットにあたるソースコード群もアウトプットにあたる生成物(.Net5.0ならXXX.DLL等ですね)も両方 "アーティファクト"と呼びます。前者がSource Artifactや入力アーティファクト、後者がBuild Artifactや出力アーティファクトと呼ばれるようですが、設定画面上ではただアーティファクトとだけ記載されるため入力なのか出力なのか判別し難い事態にしばしば遭遇します。当然ながら入力と出力を勘違いするとひどい結果になるため、設定中は注意が必要です。

2. 取得したコードをビルドし、さらにコンテナ化する

CodeBuildの設定を行います。コードのビルドからコンテナ化、ECRへの保存までCodeBuildで行えます。

今回、CodeBuildの環境イメージ、コードをビルドするためのコンテナ、ビルドした生成物を起動するためのコンテナとして、それぞれ次のものを用いました。

  • 環境イメージ
    • aws/codebuild/amazonlinux2-aarch64-standard:2.0 (AWS公式のまま)
  • ビルド用コンテナ
    • mcr.microsoft.com/dotnet/sdk:5.0.400-focal-arm64v8
  • 実行用コンテナ
    • mcr.microsoft.com/dotnet/aspnet:5.0-alpine-arm64v8

いずれもARMアーキテクチャタイプのコンテナである点にご注意ください。私はt4g系のEC2で動かしているためこうなっていますが、x64のインスタンスを利用している方はx64を使う必要があるはずです。

Dockerfile.arm64

コンテナをビルドするためのDockerfileは次のようになっています。ファイル名はDockerfile.arm64です。

    # https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.alpine-arm64
    FROM mcr.microsoft.com/dotnet/sdk:5.0.400-focal-arm64v8 AS build
    WORKDIR /source

    # copy csproj and restore as distinct layers
    COPY src/WritingApi/*.csproj .
    RUN dotnet restore -r linux-musl-arm64

    # copy and publish app and libraries
    COPY src/WritingApi/ .
    RUN dotnet publish -c release -o /app -r linux-musl-arm64 --self-contained false

    # final stage/image
    FROM mcr.microsoft.com/dotnet/aspnet:5.0-alpine-arm64v8
    WORKDIR /app
    RUN apk --no-cache add curl
    COPY --from=build /app .

    # See: https://github.com/dotnet/announcements/issues/20
    # Uncomment to enable globalization APIs (or delete)
    # ENV \
    #     DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \
    #     LC_ALL=en_US.UTF-8 \
    #     LANG=en_US.UTF-8
    # RUN apk add --no-cache icu-libs

    ENTRYPOINT ["./WritingApi"]

公式のサンプルを参考にしつつ、自分の環境に合わせて作りました。

ディレクトリ構成

ディレクトリ構成は下記となります(抜粋)。

    ./
    │  buildspec.yml
    │  Dockerfile.arm64
    │  Dockerfile.x64
    │  imagedefinitions.json
    │  
    └─src
        │  WritingApi.sln
        │  
        └─WritingApi
            │  appsettings.Development.json
            │  appsettings.json
            │  appsettings.Production.json
            │  Program.cs
            │  Startup.cs
            │  WritingApi.csproj
            │  .....

buildspec.yml

CodeBuildで用いるbuildspec.ymlは下記の内容となっています。

    version: 0.2

    phases:
    build:
        commands:
        - echo Build started on `date`
        - docker build -t writing-tools-api -f Dockerfile.arm64 --platform linux/arm64 .
    post_build:
        commands:
        - aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 314544748592.dkr.ecr.ap-northeast-1.amazonaws.com
        - docker tag writing-tools-api:latest 314544748592.dkr.ecr.ap-northeast-1.amazonaws.com/writing-tools-api:latest
        - docker push 314544748592.dkr.ecr.ap-northeast-1.amazonaws.com/writing-tools-api:latest

余談ながら、開発時は最初からbuildspec.ymlをgithubにpushするのではなく、まず"ビルドコマンドの挿入"を使いました。ある程度固まってからbuildspec.ymlに保存しました。このほうがフィードバックが早くてTry&Errorを繰り返しやすい上、gitの履歴も汚さずにすむためです。

3. 作成したコンテナをデプロイする

デプロイの設定を行います。デプロイ作業はECSを通して行われるため、デプロイプロバイダーもECSを選んでいます。

imagedefinitions.json

イメージ定義ファイルは下記の内容になっています。今のところlatestで運用しているためimageUriのタグはlatestです。

    [
    {
        "name": "WritingToolsApi",
        "imageUri": "314544748592.dkr.ecr.ap-northeast-1.amazonaws.com/writing-tools-api:latest"
    }
    ]

nameにはECSのタスクで設定したコンテナ名を入力します。

以上。