見出し画像

AWSコンテナCI/CD実装ハンズオン【ECS、Fargate】


こんにちは。SHIFTのITソリューション部の當眞です。
AWSを専門に主に上流(要件定義、基本設計)工程を担当しています。

インフラエンジニアはコンテナが必修科目になってくると考えていますので、コンテナ関連の記事をどんどん書いていこうと思っています。

はじめに


クラウドコンピューティングの世界において、迅速かつ効果的なコンテナ構築は不可欠です。

このブログでは、AWSのElastic Container Service(ECS)を活用して、 簡単かつ迅速にコンテナを構築し運用する方法を紹介します。

コンテナの利用量はすごい速度で増えています。

本記事はハンズオンをメインで記載していますが、
AWSでのコンテナの概要を理解したい方は以下の記事もおすすめです。

ハンズオン内容


本記事のハンズオン内容です。

AWSコンテナ設計・構築[本格]入門 の4章のハンズオンが最短でできる構成にしています。

AWSコンテナ設計・構築[本格]入門 の4章の内容を読んだ方がより理解は深まると思うのですが、
読む時間が無く、最短で構築して運用体験をしたい方向けにまとめました。

※AWSコンテナ設計・構築[本格]入門は良書なのでおすすめです。時間があればぜひ読んでみてください。

https://www.amazon.co.jp/AWS%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E8%A8%AD%E8%A8%88%E3%83%BB%E6%A7%8B%E7%AF%89%EF%BC%BB%E6%9C%AC%E6%A0%BC%EF%BC%BD%E5%85%A5%E9%96%80-%E6%96%B0%E4%BA%95-%E9%9B%85%E4%B9%9F-ebook/dp/B09DKZC1ZH/ref=sr_1_5?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=218RSM3GEC8GO&keywords=%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A+Docker&qid=1684033055&sprefix=%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A+docker%2Caps%2C179&sr=8-5

※本書通りに進めていても、最新のnodeバージョンを使うと動かないなどのハマりポイントがあり、それを回避する手順を入れています。

コンテナを動かすだけならもっと簡単にできる方法を紹介したブログが多数ありました。

本記事は各AWSリソースの構築やコンテナイメージの作成手順などを細かく分轄することで、
便利機能によってブラックボックス化されてしまいがちな処理の理解ができるようにしています。

本記事の構成は以下の通りです。
CDK経験者であれば3時間程度で完了できるハンズオンです。

  • CDK基礎
    CDKを使ったことない方の場合はまずはCDKの理解をする時間が別途必要になります。
    最初に簡単にですがCDKについても触れます。

  • 構築:サンプルのCDKコードでコンテナを作る手順(2時間)
    ネットワーク構築からコンテナの配置してサンプルアプリをブラウザ表示できるまでの
    手順が2時間以内でできるようにまとめています。

  • 運用:コンテナのリリースを体験(1時間)
    構築完了後、サンプルアプリの変更及び、再リリースを体験できる手順です。
    ブルーグリーンデプロイが簡単に実現できることを学ぶことができます。
    (ブルーグリーンをEC2でやろうとするとかなり苦労します。。コンテナならブルーグリーン一択でよいかと思います)

CDKコードによって設定はほとんど自動で完了しますので、
コンテナ初心者の方でも、つまずくことなく設定できると思います。
構築した設定内容をマネコンで確認することも良い勉強になるかと思います。

【消し忘れは要注意】
一部時間課金のサービスを利用するため、無料ではない点に注意してください。
できれば一日でやり切れると良いと思います。3時間でやり切れれば1USD以下だと思います。
です。
ご参考:私が雑務により作業中断はさみながら8時間程度でハンズオン実施したところ1.8USD程度かかりました。

※ハンズオンはWindowsのPCで動作確認できています。
※CDKのバージョンは2.101.0で実施しましたが、CDKは後方互換ありのため、最新Verでも問題なく動作するものと思います。

CDK基礎


CDKを触ったことが無い方はCDKを理解してから本ハンズオンを実施することをおすすめします。
以下のCDK初心者向けのハンズオンをやっておくと良いでしょう。
本ハンズオンではCDKのTypescriptを利用していますので、
Typescriptワークショップを実施してください。

サンプルのCDKコードでコンテナを作る手順(2時間)


