はじめに

GitHub Actionsは、GitHubに統合された強力な自動化プラットフォームです。 CI/CD、自動テスト、コードレビュー、デプロイメントなど、開発ワークフローの あらゆる側面を自動化できます。この章では、基礎から実践的な使用方法まで、 段階的に学習していきます。

学習のポイント

GitHub Actionsは単なるCI/CDツールではありません。創造的に活用することで、開発プロセス全体を革新的に改善できる可能性を秘めています。

6.1 GitHub Actionsの基本概念

GitHub Actionsとは

GitHub Actionsは、リポジトリ内のイベントに基づいて自動化されたワークフローを 実行するためのプラットフォームです。以下の特徴があります:

イベント駆動

プッシュ、プルリクエスト、Issue作成など、様々なイベントでトリガー

クラウドベース

GitHubがホストする仮想マシンで実行(セルフホストも可能)

再利用可能

マーケットプレイスから既存のアクションを利用

多言語対応

あらゆるプログラミング言語とフレームワークをサポート

基本的な構成要素

1. ワークフロー(Workflow)

自動化されたプロセス全体を定義するYAMLファイル

.github/workflows/my-workflow.yml

2. イベント(Event)

ワークフローをトリガーする特定の活動

  • push - コードのプッシュ
  • pull_request - PRの作成/更新
  • schedule - 定期実行
  • workflow_dispatch - 手動実行

3. ジョブ(Job)

同じランナー上で実行される一連のステップ

4. ステップ(Step)

ジョブ内の個別のタスク(シェルスクリプトまたはアクション)

5. アクション(Action)

再利用可能な複雑なタスクの単位

6. ランナー(Runner)

ワークフローを実行するサーバー

最初のワークフロー

YAML
# .github/workflows/hello-world.yml
name: Hello World Workflow

# イベントトリガー
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  workflow_dispatch:  # 手動実行を許可

# ジョブの定義
jobs:
  greet:
    # ランナーの指定
    runs-on: ubuntu-latest
    
    # ステップの定義
    steps:
      # リポジトリのチェックアウト
      - name: Checkout repository
        uses: actions/checkout@v3
      
      # シンプルなコマンドの実行
      - name: Say hello
        run: echo "Hello, GitHub Actions!"
      
      # 環境変数の使用
      - name: Show event information
        run: |
          echo "Event: ${{ github.event_name }}"
          echo "Repository: ${{ github.repository }}"
          echo "Branch: ${{ github.ref }}"
          echo "Commit: ${{ github.sha }}"
          echo "Actor: ${{ github.actor }}"
      
      # 複数行のスクリプト
      - name: Run multiple commands
        run: |
          echo "Starting workflow..."
          date
          pwd
          ls -la
          echo "Workflow completed!"

ワークフローの実行フロー

イベント発生(push/PR/手動)
ワークフロー開始
ランナー割り当て
ジョブ実行
各ステップを順次実行
結果の報告

実践演習 6.1

基本的なワークフローの作成:

  1. .github/workflowsディレクトリを作成
  2. 上記の Hello World ワークフローを作成
  3. mainブランチにプッシュして実行を確認
  4. Actions タブで実行結果を確認
  5. 手動実行(workflow_dispatch)を試す

6.2 ワークフロー構文の詳細

イベントトリガーの詳細

基本的なイベント

YAML
# 単一イベント
on: push

# 複数イベント
on: [push, pull_request]

# 詳細な設定
on:
  push:
    branches:
      - main
      - 'release/**'
    tags:
      - v*
    paths:
      - 'src/**'
      - '!src/tests/**'
  
  pull_request:
    types: [opened, synchronize, reopened]
    branches:
      - main
  
  # 定期実行(cron形式)
  schedule:
    - cron: '0 0 * * *'  # 毎日0時(UTC)
    - cron: '*/15 * * * *'  # 15分ごと
  
  # 他のワークフローからの呼び出し
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
    secrets:
      deploy_key:
        required: true
  
  # 手動実行
  workflow_dispatch:
    inputs:
      environment:
        description: 'Deployment environment'
        required: true
        default: 'staging'
        type: choice
        options:
          - development
          - staging
          - production
      
      debug_enabled:
        description: 'Enable debug logging'
        required: false
        type: boolean
        default: false

