はじめに
■ご案内■
本連載の背景/作成できるアプリケーション/進め方をご理解頂く上でも【環境構築編】をご一読頂けると幸いです。
- 【環境構築編】
- 【Next.js編】
- 【Go編】
- 【AWS編】 👈いまここです
これからも頑張ってハンズオン系の記事を書いていきたいと思っているので、いいねっと思って頂けたらLGTM押していただけると励みになります!
ここからは実際にアプリケーションをAWSへデプロイしていきます。
本記事では、プロビジョニングツールとしてCloudformationを利用しています。
注意点
利用するAWSの各リソースは課金対象となるものが多く含まれています。個人利用される方は特に注意してください。発生した費用に関して一切責任を負いかねます。
また、本記事で用いる.yml
ファイルは以下のリポジトリに格納しているので、適宜クローンをお願いします。
構成図
リソース紹介
まず最初に、簡単ではありますが、本デプロイフロー(CI/CD)で利用する、主要なAWSリソースを紹介していきます。
本記事では、これまで説明したアプリケーションをAWSにデプロイすることのみに留めています。従って、各種設定ファイルの細かい説明は割愛しているのでご承知おきください。
ECS
Amazon ECS (Elastic Container Service)は、スケーラブルで高速なコンテナオーケストレーションサービスです。ユーザーは、アプリケーションのデプロイ、スケーリング、管理を簡単に行うことができます。
また、AWS Fargateは、サーバーレスのコンテナ実行環境で、インフラストラクチャの管理を気にせずにアプリケーションをビルド、デプロイ、スケールすることができます。
ECR
Amazon ECR (Elastic Container Registry)は、Dockerコンテナイメージを簡単に保存、管理、デプロイできるフルマネージド型のコンテナレジストリサービスです。
CodePipeline
AWS CodePipelineは、連続的インテグレーション (CI) と連続的デリバリー (CD) サービスです。また、同リソース内でCodeBuild, CodeDeploy等と連携することができます。
CodeBuild
AWS CodeBuildは、ソースコードのコンパイル、テストの実行、アーティファクトのパッケージ化を行うフルマネージドのビルドサービスです。
本記事では、このリソース内でDockerイメージのビルドとECRへのプッシュ、さらにhadolint/Dockle/trivyといった脆弱性診断ツールも実行しています。
また、Go製のマイグレーションツールであるGooseを用いたマイグレーションも実行しています。
Codedeploy
AWS CodeDeployは、アプリケーションの更新を自動化するデプロイサービスで、新しい機能のリリース、アップデートのバグ修正、アプリケーションの稼働時間を最大化します。
また、AWS CodeDeployを使用してECSでブルーグリーンデプロイメントを実行すると、新旧のアプリケーションバージョンを平行して実行でき、最小限のダウンタイムで安全に更新が可能です。
RDS
Amazon RDS (Relational Database Service)は、スケーリングとメンテナンスが容易なマネージドリレーショナルデータベースサービスです。MySQL, PostgreSQL, Oracleなど複数のデータベースエンジンをサポートしています。
本記事では、MySQLを利用しています。
AWS Systems Manager Parameter Store
AWS Systems Manager パラメータストアは、機密情報を安全に保存し、暗号化して管理するサービスです。データベース接続文字列やパスワードなどの機密情報をセキュアに管理できます。
事前準備
上述の通り、本記事ではIaCとしてCloudformationを利用します。
AWSのマーネージメントコンソールから、同ツールを使ってスタックをアップロードする前に事前設定を行っていきます。
また、これまでの記事で紹介した通り、
ご自身のGitHubRepositoryに以下2つのプロジェクトがプッシュされている必要があります。
go-graphql-jwt-api
next-graphql-front
IAM作成
既にAWSマネージメントコンソールにログインでき、同様の権限が付与されているIAMユーザーをお持ちの方はスキップして頂いて構いません。
以下の記事等を参考にrootに近い管理者権限を持つユーザーを作成してください。
既にIAMユーザーをお持ちでマネージメントコンソールにログインできる方は、ポリシーを付与するだけでも問題ありません。
二つのポリシーがアタッチされていることを確認してください。
- IAMUserChangePassword
- AdministratorAccess
AWS CLIインストール
既にAWS CLIがローカルマシンにインストールされている方はスキップして頂いて構いません。
本記事では以下目的でAWS CLIを利用します。
- ローカルマシンから踏み台EC2経由でRDSにssh接続
- ECRに初期イメージをプッシュ
以下の記事などを参考に設定を行ってください。
ドメイン登録とSSL/TLS認証書作成
-
Route53
- ドメイン登録(有料)を参考にドメインを作成してください。
- 本リポジトリでは以下のような形でサブドメインを利用することを想定しています。
- フロント:
front.example.com
- API:
api.example.com
- フロント:
- 各レコード/サブドメインの作成と転送先のALBとの紐付けは後述します。
-
ACMを使ったSSL/TLS認証書発行
- 本アプリケーションはhttpsでの通信を行います。
- 以下の記事を参考に作成したドメインに紐づけく証明書を発行してください。
-
2-1. AWS Certificate ManagerでSSL/TLS認証書をリクエスト
の箇所だけで大丈夫です。
EC2(RDSへの踏み台)へのSSH接続用のキーペア作成
Amazon Web Services(AWS)におけるキーペアは、EC2インスタンス(仮想サーバー)への安全なアクセスを行うために必要になります。本記事では、当該EC2は、RDSへSSH接続するための踏み台サーバーとしての役割を担います。
-
キーペア作成方法
- AWSコンソール > EC2 > キーペア > キーペアを作成
- .pemファイルをローカルマシンにダウンロード
- ローカルマシンでのペアキーの許可設定
ダウンロードしたキーを~/.ssh/
配下に移動して、適切な権限に変更します。
$ mv ~/Downloads/db-access-key.pem ~/.ssh
$ chmod 400 ~/.ssh/db-access-key.pem
ご注意
今回は簡単のため, SSH接続でEC2経由でRDSへ接続しています。一方で、Session Managerを利用すると、これまでのようにEC2インスタンスにパブリックIP経由でSSH接続せずとも色々と作業ができるのでおすすめです。
Cloudformationを使ったスタックアップロード
事前準備が完了したので、ここからは実際にAWSの各リソースを構築していきます。
Cloudformationは、Amazon Web Services(AWS)が提供するインフラストラクチャー管理サービスの1つです。
本リソースを使用することで、AWSリソース(EC2インスタンス、VPC、S3バケットなど)のプロビジョニングや管理を簡単に行うことができます。
上述の通り、本記事では以下のリポジトリに当該ファイルが格納されています。
vpc.yml(共通)
- Cloudformation > スタック > スタックの作成 > 新しいリソースを使用(標準)
- 以下の画像を参考に”ファイルの選択”/common/vpc.ymlを選択
- スタックの詳細を指定 > 各項目に表示されているデフォルのままで変更せずOK
- ネットワークのCIDRが既存の設計と競合する場合は適宜修正してください。
- スタックの名前は識別できる任意のもの
- スタックオプションの設定 > 設定不要
- 次へ > 送信
- スタックに表示されているステータスが”CREATE_COMPLETE”になれば完了
ecr.yml(API・フロント計2種構築)
- 上記と同様な手順で
/api/ecr.yml
をアップロード - スタックの詳細を指定 > 各項目に表示されているデフォルのままで変更せずOK。
- スタックの名前は識別できる任意のもの(フロントとAPIが区別できるように)。
- ex.api-ecr
- System Configuration
- Environment
- デフォルト表示のままでOK
- Environment
ローカル環境でDocker imageのビルドと脆弱性チェック
本記事で作成しているCICDでは、Dockerfileの静的解析とDockerイメージの脆弱性チェックとして、以下のツールを利用しています。
- hadolint
- Dockle
- trivy
また、各リポジトリのREADME.md
に実行用のコマンドを記載しています。
下記はgo-graphql-jwt-api
のサンプル例になります。
コマンド実行は、go-graphql-jwt-api
とnext-graphql-front
のディレクトリ配下で実行してください。
Docker Build
- 本番ターゲットを指定して実行
- ECSで実行できるようにプラットフォーム指定
# go-graphql-jwt-api
$ docker build --no-cache --target runner -t api-dev-repo --platform linux/amd64 -f ./build/docker/go/Dockerfile .
hadolint
Hadolintは、Dockerfileの静的解析ツールであり、Dockerfileの構文、構造、効率に関するベストプラクティスに従っているかどうかをチェックします。
実行後に何も表示されなければOKです。
$ brew install hadolint
$ hadolint --ignore DL3018 build/docker/go/Dockerfile
本記事では、読者の方の正常動作を担保するため、ライブラリのバージョンを固定するルールを無効化しています。ただし、Dockerfileでパッケージをインストールする際には、通常、特定のバージョンを指定することが推奨されます。
Dockle
主にDockerイメージのベストプラクティスに焦点を当てた軽量のコンテナイメージスキャナです。
※本記事の設定では一旦、warn
とinfo
は無視しています。
$ brew install goodwithtech/r/dockle
$ dockle api-dev-repo
trivy
Trivyは、脆弱性スキャナであり、Dockerイメージおよびファイルシステムに存在するセキュリティ上の脆弱性を検出することに特化しています。
実行後に、Total: 0 (CRITICAL: 0)
が表示されていればOKです。
$ brew install aquasecurity/trivy/trivy
$ trivy image --severity CRITICAL --ignore-unfixed api-dev-repo
ECRに初期イメージプッシュ
- コンソール > Amazon ECR > リポジトリ > api-dev-repoに遷移
- プッシュコマンドを表示
- ローカルマシンで下記画像のうち、それぞれ1 ~ 4のコマンドを実行
- ただし、2.の
docker build
のみ上記で実行済みなので対応不要 - プッシュ後にリポジトリにイメージが登録されていればOK
フロント用のECR構築
- 同様の手順でフロントアプリケーション用のECRも構築&pushする。
- 設定ファイルは/front/ecr.yml
Systems Managerへ環境変数登録
- マネージメントコンソール > Systems Manager > パラメータストア > パラメータを作成
- 設定方法は以下の画像を参考にしてみてください。
以下の画像を参考にAWSアカウントに紐づいているECRのドメイン部分をコピーしてください。
転記元 | SSMでのキー | 値 |
---|---|---|
ECRのドメイン部分 | ECR_REPOSITORY_DOMAIN | xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com |
ecs.yml(API ・フロント計2種構築)
Systems Managerへ環境変数登録
手順は既にECR作成時に設定した内容と全く同じです。
-
API
- ディレクトリに格納されている
.env
の内容のうち”必須”と”本番環境時に指定”と記載されている内容を参考に、それぞれパラメーターストアに一つずつ登録してください。
- ディレクトリに格納されている
# 必須
API_ENV=dev
API_FRONT_URL=https://front.example.com
API_DB_HOST=xxxxxxxx.rds.amazon.com
API_DB_NAME=golang
API_DB_USER=user
API_DB_PASS=password
API_DB_PORT=3306
API_SENTRY_DSN=https://xxxxxxxx.ingest.sentry.io/xxxxxxxx
# 本番環境時に指定
API_APP_DOMAIN=https://api.example.com
-
フロント
- .env.localを参考に作成
NEXT_PUBLIC_API_URL=https://api.example.com
スタックアップロード
- 上記と同様な手順で/api/ecs.ymlをアップロード
- スタックの詳細を指定 > 各項目に表示されている内容を下記の通り指定。
- スタック名 > ex.
api-ecs
- System Configuration > 全て表示されているデフォルトのまま
- Netowork Configuration
- VpcId
- タグ名 > system-dev-vpc
- ALBAllowInboundIP
- こちらから自分のネットワークのグローバルIPを取得
- xxx.xxx.xxx.xxx/32 ※cider範囲は32で固定とすること
- ALBSubnetId1
- タグ名 > system-dev-public-subnet-1
- ALBSubnetId2
- タグ名 > system-dev-public-subnet-2
- ECSSubnetId1
- タグ名 > system-dev-private-subnet-1
- ECSSubnetId2
- タグ名 > system-dev-private-subnet-2
- VpcId
- SSL/TLS Configuration
- コンソール > AWS Certificate Manager > 証明書 > 上記で作成した証明書を選択
- ARNをコピーする
- Cloudformationに戻りコピーしたARNを転記
- Fargate Configuration
- ECSImage
- コンソール > Amazon ECR > リポジトリ > api-dev-repo > イメージの URI > URI のコピー
- フロントとAPIのリポジトリを間違えないように
- Cloudformationに戻りコピーしたURLを転記
- ECSImage
- 上記以外の設定は全てデフォルト表示のままでOK
- スタック名 > ex.
- スタックオプションの設定 > 次へ >
- > AWS CloudFormation によって IAM リソースがカスタム名で作成される場合があることを承認します。 > チェック> 送信
タスク定義書(taskdef.json)の更新
本記事では省略するため、ECS構築時に吐き出されるタスク定義書をそのまま活用します。
ただし、"image":
タグの箇所はプレースホルダーに書き換えることを注意してください。
- マネージメントコンソール > Amazon Elastic Container Service > api-dev-cluster > タスク
- リビジョンを一つ選択 > json > クリップボードにコピー
-
go-graphql-jwt-server
配下のtaskdef.json
にそのまま貼り付ける -
"image":
タグの箇所を以下を参考に"image": "<IMAGE1_NAME>",
へ修正する
{
"taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:xxxxxxx:task-definition/api-dev-task-definition:1",
"containerDefinitions": [
{
"name": "api-container",
- "image": "xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/api-dev-repo:latest",
+ "image": "<IMAGE1_NAME>",
"cpu": 0,
"memoryReservation": 128,
"links": [],
"portMappings": [
フロント用のECS構築
- 同様の手順でフロントアプリケーション用のECSも構築
- 設定ファイルは/front/ecs.yml
- taskdef.jsonも同様に更新
cicd.yml(API ・フロント計2種構築)
System ManagerのパラメーターストアにCodeBuild用の環境変数の登録
IPガチャと呼ばれるToo Many Requests.対策のためDocer Hubのパーソナルアクセストークンを利用しています。
DockerHubにログイン後に以下の画像を参考にユーザーIDとアクセストークを取得してください。
転記元 | SSMでのキー |
---|---|
ユーザーID | DOCKER_HUB_PERSONAL_ACCOUNT_USER_ID |
作成したトークン | DOCKER_HUB_PERSONAL_ACCOUNT_USER_TOKEN |
ここまでで最終的に、System Managerへ以下のような形で環境変数が格納されていれば問題ありません。
スタックアップロード
- 上記と同様な手順で/common/cicd.ymlをアップロード
- Netowork Configuration
- VpcId
- system-dev-vpc
- CodeBuildSubnetId1
- system-dev-private-subnet-1
- CodeBuildSubnetId2
- system-dev-private-subnet-2
- VpcId
- System Configuration
- Environment
- デフォルト表示のままでOK
- Environment
- ECR/ECS Configuration
- ECRName
- デフォルト表示のままでOK
- ECRName
- GitHub Configuration
- GitHubOrganizationName
- GitHubRepositoryName
- GitHubBranchName
- フロントとAPIをリポジトリからそのままクローン、
main
ブランチの場合はデフォルト表示のまま
- フロントとAPIをリポジトリからそのままクローン、
Code Star Connectionを使ったGitHub連携
CodeStar Connectionsを用いてCodePipeLine内においてsourceステージはGithubで利用します。
- AWSコンソール > CodePipeLine > 設定 > 接続 > 保留中の接続を更新 > github側で承認(画像を参照)
- フロントも同様に設定
Code Deploy Group登録
※CodeDeployのみ用いたBlue/Greenデプロイはデプロイグループの登録については、Cloudformationが対応していません。CloudFormation を使用して、CodeDeploy を介して ECS ブルー/グリーンデプロイを実行する方法(hooks利用)もあるのですが、ここでは手動で対応します。
デプロイグループ名を取得する
- AWSコンソール > cloudformation > cicdスタックを選択 > 出力 > キー”CodeDeployGroupName” > 値をコピー
- 上記の手順でAPIとフロントアプリケーション両方をコピーする
デプロイグループを登録
- AWSコンソール > CodeDeploy > アプリケーション > アプリケーション名を選択 > デプロイグループの作成 > デプロイグループ名 > 上記でコピーした内容を転記
- api-dev-deployment-group
- サービスロール
- api-dev-cicd-deploy-role
- 環境設定
- api-dev-cluster
- api-dev-service
- Load balancer
- Load balancer選択
- api-dev-alb
- 本稼働リスナーポート
- HTTPS:443
- テストリスナーポート - オプショナル
- HTTP:8080
- ターゲットグループ 1 の名前
- api-dev-tg-blue
- ターゲットグループ 2の名前
- api-dev-tg-green
- Load balancer選択
- デプロイ設定
フロント用のCodePipeLine構築
- 同様の手順でフロントアプリケーション用のCICDも構築。
- 設定ファイルは/front/cicd.yml
- Code Star Connection/Code Deploy Groupも忘れず対応すること。
rds.yml(共通)
- 上記と同様な手順で/common/rds.ymlをアップロード
- System Configuration
- Environment
- デフォルト表示のままでOK
- Environment
- EC2 Configuration
- EC2PublicSubnet
- system-dev-public-subnet-1
- EC2ImageId
- デフォルト表示のままでOK
- KeyName
- 上記で作成したキーペアを利用
- EC2PublicSubnet
- RDS Configuration
- 以下二つは後でSystem Managerに登録する必要があるので必ず控えておくこと。
- DBMasterUsername
- user
- DBMasterPassword
- 任意のパスワードを入力。デフォルトでは”password”。
- DBPrivateSubnet1
- system-dev-private-subnet-3
- DBPrivateSubnet2
- system-dev-private-subnet-4
- Common Security group Configuration
- VpcId
- system-dev-vpc
- SourceIpAddress
- 以下のURLから自分のネットワークのグローバルIPを取得
- xxx.xxx.xxx.xxx/32 ※cider範囲は32で固定とすること
- https://www.cman.jp/network/support/go_access.cgi
- TaskSecurityGroup
- api-dev-task-sg
- CodeBuildSecurityGroup
- api-dev-codebuild-sg
- VpcId
Systems Managerへ環境変数登録
上記で作成したレコード(ドメイン)をSystems Managerに登録します。
SSMでのキー | 値 |
---|---|
API_DB_HOST | コンソール画面 > RDS > エンドポイント |
API_DB_USER | 上記で作成したユーザー名 |
API_DB_PASS | 上記で作成したパスワード |
踏み台EC2経由でアクセスしてRDSにDBを作成する
-
コンソール > EC2 > rds-dev-ec2-bastion > パブリック IPv4 アドレス > コピー
-
ローカルマシンで以下を実行
// 踏み台EC2にログイン
$ ssh -i ~/.ssh/db-access-key.pem ec2-user@${上記で取得した"EC2"のパブリックIPを入力}
// RDSにログイン
$ mysql -h ${"API_DB_HOST"を入力} -u user -p
// パスワード
password
// DB作成
CREATE DATABASE golang;
// 内容確認
show databases;
Route53でレコード登録(API ・フロント計2種構築)
- Route53 > ホストゾーン > exapmle.com > レコードを作成
- 以下の画像を参考にAPIとフロント用にサブドメインを作成
- front.example.com
- api.example.com
- 上記で作成したALBとレコードを紐づける
Systems Managerへ環境変数登録
上記で作成したレコード(ドメイン)をSystems Managerに登録します。
SSMでのキー | 値 |
---|---|
API_APP_DOMAIN | example.com |
API_FRONT_URL | https://front.example.com |
NEXT_PUBLIC_API_URL | https://api.example.com |
Blue/Greenデプロイ動作確認
上記で作成したインフラリソースをもとにGoで作成されたAPIでBlue/Greenデプロイが行われていることを確認していきます。
以下、手順で動作確認を行なっていきます。
(1) go-graphql-jwt-api
のヘルスチェック用のルート/healtcheck
のレスポンスを修正
// pkg/adapter/http/route/route.go
e.GET("/healthcheck", func(c echo.Context) error {
- return c.String(http.StatusOK, "OK")
})
e.GET("/healthcheck", func(c echo.Context) error {
+ return c.String(http.StatusOK, "New deployment test")
})
(2) main
ブランチにプッシュ
$ git commit -m "deploy: try B/G deploy"
$ git push origin main
(3) CodePipelineの管理画面で実行結果を確認
(4) デプロイ許可を行う
(5) ブラウザからテストポート(8080)でアクセスして変更が反映されていることを確認
ブラウザでhttp://api.webengrchild.com:8080/healthcheck
にアクセス
(7) 管理画面からトラフィックを本番ポート(443)に切り替える
(6) ブラウザから本番ポート(443)でアクセスして変更が反映されていることを確認
(7)トラブルが発生した場合を想定して、ロールバックすることで即座に古いリビジョンに戻す
- ブラウザで
https://api.webengrchild.com/healthcheck
に再度アクセスして元のリビジョンに切り替わっていることを確認する
アプリケーションの最終確認
-
go-graphql-server
とnext-front-app
がそれぞれデプロイされていることを確認
おわりに
ここまで長々とお付き合い頂きありがとうございました。
私自身も、久しぶりにフルスタックなアプリケーションを作成したため非常に学びの多い機会でした。
拙い部分多々あったかと思いますが、少しでもお役に立てれば幸いです。
■ご案内■
これからも頑張ってハンズオン系の記事を書いていきたいと思っているので、いいねっと思って頂けたらLGTM押していただけると励みになります!