これは、4月15日にKMC春プロジェクト(新歓講座)として開催したイベントで用いたスライドです。
※スライド中でCodebox
というものが出てきますが、これはcoder/code-server をカスタムし、KMC サーバー上にデプロイしたものです。
Codespace等でも代替可能だと思われます。Webhookの公開方法だけ、ngrokなどを使用した方法に切り替えてください。
GitHub github.com
これは、4月15日にKMC春プロジェクト(新歓講座)として開催したイベントで用いたスライドです。
※スライド中でCodebox
というものが出てきますが、これはcoder/code-server をカスタムし、KMC サーバー上にデプロイしたものです。
Codespace等でも代替可能だと思われます。Webhookの公開方法だけ、ngrokなどを使用した方法に切り替えてください。
GitHub github.com
自宅 k8s の manifest を管理するリポジトリであるwalnuts1018/infraでは、renovate を用いて、HelmRelease の自動更新を行っています。 FluxCD の HelmRelease リソースには、Chart の Version を指定する項目があるのでそこが更新されていきます。
しかし、リポジトリに Commit されているのはあくまでも HelmRelease の Manifest であり、helm install
をするのは Server-Side の helm-controller です。
そのため、Helm Chart 更新の Pull Request を出されても、結局何が変わるのか一目ではわかりません。
そこで、PR の base と head でhelm install
した後に生成されるリソースの差分を出力し、PR にコメントされるような GitHub Action を作成しました。
さっそく完成形を置いてしまいます。
https://github.com/walnuts1018/infra/pull/307#issuecomment-2039935834
このように、PR にコメントが投稿され、Helm によって生成されるリソースの差分が表示されています。
helm repo add
を行うhelm template
を行うactions/checkout
を使って、PR の base と head のリポジトリをチェックアウトし、azure/k8s-bake
を使って kustomize build を行います。
- uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.base.ref }} - uses: azure/k8s-bake@v3 with: renderEngine: "kustomize" kustomizationPath: "./k8s/clusters/kmc/" kubectl-version: "latest" id: base_bake - run: "mv ${{ steps.base_bake.outputs.manifestsBundle }} /tmp/manifests-base.yaml" - uses: actions/checkout@v4 - uses: azure/k8s-bake@v3 id: base_changed with: renderEngine: "kustomize" kustomizationPath: "./k8s/clusters/kmc/" kubectl-version: "latest" - run: "mv ${{ steps.base_changed.outputs.manifestsBundle }} /tmp/manifests-head.yaml"
この部分はもともと存在していたのでそのまま使うだけです
yq と jq と helm をいれます
- name: install dependencies run: | # Install jq sudo wget https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64 -O /usr/bin/jq sudo chmod +x /usr/bin/jq # Install yq sudo wget https://github.com/mikefarah/yq/releases/download/v4.43.1/yq_linux_amd64 -O /usr/bin/yq sudo chmod +x /usr/bin/yq # install helm curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | sudo bash ```
/tmp/manifests-base.yaml
と /tmp/manifests-head.yaml
から、HelmRepository と HelmRelease のリソースを抽出します。
- name: HelmReleaseとHelmRepoを抽出 run: | cat /tmp/manifests-head.yaml |yq -j '. | select(.kind == "HelmRelease") | sort_keys(.metadata.name .metadata.namespace)' > /tmp/helmreleases-head.json cat /tmp/manifests-head.yaml |yq '. | select(.kind == "HelmRepository") | sort_keys(.metadata.name .metadata.namespace)' > /tmp/helmrepository-head.yaml cat /tmp/manifests-base.yaml |yq -j '. | select(.kind == "HelmRelease") | sort_keys(.metadata.name .metadata.namespace)' > /tmp/helmreleases-base.json cat /tmp/manifests-base.yaml |yq '. | select(.kind == "HelmRepository") | sort_keys(.metadata.name .metadata.namespace)' > /tmp/helmrepository-base.yaml
/tmp/helmrepository-[head|base].yaml
から、helm の repository 情報を取得し、helm repo add
を行います。
- name: helm repo add run: | # helm repo add head-<.metadata.name-.metadata.namespace> <.spec.url> cat /tmp/helmrepository-head.yaml | yq -r '"head-" + .metadata.name + "-" + .metadata.namespace + " " + .spec.url' | while read line; do helm repo add $line done # helm repo add base-<.metadata.name-.metadata.namespace> <.spec.url> cat /tmp/helmrepository-base.yaml | yq -r '"base-" + .metadata.name + "-" + .metadata.namespace + " " + .spec.url' | while read line; do helm repo add $line done ```
[head|base]-<.metadata.name-.metadata.namespace>
という名前で helm repository が追加されました。
yq の中で実行コマンド文字列を生成しているので、Parse 処理が一回で済みます。
/tmp/helmreleases-[head|base].json
と/tmp/helmrepository-[head|base].yaml
をもとに、helm template
コマンドを使って、リソースを生成します。
helm install --dry-run は、k8s cluster にアクセスできる必要がありますが、helm template は helm 単体で実行できるので、こちらを使います。
- name: helm template run: | length=$(cat /tmp/helmreleases-head.json | jq -s length) for i in $( seq 0 $(($length - 1)) ); do cat /tmp/helmreleases-head.json | jq -rs ".[$i] | .spec.values" | tee values.json >> /dev/null command=$(cat /tmp/helmreleases-head.json | jq -rs "\"helm template \" + .[$i].metadata.name + \" --namespace \" + .[$i].metadata.namespace + \" head-\" + .[$i].spec.chart.spec.sourceRef.name + \"-\" + .[$i].metadata.namespace + \"/\" + .[$i].spec.chart.spec.chart + \" --version \" + .[$i].spec.chart.spec.version + \" -f values.json\"") eval $command | yq "sort_keys(..)" >> /tmp/manifests-head.yaml done length=$(cat /tmp/helmreleases-base.json | jq -s length) for i in $( seq 0 $(($length - 1)) ); do cat /tmp/helmreleases-base.json | jq -rs ".[$i] | .spec.values" | tee values.json >> /dev/null command=$(cat /tmp/helmreleases-base.json | jq -rs "\"helm template \" + .[$i].metadata.name + \" --namespace \" + .[$i].metadata.namespace + \" base-\" + .[$i].spec.chart.spec.sourceRef.name + \"-\" + .[$i].metadata.namespace + \"/\" + .[$i].spec.chart.spec.chart + \" --version \" + .[$i].spec.chart.spec.version + \" -f values.json\"") eval $command | yq "sort_keys(..)" >> /tmp/manifests-base.yaml done
helmreleases-[head|base].json
に、それぞれの HelmRelease のリソースが jsonl として入っているので、jq -s
で配列に変換、length
で長さを取得し、for ループで一つずつ処理していきます。
対象の HelmRelease のspec.values
を取得し、values.json として出しておきます。
次に、helm template コマンドを組み立てていきます。これも jq の中で文字列を生成しているので、一回で済みます。
helm template <HelmReleaseのname> --namespace <HelmReleaseのnamespace> <[head|base]-HelmRelease中で指定されているHelmReporisitoryの名前>-<HelmReleaseのnamespace(=HelmRepositoryのnamespace)>/<HelmRelease中で指定されているChartの名前> --version <HelmRelease中で指定されているChartのバージョン> -f values.json
という文字列が最終的に出来上がります。
そして最後にこれを実行し、出力されたリソースを/tmp/manifests-[head|base].yaml
に追記していきます。デフォルトで1行目に---
が入るので、そのまま追記できます。
ただしここで、sort_keys(..)
を使って、リソースの順番を揃えています。完全ではありませんが、ほとんどの項目が head と base で同じ順番になるので余計な差分が出にくくなります。
ただ diff コマンドを実行するだけです
- name: Build markdown comment with manifest diff run: | echo "## Manifest diff <details> <summary>Click to expand</summary> \`\`\`diff $(diff -u manifests-base.yaml manifests-head.yaml) \`\`\` </details>" | tee /tmp/comment.md ```
これが少し難しいですが、「Pull request レビュー コメント用 REST API」を使うのではなく、「issue コメント用の REST API」を使います。 「Pull request レビュー コメント用 REST API」は、PR のコミットに対するコメントが付くので、少し探しにくくなってしまいます。
「issue コメント用の REST API」は、issue だけでなく PR にも使うことができ、これが一番皆さんの使う「PR のコメント」となります。
- name: Comment manifest diff to GitHub PR run: | cat /tmp/comment.md | jq -Rs '{ "body": . }' | curl --fail \ -X POST -H 'Accept: application/vnd.github.v3+json' \ --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.number }}/comments \ -d @-
もっと複雑になるかと思ったのですが、意外と単純に行けました。 あと、実行時間ももっとかかるかと思ったのですが、1 分ちょいで終わります。
これで少しは PR の中身をちゃんと確認する気が起きそうです。(今までは脳死で Merge していました) renovate の PR だけではなく自分で更新する際にも意図しない変更がないかどうか確認出来てよさそうです。
とりあえず自宅 infra のリポジトリと、KMC のリポジトリに導入してみました。しばらく様子をみて改善していきたいと思います。
また、今後は「kustomize の resources に include していない」「Chart の values.yaml に存在しない項目を指定している」などについてもテストを用意していきたいな~と考えています。
function SetAlias { Param( [Parameter(Mandatory = $true, Position = 0)] [string]$Alias, [Parameter(Mandatory = $true, Position = 1)] [string[]]$Command ) foreach ($cmd in $Command) { # 空白を含んでいたら関数として定義 if ($Command -match "\s") { $func = "function global:$Alias { $Command }" Invoke-Expression $func return } # それ以外はAlias else { # コマンドが存在するかどうかチェック if (Get-Command $cmd -ea SilentlyContinue) { Set-Alias -Name: $Alias -Value: $cmd -Scope: Global break } } } }
PowerShellでは、空白を含むAliasを登録できないので、オプションまで含めてAliasにしたいときは、functionとして登録する必要があります。
また、Alias登録時に対象のコマンドが存在しないと困るので、そのチェックも入れています。
それだけでした。
FluxCD には、Manifest 中の Image フィールドを自動的に更新してくれる機能があります。
とても便利な機能なのですが、公式ドキュメントの通りに ImageUpdateAutomation
を設定すると、main
ブランチに直接コミットされていきます。
そうではなくて、Renovate のように更新ごとに別のブランチを作成し、Pull Request を作成する挙動にしたい!というのが今回の目的です。
また、自動的に Test を実行し、テストに成功したら Merge するという挙動を実現する際に、思ったよりも手間がかかったので、その手順も記録しておきます。
あまり詳しくは説明しないので、興味のある方は公式ドキュメントや、id:kakku22 さんの 「Flux v2 で Image Ops を実現する「自動イメージ更新機能」を試した - kakakakakku blog 」などを参照してください。
今回は、アプリごとに以下のような Manifest を適用しました。
apiVersion: image.toolkit.fluxcd.io/v1beta1 kind: ImageUpdateAutomation metadata: name: hedgedoc spec: git: checkout: ref: branch: main commit: author: email: fluxcdbot@users.noreply.github.com name: fluxcdbot messageTemplate: "{{range .Updated.Images}}{{println .}}{{end}}" push: branch: main interval: 1m0s sourceRef: kind: GitRepository name: flux-system namespace: flux-system update: path: ./k8s/apps/hedgedoc strategy: Setters --- apiVersion: image.toolkit.fluxcd.io/v1beta2 kind: ImageRepository metadata: name: hedgedoc spec: image: quay.io/hedgedoc/hedgedoc interval: 2m0s --- apiVersion: image.toolkit.fluxcd.io/v1beta2 kind: ImagePolicy metadata: name: hedgedoc spec: imageRepositoryRef: name: hedgedoc policy: semver: range: "^1.x.x"
ImageUpdateAutomation
には、更新方法についての設定、つまり参照する GitRepository や、ブランチ、コミットの Author、更新を監視するパスなどの設定などを記述します。
ImageRepository
では image が公開されている repository や取得頻度を設定します。
最後にImagePolicy
では、どのような条件で更新を行うかを設定します。
そして、Deployment などの image の項目に
image: quay.io/hedgedoc/hedgedoc:1.9.9 # {"$imagepolicy": "default:hedgedoc"}
のようにコメントすることで Image の更新が自動的に Push されるようになります。
このままでは、main
ブランチに直接コミットされてしまうので、アプリケーション事に別のブランチにコミットされるようにします。
ImageUpdateAutomation
のpush
フィールドを以下のように変更します。
push: branch: fluxcd/hedgedoc
こうすると、main から pull し、タグを更新後、fluxcd/hedgedoc
ブランチにコミットされるという挙動になります。
FluxCD 単体では、自動的に Pull Request を作成する機能は存在しないようでした。そこで、GitHub Actions を利用して、自動的に Pull Request を作成するようにします。
以下のような GitHub Actions を作成しました。
name: flux-auto-PR on: workflow_dispatch: push: branches: - "fluxcd/*" jobs: create-pull-request: name: Open PR to main runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 name: checkout - name: create pull-request run: | gh pr create --body ":crown: *An automated PR*" --base main --title "Update image tag" env: GITHUB_TOKEN: ${{ github.token }}
push
イベントをトリガーに、fluxcd/
から始まるブランチに変更があった場合に、自動的に Pull Request を作成します。GitHub CLI を利用しているので、オプションを変更することで、Reviewer の設定や、Label の設定なども行うことができます。
これで、FluxCD によって作られたブランチに対して、自動的に Pull Request が作成されるようになります。
テストが通ったら自動的に Merge するようにしたいですよね。Renovate などでは簡単に実現できますが、Actions 経由だと少し手間がかかります。
まず、Actions の create pull-request ステップに automerge の設定を追加します。
run: | gh pr create --body ":crown: *An automated PR*" --base main --title "Update image tag" gh pr merge --auto --squash gh pr merge --merge --auto
リポジトリの設定でAllow auto-merge
が有効化されており、Branch protection rules でRequire status checks to pass before merging
が適切に設定されていれば、Status Check が通った後に自動的に Merge されるようになります。
しかし、この設定では、Pull Request の作成までは動くのですが、Status Check のための Actions がいつまでたっても実行されないという問題が発生しました。
原因を調べてみると、どうやら Actions で用いられる${{secrets.GITHUB_TOKEN}}
の権限では GitHub Actions 内から別の Action の Workflow をトリガーすることができないようです。*1
ということで、記事にある通り、GitHub App を作り、その App に Pull Request を作らせることで、Test の Workflow をトリガーすることにしました。
https://github.com/settings/apps/newに飛び、新しい App を作成します。名前とHomepage URL
は適当に、Webhook のActive
を無効化し、Permissions
は、Repository permissions
内のContents
とPull requests
をRead & write
に設定します。
作成した App に対して、Install App
をクリックし、インストールします。その後、App 設定内のApp ID
とPrivate keys
をメモしておきます。
次に、リポジトリの設定に戻り、Secrets
にAPP_ID
とPRIVATE_KEY
を追加します。
最後に Workflow を以下のように書き換えます。
name: flux-auto-PR on: workflow_dispatch: push: branches: - "fluxcd/*" jobs: create-pull-request: name: Open PR to main runs-on: ubuntu-latest steps: - uses: actions/create-github-app-token@v1 id: generate_token with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.PRIVATE_KEY }} - name: Set env run: | echo "GH_TOKEN=${{ steps.generate_token.outputs.token }}" >> $GITHUB_ENV - uses: actions/checkout@v4 name: checkout - name: create pull-request run: | gh pr create --body ":crown: *An automated PR*" --base main --title "Update image tag" gh pr merge --merge --auto
create-github-app-token
アクションを利用して、GitHub App に対してトークンを発行し、$GH_TOKEN に格納することで、GitHub CLI から新しい Token が自動的に利用されます。
これでようやく、Pull Request が作成され、Test が実行され、Auto Merge されるようになりました!
リポジトリの設定でAutomatically delete head branches
を有効化しておくとmerge後にブランチを消してくれるので便利です。
Actions で作成された Pull Request にそのような制約があったことは知りませんでした。
そもそも Actions で頑張るのは面倒すぎるので、FluxCD 側で早く実装してほしいですね~
walnuts.devのk8sクラスタは、FluxCDを使って管理されています。
多くのアプリケーションはFluxCD特有のHelm用設定ファイルやdeployment.yamlなどのManifestをコミットすることで本番環境に自動適用される、という運用になっています。
しかし、Helmに対応しておらず、kubectl apply -f https://github.com/<org>/<repo>/releases/download/<version>/<hoge>.yaml
を実行してインストールしなさい、的なアプリケーションも意外とあります。(flannel、kubevirtなど)
そこで今回は、そのようなアプリケーションにおいて、手動による操作を省きFluxCDの管理下に置く、さらにRenovateで更新まで自動化することを目標にします。
FluxCDではkind: Kustomization
のManifestを置くことで、kustomize build
をflux上で行うことができます。
Kustomizeでは、resources
に適用するファイルの相対パスを書いていきますが、ここに直接URLを書くことができます。
apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - https://github.com/flannel-io/flannel/releases/download/v0.24.2/kube-flannel.yml
こうすることで、リモートのファイルをわざわざwget
して、コミットして...といった手間が生じなくなります。楽~
このままでは一生更新されないので、Renovateを用いて先ほどのresources内のタグを自動更新します。Renovateというとnpmなどパッケージマネージャーの依存関係を更新する、というイメージがありますが、調べてみるといろいろなDataSourceを用いることができることがわかります。
この中にgithub-tags
というものがありました。今回の用途にマッチしそうですね。
customManagersのcustomType: "regex"
を用いることで、自前で対象ファイルをパースし、パッケージ名(今回は<user|org>/
{ ... customManagers: [ { customType: "regex", fileMatch: "^k8s/apps/.*/kustomization.yaml$", matchStringsStrategy: "any", matchStrings: [ "https:\\/\\/github\\.com\\/(?<depName>.*?)\\/releases\\/download\\/(?<currentValue>\\S+)\\/.*\\s*", ], datasourceTemplate: "github-tags", }, ], ... }
この設定を入れると、リポジトリ内の、/k8s/apps/<hoge>/kustomization.yaml
ファイルにおいてhttps://github.com/<depName>/releases/download/<currentValue>/<hoge>
という文字列を探し出し、github-tags
という名前のdatasourceTemplateにおけるdepName
・currentValue
に対応付けてくれます。
このrenovate設定をリポジトリに入れることで、自動的にPullRequestを作ってくれます。便利ですね~~~~
https://github.com/walnuts1018/infra/pull/96
packageRulesを定義することで、特定の条件下での振る舞いを変えることができます。 これを用いることでパッチバージョンについてはAutoMergeを有効化する、といった設定が行えます。
{ ... packageRules: [ { matchUpdateTypes: ["patch"], matchDatasources: ["github-tags"], automerge: true, }, ], ... }
この辺りはご自宅のテスト環境とお好み次第、といった感じですね~
renovate.jsonの完全版はこちら
flannelとkubevirtくらいしか影響がないので正直そこまで困っていなかったですが、世界がちょっとだけ平和になった気がします。
HelmReleaseの自動更新はid:nonyleneさんの Renovate で Flux 配下の Helm Chart をアップデートする - Unyablog. が大変役に立ったのでみなさんも是非。
昇降デスクのケーブルって最大限上げてもつっかえないようにケーブルを眺めにとっているので、下げて使っている時ブラーんとなって邪魔ですよね。
ということでケーブルチューブを買ってきれいにしました。
Amazonで800円くらいで売ってました。
意外とこれつけるの大変で、既存の配線にぐるぐる巻きつけていくのでチューブがどんどんねじれていっちゃいます。気合で直しました。
充電器系は長さに余裕がなかったので、昇降しない側のデスクに移して充電ステーションスペースを用意しました。
マグネットでケーブルまとめるやつ結構便利でした。
さて、綺麗なデスクは何週間持つのでしょうか。
これは KMC Advent Calendar 2023 6日目の記事です。
こんにちは。KMC 46代会長のWalnutsです。最近はKMCのインフラを破壊する仕事をしています。この前はJenkinsや内部DNSなどが動くマシンを破壊しました。次はKubernetesクラスターを壊す予定ですのでお楽しみに。
そして昨日のアドベントカレンダー記事は進捗ゼミさんの「仏像をぶつぞう!!!!というゲームを作りました」でした。
私もイラストの背景透過などについてお手伝いしました、面白いゲームでしたよね~
さて、今回の記事ではWalnuts家の自宅インフラについて解説していきます。
とりあえず今の所は以下のように少しずつ記事を分けで紹介していきたいと思います。
第一回では物理的なサーバー構成やOS、ネットワークなどについて紹介していきたいと思います。
HostName | Model | CPU | Memory | Disk | OS | 役割 |
---|---|---|---|---|---|---|
cake | ProDesk 400 G4 DM | Intel i5-8500T (6 cores) | 64GB | KIOXIA-EXCERIA G2 SSD 1TB | Ubuntu 22.04 | k8s Node |
snow | Raspberry Pi 4B | BCM2835 (4 cores) | 4GB | CSSD-S6O240NCG1Q 240GB | Debian GNU/Linux 11 | k8s Node |
donut | Raspberry Pi 4B | BCM2835 (4 cores) | 2GB | Apacer AS340 120GB | Debian GNU/Linux 11 | k8s Node |
alice | 自作PC | Intel i5-12400f (6 cores) | 48GB | Crucial P5 Plus 1TB / Western Digital 1TB WD Blue | Proxmox8 | VM基盤&メインマシン |
inazuma | OptiPlex 790 | Core i5-2500 (4 cores) | 8GB | SSD 240GB / HDD 500GB / HDD 500GB | Ubuntu 22.04 | バックアップ? |
現在Walnuts家には5台のマシンがいます。そのうちMini PCのcake、ラズパイのsnow・donutはk8s専用サーバーとして使っています。自作PCのaliceはProxmoxを入れてVM基盤を作り、その上にWindows、Manjaroのクライアントマシン、k8s Nodeのマシン、検証環境などを立てています。
特に、Windows仮想マシンはホストマシンのGPUやUSBデバイスをアタッチすることによって普通のデスクトップPCのように使えるようにしています。モニターに直接映像出せて便利~
walnuts.devの全サービスをk8sに移行したので、VMを常駐サーバーとしては使わなくなりました。常駐マシンはcake、snow、donutだけですね。数カ月前まではcakeがなかったので、たった6GBでk8sを運用していました。よく動いていたな...という感じです、、、
常駐させたときの消費電力は大体30Wくらいです。めちゃめちゃエコ~~~
物理ネットワークは特になにも面白いところはないです...
図を見ると分かる通り、自宅内のルーターの上に共用ルーターがあります。そのせいで自宅ではポート開放などができません。
そこで、Cloudflare Tunnelを使うことで各サイトを公開しています。
Cloudflare Tunnelはその名の通り、Cloudflareと自宅間でトンネルを構成し、特定のドメインに来たリクエストをプロキシし、自宅側に転送してくれるサービスです。
アウトバウンド接続のみで外部アクセスができるようになるのでポート開放は不要です。
自宅ではCloudflaredをsnow上で動かし、*.walnuts.devをプロキシしています。本当はk8s Podとして動かしたいのですが謎のエラーによりまだ動かせていません。
Kubernetes構成編などでまた説明しますが、ExternalDNSにパッチを当て、Ingressリソースから自動でCloudflare Tunnelの設定を行うようにしています。Ingressをつくるだけで外部公開サイトを追加できるのでめちゃめちゃ便利です。
HTTP/HTTPS以外はTailscaleを用いて外部からのアクセスを実現しています。SSHやRDPなどは自分しか使わないので、ClientAppが必要なTailscaleでも今の所問題は起きていません。
k8s内のネットワークはMetalLB と Flannel を用いていい感じにしています。そしてkube-api-serverへのアクセスはkeepalivedを用いて冗長構成としています。
うーん、物理環境とネットワークについてはあんまりおもしろい内容はありませんでしたね......。
次回以降のKubernetes関連記事ではもっといろいろ面白い話があると思うので期待していてください!
明日の記事はヘルファイアいしだ さんの記事です!お楽しみに!