PR

【初心者向け】GitHub Actionsをモジュール化して効率的なワークフローを構築する方法

GitHub Actionsのモジュール化とは?

GitHub Actionsのモジュール化とは、繰り返し使用されるワークフローの処理を独立したパーツに分割し、再利用可能にする技術です。これにより、コードの重複を減らし、メンテナンスを簡単にし、チーム全体で効率的な開発を実現できます。

例えば、複数のプロジェクトで同じテスト手順やデプロイ処理を行う場合、毎回同じコードを書く代わりに、一度作成したモジュールを呼び出すだけで済むようになります。

なぜモジュール化が重要なのか?

1. コードの重複を削減

複数のワークフローで同じ処理を書く必要がなくなり、開発効率が向上します。

2. メンテナンスの簡素化

修正が必要になった場合、モジュール化された部分を一箇所修正するだけで、すべての利用箇所に反映されます。

3. チーム間での共有

作成したモジュールをチームメンバーと共有することで、開発の標準化と品質向上が図れます。

4. エラーの削減

テスト済みのモジュールを再利用することで、新しいワークフローでのエラー発生率を低減できます。

GitHub Actionsモジュール化の主な手法

1. Composite Actions(複合アクション)

複数のステップをまとめて一つのアクションとして定義する方法です。最も基本的で理解しやすいモジュール化手法です。

基本的な複合アクションの作成例

# .github/actions/setup-node/action.yml
name: 'Node.js セットアップ'
description: 'Node.jsとパッケージのインストールを行います'

inputs:
  node-version:
    description: 'Node.jsのバージョン'
    required: false
    default: '18'
  
  cache-key:
    description: 'キャッシュキー'
    required: false
    default: 'npm-cache'

runs:
  using: 'composite'
  steps:
    - name: Node.jsセットアップ
      uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: 'npm'
        
    - name: 依存関係インストール
      run: npm ci
      shell: bash
      
    - name: キャッシュ保存
      uses: actions/cache@v3
      with:
        path: ~/.npm
        key: ${{ inputs.cache-key }}-${{ hashFiles('**/package-lock.json') }}

複合アクションの使用例

# .github/workflows/test.yml
name: テスト実行

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: コードチェックアウト
        uses: actions/checkout@v4
        
      - name: Node.js環境構築
        uses: ./.github/actions/setup-node
        with:
          node-version: '20'
          cache-key: 'test-cache'
          
      - name: テスト実行
        run: npm test

2. Reusable Workflows(再利用可能ワークフロー)

ワークフロー全体を再利用可能にする手法です。複数のジョブを含む複雑な処理を標準化したい場合に適しています。

再利用可能ワークフローの作成例

# .github/workflows/reusable-test.yml
name: 再利用可能テストワークフロー

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
        description: '実行環境'
      node-version:
        required: false
        type: string
        default: '18'
        description: 'Node.jsバージョン'
    
    outputs:
      test-result:
        description: 'テスト結果'
        value: ${{ jobs.test.outputs.result }}

jobs:
  test:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    outputs:
      result: ${{ steps.test.outputs.result }}
    
    steps:
      - name: コードチェックアウト
        uses: actions/checkout@v4
        
      - name: Node.js環境構築
        uses: ./.github/actions/setup-node
        with:
          node-version: ${{ inputs.node-version }}
          
      - name: リント実行
        run: npm run lint
        
      - name: テスト実行
        id: test
        run: |
          npm test
          echo "result=success" >> $GITHUB_OUTPUT
          
      - name: カバレッジレポート生成
        run: npm run coverage
        
      - name: テスト結果アップロード
        uses: actions/upload-artifact@v3
        with:
          name: test-results-${{ inputs.environment }}
          path: coverage/

再利用可能ワークフローの呼び出し例

# .github/workflows/ci.yml
name: CI パイプライン

on: [push, pull_request]

jobs:
  test-development:
    uses: ./.github/workflows/reusable-test.yml
    with:
      environment: 'development'
      node-version: '18'
      
  test-production:
    uses: ./.github/workflows/reusable-test.yml
    with:
      environment: 'production'
      node-version: '20'
      
  deploy:
    needs: [test-development, test-production]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: デプロイ実行
        run: echo "テスト完了、デプロイを実行します"

3. External Actions(外部アクション)

独立したリポジトリでアクションを作成し、複数のプロジェクトから利用する方法です。組織全体での標準化に最適です。

外部アクションの作成例

# 独立リポジトリ:my-org/common-actions
# action.yml
name: 'データベースセットアップ'
description: 'テスト用データベースの起動と初期化'

inputs:
  database-type:
    description: 'データベースタイプ (mysql, postgres)'
    required: true
  
  database-version:
    description: 'データベースバージョン'
    required: false
    default: 'latest'

