LCL Engineers' Blog

バス比較なび・格安移動・バスとりっぷを運営する LCLの開発者ブログ

CodePipeline/CodeBuild/ECR/ECS/Fargateのコンテナデプロイ基盤を構築してみました

モバイルアプリエンジニアの山下(@yamshta)です。
今回は、AWSの以下のサービスを用いてコンテナデプロイ基盤の構築を試してみました。

  • CodePipeline
  • CodeBuild
  • ECR
  • ECS
    • Fargate

AWSのドキュメントは丁寧で情報も豊富ですが、サービス毎に手順が書かれているため一連の流れをまとめました。CLIでの操作のみで手順を進めています。

なぜアプリエンジニアがデプロイ基盤を構築するのか

疑問に思った方もいらっしゃると思うので手短に書かせていただきます。
LCLのエンジニアチームはスペシャリスト集団というよりはゼネラリスト集団に近く、特定の技術に縛られない文化とそれを推奨する環境になっています。そのため、メインに扱う技術力も伸ばしながらも別の技術を習得することができます。

今回の経緯ですが、私個人としてはインフラには興味がありませんでした。しかし、Dockerは環境構築の手軽さから数年前から色々な場面で使っていました。また、業務改善が好きでDevOpsのような取り組みにも興味がありました。
それゆえ、サーバ環境をコンテナで取り扱えるECSに興味を持ち、その流れでデプロイ基盤まで試してみることになりました。

コンテナデプロイ基盤の概要

自動テストなどは含めず、純粋なデプロイフローのみで構築します。

f:id:lcl-engineer:20181012154733p:plain

構築手順

順番はコンテナ側からボトムアップで進めます。既にECR、ECSの環境を用意されている場合はCodeBuildの手順まで飛ばしてください。
手順中でIAM FullAccessを割り当てるなど権限を大胆に与えていますが、あくまでドキュメントに沿った操作となります。権限の取り扱いは充分ににお気をつけください。

事前準備

IAM、ECS、ECRのアクセス権限があるユーザのアクセスキーとシークレットキーを用意してください。

はじめに、CLIの操作をするためにaws-cliをインストールします。

$ brew install awscli

AWS CLI用の設定ファイルと認証情報ファイルを作成

参考: 設定ファイルと認証情報ファイル

$ aws configure

以下の項目を対話的に入力

key value
aws_access_key_id 任意の文字列
aws_secret_access_key 任意の文字列
region ap-northeast-1
output json

ECS CLI用の設定ファイルと認証情報ファイルを作成

同じくamazon-ecs-cliをインストールします。

$ brew install amazon-ecs-cli

リージョンとECSクラスタ名を指定し設定ファイルを作成します。

$ ecs-cli configure --cluster ecs-cli-app --region ap-northeast-1 --default-launch-type FARGATE --config-name fargate-app

認証情報ファイルを作成します。

$ ecs-cli configure profile --access-key <ACCESS_KEY> --secret-key <SEACRET_KEY> --profile-name fargate-app

ECR

ここではアプリケーションのdocker imageをECRへPUSHします。
CodeBuild上で行うため、将来的には手動での操作は不要ですが手順を残しておきます。

今回はRailsアプリケーションを扱うため、以下のDockerfileを利用します。

FROM ruby:2.5.1

ENV ROOT_PATH /app

# リポジトリを更新し依存モジュールをインストール
RUN apt-get update -qq && \
    apt-get install -y apt-utils \
                       build-essential \
                       libpq-dev \
                       nodejs \
                       locales

RUN locale-gen ja_JP.UTF-8
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:en
ENV LC_ALL ja_JP.UTF-8

# ルート直下にリポジトリ名で作業ディレクトリを作成(コンテナ内のアプリケーションディレクトリ)
RUN mkdir $ROOT_PATH
WORKDIR $ROOT_PATH

# ホストのGemfileとGemfile.lockをコンテナにコピー
ADD Gemfile $ROOT_PATH/Gemfile
ADD Gemfile.lock $ROOT_PATH/Gemfile.lock

