Skip to content

実世界の例

ソフトウェア開発、運用、自動化における一般的なシナリオのための本番環境対応のPlexr設定。

フルスタックアプリケーションセットアップ

フロントエンド、バックエンド、データベースを含むモダンなフルスタックアプリケーションの完全なセットアップ:

yaml
name: "フルスタックアプリケーションセットアップ"
version: "1.0"
description: "Reactフロントエンド、Node.jsバックエンド、PostgreSQLデータベースをセットアップ"

vars:
  app_name: "myapp"
  node_version: "18"
  postgres_version: "15"
  redis_version: "7"

steps:
  # 環境チェック
  - name: "前提条件を確認"
    command: |
      echo "必要なツールを確認中..."
      for cmd in git node npm docker docker-compose psql redis-cli; do
        if ! command -v $cmd &> /dev/null; then
          echo "エラー: $cmd がインストールされていません"
          exit 1
        fi
      done
      echo "すべての前提条件が満たされています"

  # データベースセットアップ
  - name: "PostgreSQLを開始"
    command: |
      docker run -d \
        --name `{{.app_name}}`-postgres \
        -e POSTGRES_PASSWORD=postgres \
        -e POSTGRES_DB=`{{.app_name}}` \
        -p 5432:5432 \
        postgres:`{{.postgres_version}}`
    outputs:
      - name: db_container_id
        from: stdout

  - name: "PostgreSQLを待機"
    command: |
      for i in {1..30}; do
        if docker exec `{{.app_name}}`-postgres pg_isready -U postgres; then
          echo "PostgreSQLの準備ができました"
          exit 0
        fi
        echo "PostgreSQLを待機中... ($i/30)"
        sleep 2
      done
      exit 1
    depends_on: ["PostgreSQLを開始"]

  # Redisセットアップ
  - name: "Redisを開始"
    command: |
      docker run -d \
        --name `{{.app_name}}`-redis \
        -p 6379:6379 \
        redis:`{{.redis_version}}` \
        redis-server --appendonly yes
    outputs:
      - name: redis_container_id
        from: stdout

  # バックエンドセットアップ
  - name: "バックエンドをセットアップ"
    parallel:
      - name: "バックエンド依存関係をインストール"
        command: "npm install"
        workdir: "./backend"
        
      - name: "環境をセットアップ"
        command: |
          cat > .env << EOF
          NODE_ENV=development
          PORT=3001
          DATABASE_URL=postgresql://postgres:postgres@localhost:5432/`{{.app_name}}`
          REDIS_URL=redis://localhost:6379
          JWT_SECRET=$(openssl rand -hex 32)
          EOF
        workdir: "./backend"

  - name: "データベースマイグレーションを実行"
    command: "npm run migrate"
    workdir: "./backend"
    depends_on: ["PostgreSQLを待機", "バックエンドをセットアップ"]
    env:
      DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/`{{.app_name}}`"

  - name: "データベースをシード"
    command: "npm run seed"
    workdir: "./backend"
    depends_on: ["データベースマイグレーションを実行"]
    condition: '`{{.seed_data | default "true"}}` == "true"'

  # フロントエンドセットアップ
  - name: "フロントエンドをセットアップ"
    parallel:
      - name: "フロントエンド依存関係をインストール"
        command: "npm install"
        workdir: "./frontend"
        
      - name: "フロントエンド環境をセットアップ"
        command: |
          cat > .env.local << EOF
          REACT_APP_API_URL=http://localhost:3001
          REACT_APP_WS_URL=ws://localhost:3001
          EOF
        workdir: "./frontend"

  # サービスを開始
  - name: "バックエンドを開始"
    command: "npm run dev"
    workdir: "./backend"
    background: true
    depends_on: ["データベースマイグレーションを実行", "Redisを開始"]
    outputs:
      - name: backend_pid
        from: stdout

  - name: "フロントエンドを開始"
    command: "npm start"
    workdir: "./frontend"
    background: true
    depends_on: ["フロントエンドをセットアップ"]
    outputs:
      - name: frontend_pid
        from: stdout

  - name: "サービスを待機"
    command: |
      echo "サービスの開始を待機中..."
      sleep 5
      
      # バックエンドを確認
      if curl -f http://localhost:3001/health; then
        echo "バックエンドが実行中"
      else
        echo "バックエンドの開始に失敗"
        exit 1
      fi
      
      # フロントエンドを確認
      if curl -f http://localhost:3000; then
        echo "フロントエンドが実行中"
      else
        echo "フロントエンドの開始に失敗"
        exit 1
      fi
    depends_on: ["バックエンドを開始", "フロントエンドを開始"]

  - name: "アクセスURLを表示"
    command: |
      echo "🚀 アプリケーションの準備ができました!"
      echo "フロントエンド: http://localhost:3000"
      echo "バックエンドAPI: http://localhost:3001"
      echo "データベース: postgresql://localhost:5432/`{{.app_name}}`"
      echo "Redis: redis://localhost:6379"
    depends_on: ["サービスを待機"]