runs:
  using: 'composite'
  steps:
    - name: データベース起動
      run: |
        if [ "${{ inputs.database-type }}" == "mysql" ]; then
          docker run -d --name test-db \
            -e MYSQL_ROOT_PASSWORD=root \
            -e MYSQL_DATABASE=testdb \
            -p 3306:3306 \
            mysql:${{ inputs.database-version }}
        elif [ "${{ inputs.database-type }}" == "postgres" ]; then
          docker run -d --name test-db \
            -e POSTGRES_PASSWORD=postgres \
            -e POSTGRES_DB=testdb \
            -p 5432:5432 \
            postgres:${{ inputs.database-version }}
        fi
      shell: bash
      
    - name: データベース接続待機
      run: |
        for i in {1..30}; do
          if [ "${{ inputs.database-type }}" == "mysql" ]; then
            if docker exec test-db mysql -uroot -proot -e "SELECT 1" >/dev/null 2>&1; then
              echo "MySQLが利用可能になりました"
              break
            fi
          elif [ "${{ inputs.database-type }}" == "postgres" ]; then
            if docker exec test-db pg_isready >/dev/null 2>&1; then
              echo "PostgreSQLが利用可能になりました"
              break
            fi
          fi
          echo "データベース起動を待機中... ($i/30)"
          sleep 2
        done
      shell: bash

外部アクションの使用例

# 任意のプロジェクトから使用
name: アプリケーションテスト

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: データベース環境構築
        uses: my-org/common-actions@v1
        with:
          database-type: 'postgres'
          database-version: '15'
          
      - name: アプリケーションテスト
        run: npm test

実践的なモジュール化戦略

1. 段階的なモジュール化アプローチ

いきなり全てをモジュール化するのではなく、段階的に進めることが成功の鍵です。

フェーズ1:重複処理の特定

# 現在のワークフローで重複している処理を特定
- Node.js セットアップ + npm install
- Docker イメージビルド
- テスト実行 + カバレッジ生成
- Slack通知

フェーズ2:複合アクション化

# .github/actions/notify-slack/action.yml
name: 'Slack通知'
description: 'ビルド結果をSlackに通知します'

inputs:
  webhook-url:
    description: 'Slack Webhook URL'
    required: true
  status:
    description: 'ビルドステータス (success, failure, cancelled)'
    required: true
  job-name:
    description: 'ジョブ名'
    required: false
    default: 'ビルド'

runs:
  using: 'composite'
  steps:
    - name: Slack通知送信
      run: |
        if [ "${{ inputs.status }}" == "success" ]; then
          COLOR="good"
          EMOJI=":white_check_mark:"
          MESSAGE="${{ inputs.job-name }}が成功しました"
        elif [ "${{ inputs.status }}" == "failure" ]; then
          COLOR="danger"
          EMOJI=":x:"
          MESSAGE="${{ inputs.job-name }}が失敗しました"
        else
          COLOR="warning"
          EMOJI=":warning:"
          MESSAGE="${{ inputs.job-name }}がキャンセルされました"
        fi
        
        curl -X POST -H 'Content-type: application/json' \
          --data "{
            \"attachments\": [{
              \"color\": \"$COLOR\",
              \"text\": \"$EMOJI $MESSAGE\"
            }]
          }" \
          ${{ inputs.webhook-url }}
      shell: bash

フェーズ3:再利用可能ワークフロー化

# .github/workflows/deploy-template.yml
name: デプロイテンプレート

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      app-name:
        required: true
        type: string
      docker-tag:
        required: false
        type: string
        default: 'latest'
    secrets:
      DEPLOY_TOKEN:
        required: true
      SLACK_WEBHOOK:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Docker ログイン
        run: echo ${{ secrets.DEPLOY_TOKEN }} | docker login -u oauth2accesstoken --password-stdin
        
      - name: アプリケーションデプロイ
        run: |
          docker pull myregistry/${{ inputs.app-name }}:${{ inputs.docker-tag }}
          docker run -d --name ${{ inputs.app-name }}-${{ inputs.environment }} \
            myregistry/${{ inputs.app-name }}:${{ inputs.docker-tag }}
            
      - name: デプロイ結果通知
        if: always()
        uses: ./.github/actions/notify-slack
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK }}
          status: ${{ job.status }}
          job-name: '${{ inputs.app-name }} デプロイ (${{ inputs.environment }})'

2. パラメータ化による柔軟性の確保

モジュールを作成する際は、適切なパラメータを設定することで、様々な状況に対応できるようにします。

# .github/actions/build-and-push/action.yml
name: 'Docker ビルド & プッシュ'
description: 'Dockerイメージのビルドとレジストリへのプッシュ'