高度なイベントフィルタリング

YAML
on:
  push:
    # ブランチフィルタ
    branches:
      - main
      - 'feature/**'
      - '!feature/experimental-*'  # 除外
    
    # タグフィルタ
    tags:
      - 'v[0-9]+.[0-9]+.[0-9]+'  # セマンティックバージョン
    
    # パスフィルタ
    paths:
      - '**.js'
      - 'src/**'
      - '!**.test.js'  # テストファイルは除外
    
    # 特定のパスが変更された時のみ
    paths-ignore:
      - 'docs/**'
      - '**.md'
      - '.gitignore'

ジョブの高度な設定

並列実行と依存関係

YAML
jobs:
  # 並列実行されるジョブ
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run tests
        run: npm test
  
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run linter
        run: npm run lint
  
  # 依存関係のあるジョブ
  build:
    needs: [test, lint]  # test と lint が成功後に実行
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Build application
        run: npm run build
  
  deploy:
    needs: build
    if: github.ref == 'refs/heads/main'  # mainブランチのみ
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to production
        run: echo "Deploying..."

マトリックスビルド

YAML
jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node: [14, 16, 18, 20]
        include:
          # 特定の組み合わせを追加
          - os: ubuntu-latest
            node: 21
            experimental: true
        exclude:
          # 特定の組み合わせを除外
          - os: windows-latest
            node: 14
      fail-fast: false  # 1つ失敗しても他は継続
    
    runs-on: ${{ matrix.os }}
    continue-on-error: ${{ matrix.experimental || false }}
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js ${{ matrix.node }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node }}
      
      - name: Install and test
        run: |
          npm ci
          npm test

環境変数とシークレット

YAML
name: Environment Variables Example

env:
  # ワークフローレベルの環境変数
  NODE_ENV: production
  CI: true

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      # ジョブレベルの環境変数
      BUILD_DIR: ./dist
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up environment
        env:
          # ステップレベルの環境変数
          API_ENDPOINT: https://api.example.com
        run: |
          echo "NODE_ENV: $NODE_ENV"
          echo "BUILD_DIR: $BUILD_DIR"
          echo "API_ENDPOINT: $API_ENDPOINT"
      
      - name: Use secrets
        env:
          API_KEY: ${{ secrets.API_KEY }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
        run: |
          # シークレットは自動的にマスクされる
          echo "Connecting to database..."
          # 実際の値は ****** として表示される
      
      - name: Dynamic environment variables
        run: |
          # GitHub環境変数をファイルに追加
          echo "DEPLOYMENT_ID=${{ github.run_id }}" >> $GITHUB_ENV
          echo "BUILD_TIME=$(date -u +%Y%m%d%H%M%S)" >> $GITHUB_ENV
      
      - name: Use dynamic variables
        run: |
          echo "Deployment ID: $DEPLOYMENT_ID"
          echo "Build Time: $BUILD_TIME"
セキュリティのベストプラクティス
  • シークレットは Settings → Secrets で設定
  • ログに出力されても自動的にマスクされる
  • フォークされたリポジトリからのPRではシークレットは利用不可
  • 最小権限の原則に従う

条件付き実行

YAML
jobs:
  conditional-job:
    runs-on: ubuntu-latest
    # ジョブレベルの条件
    if: |
      github.event_name == 'push' && 
      contains(github.ref, 'refs/heads/release')
    
    steps:
      - uses: actions/checkout@v3
      
      # 常に実行
      - name: Always runs
        run: echo "This always runs"
      
      # 条件付き実行
      - name: Only on main branch
        if: github.ref == 'refs/heads/main'
        run: echo "Running on main branch"
      
      # 前のステップの結果に基づく
      - name: Test
        id: test
        run: |
          echo "Running tests..."
          echo "status=success" >> $GITHUB_OUTPUT
      
      - name: Deploy if tests pass
        if: steps.test.outputs.status == 'success'
        run: echo "Deploying..."
      
      # 失敗時のみ実行
      - name: Notify on failure
        if: failure()
        run: echo "Workflow failed!"
      
      # 常に実行(成功/失敗に関わらず)
      - name: Cleanup
        if: always()
        run: echo "Cleaning up..."
      
      # キャンセル時も実行
      - name: On cancel
        if: cancelled()
        run: echo "Workflow was cancelled"

6.3 実践的なCI/CDパイプライン

Node.jsプロジェクトの完全なCI/CD

YAML
# .github/workflows/nodejs-ci-cd.yml
name: Node.js CI/CD

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]
  release:
    types: [ created ]

