LoginSignup
22
14

More than 1 year has passed since last update.

【GitHub Actions】ワークフローはなんとなく書けるけど構造や仕組みの理解が曖昧な方々向けの記事

Last updated at Posted at 2021-03-25

最近、現場でGitHub Actionsを使い始めました。CI/CDってなんやねんというレベルだったので、基礎から学んで簡単なワークフローをいろいろつくって勉強してみることにしました。

GitHub Actionsって何?

GitHub上のリポジトリやイシューに対するさまざまな操作(Push, Pull Request, 外部イベントなど)や指定したスケジュールをトリガとして、あらかじめ定義した処理(Workflow)を実行する機能です。今までは自動テストや自動ビルドを実施するのにCircle CIやTravis CIなどの外部サービスとの連携が必要でしたが、GitHub Actionsを使うことでGitHubだけで実現することができます。

WorkflowはJobという単位にわけられ、仮想マシンのインスタンス上で実行されます。JobはさらにStepという単位にわけられ、コマンドや既存の処理(アクション)が実行されます。
スクリーンショット 2021-03-24 7.57.22.png

Jobごとに仮想マシンをたてることができるので、それぞれの環境に適した処理を実行することができます。
スクリーンショット 2021-03-24 20.59.48.png

Runnerって何?

Runnerはトリガとなるイベントが起こったときにJobを実行するためのアプリケーションです。GitHubに標準装備されているもの(GitHub-hosted Runner)と、自分でカスタマイズして実装するもの(Self-hosted Runner)があります。それぞれ以下の特徴をもっています。

GitHub-hosted Runner

  • ソフトウェアがプリインストールされたLinux, Windows, MacOSの仮想環境が提供される
  • GitHubで管理される(ユーザによるハードウェアのカスタマイズは不可)

プリインストールされているソフトウェアには以下のものがあります。

  • curl, git, npm, yarn, pipなどのツール
  • Python, Ruby, Node.jsなどの言語
  • Android SDK, XCodeなどの開発キット

Self-hosted Runner

  • Runnerアプリケーションがインストールされたマシンをユーザが管理する
  • ハードウェア、OS、ソフトウェアツールをユーザが自由にカスタマイズできる(ex.メモリ増設、hosted-Runnerで使えないOSを使用)

ワークフローの作成

簡単なワークフローをいろいろつくって挙動やオプションを学んでいきます。

Hello World

実際にHello Worldを出力(Step1)して、nodeとnpmのバージョンを表示(Step2)するような簡単なワークフローをつくってみます。ワークフローはyml形式で以下のように記述することができます。

simple.yml
name: Shell Commands #ワークフローの名前

on: [push] #ワークフロー実行のトリガーとなるイベント

jobs: #ワークフローで実行するジョブ(複数記述可能)
  run-shell-command: #ジョブの名前
    runs-on: ubuntu-latest #jobを実行するVMのOSを指定
    steps: #jobの実行内容
      - name: echo a string
        run: echo "Hello World"
      - name: multiline script 
        run: |
          node -v 
          npm -v

ワークフローのファイルはプロジェクト/.github/workflowの直下に保存します。リモートリポジトリをつくってgit pushすると、Actionタブでワークフローの状態を確認することができます。緑のチェックがついていれば成功です。
スクリーンショット 2021-03-21 15.12.43.png

Runner診断ログ

SettingsタブのsecretsにACTIONS_RUNNER_DEBUG, ACTIONS_STEP_DEBUGをtrueで保存すると、Jobの実行状態に関する情報を含む追加のログファイルが2つ提供されます。
スクリーンショット 2021-03-21 15.18.27.png

シェルコマンドの実行

shellを追加することで、runステップに対するデフォルトのシェルを指定することができます。記述場所によってWorkflow単位, Job単位, Step単位で影響範囲を変えることができます。

以下ではPythonシェルを指定して、runステップでコマンドを実行しています。

      - name: python Command
        run: |
          import platform
          print(platform.processor())
        shell: python

スクリーンショット 2021-03-21 15.34.35.png

次にwinOSの仮想マシン(VM)で新たなジョブを作って、PowerShellとbashを実行してみます。

  run-windows-commands:
    runs-on: windows-latest #ジョブを実行するVMのOSをwindowsに指定
    needs: ['run-shell-command'] #run-shell-commandのジョブが成功したときだけ実行
    steps:
      - name: Directory PowerShell
        run: Get-Location #winOSなのでPowerShellを実行
      - name: Directory Bash
        run: pwd #bashのpwdコマンドを実行
        shell: bash #Directory Bashのshellをbashに指定