ここでは以下のハンズオンを実施します。
CDKやエディタ(VSCodeがおすすめ)のインストールはできているものとします。
(各コマンドは注記無ければVSCodeのPowerShellで実行しています。)

  1. ネットワークなど構築(IAM Role、VPC、サブネット、IGW、ルートテーブル)

  2. コンテナレジストリ構築(ECR)

  3. コンテナイメージ作成(Cloud9★で実施)

  4. オーケストレータ構築(SG、★ELB、★VPC Endpoint、★ECS、CodeDeploy)
    ★は課金対象です。(他は無料かほぼ無料、Cloud9は無料枠が使えれば無料)

※本コードによって作成されるAWS構成図はAWSコンテナ設計・構築[本格]入門 の4章をご確認頂ければと思います。

順を追って手順を示します。

サンプルCDKコード取得

どのパスでも良いので最初に「container-demo」というディレクトリを作成します。
作成したディレクトリに移動し、「container-temp」という名前で新規ディレクトリを作成し、
作成したディレクトリに移動します。

以下のコマンドを実行します。

cdk init --language typescript

「node_modules」フォルダが作成されたことを確認します。

「container-demo」に戻り、以下を実行します。

git clone https://github.com/toma1110/container-basic.git

「container-basic」ディレクトリが作成されます。

ここまでで以下のようなフォルダ構成になっているはずです。

「container-temp」ディレクトリにcdk initコマンドで自動作成された「node_modules」フォルダを 「container-basic」ディレクトリにまるごとコピーします。

container-basicディレクトリに移動して以下のコマンドを実行します。

cdk ls

以下の通り表示されたらコード実行準備が完了です。

VpcStack
IamStack
EcrStack
EcsStack
SecretsManagerMasterUserStack
RdsStack
SecretsManagerDBUserStack

ネットワークなど構築(IAM Role、VPC、サブネット、IGW、ルートテーブル)

以下の通り順次、CDKを実行します。

cdk deploy IamStack

cdk deploy VpcStack

両コマンドの正常終了を確認できたらIAMとVPCリソースの作成は完了です。

コンテナレジストリ構築(ECR)

cdk deploy EcrStack

ECRの作成はこれで完了です。

コンテナイメージ作成(Cloud9★で実施)

Cloud9上で実施します。まずはCloud9を構築します。 Cloud9は有料のため、ハンズオンが終わったら削除しましょう。 (削除し忘れるとストレージ料金がかかります(EC2インスタンスはCloud9が自動停止してくれます)。)

※途中でCloud9のストレージ容量を追加する手順を含めているのですが、作成時にストレージ容量指定できるようにしてほしいです。。
※CDKコードでCloud9を作成するとエラーになり、エラー解消ができなかったため、この部分はCDKコード化ができませんでした。

Cloud9作成手順

AWSマネージメントコンソールにてCloud9サービスを開きます。

「環境を作成」をクリックします。
以下の通り設定します。記載のない項目はデフォルトで大丈夫です。

  • 名前 任意の名前

  • インスタンスタイプ t3.small

  • プラットフォーム AmazonLinux2(※)

  • ネットワーク設定

  • 接続 セキュアシェル (SSH)

  • VPC設定

  • VPC:sbcntr-stg-vpc

  • subnet:sbcntr-stg-subnet-mng-1a

※AmazonLinux2023を選択すると後のコマンドがエラーで進めなくなるため要注意

しばらく待ち(5分くらい)正常に作成されたら「Cloud9 IDE」列の「開く」をクリックします。
ブラウザ上でIDEが表示されます。コンテナイメージ作成はこのIDE上で実施していきます。

Backendコンテナイメージ作成

Cloud9のIDE上で以下のコマンドを実施します。

  • コンテナイメージのビルド

git clone https://github.com/uma-arai/sbcntr-backend.git
cd sbcntr-backend/
docker image build -t sbcntr-stg-backend:v1 .

→2~3分くらい待ちます

  • イメージのリポジトリ登録

AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
docker image tag sbcntr-stg-backend:v1 ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sbcntr-stg-backend:v1
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sbcntr-stg-backend:v1
docker image rm -f $(docker image ls -q)
docker image ls

→ローカルのDockerイメージがすべて削除されたことを確認します。

  • イメージのプルと動作確認

docker image pull ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sbcntr-stg-backend:v1
docker image ls