env:
  NODE_VERSION: '18.x'
  ARTIFACT_NAME: build-artifact

jobs:
  # コード品質チェック
  quality:
    name: Code Quality
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run linter
        run: npm run lint
      
      - name: Check formatting
        run: npm run format:check
      
      - name: Type check
        run: npm run type-check

  # テスト実行
  test:
    name: Test (${{ matrix.os }} - Node ${{ matrix.node }})
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        node: [16.x, 18.x, 20.x]
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js ${{ matrix.node }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node }}
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run unit tests
        run: npm run test:unit -- --coverage
      
      - name: Run integration tests
        run: npm run test:integration
      
      - name: Upload coverage
        if: matrix.os == 'ubuntu-latest' && matrix.node == '18.x'
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/lcov.info
          flags: unittests
          name: codecov-umbrella

  # セキュリティスキャン
  security:
    name: Security Scan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Run npm audit
        run: npm audit --production
      
      - name: Run Snyk scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high
      
      - name: Upload SARIF results
        uses: github/codeql-action/upload-sarif@v2
        if: always()
        with:
          sarif_file: snyk.sarif

  # ビルド
  build:
    name: Build Application
    needs: [quality, test, security]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build application
        run: |
          npm run build
          echo "${{ github.sha }}" > dist/version.txt
      
      - name: Create deployment artifact
        run: |
          tar -czf ${{ env.ARTIFACT_NAME }}.tar.gz dist/
      
      - name: Upload artifact
        uses: actions/upload-artifact@v3
        with:
          name: ${{ env.ARTIFACT_NAME }}
          path: ${{ env.ARTIFACT_NAME }}.tar.gz
          retention-days: 7

  # ステージング環境へのデプロイ
  deploy-staging:
    name: Deploy to Staging
    needs: build
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.example.com
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Download artifact
        uses: actions/download-artifact@v3
        with:
          name: ${{ env.ARTIFACT_NAME }}
      
      - name: Extract artifact
        run: tar -xzf ${{ env.ARTIFACT_NAME }}.tar.gz
      
      - name: Deploy to staging
        env:
          DEPLOY_KEY: ${{ secrets.STAGING_DEPLOY_KEY }}
          DEPLOY_HOST: ${{ secrets.STAGING_HOST }}
        run: |
          # SSH deployment example
          echo "$DEPLOY_KEY" > deploy_key
          chmod 600 deploy_key
          rsync -avz -e "ssh -i deploy_key -o StrictHostKeyChecking=no" \
            ./dist/ deploy@$DEPLOY_HOST:/var/www/staging/
      
      - name: Run smoke tests
        run: |
          curl -f https://staging.example.com/health || exit 1
      
      - name: Notify Slack
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          text: 'Staging deployment ${{ job.status }}'
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

  # 本番環境へのデプロイ
  deploy-production:
    name: Deploy to Production
    needs: build
    if: github.event_name == 'release'
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Download artifact
        uses: actions/download-artifact@v3
        with:
          name: ${{ env.ARTIFACT_NAME }}
      
      - name: Extract artifact
        run: tar -xzf ${{ env.ARTIFACT_NAME }}.tar.gz
      
      - name: Deploy to production
        env:
          DEPLOY_KEY: ${{ secrets.PROD_DEPLOY_KEY }}
          DEPLOY_HOST: ${{ secrets.PROD_HOST }}
        run: |
          # Production deployment with backup
          echo "$DEPLOY_KEY" > deploy_key
          chmod 600 deploy_key
          
          # Backup current version
          ssh -i deploy_key deploy@$DEPLOY_HOST \
            "cp -r /var/www/app /var/www/backup-$(date +%Y%m%d%H%M%S)"
          
          # Deploy new version
          rsync -avz -e "ssh -i deploy_key -o StrictHostKeyChecking=no" \
            ./dist/ deploy@$DEPLOY_HOST:/var/www/app/
      
      - name: Run health checks
        run: |
          for i in {1..5}; do
            if curl -f https://example.com/health; then
              echo "Health check passed"
              break
            fi
            echo "Health check attempt $i failed, retrying..."
            sleep 10
          done
      
      - name: Create deployment record
        uses: actions/github-script@v6
        with:
          script: |
            await github.rest.repos.createDeployment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              ref: context.sha,
              environment: 'production',
              description: 'Production deployment',
              auto_merge: false,
              required_contexts: []
            });