winOSのデフォルトシェルがPowerShellなので、Directory PowerShellのステップではD:\a\github-actions-test\github-actions-testが出力されます。Directory Bashのステップではbashを指定しているので、/d/a/github-actions-test/github-actions-testが出力されます。

また、複数のジョブはデフォルトで並列に実行されますが、needsを指定することで別のジョブが成功したときだけ実行することができます。

アクションの実行

ステップでは指定のコマンドを実行するだけではなく、usesでパッケージ化された処理(アクション)を読み込んで実行することもできます。
まずはHello Worldを行うアクション(hello-world-javascript-action)を読み込んで実行してみます。

actions.yml
name: Actions Workflow

on: [push]

jobs: 
  run-github-actions:
    runs-on: ubuntu-latest
    steps:
      - name: Simple JS Action
        id: greet #別のstepで読み込むためのid
        uses: actions/hello-world-javascript-action@v1 #使用したい外部のアクション([ユーザ名/リポジトリ名]@[ブランチ名])
        with: 
          who-to-greet: John
      - name: Log Greeting Time
        run: echo "${{ steps.greet.outputs.time }}" #greetのstepを実行した時間を呼出

Simple JS Actionのステップでアクションを読み込んでHello Worldを実行し、Log Greeting Timeのステップで前ステップを実行した時間を呼び出しています。

スクリーンショット 2021-03-21 19.29.19.png

スクリーンショット 2021-03-21 19.29.51.png

Checkoutって何?

簡単なワークフローをいろいろつくりましたが、実際にワークフローは仮想マシンのどのディレクトリで実行されているのでしょうか。
確認のために以下のステップを追加して実行してみます。

      - name: List Files
        run: |
          pwd
          ls

pwdの結果は/home/runner/work/github-actions-test/github-actions-testとなり、ワークフローを実行するためのフォルダgithub-actions-testが作成されていることがわかります。

一方、lsの結果は空でgithub-actions-testフォルダにはファイルが存在していません。これはデフォルトでファイルを仮想マシンのディレクトリにクローンしないようになっているためです。

ただ、このままではワークフローでテストやビルドを実施することができません。
このときに使用するのが、actions/checkoutというアクションです。これを使うことにより、ワークフローが実行されたときにリポジトリがトリガとなったコミットにチェックアウトされて、仮想マシンのディレクトリにクローンされます。

      - name: List Files
        run: |
          pwd
          ls -a
      - name: Checkout
        uses: actions/checkout@v2
      - name: List Files After Checkout
        run: |
          pwd
          ls -a

Checkoutステップ後に仮想マシン上のディレクトリを確認すると、リポジトリがちゃんとクローンされています。
スクリーンショット 2021-03-21 19.57.45.png

また、このアクションをGitコマンドに置き換えると以下のようになります。

          git clone git@github:$GITHUB_REPOSITORY
          git checkout $GITHUB_SHA

$GITHUB_REPOSITORY$GITHUB_SHAはGitHubでデフォルトで用意されている環境変数です。それぞれ、ワークフローが実行されるリポジトリとワークフローをトリガしたコミットSHA(ID)を表しています。

変数の中身はそれぞれ以下のように出力されます。

      - name: List Files
        run: |
          pwd
          ls -a
          echo $GITHUB_SHA #最新のコミットID
          echo $GITHUB_REPOSITORY #ユーザ名/リポジトリ名
          echo $GITHUB_WORKSPACE #VMのディレクトリ
          echo "${{ github.token }}" #GitHubのトークン

スクリーンショット 2021-03-21 20.31.50.png

ワークフローのトリガの種類

ここまではgit pushをトリガとしたワークフローを取り扱ってきましたが、push以外のトリガとなるイベントについてまとめました。

Git操作のイベント

以下はプルリクエストをトリガとするイベントの記述です。typesの中でプルリクがcloseしたときやopenしたときのイベントを指定することができます。

  pull_request:
    types: [closed, assigned, opened, reopened]

スケジュール

1日ごと1ヶ月ごとなど指定したスケジュールでワークフローを実行することができます。Crontab.guruというエディタを使うと簡単にcronの記述を行うことができます。

  schedule:
    - cron: "0/5 * * * *"

