T-CREATOR

GitHub Actions で実現する「宣言的自動化」:手作業を消す設計思考

GitHub Actions で実現する「宣言的自動化」:手作業を消す設計思考

「また手作業でデプロイするの?」「テストを手動で実行し忘れた…」そんな経験はありませんか。開発現場では、コードを書く時間よりも手作業に費やす時間の方が多いことがあります。GitHub Actions を使えば、こうした手作業を「宣言的」に記述するだけで自動化できるんです。

本記事では、GitHub Actions を使った「宣言的自動化」の考え方と、手作業をなくすための設計思考について解説します。宣言的自動化とは、「何をすべきか」を記述するだけで、「どうやるか」の詳細は任せるアプローチです。この考え方を身につければ、複雑な手作業も YAML ファイル一つで解決できるようになりますよ。

背景

従来の手作業による開発プロセス

ソフトウェア開発では、コーディング以外にも多くの作業が発生します。テスト実行、コードレビュー、デプロイ、ドキュメント生成など、これらの作業を手動で行うと時間がかかるだけでなく、ミスも起こりやすくなるんです。

特にチーム開発では、作業手順が属人化しやすく、「あの人しかデプロイできない」といった状況に陥りがちでした。また、手順書を作成しても、それが最新の状態に保たれているとは限りません。こうした問題を解決するために、CI/CD(継続的インテグレーション/継続的デリバリー)の概念が広まりました。

宣言的アプローチの台頭

近年、Infrastructure as Code(IaC)や Kubernetes の普及により、「宣言的」なアプローチが注目されています。これは、「最終的にこうあるべき」という状態を宣言すれば、システムが自動的にその状態を実現してくれる考え方ですね。

以下の図は、従来の手続き型アプローチと宣言的アプローチの違いを示しています。

mermaidflowchart LR
  procedural["手続き型<br/>アプローチ"] -->|"ステップ1を実行"| step1["処理 A"]
  step1 -->|"ステップ2を実行"| step2["処理 B"]
  step2 -->|"ステップ3を実行"| step3["処理 C"]

  declarative["宣言的<br/>アプローチ"] -->|"最終状態を宣言"| desired["望ましい<br/>状態"]
  desired -->|"自動判断"| system["システムが<br/>自動実行"]

手続き型では「どう実現するか」の詳細な手順を記述する必要がありましたが、宣言的アプローチでは「何を実現したいか」を記述するだけで済みます。これにより、実装の詳細を気にせずに、本質的な要求に集中できるんです。

GitHub Actions の登場

GitHub Actions は、2019 年に正式リリースされた GitHub 公式の CI/CD プラットフォームです。YAML 形式で「何をしたいか」を宣言するだけで、テストやデプロイなどの自動化を実現できます。

従来の CI/CD ツール(Jenkins、CircleCI など)と比較すると、GitHub Actions には以下の特徴があります。

#特徴説明
1GitHub ネイティブリポジトリと完全に統合されており、別途サーバーを用意する必要がない
2マーケットプレイス12,000 以上の再利用可能なアクションが公開されている
3マトリックスビルド複数の OS・バージョンでの並列テストが簡単
4イベント駆動プルリクエスト、Issue、コメントなど多様なトリガーに対応

これらの特徴により、GitHub Actions は「宣言的自動化」を実現する理想的なプラットフォームとなっています。

課題

手作業が引き起こす問題

開発現場における手作業には、以下のような課題があります。

作業の属人化

「デプロイは田中さんしかできない」「本番環境への反映手順を知っているのは一部の人だけ」といった状況は、多くのチームで見られる問題です。作業者が休暇を取ったり退職したりすると、業務が止まってしまうリスクがありますね。

手順書を作成しても、実際の作業と乖離していくため、結局は「詳しい人に聞く」ことになってしまいます。

ヒューマンエラーの発生

人間が手動で作業を行う限り、ミスは避けられません。特に以下のようなミスが頻発します。

  • テストを実行し忘れる
  • 間違ったブランチをデプロイする
  • 環境変数の設定を間違える
  • バージョンタグの付け忘れ

これらのミスは、深刻な本番障害につながる可能性があるんです。

時間とコストの浪費

手作業には想像以上に時間がかかります。たとえば、以下のような作業を毎回手動で行っているとしましょう。