データベースマイグレーションシステム

ロールバックサポート付きの本番グレードのデータベースマイグレーションワークフロー:

yaml
name: "データベースマイグレーションシステム"
version: "1.0"
description: "ロールバックサポート付きの安全なデータベースマイグレーション"

vars:
  db_host: "${DB_HOST:-localhost}"
  db_name: "${DB_NAME:-myapp}"
  db_user: "${DB_USER:-postgres}"
  migration_table: "schema_migrations"
  migrations_dir: "./migrations"

steps:
  # マイグレーション前チェック
  - name: "データベース接続を確認"
    command: |
      psql -h `{{.db_host}}` -U `{{.db_user}}` -d `{{.db_name}}` -c "SELECT 1"
    env:
      PGPASSWORD: "${DB_PASSWORD}"
    
  - name: "マイグレーションテーブルを作成"
    command: |
      psql -h `{{.db_host}}` -U `{{.db_user}}` -d `{{.db_name}}` << EOF
      CREATE TABLE IF NOT EXISTS `{{.migration_table}}` (
        version VARCHAR(255) PRIMARY KEY,
        applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        checksum VARCHAR(64),
        execution_time INTEGER,
        success BOOLEAN DEFAULT TRUE
      );
      EOF
    env:
      PGPASSWORD: "${DB_PASSWORD}"
    depends_on: ["データベース接続を確認"]

  - name: "適用されたマイグレーションを取得"
    command: |
      psql -h `{{.db_host}}` -U `{{.db_user}}` -d `{{.db_name}}` -t -c \
        "SELECT version FROM `{{.migration_table}}` WHERE success = true ORDER BY version"
    env:
      PGPASSWORD: "${DB_PASSWORD}"
    outputs:
      - name: applied_migrations
        from: stdout
        as_array: true
    depends_on: ["マイグレーションテーブルを作成"]

  - name: "保留中のマイグレーションを取得"
    command: |
      ls `{{.migrations_dir}}`/*.up.sql | sort | while read file; do
        version=$(basename $file .up.sql)
        if ! echo "`{{.applied_migrations}}`" | grep -q "$version"; then
          echo "$file"
        fi
      done
    outputs:
      - name: pending_migrations
        from: stdout
        as_array: true
    depends_on: ["適用されたマイグレーションを取得"]

  - name: "マイグレーション計画を表示"
    command: |
      if [ -z "`{{.pending_migrations}}`" ]; then
        echo "保留中のマイグレーションはありません"
      else
        echo "保留中のマイグレーション:"
        echo "`{{.pending_migrations}}`" | tr ' ' '\n'
      fi
    depends_on: ["保留中のマイグレーションを取得"]

  # マイグレーション前のバックアップ
  - name: "バックアップを作成"
    command: |
      backup_file="backup-$(date +%Y%m%d-%H%M%S).sql"
      pg_dump -h `{{.db_host}}` -U `{{.db_user}}` -d `{{.db_name}}` > $backup_file
      echo $backup_file
    env:
      PGPASSWORD: "${DB_PASSWORD}"
    outputs:
      - name: backup_file
        from: stdout
    condition: '`{{.skip_backup | default "false"}}` != "true"'

  # マイグレーションを適用
  - name: "マイグレーションを適用"
    command: |
      migration_file="`{{.migration}}`"
      version=$(basename $migration_file .up.sql)
      checksum=$(sha256sum $migration_file | cut -d' ' -f1)
      
      echo "マイグレーションを適用中: $version"
      start_time=$(date +%s)
      
      # トランザクションを開始
      psql -h `{{.db_host}}` -U `{{.db_user}}` -d `{{.db_name}}` << EOF
      BEGIN;
      
      -- マイグレーションを適用
      \i $migration_file
      
      -- マイグレーションを記録
      INSERT INTO `{{.migration_table}}` (version, checksum, execution_time)
      VALUES ('$version', '$checksum', $(date +%s) - $start_time);
      
      COMMIT;
      EOF
      
      echo "マイグレーション $version が正常に適用されました"
    env:
      PGPASSWORD: "${DB_PASSWORD}"
    for_each:
      items: "`{{.pending_migrations}}`"
      var: migration
    depends_on: ["バックアップを作成"]
    on_failure:
      - name: "マイグレーションをロールバック"
        command: |
          version=$(basename `{{.migration}}` .up.sql)
          rollback_file="`{{.migrations_dir}}`/$version.down.sql"
          
          if [ -f "$rollback_file" ]; then
            echo "マイグレーションをロールバック中: $version"
            psql -h `{{.db_host}}` -U `{{.db_user}}` -d `{{.db_name}}` < $rollback_file
            
            # 失敗としてマーク
            psql -h `{{.db_host}}` -U `{{.db_user}}` -d `{{.db_name}}` -c \
              "UPDATE `{{.migration_table}}` SET success = false WHERE version = '$version'"
          else
            echo "$version のロールバックファイルが見つかりません"
          fi
        env:
          PGPASSWORD: "${DB_PASSWORD}"

  # マイグレーション後の検証
  - name: "スキーマを検証"
    command: |
      # スキーマ検証テストを実行
      npm run test:schema
    depends_on: ["マイグレーションを適用"]
    condition: '`{{.skip_validation | default "false"}}` != "true"'

  - name: "マイグレーションサマリー"
    command: |
      psql -h `{{.db_host}}` -U `{{.db_user}}` -d `{{.db_name}}` << EOF
      SELECT 
        version,
        applied_at,
        execution_time || '秒' as 期間,
        CASE WHEN success THEN '✓' ELSE '✗' END as ステータス
      FROM `{{.migration_table}}`
      ORDER BY applied_at DESC
      LIMIT 10;
      EOF
    env:
      PGPASSWORD: "${DB_PASSWORD}"
    depends_on: ["マイグレーションを適用"]

CI/CDパイプライン

テスト、ビルド、デプロイを含む完全なCI/CDパイプライン:

yaml
name: "CI/CDパイプライン"
version: "1.0"
description: "並列テストとステージングデプロイメントを含む完全なCI/CDパイプライン"

vars:
  app_name: "${APP_NAME}"
  git_sha: "${GITHUB_SHA:-$(git rev-parse HEAD)}"
  git_branch: "${GITHUB_REF_NAME:-$(git branch --show-current)}"
  docker_registry: "${DOCKER_REGISTRY:-ghcr.io}"
  docker_repo: "`{{.docker_registry}}`/`{{.github_repository | default .app_name}}`"

steps:
  # コード品質チェック
  - name: "コード品質"
    parallel:
      - name: "リント"
        command: "npm run lint"
        
      - name: "型チェック"
        command: "npm run type-check"
        
      - name: "セキュリティ監査"
        command: |
          npm audit --audit-level=high
          trivy fs --severity HIGH,CRITICAL .
          
      - name: "ライセンスチェック"
        command: "license-checker --onlyAllow 'MIT;Apache-2.0;BSD-3-Clause;ISC'"

  # テスト
  - name: "テストスイート"
    parallel:
      - name: "ユニットテスト"
        command: "npm run test:unit -- --coverage"
        outputs:
          - name: unit_coverage
            from: stdout
            regex: "Statements\\s+:\\s+([0-9.]+)%"
            
      - name: "統合テスト"
        command: |
          docker-compose -f docker-compose.test.yml up -d
          npm run test:integration
          docker-compose -f docker-compose.test.yml down
          
      - name: "E2Eテスト"
        command: |
          npm run build
          npm run test:e2e
        env:
          HEADLESS: "true"
    depends_on: ["コード品質"]

  - name: "カバレッジを確認"
    command: |
      if (( $(echo "`{{.unit_coverage}}` < 80" | bc -l) )); then
        echo "カバレッジ `{{.unit_coverage}}`% が閾値80%を下回っています"
        exit 1
      fi
      echo "カバレッジ `{{.unit_coverage}}`% が閾値を満たしています"
    depends_on: ["テストスイート"]

  # ビルド
  - name: "アプリケーションをビルド"
    command: |
      npm run build
      echo "`{{.git_sha}}`" > dist/VERSION
      echo "`{{.git_branch}}`" > dist/BRANCH
      date -u +"%Y-%m-%dT%H:%M:%SZ" > dist/BUILD_TIME
    outputs:
      - name: build_time
        from: stdout
    depends_on: ["テストスイート"]

  - name: "Dockerイメージをビルド"
    command: |
      docker build \
        --build-arg VERSION=`{{.git_sha}}` \
        --build-arg BUILD_TIME=`{{.build_time}}` \
        -t `{{.docker_repo}}`:`{{.git_sha}}` \
        -t `{{.docker_repo}}`:`{{.git_branch}}`-latest \
        .
    depends_on: ["アプリケーションをビルド"]

  # セキュリティスキャン
  - name: "Dockerイメージをスキャン"
    command: |
      trivy image --severity HIGH,CRITICAL `{{.docker_repo}}`:`{{.git_sha}}`
      grype `{{.docker_repo}}`:`{{.git_sha}}` -f critical
    depends_on: ["Dockerイメージをビルド"]

  # レジストリにプッシュ
  - name: "Dockerイメージをプッシュ"
    command: |
      echo "$DOCKER_PASSWORD" | docker login `{{.docker_registry}}` -u "$DOCKER_USERNAME" --password-stdin
      docker push `{{.docker_repo}}`:`{{.git_sha}}`
      docker push `{{.docker_repo}}`:`{{.git_branch}}`-latest
    depends_on: ["Dockerイメージをスキャン"]
    condition: '`{{.git_branch}}` == "main" || `{{.git_branch}}` == "develop"'

  # 環境にデプロイ
  - name: "ステージングにデプロイ"
    command: |
      kubectl set image deployment/`{{.app_name}}` \
        `{{.app_name}}`=`{{.docker_repo}}`:`{{.git_sha}}` \
        -n staging
        
      kubectl rollout status deployment/`{{.app_name}}` -n staging
    depends_on: ["Dockerイメージをプッシュ"]
    condition: '`{{.git_branch}}` == "develop"'

  - name: "スモークテスト - ステージング"
    command: |
      ./scripts/smoke-tests.sh https://staging.example.com
    depends_on: ["ステージングにデプロイ"]
    retry:
      attempts: 3
      delay: 30s

  - name: "本番環境にデプロイ"
    command: |
      # ブルーグリーンデプロイメント
      kubectl apply -f - << EOF
      apiVersion: v1
      kind: Service
      metadata:
        name: `{{.app_name}}`-green
        namespace: production
      spec:
        selector:
          app: `{{.app_name}}`
          version: `{{.git_sha}}`
        ports:
        - port: 80
          targetPort: 8080
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: `{{.app_name}}`-`{{.git_sha}}`
        namespace: production
      spec:
        replicas: 3
        selector:
          matchLabels:
            app: `{{.app_name}}`
            version: `{{.git_sha}}`
        template:
          metadata:
            labels:
              app: `{{.app_name}}`
              version: `{{.git_sha}}`
          spec:
            containers:
            - name: `{{.app_name}}`
              image: `{{.docker_repo}}`:`{{.git_sha}}`
              ports:
              - containerPort: 8080
      EOF
      
      kubectl rollout status deployment/`{{.app_name}}`-`{{.git_sha}}` -n production
    depends_on: ["スモークテスト - ステージング"]
    condition: '`{{.git_branch}}` == "main" && `{{.manual_approval}}` == "true"'

  - name: "ヘルスチェック - 本番環境"
    command: |
      for i in {1..10}; do
        if curl -f https://example.com/health; then
          echo "本番デプロイメントが健全です"
          exit 0
        fi
        echo "ヘルスチェック試行 $i/10 が失敗しました"
        sleep 6
      done
      exit 1
    depends_on: ["本番環境にデプロイ"]

  - name: "トラフィックを切り替え"
    command: |
      # メインサービスを新しいデプロイメントに向ける
      kubectl patch service `{{.app_name}}` -n production -p \
        '{"spec":{"selector":{"version":"`{{.git_sha}}`"}}`}'
        
      echo "トラフィックがバージョン `{{.git_sha}}` に切り替わりました"
    depends_on: ["ヘルスチェック - 本番環境"]

  - name: "古いデプロイメントをクリーンアップ"
    command: |
      # 最新の3つのデプロイメントのみを保持
      kubectl get deployments -n production -l app=`{{.app_name}}` \
        --sort-by=.metadata.creationTimestamp -o name | \
        head -n -3 | xargs -r kubectl delete -n production
    depends_on: ["トラフィックを切り替え"]

  # 通知
  - name: "通知を送信"
    parallel:
      - name: "Slack通知"
        command: |
          curl -X POST $SLACK_WEBHOOK -H 'Content-type: application/json' -d '{
            "text": "デプロイメント成功",
            "attachments": [{
              "color": "good",
              "fields": [
                {"title": "アプリケーション", "value": "`{{.app_name}}`", "short": true},
                {"title": "バージョン", "value": "`{{.git_sha}}`", "short": true},
                {"title": "環境", "value": "production", "short": true},
                {"title": "ブランチ", "value": "`{{.git_branch}}`", "short": true}
              ]
            }]
          }'
          
      - name: "デプロイメントトラッカーを更新"
        command: |
          curl -X POST https://deployments.example.com/api/deployments \
            -H "Authorization: Bearer $DEPLOY_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{
              "app": "`{{.app_name}}`",
              "version": "`{{.git_sha}}`",
              "environment": "production",
              "timestamp": "`{{now | date "2006-01-02T15:04:05Z07:00"}}`",
              "status": "success"
            }'
    depends_on: ["トラフィックを切り替え"]
    always_run: true

チームコラボレーションワークフロー

マルチユーザー開発環境セットアップ:

yaml
name: "チーム開発環境"
version: "1.0"
description: "チームコラボレーション用の標準化された開発環境"

vars:
  project_name: "${PROJECT_NAME}"
  team_members: "${TEAM_MEMBERS}"  # カンマ区切りリスト
  base_port: 3000

steps:
  # 共有サービスをセットアップ
  - name: "共有ネットワークを作成"
    command: "docker network create `{{.project_name}}`-dev || true"

  - name: "共有データベースを開始"
    command: |
      docker run -d \
        --name `{{.project_name}}`-db \
        --network `{{.project_name}}`-dev \
        -e POSTGRES_PASSWORD=dev \
        -e POSTGRES_DB=`{{.project_name}}` \
        -v `{{.project_name}}`-db-data:/var/lib/postgresql/data \
        -p 5432:5432 \
        postgres:15
        
  - name: "共有Redisを開始"
    command: |
      docker run -d \
        --name `{{.project_name}}`-redis \
        --network `{{.project_name}}`-dev \
        -v `{{.project_name}}`-redis-data:/data \
        -p 6379:6379 \
        redis:7 redis-server --appendonly yes

  # 各チームメンバー用のセットアップ
  - name: "開発者環境をセットアップ"
    command: |
      member="`{{.member}}`"
      port_offset=`{{.index}}`
      
      # 開発者固有の設定を作成
      mkdir -p .dev/$member
      cat > .dev/$member/config.env << EOF
      DEVELOPER=$member
      API_PORT=$((`{{.base_port}}` + port_offset * 10))
      WEB_PORT=$((`{{.base_port}}` + port_offset * 10 + 1))
      DB_NAME=`{{.project_name}}`_${member}
      REDIS_DB=$port_offset
      EOF
      
      # 開発者データベースを作成
      docker exec `{{.project_name}}`-db psql -U postgres -c \
        "CREATE DATABASE `{{.project_name}}`_${member};"
        
      echo "$member の環境が作成されました"
      echo "APIポート: $((`{{.base_port}}` + port_offset * 10))"
      echo "Webポート: $((`{{.base_port}}` + port_offset * 10 + 1))"
    for_each:
      items: "`{{.team_members | split \",\"}}`"
      var: member
      index: index
    depends_on: ["共有データベースを開始", "共有Redisを開始"]

  # 開発ツールをセットアップ
  - name: "開発プロキシを開始"
    command: |
      # チーム用のnginx設定を作成
      cat > .dev/nginx.conf << 'EOF'
      events { worker_connections 1024; }
      http {
        `{{range $i, $member := .team_members | split ","}}`
        upstream `{{$member}}`_api {
          server localhost:`{{add $.base_port (mul $i 10)}}`;
        }
        upstream `{{$member}}`_web {
          server localhost:`{{add $.base_port (mul $i 10) 1}}`;
        }
        `{{end}}`
        
        server {
          listen 80;
          
          `{{range $i, $member := .team_members | split ","}}`
          location /`{{$member}}`/api {
            rewrite ^/`{{$member}}`/api(.*)$ $1 break;
            proxy_pass http://`{{$member}}`_api;
          }
          location /`{{$member}}` {
            proxy_pass http://`{{$member}}`_web;
          }
          `{{end}}`
        }
      }
      EOF
      
      docker run -d \
        --name `{{.project_name}}`-proxy \
        -p 80:80 \
        -v $(pwd)/.dev/nginx.conf:/etc/nginx/nginx.conf:ro \
        nginx:alpine

  # コード共有をセットアップ
  - name: "Gitフックを初期化"
    command: |
      # コード標準のためのpre-commitフック
      cat > .git/hooks/pre-commit << 'EOF'
      #!/bin/bash
      echo "pre-commitチェックを実行中..."
      
      # リンティングを実行
      npm run lint || exit 1
      
      # 変更されたファイルのテストを実行
      git diff --cached --name-only | grep -E '\.(js|ts)$' | xargs npm test -- --findRelatedTests
      
      # console.log文をチェック
      if git diff --cached | grep -E '^\+.*console\.log'; then
        echo "エラー: console.log文が見つかりました"
        exit 1
      fi
      EOF
      
      chmod +x .git/hooks/pre-commit

  - name: "コードレビューツールをセットアップ"
    command: |
      # コードレビューツールをインストールして設定
      npm install -D prettier eslint husky lint-staged
      
      # prettierを設定
      cat > .prettierrc << EOF
      {
        "semi": true,
        "singleQuote": true,
        "tabWidth": 2,
        "trailingComma": "es5"
      }
      EOF
      
      # huskyをセットアップ
      npx husky install
      npx husky add .husky/pre-commit "npx lint-staged"
      
      # lint-stagedを設定
      cat > .lintstagedrc.json << EOF
      {
        "*.{js,ts,jsx,tsx}": ["eslint --fix", "prettier --write"],
        "*.{json,md,yml,yaml}": ["prettier --write"]
      }
      EOF

  # ドキュメントとオンボーディング
  - name: "開発者ドキュメントを生成"
    command: |
      cat > .dev/README.md << EOF
      # `{{.project_name}}` 開発環境
      
      ## クイックスタート
      
      1. \`plexr execute\` を実行して環境をセットアップ
      2. 環境をソース: \`source .dev/$USER/config.env\`
      3. サービスを開始: \`npm run dev\`
      
      ## チーム環境
      
      | 開発者 | APIポート | Webポート | URL |
      |-----------|----------|----------|-----|
      `{{range $i, $member := .team_members | split ","}}`| `{{$member}}` | `{{add $.base_port (mul $i 10)}}` | `{{add $.base_port (mul $i 10) 1}}` | http://localhost/`{{$member}}` |
      `{{end}}`
      
      ## 共有サービス
      
      - データベース: postgresql://localhost:5432/`{{.project_name}}`_$USER
      - Redis: redis://localhost:6379 (割り当てられたDB番号を使用)
      - プロキシ: http://localhost (すべての開発者環境にルーティング)
      
      ## コマンド
      
      - \`npm run dev\` - 開発サーバーを開始
      - \`npm test\` - テストを実行
      - \`npm run lint\` - コードスタイルをチェック
      - \`npm run db:migrate\` - データベースマイグレーションを実行
      
      ## Gitワークフロー
      
      1. フィーチャーブランチを作成: \`git checkout -b feature/your-feature\`
      2. 変更を加えてコミット
      3. プッシュしてPRを作成
      4. チームからレビューをリクエスト
      EOF
      
      echo "ドキュメントが .dev/README.md に生成されました"

  - name: "サマリーを表示"
    command: |
      echo "🚀 チーム開発環境の準備ができました!"
      echo ""
      echo "共有サービス:"
      echo "- データベース: postgresql://localhost:5432"
      echo "- Redis: redis://localhost:6379"
      echo "- プロキシ: http://localhost"
      echo ""
      echo "以下の開発者環境が作成されました:"
      echo "`{{.team_members}}`" | tr ',' '\n' | sed 's/^/- /'
      echo ""
      echo "次のステップ:"
      echo "1. 環境をソース: source .dev/$USER/config.env"
      echo "2. マイグレーションを実行: npm run db:migrate"
      echo "3. 開発を開始: npm run dev"
      echo ""
      echo "詳細は .dev/README.md を参照してください"

モニタリングとアラートのセットアップ

本番モニタリングスタックのデプロイメント:

yaml
name: "モニタリングスタックデプロイメント"
version: "1.0"
description: "Prometheus、Grafana、AlertManagerをデプロイ"

vars:
  stack_name: "monitoring"
  prometheus_retention: "30d"
  grafana_admin_password: "${GRAFANA_ADMIN_PASSWORD}"
  slack_webhook: "${SLACK_WEBHOOK}"
  pagerduty_key: "${PAGERDUTY_KEY}"

steps:
  - name: "モニタリング名前空間を作成"
    command: "kubectl create namespace `{{.stack_name}}` --dry-run=client -o yaml | kubectl apply -f -"

  - name: "Prometheusをデプロイ"
    command: |
      cat << EOF | kubectl apply -f -
      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: prometheus-config
        namespace: `{{.stack_name}}`
      data:
        prometheus.yml: |
          global:
            scrape_interval: 30s
            evaluation_interval: 30s
          
          rule_files:
            - /etc/prometheus/rules/*.yml
          
          alerting:
            alertmanagers:
              - static_configs:
                  - targets: ['alertmanager:9093']
          
          scrape_configs:
            - job_name: 'kubernetes-pods'
              kubernetes_sd_configs:
                - role: pod
              relabel_configs:
                - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
                  action: keep
                  regex: true
                - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
                  action: replace
                  target_label: __metrics_path__
                  regex: (.+)
                - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
                  action: replace
                  regex: ([^:]+)(?::\d+)?;(\d+)
                  replacement: \$1:\$2
                  target_label: __address__
      ---
      apiVersion: apps/v1
      kind: StatefulSet
      metadata:
        name: prometheus
        namespace: `{{.stack_name}}`
      spec:
        serviceName: prometheus
        replicas: 1
        selector:
          matchLabels:
            app: prometheus
        template:
          metadata:
            labels:
              app: prometheus
          spec:
            containers:
            - name: prometheus
              image: prom/prometheus:latest
              args:
                - '--config.file=/etc/prometheus/prometheus.yml'
                - '--storage.tsdb.path=/prometheus'
                - '--storage.tsdb.retention.time=`{{.prometheus_retention}}`'
              ports:
              - containerPort: 9090
              volumeMounts:
              - name: config
                mountPath: /etc/prometheus
              - name: storage
                mountPath: /prometheus
            volumes:
            - name: config
              configMap:
                name: prometheus-config
        volumeClaimTemplates:
        - metadata:
            name: storage
          spec:
            accessModes: ["ReadWriteOnce"]
            resources:
              requests:
                storage: 50Gi
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: prometheus
        namespace: `{{.stack_name}}`
      spec:
        ports:
        - port: 9090
        selector:
          app: prometheus
      EOF

  - name: "アラートルールをデプロイ"
    command: |
      cat << 'EOF' | kubectl apply -f -
      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: prometheus-rules
        namespace: `{{.stack_name}}`
      data:
        alerts.yml: |
          groups:
            - name: application
              interval: 30s
              rules:
                - alert: HighErrorRate
                  expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
                  for: 5m
                  labels:
                    severity: critical
                  annotations:
                    summary: "高いエラー率が検出されました"
                    description: "`{{ $labels.instance }}` のエラー率が `{{ $value }}` です"
                
                - alert: HighMemoryUsage
                  expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) > 0.9
                  for: 5m
                  labels:
                    severity: warning
                  annotations:
                    summary: "高いメモリ使用率"
                    description: "`{{ $labels.instance }}` のメモリ使用率が90%を超えています"
                
                - alert: PodCrashLooping
                  expr: rate(kube_pod_container_status_restarts_total[15m]) > 0
                  for: 5m
                  labels:
                    severity: critical
                  annotations:
                    summary: "Podがクラッシュループしています"
                    description: "Pod `{{ $labels.namespace }}`/`{{ $labels.pod }}` がクラッシュループしています"
      EOF
    depends_on: ["Prometheusをデプロイ"]

  - name: "AlertManagerをデプロイ"
    command: |
      cat << EOF | kubectl apply -f -
      apiVersion: v1
      kind: Secret
      metadata:
        name: alertmanager-config
        namespace: `{{.stack_name}}`
      stringData:
        alertmanager.yml: |
          global:
            resolve_timeout: 5m
            slack_api_url: '`{{.slack_webhook}}`'
          
          route:
            group_by: ['alertname', 'cluster', 'service']
            group_wait: 10s
            group_interval: 10s
            repeat_interval: 12h
            receiver: 'default'
            routes:
              - match:
                  severity: critical
                receiver: 'critical'
          
          receivers:
            - name: 'default'
              slack_configs:
                - channel: '#alerts'
                  title: 'アラート: `{{ .GroupLabels.alertname }}`'
                  text: '`{{ range .Alerts }}``{{ .Annotations.description }}``{{ end }}`'
            
            - name: 'critical'
              slack_configs:
                - channel: '#alerts-critical'
                  title: '🚨 クリティカル: `{{ .GroupLabels.alertname }}`'
                  text: '`{{ range .Alerts }}``{{ .Annotations.description }}``{{ end }}`'
              pagerduty_configs:
                - service_key: '`{{.pagerduty_key}}`'
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: alertmanager
        namespace: `{{.stack_name}}`
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: alertmanager
        template:
          metadata:
            labels:
              app: alertmanager
          spec:
            containers:
            - name: alertmanager
              image: prom/alertmanager:latest
              args:
                - '--config.file=/etc/alertmanager/alertmanager.yml'
              ports:
              - containerPort: 9093
              volumeMounts:
              - name: config
                mountPath: /etc/alertmanager
            volumes:
            - name: config
              secret:
                secretName: alertmanager-config
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: alertmanager
        namespace: `{{.stack_name}}`
      spec:
        ports:
        - port: 9093
        selector:
          app: alertmanager
      EOF

  - name: "Grafanaをデプロイ"
    command: |
      cat << EOF | kubectl apply -f -
      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: grafana-datasources
        namespace: `{{.stack_name}}`
      data:
        prometheus.yaml: |
          apiVersion: 1
          datasources:
            - name: Prometheus
              type: prometheus
              access: proxy
              url: http://prometheus:9090
              isDefault: true
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: grafana
        namespace: `{{.stack_name}}`
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: grafana
        template:
          metadata:
            labels:
              app: grafana
          spec:
            containers:
            - name: grafana
              image: grafana/grafana:latest
              env:
              - name: GF_SECURITY_ADMIN_PASSWORD
                value: "`{{.grafana_admin_password}}`"
              - name: GF_INSTALL_PLUGINS
                value: "grafana-clock-panel,grafana-simple-json-datasource"
              ports:
              - containerPort: 3000
              volumeMounts:
              - name: datasources
                mountPath: /etc/grafana/provisioning/datasources
            volumes:
            - name: datasources
              configMap:
                name: grafana-datasources
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: grafana
        namespace: `{{.stack_name}}`
      spec:
        type: LoadBalancer
        ports:
        - port: 80
          targetPort: 3000
        selector:
          app: grafana
      EOF

  - name: "デプロイメントを待機"
    command: |
      for deployment in prometheus alertmanager grafana; do
        kubectl rollout status deployment/$deployment -n `{{.stack_name}}`
      done
    depends_on: ["Prometheusをデプロイ", "AlertManagerをデプロイ", "Grafanaをデプロイ"]

  - name: "Grafanaダッシュボードをインポート"
    command: |
      # Grafanaの準備を待つ
      sleep 30
      
      # GrafanaのURLを取得
      GRAFANA_URL=$(kubectl get svc grafana -n `{{.stack_name}}` -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
      
      # ダッシュボードをインポート
      for dashboard in dashboards/*.json; do
        curl -X POST \
          -H "Content-Type: application/json" \
          -u "admin:`{{.grafana_admin_password}}`" \
          -d @$dashboard \
          http://$GRAFANA_URL/api/dashboards/db
      done
    depends_on: ["デプロイメントを待機"]

  - name: "アクセス情報を表示"
    command: |
      GRAFANA_URL=$(kubectl get svc grafana -n `{{.stack_name}}` -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
      
      echo "✅ モニタリングスタックが正常にデプロイされました!"
      echo ""
      echo "アクセスURL:"
      echo "- Grafana: http://$GRAFANA_URL (admin/`{{.grafana_admin_password}}`)"
      echo "- Prometheus: kubectl port-forward -n `{{.stack_name}}` svc/prometheus 9090:9090"
      echo "- AlertManager: kubectl port-forward -n `{{.stack_name}}` svc/alertmanager 9093:9093"
      echo ""
      echo "次のステップ:"
      echo "1. アプリケーションがメトリクスを公開するよう設定"
      echo "2. 追加のGrafanaダッシュボードをインポート"
      echo "3. Prometheusのアラートルールをカスタマイズ"
      echo "4. テストアラートをトリガーしてアラートをテスト"
    depends_on: ["Grafanaダッシュボードをインポート"]

Released under the MIT License.