dispatch event

dispatch eventは手動のトリガによってワークフローを実行させる手段です。

以下の記述によって、buildというイベントのリクエストがリポジトリに送られると、ワークフローが実行されるようになります。

  repository_dispatch:
    types: [build]

試しにPostmanでリクエストを送ってみます。

Bodyにevent_typeを記述します。
スクリーンショット 2021-03-22 21.39.47.png

リクエスト投げるにはトークンが必要なので、GitHubのDeveloper settingsから新しく作成します。
スクリーンショット 2021-03-22 21.43.53.png

PostmanのAuthorizationタブでPasswordにトークンを入力してSendを押し、何も表示されなければリクエストの送信は成功です。
スクリーンショット 2021-03-22 21.45.55.png

GitHubのActionsタブをみると、ワークフローが確かに実行されています。
スクリーンショット 2021-03-22 21.49.17.png

ワークフローのフィルタリング

pushやpull_requestのイベントが起こった際、特定のブランチ、タグ(コミット)、パスの条件に一致したときだけワークフローを実行するように設定することができます。

パスを例にすると、1つ以上の変更されたファイルがpaths-ignore にマッチしない場合や、1つ以上の変更されたファイルが設定されたpathsにマッチする場合にワークフローを実行することができます。

  push:
    branches:
      - master
      - "feature/*" #feature/featA, feature/featBなど
      - "feature/**" #feature/feat/a
      - "!feature/featC"
    tags:
      - v1.*
    paths:
      - "**.js"
      - "!filename.js"

また、ifを使ってジョブやステップの条件実行を行うこともできます。

jobs:
  one:
    runs-on: ubuntu-latest
    if: github.event_name == 'push' #pushイベントのときだけジョブを実行

環境変数

ワークフローでは、GitHubでデフォルトで用意されている環境変数と自分で作成した環境変数の両方を使うことができます。

envを設定することで環境変数を自分で作成することができ、記述する位置によって、ワークフロー内、ジョブ内、ステップ内と変数を使用できる範囲を変えることができます。

デフォルトの環境変数については、${HOME}${GITHUB_SHA}と記述することで読み込むことができます。

name: ENV Variable
on: push
env: #すべてのジョブから利用できる環境変数
  WF_ENV: Available to all jobs

jobs:
  log-env:
    runs-on: ubuntu-latest
    env: #ジョブ内で利用できる環境変数
      JOB_ENV: Available to all steps in log-env job
    steps:
      - name: Log ENV Variables
        env: #ステップ内で利用できる環境変数
          STEP_ENV: Available to only this step
        run: |
          echo "WF_ENV: ${WF_ENV}"
          echo "JOB_ENV: ${JOB_ENV}"
          echo "STEP_ENV: ${STEP_ENV}"
  log-default-env:
    runs-on: ubuntu-latest
    steps:
      - name: Default ENV Variables
        run: |
          echo "HOME: ${HOME}"
          echo "GITHUB_WORKFLOW: ${GITHUB_WORKFLOW}"
          echo "GITHUB_SHA: ${GITHUB_SHA}"

自分でカスタマイズした環境変数はGitHub上で管理することができます。Actions secretsに保存することで、WF_ENV: ${{ secrets.WF_ENV }}のようにしてワークフロー内で読み込むことができます。
スクリーンショット 2021-03-22 22.44.20.png

環境変数を使用するジョブ

環境変数について理解したところで、2つのジョブをつくってみようと思います。

issueを作成するジョブ(create-issue)

適当なファイルをリポジトリにpushして、同時にissueを作成するようなジョブを作成します。これらの流れを2つのステップにわけて実装します。

ファイルプッシュ(Push a random file)

runにgitコマンドがいろいろ書いてありますが、ここでは単に、仮想マシンのディレクトリでリポジトリの最新のmasterブランチをプルして、ファイルを追加して、リポジトリにプッシュしているだけです。

      - name: Push a random file
        run: |
          pwd
          ls -a
          git init
          git remote add origin "https://$GITHUB_ACTOR:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY.git"
          git config --global user.email "my-bot@bot.com"
          git config --global user.name "my-bot"
          git fetch
          git checkout master
          git branch --set-upstream-to=origin/master
          git pull
          ls -a
          echo $RANDOM >> random.txt
          ls -a
          git add -A
          git commit -m "Random file"
          git push