Docker コンテナのビルドとプッシュ

YAML
name: Docker Build and Push

on:
  push:
    branches: [ main ]
    tags: [ 'v*' ]
  pull_request:
    branches: [ main ]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      
      - name: Log in to the Container registry
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v2
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v4
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
            type=sha,prefix={{branch}}-
      
      - name: Build and push Docker image
        uses: docker/build-push-action@v4
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            BUILD_DATE=${{ steps.meta.outputs.created }}
            VERSION=${{ steps.meta.outputs.version }}
      
      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
          format: 'sarif'
          output: 'trivy-results.sarif'
      
      - name: Upload Trivy scan results
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: 'trivy-results.sarif'

実践演習 6.2

実践的なCI/CDパイプラインの構築:

  1. サンプルのNode.jsプロジェクトを作成
  2. テスト、リント、ビルドスクリプトを追加
  3. 上記のCI/CDワークフローを適用
  4. プルリクエストを作成してCIの動作を確認
  5. 環境変数とシークレットを設定
  6. ビルドアーティファクトの生成と保存を確認

6.4 カスタムアクションの作成

アクションの種類

JavaScript アクション

  • Node.js で実装
  • 高速な実行
  • GitHub APIとの統合が容易
  • すべてのランナーで動作

Docker コンテナアクション

  • 任意の言語で実装可能
  • 完全な実行環境の制御
  • 複雑な依存関係の管理
  • Linuxランナーのみ

複合アクション

  • 複数のステップを組み合わせ
  • 既存のアクションを再利用
  • YAMLで定義
  • 簡単な作成と保守

JavaScript アクションの作成

ディレクトリ構造

my-action/
├── action.yml
├── index.js
├── package.json
├── package-lock.json
├── node_modules/
├── README.md
└── .gitignore

action.yml - アクションのメタデータ

YAML
name: 'PR Comment Analyzer'
description: 'Analyzes PR comments and provides insights'
author: 'Your Name'

branding:
  icon: 'message-square'
  color: 'blue'

inputs:
  github-token:
    description: 'GitHub token for API access'
    required: true
  
  threshold:
    description: 'Minimum comment length to analyze'
    required: false
    default: '50'
  
  language:
    description: 'Language for sentiment analysis'
    required: false
    default: 'en'

outputs:
  total-comments:
    description: 'Total number of comments'
  
  sentiment-score:
    description: 'Average sentiment score'
  
  summary:
    description: 'Analysis summary'

runs:
  using: 'node16'
  main: 'dist/index.js'

index.js - アクションの実装

JavaScript
const core = require('@actions/core');
const github = require('@actions/github');

async function run() {
  try {
    // 入力値の取得
    const token = core.getInput('github-token', { required: true });
    const threshold = parseInt(core.getInput('threshold'));
    const language = core.getInput('language');
    
    // GitHub API クライアントの初期化
    const octokit = github.getOctokit(token);
    const context = github.context;
    
    // PRコメントの取得
    if (context.eventName !== 'pull_request') {
      core.setFailed('This action only works on pull_request events');
      return;
    }
    
    const { data: comments } = await octokit.rest.issues.listComments({
      owner: context.repo.owner,
      repo: context.repo.repo,
      issue_number: context.payload.pull_request.number
    });
    
    // コメントの分析
    let totalComments = 0;
    let totalLength = 0;
    let sentimentSum = 0;
    
    for (const comment of comments) {
      if (comment.body.length >= threshold) {
        totalComments++;
        totalLength += comment.body.length;
        
        // 簡単なセンチメント分析(実際はより高度な分析を行う)
        const sentiment = analyzeSentiment(comment.body);
        sentimentSum += sentiment;
        
        core.info(`Analyzed comment from @${comment.user.login}: ${sentiment}`);
      }
    }
    
    const avgSentiment = totalComments > 0 ? sentimentSum / totalComments : 0;
    
    // 結果の出力
    core.setOutput('total-comments', totalComments.toString());
    core.setOutput('sentiment-score', avgSentiment.toFixed(2));
    core.setOutput('summary', `Analyzed ${totalComments} comments with average sentiment ${avgSentiment.toFixed(2)}`);
    
    // PRにコメントを投稿
    if (totalComments > 0) {
      await octokit.rest.issues.createComment({
        owner: context.repo.owner,
        repo: context.repo.repo,
        issue_number: context.payload.pull_request.number,
        body: `## PR Comment Analysis 📊
        
Total comments analyzed: **${totalComments}**
Average sentiment score: **${avgSentiment.toFixed(2)}** ${getSentimentEmoji(avgSentiment)}
Average comment length: **${(totalLength / totalComments).toFixed(0)}** characters

${avgSentiment < 0.5 ? '⚠️ This PR has received mostly negative feedback. Consider addressing the concerns raised.' : '✅ This PR has received positive feedback!'}
`
      });
    }
    
  } catch (error) {
    core.setFailed(`Action failed with error: ${error.message}`);
  }
}

function analyzeSentiment(text) {
  // 簡単なセンチメント分析の例
  const positiveWords = ['good', 'great', 'excellent', 'nice', 'perfect', 'thanks'];
  const negativeWords = ['bad', 'wrong', 'error', 'issue', 'problem', 'fix'];
  
  let score = 0.5; // neutral
  
  positiveWords.forEach(word => {
    if (text.toLowerCase().includes(word)) score += 0.1;
  });
  
  negativeWords.forEach(word => {
    if (text.toLowerCase().includes(word)) score -= 0.1;
  });
  
  return Math.max(0, Math.min(1, score));
}

function getSentimentEmoji(score) {
  if (score >= 0.8) return '😄';
  if (score >= 0.6) return '🙂';
  if (score >= 0.4) return '😐';
  if (score >= 0.2) return '😕';
  return '😞';
}

run();

package.json

JSON
{
  "name": "pr-comment-analyzer",
  "version": "1.0.0",
  "description": "GitHub Action to analyze PR comments",
  "main": "index.js",
  "scripts": {
    "build": "ncc build index.js -o dist",
    "test": "jest"
  },
  "dependencies": {
    "@actions/core": "^1.10.0",
    "@actions/github": "^5.1.1"
  },
  "devDependencies": {
    "@vercel/ncc": "^0.34.0",
    "jest": "^29.0.0"
  }
}

複合アクションの作成

YAML
# action.yml - 複合アクション
name: 'Setup and Test'
description: 'Sets up the environment and runs tests'

inputs:
  node-version:
    description: 'Node.js version to use'
    required: false
    default: '18'
  
  package-manager:
    description: 'Package manager (npm, yarn, pnpm)'
    required: false
    default: 'npm'

outputs:
  test-results:
    description: 'Path to test results'
    value: ${{ steps.test.outputs.results }}

runs:
  using: 'composite'
  steps:
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ inputs.node-version }}
        cache: ${{ inputs.package-manager }}
    
    - name: Install dependencies
      shell: bash
      run: |
        if [ "${{ inputs.package-manager }}" = "npm" ]; then
          npm ci
        elif [ "${{ inputs.package-manager }}" = "yarn" ]; then
          yarn install --frozen-lockfile
        elif [ "${{ inputs.package-manager }}" = "pnpm" ]; then
          pnpm install --frozen-lockfile
        fi
    
    - name: Run linter
      shell: bash
      run: ${{ inputs.package-manager }} run lint
    
    - name: Run tests
      id: test
      shell: bash
      run: |
        ${{ inputs.package-manager }} run test -- --coverage
        echo "results=coverage/lcov.info" >> $GITHUB_OUTPUT
    
    - name: Upload coverage
      uses: actions/upload-artifact@v3
      with:
        name: coverage-report
        path: coverage/

アクションの公開と使用

マーケットプレイスへの公開手順

  1. アクションのリポジトリを作成
  2. action.ymlまたはaction.yamlをルートに配置
  3. 適切なREADME.mdを作成(使用例を含む)
  4. セマンティックバージョンのタグを作成(v1.0.0)
  5. GitHubでリリースを作成
  6. 「Publish this Action to the GitHub Marketplace」にチェック

アクションの使用例

YAML
name: Use Custom Actions

on: [push, pull_request]

jobs:
  analyze:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      # マーケットプレイスのアクション
      - name: Analyze PR Comments
        uses: username/pr-comment-analyzer@v1
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          threshold: '100'
      
      # 複合アクション(同じリポジトリ内)
      - name: Setup and Test
        uses: ./.github/actions/setup-and-test
        with:
          node-version: '18'
          package-manager: 'npm'
      
      # 特定のコミット/ブランチを使用
      - name: Custom Action (specific ref)
        uses: username/my-action@abc123
        with:
          parameter: value

6.5 高度なワークフローパターン

再利用可能なワークフロー

共通ワークフローの定義

YAML
# .github/workflows/reusable-deploy.yml
name: Reusable Deploy Workflow

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      version:
        required: true
        type: string
      dry-run:
        required: false
        type: boolean
        default: false
    secrets:
      deploy-key:
        required: true
      slack-webhook:
        required: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v3
      
      - name: Validate inputs
        run: |
          echo "Deploying version ${{ inputs.version }} to ${{ inputs.environment }}"
          if [[ ! "${{ inputs.version }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
            echo "Invalid version format"
            exit 1
          fi
      
      - name: Download release artifact
        uses: actions/download-artifact@v3
        with:
          name: release-${{ inputs.version }}
      
      - name: Deploy application
        if: ${{ !inputs.dry-run }}
        env:
          DEPLOY_KEY: ${{ secrets.deploy-key }}
        run: |
          echo "Deploying to ${{ inputs.environment }}..."
          # 実際のデプロイコマンド
      
      - name: Notify deployment
        if: ${{ secrets.slack-webhook != '' }}
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          text: |
            Deployment to ${{ inputs.environment }}
            Version: ${{ inputs.version }}
            Status: ${{ job.status }}
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.slack-webhook }}

ワークフローの呼び出し

YAML
name: Deploy to Multiple Environments

on:
  release:
    types: [published]

jobs:
  deploy-staging:
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: staging
      version: ${{ github.event.release.tag_name }}
    secrets:
      deploy-key: ${{ secrets.STAGING_DEPLOY_KEY }}
      slack-webhook: ${{ secrets.SLACK_WEBHOOK }}
  
  deploy-production:
    needs: deploy-staging
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: production
      version: ${{ github.event.release.tag_name }}
      dry-run: false
    secrets:
      deploy-key: ${{ secrets.PROD_DEPLOY_KEY }}
      slack-webhook: ${{ secrets.SLACK_WEBHOOK }}

動的なマトリックス生成

YAML
name: Dynamic Matrix

on: push

jobs:
  setup:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - uses: actions/checkout@v3
      
      - name: Generate matrix
        id: set-matrix
        run: |
          # プロジェクトの構造に基づいて動的にマトリックスを生成
          PROJECTS=$(find . -name "package.json" -not -path "*/node_modules/*" | xargs dirname)
          
          MATRIX_JSON="["
          FIRST=true
          
          for project in $PROJECTS; do
            if [ "$FIRST" = true ]; then
              FIRST=false
            else
              MATRIX_JSON+=","
            fi
            MATRIX_JSON+="{\"project\":\"$project\"}"
          done
          
          MATRIX_JSON+="]"
          echo "matrix={\"include\":$MATRIX_JSON}" >> $GITHUB_OUTPUT
  
  test:
    needs: setup
    runs-on: ubuntu-latest
    strategy:
      matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
    steps:
      - uses: actions/checkout@v3
      
      - name: Test ${{ matrix.project }}
        run: |
          cd ${{ matrix.project }}
          npm install
          npm test

