GitHub Actions を利用した本番環境適用(デプロイ)の方法

Gitクラウド

Git ではフックスクリプトを記述することで本番環境への適用を行いますが、GitHub には Actions というフックの拡張版のような機能が備わっています。
GitHub Actions は、フックスクリプトよりも可読性が高く、記述が容易です。
なにより GitHub の Web 画面上から結果を確認できたり実行できたりするのが便利ですね。
本記事では、GitHub の Actions を利用してビルドやデプロイ、自動テスト実行等を行う方法について説明いたします。

Git におけるデプロイの仕組みやフックスクリプトの内容については以下を参照してください。

参考: Git のフックを利用したデプロイの方法


GitHub Actions の作り方
リポジトリのトップ画面より上部タブ Actions をクリックすると、以下のような画面になります(アクションが存在しない場合)。
該当するアクションを選択するとテンプレートを作ってくれるみたいなのですが、なんかよくわからないので自分で作った方が早そうです。
set up a workflow yourself」をクリックします。

GitHub Action トップ画面

ちなみにアクションがすでに存在する場合は、以下のような画面となるので「New workflow」をクリックして新規作成します。

New workflow

作成画面は以下のようになります。
.github/workflows配下にファイルが作成されることがわかりますね(下図青枠部分)。
言語は YAML です。
ワークフローの内容の記述は後からやるとして、まずは作成してみます。
ファイル名を指定して、右側の「Start commit」ボタンをクリックします。

GitHub Action の作成

説明を記述して「Commit new file」をクリックすると新しくファイルが作成されます。

Commit new file

これでアクションの作成が完了しました。

GitHub Action の作成完了画面


規定の Git-flow(ブランチモデル)に従うのであれば、featureブランチを作成してプルリクエストを発行するところですが、プログラムそのものの修正ではないので、mainブランチに直接commitでも構わないかと思います。
ただしdevelopブランチへの反映を忘れずに。


トリガーとなるイベント
GitHub Actions は Git に対する様々なイベントをトリガーとしてワークフロー(スクリプト処理)を走らせることができます。
トリガーとなるイベントは、onで記述します。
以下、主要なイベントごとに記述方法を説明します。


プルリクエスト発行時
e.g. プルリクエストの発行時に単体テストを自動実行したい

プルリクエストが作成されたときに動作します。
なお、コンフリクトの起きる可能性のあるプルリクエストではワークフローは実行されません。

on:
  pull_request:
    types: [ opened, synchronize, reopend ]

・opened
プルリクエストを発行したとき

・reopened
クローズしてから再度オープンしたとき

・synchronize
ブランチに更新があったとき


develop ブランチの更新時
e.g. ステージング環境に最新ソースを適用(ビルド、デプロイ)したい

developブランチへpushが行われたときやdevelopブランチに他のブランチがマージされたときに動作します。

on:
  push:
    branches:
      - develop


main ブランチの更新時
e.g. 本番環境に最新ソースを適用(ビルド、デプロイ)したい

mainブランチへpushが行われたときやmainブランチに他のブランチがマージされたときに動作します。

on:
  push:
    branches:
      - main


手動で実行したい
e.g. 本番環境にメンテナンス画面を表示したい

GitHub Actions の画面から手動で実行します。
画面左メニューから実行したいワークフローを選択し、画面右側の「Run workflow」をクリック、ブランチを選択した後に緑色の「Run workflow」ボタンをクリックします。

GitHub Actions の手動実行
on:
  workflow_dispatch:

なお手動実行の場合、YAML ファイルはデフォルトブランチに含める必要があります。


定期的に実行したい
e.g. 毎日深夜にデータベースのバックアップをとりたい

cronの書式でスケジュールした時刻にワークフローを実行できます。

on:
  schedule:
    - cron: '30 5 * * 1,3'


