LoginSignup
4
1

More than 1 year has passed since last update.

【AWS】 (初心者向け) CodePipelineを使ったECSコンテナのデプロイ自動化 その2(CodeCommit, CodeBuild, CodePipeline)

Last updated at Posted at 2022-08-23

概要

CodePipelineでコンテナデプロイを実行するCICD構築をまとめました。
本記事は【AWS】 (初心者向け) CodePipelineを使ったECSコンテナのデプロイ自動化 その1(ECR, ECS Fargate)の続きです。

実行されるパイプラインの流れは下記です。

  1. CodeCommitにプッシュ
  2. 特定のブランチへのプッシュをトリガーにCodePipelineを起動
  3. CodeBuildにてビルドを開始
  4. ECRリポジトリにイメージをプッシュ
  5. ECS Fargateにデプロイ

コンテナはFlaskを実行するコンテナを構築します。
CICDに焦点をあてるため、簡単なソース更新にてデプロイ実行を確認します。

目次

記事を3つに分割しました。

  1. コンテナ構築 / 起動
    1-1. Flaskアプリケーションの作成
    1-2. ローカル環境でコンテナの作成 / 起動確認
    1-3. ECRへイメージをプッシュ
    1-4. ECSでコンテナ起動

  2. CICDパイプラインの作成 ←本記事はここから
    2-1. CodeCommitの作成
    2-2. CodeBuildの作成
    2-3. CodePipelineの作成 ←ここまで

  3. 上記のコンテナ / CICDパイプライン構築をコード化
    3-1. CloudFormationテンプレート

前の記事はこちら
【AWS】 (初心者向け) CodePipelineを使ったECSコンテナのデプロイ自動化 その1(ECR, ECS Fargate)
後続の記事はこちら
【AWS】 (中級者向け) CodePipelineを使ったECSコンテナのデプロイ自動化 その3(CloudFormationでインフラコード化)

構成図

最終的な構成はこちら
aws構成図.png

2. CICDパイプラインの作成

2-1. CodeCommitの作成

パイプライン起動のトリガーとするCodeCommitを作成します。
AWSマネジメントコンソールにてCodeCommit管理画面でリポジトリを作成してローカル環境にクローンします。
今回はdevelopという名前のブランチへのプッシュをトリガーにパイプラインを実行するよう設定します。

git checkout -b develop
git add -A
git commit -m "develop branch"
git push -u origin develop

2-2. CodeBuildの作成

続いてCodeBuildを作成します。
まずAWS Secret ManagerというAWSサービスを使ってDockerHubの認証情報を保存しておきましょう。

本題から逸れるため、ここでは詳細まで記載しませんが理由は下記です。
参考にどうぞ。

DockerHubの認証情報が必要な理由

DockerHubは匿名ユーザーでのImageのダウンロード回数に、IPアドレスを基準とした制限を設けています。
匿名ユーザーはCodeBuildのグローバルIPを共有しているため、ダウンロード制限に引っかかってしまいビルド時にToo Many Requestsエラーが発生することがあります。

参考記事:
①AWS公式より一次情報
AWS CodeBuild で Docker イメージを使用する際に発生する「イメージ設定のプルエラー: toomanyrequests」というエラーを解決するにはどうすればよいですか?
②Classmethodさんより
“Too Many Requests.” でビルドが失敗する…。AWS CodeBuild で IP ガチャを回避するために Docker Hub ログインしよう!という話

2-2-1. Secret ManagerにDockerHub認証情報の保存

AWSマネジメントコンソールにてSecretManager管理画面で新しいシークレットを保存するを選択します。
シークレットのタイプでその他のシークレットのタイプを選択して、キー/値のペアにDockerHubのユーザー名、パスワードを保存します。
secretmanager_01.png

2-2-2. buildspec.ymlの作成

それでは前回までの作業フォルダにbuildspec.ymlというビルド時に使用する設定ファイルを作成します。

buildspec.yml
version: 0.2