# bundle installの実行
RUN gem install bundler
RUN bundle install -j4

# ホストのアプリケーションディレクトリ内をすべてコンテナにコピー
ADD . $ROOT_PATH

ECRへのログイン情報を取得します。

$ aws ecr get-login --no-include-email --region ap-northeast-1

上で出力されたdocker loginコマンドを実行します。丸々コピペで構いません。

$ docker login -u AWS -p xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com

補足: evalを使うことで出力をそのまま実行できます。

$ eval $(aws ecr get-login --no-include-email --region ap-northeast-1)

リポジトリを作成します。

$ aws ecr create-repository --repository-name app
{
    "repository": {
        "registryId": "111122223333",
        "repositoryName": "app",
        "repositoryArn": "arn:aws:ecr:ap-northeast-1:111122223333:repository/app"
    }
}

docker imageをビルドして、イメージURLをタグ付けします。

$ docker build -t <AWS_ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com/app/rails:latest .

ECRへPUSHします。

$ docker push <AWS_ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com/app/rails:latest

以上でECRへイメージがPUSHされたかと思います。
ECSではこれを利用してコンテナを作成します。

ECS

ここではクラスタを作成し、クラスタの管理下でサービス(≒コンテナ)を起動します。
こちらもCodeBuild上で行うため、将来的には手動での操作は不要です。

まずはクラスタを作成します。

参考: ecs-cli up - Amazon Elastic Container Service

$ ecs-cli up --keypair <KEYPAIR> --capability-iam --size 1 --instance-type t2.micro --cluster-config fargate-app --vpc <VPC_ID> --subnets <SUBNET_ID> --security-group <SECURITY_GROUP_ID>
INFO[0000] Using recommended Amazon Linux AMI with ECS Agent 1.20.2 and Docker version 18.03.1-ce
INFO[0000] Created cluster                               cluster=ecs-cli-app region=ap-northeast-1
INFO[0000] Waiting for your cluster resources to be created...
INFO[0000] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0061] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0122] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
Cluster creation succeeded.

次にサービスを起動しますが、その前に以下のようなdocker-compose.ymlを用意してください。
imageにECRのイメージURLをセットするとこで上でPUSHしたイメージを扱うことができます。

version: '3'
services:
  ruby:
    image: xxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/app/rails:latest
    ports:
      - 80:80
    command: /bin/sh -c "rm -f tmp/pids/unicorn.pid && bundle exec unicorn_rails -E test -p 80 -c unicorn.rb"
    environment:
      RUBYOPT: -EUTF-8
      RAILS_LOG_TO_STDOUT: 1
    logging:
      driver: awslogs
      options:
        awslogs-group: app
        awslogs-region: ap-northeast-1

さらに、ECSの設定をするためにecs-compose.ymlを作成します。

version: 1
task_definition:
  task_execution_role: ecsTaskExecutionRole
  ecs_network_mode: awsvpc
  task_size:
    cpu_limit: 256
    mem_limit: 512
run_params:
  network_configuration:
    awsvpc_configuration:
      subnets:
        - <SUBNET_ID>
      security_groups:
        - <SECURITY_GROUP_ID>
      assign_public_ip: DISABLED

上のdocker-compose.ymlを元にサービス(≒コンテナ)を起動します。
ファイル名に変更がない場合は省略可能です。

$ ecs-cli compose --file docker-compose.yml service up --cluster-config fargate-app --deployment-max-percent 200 --deployment-min-healthy-percent 50
INFO[0000] Using ECS task definition                     TaskDefinition="app:1"
INFO[0000] Starting container...                         container=74a9bf97-c5e0-4c1a-b9f2-9f3300fe9660/ruby
INFO[0000] Describe ECS container status                 container=74a9bf97-c5e0-4c1a-b9f2-9f3300fe9660/ruby desiredStatus=RUNNING lastStatus=PENDING taskDefinition="app:1"
INFO[0012] Describe ECS container status                 container=74a9bf97-c5e0-4c1a-b9f2-9f3300fe9660/ruby desiredStatus=RUNNING lastStatus=PENDING taskDefinition="app:1"
INFO[0024] Started container...                          container=74a9bf97-c5e0-4c1a-b9f2-9f3300fe9660/ruby desiredStatus=RUNNING lastStatus=RUNNING taskDefinition="app:1"