ワークフローの書き方
まずは全体的な概要を説明します。
主な書式は以下の通りです。
この他にも様々な指定が可能ですが、おおむねここで説明しているものだけでデプロイ等の実装は可能です。

GitHub Docs – GitHub Actions のワークフロー構文
https://docs.github.com/ja/actions/using-workflows/workflow-syntax-for-github-actions

name: MyWorkflow # 1
on: # 2

  # ここにトリガーイベントを記述(前章で説明)

jobs: # 3
  job1: # 4
    name: MyJob1 # 5
    runs-on: ubuntu-latest # 6
    steps: # 7

      # 実行するタスクを記述

  job2:
    name: MyJob2
    ・
    ・
    ・

① name
ワークフローに名前をつけます。

② on
前章で説明したトリガーとなるイベントを記述することで、どのタイミングで実行されるかを定義します。

③ jobs
ワークフローの実行単位です。

④ jobs.<job_id>
ジョブの識別子を定義します。
1つの jobs の中に複数のジョブを定義できます。
ジョブが複数存在する場合、ジョブは並列実行されますが、もしシーケンシャルに実行させたい場合は、jobs.<job_id>.needsキーワードでジョブ同士の依存関係を定義します。

⑤ jobs.<job_id>.name
ジョブに名前をつけます。

⑥ jobs.<job_id>.runs-on
ジョブを実行する環境(ホステッドランナー)を指定します。
ホステッドランナーは、GitHub が提供するホストされた仮想マシンのことで、基本的にジョブはこの仮想マシン上で動作します。
異なるジョブには異なるホステッドランナーが割り当てられます。

・Linux 上で動作させたい場合: ubuntu-latest
・Windows 上で動作させたい場合: windows-latest
・MacOS 上で動作させたい場合: macos-latest

もしワークフローの実行に必要なソフトウェアが存在する場合、jobs.<job_id>.stepsでインストールを行います。

GitHub Docs – GitHub ホステッド ランナーのカスタマイズ
https://docs.github.com/ja/actions/using-github-hosted-runners/customizing-github-hosted-runners

その他のバージョンやハードウェアスペックの詳細を知りたい場合は、GitHub Docs を参照してください。

GitHub Docs – サポートされているランナーとハードウェアリソース
https://docs.github.com/ja/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources

⑦ jobs.<job_id>.steps
ジョブに含まれるタスクを定義します。
ステップごとに1つのシェル(プロセス)が割り当てられるので、ステップ間で環境変数は引き継がれません。
jobs.<job_id>.stepsの詳細については、次の章で詳しく説明いたします。


ステップの書き方
基本的な書き方は以下の通りです。
※インデントに注意してください、stepsを左端にしています。
※インデントがずれると Syntax Error になる場合があります。

steps
  - name: Print hello # 1
    run: echo "Hello World" # 2
  - name: Clean install dependencies and build
    run: |
      npm ci
      npm run build

① jobs.<job_id>.steps.name
ステップに名前をつけます。

② jobs.<job_id>.steps.run
実行したいコマンドを記述します。
1つのstepsの中に複数のステップを記述できます。
さらに1つのrunの中で複数のコマンドを実行することもできます。
jobs.<job_id>.steps.shellキーワードで実行したいシェルを指定することも可能です。

GitHub Docs – Workflow syntax – shell
https://docs.github.com/ja/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell

ホステッドランナー上で動作させるだけであればこれで事足りるのですが、実際に本番環境へのデプロイを行う場合には、本番サーバへ SSH 接続したり、本番サーバのリストを取得したりする必要があります。
実はこのような用途のために外部公開されたアクションが存在し、これを利用することで簡単にワークフローを記述することができます。
外部公開のアクションを利用する場合は、useswithキーワードを使用します。