typescript// 手動で行う典型的な作業フロー
// 1. コードをプルする(1分)
// 2. 依存関係をインストールする(3分)
// 3. テストを実行する(5分)
// 4. ビルドを実行する(2分)
// 5. デプロイコマンドを実行する(1分)
// 6. 動作確認をする(5分)
// 合計: 17分 × 日5回 = 85分/

1 日に 5 回デプロイするとして、毎日約 1.5 時間を手作業に費やしていることになります。年間で計算すると、約 365 時間、つまり 45 営業日分もの時間です。

以下の図は、手作業が引き起こす問題の連鎖を示しています。

mermaidflowchart TD
  manual["手作業による<br/>開発プロセス"] -->|"発生"| issues["複数の課題"]

  issues --> attr["属人化"]
  issues --> error["ヒューマン<br/>エラー"]
  issues --> time["時間の浪費"]

  attr --> risk1["人がいないと<br/>作業できない"]
  error --> risk2["本番障害の<br/>リスク増大"]
  time --> risk3["開発速度の<br/>低下"]

  risk1 --> impact["ビジネス<br/>インパクト"]
  risk2 --> impact
  risk3 --> impact

既存の自動化における課題

一部のチームでは既に自動化を導入していますが、以下のような課題があります。

手続き型スクリプトの複雑化

Bash スクリプトや Python スクリプトで自動化を実装すると、「どうやるか」の詳細をすべて記述する必要があります。これにより、スクリプトが複雑化し、メンテナンスが困難になるんです。

bash# 手続き型のデプロイスクリプト例(複雑になりがち)
#!/bin/bash

# 変数設定
ENV=$1
BRANCH=$2

# エラーチェック
if [ -z "$ENV" ]; then
  echo "Error: Environment not specified"
  exit 1
fi

# ブランチ切り替え
git checkout $BRANCH
if [ $? -ne 0 ]; then
  echo "Error: Failed to checkout branch"
  exit 1
fi

# 依存関係インストール
yarn install
if [ $? -ne 0 ]; then
  echo "Error: Failed to install dependencies"
  exit 1
fi

# ...さらに処理が続く

このようなスクリプトは、エラーハンドリング、ログ出力、条件分岐などが入り混じり、可読性が低下します。

CI/CD ツールの学習コスト

Jenkins などの従来の CI/CD ツールは、セットアップや設定に専門知識が必要でした。サーバーの管理、プラグインのインストール、セキュリティ設定など、自動化のための作業自体が負担になっていたんです。

再利用性の低さ

個別にスクリプトを作成すると、他のプロジェクトで再利用しにくくなります。結果として、似たような処理を何度も書くことになり、DRY(Don't Repeat Yourself)原則に反してしまいます。

解決策

宣言的自動化の原則

GitHub Actions を使った宣言的自動化では、以下の原則に従います。

原則 1:What(何を)を宣言し、How(どう)は任せる

「テストを実行したい」「デプロイしたい」という目的を YAML で宣言すれば、具体的な実行方法は GitHub Actions に任せます。これにより、実装の詳細から解放されるんです。

yaml# 宣言的なアプローチ
name: CI

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: yarn test

上記の例では、「push されたらテストを実行する」という意図を宣言しているだけです。どのように環境をセットアップするか、どのようにコードをチェックアウトするかといった詳細は、アクションに任せています。

原則 2:再利用可能なアクションを活用する

GitHub Marketplace には、12,000 以上のアクションが公開されています。車輪の再発明をせず、既存のアクションを組み合わせることで、効率的に自動化を実現できますよ。

#アクション名用途
1actions/checkoutリポジトリのコードをチェックアウト
2actions/setup-nodeNode.js 環境のセットアップ
3docker/build-push-actionDocker イメージのビルドとプッシュ
4aws-actions/configure-aws-credentialsAWS 認証情報の設定

原則 3:イベント駆動で自動化する

GitHub Actions は、イベント駆動型のアーキテクチャを採用しています。「プルリクエストが作成されたら」「特定のラベルが付けられたら」といったイベントをトリガーに、自動的に処理を実行できます。

mermaidflowchart LR
  events["GitHubイベント"] --> pr["プルリクエスト<br/>作成"]
  events --> push["コードプッシュ"]
  events --> issue["Issue作成"]
  events --> schedule["スケジュール"]

  pr --> workflow1["テスト実行<br/>ワークフロー"]
  push --> workflow2["デプロイ<br/>ワークフロー"]
  issue --> workflow3["自動ラベリング<br/>ワークフロー"]
  schedule --> workflow4["定期バックアップ<br/>ワークフロー"]

この図は、さまざまな GitHub イベントが対応するワークフローをトリガーする仕組みを示しています。

GitHub Actions の基本構造

GitHub Actions のワークフローは、以下の階層構造で構成されます。

yaml# ワークフロー全体の名前
name: CI/CD Pipeline

# トリガー条件(いつ実行するか)
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

ワークフロー名とトリガー条件を宣言します。この例では、main ブランチへのプッシュまたはプルリクエストが作成されたときに実行されます。

yaml# ジョブの定義
jobs:
  build:
    # 実行環境
    runs-on: ubuntu-latest

    # 実行ステップ
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

ジョブと実行環境を定義します。runs-on で実行環境を指定し、steps で具体的な処理を列挙します。

yaml- name: Setup Node.js
  uses: actions/setup-node@v4
  with:
    node-version: '20'

- name: Install dependencies
  run: yarn install

- name: Run tests
  run: yarn test

各ステップでは、既存のアクション(uses)を利用するか、コマンド(run)を実行します。

マトリックス戦略による並列化

複数の環境でテストを実行したい場合、マトリックス戦略を使うと簡単に並列化できます。

yamljobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20, 22]

この設定により、3 つの OS × 3 つの Node.js バージョン = 9 パターンのテストが自動的に並列実行されます。手続き型のスクリプトでこれを実現しようとすると、複雑なループ処理が必要になりますが、宣言的アプローチではこのように簡潔に記述できるんです。

yamlsteps:
  - uses: actions/checkout@v4
  - name: Setup Node.js ${{ matrix.node-version }}
    uses: actions/setup-node@v4
    with:
      node-version: ${{ matrix.node-version }}
  - run: yarn install
  - run: yarn test

環境変数とシークレット管理

機密情報は、GitHub Secrets を使って安全に管理します。YAML ファイルには機密情報を直接書かず、参照だけを記述するんです。

yamljobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

${{ secrets.AWS_ACCESS_KEY_ID }} のように、Secrets を参照することで、認証情報を安全に扱えます。

yaml- name: Deploy to S3
  run: |
    aws s3 sync ./build s3://${{ secrets.S3_BUCKET_NAME }} --delete

環境変数やシークレットを活用することで、環境ごとに異なる設定を柔軟に管理できますよ。

具体例

ケース 1:プルリクエスト時の自動テストとレビュー

プルリクエストが作成されたときに、自動的にテストを実行し、コードの品質チェックを行うワークフローを作成します。

ワークフローファイルの配置

GitHub Actions のワークフローファイルは、リポジトリの .github​/​workflows​/​ ディレクトリに配置します。

yaml# .github/workflows/pr-check.yml
name: Pull Request Check

on:
  pull_request:
    branches:
      - main
      - develop
    types:
      - opened
      - synchronize
      - reopened

この設定により、main または develop ブランチへのプルリクエストが開かれたとき、更新されたとき、再オープンされたときにワークフローが実行されます。

テストジョブの定義

次に、実際にテストを実行するジョブを定義します。

yamljobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'yarn'

cache: 'yarn' を指定することで、依存関係のキャッシュが自動的に行われ、ビルド時間が短縮されます。

yaml- name: Install dependencies
  run: yarn install --frozen-lockfile

- name: Run lint
  run: yarn lint

- name: Run type check
  run: yarn type-check

- name: Run tests
  run: yarn test --coverage

リント、型チェック、テストを順番に実行します。--frozen-lockfile オプションを使用することで、lock ファイルが変更されないことを保証できますね。

テストカバレッジのレポート

テストカバレッジを自動的にプルリクエストにコメントするには、専用のアクションを活用します。

yaml- name: Upload coverage to Codecov
  uses: codecov/codecov-action@v4
  with:
    token: ${{ secrets.CODECOV_TOKEN }}
    files: ./coverage/coverage-final.json
    flags: unittests
    name: codecov-umbrella

これにより、カバレッジレポートが自動的に生成され、プルリクエストのコメントとして表示されます。

ケース 2:main ブランチへのマージ時の自動デプロイ