→リポジトリからプルしてきたDockerイメージのみが表示されます。

docker container run -d -p 8080:80 ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sbcntr-stg-backend:v1
docker container ls

→container idをメモします。

curl http://localhost:8080/v1/helloworld

→ここで「{"data":"Hello world"}」と表示されれば正しくコンテナの作成が完了できています。

  • 後片付け

docker container stop <ここに「docker container ls」で確認したコンテナIDを入力>
docker image rm -f $(docker image ls -q)
cd ../
rm -rf sbcntr-backend

Cloud9ストレージ増加

Cloud9によって作成されたEC2のストレージ容量を10GBから20GBに変更します。

ボリュームはまずmodifyingの状態になりますが、すぐにOptimizingの状態になります。
Optimizingの状態なればサイズの変更は完了していますので、
Cloud9上で以下のコマンドを実行し、パーティション拡張します。
(Optimizingの状態はしばらく続きます)

lsblk

→以下のように表示されます。「/」のパーティションは10GBです。

NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
nvme0n1 259:0 0 20G 0 disk
├─nvme0n1p1 259:1 0 10G 0 part /
└─nvme0n1p128 259:2 0 1M 0 part

sudo growpart /dev/xvda 1
sudo xfs_growfs /dev/xvda1
lsblk

→「/」のパーティションが20GBになっていたらストレージの変更は完了です。

NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
nvme0n1 259:0 0 20G 0 disk
├─nvme0n1p1 259:1 0 20G 0 part /
└─nvme0n1p128 259:2 0 1M 0 part

Frontendコンテナイメージ作成

  • コンテナイメージのビルド

git clone https://github.com/uma-arai/sbcntr-frontend.git
git reset --hard 1c13f7b3363a0f30efef94e3dd3313e838bb9889
cd sbcntr-frontend/
docker image build -t sbcntr-stg-frontend:v1 .

→5分〜10分くらい待ちます。warningは無視して大丈夫です。
※【2024/10/23追記】2024/6/4のCommitによりエラーが発生するようになったため、その前のコミットの状態に戻すため、git resetを追加しました

  • イメージのリポジトリ登録

AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
docker image tag sbcntr-stg-frontend:v1 ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sbcntr-stg-frontend:v1
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sbcntr-stg-frontend:v1
docker image rm -f $(docker image ls -q)
docker image ls

→Dockerイメージがすべて削除されたことを確認します。

  • イメージのプルと動作確認

docker image pull ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sbcntr-stg-frontend:v1
docker image ls

→リポジトリからプルしてきたDockerイメージのみが表示されます。

docker container run -d -p 8080:80 ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sbcntr-stg-frontend:v1
docker container ls

→container idをメモします。

curl http://localhost:8080/ |grep アライとウマカツ

→ここでHTMLの一部が行表示されることを確認します。

  • 後片付け

docker container stop <ここに「docker container ls」で確認したコンテナIDを入力>
docker image rm -f $(docker image ls -q)
cd ../
rm -rf sbcntr-frontend

オーケストレータなど構築(SG、★ELB、★VPC Endpoint、★ECS、CodeDeploy)

ここからは本格的に課金対象のリソースを作っていきます。
時間に余裕がなければここで今日はここまでで作業終了としても良いです。
(cloud9は自動停止してくれます)

オーケストレータに必要なリソース構築(SG、★ELB、★VPC Endpoint)

cdk deploy EcsStack

→SG、ELB、VPCエンドポイントが作成されます。

オーケストレータ構築(★ECS)

  • コード修正
    lib\stack\ecs_stack.tsの
    「targetGroupが無いとエラーになる。」
    と記載の個所をコメントインする。

cdk deploy EcsStack

→ECS(クラスター、サービス、タスク定義)が作成されます。

オーケストレータ構築(CodeDeploy)

マネコンで作成する場合、CodeDeployはECSサービスの作成時にデプロイメントタイプをBlueGreenにすると自動作成されるのですが、
CDKの場合は自動作成されなかったので、コードで作成しています。

  • コード修正
    lib\stack\ecs_stack.tsの
    「ECSが無いとエラーになる。」
    と記載の個所をコメントインする。

cdk deploy EcsStack

→CodeDeployが作成されます。

動作確認

