はじめに
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)
ワークフローを実行するサーバー
最初のワークフロー
# .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!"
ワークフローの実行フロー
実践演習 6.1
基本的なワークフローの作成:
.github/workflows
ディレクトリを作成- 上記の Hello World ワークフローを作成
- mainブランチにプッシュして実行を確認
- Actions タブで実行結果を確認
- 手動実行(workflow_dispatch)を試す
6.2 ワークフロー構文の詳細
イベントトリガーの詳細
基本的なイベント
# 単一イベント
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
高度なイベントフィルタリング
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'
ジョブの高度な設定
並列実行と依存関係
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..."
マトリックスビルド
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
環境変数とシークレット
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ではシークレットは利用不可
- 最小権限の原則に従う
条件付き実行
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
# .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 コンテナのビルドとプッシュ
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パイプラインの構築:
- サンプルのNode.jsプロジェクトを作成
- テスト、リント、ビルドスクリプトを追加
- 上記のCI/CDワークフローを適用
- プルリクエストを作成してCIの動作を確認
- 環境変数とシークレットを設定
- ビルドアーティファクトの生成と保存を確認
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 - アクションのメタデータ
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 - アクションの実装
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
{
"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"
}
}
複合アクションの作成
# 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/
アクションの公開と使用
マーケットプレイスへの公開手順
- アクションのリポジトリを作成
action.yml
またはaction.yaml
をルートに配置- 適切なREADME.mdを作成(使用例を含む)
- セマンティックバージョンのタグを作成(v1.0.0)
- GitHubでリリースを作成
- 「Publish this Action to the GitHub Marketplace」にチェック
アクションの使用例
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 高度なワークフローパターン
再利用可能なワークフロー
共通ワークフローの定義
# .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 }}
ワークフローの呼び出し
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 }}
動的なマトリックス生成
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 環境保護
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
高度なワークフローの実装:
- カスタムJavaScriptアクションを作成
- 複合アクションを作成して既存ワークフローを簡素化
- 再利用可能なワークフローを定義
- 動的マトリックスを使用したテストを実装
- 複数環境への並行デプロイを設定
- エラーハンドリングとリトライロジックを追加
6.6 まとめと次のステップ
この章で学んだこと
- GitHub Actionsの基本概念と構成要素
- ワークフロー構文の詳細(イベント、ジョブ、ステップ)
- 実践的なCI/CDパイプラインの構築
- カスタムアクションの作成(JavaScript、複合)
- 高度なワークフローパターン(再利用、動的マトリックス)
GitHub Actions チェックリスト
ワークフロー設計
- 適切なイベントトリガーの選択
- ジョブの依存関係の定義
- 条件付き実行の活用
- シークレットの適切な管理
- アーティファクトの保存戦略
パフォーマンス最適化
- キャッシュの活用
- 並列実行の最適化
- 不要なステップの削除
- セルフホストランナーの検討
セキュリティ
- 最小権限の原則
- サードパーティアクションの検証
- シークレットスキャンの有効化
- OIDC認証の活用
理解度チェック
確認問題
- GitHub Actionsの主要な構成要素を5つ挙げ、それぞれの役割を説明してください
- プッシュイベントとプルリクエストイベントの違いは何ですか?
- マトリックスビルドはどのような場面で有効ですか?
- カスタムアクションの3つのタイプとその特徴を説明してください
- ワークフローのセキュリティを向上させる方法を3つ挙げてください
次章への準備
第7章では、これまでの知識を統合し、Claude Codeを活用したAI支援開発について学びます。 GitHub ActionsとAIを組み合わせることで、さらに高度な自動化が可能になります。
- GitHub Actions Marketplaceで人気のアクションを調査
- 自分のプロジェクトに合わせたワークフローを作成
- コスト最適化(実行時間の短縮)を意識
- セキュリティベストプラクティスの実践