サービスのステータスを確認します。

$ ecs-cli compose --file docker-compose.yml service ps --cluster-config fargate-app
Name                                       State    Ports  TaskDefinition  Health
74a9bf97-c5e0-4c1a-b9f2-9f3300fe9660/ruby  RUNNING         app:1           UNKNOWN

RUNNINGとなっていれば正常に動作しています。

サービスを停止する際は以下のコマンドを実行します。

$ ecs-cli compose service stop --cluster-config fargate-app

トラブルシューティング

maximumPercentminimumHealthyPercentを設定していない場合に、以下のようなエラーが発生しました。

$ ecs-cli compose service down
ERRO[0000] Error updating service                        error="InvalidParameterException: At least one of maximumPercent or minimumHealthyPercent must be present in DeploymentConfiguration\n\tstatus code: 400, request id: ad136f04-c544-11e8-845c-4b2f4a6290b9" service=app
FATA[0000] InvalidParameterException: At least one of maximumPercent or minimumHealthyPercent must be present in DeploymentConfiguration
    status code: 400, request id: ad136f04-c544-11e8-845c-4b2f4a6290b9

残っているサービスが消せず、新規にサービスを起動することもできなくなってしまいましたが、一度scaleを0にしてから再度service rmしてみると消せました。

CodeBuild

はじめに、CodeBuildを操作するための権限を付与します。

サービスロールを作成します。

参考:高度な設定 > AWS CodeBuild サービスロールを作成するには (AWS CLI)

create-role.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "codebuild.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

JSONファイルを引数にしてcreate-roleコマンドを実行します。

$ aws iam create-role --role-name CodeBuildServiceRole --assume-role-policy-document file://create-role.json

次に、ECRやS3などの操作やリソースにアクセスするためにポリシーをアタッチします。

参考:
AWS CodeBuild でアイデンティティベースのポリシー (IAM ポリシー) を使用する
AWS CodeBuild の Amazon ECR サンプル
AWS CodeBuild の Docker サンプル

put-role-policy.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CloudWatchLogsPolicy",
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    },
    {
      "Sid": "S3GetObjectPolicy",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:GetObjectVersion"
      ],
      "Resource": "*"
    },
    {
      "Sid": "S3PutObjectPolicy",
      "Effect": "Allow",
      "Action": [
        "s3:PutObject"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:CompleteLayerUpload",
        "ecr:GetAuthorizationToken",
        "ecr:InitiateLayerUpload",
        "ecr:PutImage",
        "ecr:UploadLayerPart"
      ],
      "Resource": "*"
    }
  ]
}

こちらもJSONファイルを引数にしてput-role-policyコマンドを実行します。

$ aws iam put-role-policy --role-name CodeBuildServiceRole --policy-name CodeBuildServiceRolePolicy --policy-document file://put-role-policy.json

コマンドラインからCodeBuild を直接実行する場合

AWS CodeBuild にアクセスするには、IAM グループまたは IAM ユーザーにアクセス許可を追加する必要があるため、コマンドラインからIAM ユーザとして実行する際には許可をする必要があります。

参考:IAM グループまたは IAM ユーザー (AWS CLI) に AWS CodeBuild アクセス許可を追加するには

AWS CodeBuild にフルアクセス許可を追加するには、以下のポリシーARN をIAM Userにアタッチします。

$ aws iam attach-user-policy --user-name aws-user --policy-arn arn:aws:iam::aws:policy/AWSCodeBuildAdminAccess
$ aws iam attach-user-policy --user-name aws-user --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
$ aws iam attach-user-policy --user-name aws-user --policy-arn arn:aws:iam::aws:policy/IAMFullAccess

