はじめに
モダンなWeb開発においては、フロントエンドとバックエンドを含むフルスタックアプリケーションの開発が一般的になっています。このようなアプリケーションを本番環境で安定して運用するためには、適切なホスティング戦略が必要です。この記事では、Docker コンテナと AWS のサービスを組み合わせて、スケーラブルで高可用性のあるフルスタックアプリケーションをホスティングする方法について解説します。
フルスタックアプリケーションをDockerとAWSでホスティングする主なメリットは以下の通りです:
- 環境の一貫性: 開発環境と本番環境の差異を最小化
- スケーラビリティ: トラフィック増加に応じて自動的にスケール
- 分離性: マイクロサービスアーキテクチャの実現が容易
- 復元力: 障害発生時の復旧が迅速
- セキュリティ: AWSの堅牢なセキュリティサービスの活用
前提条件
- AWS アカウント
- Docker と Docker Compose がインストールされた環境
- フルスタックアプリケーション(例: React + Node.js/Express + MongoDB)
- AWS CLI(設定済み)
- 基本的な DevOps の知識
アーキテクチャ概要
今回構築するシステムのアーキテクチャは次のようになります:
- フロントエンド: React アプリケーション(S3 + CloudFront)
- バックエンド: Node.js API サーバー(ECS Fargate)
- データベース: MongoDB(Amazon DocumentDB または MongoDB Atlas)
- ネットワーク: VPC、サブネット、セキュリティグループ
- ロードバランサー: Application Load Balancer
- CI/CD: AWS CodePipeline または GitHub Actions
実装手順
1. アプリケーションのコンテナ化
フルスタックアプリケーションをコンテナ化するために、フロントエンドとバックエンドそれぞれのDockerfileを作成します。
フロントエンド(React)のDockerfile
# ビルドステージ
FROM node:16-alpine as build
WORKDIR /app
# 依存関係のインストール
COPY package.json package-lock.json ./
RUN npm ci
# ソースコードのコピーとビルド
COPY . ./
RUN npm run build
# 本番ステージ
FROM nginx:alpine
# ビルドされたファイルをnginxのコンテンツディレクトリにコピー
COPY --from=build /app/build /usr/share/nginx/html
# SPAのためのnginx設定
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
バックエンド(Node.js)のDockerfile
FROM node:16-alpine
WORKDIR /app
# 依存関係のインストール
COPY package.json package-lock.json ./
RUN npm ci
# ソースコードのコピー
COPY . ./
# 環境変数の設定
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server.js"]
Docker Compose でのローカル開発
docker-compose.yml
ファイルを作成して、ローカル開発環境を設定します:
version: '3.8'
services:
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend
backend:
build: ./backend
ports:
- "3000:3000"
environment:
- MONGODB_URI=mongodb://mongo:27017/myapp
depends_on:
- mongo
mongo:
image: mongo:latest
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
volumes:
mongo-data:
2. AWS インフラストラクチャのセットアップ
VPC とネットワークの設定
AWS CloudFormation または AWS CDK を使用して、基本的なネットワークインフラを構築します。以下のリソースを作成します:
- VPC
- パブリックサブネットとプライベートサブネット(2つ以上のアベイラビリティゾーンにまたがる)
- インターネットゲートウェイ
- NAT ゲートウェイ
- ルートテーブル
- セキュリティグループ
AWS CDKを使用した例(TypeScript):
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { Construct } from 'constructs';
export class NetworkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPCの作成
const vpc = new ec2.Vpc(this, 'AppVPC', {
maxAzs: 2,
natGateways: 1,
subnetConfiguration: [
{
name: 'public',
subnetType: ec2.SubnetType.PUBLIC,
cidrMask: 24,
},
{
name: 'private',
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
cidrMask: 24,
},
],
});
// バックエンド用セキュリティグループ
const backendSG = new ec2.SecurityGroup(this, 'BackendSG', {
vpc,
description: 'Security group for backend service',
allowAllOutbound: true,
});
backendSG.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(3000),
'Allow HTTP access to the backend'
);
// データベース用セキュリティグループ
const dbSG = new ec2.SecurityGroup(this, 'DatabaseSG', {
vpc,
description: 'Security group for database',
allowAllOutbound: true,
});
dbSG.addIngressRule(
backendSG,
ec2.Port.tcp(27017),
'Allow backend to access the database'
);
}
}
ECR リポジトリの作成
バックエンドイメージを保存するための ECR リポジトリを作成します:
aws ecr create-repository --repository-name fullstack-app-backend
イメージのビルドとプッシュ:
# リポジトリURIを取得
REPOSITORY_URI=$(aws ecr describe-repositories --repository-names fullstack-app-backend --query 'repositories[0].repositoryUri' --output text)
# Dockerイメージのビルド
cd backend
docker build -t fullstack-app-backend .
# ECRにログイン
aws ecr get-login-password | docker login --username AWS --password-stdin $REPOSITORY_URI
# イメージにタグを付けてプッシュ
docker tag fullstack-app-backend:latest $REPOSITORY_URI:latest
docker push $REPOSITORY_URI:latest
3. フロントエンドのデプロイ(S3 + CloudFront)
S3 バケットの作成
# S3バケットの作成
aws s3 mb s3://my-fullstack-app-frontend
# 静的ウェブサイトホスティングの設定
aws s3 website s3://my-fullstack-app-frontend --index-document index.html --error-document index.html
フロントエンドのビルドとデプロイ
# フロントエンドのビルド
cd frontend
npm run build
# S3へのアップロード
aws s3 sync build/ s3://my-fullstack-app-frontend --delete
CloudFront ディストリビューションの設定
CloudFront コンソールで、以下の設定でディストリビューションを作成します:
- オリジン: S3バケット
- ビューワープロトコルポリシー: Redirect HTTP to HTTPS
- キャッシュポリシー: CachingOptimized
- オリジンリクエストポリシー: CORS-S3Origin
- デフォルトルートオブジェクト: index.html
- エラーページのカスタマイズ: 404エラーを/index.htmlにリダイレクト(SPA対応)
4. バックエンドのデプロイ(ECS Fargate)
ECS クラスターの作成
aws ecs create-cluster --cluster-name fullstack-app-cluster
タスク定義の作成
task-definition.json
ファイルを作成します:
{
"family": "fullstack-app-backend",
"networkMode": "awsvpc",
"executionRoleArn": "arn:aws:iam::ACCOUNT_ID:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::ACCOUNT_ID:role/ecsTaskRole",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"containerDefinitions": [
{
"name": "backend",
"image": "REPOSITORY_URI:latest",
"essential": true,
"portMappings": [
{
"containerPort": 3000,
"hostPort": 3000,
"protocol": "tcp"
}
],
"environment": [
{
"name": "NODE_ENV",
"value": "production"
},
{
"name": "MONGODB_URI",
"value": "YOUR_MONGODB_CONNECTION_STRING"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/fullstack-app-backend",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
タスク定義の登録:
aws ecs register-task-definition --cli-input-json file://task-definition.json
ロードバランサーの作成
# ロードバランサーの作成
aws elbv2 create-load-balancer \
--name fullstack-app-lb \
--subnets subnet-12345678 subnet-87654321 \
--security-groups sg-12345678 \
--scheme internet-facing \
--type application
# ターゲットグループの作成
aws elbv2 create-target-group \
--name fullstack-app-tg \
--protocol HTTP \
--port 3000 \
--vpc-id vpc-12345678 \
--target-type ip \
--health-check-path /health
# リスナーの作成
aws elbv2 create-listener \
--load-balancer-arn LOAD_BALANCER_ARN \
--protocol HTTP \
--port 80 \
--default-actions Type=forward,TargetGroupArn=TARGET_GROUP_ARN
ECS サービスの作成
aws ecs create-service \
--cluster fullstack-app-cluster \
--service-name fullstack-app-backend \
--task-definition fullstack-app-backend \
--desired-count 2 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[subnet-12345678,subnet-87654321],securityGroups=[sg-12345678],assignPublicIp=DISABLED}" \
--load-balancers "targetGroupArn=TARGET_GROUP_ARN,containerName=backend,containerPort=3000"
5. データベースのセットアップ
フルスタックアプリケーションでは、データの永続化が必要です。ここでは Amazon DocumentDB(MongoDB互換)を使用する例を紹介します。
DocumentDB クラスターの作成
aws docdb create-db-cluster \
--db-cluster-identifier fullstack-app-db \
--engine docdb \
--master-username admin \
--master-user-password YOUR_PASSWORD \
--vpc-security-group-ids sg-12345678 \
--db-subnet-group-name my-db-subnet-group
バックエンドサービスとの接続
DocumentDB クラスターへの接続文字列を環境変数として ECS タスク定義に追加します。また、必要に応じて AWS Secrets Manager を使用して認証情報を安全に管理します。
6. CI/CD パイプラインの構築
継続的インテグレーション/継続的デプロイメントのパイプラインを構築することで、コードの変更が自動的に本番環境に反映されるようにします。ここでは GitHub Actions を使用した例を紹介します。
.github/workflows/deploy.yml
ファイルを作成します:
name: Deploy Fullstack App
on:
push:
branches: [ main ]
jobs:
deploy-frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install and Build Frontend
run: |
cd frontend
npm ci
npm run build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
- name: Deploy to S3
run: |
aws s3 sync frontend/build/ s3://my-fullstack-app-frontend --delete
- name: Invalidate CloudFront cache
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*"
deploy-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: fullstack-app-backend
IMAGE_TAG: ${{ github.sha }}
run: |
cd backend
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
- name: Update ECS service
run: |
aws ecs update-service \
--cluster fullstack-app-cluster \
--service fullstack-app-backend \
--force-new-deployment
7. セキュリティ対策
AWS環境でのセキュリティ対策は非常に重要です。以下の対策を実施しましょう:
IAM ロールと権限の最小化
- ECSタスク実行ロール
- ECSタスクロール
- CI/CDパイプライン用のIAMユーザー
HTTPS の有効化
- CloudFrontでのHTTPSの強制
- ALBでのHTTPSリスナーの追加(ACM証明書を使用)
セキュリティグループの適切な設定
- バックエンドへのアクセスはALBからのみ許可
- データベースへのアクセスはバックエンドからのみ許可
シークレット管理
機密情報は AWS Secrets Manager を使用して管理します:
aws secretsmanager create-secret \
--name fullstack-app/db-credentials \
--description "Database credentials for Fullstack App" \
--secret-string '{"username":"admin","password":"YOUR_PASSWORD"}'
8. モニタリングと運用
CloudWatch Dashboards
CloudWatch ダッシュボードを作成して、アプリケーションのパフォーマンスをモニタリングします:
- CPU使用率
- メモリ使用率
- リクエスト数
- エラーレート
- レスポンスタイム
アラームの設定
重要なメトリクスに対してアラームを設定し、問題が発生した場合に通知を受け取ります:
aws cloudwatch put-metric-alarm \
--alarm-name HighCPUUsage \
--alarm-description "Alarm when CPU exceeds 80%" \
--metric-name CPUUtilization \
--namespace AWS/ECS \
--statistic Average \
--period 60 \
--threshold 80 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=ClusterName,Value=fullstack-app-cluster Name=ServiceName,Value=fullstack-app-backend \
--evaluation-periods 1 \
--alarm-actions arn:aws:sns:ap-northeast-1:ACCOUNT_ID:alerts
Auto Scaling の設定
トラフィックの増減に応じて自動的にスケールするように ECS サービスを設定します:
aws application-autoscaling register-scalable-target \
--service-namespace ecs \
--resource-id service/fullstack-app-cluster/fullstack-app-backend \
--scalable-dimension ecs:service:DesiredCount \
--min-capacity 2 \
--max-capacity 10
aws application-autoscaling put-scaling-policy \
--service-namespace ecs \
--resource-id service/fullstack-app-cluster/fullstack-app-backend \
--scalable-dimension ecs:service:DesiredCount \
--policy-name cpu-tracking-scaling-policy \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"TargetValue": 70.0,
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ECSServiceAverageCPUUtilization"
},
"ScaleOutCooldown": 60,
"ScaleInCooldown": 60
}'
コスト最適化のヒント
AWS でのホスティングコストを最適化するためのヒントをいくつか紹介します:
- 適切なインスタンスサイズの選択: ECS Fargate のタスクサイズを適切に設定する
- リザーブドインスタンス: 長期運用が予定されている場合、リザーブドインスタンスを検討する
- Auto Scaling: トラフィックに応じてリソースを動的に調整する
- S3 のライフサイクルポリシー: 古いバックアップやログを自動的に削除または低コストのストレージクラスに移動する
- CloudFront の Price Class: 必要なリージョンのみをカバーする Price Class を選択する
本番運用のベストプラクティス
ブルー/グリーンデプロイメント
新しいバージョンのアプリケーションをデプロイする際には、ブルー/グリーンデプロイメント戦略を使用すると、ダウンタイムを最小限に抑えることができます:
- 新しいバージョンの ECS タスク定義を作成する
- 新しいタスク定義を使用して新しい ECS サービスをデプロイする
- 新しいバージョンのテストを実施する
- ロードバランサーのリスナールールを更新して、トラフィックを新しいサービスに転送する
- 古いバージョンのサービスを削除する
バックアップ戦略
データベースのバックアップを定期的に取得し、障害発生時に復旧できるようにします:
# DocumentDBのスナップショットを作成
aws docdb create-db-cluster-snapshot \
--db-cluster-identifier fullstack-app-db \
--db-cluster-snapshot-identifier fullstack-app-db-snapshot-$(date +%Y%m%d)
ディザスタリカバリ計画
障害発生時の復旧計画を策定し、定期的にテストします:
- マルチAZ構成での運用
- リージョン間レプリケーション
- 自動バックアップと復元テスト
- インシデント対応計画の策定
まとめ
この記事では、Docker と AWS を使用してフルスタックアプリケーションをホスティングする方法について解説しました。以下のようなポイントをカバーしました:
- Docker を使用したアプリケーションのコンテナ化
- AWS インフラストラクチャのセットアップ(VPC、ECS、S3、CloudFront、DocumentDB など)
- CI/CD パイプラインの構築
- セキュリティ対策
- モニタリングと運用の自動化
- コスト最適化と本番運用のベストプラクティス
これらのステップを実施することで、スケーラブルで信頼性の高いフルスタックアプリケーションの実行環境を構築できます。また、継続的インテグレーション/継続的デプロイメントの仕組みを導入することで、開発チームの生産性向上にも寄与します。
Docker と AWS の組み合わせは、モダンなアプリケーションのホスティングに非常に強力なソリューションを提供します。これらのテクノロジーを活用して、優れたユーザーエクスペリエンスと運用効率を両立させたアプリケーションを構築しましょう。
コメント