Contents
1. NestJS マイクロサービスの基本概念と代表的構成例
NestJS は @nestjs/microservices パッケージにより、TCP・gRPC・NATS など複数のトランスポート層を統一的に扱えるフレームワークです。マイクロサービス化すると、機能単位で独立したプロセスとしてデプロイできるため、障害が特定サービスに限定されやすくなります。
1‑1. マイクロサービスとは
マイクロサービス は「単一機能を独立したサービスとして提供」する設計思想です。
各サービスは自前のデータベースと API を持ち、軽量プロトコルで相互に連携します。
- 独立性:障害が局所化しやすく、他サービスへの波及を防げます。
- スケーラビリティ:負荷が高いサービスだけを個別に水平拡張できます。
- 技術選択の自由:言語・フレームワークをサービスごとに最適化できます。
1‑2. NestJS が提供する主要トランスポート
| トランスポート | 特徴 | 主な利用シーン |
|---|---|---|
| TCP | シンプルでバイナリ通信。設定が最小限。 | 小規模・低遅延が求められる内部連携 |
| gRPC | Protocol Buffers による高速かつ型安全な RPC。 | 大規模サービス間、厳密なスキーマ管理が必要な場合 |
| NATS | Pub/Sub とキューイングを同時に提供する軽量メッセージブローカー。 | イベント駆動アーキテクチャ、非同期処理全般 |
NestJS は Transport 列挙体でこれらを切り替えられるため、コードベースはほぼ変わりません。
1‑3. 代表的なアーキテクチャパターン
-
API Gateway パターン
外部からの入口を統一し、認証・ルーティング・集約処理を集中管理します。NestJS の@nestjs/microservicesと組み合わせると、Gateway が各マイクロサービスへリクエストを転送できます。 -
Event‑Driven パターン
NATS や Kafka を利用し、非同期メッセージで疎結合な連携を実現します。イベントソーシングや CQRS と相性が高いです。 -
Sidecar パターン
各サービスに監視・ログ収集用のサイドカーコンテナ(例: Prometheus exporter)を付与し、機能拡張を外部化します。
2. 推奨プロジェクトレイアウトとコード整理方法
大規模マイクロサービス群ではディレクトリ構造が保守性の鍵となります。この章では、機能別モジュール と 共通ライブラリ を明確に分離したレイアウト例を示します。
2‑1. 全体ディレクトリ構成
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
project-root/ ├─ src/ │ ├─ modules/ # ビジネスドメインごとの Nest モジュール │ │ ├─ users/ │ │ │ ├─ controllers/ │ │ │ ├─ services/ │ │ │ └─ users.module.ts │ │ └─ orders/ … │ ├─ common/ # DTO、Pipe、Guard 等の全体共通コード │ │ ├─ dto/ │ │ ├─ pipes/ │ │ └─ guards/ │ └─ libs/ # 外部依存(ロガー・認証ヘルパー等)をまとめたライブラリ │ ├─ logger/ │ └─ utils/ ├─ Dockerfile └─ docker-compose.yml |
- modules 配下に機能単位のモジュールを配置することで、
@Moduleデコレーターだけで依存関係が把握できます。 - common は全サービスで再利用できるユーティリティやバリデーションロジックを集約し、重複実装を防ぎます。
- libs には外部パッケージのラップや社内共通ライブラリを置き、バージョン管理とテストが容易になります。
2‑2. モジュール別ディレクトリ詳細
usersモジュールは典型的な CRUD API とマイクロサービスハンドラの両方を持つ例です。
- controllers/ – HTTP エンドポイントや microservice メッセージハンドラを配置
- services/ – ビジネスロジックを実装し、
@Injectable()で提供 - users.module.ts –
imports,providers,exportsを定義し、他モジュールからの利用を許可
2‑3. 共有ライブラリ・ユーティティの配置指針
| ディレクトリ | 主な内容 | 利点 |
|---|---|---|
| common/dto | 入出力データ構造(クラスバリデーション付き) | 型安全かつ自動ドキュメント生成が容易 |
| common/pipes | バリデーション・変換ロジック | コントローラの記述量を削減 |
| libs/logger | Winston や Pino をラップしたロガーモジュール | 各サービスで同一ログフォーマットを使用 |
3. Docker 化:マルチステージ Dockerfile と docker‑compose 設定
Docker による環境統一は、開発・テスト・本番の差異をなくす最も実用的な手段です。ここではビルド効率とセキュリティに配慮した構成を示します。
3‑1. マルチステージ Dockerfile(非特権ユーザー設定付き)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# ---------- Build Stage ---------- FROM node:20-alpine AS builder WORKDIR /app # 依存関係だけ先にコピーしてキャッシュを活用 COPY package*.json ./ RUN npm ci --omit=dev # 本番実行に不要な devDependencies を除外 # ソースコード全体をコピーし、NestJS アプリをビルド COPY . . RUN npm run build # dist/ ディレクトリが生成される # ---------- Runtime Stage ---------- FROM node:20-alpine AS runtime WORKDIR /app # 必要な成果物だけをコピー COPY --from=builder /app/dist ./dist COPY --from=builder /app/package*.json ./ COPY --from=builder /app/node_modules ./node_modules # 非特権ユーザーの作成と切替(UID 1000 が node ユーザー) RUN addgroup -S appgroup && adduser -S appuser -G appgroup USER appuser ENV NODE_ENV=production EXPOSE 3000 CMD ["node", "dist/main"] |
- キャッシュ最適化:
package*.jsonのみを先にコピーし、依存変更が無い限り再ビルドを防止。 - 非特権ユーザー:
appuser(UID 1000)で実行することで、コンテナ内の root 権限によるリスクを回避します。
3‑2. docker‑compose.yml のサービス定義
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
version: "3.9" services: api-gateway: build: context: . target: runtime # 本番イメージのみ使用 ports: - "80:3000" env_file: - .env.production depends_on: orders: condition: service_healthy # orders がヘルスチェックに成功するまで待機 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 15s timeout: 5s retries: 3 orders: build: context: . target: runtime env_file: - .env.production healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 15s timeout: 5s retries: 3 networks: default: name: nest_micro_net |
- ネットワーク:
nest_micro_netと名前付けし、サービス間の DNS 解決をシンプルに。 - 環境変数管理:
.env.productionはサーバ側でのみ保持し、コードリポジトリには含めません。 - 起動順序とヘルスチェック:
depends_onとcondition: service_healthyにより、依存サービスが正常になるまで待機します。
4. CI/CD パイプラインと本番デプロイのベストプラクティス
自動化されたパイプラインは手作業ミスを排除し、リリースサイクルを短縮します。ここでは GitHub Actions を用いたフローと、セキュリティ面で特に注意すべきポイントを解説します。
4‑1. GitHub Actions ワークフロー全体像
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
name: CI/CD on: push: branches: [ main ] jobs: build-test-deploy: runs-on: ubuntu-latest steps: # ソース取得 - name: Checkout repository uses: actions/checkout@v4 # 静的解析・テスト実行 - name: Install dependencies run: npm ci - name: Lint and test run: | npm run lint npm run test -- --coverage # Docker ビルド(マルチステージ) - name: Build Docker image run: | docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} . # 脆弱性スキャン (Trivy) - name: Scan image with Trivy uses: aquasecurity/trivy-action@master with: image-ref: ghcr.io/${{ github.repository }}:${{ github.sha }} format: table exit-code: '1' # 重大度が High 以上なら失敗 # GHCR にプッシュ - name: Log in to GitHub Container Registry uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Push image run: | docker push ghcr.io/${{ github.repository }}:${{ github.sha }} # 本番サーバへデプロイ(SSH + Docker Compose) - name: Deploy to production uses: appleboy/ssh-action@v0.1.10 with: host: ${{ secrets.PROD_HOST }} username: ${{ secrets.PROD_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} script: | cd /opt/nest-microservices docker compose pull docker compose up -d |
- シークレット管理:
PROD_HOST,PROD_USER,SSH_PRIVATE_KEYは GitHub Secrets に格納し、平文で残さない。 - 非特権コンテナの継承:Dockerfile で設定した
USER appuserがビルド・実行時にそのまま適用されます。
4‑2. 本番環境向け追加セキュリティ対策
| 項目 | 推奨設定例 |
|---|---|
| 実行ユーザー | Dockerfile の USER appuser(UID 1000) |
| イメージスキャン | Trivy / Snyk を CI に組み込み、Critical/High はビルド失敗させる |
| シークレット保管 | .env.production はサーバ側にのみ配置し、リポジトリには絶対に含めない |
| ネットワーク分離 | bridge ネットワーク上で内部サービスは名前解決だけで通信。外部公開は必要なポート(例: 80, 443)のみ開放 |
| リソース制限 | docker-compose.yml の各サービスに cpu_shares, mem_limit を設定し、DoS 攻撃時の影響を抑える |
5. 運用・トラブルシューティングと次のアクション
本番運用では 可観測性 と 迅速な復旧手順 が成功の鍵です。以下に実装例と、よくある障害への対処法をまとめます。
5‑1. ヘルスチェック・ロギング・メトリクスの統合
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// src/common/health.controller.ts import { Controller, Get } from '@nestjs/common'; import { HealthCheckService, HttpHealthIndicator, } from '@nestjs/terminus'; @Controller('health') export class HealthController { constructor( private readonly health: HealthCheckService, private readonly http: HttpHealthIndicator, ) {} @Get() check() { return this.health.check([ () => this.http.pingCheck('api-gateway', 'http://localhost:3000/ready'), ]); } } |
|
1 2 3 4 |
# Dockerfile の末尾に追加 HEALTHCHECK --interval=15s --timeout=3s \ CMD curl -f http://localhost:3000/health || exit 1 |
- Logger:
app.useLogger(new Logger('NestApp'))により、ログレベルとタグを統一。 - Prometheus Exporter:
@willsoto/nestjs-prometheusをインストールし、/metricsエンドポイントでメトリクスを公開。Grafana ダッシュボードは公式テンプレートからカスタマイズ可能です。
5‑2. よくあるエラーと対処法(Q&A)
| 問題 | 主な原因 | 推奨解決策 |
|---|---|---|
| ポート競合 | ローカルで 3000 が既に使用中 |
docker-compose.yml の ports を "4000:3000" などに変更し、.env.production に変数化 |
| 依存サービスの起動順序エラー | api-gateway が DB/Orders 起動前にリクエスト送信 |
depends_on と healthcheck を併用し、サービスが healthy になるまで待機 |
| Docker ビルド失敗(キャッシュ破損) | 古いレイヤーが残り node_modules が不整合 |
CI ランナーで docker builder prune --all 実行後、再ビルド |
| メモリ不足による OOM キラー発動 | 本番イメージに不要な devDependencies が含まれる | マルチステージビルドで --omit=dev を使用し、本番イメージを軽量化 |
5‑3. 次のアクションプラン
- モニタリング基盤の構築
- Prometheus + Grafana のスタックを別サービスとしてデプロイ。
- 障害シナリオの演習
docker compose downやネットワーク切断を意図的に実行し、復旧手順をドキュメント化。- CI に自動ロールバック機構追加
- デプロイ失敗時は前バージョンのタグ (
latest-stable) に即座にロールバックするスクリプトを組み込む。
まとめ
- NestJS はマルチトランスポート対応が標準装備 のため、要件に合わせて TCP・gRPC・NATS を柔軟に選択できる。
- 機能別モジュールと共通ライブラリを分離したディレクトリ構成 が保守性を大幅に向上させる。
- マルチステージ Dockerfile と非特権ユーザー設定 により、イメージは軽量かつ安全になる。
- GitHub Actions + Trivy の CI/CD パイプライン は自動ビルド・テスト・脆弱性チェック・デプロイを一元化し、人的ミスを最小化する。
- ヘルスチェック・ロギング・Prometheus による可観測性 が整備されていれば、障害検知と復旧が迅速に行える。
このガイドを参考に、自社プロジェクトのマイクロサービス化を段階的に進めてください。質問や実装上の課題があれば、コメントで遠慮なくお問い合わせください。