env:
  secrets-manager:
    DOCKERHUB_USER: arn:aws:secretsmanager:$AWS_DEFAULT_REGION:$AWS_ACCOUNT_ID:secret:${作成したシークレット名}:username
    DOCKERHUB_PASS: arn:aws:secretsmanager:$AWS_DEFAULT_REGION:$AWS_ACCOUNT_ID:secret:${作成したシークレット名}:password
    # ${作成したシークレット名}は各自の環境のものに置き換え

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION) # ECRへログイン
      - echo Logging in to Docker Hub...
      - echo $DOCKERHUB_PASS | docker login -u $DOCKERHUB_USER --password-stdin # DockerHubへログイン
      - IMAGE_TAG=$CODEBUILD_RESOLVED_SOURCE_VERSION
  build:
    commands:
      - docker build -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:
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG # ECRへプッシュ
      - printf '[{"name":"%s","imageUri":"%s"}]' $CONTAINER_NAME $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG  > imagedefinitions.json

artifacts:
  files:
    - imagedefinitions.json

buildspec.ymlを追加したこの時点で一度developブランチへプッシュしておきます。

2-2-3. ビルドプロジェクトの作成

AWSマネジメントコンソールにてCodeBuild管理画面でサイドメニューのビルドプロジェクトのページに移動します。
ビルドプロジェクトを作成するから構築を進めます。

ソースに先ほど作成したCodeCommitを指定します。
codebuild_01.png

環境設定ではdockerの特権付与にチェックを入れることに注意しましょう。
サービスロールは新規作成で今回は進めます。
codebuild_02.png
▼追加設定を開き、buildspec.ymlに記載した環境変数をここで設定します。
codebuild_03.png
下記の値を設定します。

  • AWS_ACCOUNT_ID: アカウントID
  • AWS_DEFAULT_REGION: デフォルトのリージョン名(ap-northeast-1)
  • IMAGE_REPO_NAME: イメージのリポジトリ名(ECRで作成したリポジトリ名)
  • CONTAINER_NAME: コンテナ名(ECSタスク定義の作成時にコンテナの定義で追加したコンテナ名)

Buildspecの項目ではデフォルトでルートディレクトリのbuildspec.ymlが使用されます。
buildspec.ymlではないファイル名やルートディレクトリでない場所を使用している場合は、ファイル名を入力しましょう。
codebuild_04.png

ログの項目ではCloudWatchLogsをチェックしておきましょう。
CloudWatchのロググループが作成されビルドログを確認することができます。

ビルドプロジェクトが完成しましたが、このままではCodeBuildからSecret ManagerとECRへのアクセス権限エラーが発生するため権限を追加します。
サービスロールにアタッチされているポリシーに下記を追記します。

# CodeBuildからSecret Managerへのアクセスを許可
{
    "Effect": "Allow",
    "Action": "secretsmanager:GetSecretValue",
    "Resource": # 作成したシークレットのARN
}

またサービスロールにAWS管理ポリシーのAmazonEC2ContainerRegistryPowerUserをアタッチし、CodeBuildからECRへのアクセスを許可します。

ここまで設定した上でビルドプロジェクトからビルドの開始を実行しましょう。

ビルドのステータスが成功になりましたら、ECR管理画面でイメージがプッシュされていることも確認しましょう。

2-3. CodePipelineの作成

2-3-1. パイプラインの作成

つづいてCodePipelineを作成します。
AWSマネジメントコンソールにてCodePipeline管理画面のパイプラインを作成するから構築を進めます。
CodeBuildと同様にサービスロールは新規作成します。

ソースステージの追加で、先ほど作成したCodeCommitとdevelopブランチを選択します。
検出オプションでAmazon CloudWatch Events (推奨)を選択することでブランチ更新をトリガーにするイベントルールが作成されます。(次章で説明)
codepipeline_01.png

ビルドステージの追加で、先ほど作成したビルドプロジェクトを選択します。
ビルドタイプは単一ビルドを選択します。
codepipeline_02.png

デプロイステージの追加で、作成したECSクラスター、サービスを選択します。
イメージ定義ファイルにbuildspec.ymlで記載したimagedefinitions.jsonを指定します。
codepipeline_03.png

パイプラインを作成すると初回実行が開始されます。
デプロイステージ成功まで少し待ちます。
※必要に応じて自動テストや手動承認等のステージも含めるとより実運用に近いパイプラインになると思います。

2-3-2. developブランチの更新

