コミットされた 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'
といった文字列を作成する。-f
はkubectl 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
には対応していないのが惜しいところ。良い方法があったら教えてください。