REST APIでissue作成(Creating issue using REST API)

curlコマンドでGitHub REST APIをたたき、Issueを作成します。APIをコールするためにGitHubトークンが必要となるので、${{ secrets.GITHUB_TOKEN }}のようにして読み込んでいます。

  create-issue:
    runs-on: ubuntu-latest
    steps:
    - name: Create issue using REST API
      run: |
        curl --request POST \
        --url https://api.github.com/repos/${{ github.repository }}/issues \
        --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
        --header 'content-type: application/json' \
        --data '{
          "title": "Automated issue for commit: ${{ github.sha }}",
          "body": "This issue was automatically created by the GitHub Action workflow **${{ github.workflow }}**. \n\n The commit hash was: _${{ github.sha }}_."
          }'

ワークフローを実行すると、issueが確かに作成されています。
スクリーンショット 2021-03-23 7.35.35.png

暗号化ファイルの復号するジョブ

gpgで暗号化されたファイルsecret.json.gpgを復号化して中身を確認するジョブを作成します。
ここではGitHubのsecretsに保存したPASSPHRASEを使用して、復号化のコマンドを実行しています。

  decrypt:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Decrypt file
        run: gpg --quiet --batch --yes --decrypt --passphrase="$PASSPHRASE" --output $HOME/secret.json secret.json.gpg 
        env:
          PASSPHRASE: ${{ secrets.PASSPHRASE }}
      - name: Print our file content
        run: cat $HOME/secret.json

その他オプション

continue-on-error, timeout-minutes

ジョブの実行や停止を制御するオプションです。

    steps: #ジョブの実行内容
      - name: echo a string
        run: echo "Hello World"
        continue-on-error: true #前ステップでエラーが発生しても実行する
        timeout-minutes: 0 #指定時間以上経過したらジョブを停止する

matrix

複数のOSで同じジョブを実行したい場合、同様の記述を繰り返せば実現することができますが、あまり綺麗なやり方ではありません。
このようなときにmatrixを使用します。

以下の例では、os: [macos-latest, ubuntu-latest, windows-latest]の3つのOS、node_version: [6, 8, 10]で3つのnodeバージョンを指定しています。

この場合、以下の記述だけで3×3=9パターンのジョブを実行することができます。

matrix.yml
name: Matrix
on: push

jobs:
  node-version:
    strategy:
      matrix:
        os: [macos-latest, ubuntu-latest, windows-latest] #3パターンのOS
        node_version: [6, 8, 10] #3パターンのnode_version
        max-parallel: 2 #ジョブの最大並列実行数
        fail-fast: true #3つのジョブのどれかがfailだったらstop
    runs-on: ${{ matrix.os }} #3パターン
    steps:
      - name: Log node version
        run: node -v
      - uses: actions/setup-node@v2 #nodeのダウンロード
        with:
          node-version: ${{ matrix.node_version }} #3パターン
      - name: Log node version
        run: node -v    

また、include, excludeで、実行する(しない)matrixの組合せを指定することができます。
includeでは特定の組合せのときだけ使える変数(以下の例のis_ubuntu_8)を設定することができます。
excludeに2パターン記述しているので、以下で実行されるジョブ数は9-2=7つとなります。

matrix.yml
name: Matrix
on: push

jobs:
  node-version:
    strategy:
      matrix:
        os: [macos-latest, ubuntu-latest, windows-latest]
        node_version: [6, 8, 10] #3パターンのnode_version
        include: #指定したmatrixの組合せを実行する
          - os: ubuntu-latest
            node_version: 8
            is_ubuntu_8: "true" #この組合せのときだけ使用できる変数
        exclude: #指定したmatrixの組合せを実行しない
          - os: ubuntu-latest
            node_version: 6
          - os: macos-latest
            node_version: 8
    runs-on: ${{ matrix.os }}
    env:
      IS_UBUNTU_8: ${{ matrix.is_ubuntu_8 }}
    steps:
      - name: Log node version
        run: node -v
      - uses: actions/setup-node@v2 #nodeのダウンロード
        with:
          node-version: ${{ matrix.node_version }} #3パターン
      - name: Log node version
        run: |
          node -v
          echo $IS_UBUNTU_8

おわりに

1ヶ月前に挫折したDockerコンテナの自動ビルドに再度挑戦してみたいと思います。

参考資料

22
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
14