作成されたELBのsbcntr-stg-alb-frontendの方のDNS名をコピーします。(以下のようなURLです)
ブラウザで「Hello world」という画面が表示されることを確認できます。
http://sbcntr-stg-alb-frontend-XXXXXXXXXX.ap-northeast-1.elb.amazonaws.com/

現時点ではデータベースが構築されていないため、画面表示までの確認になります。
ログイン機能や新規ユーザー作成機能はDBが無いため、エラーになります。

ここまででコンテナイメージの作成とコンテナ実行し、グローバルに公開するところまでの確認ができました。

もし別環境でECSのコンテナが起動しない時、ここまで実施したハンズオンの設定と見比べることで、誤りに気付くことができるかもしれません。

それでは引き続き設定変更する体験を実施していきます。

コンテナのリリースを体験(1時間)


ユーザー登録機能を追加するためにデータベースを作成し、
コンテナがDBアクセスできるように変更します。

データベース構築

cdk deploy SecretsManagerMasterUserStack

→DBユーザーのパスワード用シークレット「sbcntr-stg-secret-rds-cluster」が作成されます。
シークレットの値(パスワード)をメモしておきます。

cdk deploy RdsStack

→DB作成は15分くらいかかります。

DBデータ投入

Cloud9上で以下のコマンドを実行します。

■DBユーザー作成

cd /home/ec2-user/environment
  • RDSのエンドポイント確認コマンド

aws rds describe-db-clusters --query "DBClusters[].[Endpoint]" --output text | column -t
export DbEndpoint=[Auroraクラスター書き込み(ライター)エンドポイント]
mysql -h ${DbEndpoint} -u admin -p

※パスワードはSecretsManagerのsbcntr-stg-secret-rds-clusterから取得

SELECT Host, User FROM mysql.user;

→最初はデフォルトユーザーしかいません。

CREATE USER sbcntruser@'%' IDENTIFIED BY 'sbcntrEncP';
GRANT ALL ON sbcntrapp.* TO sbcntruser@'%' WITH GRANT OPTION;
CREATE USER migrate@'%' IDENTIFIED BY 'sbcntrMigrate';
GRANT ALL ON sbcntrapp.* TO migrate@'%' WITH GRANT OPTION;
GRANT ALL ON `prisma_migrate_shadow_db%`.* TO migrate@'%' WITH GRANT OPTION;
SELECT Host, User FROM mysql.user;

→migrateとsbcntruserユーザーが作成されたことを確認します。

+-----------+------------+
| Host | User |
+-----------+------------+
| % | admin |
| % | migrate |
| % | sbcntruser |
| localhost | mysql.sys |
| localhost | rdsadmin |
+-----------+------------+

exit

■テーブルとデータの作成

  • 作成済ユーザーによるログイン確認とテーブルが存在しないことを確認。

mysql -h ${DbEndpoint} -u sbcntruser -psbcntrEncP
exit
mysql -h ${DbEndpoint} -u migrate -psbcntrMigrate
use sbcntrapp;
show tables;

→「Empty set」と表示される(=テーブルが無い)ことを確認。

exit

○テーブル作成とデータ投入。

git clone https://github.com/uma-arai/sbcntr-frontend.git
cd sbcntr-frontend/
git checkout main
export DB_USERNAME=migrate
export DB_PASSWORD=sbcntrMigrate
export DB_HOST=${DbEndpoint}
export DB_NAME=sbcntrapp
  • モジュールインストール(npmではなくyarnでモジュール管理している)

npm install -g yarn
yarn install
  • テーブル作成

npm run migrate:dev

「? Name of migration ?」と表示されたら

init

と入力する。

  • データ作成

最新のnodeではデータ作成に失敗するため、Ver14をインストールして実施。

nvm install 14.21.3
nvm use 14.21.3
npm run seed
  • テーブル、データ確認

mysql -h ${DbEndpoint} -u sbcntruser -psbcntrEncP
use sbcntrapp;
show tables;

→以下の通りテーブル作成されていることを確認します。

+---------------------+
| Tables_in_sbcntrapp |
+---------------------+
| Item |
| Notification |
| Session |
| User |
| _prisma_migrations |
+---------------------+

select * from Notification;

→コンテナアプリケーションを作成した時間が表示されることを確認します。

exit

コンテナイメージの変更

■Frontendコンテナ(DBアクセス有版)作成