AWS CLI がインストールされているローカルワークステーションまたはインスタンス上の空のディレクトリに、put-user-policy.json という名前のファイルを作成します。

put-user-policy.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CodeBuildAccessPolicy",
      "Effect": "Allow",
      "Action": [
        "codebuild:*",
        "iam:PassRole"
      ],
      "Resource": "*"
    },
    {
      "Sid": "CloudWatchLogsAccessPolicy",
      "Effect": "Allow",
      "Action": [
        "logs:FilterLogEvents",
        "logs:GetLogEvents"
      ],
      "Resource": "*"
    },
    {
      "Sid": "S3AccessPolicy",
      "Effect": "Allow",
      "Action": [
        "s3:CreateBucket",
        "s3:GetObject",
        "s3:List*",
        "s3:PutObject"
      ],
      "Resource": "*"
    }
  ]
}

JSONファイルを引数にポリシーをアタッチする。

$ aws iam put-user-policy --user-name aws-user --policy-name CodeBuildUserAccessPolicy --policy-document file://put-user-policy.json

ここまででアクセス許可の準備ができたので、次にビルド内容を決定するビルド仕様ファイルを作成します。

ビルド仕様をソースコードの一部として含める場合、デフォルトのビルド仕様ファイルの名前はbuildspec.yml で、ソースディレクトリのルートに配置します。
※ デフォルトのビルド仕様ファイルの名前と場所を変更することも可能です

参考:AWS CodeBuild のビルド仕様に関するリファレンス

プロジェクトによって設定が異なるため詳しい説明は省きますが、今回は上で示した構成を以下のように表現します。
各変数は後述するCodeBuildのビルドプロジェクトから渡します。buildspec.yml を作成したら一度GitHubへPUSHします。

buildspec.yml:

version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build --no-cache -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
      - REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME
      - echo "[{\"name\":\"ruby\",\"imageUri\":\"${REPOSITORY_URI}:${IMAGE_TAG}\"}]" > imagedefinitions.json
artifacts:
  files: imagedefinitions.json

後はビルドプロジェクトを作成して、動作を確認するだけです。

参考:AWS CodeBuild でビルドプロジェクトを作成する

ビルドプロジェクトのテンプレートを用意します。
create-projectを実行して、CodeBuildでプロジェクトを作成するためのJSONテンプレートを取得します。

$ aws codebuild create-project --generate-cli-skeleton

JSONファイルを作成し、出力されたJSONをコピペした後に以下のように編集します。

create-project.json:

{
    "name": "build-app",
    "source": {
        "type": "GITHUB",
        "location": "https://github.com/xxxx/app.git",
        "gitCloneDepth": 1,
        "buildspec": "buildspec.yml",
        "auth": {
            "type": "OAUTH"
        },
        "reportBuildStatus": true,
        "insecureSsl": false
    },
    "artifacts": {
        "type": "NO_ARTIFACTS"
    },
    "cache": {
        "type": "NO_CACHE"
    },
    "environment": {
        "type": "LINUX_CONTAINER",
        "image": "aws/codebuild/docker:17.09.0",
        "computeType": "BUILD_GENERAL1_SMALL",
        "environmentVariables": [
            {
                "name": "AWS_DEFAULT_REGION",
                "value": "ap-northeast-1"
            },
            {
                "name": "AWS_ACCOUNT_ID",
                "value": "<AWS_ACCOUNT_ID>"
            },
            {
                "name": "IMAGE_REPO_NAME",
                "value": "app/rails"
            },
            {
                "name": "IMAGE_TAG",
                "value": "latest"
            }
        ]
    },
    "serviceRole": "arn:aws:iam::<AWS_ACCOUNT_ID>:role/service-role/codebuild-app-service-role",
    "timeoutInMinutes": 60,
    "encryptionKey": "arn:aws:kms:ap-northeast-1:<AWS_ACCOUNT_ID>:alias/aws/s3",
    "tags": [],
    "vpcConfig": {
        "vpcId": "<VPC_ID>",
        "subnets": [
            "<SUBNET_ID>"
        ],
        "securityGroupIds": [
            "<SECURITY_GROUP_ID>"
        ]
    },
    "badgeEnabled": false
}