シークレットの設定
具体的なステップの記述の前に、SSH 接続におけるパスワード等の機密情報を隠す方法を説明いたします。
SSH 接続に必要なパラメータとしては、ホスト名、ユーザ名、パスワードなどがあります。
この他、公開鍵認証で接続する場合は秘密鍵やパスフレーズが、ポート番号をデフォルトから変更している場合はポート番号が必要です。
これらの値をそのままワークフローに記述してしまうとセキュリティ的に問題があるので、シークレットという機能で隠します。

上部タブメニューの Settings より左メニューの SecretsActions をクリックします。
シークレットが存在しない場合は以下のような画面が表示されます。

シークレットの設定

右側の「New repository secret」ボタンをクリックすると、シークレットネームと値を入力する画面が表示されます。
ここに先ほどの隠したいパラメータを入力して「Add secret」ボタンをクリックします。

シークレット入力画面

以下は設定するパラメータの例です。

SSH_HOST
SSH 接続する本番サーバの IP アドレスもしくはドメイン名

SSH_USERNAME
SSH 接続のユーザ名

SSH_PASSWORD
SSH 接続のパスワード

SSH_PRIVATE_KEY
公開鍵認証を利用する場合の秘密鍵の内容(.ssh/id_rsaの中身をそのままコピーする)
BEGIN 行と END 行を含めること

SSH_PASSPHRASE
公開鍵認証を利用する場合のパスフレーズ

SSH_PORT
SSH 接続のポート番号

GitHub Docs – 暗号化されたシークレット
https://docs.github.com/ja/actions/security-guides/encrypted-secrets


本番環境へのデプロイ
mainブランチへのマージをトリガーとして本番環境へ最新のソースを適用します。
具体的には、本番環境へ SSH 接続し、git pull origin mainを行います。
その後、必要に応じてcomposernpm install、マイグレーション等を行います。
ステージング環境への適用であれば、トリガーとなるイベントがdevelopブランチになるでしょう。

name: Release to Production Environment
on:
  push:
    branches:
      - main

jobs:
  job1:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
      - name: ssh and deploy
        uses: appleboy/ssh-action@master # 1
        with: # 2 アクションの入力パラメータを指定
          host: ${{ secrets.SSH_HOST }} # ホスト名
          username: ${{ secrets.SSH_USERNAME }} # SSH ユーザ名
          key: ${{ secrets.SSH_PRIVATE_KEY }} # 秘密鍵の内容
          port: ${{ secrets.SSH_PORT }} # ポート番号
          script: |
            cd /path/to/git_current
            git switch main
            git pull origin main
            php artisan migrate --force

① uses
例では SSH 接続にappleboy/ssh-actionという公開されたアクションを利用しています。
公開されたアクションは、{owner}/{repo}@{ref}のような書式で指定します。

② with
appleboy/ssh-actionには、ホスト名やユーザ名、秘密鍵、ポート番号、実行するコマンドをwithキーワードで指定します。
利用するのであればパスワードやパスフレーズも指定できます。
その他のパラメータについては以下を参照してください。

https://github.com/appleboy/ssh-action

シークレットに登録した値は例のようにsecrets.xxxxの形で指定します。

③ script
scriptキーワード以降が SSH 接続後の本番環境サーバで実行するコマンドの内容です。
実行するコマンドが複数ある場合は、例のような書式で各コマンドを改行して記述できます。
実行内容としては、まず Git のワーキングディレクトリへ移動し、mainブランチに切り替え、origin mainpullしています。
ステージング環境だとdevelopブランチに切り替えてorigin developpullします。
これで最新ソースの適用は完了ですが、必要に応じて DB のマイグレーションや、パッケージのインストールを行います。
例では Laravel のマイグレーションコマンドを実行しています。

なお、本番環境やステージング環境では SSH で接続するための SSH サーバ(SSHD)が必要になります。
SSH サーバの設定方法については、以下を参照してください。

参考: SSHD の設定


