コミットされた YAML ファイルを Kubernetes にデプロイする GitHub Actions

Kubernetes のマニフェストファイルを管理している GitHub リポジトリがあって、そこにコミット・プッシュされた YAML ファイルを特定して、Kubernetes クラスタにデプロイするような GitHub Actions を作ってみた。

コード全量と使い方

先にコード全量。

# コミットされた YAML ファイルを Kubernetes クラスタにデプロイする
name: deploy-manifests
on:
  push:
    branches:
      - master
jobs:
  deploy:
    name: Deploy Manifests
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Get Changed Files
        id  : get_changed_files
        uses: jitterbit/get-changed-files@v1
        with:
          format: json
      # 対象とする deployments/ ディレクトリ配下の YAML ファイルのみにフィルタリングし ' -f FILE1.yaml -f FILE2.yaml' といった文字列を作成する
      # - added_modified は追加・更新したファイルのみで、リネームした場合が含まれない。そこで renamed を配列結合してからフィルタリングする
      # - TEMPLATE.yaml は除外する
      # - フィルタリングした結果が0件なら空文字となる
      - name: Filter Files
        id  : filter_files
        run : |
          echo "::set-output name=filtered_files::$(jq -r -j --argjson added_modified '${{ steps.get_changed_files.outputs.added_modified }}' --argjson renamed '${{ steps.get_changed_files.outputs.renamed }}' -n '$added_modified + $renamed | map(select(test("deployments/"))) | map(select(test("TEMPLATE") | not)) | map(select(test(".(?i)(yml|yaml)$"))) | map(" -f " + . + " ")[]')"
      - name: Print Filtered Files
        run : |
          echo 'Filtered Files :'
          echo "${{ steps.filter_files.outputs.filtered_files }}"
      # フィルタリングした結果、追加・更新されたファイルがある場合のみ kubectl apply を実行する
      - name: Deploy Files With Kubernetes CLI
        if  : steps.filter_files.outputs.filtered_files != null && steps.filter_files.outputs.filtered_files != ''
        run : |
          echo "${{ secrets.KUBE_CONFIG }}" | base64 --decode > /tmp/config
          export KUBECONFIG=/tmp/config
          kubectl apply ${{ steps.filter_files.outputs.filtered_files }}

KubeConfig の内容を Secret として使用しているので、次のように KubeConfig の内容を Base64 エンコードして、KUBE_CONFIG という Secret 名で登録しておく。

$ cat "${HOME}/.kube/config" | base64

詳細解説

それでは使い方を説明する。

on.push.branches で指定したブランチに対する Push が発生した時に、この GitHub Actions ワークフローが実行される。ココでは master ブランチへの Push 時となる。


name: Get Changed Files Step では、jitterbit/get-changed-files@v1 を使い、コミット内容から「新規追加されたファイル」「更新されたファイル」「リネームされたファイル」などを JSON 形式で取得している。

取得結果を次の Step で利用したいので、id: get_changed_files と Step ID を付与している。Step ID を書いておくと、別の Step で ${{ steps.【Step ID】.outputs.【変数名】 }} という風に参照できるようになる。


name: Filter Files Step でやっているのがキモで、変更があったファイルの JSON 情報を利用して jq 芸を行っている。

これらのコマンドの結果を、次の Step で使えるようにするため、

echo "::set-output name=【変数名】::$( 【コマンド】 )"

echo "::set-output name=filtered_files::$( jq -r -j …… )"

という専用の構文で変数名を付けて Output している。ココで Output した内容を使用する時は、

${{ steps.【Step ID】.outputs.【変数名】 }}

${{ steps.filter_files.outputs.filtered_files }}

といった形で読み込める。


ある Step を実行するかどうかは、if プロパティが使える。ココはシェルではなく独特の構文が使えるところ。jq 芸の結果が null や空文字でないことを確認している。

if: steps.filter_files.outputs.filtered_files != null && steps.filter_files.outputs.filtered_files != ''

Secret に登録しておいた、Base64 エンコードされた KubeConfig をデコードしてファイルに出力、そしてそれを export KUBECONFIG で読み込んでいる。

echo "${{ secrets.KUBE_CONFIG }}" | base64 --decode > /tmp/config
export KUBECONFIG=/tmp/config

このやり方は以下を参考にした。

kubectl コマンドは GitHub Actions のデフォルト環境にインストールされているので、KUBECONFIG を用意しておくだけで良い。

Output を渡す際はクォートで囲まずに渡すことで、eval 的に適用させている。

kubectl apply ${{ steps.filter_files.outputs.filtered_files }}

# 以下のように解釈・実行させる。クォートで囲んでしまうとおかしくなる
kubectl apply -f deployments/FILE1.yaml -f deployments/FILE2.yaml

ということで、コミットされたファイルを kubectl apply するので、kubectl apply --prune だったり、kubectl delete には対応していないのが惜しいところ。良い方法があったら教えてください。