inputs:
  dockerfile:
    description: 'Dockerfileのパス'
    required: false
    default: './Dockerfile'
  
  context:
    description: 'ビルドコンテキスト'
    required: false
    default: '.'
  
  registry:
    description: 'コンテナレジストリURL'
    required: true
  
  image-name:
    description: 'イメージ名'
    required: true
  
  tags:
    description: 'イメージタグ (カンマ区切り)'
    required: false
    default: 'latest'
  
  build-args:
    description: 'ビルド引数 (KEY=VALUE形式、改行区切り)'
    required: false
  
  push:
    description: 'レジストリにプッシュするか'
    required: false
    default: 'true'

outputs:
  image-digest:
    description: 'ビルドしたイメージのダイジェスト'
    value: ${{ steps.build.outputs.digest }}

runs:
  using: 'composite'
  steps:
    - name: Dockerビルドセットアップ
      uses: docker/setup-buildx-action@v3
      
    - name: イメージビルド
      id: build
      uses: docker/build-push-action@v5
      with:
        context: ${{ inputs.context }}
        file: ${{ inputs.dockerfile }}
        push: ${{ inputs.push }}
        tags: ${{ inputs.registry }}/${{ inputs.image-name }}:${{ inputs.tags }}
        build-args: ${{ inputs.build-args }}
        cache-from: type=gha
        cache-to: type=gha,mode=max

ベストプラクティス

1. 命名規則の統一

  • アクション名: 動詞-名詞形式(例:setup-node, build-docker, deploy-app)
  • 入力パラメータ: ケバブケース(例:node-version, database-type)
  • 出力: 結果を表す名詞(例:test-result, image-digest)

2. ドキュメント化の徹底

# README.mdの例
# Setup Node.js Action

## 概要
Node.js環境のセットアップと依存関係のインストールを行います。

## 使用方法
```yaml
- name: Node.js環境構築
  uses: ./.github/actions/setup-node
  with:
    node-version: '18'        # Node.jsバージョン (デフォルト: 18)
    package-manager: 'npm'    # パッケージマネージャー (npm, yarn, pnpm)
    cache-key: 'my-cache'     # キャッシュキー (デフォルト: auto-generated)
```

## 入力パラメータ
| パラメータ | 必須 | デフォルト | 説明 |
|------------|------|------------|------|
| node-version | いいえ | '18' | 使用するNode.jsのバージョン |
| package-manager | いいえ | 'npm' | パッケージマネージャー (npm/yarn/pnpm) |
| cache-key | いいえ | auto | キャッシュキー |

## 出力
| 出力名 | 説明 |
|--------|------|
| cache-hit | キャッシュがヒットした場合はtrue |

3. バージョン管理戦略

  • セマンティックバージョニングを採用
  • メジャーバージョンのタグを並行で管理
  • 変更ログの維持
# バージョンタグの例
git tag v1.0.0    # 初回リリース
git tag v1        # メジャーバージョンタグ

git tag v1.1.0    # 機能追加
git tag v1        # メジャーバージョンタグ更新

git tag v2.0.0    # 破壊的変更
git tag v2        # 新しいメジャーバージョンタグ

4. エラーハンドリング

# エラーハンドリングの例
- name: テスト実行
  id: test
  run: |
    set -e  # エラー時に即座に終了
    
    if ! npm test; then
      echo "テストが失敗しました"
      echo "::error::テスト実行中にエラーが発生しました"
      exit 1
    fi
    
    echo "::notice::すべてのテストが正常に完了しました"
    echo "result=success" >> $GITHUB_OUTPUT
  shell: bash
  
- name: エラー時の通知
  if: failure()
  uses: ./.github/actions/notify-slack
  with:
    webhook-url: ${{ secrets.SLACK_WEBHOOK }}
    status: 'failure'
    job-name: 'テスト実行'

まとめ

GitHub Actionsのモジュール化は、開発チームの生産性を大幅に向上させる重要な技術です。この記事で紹介した手法を参考に、以下のステップで段階的にモジュール化を進めてください:

  1. 現在のワークフローを分析し、重複する処理を特定する
  2. 複合アクションから始めて、基本的なモジュール化を実践する
  3. 再利用可能ワークフローで複雑な処理を標準化する
  4. 外部アクションで組織全体での共有を実現する
  5. 継続的な改善でモジュールの品質を向上させる

適切なモジュール化により、コードの品質向上、開発速度の向上、メンテナンスコストの削減を実現し、より効率的なCI/CDパイプラインを構築できるでしょう。

次回は、実際のプロジェクトでのモジュール化事例や、より高度なテクニックについて詳しく解説する予定です。

タイトルとURLをコピーしました