本番環境が複数存在する場合の対処
本番環境のサーバが複数ある場合はどうでしょうか。
SSH 接続するホスト名はhostパラメータにカンマ区切りで複数設定できますが、サーバが増えるたびにシークレット値を変更する必要がでてきます。
これでも別に構いませんが、例えばサーバのリストを返す API を作成することでデプロイの自動化が進みます。
※インデントに注意してください、stepsを左端にしています。

steps
  - name: get servers
    uses: sergeysova/jq-action@v2 # 1
    id: servers # 2
    with: # 3
      cmd: "curl ${{ secrets.URL_GET_SERVERS }} | jq '.allservers' -r"
  - name: ssh and deploy
    uses: appleboy/ssh-action@master
    with:
      host: "${{ steps.servers.outputs.value }}" # 4
      username: ${{ secrets.SSH_USERNAME }}
      key: ${{ secrets.SSH_PRIVATE_KEY }}
      port: ${{ secrets.SSH_PORT }}
      script: |
        cd /path/to/git_current
        git switch main
        git pull origin main
        php artisan migrate --force

① uses
API が JSON 形式で応答する場合、jqコマンドを実行できるアクションsergeysova/jq-actionを利用します。

② id
ステップの識別子を定義します。
このステップで得られた値を他のステップで利用することができます。

③ with
sergeysova/jq-actionへの入力パラメータをwithで指定します。
cmdパラメータに実行するコマンドを指定します。
その他のパラメータについては以下を参照してください。

https://github.com/sergeysova/jq-action

API の URL はシークレットURL_GET_SERVERSに設定しています。
例えばこの API は以下のような応答を返します。

{
	"code": "ok",
	"message": "",
	"allservers": "11.22.33.44, 55,66,77,88, 99.00.11.22"
}

④ host
appleboy/ssh-actionhostパラメータに API から取得した値を指定します。
stepsから得た値は、steps.識別子.outputs.valueの形で利用できます。


メンテナンス画面の表示
手動で登録されたアクションを実行する場合の例として、メンテナンス画面の表示用のワークフローを紹介します。

name: Start Maintenance
on:
  workflow_dispatch:

jobs:
  job1:
    name: Displaying the Maintenance screen
    runs-on: ubuntu-latest
    steps:
      - name: get servers
        uses: sergeysova/jq-action@v2
        id: servers
        with:
          cmd: "curl ${{ secrets.URL_GETSERVERS }} | jq '.allservers' -r"
      - name: maintenance
        uses: appleboy/ssh-action@master
        with:
          host: "${{ steps.servers.outputs.value }}"
          username: ${{ secrets.SSH_USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: ${{ secrets.SSH_PORT }}
          script: |
            cd /path/to/git_current
            php artisan down --message="メンテナンス中です。" --secret="パスワード"

デプロイのときと同じく、まずは本番サーバのリストを取得し、各サーバに SSH 接続します。
SSH で接続したサーバ上でメンテナンス画面の表示コマンドを実行します。
例では Laravel のartisan downコマンドを実行しています。
なおメンテナンス画面の解除は、最後の行がphp artisan upになるだけであとは同じです。


アクションの実行結果
アクションの実行結果は、Actions の画面で確認することができます。
画面左のアクションを選択すると右側にそのアクションの実行結果が表示されます。

アクションの実行結果

緑色が成功で赤色が失敗です。
このアクションの実行ステータスはコマンドの終了コードで判定されています。
終了コードが0以外の場合、FAILとなり赤色の×で画面に表示されます。
StepごとのSUCCESS/FAILも確認することができます。
下図ではデプロイのアクションで、SSH 接続の部分でエラーとなっています。

アクションの実行結果詳細

終了コードの詳細は以下を参照してください。

GitHub Docs – アクションの終了コードの設定
https://docs.github.com/ja/actions/creating-actions/setting-exit-codes-for-actions


参考: GitHub の環境構築から git clone までの手順
参考: GitHub のタスク管理やプロジェクト管理を利用した運用方法

コメント