Docker でマイクロサービスを構築するアーキテクチャ入門

近年、多くの企業がマイクロサービスアーキテクチャを採用し、システムの柔軟性と拡張性を追求しています。この変革の背景には、Dockerのようなコンテナ技術の成熟があります。
従来のモノリシックアプリケーションでは、すべての機能が単一のアプリケーション内に実装されていました。これに対してマイクロサービスは、機能ごとに独立したサービスとして分割し、それぞれが独立してデプロイ・スケーリング可能な設計です。
この記事では、Dockerを活用したマイクロサービスアーキテクチャの基本概念から実装まで、初心者の方にも分かりやすく解説いたします。具体的なコード例とサンプルアプリケーションを通じて、実際に手を動かしながら学習できる内容となっています。
背景
マイクロサービスアーキテクチャとは
マイクロサービスアーキテクチャは、アプリケーションを小さな独立したサービスの集合として構築するアプローチです。各サービスは特定のビジネス機能に特化し、独自のデータベースを持ちます。
モノリシックからマイクロサービスへの移行トレンド
従来のモノリシックアプリケーションでは、以下のような課題が顕在化していました。
# | モノリシックの課題 | 詳細説明 |
---|---|---|
1 | 部分的なスケーリングの困難さ | 全体をスケールする必要があり、リソースが無駄になる |
2 | 技術スタックの制約 | 一度選択した技術から変更が困難 |
3 | 開発チーム間の依存関係 | 一つの変更が全体に影響を与える |
4 | デプロイリスクの高さ | 小さな変更でも全体のデプロイが必要 |
これらの課題を解決するために、Netflix、Amazon、Uberなどの大手企業が率先してマイクロサービスアーキテクチャを採用し、その有効性が実証されています。
分散システムとしての特徴
マイクロサービスは本質的に分散システムです。以下の図で基本的な構造を理解しましょう。
mermaidflowchart TB
client[クライアント] -->|API 呼び出し| gateway[API Gateway]
gateway --> service1[ユーザー<br/>サービス]
gateway --> service2[商品<br/>サービス]
gateway --> service3[注文<br/>サービス]
service1 --> db1[(ユーザー<br/>DB)]
service2 --> db2[(商品<br/>DB)]
service3 --> db3[(注文<br/>DB)]
service1 -.->|データ取得| service2
service3 -.->|ユーザー情報| service1
service3 -.->|商品情報| service2
この図から分かるように、各サービスが独立しており、必要に応じてサービス間で通信を行います。API Gatewayが外部からの窓口となり、適切なサービスにリクエストをルーティングします。
ビジネス要件と技術要件の変化
現代のビジネス環境では、以下のような要件が重視されています。
- 迅速な機能リリース: 市場の変化に素早く対応する必要性
- 高可用性: システム停止によるビジネス損失の最小化
- グローバル展開: 地域ごとの要件に柔軟に対応
これらの要件を満たすために、マイクロサービスアーキテクチャが注目されているのです。
Dockerがマイクロサービスに果たす役割
コンテナ技術の基本概念
Dockerはコンテナ技術を使用して、アプリケーションとその依存関係を軽量で移植可能なコンテナにパッケージ化します。
コンテナと仮想マシンの違いを表で比較してみましょう。
# | 項目 | コンテナ | 仮想マシン |
---|---|---|---|
1 | リソース使用量 | 軽量(MB単位) | 重い(GB単位) |
2 | 起動時間 | 秒単位 | 分単位 |
3 | OS | ホストOSを共有 | 独自のOS |
4 | 分離レベル | プロセス分離 | ハードウェア分離 |
マイクロサービスとの親和性
Dockerとマイクロサービスは以下の点で非常に相性が良いです。
- サービスの独立性: 各コンテナが独立した実行環境を提供
- ポータビリティ: どの環境でも同じように動作
- スケーラビリティ: 必要なサービスだけをスケール
以下のDockerfileの基本例をご覧ください。
dockerfileFROM node:18-alpine
WORKDIR /app
# パッケージファイルをコピー
COPY package*.json ./
# 依存関係をインストール
RUN yarn install --frozen-lockfile
このように、アプリケーションの実行環境を明示的に定義することで、環境の違いによる問題を回避できます。
開発・運用の効率化
Dockerを使用することで、以下のような効率化が実現されます。
- 環境の統一: 開発、テスト、本番環境で同じコンテナを使用
- デプロイの簡素化: コンテナ単位でのデプロイが可能
- ロールバックの容易性: 前のバージョンのコンテナに瞬時に切り戻し
課題
マイクロサービス構築における技術的課題
マイクロサービスアーキテクチャは多くの利点をもたらしますが、同時に新たな技術的課題も発生します。
サービス間通信の複雑性
モノリシックアプリケーションでは単純な関数呼び出しだったものが、マイクロサービスではネットワーク通信になります。
以下の図で通信の複雑さを理解しましょう。
mermaidsequenceDiagram
participant Client as クライアント
participant Gateway as API Gateway
participant UserService as ユーザーサービス
participant ProductService as 商品サービス
participant OrderService as 注文サービス
Client->>Gateway: 注文作成リクエスト
Gateway->>OrderService: 注文処理開始
OrderService->>UserService: ユーザー情報取得
UserService-->>OrderService: ユーザー情報
OrderService->>ProductService: 商品情報・在庫確認
ProductService-->>OrderService: 商品情報
OrderService-->>Gateway: 注文完了
Gateway-->>Client: レスポンス
この図から分かるように、単一の操作でも複数のサービス間でやり取りが発生し、エラーハンドリングや遅延の管理が重要になります。
データの一貫性管理
分散システムでは、ACID特性の維持が困難です。特に以下の点が課題となります。
- 結果的整合性: データの整合性が即座に保証されない
- 分散トランザクション: 複数のサービスにまたがる処理の原子性確保
- データ複製の管理: 同じデータが複数のサービスで必要な場合
運用監視の困難さ
マイクロサービスでは監視すべき対象が大幅に増加します。
# | 監視項目 | モノリシック | マイクロサービス |
---|---|---|---|
1 | アプリケーション数 | 1個 | N個 |
2 | ログの種類 | 統一 | サービス毎に異なる |
3 | 障害の原因特定 | 比較的容易 | サービス間の依存関係を考慮 |
4 | パフォーマンス分析 | 単一指標 | 複数サービスの総合評価 |
開発チーム間の連携
マイクロサービスでは、各サービスが異なるチームによって開発されることが多く、以下の課題があります。
- API契約の管理: サービス間のインターフェース定義
- バージョン互換性: サービスの更新タイミングの調整
- 共通ライブラリ: 重複コードの管理
Dockerを使わない場合の問題点
環境依存の問題
従来の開発では「私の環境では動くのに...」という問題が頻繁に発生していました。
bash# 開発者Aの環境
node --version # v16.14.0
npm --version # 8.3.1
# 開発者Bの環境
node --version # v18.12.0
npm --version # 9.1.0
このようなバージョンの違いが予期しない動作を引き起こすことがありました。
デプロイメントの複雑さ
従来のデプロイでは以下のような手順が必要でした。
- サーバーにアプリケーションをコピー
- 依存関係のインストール
- 設定ファイルの更新
- サービスの再起動
- 正常性の確認
これらの手順が複数のサービスで必要になると、デプロイの複雑さは指数関数的に増大します。
スケーリングの困難さ
マイクロサービスでは、需要に応じて特定のサービスのみをスケールしたいケースが多くあります。しかし、従来の環境では全体をスケールするか、手動で新しいインスタンスを設定する必要がありました。
解決策
Dockerを活用したマイクロサービス設計
コンテナ化戦略
効果的なコンテナ化戦略では、以下の原則に従います。
Single Responsibility原則 各コンテナは単一の責任を持つように設計します。
dockerfile# 良い例:Webアプリケーション専用
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN yarn install --frozen-lockfile
COPY src/ ./src/
COPY public/ ./public/
EXPOSE 3000
CMD ["yarn", "start"]
イミュータブルインフラストラクチャ コンテナイメージは変更不可能として扱い、設定変更時は新しいイメージをビルドします。
Docker Composeによるオーケストレーション
複数のマイクロサービスを連携させるために、Docker Composeを使用します。
yamlversion: '3.8'
services:
api-gateway:
build: ./api-gateway
ports:
- "3000:3000"
depends_on:
- user-service
- product-service
environment:
- NODE_ENV=production
yaml user-service:
build: ./user-service
ports:
- "3001:3000"
depends_on:
- user-db
environment:
- DB_HOST=user-db
- DB_PORT=5432
yaml user-db:
image: postgres:14
environment:
POSTGRES_DB: users
POSTGRES_USER: admin
POSTGRES_PASSWORD: password
volumes:
- user_data:/var/lib/postgresql/data
volumes:
user_data:
ネットワーク設計
Docker Composeでは、サービス間の通信を効率的に管理できます。
mermaidflowchart LR
subgraph "Docker Network"
gateway[API Gateway<br/>Port: 3000]
user[User Service<br/>Port: 3001]
product[Product Service<br/>Port: 3002]
order[Order Service<br/>Port: 3003]
gateway --> user
gateway --> product
gateway --> order
order --> user
order --> product
end
external[外部クライアント] --> gateway
この設計により、外部からは API Gateway のみがアクセス可能で、内部のサービス間通信はDocker内部ネットワークで保護されます。
データ管理戦略
マイクロサービスでは、各サービスが独自のデータベースを持つのが基本原則です。
Database per Service パターン
yaml# docker-compose.yml での DB分割例
user-db:
image: postgres:14
environment:
POSTGRES_DB: users
product-db:
image: mysql:8.0
environment:
MYSQL_DATABASE: products
order-db:
image: mongodb:5.0
environment:
MONGO_INITDB_DATABASE: orders
各サービスが最適なデータベースを選択できるのも、マイクロサービスの利点です。
開発・運用プロセスの改善
CI/CDパイプライン構築
GitHubActionsを使用したCI/CDパイプラインの例をご紹介します。
yaml# .github/workflows/deploy.yml
name: Deploy Microservices
on:
push:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
yaml - name: Build and test user service
run: |
cd user-service
docker build -t user-service:latest .
docker run --rm user-service:latest yarn test
- name: Build and test product service
run: |
cd product-service
docker build -t product-service:latest .
docker run --rm product-service:latest yarn test
監視・ログ管理
マイクロサービスでは集約ログ管理が重要です。
yaml# docker-compose.yml にログ収集を追加
user-service:
build: ./user-service
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
labels:
- "service=user-service"
- "environment=production"
構造化ログの実装例:
javascript// user-service/src/logger.js
const winston = require('winston');
const logger = winston.createLogger({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.Console()
]
});
module.exports = logger;
セキュリティ対策
コンテナレベルでのセキュリティ対策も重要です。
dockerfile# セキュリティを考慮したDockerfile
FROM node:18-alpine
# 非rootユーザーを作成
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# 必要最小限のパッケージのみインストール
RUN apk add --no-cache libc6-compat
USER nextjs
WORKDIR /app
具体例
サンプルアプリケーション構築
実際にECサイトを想定したマイクロサービスシステムを構築してみましょう。
ECサイトを想定した3つのマイクロサービス
以下の図で全体のアーキテクチャを確認しましょう。
mermaidflowchart TB
subgraph "External"
web[Web Frontend]
mobile[Mobile App]
end
subgraph "API Layer"
gateway[API Gateway<br/>Express.js]
end
subgraph "Microservices"
user[User Service<br/>Node.js + Express]
product[Product Service<br/>Node.js + Express]
order[Order Service<br/>Node.js + Express]
end
subgraph "Data Layer"
userdb[(User Database<br/>PostgreSQL)]
productdb[(Product Database<br/>MySQL)]
orderdb[(Order Database<br/>MongoDB)]
end
web --> gateway
mobile --> gateway
gateway --> user
gateway --> product
gateway --> order
user --> userdb
product --> productdb
order --> orderdb
order -.-> user
order -.-> product
このアーキテクチャにより、各サービスが独立して開発・デプロイ可能となり、適切なデータベースを選択できます。
ユーザー管理サービス
まず、ユーザー管理サービスのDockerfileから作成しましょう。
dockerfile# user-service/Dockerfile
FROM node:18-alpine
WORKDIR /app
# パッケージファイルをコピー
COPY package*.json ./
RUN yarn install --frozen-lockfile
# アプリケーションコードをコピー
COPY src/ ./src/
COPY .env.example .env
EXPOSE 3000
# ヘルスチェック機能を追加
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
CMD ["yarn", "start"]
ユーザーサービスのAPIエンドポイント実装例:
javascript// user-service/src/routes/users.js
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const router = express.Router();
// ユーザー登録
router.post('/register', async (req, res) => {
try {
const { email, password, name } = req.body;
// パスワードをハッシュ化
const hashedPassword = await bcrypt.hash(password, 10);
const user = await User.create({
email,
password: hashedPassword,
name
});
res.status(201).json({
message: 'ユーザーが正常に作成されました',
userId: user.id
});
} catch (error) {
res.status(400).json({ error: error.message });
}
});
module.exports = router;
商品管理サービス
商品サービスのDockerfile:
dockerfile# product-service/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN yarn install --frozen-lockfile
COPY src/ ./src/
EXPOSE 3000
CMD ["yarn", "start"]
商品情報を管理するAPI実装:
javascript// product-service/src/routes/products.js
const express = require('express');
const Product = require('../models/Product');
const router = express.Router();
// 商品一覧取得
router.get('/', async (req, res) => {
try {
const { page = 1, limit = 10, category } = req.query;
const filter = category ? { category } : {};
const products = await Product.find(filter)
.limit(limit * 1)
.skip((page - 1) * limit)
.exec();
res.json({
products,
currentPage: page,
totalPages: Math.ceil(await Product.countDocuments(filter) / limit)
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;
注文管理サービス
注文サービスでは他のサービスとの連携が重要です。
javascript// order-service/src/services/orderService.js
const axios = require('axios');
const Order = require('../models/Order');
class OrderService {
async createOrder(userId, items) {
try {
// ユーザー情報を確認
const userResponse = await axios.get(`http://user-service:3000/users/${userId}`);
if (!userResponse.data) {
throw new Error('ユーザーが見つかりません');
}
// 商品情報と在庫を確認
const productChecks = await Promise.all(
items.map(item =>
axios.get(`http://product-service:3000/products/${item.productId}`)
)
);
// 在庫確認
productChecks.forEach((response, index) => {
const product = response.data;
if (product.stock < items[index].quantity) {
throw new Error(`商品 ${product.name} の在庫が不足しています`);
}
});
// 注文を作成
const order = await Order.create({
userId,
items,
status: 'pending',
createdAt: new Date()
});
return order;
} catch (error) {
throw error;
}
}
}
module.exports = OrderService;
API Gateway実装
API Gatewayで各サービスへのルーティングを管理します。
javascript// api-gateway/src/app.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const rateLimit = require('express-rate-limit');
const app = express();
// レート制限を設定
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分
max: 100 // リクエスト数上限
});
app.use(limiter);
app.use(express.json());
// ユーザーサービスへのプロキシ
app.use('/api/users', createProxyMiddleware({
target: 'http://user-service:3000',
changeOrigin: true,
pathRewrite: {
'^/api/users': '/users'
}
}));
// 商品サービスへのプロキシ
app.use('/api/products', createProxyMiddleware({
target: 'http://product-service:3000',
changeOrigin: true,
pathRewrite: {
'^/api/products': '/products'
}
}));
module.exports = app;
実装手順とコード例
Dockerfile作成
各サービス用のDockerfileの作成方法を詳しく見ていきましょう。
マルチステージビルドの活用
dockerfile# user-service/Dockerfile(本番用最適化版)
# ビルドステージ
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN yarn install --frozen-lockfile
COPY src/ ./src/
RUN yarn build
# 本番ステージ
FROM node:18-alpine AS production
RUN addgroup -g 1001 -S nodejs
RUN adduser -S appuser -u 1001
WORKDIR /app
COPY package*.json ./
RUN yarn install --production --frozen-lockfile
COPY --from=builder /app/dist ./dist
COPY --chown=appuser:nodejs . .
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]
docker-compose.yml設定
開発環境用と本番環境用のCompose設定例:
開発環境用(docker-compose.dev.yml)
yamlversion: '3.8'
services:
api-gateway:
build:
context: ./api-gateway
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- ./api-gateway/src:/app/src
environment:
- NODE_ENV=development
- USER_SERVICE_URL=http://user-service:3000
- PRODUCT_SERVICE_URL=http://product-service:3000
depends_on:
- user-service
- product-service
- order-service
yaml user-service:
build:
context: ./user-service
dockerfile: Dockerfile.dev
ports:
- "3001:3000"
volumes:
- ./user-service/src:/app/src
environment:
- NODE_ENV=development
- DB_HOST=user-db
- DB_PORT=5432
- DB_NAME=users_dev
depends_on:
- user-db
本番環境用(docker-compose.prod.yml)
yamlversion: '3.8'
services:
api-gateway:
build:
context: ./api-gateway
dockerfile: Dockerfile
ports:
- "80:3000"
environment:
- NODE_ENV=production
- USER_SERVICE_URL=http://user-service:3000
restart: always
deploy:
replicas: 2
resources:
limits:
cpus: '0.5'
memory: 512M
サービス間通信の実装
サービス間の通信にはHTTP REST APIを使用し、エラーハンドリングとリトライ機能を実装します。
javascript// shared/src/serviceClient.js
const axios = require('axios');
class ServiceClient {
constructor(baseURL, retries = 3) {
this.client = axios.create({
baseURL,
timeout: 5000,
headers: {
'Content-Type': 'application/json'
}
});
this.retries = retries;
// リクエストインターセプター
this.client.interceptors.request.use(
config => {
console.log(`API Request: ${config.method?.toUpperCase()} ${config.url}`);
return config;
},
error => Promise.reject(error)
);
// レスポンスインターセプター
this.client.interceptors.response.use(
response => response,
async error => {
const config = error.config;
if (!config || !config.retry || config.retry >= this.retries) {
return Promise.reject(error);
}
config.retry += 1;
// 指数バックオフでリトライ
const delay = Math.pow(2, config.retry) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
return this.client(config);
}
);
}
async get(url, config = {}) {
config.retry = 0;
const response = await this.client.get(url, config);
return response.data;
}
async post(url, data, config = {}) {
config.retry = 0;
const response = await this.client.post(url, data, config);
return response.data;
}
}
module.exports = ServiceClient;
データベース分割
各サービスのデータベース接続とマイグレーション設定:
javascript// user-service/src/database/connection.js
const { Pool } = require('pg');
const pool = new Pool({
user: process.env.DB_USER || 'admin',
host: process.env.DB_HOST || 'user-db',
database: process.env.DB_NAME || 'users',
password: process.env.DB_PASSWORD || 'password',
port: process.env.DB_PORT || 5432,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// ヘルスチェック
pool.on('connect', () => {
console.log('PostgreSQL に接続しました');
});
pool.on('error', (err) => {
console.error('予期しないPostgreSQLエラー:', err);
process.exit(-1);
});
module.exports = pool;
マイグレーションファイルの例:
sql-- user-service/migrations/001_create_users_table.sql
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_users_email ON users(email);
図で理解できる要点
- 各マイクロサービスが独立したコンテナとして実行される
- API Gatewayがサービス間の通信を仲介する
- データベースもサービスごとに分離されている
- Docker Composeで全体のオーケストレーションを管理する
まとめ
Dockerを活用したマイクロサービスアーキテクチャは、現代のWebアプリケーション開発において非常に有効なアプローチです。
本記事では、基本概念から具体的な実装まで幅広くご紹介しました。重要なポイントをおさらいしますと、以下の通りです。
技術的な利点
- サービスの独立性により、部分的なスケーリングとデプロイが可能
- Dockerコンテナによる環境の標準化と移植性の確保
- 各サービスに最適な技術スタックの選択が可能
開発・運用面での改善
- チーム間の独立した開発サイクルの実現
- CI/CDパイプラインによる自動化の促進
- 障害の影響範囲を限定的にすることでシステム全体の可用性向上
実装のポイント
- Docker Composeによるローカルでのオーケストレーション
- API Gatewayパターンによるサービス間通信の管理
- Database per Serviceパターンによるデータの分離
マイクロサービスアーキテクチャの導入は、初期の複雑性は増しますが、長期的にはシステムの柔軟性と拡張性に大きな価値をもたらします。本記事で紹介した実装例を参考に、まずは小規模なサンプルから始めて、段階的に理解を深めていくことをお勧めいたします。
関連リンク
- article
Docker でマイクロサービスを構築するアーキテクチャ入門
- article
Vite × Docker:本番運用を見据えたコンテナ化手順
- article
【解説】Docker Hub とプライベートレジストリの違いと使い分け
- article
Docker で GPU を活用する:機械学習環境を構築するための手順
- article
Docker Compose と Makefile を組み合わせて開発効率を最大化する方法
- article
【保存版】Dockerfile のベストプラクティス 10 選:効率的な記述方法まとめ
- article
WordPress のインストール完全手順:レンタルサーバー・Docker・ローカルを徹底比較
- article
gpt-oss で始めるローカル環境 AI 開発入門
- article
GPT-5 で変わる自然言語処理:文章生成・要約・翻訳の精度検証
- article
WebSocket と HTTP/2・HTTP/3 の違いを徹底比較
- article
Emotion で SVG アイコンや画像にスタイルを適用する
- article
WebRTC でビデオチャットアプリを作る手順【初心者向け】
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来