フルスタックアプリをDocker + AWSでホスティングする方法

はじめに

モダンなWeb開発においては、フロントエンドとバックエンドを含むフルスタックアプリケーションの開発が一般的になっています。このようなアプリケーションを本番環境で安定して運用するためには、適切なホスティング戦略が必要です。この記事では、Docker コンテナと AWS のサービスを組み合わせて、スケーラブルで高可用性のあるフルスタックアプリケーションをホスティングする方法について解説します。

フルスタックアプリケーションをDockerとAWSでホスティングする主なメリットは以下の通りです:

  • 環境の一貫性: 開発環境と本番環境の差異を最小化
  • スケーラビリティ: トラフィック増加に応じて自動的にスケール
  • 分離性: マイクロサービスアーキテクチャの実現が容易
  • 復元力: 障害発生時の復旧が迅速
  • セキュリティ: AWSの堅牢なセキュリティサービスの活用

前提条件

  • AWS アカウント
  • Docker と Docker Compose がインストールされた環境
  • フルスタックアプリケーション(例: React + Node.js/Express + MongoDB)
  • AWS CLI(設定済み)
  • 基本的な DevOps の知識

アーキテクチャ概要

今回構築するシステムのアーキテクチャは次のようになります:

  1. フロントエンド: React アプリケーション(S3 + CloudFront)
  2. バックエンド: Node.js API サーバー(ECS Fargate)
  3. データベース: MongoDB(Amazon DocumentDB または MongoDB Atlas)
  4. ネットワーク: VPC、サブネット、セキュリティグループ
  5. ロードバランサー: Application Load Balancer
  6. 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 でのホスティングコストを最適化するためのヒントをいくつか紹介します:

  1. 適切なインスタンスサイズの選択: ECS Fargate のタスクサイズを適切に設定する
  2. リザーブドインスタンス: 長期運用が予定されている場合、リザーブドインスタンスを検討する
  3. Auto Scaling: トラフィックに応じてリソースを動的に調整する
  4. S3 のライフサイクルポリシー: 古いバックアップやログを自動的に削除または低コストのストレージクラスに移動する
  5. CloudFront の Price Class: 必要なリージョンのみをカバーする Price Class を選択する

本番運用のベストプラクティス

ブルー/グリーンデプロイメント

新しいバージョンのアプリケーションをデプロイする際には、ブルー/グリーンデプロイメント戦略を使用すると、ダウンタイムを最小限に抑えることができます:

  1. 新しいバージョンの ECS タスク定義を作成する
  2. 新しいタスク定義を使用して新しい ECS サービスをデプロイする
  3. 新しいバージョンのテストを実施する
  4. ロードバランサーのリスナールールを更新して、トラフィックを新しいサービスに転送する
  5. 古いバージョンのサービスを削除する

バックアップ戦略

データベースのバックアップを定期的に取得し、障害発生時に復旧できるようにします:

# 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)

ディザスタリカバリ計画

障害発生時の復旧計画を策定し、定期的にテストします:

  1. マルチAZ構成での運用
  2. リージョン間レプリケーション
  3. 自動バックアップと復元テスト
  4. インシデント対応計画の策定

まとめ

この記事では、Docker と AWS を使用してフルスタックアプリケーションをホスティングする方法について解説しました。以下のようなポイントをカバーしました:

  • Docker を使用したアプリケーションのコンテナ化
  • AWS インフラストラクチャのセットアップ(VPC、ECS、S3、CloudFront、DocumentDB など)
  • CI/CD パイプラインの構築
  • セキュリティ対策
  • モニタリングと運用の自動化
  • コスト最適化と本番運用のベストプラクティス

これらのステップを実施することで、スケーラブルで信頼性の高いフルスタックアプリケーションの実行環境を構築できます。また、継続的インテグレーション/継続的デプロイメントの仕組みを導入することで、開発チームの生産性向上にも寄与します。

Docker と AWS の組み合わせは、モダンなアプリケーションのホスティングに非常に強力なソリューションを提供します。これらのテクノロジーを活用して、優れたユーザーエクスペリエンスと運用効率を両立させたアプリケーションを構築しましょう。

コメント

タイトルとURLをコピーしました