DockerイメージのビルドとAmazon ECRへのプッシュをCodeBuildで自動化する

S3に配置したアーティファクトからDockerイメージを作成し、ECRへpushする部分をCodeBuidで自動化します。  

CodeBuildの勉強を兼ねて本記事の手順を踏んでいますが、ソースコードリポジトリからDockerイメージのビルド・Amazon ECRへのプッシュが自動化できればOKという方は、以下のような素晴らしい記事がありますので、ぜひそちらをご参照ください。  
[AWS CodePipelineのコンテナビルドが簡単に！？](https://qiita.com/omk-pudding/items/6123f342be9eb2a58da7)

## 記事の概要
[こちら](https://qiita.com/e52yamada/items/4ec8080b9b4ab723ebfd)の記事で、ぽちぽちコマンドを実行してローカル環境でDockerイメージのビルド＆Amazon ECRへのプッシュを行いました。 数回ならいいですが、ソースコードやDockerfileの中身が変わるたびにコマンドを実行しているとめんどくさいのでCodeBuildで自動化してみます。以下の構成となるイメージです。  

![image (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261789/ce5b6399-c4ed-716d-5bf6-51f2e2224299.png)  


CodeBuildの利用で以下のようにフローが変わるイメージです。★がAfterで自動化される部分になります。
- Before
    - Dockerコマンド/AWS CLIが実行可能な開発環境を用意(初回のみ)
    - ソースコードとDockerfileを作成・変更
    - Dockerコマンド実行でDockerイメージを作成 ★
    - AWS CLIでDockerイメージをECRへプッシュする ★

- After
    - CodeBuildビルドプロジェクトの作成(初回のみ)
    - ソースコードとDockerfileを作成・変更し、S3にアップロードする
    - CodeBuildでビルドを実行

## ソースコードの準備
今回はPythonコードをコンテナで実行して簡単なWebアプリケーションを公開するので、app.pyというソースコードとDockerfileを用意します。

今回はWebサイトに、**`Hello world ver2`** と表示させます。ソースコードとDockerfileの内容は[記事の最後](#ソースの内容)に記載しました。

### S3へのアップロード
CodeBuildで自動化するにあたり、これらのソースコードをS3にアップロードする必要があります。今回は2つファイルをアップロードする必要がありますが、同じパスにアップロードするようにしてください。今回は以下の構成にしました。
```sh
<S3バケット>
    ∟ build-resource  //任意の名前でOK
        ∟ app.py
        ∟ Dockerfile  
```

S3バケットを作成していない場合は、S3の画面から「バケットを作成」ボタンをクリックしてバケットを作成できます。  
※バケット名は他者の所有するバケットも含めて一意の名前にする必要があります  

## CodeBuildの設定
CodeBuildを使い始めるには
- ビルドプロジェクト
- buildspec  

の2つを定義します。  

- ビルドプロジェクト
    - ソースコードの取得先、ビルド環境、実行するビルドコマンド、ビルドの出力先といったビルドの実行方法を指定
- buildspec
    - YAML形式でCodeBuildがビルドを行う際のコマンドや関連する設定を記載する

今回、ビルドプロジェクトはマネジメントコンソールで設定し、buildspecはymlファイルを用意します。  

### buildspec.ymlの作成 
先にbuildspec.ymlファイルを作成しておきます。buildspec.ymlのフォーマットやサンプルは[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/sample-docker.html)でご確認ください。  

```yml
version: 0.2
env:
  variables:
    IMAGE_REPO_NAME: "<ECRプライベートリポジトリの名前>"
    IMAGE_TAG: "latest"
    AWS_ACCOUNT_ID: "<アカウントID>"
    AWS_DEFAULT_REGION: "<リージョン>"
phases:
  pre_build:
    commands:
      - 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
  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

```
buildspec.ymlに`commands`で記載しているコマンドは全て、以下の記事で実行したコマンドそのものです。（変数を使っているので分かりにくいですが、手動で実行したコマンドをそのまま貼り付けてもOKです）  
[AWS CloudShellでDockerイメージを作成してAmazonECRプライべートリポジトリにpushしてみる](https://qiita.com/e52yamada/items/4ec8080b9b4ab723ebfd#ecr%E3%81%AE%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8Bdocker%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%81%AE%E4%BD%9C%E6%88%90ecr%E3%81%B8%E3%81%AE%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5)  

自分が実行したコマンドをbuildspec.ymlで定義しているだけです、わかりやすいですね。

buildspec.ymlを作成したら、ソースコードと同じS3のディレクトリにアップロードします。S3バケット内の構成は最終的に以下になります。

```sh
<S3バケット>
    ∟ build-resource //任意の名前でOK
        ∟ app.py
        ∟ Dockerfile  
        ∟ buildspec.yml 
```

### ビルドプロジェクトの作成
もう一つ必要な準備として、マネジメントコンソールでビルドプロジェクトの設定をしていきます。  
- CodeBuildの画面で「プロジェクトの作成」をクリックします
- プロジェクト名は任意の名前を入力し、ソースプロバイダは「Amazon S3」を選択し、バケットにソースコードをアップロードしたS3バケットを選択。「S3オブジェクトキーまたはS3フォルダ」はS3バケットの構成に合わせて入力（今回の構成の場合は`build-resource`を入力）  
![image (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261789/0f14b2b0-e765-e1af-c722-4eace45e1b63.png)

- 環境はデフォルトの設定です。コンテナの動作環境に合わせてオペレーティングシステムは「Amazon Linux」としました。イメージ、イメージのバージョンは最新のものを選んでおけばOKだと思います。  

![image (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261789/5d24c725-49a9-ba8e-c041-0e523a004f66.png)  

- 今回はbuildspec.ymlを別途用意したので、ビルド仕様では「buildspec.yml ファイルを使用する」を選択します。ファイル名は指定することもできますが、デフォルトで「buildspec.yml」が指定されるのでそのままにしておきます。  

![image (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261789/bd5f433a-6de0-6dab-1a77-0b7ce5f2bc9e.png)


ここまで入力したら、「ビルドプロジェクトを作成する」ボタンをクリックします。

### IAMロールの編集
ビルドプロジェクト作成後、一つだけ変更を行います。デフォルトで作成されるIAMロールはCodeBuil自体の実行やS3へのアクセスは可能ですが、ECRへのアクセス許可が不足しているので追加します。  

- 作成したビルドプロジェクトの「プロジェクト詳細」タブを開いてスクロールしていくと「環境」セクション内に「サービスロール」が表示されているので、リンクをクリックします。  

![image (9)-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261789/6cb0d220-dc70-d84d-1377-33f8bf5c347a.png)  


- 「許可を追加」>「ポリシーを追加」をクリックします。今回は検証のみなので、追加するポリシーとしてAWSマネージドポリシーの `AmazonEC2ContainerRegistryFullAccess` を選択します
:::note warn
実際は最低限のアクセス許可のみの追加が推奨されます  
:::
![image (10)-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261789/bf927f5b-09bb-4083-baa0-9c60c5f608ff.png)


ここまででCodeBuildの設定は完了です。  

## ビルドの確認
実際に自動ビルドを試してみます。

### ビルドの実行
作成したビルドプロジェクトの画面に「ビルドを開始」ボタンがあるので、これをクリックします。  

![image (11).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261789/658be6d0-8836-5426-e376-42f27dd1246f.png)  


ステータスが「進行中」から「成功」に変わったらビルド完了となります。  

![image (12).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261789/dd8ac6aa-da43-58e4-743e-ae55dbca92e2.png)  


buildspecの記述通りにビルドし、指定したECRリポジトリへイメージがpushされているはずなので、ECRリポジトリのイメージ一覧を確認してみます。  

![image (15).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261789/b0e07aaa-cb40-b8ba-7b12-aa7ed1975ce4.png)  

新しいイメージが追加されていることが確認できると思います。

### AppRunnerでデプロイして確認する
せっかくなので、pushしたDokcerイメージをAppRunnerにデプロイして確認してみたいと思います。AppRunnerについては以下の記事で設定済みですので、詳細な手順はこちらをご参照ください。  
[AWS AppRunnerを使ってAWS ECRに配置したDockerイメージをAWSにデプロイする](https://qiita.com/e52yamada/items/6f03efa4a164e6c9747a)  

AppRunnerでサービスをデプロイ済であれば、再度AppRunnerのサービスの管理画面で「デプロイ」ボタンをクリックすることで、新しいDockerイメージを使ってサービスがデプロイされます。  

![image (13)-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261789/190d1b87-610d-5532-4835-f68992f833f4.png)  


ステータスが「Running」になったらデプロイ完了ですので、「デフォルトドメイン」のリンクをクリックして、インターネットに公開されているサービスを確認してみます。  

![image (14).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261789/8c85ee1e-2368-912d-a4f0-c048c859ea5b.png)  


**`Hello world ver2`** と表示されており、今回アップデートしたソースコードの内容が反映されていることが確認できました。

## ソースの内容
参考としてS3にアップロードするソースコード(Python)とDockerfileを記載しておきます。

app.py
```py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
        str_out = '<h2>Hello world ver2</h2>'
        return str_out
        
app.run(host='0.0.0.0',port=80)
```

Dockerfile
```yaml
#ベースイメージとして pythonn v3.12 を指定
FROM python:3.12

# 以降の RUN, CMD コマンドを実行する作業ディレクトリを指定
WORKDIR /app

# カレントディレクトリにあるファイルをコンテナ上の ｢/app｣ ディレクトリにコピーする
ADD . /app

# Flaskをインストール
RUN pip install flask

# 待受けポートの指定
EXPOSE 80

# コンテナが起動時に実行するコマンドを指定
CMD ["python", "app.py"]
```
---
この記事はここまでとなります。

#### Next:  
S3へのソースコードアップロード～ビルドプロジェクト実行を自動化したい▼
[S3へのコードのアップロードを検知してCodeBuildを実行するCodePipelineの作成](https://qiita.com/e52yamada/items/21d725b8a7aa3e925080)