developブランチの更新からCodePipelineの自動起動を確認します。

AWSマネジメントコンソールにてAmazon EventBridge管理画面でCodePipeline構築時にイベントルールが同時に作成されていることを確認します。
イベントパターンは下記のようになっているはずです。

{
  "source": ["aws.codecommit"],
  "detail-type": ["CodeCommit Repository State Change"],
  "resources": ["arn:aws:codecommit:${リージョン名}:${アカウントID}:${CodeCommitリポジトリ名}"],
  "detail": {
    "event": ["referenceCreated", "referenceUpdated"],
    "referenceType": ["branch"],
    "referenceName": ["develop"]
  }
}

"CodeCommit Repository State Change"
指定したCodeCommitリポジトリ(resources)のdevelopブランチ(referenceName)の作成、更新(event)をトリガーにCodePipelineを実行するといったイベントパターンです。

ちょこっとFlaskの勉強がてらBlueprintを使ってアプリを分割してみます。
※内容はなんでもいいです。今回はヨーロッパの都市と経度緯度を表示します。(Google Mapより)

  • cities.py (新規作成)
cities.py
from flask import Blueprint, render_template, request

api = Blueprint('cities', __name__)

@api.route('/', methods=["POST"])
def cities_europe():
    data = {
        "london": {
            "name": "London",
            "longitude": "-0.10661404281468599",
            "latitude": "51.520022345497104",
        },
        "paris": {
            "name": "Paris",
            "longitude": "2.383285523499959",
            "latitude": "48.885820046457034",
        },
        "amsterdam": {
            "name": "Amsterdam",
            "longitude": "4.7347868083578",
            "latitude": "52.48872238283727",
        }
    }
    name = request.form["europe"]
    city = data[name]
    return render_template('index.html', city=city)
  • app.py
app.py
from flask import Flask, render_template
from cities import api # この行を追加

app = Flask(__name__)
app.register_blueprint(api) # この行を追加

@app.route('/')
def index():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=80)
  • index.html
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Flask</title>
</head>
<body>
    <p>Hello from flask</p>

    <!-- 下記を追加 -->
    <form action="/" method="POST">
        <label for="europe">
            <div>
                <input type="radio" name="europe" value="london" id="london" checked>
                <label for="london">London</label>
            </div>
            <div>
                <input type="radio" name="europe" value="paris" id="paris">
                <label for="paris">Paris</label>
            </div>
            <div>
                <input type="radio" name="europe" value="amsterdam" id="amsterdam">
                <label for="amsterdam">Amsterdam</label>
            </div>
        </label>
        <input type="submit" value="Submit">
    </form>
    {% if city %}
    <p>welcome to {{city["name"]}}</p>
    <p>longitude: {{city["longitude"]}}</p>
    <p>latitude: {{city["latitude"]}}</p>
    {% endif %}
    <!-- ここまで -->

</body>
</html>

上記の変更をdevelopブランチにプッシュします。

CodePipelineの管理画面に移動して対象のパイプラインが自動起動し進行中になっていることを確認します。
デプロイまで完了したらブラウザでパブリックIPにアクセスしてみます。
※今回はEIPやドメイン設定をしていないためIPアドレスが可変のため注意です。
最新化されたindex.htmlがデプロイされていたら成功です。

citiespy_01.png
こんな感じです。

まとめ

今回はCodeCommitのdevelopブランチの更新をトリガーに、CodePipelineを実行する流れを簡単にまとめました。
デプロイまでの流れに重点をおいたため、ソースの更新やbuildspec.ymlの内容をより作り込むこともできると思いますが、それは状況に応じて置き換えてもらえればと思います。
余談ですが、ビルドログが更新されていくの眺めているの気持ちいいですよね。(病気)

パイプライン構築は扱うサービスも設定項目も多いので複数環境作成する際に手間がかかりますし、意図せぬ環境差分を作ってしまう恐れがあります。
そのため次記事ではECRの作成からCodePipelineの作成までをコード化してCloudFormationで構築する流れをまとめます。

後続の記事はこちら
【AWS】 (中級者向け) CodePipelineを使ったECSコンテナのデプロイ自動化 その3(CloudFormationでインフラコード化)

4
1
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
4
1