ゼロから始めるGitHub Actions CI/CDガイド:開発を楽にする自動化

Development tutorial - IT technology blog
Development tutorial - IT technology blog

「手動デプロイ」の苦痛と「あれ、自分のマシンでは動いたのに?」というセリフ

開発者の皆さんなら、こんな光景に見覚えがあるでしょう。渾身の機能をコーディングし、自分のマシンで入念にテストした後、自信満々にgit pushコマンドを打ち込む。数分後、テストチームや上司から「アプリが落ちたよ」「ABC機能が動かない」といったメッセージが次々と届く…。その時の気持ちは、言葉では言い表せません。

私が初めて参加したウェブアプリのプロジェクトのことは、今でも鮮明に覚えています。チームは5人の開発者で、コードは絶えずdevelopブランチにマージされていました。週末のリリースは毎回「息をのむ」ような作業でした。デプロイ担当者はサーバーにログインしgit pullを実行し、一連のビルドコマンドを打ち、サービスを再起動する必要がありました。環境変数を一つ間違えるだけで、システム全体が「おじゃん」になる可能性があったのです。デプロイには緊張の30〜45分がかかり、そして何より、夜もぐっすり眠れませんでした。

これこそが自動化の欠如がもたらす代償です。時間の浪費、精神的な消耗、そして避けられたはずのヒューマンエラーによるリスクに満ちています。

なぜこんなに苦労するのか?問題の根本原因を分析

より深く見ていくと、これらの「苦痛」は主に3つの原因から生じています。

  • 分断され、一貫性のないプロセス: 開発者それぞれに異なる習慣があります。プッシュ前にテストを実行する人もいれば、しない人もいる。npmを使う人もいれば、yarnを好む人もいて、ロックファイル(package-lock.json vs yarn.lock)が同期されません。共通の基準がなければ、コンフリクトやエラーは避けられません。
  • 自動テストの欠如: テストの実行が各個人の意識に依存してしまいます。プロジェクトが切迫していると、このステップはしばしば省略されます。その結果、バグのあるコードがメインブランチに直接マージされてしまいます。後の段階でバグを発見し修正するコストは、はるかに高くなります。
  • 環境の差異(Environment Drift) 「自分のマシンでは動いたのに」というセリフは、まさにこの結果です。開発者のローカル環境(例:MacOSとNode v18)が、本番環境(例:UbuntuとNode v16)と大きく異なることがあります。ライブラリのバージョン、環境変数、OSの設定の違いが、無数の予期せぬエラーの原因となります。

よく使われる「対策」(そして、なぜそれらが不十分なのか)

このような状況に対処するため、チームはいくつかの方法をよく採用します。

方法1:「デプロイ担当者」を任命する
一人の人物(通常はリーダーやDevOps担当者)がすべてのデプロイに責任を持つ方法です。これにより一貫性は多少保たれますが、その人が「バス係数(bus factor)」No.1になってしまいます。その人が忙しかったり休暇を取ったりすると、すべてが滞ってしまいます。ヒューマンエラーのリスクも依然として残ります。

方法2:デプロイスクリプトを自作する(例:`deploy.sh`)
これは一歩進んだ方法です。デプロイに必要なコマンドを含むスクリプトファイルを作成します。必要な時にそのファイルを実行するだけです。

#!/bin/bash

echo "🚀 デプロイを開始します..."

# 新しいバージョンをプル
git pull origin main

# 依存関係をインストール
npm install

# プロジェクトをビルド
npm run build

# pm2でサーバーを再起動
pm2 restart my-app

echo "✅ デプロイが完了しました!"

進歩はしていますが、まだ弱点があります。誰が、いつこのスクリプトを実行するのでしょうか?APIキーやパスワードをどう安全に管理しますか?そして、チーム全員がデプロイ履歴をどうやって確認するのでしょうか?

究極の解決策:GitHub ActionsによるCI/CD

ここでCI/CDが真価を発揮します。CI/CDはプロセス全体を自動化し、一貫性のある安全なワークフローを構築することで、これらの問題を根本的に解決します。

CI/CDとは?簡単な解説

  • CI(Continuous Integration – 継続的インテグレーション): コードをプッシュするたびに、ボットが自動的にコードを取得し、ビルドし、すべてのテストを実行するのを想像してみてください。目的は、バグを即座に検出し、新しいコードが正常に動作しているものを壊さないようにすることです。
  • CD(Continuous Deployment/Delivery – 継続的デプロイメント/デリバリー): CIが成功(コードがすべてのテストに合格)した後、ボットは指定された環境にコードを自動的にデプロイします。これは、テストチームがレビューするためのステージング環境かもしれませんし、エンドユーザーが使用する本番環境かもしれません。

そしてGitHub Actionsは、GitHub「製」のCI/CDツールであり、強力で、パブリックプロジェクトでは無料で利用でき、使い慣れたGitHubのインターフェースに直接統合されています。

実践:Node.jsプロジェクトで初のCI/CDワークフローを構築する

理論はもう十分です。早速始めましょう。ここでは、シンプルなCIワークフローを作成する方法を説明します。新しいコードがmainブランチにプッシュされるたびに、GitHub Actionsが自動的に環境をセットアップし、依存関係をインストールし、ユニットテストを実行します。

ステップ1:ワークフロー用のディレクトリとファイルを作成する