プルリクエストがマージされて main ブランチに統合されたら、自動的に本番環境へデプロイするワークフローを作成します。

デプロイワークフローの定義

yaml# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com

environment を指定することで、デプロイ環境を管理し、デプロイ履歴を GitHub 上で確認できます。

ビルドとテスト

デプロイ前に、もう一度テストを実行して安全性を確保します。

yamlsteps:
  - name: Checkout code
    uses: actions/checkout@v4

  - name: Setup Node.js
    uses: actions/setup-node@v4
    with:
      node-version: '20'
      cache: 'yarn'

  - name: Install dependencies
    run: yarn install --frozen-lockfile

  - name: Run tests
    run: yarn test

テストが成功した場合のみ、次のステップに進みます。

Docker イメージのビルドとプッシュ

アプリケーションを Docker コンテナ化してデプロイする場合、以下のように記述します。

yaml- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3

- name: Login to Docker Hub
  uses: docker/login-action@v3
  with:
    username: ${{ secrets.DOCKER_USERNAME }}
    password: ${{ secrets.DOCKER_PASSWORD }}

Docker Buildx をセットアップし、Docker Hub にログインします。

yaml- name: Build and push
  uses: docker/build-push-action@v5
  with:
    context: .
    push: true
    tags: |
      myapp/api:latest
      myapp/api:${{ github.sha }}
    cache-from: type=registry,ref=myapp/api:latest
    cache-to: type=inline

イメージをビルドし、Docker Hub にプッシュします。コミット SHA をタグとして付与することで、どのバージョンがデプロイされているか追跡できますよ。

AWS へのデプロイ

AWS ECS にデプロイする場合の例です。

yaml- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
    aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    aws-region: ap-northeast-1

- name: Deploy to ECS
  run: |
    aws ecs update-service \
      --cluster production-cluster \
      --service api-service \
      --force-new-deployment

AWS 認証情報を設定し、ECS サービスを更新します。これにより、新しいイメージがデプロイされます。

ケース 3:定期的なバックアップと監視

スケジュールトリガーを使って、定期的なバックアップや監視タスクを自動化できます。

yaml# .github/workflows/backup.yml
name: Daily Backup

on:
  schedule:
    # 毎日午前2時(UTC)に実行
    - cron: '0 2 * * *'
  workflow_dispatch: # 手動実行も可能

jobs:
  backup:
    runs-on: ubuntu-latest

    steps:
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

schedule でクーロン形式のスケジュールを指定します。workflow_dispatch を追加すると、必要に応じて手動でもワークフローを実行できますね。

yaml- name: Backup database
  run: |
    TIMESTAMP=$(date +%Y%m%d_%H%M%S)
    aws rds create-db-snapshot \
      --db-instance-identifier production-db \
      --db-snapshot-identifier backup-$TIMESTAMP

- name: Notify Slack
  uses: slackapi/slack-github-action@v1
  with:
    webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
    payload: |
      {
        "text": "Database backup completed: backup-${{ env.TIMESTAMP }}"
      }

データベースのスナップショットを作成し、Slack に通知を送信します。

ケース 4:複数ジョブの依存関係管理

複雑なワークフローでは、ジョブ間の依存関係を宣言的に管理できます。

yaml# .github/workflows/complex-pipeline.yml
name: Complex Pipeline

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: yarn install
      - run: yarn build
      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/

まず、ビルドジョブでアプリケーションをビルドし、成果物をアップロードします。

yamltest:
  needs: build
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - name: Download build artifacts
      uses: actions/download-artifact@v4
      with:
        name: build-output
        path: dist/
    - run: yarn test

テストジョブは needs: build により、ビルドジョブの完了後に実行されます。ビルド成果物をダウンロードして、テストを実行するんです。

yamldeploy:
  needs: [build, test]
  runs-on: ubuntu-latest
  if: github.ref == 'refs/heads/main'
  steps:
    - uses: actions/checkout@v4
    - name: Download build artifacts
      uses: actions/download-artifact@v4
      with:
        name: build-output
        path: dist/
    - name: Deploy
      run: yarn deploy

デプロイジョブは、ビルドとテストの両方が成功した場合のみ実行されます。if 条件により、main ブランチの場合のみデプロイが行われますよ。

以下の図は、ジョブ間の依存関係を示しています。