編集したJSONファイルを元に、ビルドプロジェクトを作成します。

$ aws codebuild create-project --cli-input-json file://create-project.json

start-build コマンドで実行します。成功した場合は、「ビルドを実行するには (AWS CLI)」の手順で説明されているのと同様のデータが出力に表示されます。

$ aws codebuild start-build --project-name build-app
{
    "build": {
        "id": "build-app:ec61105c-c84d-4833-ac0b-44c170de73cc",
        "arn": "arn:aws:codebuild:ap-northeast-1:xxxxxxxx:build/build-app:ec61105c-c84d-4833-ac0b-44c170de73cc",
        "startTime": 1538556074.906,
        ...

実行したビルドの詳細は、実行時に返ってきたJSONに含まれるidを引数にbatch-get-buildsコマンドを実行することで取得できます。

参考:AWS CodeBuild のビルドの詳細を表示する

$ aws codebuild batch-get-builds --ids build-app:ec61105c-c84d-4833-ac0b-44c170de73cc

もし、ビルドプロジェクトの編集をしたい場合はJSONファイルの内容を変更してupdate-projectコマンドを実行します。

参考:AWS CodeBuild でビルドプロジェクトの設定を変更する - AWS CodeBuild

$ aws codebuild update-project --cli-input-json file://create-project.json

CodePipeline

今回も先にIAMの準備から始めます。

参考:AWS CodeBuild で AWS CodePipeline を使用してコードをテストし、ビルドを実行する > 前提条件

IAM ユーザーを AWS CodePipeline へのアクセスに使用している場合、AWSCodePipelineFullAccess にという名前の管理ポリシーをユーザーにアタッチします。

$ aws iam attach-user-policy --user-name aws-user --policy-arn arn:aws:iam::aws:policy/AWSCodePipelineFullAccess

サービスロールを作成します。
今回はJSONファイルを作らずに、直接JSONを渡すやり方をしてみます。
ファイルで管理したい場合は、CodeBuildと同様の手順で行ってください。

$ aws iam create-role --role-name AWS-CodePipeline-CodeBuild-Service-Role --assume-role-policy-document '{"Version":"2012-10-17","Statement":{"Effect":"Allow","Principal":{"Service":"codepipeline.amazonaws.com"},"Action":"sts:AssumeRole"}}'

次にポリシーをアタッチします。AWS CodePipelineのIAM サービスロール、AWS-CodePipeline-Serviceのポリシーステートメントには、アカウントの他のリソースを使用するためにAWS CodePipelineで必要なアクセス許可がデフォルトで含まれています。そのため、ここではデフォルトで含まれていないもの許可するようにします。

今回は、ECSやCodeBuildの操作を許可する必要があるため、それらのポリシーをアタッチします。

参加:AWS CodePipeline サービスロールの管理 > 他の AWS サービスのアクセス許可の追加

put-codepipeline-role-policy.json:

{
  "Version": "2012-10-17",
  "Statement" : [
    {
      "Effect": "Allow",
      "Action": [
        "codebuild:BatchGetBuilds",
        "codebuild:StartBuild"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ecs:*"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "iam:PassRole",
      "Resource": "*"
    }
  ]
}

JSONファイルを引数にしてput-role-policyコマンドを実行します。

$ aws iam put-role-policy --role-name AWS-CodePipeline-CodeBuild-Service-Role --policy-name CodePipelineCodeBuildServiceRolePolicy --policy-document file://put-role-policy.json

準備ができたのでパイプラインを作成します。
まず、パイプラインの構造を表すJSON 形式のファイルを作成します。

参考: AWS CodeBuild で AWS CodePipeline を使用してコードをテストし、ビルドを実行する

GitHubと連携する際は、リポジトリのアクセストークンが必要です。(こちらの手順は省略します)

参考:GitHub と AWS CodePipeline CLI を使用して、GitHub 個人用のアクセストークンを作成し、定期的にローテーションする

create-pipeline.json:

{
  "pipeline": {
    "roleArn": "arn:aws:iam::<AWS_ACCOUNT_ID>:role/AWS-CodePipeline-CodeBuild-Service-Role",
    "stages": [
      {
        "name": "Source",
        "actions": [
          {
            "inputArtifacts": [],
            "name": "Source",
            "actionTypeId": {
              "category": "Source",
              "owner": "ThirdParty",
              "version": "1",
              "provider": "GitHub"
            },
            "outputArtifacts": [
              {
                "name": "Source"
              }
            ],
            "configuration": {
              "Owner": "xxx",
              "Repo": "app",
              "PollForSourceChanges": "false",
              "Branch": "master",
              "OAuthToken": "<GITHUB_REPO_ACCESS_TOKEN>"
            },
            "runOrder": 1
          }
        ]
      },
      {
        "name": "Build",
        "actions": [
          {
            "inputArtifacts": [
              {
                "name": "Source"
              }
            ],
            "name": "Build",
            "actionTypeId": {
              "category": "Build",
              "owner": "AWS",
              "version": "1",
              "provider": "CodeBuild"
            },
            "outputArtifacts": [
              {
                "name": "Build"
              }
            ],
            "configuration": {
              "ProjectName": "build-app"
            },
            "runOrder": 1
          }
        ]
      },
      {
        "name": "Deploy",
        "actions": [
          {
            "inputArtifacts": [
              {
                "name": "Build"
              }
            ],
            "name": "Deploy",
            "actionTypeId": {
              "category": "Deploy",
              "owner": "AWS",
              "version": "1",
              "provider": "ECS"
            },
            "configuration": {
              "ClusterName": "ecs-cli-app",
              "ServiceName": "app"
            },
            "runOrder": 1
          }
        ]
      }
    ],
    "artifactStore": {
      "type": "S3",
      "location": "lcl-codepipeline"
    },
    "name": "pipeline-app",
    "version": 1
  }
}

create-pipelineコマンドを実行してパイプラインを作成します。成功したらJSON 形式のデータが出力に表示されます。

$ aws codepipeline create-pipeline --cli-input-json file://create-pipeline.json

問題なくパイプラインを作成できたら、パイプラインを手動で開始します。
補足ですがこの場合は、IAM Userの権限が参照されます。

$ aws codepipeline start-pipeline-execution --name pipeline-app
{
    "pipelineExecutionId": "41cda22f-536c-43b9-9756-adc05711827a"
}

無事に実行されたか、パイプラインのステータスを確認します。

参考:AWS CodePipeline でパイプラインの詳細と履歴を表示する

$ aws codepipeline get-pipeline-state --name pipeline-app

パイプラインを編集する際は、JSONファイルを変更してupdate-pipelineコマンドを実行します。

$ aws codepipeline update-pipeline --cli-input-json file://pipeline.json

以上でデプロイ環境が構築できたかと思います。

実運用するためには、他にも

  • CodePipelineを自動で実行するためにGitHubのWebhookに対応
  • ECS サービスディスカバリ等を利用してドメインに対してIPの自動割り当て

などの手順が必要になると思います。サービスディスカバリについては別記事で紹介しようと思います。

よかった点

  • デプロイ基盤構築が簡単にできる
  • 社内向けの環境などはコンテナを1日中起動しないのでコストの節約になる
  • オートスケーリングが簡単でデプロイ時のコンテナの切り替えもうまくやってくれる

課題点

  • デプロイ完了までに時間がかかる 現状10分弱
    • イメージの大きさが最適化されてないため改善の余地あり
    • Fargateの起動は比較的遅いため、Blue Green Deploymentに対応する検討が必要
  • ドキュメントの手順通りだとIAMにフルアクセスを与えてしまう

(おまけ)各サービスのコスト

AWS CodePipeline

  • アクティブなパイプライン1つにつき1ヶ月あたり1USD の料金が発生
  • アクティブなパイプライン = 1ヶ月間に少なくとも1つのコード変更が発生したパイプライン
  • 以下の場合は追加料金が発生する可能性がある
    • パイプラインのアーティファクトを Amazon S3 に保存してアクセスする場合
    • パイプラインに接続するその他のAWSおよびサードパーティのサービスからアクションをトリガーした場合
  • 【無料利用枠】アクティブなパイプラインを 1 か月あたり 1 つ

CodeBuild

  • ビルドの実行にかかった時間に基づいて、コンピューティングリソースに対して課金
  • ビルド時間はビルドを送信してからビルドが終了するまでの時間で、分単位で切り上げられる
  • 以下の場合は追加料金が発生する可能性がある
    • ビルドでデータを転送する場合、または AWS の他のサービスを使用する場合
  • 【無料利用枠】build.general1.small を使って 1 か月あたりビルドを 100 分使用できる
コンピューティングインスタンスタイプ メモリ (GB) vCPU Linux のビルド 1 分あたりの料金 (USD) Windows のビルド 1 分あたりの料金 (USD)
build.general1.small 3 2 0.005 該当なし
build.general1.medium 7 4 0.010 0.018
build.general1.large 15 8 0.020 0.036

Fargate

  • 1vCPU 1時間あたり 0.0632USD
  • 1GBメモリ 1時間あたり 0.0158USD
  • コンテナイメージのダウンロード (docker pull) を開始した時点から Amazon ECS タスクが終了するまでに使用された vCPU およびメモリリソースに基づいて計算され、最も近い秒数に切り上げられる
  • 1 分の最低料金が適用
  • 2 つの料金体系は単独で設定可能
  • 以下の場合は追加料金が発生する可能性がある
    • コンテナで他の AWS のサービスを使用したり、データを転送したりする場合

サポートされる設定

CPU メモリの値
0.25 vCPU 0.5 GB、1 GB、および 2 GB
0.5 vCPU 最低1 GB および最大4 GB (1 GB 単位)
1 vCPU 最低2 GB および最大8 GB (1 GB 単位)
2 vCPU 最低4 GB および最大16 GB (1 GB 単位)
4 vCPU 最低8 GB および最大30 GB (1 GB 単位)

ECR

  • ストレージ
    • 1 か月あたり GB 単位で 0.10USD
  • データ転送
    • イン: 0.00USD/GB
    • アウト:
      • 1 GB まで/月 0.00USD/GB
      • 次の 9.999 TB/月 0.114USD/GB
      • 次の 40 TB/月 0.089USD/GB
      • 次の 100 TB/月 0.086USD/GB
      • 150 TB /月より大きい 0.084USD/GB
  • 【無料利用枠】月 500 MB のストレージ

おわりに

デプロイ基盤を構築するのは大掛かりで大変な作業ですが、これらAWSのサービスを利用することあっという間に用意することができました。
つい本日、Code4兄弟(CodeCommit、CodeBuild、CodeDeploy、CodePipeline)のコンソールUIが刷新されましたが、手順をCLIで追うことでUI変更に影響なくドキュメント管理できますね。

インフラやAWSの知識が全く無い状態から触り始めましたが、試行錯誤していくうちに概念がわかるようになりました。そのおかげで、先日のDeveloper.IO Outumberのセッションも不自由なく把握することができたので、引き続き理解を深めて技術力を向上していきたいと思います。

エンジニア募集

LCLは、技術を特定の領域に絞りたくないエンジニアにとって働きやすい環境です。入社後しばらくはメインの技術に集中して取り組んで慣れてきたころに手を広げるという働き方を推奨しています。 カジュアル面談から応募が可能なので技術や活動の幅を広げて働きたい・成長したいと思っている方は是非一度話を聞きにいらしてください。

採用情報 | 株式会社LCL(エルシーエル)
https://www.lclco.com/recruit/