docker image build -t sbcntr-stg-frontend .

→10分くらい待ちます。
※ここで出るワーニングメッセージは無視して大丈夫です。

docker image ls
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
docker image tag sbcntr-stg-frontend:latest ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sbcntr-stg-frontend:dbv1
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sbcntr-stg-frontend:dbv1
docker image rm -f $(docker image ls -q)
cd ../
rm -rf sbcntr-frontend

DB情報の登録

cdk deploy SecretsManagerDBUserStack

オーケストレータ設定変更(Frontend)

lib\resource\ecs.tsの
「コンテナのリリースを体験」実行の際にコメントイン
と記載の個所をコメントインする。
※3か所あります

「コンテナのリリースを体験」実行の際にコメントアウト
と記載の個所をコメントアウトする。

cdk deploy EcsStack

オーケストレータ設定変更(Backend)

BackendはCDKで変更しようとすると以下のエラーが出るため手動対応としています。 エラー内容からは手動でも更新できないように感じますが手動なら更新できます。

  • エラー内容

Invalid request provided: UpdateService error: Unable to update task definition on services with a CODE_DEPLOY deployment controller. Use AWS CodeDeploy to trigger a new deployment.

このエラーメッセージが意味するのは、CDKを使用して直接ECSタスク定義を更新しようとしても、
CODE_DEPLOYデプロイメントコントローラーを使っている場合はその方法で更新できないということです。
CDKではなく、AWS CodeDeployを介してデプロイメントを実行し、新しいタスク定義を適用する必要があります。

ECSサービス「sbcntr-stg-backend-service」手動更新します。(以下3点)

  1. 「新しいデプロイの強制」をチェックする

  2. 「デプロイオプション」で作成したCodeDeployを選択する

  3. 「環境変数」に以下を登録する(すべてValueFromで登録する※)

※登録内容はsbcntr-stg-frontend-serviceと同様

"name": "DB_HOST",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-1:XXXXXXXXXXXX㊙️sbcntr/mysql-XXXXXX:host::"

"name": "DB_NAME",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-1:XXXXXXXXXXXX㊙️sbcntr/mysql-XXXXXX:dbname::

"name": "DB_USERNAME",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-1:XXXXXXXXXXXX㊙️sbcntr/mysql-XXXXXX:username

"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-1:XXXXXXXXXXXX㊙️sbcntr/mysql-XXXXXX:password

新しいデプロイの強制をチェックすることでCodeDeployのデプロイが開始されます。(ブルーグリーンデプロイ)

動作確認

作成されたELBのsbcntr-stg-alb-frontendの方のDNS名をコピーします。(以下のようなURLです)
http://sbcntr-stg-alb-frontend-XXXXXXXXXX.ap-northeast-1.elb.amazonaws.com/

アプリケーションが更新され、サインアップ、ログイン、ログアウトなどが成功することを確認します。

後片付け


ECRにイメージが残っているとCDKのリソース削除に失敗するため、アップロードしたイメージを手動削除します。

クロススタックで相互参照となっているため、SecretsManagerDBUserStackの削除に失敗します。
(これはCDKで有名なハマりポイントです。CDK実運用の時には一番注意が必要な部分です。スタック構成を見直せば回避できます。)

そのため、コードを編集前の状態に戻します。(強制的にGit Pullしたときのコードに戻す)

git fetch origin main
git reset --hard origin/main

この状態で一度EcsStackを更新することで、相互参照を削除できます。

cdk deploy EcsStack

相互参照を削除したらリソース全削除します。

cdk destroy --all

手動作成した、Cloud9の削除を行います。

これで後片付け完了です。お疲れさまでした。


執筆者プロフィール(當眞 尚平)
AWSエンジニアです。インフラ案件上流(要件定義)から下流(運用)までこなします。
得意分野:IaC(Terraform、CDK)AWS認定:SAP、SAA、DVA、SOA、DOP、MLS、DAS、SCS、ANS直近の目標:DevOpsエンジニアになる
SHIFTでやっていること:IaC、CI/CD、AWSセキュリティ実装

お問合せはお気軽に

SHIFTについて(コーポレートサイト)

SHIFTのサービスについて(サービスサイト)

SHIFTの導入事例

お役立ち資料はこちら

SHIFTの採用情報はこちら

PHOTO:UnsplashAaron McLean