mermaidflowchart TD
  trigger["プッシュ<br/>イベント"] --> build["Build<br/>ジョブ"]

  build -->|"成功"| test["Test<br/>ジョブ"]
  build -->|"アーティファクト<br/>アップロード"| artifacts["ビルド<br/>成果物"]

  test -->|"成功 + main"| deploy["Deploy<br/>ジョブ"]
  artifacts -->|"ダウンロード"| test
  artifacts -->|"ダウンロード"| deploy

  build -->|"失敗"| end_fail["ワークフロー<br/>失敗"]
  test -->|"失敗"| end_fail
  deploy -->|"成功"| end_success["ワークフロー<br/>成功"]

この図から、ビルドが失敗した場合は後続のジョブが実行されないこと、テストが成功した場合のみデプロイが実行されることがわかります。

ケース 5:条件分岐とエラーハンドリング

実際の運用では、エラーが発生した場合の処理も重要です。GitHub Actions では、条件分岐とエラーハンドリングを宣言的に記述できます。

yamljobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy
        id: deploy
        continue-on-error: true
        run: |
          yarn deploy
          echo "status=success" >> $GITHUB_OUTPUT

continue-on-error: true を指定すると、このステップが失敗してもワークフローが続行されます。id を付与することで、後続のステップから参照できますね。

yaml- name: Rollback on failure
  if: steps.deploy.outcome == 'failure'
  run: |
    echo "Deployment failed, rolling back..."
    yarn rollback

- name: Notify success
  if: steps.deploy.outcome == 'success'
  uses: slackapi/slack-github-action@v1
  with:
    webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
    payload: '{"text": "Deployment succeeded!"}'

if 条件により、デプロイの結果に応じて異なる処理を実行します。失敗した場合はロールバック、成功した場合は Slack に通知を送信するんです。

yaml- name: Always cleanup
  if: always()
  run: |
    echo "Cleaning up temporary files..."
    rm -rf tmp/

if: always() を指定すると、前のステップの成否に関わらず常に実行されます。クリーンアップ処理などに便利ですよ。

まとめ

GitHub Actions を使った宣言的自動化により、開発プロセスから手作業を排除できます。本記事で解説した内容をまとめましょう。

宣言的自動化の本質

宣言的自動化の核心は、「何をしたいか(What)」を記述し、「どうやるか(How)」の詳細は任せることです。これにより、複雑な実装から解放され、本質的な要求に集中できます。YAML ファイル一つで、テスト、ビルド、デプロイといった一連のプロセスを自動化できるんです。

得られるメリット

GitHub Actions による宣言的自動化を導入すると、以下のメリットが得られます。

#メリット詳細
1属人化の解消誰でもワークフローを理解・実行できる
2ヒューマンエラーの削減手作業によるミスがなくなる
3開発速度の向上自動化により時間を本質的な開発に使える
4品質の向上常に同じ手順でテスト・デプロイが実行される
5透明性の向上処理の履歴が GitHub 上で確認できる

設計思考のポイント

手作業を宣言的自動化に変換する際は、以下のポイントを意識しましょう。

まず、現在の手作業を洗い出し、自動化できる部分を特定します。すべてを一度に自動化しようとせず、影響範囲が小さく、繰り返し頻度が高いタスクから始めるのがおすすめです。

次に、既存のアクションを積極的に活用しましょう。GitHub Marketplace には多様なアクションが公開されており、車輪の再発明を避けられます。

そして、イベント駆動で考えることが重要です。「いつ」「何が起きたら」「何をすべきか」を明確にすることで、適切なトリガーとワークフローを設計できますよ。

次のステップ

本記事で解説した内容を実践するには、まず小さなワークフローから始めることをおすすめします。たとえば、プルリクエスト時の自動テストから始めて、徐々にデプロイや定期タスクへと拡張していくと良いでしょう。

また、チーム全体でワークフローを共有し、改善を続けることも大切です。GitHub Actions のワークフローファイルはコードとして管理されるため、プルリクエストを通じてレビューや改善ができます。

宣言的自動化は、単なる技術的な手法ではなく、開発プロセス全体を見直す設計思考です。「この作業は本当に必要か」「自動化できないか」と常に問い続けることで、より効率的で品質の高い開発が実現できるんです。

ぜひ、GitHub Actions を活用して、手作業から解放された開発体験を手に入れてください。

関連リンク