Corredor

ウェブ、プログラミングの勉強メモ。

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

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

コード全量と使い方

先にコード全量。

  • .github/workflows/deploy-manifests.yaml
# コミットされた 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 芸を行っている。

  • jq -r -j (--raw-output--join-output) で、最終結果を1行にまとめる
  • 2つの JSON 配列をマージする。--argjson 【名前】 '【】' で JSON 配列をそれぞれ定義し、-n $【名前1】 + $【名前2】 (--null-input) とすると結合できる
  • 結合した JSON 配列に対し、map(select(test( ))) のパイプを組み合わせることで、対象としたいファイル、除外したいファイルを見極めている
    • map(select(test("deployments/"))) で、deployments/ ディレクトリ配下に保存されたファイルのみに絞り込む
    • その内、map(select(test("TEMPLATE") | not)) で、deployments/ ディレクトリ配下にあっても TEMPLATE の文字を含むファイルは除外している (deployments/TEMPLATE.yaml なんかを kubectl apply 対象にしないようにする)
    • 最後に拡張子判定。map(select(test(".(?i)(yml|yaml)$"))) で、大文字・小文字を区別せ YAML ファイル (.yml.yaml.YML.YAML) のみに絞り込む
    • フィルタリングした結果、対象となるファイルが1つもないと空の配列になるので、その場合は最終的に空文字になる
  • 最後に map(" -f " + . + " ")[]' と書いて、'-f deployments/FILE1.yaml -f deployments/FILE2.yaml' といった文字列を作成する。-fkubectl apply コマンドに渡す際に必要になるので付与している

これらのコマンドの結果を、次の 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 には対応していないのが惜しいところ。良い方法があったら教えてください。