プロジェクトのルートディレクトリに、.github/workflows/というディレクトリ構造を作成します。`workflows`ディレクトリの中に、ci.ymlという名前のファイルを作成してください。

ステップ2:YAMLで自動化スクリプトを記述する

ci.ymlファイルを開き、以下のコードを貼り付けます。

name: Node.js CI

# トリガー: "main" ブランチへの push または pull request 時に実行
on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build_and_test:
    # GitHubが提供する最新版のUbuntu仮想マシン上で実行
    runs-on: ubuntu-latest

    strategy:
      matrix:
        # 超強力な機能:複数のNode.jsバージョンでテストを並行実行
        node-version: [16.x, 18.x, 20.x]

    steps:
    # ステップ1:リポジトリから仮想マシン(ランナー)にコードをチェックアウト
    - name: Checkout repository
      uses: actions/checkout@v4

    # ステップ2:matrixに応じたNode.jsバージョンをセットアップ
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v4
      with:
        node-version: ${{ matrix.node-version }}
        # ヒント:キャッシュを有効にして、次回の実行時に依存関係のインストールを高速化
        cache: 'npm'

    # ステップ3:ライブラリをインストール(安全性と速度のためnpm ciを使用)
    - name: Install dependencies
      run: npm ci

    # ステップ4:ビルドコマンドを実行(package.jsonに存在する場合)
    - name: Build project
      run: npm run build --if-present

    # ステップ5:ユニットテストを実行
    - name: Run tests
      run: npm test

ステップ3:スクリプトの分析

  • name: ワークフローの名前。GitHubのActionsタブに表示されます。
  • on: トリガー。mainブランチへのpushまたはpull_requestが作成・更新されたときにワークフローが実行されます。
  • jobs: 実行するジョブのコンテナ。ワークフローは1つまたは複数のジョブを持つことができます。
  • build_and_test: 私たちのジョブの名前です。
  • runs-on: ジョブを実行する仮想マシンのOSを指定します。<a href="https://itfromzero.com/ja/linux/ubuntu-ja-linux/fail2ban%e3%81%a7ubuntu%e3%82%b5%e3%83%bc%e3%83%90%e3%83%bc%e3%82%92%e3%83%96%e3%83%ab%e3%83%bc%e3%83%88%e3%83%95%e3%82%a9%e3%83%bc%e3%82%b9%e6%94%bb%e6%92%83%e3%81%8b%e3%82%89%e5%ae%88%e3%82%8b.html">ubuntu-latest</a>が最も一般的な選択肢です。
  • strategy.matrix: 複数の環境でテストするための超強力な機能。このジョブは複製され、Node.jsの3つのバージョン(16.x, 18.x, 20.x)で並行して実行され、コードの幅広い互換性を保証します。
  • steps: ジョブが順次実行するステップ。
  • uses: コミュニティから提供されている既存の「アクション」を利用して、「車輪の再発明」を避けます。actions/checkout@v4actions/setup-node@v4は最も人気のあるアクションで、GitHub自身によってメンテナンスされています。
  • run: 仮想マシン上でコマンドライン命令を実行します。

これで、コードをプッシュするたびに、GitHubリポジトリの「Actions」タブに移動してみてください。ワークフローが実行されているのが見えるはずです。いずれかのステップでエラーが発生した場合(例:テストの失敗)、ワークフローは赤く表示され、すぐにメールで通知が届きます。

「痛い」経験から学ぶヒントとベストプラクティス

  1. APIキーやトークンを絶対に保護する: 機密情報を決してハードコードしないでください。これは深刻なセキュリティ上の欠陥ですGitHub Secretsを使いましょう。Settings > Secrets and variables > Actionsに移動し、新しいシークレット(例:DATABASE_URL)を作成します。ワークフローでは、env: DATABASE_URL: ${{ secrets.DATABASE_URL }}`を介して安全に呼び出せます。このデータは暗号化され、ログに表示されることはありません。
  2. npm installの代わりにnpm ciを使う: npm cipackage-lock.jsonファイルにあるバージョンを正確にインストールするため、CI環境が開発者の環境と完全に一致することを保証します。また、自動化プロセスにとってより速く、より安全です。
  3. キャッシュを活用して高速化する: 例のようにwith: cache: 'npm'を使用すると、ダウンロードされたライブラリ(node_modulesディレクトリ)が保存されます。次回実行時にpackage-lock.jsonファイルに変更がなければ、GitHub Actionsは最初から再ダウンロードする代わりにキャッシュを復元します。大規模なプロジェクトでは、このステップで30秒から数分節約できることがあります。
  4. CIとCDを分離する: 各段階(例:testbuilddeploy)に個別のjobを使用します。needs: [test, build]キーワードを使って、deployジョブがtestbuildジョブが100%成功した場合にのみトリガーされるようにします。

5人の開発者がいたプロジェクトの話に戻りましょう。CI/CDを導入した後、デプロイ時間は緊張の30分から、約5分の完全自動化されたプロセスに短縮されました。さらに重要なのは、チーム全体が自信を深め、コードをプッシュすることがシステムをクラッシュさせるのではないかという恐怖がなくなったことです。シンプルなYAMLファイル一つで、時間を節約できるだけでなく、自動化され、安全で、透明性の高い、強固なDevOps文化の基盤を築くことができるのです。

Share: