LoginSignup
34
30

【AWS編】Next.js × Go × AWSでJWT認証付きGraphQLアプリとCI/CDを構築してみよう

Last updated at Posted at 2023-05-26

はじめに

■ご案内■
本連載の背景/作成できるアプリケーション/進め方をご理解頂く上でも【環境構築編】をご一読頂けると幸いです。

これからも頑張ってハンズオン系の記事を書いていきたいと思っているので、いいねっと思って頂けたらLGTM押していただけると励みになります!

ここからは実際にアプリケーションをAWSへデプロイしていきます。
本記事では、プロビジョニングツールとしてCloudformationを利用しています。

注意点
利用するAWSの各リソースは課金対象となるものが多く含まれています。個人利用される方は特に注意してください。発生した費用に関して一切責任を負いかねます。

また、本記事で用いる.ymlファイルは以下のリポジトリに格納しているので、適宜クローンをお願いします。

構成図

Qiita.drawio (3).png

リソース紹介

まず最初に、簡単ではありますが、本デプロイフロー(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ファイルをローカルマシンにダウンロード

スクリーンショット 2023-05-25 6.58.11.png

  • ローカルマシンでのペアキーの許可設定

ダウンロードしたキーを~/.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”になれば完了

cfn使い方.png
スクリーンショット 2023-05-11 7.04.20.png
スクリーンショット 2023-05-07 11.52.14.png

ecr.yml(API・フロント計2種構築)

  • 上記と同様な手順で/api/ecr.ymlをアップロード
  • スタックの詳細を指定 > 各項目に表示されているデフォルのままで変更せずOK。
  • スタックの名前は識別できる任意のもの(フロントとAPIが区別できるように)。
    • ex.api-ecr
  • System Configuration
    • Environment
      • デフォルト表示のままでOK

ローカル環境でDocker imageのビルドと脆弱性チェック

本記事で作成しているCICDでは、Dockerfileの静的解析とDockerイメージの脆弱性チェックとして、以下のツールを利用しています。

  • hadolint
  • Dockle
  • trivy

また、各リポジトリのREADME.mdに実行用のコマンドを記載しています。

下記はgo-graphql-jwt-apiのサンプル例になります。

コマンド実行は、go-graphql-jwt-apinext-graphql-frontのディレクトリ配下で実行してください。

Docker Build

  • 本番ターゲットを指定して実行
  • ECSで実行できるようにプラットフォーム指定
/go-graphql-jwt-api/README.md
# 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です。

/go-graphql-jwt-api/README.md
$ brew install hadolint
$ hadolint --ignore DL3018 build/docker/go/Dockerfile

本記事では、読者の方の正常動作を担保するため、ライブラリのバージョンを固定するルールを無効化しています。ただし、Dockerfileでパッケージをインストールする際には、通常、特定のバージョンを指定することが推奨されます。

Dockle

主にDockerイメージのベストプラクティスに焦点を当てた軽量のコンテナイメージスキャナです。

※本記事の設定では一旦、warninfoは無視しています。

/go-graphql-jwt-api/README.md
$ brew install goodwithtech/r/dockle
$ dockle api-dev-repo

trivy

Trivyは、脆弱性スキャナであり、Dockerイメージおよびファイルシステムに存在するセキュリティ上の脆弱性を検出することに特化しています。

実行後に、Total: 0 (CRITICAL: 0)が表示されていればOKです。

/go-graphql-jwt-api/README.md
$ 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

スクリーンショット 2023-05-07 11.56.48.png

api-dev-repo.png

プッシュコマンドの表示_2.png

api-dev-repo.png

フロント用のECR構築

  • 同様の手順でフロントアプリケーション用のECRも構築&pushする。
  • 設定ファイルは/front/ecr.yml

スクリーンショット 2023-05-07 12.47.51.png

Systems Managerへ環境変数登録

  • マネージメントコンソール > Systems Manager > パラメータストア > パラメータを作成
  • 設定方法は以下の画像を参考にしてみてください。

スクリーンショット 2023-05-07 13.46.09.png

以下の画像を参考にAWSアカウントに紐づいているECRのドメイン部分をコピーしてください。

転記元 SSMでのキー
ECRのドメイン部分 ECR_REPOSITORY_DOMAIN xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com

スクリーンショット 2023-05-07 16.13.03.png

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
    • SSL/TLS Configuration
      • コンソール > AWS Certificate Manager > 証明書 > 上記で作成した証明書を選択
      • ARNをコピーする
      • Cloudformationに戻りコピーしたARNを転記
    • Fargate Configuration
      • ECSImage
        • コンソール > Amazon ECR > リポジトリ > api-dev-repo > イメージの URI > URI のコピー
        • フロントとAPIのリポジトリを間違えないように
      • Cloudformationに戻りコピーしたURLを転記
    • 上記以外の設定は全てデフォルト表示のままでOK
  • スタックオプションの設定 > 次へ >
    •  > 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>",へ修正する

スクリーンショット 2023-05-13 12.37.08.png

スクリーンショット 2023-05-13 12.44.14.png

{
  "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とアクセストークを取得してください。

スクリーンショット 2023-05-07 15.59.57.png

転記元 SSMでのキー
ユーザーID DOCKER_HUB_PERSONAL_ACCOUNT_USER_ID
作成したトークン DOCKER_HUB_PERSONAL_ACCOUNT_USER_TOKEN

ここまでで最終的に、System Managerへ以下のような形で環境変数が格納されていれば問題ありません。

スクリーンショット 2023-05-07 16.32.59.png

スタックアップロード

  • 上記と同様な手順で/common/cicd.ymlをアップロード
  • Netowork Configuration
    • VpcId
      • system-dev-vpc
    • CodeBuildSubnetId1
      • system-dev-private-subnet-1
    • CodeBuildSubnetId2
      • system-dev-private-subnet-2
  • System Configuration
    • Environment
      • デフォルト表示のままでOK
  • ECR/ECS Configuration
    • ECRName
      • デフォルト表示のままでOK
  • GitHub Configuration

Code Star Connectionを使ったGitHub連携

CodeStar Connectionsを用いてCodePipeLine内においてsourceステージはGithubで利用します。

  • AWSコンソール > CodePipeLine > 設定 > 接続 > 保留中の接続を更新 > github側で承認(画像を参照)
  • フロントも同様に設定

スクリーンショット 2023-05-08 6.56.45.png

apl-dev-cicd-github.png

AWS Connector for GitHub.png

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
  • デプロイ設定
    デプロイ設定.png

フロント用のCodePipeLine構築

  • 同様の手順でフロントアプリケーション用のCICDも構築。
  • 設定ファイルは/front/cicd.yml
  • Code Star Connection/Code Deploy Groupも忘れず対応すること。

rds.yml(共通)

  • 上記と同様な手順で/common/rds.ymlをアップロード
  • System Configuration
    • Environment
      • デフォルト表示のままでOK
  • EC2 Configuration
    • EC2PublicSubnet
      • system-dev-public-subnet-1
    • EC2ImageId
      • デフォルト表示のままでOK
    • KeyName
      • 上記で作成したキーペアを利用
  • 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
    • TaskSecurityGroup
      • api-dev-task-sg
    • CodeBuildSecurityGroup
      • api-dev-codebuild-sg

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とレコードを紐づける

スクリーンショット 2023-05-10 5.42.53.png

修正_68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f313832343734302f39353837643030372d623263302d383964622d343536342d6132313361343931626264622e706e67.png

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の管理画面で実行結果を確認

スクリーンショット 2023-05-13 15.44.15.png

(4) デプロイ許可を行う

スクリーンショット 2023-05-13 15.51.33.png

(5) ブラウザからテストポート(8080)でアクセスして変更が反映されていることを確認

ブラウザでhttp://api.webengrchild.com:8080/healthcheckにアクセス
スクリーンショット 2023-05-13 15.51.05.png

(7) 管理画面からトラフィックを本番ポート(443)に切り替える

03CE2BDA-0FBC-4438-ABAB-FF295F854CE7.jpg

(6) ブラウザから本番ポート(443)でアクセスして変更が反映されていることを確認

スクリーンショット 2023-05-13 13.57.12.png

(7)トラブルが発生した場合を想定して、ロールバックすることで即座に古いリビジョンに戻す

スクリーンショット 2023-05-13 13.59.39.png

  • ブラウザでhttps://api.webengrchild.com/healthcheckに再度アクセスして元のリビジョンに切り替わっていることを確認する

スクリーンショット 2023-05-13 14.30.28.png

アプリケーションの最終確認

  • go-graphql-servernext-front-appがそれぞれデプロイされていることを確認

スクリーンショット 2023-05-13 14.13.02.png

スクリーンショット 2023-05-13 14.41.13.png

おわりに

ここまで長々とお付き合い頂きありがとうございました。

私自身も、久しぶりにフルスタックなアプリケーションを作成したため非常に学びの多い機会でした。

拙い部分多々あったかと思いますが、少しでもお役に立てれば幸いです。

■ご案内■

これからも頑張ってハンズオン系の記事を書いていきたいと思っているので、いいねっと思って頂けたらLGTM押していただけると励みになります!

34
30
3

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
34
30