並行デプロイメント with 環境保護

YAML
name: Multi-Region Deployment

on:
  workflow_dispatch:
    inputs:
      regions:
        description: 'Regions to deploy (comma-separated)'
        required: true
        default: 'us-east-1,eu-west-1,ap-northeast-1'

jobs:
  prepare:
    runs-on: ubuntu-latest
    outputs:
      regions: ${{ steps.parse.outputs.regions }}
    steps:
      - name: Parse regions
        id: parse
        run: |
          REGIONS='${{ github.event.inputs.regions }}'
          # Convert comma-separated to JSON array
          JSON_ARRAY=$(echo $REGIONS | jq -R 'split(",") | map({"region": .})')
          echo "regions={\"include\":$JSON_ARRAY}" >> $GITHUB_OUTPUT
  
  deploy:
    needs: prepare
    runs-on: ubuntu-latest
    strategy:
      matrix: ${{ fromJSON(needs.prepare.outputs.regions) }}
      max-parallel: 2  # 同時実行数を制限
    environment:
      name: production-${{ matrix.region }}
      url: https://${{ matrix.region }}.example.com
    steps:
      - uses: actions/checkout@v3
      
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ matrix.region }}
      
      - name: Deploy to ${{ matrix.region }}
        run: |
          echo "Deploying to region: ${{ matrix.region }}"
          # AWS deployment commands
          
      - name: Health check
        run: |
          ENDPOINT="https://${{ matrix.region }}.example.com/health"
          for i in {1..10}; do
            if curl -f $ENDPOINT; then
              echo "Health check passed"
              break
            fi
            echo "Attempt $i failed, retrying..."
            sleep 30
          done

実践演習 6.3

高度なワークフローの実装:

  1. カスタムJavaScriptアクションを作成
  2. 複合アクションを作成して既存ワークフローを簡素化
  3. 再利用可能なワークフローを定義
  4. 動的マトリックスを使用したテストを実装
  5. 複数環境への並行デプロイを設定
  6. エラーハンドリングとリトライロジックを追加

6.6 まとめと次のステップ

この章で学んだこと

  • GitHub Actionsの基本概念と構成要素
  • ワークフロー構文の詳細(イベント、ジョブ、ステップ)
  • 実践的なCI/CDパイプラインの構築
  • カスタムアクションの作成(JavaScript、複合)
  • 高度なワークフローパターン(再利用、動的マトリックス)

GitHub Actions チェックリスト

ワークフロー設計

  • 適切なイベントトリガーの選択
  • ジョブの依存関係の定義
  • 条件付き実行の活用
  • シークレットの適切な管理
  • アーティファクトの保存戦略

パフォーマンス最適化

  • キャッシュの活用
  • 並列実行の最適化
  • 不要なステップの削除
  • セルフホストランナーの検討

セキュリティ

  • 最小権限の原則
  • サードパーティアクションの検証
  • シークレットスキャンの有効化
  • OIDC認証の活用

理解度チェック

確認問題

  1. GitHub Actionsの主要な構成要素を5つ挙げ、それぞれの役割を説明してください
  2. プッシュイベントとプルリクエストイベントの違いは何ですか?
  3. マトリックスビルドはどのような場面で有効ですか?
  4. カスタムアクションの3つのタイプとその特徴を説明してください
  5. ワークフローのセキュリティを向上させる方法を3つ挙げてください

次章への準備

第7章では、これまでの知識を統合し、Claude Codeを活用したAI支援開発について学びます。 GitHub ActionsとAIを組み合わせることで、さらに高度な自動化が可能になります。

さらなる学習のために
  • GitHub Actions Marketplaceで人気のアクションを調査
  • 自分のプロジェクトに合わせたワークフローを作成
  • コスト最適化(実行時間の短縮)を意識
  • セキュリティベストプラクティスの実践