🎄 科学と神々株式会社 アドベントカレンダー 2025
Hybrid License System Day 23: CI/CDパイプライン
統合・デプロイ編 (3/5)
📖 はじめに
Day 23では、CI/CDパイプラインを学びます。GitHub Actions設定、自動テスト実行、Dockerイメージビルド、デプロイ自動化を実装しましょう。
🔄 CI/CDの概要
Continuous Integration (CI)
開発者がコードをプッシュ
↓
自動テスト実行
↓
コード品質チェック
↓
ビルド検証
↓
統合成功 ✅ / 失敗 ❌
Continuous Deployment (CD)
CI成功
↓
Dockerイメージビルド
↓
コンテナレジストリにプッシュ
↓
ステージング環境デプロイ
↓
自動E2Eテスト
↓
本番環境デプロイ(手動承認)
🚀 GitHub Actionsワークフロー
.github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
NODE_VERSION: '20'
DOCKER_REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# ========== テストジョブ ==========
test:
name: Run Tests
runs-on: ubuntu-latest
strategy:
matrix:
service: [api-gateway, auth-service, admin-service]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: ${{ matrix.service }}/package-lock.json
- name: Install dependencies
working-directory: ./${{ matrix.service }}
run: npm ci
- name: Run linter
working-directory: ./${{ matrix.service }}
run: npm run lint || true
- name: Run unit tests
working-directory: ./${{ matrix.service }}
run: npm run test
- name: Run integration tests
working-directory: ./${{ matrix.service }}
run: npm run test:integration
- name: Generate coverage report
working-directory: ./${{ matrix.service }}
run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./${{ matrix.service }}/coverage/lcov.info
flags: ${{ matrix.service }}
# ========== セキュリティスキャン ==========
security:
name: Security Scan
runs-on: ubuntu-latest
needs: test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run npm audit
run: |
cd api-gateway && npm audit --audit-level=high || true
cd ../auth-service && npm audit --audit-level=high || true
cd ../admin-service && npm audit --audit-level=high || true
- name: Run Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
command: test
# ========== Dockerビルド ==========
build:
name: Build Docker Images
runs-on: ubuntu-latest
needs: [test, security]
strategy:
matrix:
service: [api-gateway, auth-service, admin-service]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.service }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=sha
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./${{ matrix.service }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.service }}:buildcache
cache-to: type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.service }}:buildcache,mode=max
# ========== E2Eテスト ==========
e2e:
name: E2E Tests
runs-on: ubuntu-latest
needs: build
if: github.event_name != 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Start services with Docker Compose
run: docker-compose -f docker-compose.ci.yml up -d
- name: Wait for services to be ready
run: |
timeout 60 bash -c 'until curl -f http://localhost:3000/health; do sleep 2; done'
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install Playwright
run: |
npm install -D @playwright/test
npx playwright install --with-deps
- name: Run E2E tests
run: npm run test:e2e
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: playwright-report/
- name: Cleanup
if: always()
run: docker-compose -f docker-compose.ci.yml down
🚢 デプロイワークフロー
.github/workflows/deploy.yml
name: Deploy to Production
on:
push:
tags:
- 'v*.*.*'
env:
DOCKER_REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
jobs:
# ========== デプロイ準備 ==========
prepare:
name: Prepare Deployment
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
steps:
- name: Get version from tag
id: get_version
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Create GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
# ========== ステージング環境デプロイ ==========
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: prepare
environment:
name: staging
url: https://staging.license-system.example.com
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy to staging
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/license-system
docker-compose pull
docker-compose up -d
docker-compose exec -T api-gateway npm run health-check
- name: Run smoke tests
run: |
curl -f https://staging.license-system.example.com/health
curl -f https://staging.license-system.example.com/api/v1/health
# ========== 本番環境デプロイ(手動承認) ==========
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: deploy-staging
environment:
name: production
url: https://license-system.example.com
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy to production
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/license-system
# Blue-Green Deployment
docker-compose -f docker-compose.green.yml pull
docker-compose -f docker-compose.green.yml up -d
# ヘルスチェック
sleep 10
curl -f http://localhost:3000/health
# トラフィック切り替え
docker-compose -f docker-compose.blue.yml down
mv docker-compose.green.yml docker-compose.blue.yml
- name: Notify deployment success
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Deployment to production successful!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
🔧 Dockerfile最適化
マルチステージビルド
# api-gateway/Dockerfile
# ========== ビルドステージ ==========
FROM node:20-alpine AS builder
WORKDIR /app
# 依存関係のみ先にインストール(レイヤーキャッシュ活用)
COPY package*.json ./
RUN npm ci --only=production
# ソースコピー
COPY . .
# ========== 本番ステージ ==========
FROM node:20-alpine
# セキュリティ: 非rootユーザー作成
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
# ビルドステージから成果物コピー
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --chown=nodejs:nodejs . .
# ユーザー切り替え
USER nodejs
# ポート公開
EXPOSE 3000
# ヘルスチェック
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
# 起動コマンド
CMD ["node", "src/index.js"]
📦 デプロイ戦略
Blue-Green Deployment
# docker-compose.blue.yml(現行環境)
version: '3.8'
services:
api-gateway-blue:
image: ghcr.io/yourorg/license-system-api-gateway:v1.0.0
ports:
- "3000:3000"
environment:
- NODE_ENV=production
# docker-compose.green.yml(新環境)
version: '3.8'
services:
api-gateway-green:
image: ghcr.io/yourorg/license-system-api-gateway:v1.1.0
ports:
- "3001:3000"
environment:
- NODE_ENV=production
Canary Deployment
# nginx.conf - トラフィック分散(90% Blue, 10% Green)
upstream backend {
server api-gateway-blue:3000 weight=9;
server api-gateway-green:3000 weight=1;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
🔔 通知設定
Slack通知
# .github/workflows/notify.yml
- name: Notify on failure
if: failure()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: |
Deployment failed!
Commit: ${{ github.sha }}
Author: ${{ github.actor }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
fields: repo,message,commit,author,action,eventName,ref,workflow
📊 デプロイメトリクス
デプロイ頻度測定
- name: Record deployment
run: |
echo "Deployment at $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> deployments.log
git add deployments.log
git commit -m "Record deployment"
git push
MTTR (Mean Time To Recovery) 追跡
- name: Calculate MTTR
if: failure()
run: |
LAST_SUCCESS=$(git log --grep="Deployment successful" -1 --format=%ct)
CURRENT_TIME=$(date +%s)
MTTR=$((CURRENT_TIME - LAST_SUCCESS))
echo "MTTR: $MTTR seconds" >> $GITHUB_STEP_SUMMARY
🎯 次のステップ
Day 24では、Kubernetes対応を学びます。Kubernetesマニフェスト作成、Service/Deployment設定、ConfigMap/Secret管理、Ingress設定について詳しく解説します。
🔗 関連リンク
次回予告: Day 24では、K8s Deployment設計とHPA(Horizontal Pod Autoscaler)を詳しく解説します!
Copyright © 2025 Gods & Golem, Inc. All rights reserved.