Gateway APIとNGINX Gateway Fabricを自前のKubernetesクラスタで
まとめ
今回はおうちの自前Kubernetesクラスタで、2023年10月にv1.0.0リリースを迎えたKubernetes Gateway APIを動かしてみました。
- 自前のKubernetesクラスタなのでパブリッククラウドサービスのようにServiceのexposeに難あり
- MetalLB導入でLAN上、クラスタ外からアクセスできるようにした
- GitOps (Flux)でやりたい
- TLS証明書の内容をそのままgitに載せずにすむようSOPS暗号化復号化レポジトリを用意した
- Gateway APIを試したい
- NGINX Gateway FabricがGateway Controllerとして利用できた
- Gatewayとアプリでnamespaceおよびgitレポジトリを分けて作ってみた(=管理担当チームが分かれているイメージ)
本題であるGateway APIでどうこうし始める前にセットアップについて紹介していきます。おおまかに以下をカバーしています。
- セットアップの紹介
- Kubernetesクラスタ
- GitLab + Flux
- metallb
- SOPS
- Gateway APIをクラスタへ導入
- NGINX Gateway FabricをコントローラとしてGateway APIを利用
セットアップの紹介
自前の環境でいろいろ自由に試して遊んでみたいと思い、おうちのLANにDockerやKubernetesで動かしているものをあれこれ持っています。
遊びで好き勝手にKubernetesクラスタをいじり始めると、気軽にあれこれマニフェストを投入してしまいます。そうなると次第にクラスタの状態が分からなくなってしまったので、今はFluxとGitLabを利用したGitOps環境をセットアップしてクラスタの状態を把握できるようにしています。
最初にGitLabを自前の環境で用意したときの投稿が、以下1つ目のリンクです。そしてKubernetesで遊び始めて、自前のGitlabとkubernetesクラスタを、Fluxを通じて管理するGitOps環境を用意したときの投稿が2つ目のリンクとなります。興味があればご覧くださいませ。
特に、今回のポストでは事前準備にあたる部分はおおまかな流れの紹介だけになります。そのような箇所の詳細に関してはそれぞれの公式ドキュメントなどを参照されるか、以下のシリーズの中でも確認できます。
なお、SOPSのセットアップに関しては私の過去の投稿でカバーしていないので、本投稿で詳細を紹介していきます。
Series TOP: Dockerで作るおうちLAN遊び場
Kubernetesクラスタのセットアップ紹介
クラスタの構築はKubernetes公式ドキュメントを参考に普通に作りました。公式ドキュメントをなぞっただけですが、先のリンクにある投稿でも書いたことがあるので、ここではおおまかにやったことをリストだけします。
なお公式ドキュメントですが、こちらのページから追うのが良いと思います。
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
- Raspberry Pi 4b+ setup
- Raspberry Pi OS Lite (Debian 12), headless, static IP address
- disable swap
- enable cgroup memory
- follow k8s doc to load network related modules and prep sysctl conf
- install containerd
- configure it to use systemd cgroup
- prep systemd service unit
- install runc
- install CNI plugin
- install kubeadm, kubelet, and kubectl
- and setup a few more amd64 arch nodes similar to the pi
- Setup a kubernetes cluster
-
kubeadm init
on Raspberry Pi and also on another amd64 node as the control plane nodes- prepare a kubeadm config file to
kubeadm init
to create a cluster- edit
controlPlaneEndpoint
andnetworking.podSubnet
- edit
- copy over the cert/key files to the second node to join as the second control plane node
- prepare a kubeadm config file to
- join rest of the nodes to the cluster
-
- Apply a network addon manifest
- the Kubernetes cluster is now ready and running
GitOps - GitLabとFlux
続いて簡単にGitOpsのセットアップもカバーしていきます。以下の手順でセットアップ後、"flux"というレポジトリ内、./clusters/my-cluster/
以下に用意したマニフェストがFluxによって自動で拾われ、クラスタに適用されるようになります。
- Control planeなどにflux CLIをインストール
- GitLab上でkubernetesクラスタを管理するためのレポジトリを作成
- create a group "gitops"
- create a project "flux" under the "gitops" group
- repo url = https://{gitlab server}/gitops/flux.git
- gitopsグループの管理メニューより、ownerロールでaccess tokenを払い出し
- トークン認証を用いてflux CLIでbootstrapする
- GitOps環境完成
最近、GitLabのaccess tokenの設定可能有効期限が短くなりました。私の以前の投稿では、access tokenの期限を長く設定してセットアップを進めていましたが、今回は期限の短いトークンでbootstrapしています。具体的には以下のコマンドでbootstrapしており、bootstrapプロセス中にfluxとGitLabでやり取りしてdeploy tokenを別途作るようになりました。
❯ flux bootstrap gitlab \
--deploy-token-auth \
--hostname={gitlab server} \
--owner=gitops \
--repository=flux \
--path=./clusters/my-cluster \
--branch=main
► connecting to https://{gitlab server}
► cloning branch "main" from Git repository "https://{gitlab server}/gitops/flux.git"
✔ cloned repository
► generating component manifests
✔ generated component manifests
✔ committed sync manifests to "main" ("fca8daa1a033c5a4e002e0ecfe85bc3b411d4310")
► pushing component manifests to "https://{gitlab server}/gitops/flux.git"
► installing components in "flux-system" namespace
✔ installed components
✔ reconciled components
► checking to reconcile deploy token for source secret
✔ configured deploy token "flux-system-main-flux-system-./clusters/my-cluster" for "https://{gitlab server}/gitops/flux"
► determining if source secret "flux-system/flux-system" exists
► generating source secret
► applying source secret "flux-system/flux-system"
✔ reconciled source secret
► generating sync manifests
✔ generated sync manifests
✔ committed sync manifests to "main" ("4055a74b2f3039d53f30f5e7e3047a0cd82eb18b")
► pushing sync manifests to "https://{gitlab server}/gitops/flux.git"
► applying sync manifests
✔ reconciled sync configuration
◎ waiting for Kustomization "flux-system/flux-system" to be reconciled
✔ Kustomization reconciled successfully
► confirming components are healthy
✔ helm-controller: deployment ready
✔ kustomize-controller: deployment ready
✔ notification-controller: deployment ready
✔ source-controller: deployment ready
✔ all components are healthy
❯ flux version
flux: v2.1.2
helm-controller: v0.36.2
kustomize-controller: v1.1.1
notification-controller: v1.1.0
source-controller: v1.1.2
❯ flux get all
NAME REVISION SUSPENDED READY MESSAGE
gitrepository/flux-system main@sha1:4055a74b False True stored artifact for revision 'main@sha1:4055a74b'
NAME REVISION SUSPENDED READY MESSAGE
kustomization/flux-system main@sha1:4055a74b False True Applied revision: main@sha1:4055a74b
MetalLBの導入
次にMetalLBの導入です。
Google Cloud Platform, GCPなどのパブリッククラウドサービスでは、serviceのマニフェストでLoadBalancerを要求するとクラスタ外からアクセスできるIPアドレスがセットされますが、自前のただのkubernetesクラスタにはそういった機能がありません。
MetalLBはLANでkubernetesクラスタ外からクラスタ内のサービスにアクセスできるIPアドレスの割り振り、及びクラスタ外、実LAN環境へアドレスを広報してくれるサービスです。使い方としては、Serviceマニフェストで.spec.type
で"LoadBalancer"として作成すると、MetalLBがIPアドレスプールからingress IPとしてセットしてくれます。
なお以前の投稿ではkustomizationで導入したのですが、今回はhelmを利用します。
そのあたりの手順だけは詳細をカバーしていきます。
おおまかな流れは次の通りです。
- create and push .sourceignore for flux to ignore helm values.yaml files
- prepare a directory for metallb in the gitops repo
- prepare a namespace manifest
- prepare helm values.yaml file in the directory
- prepare flux HelmRepository and HelmRelease manifests
- push to apply
- create IPAddressPool and L2Advertisement manifests
- push to apply
Flux向け .sourceignore とディレクトリの用意
まず./clusters/my-cluster
以下に、fluxに無視させるファイルを指定するための.sourceignore
ファイルと、metallb用のディレクトリを用意します。
# clone if not done so
# git clone https://{gitlab server}/gitops/flux.git
cd flux/clusters/my-cluster
# create .sourceignore file here
# and then create metallb dir and move there
mkdir metallb && cd metallb
雑ですが、作成した.sourceignore
ファイルの内容はこちらです。
今後Helmを利用し、コンフィグ用にvalues.yaml
ファイルをいじる時に、
それらコンフィグファイル自体はfluxによってクラスタへ導入してもらいたい内容ではないため、無視させます。
# clusters/my-cluster/.sourceignore
**/values.yaml
**/*values.yaml
metallb namespace
namespaceマニフェストを用意します。
# clusters/my-cluster/metallb/ns.yaml
---
kind: Namespace
apiVersion: v1
metadata:
name: metallb-system
Helm repoやvalues.yaml
次に、MetalLBのhelmレポジトリよりvalues.yaml
ファイルを用意します。
わざわざ2つのファイルdefault-values.yaml
とvalues.yaml
を用意しているのですが、
diff values.yaml default-values.yaml
などで変更内容確認したいという狙いでこのようにしています。
# add the metallb helm repository
helm repo add metallb https://metallb.github.io/metallb
# generate the values.yaml file using the helm repository
helm show values metallb/metallb > default-values.yaml
cp default-values.yaml values.yaml
なお、今回は何もここで設定変更しないです。意味無いですね。
Flux向けのHelmRepositoryとHelmReleaseマニフェスト
Flux向けに外部sourceとしてHelmRepositoryマニフェスト、
そしてクラスタへの導入用にHelmReleaseマニフェストを用意します。
Flux CLIでマニフェストファイルが用意できるのですが、
スクリプトからファイル作成することにします。
スクリプト内でvalues.yaml
を参照してマニフェスト生成するよう指定していますが、
先に触れた通り、何も設定変更していないので意味無しです!
# clusters/my-cluster/metallb/generate.sh
#!/bin/bash
# helm source
flux create source helm metallb \
--url=https://metallb.github.io/metallb \
--interval=1h0m0s \
--namespace=metallb-system \
--export > metallb.yaml
# helm release
flux create helmrelease metallb \
--source=HelmRepository/metallb \
--chart=metallb \
--values=./values.yaml \
--interval=10m \
--namespace=metallb-system \
--target-namespace=metallb-system \
--export >> metallb.yaml
このスクリプトを走らせるとFlux向けのマニフェストファイルができます。"values"は長いので、こちらは省略した出力です。
chmod u+x generate.sh
./generate.sh
❯ cat metallb.yaml
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: metallb
namespace: metallb-system
spec:
interval: 1h0m0s
url: https://metallb.github.io/metallb
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: metallb
namespace: metallb-system
spec:
chart:
spec:
chart: metallb
reconcileStrategy: ChartVersion
sourceRef:
kind: HelmRepository
name: metallb
interval: 10m0s
targetNamespace: metallb-system
values:
...
ここまででPUSH
一旦ここまでで、.sourceignore
やmetallbのns.yaml
、metallb.yaml
をレポジトリにpushして、fluxによってクラスタへ適用してもらいます。
適用した結果、helm関連のリソースは次のようになっていると思います。
実働リソースに関しては省略しますが、kubectl get all -n metallb-system
でmetallb用にpodなどが作られていることが確認できると思います。
# kubectl
❯ kubectl get helmrepo -n metallb-system
NAME URL AGE READY STATUS
metallb https://metallb.github.io/metallb 4d20h True stored artifact: revision 'sha256:bd1fd831e1b997b949fd46a0f4bdf2525393933b682df5f0914fd5707287e281'
❯ kubectl get hc -n metallb-system
NAME CHART VERSION SOURCE KIND SOURCE NAME AGE READY STATUS
metallb-system-metallb metallb * HelmRepository metallb 4d20h True pulled 'metallb' chart with version '0.13.12'
❯ kubectl get hr -n metallb-system
NAME AGE READY STATUS
metallb 4d20h True Release reconciliation succeeded
# flux
❯ flux get sources helm -n metallb-system
NAME REVISION SUSPENDED READY MESSAGE
metallb sha256:bd1fd831 False True stored artifact: revision 'sha256:bd1fd831'
❯ flux get sources chart -n metallb-system
NAME REVISION SUSPENDED READY MESSAGE
metallb-system-metallb 0.13.12 False True pulled 'metallb' chart with version '0.13.12'
❯ flux get helmrelease -n metallb-system
NAME REVISION SUSPENDED READY MESSAGE
metallb 0.13.12 False True Release reconciliation succeeded
現時点で、レポジトリはこんな感じです。
❯ tree
.
|-clusters
| |-my-cluster
| | |-flux-system
| | | |-kustomization.yaml
| | | |-gotk-sync.yaml
| | | |-gotk-components.yaml
| | |-metallb
| | | |-generate.sh
| | | |-ns.yaml
| | | |-metallb.yaml
| | | |-values.yaml
| | | |-default_values.yaml
| | |-.sourceignore
|-README.md
|-.git
❯ which tree
tree: aliased to find . -not -path "*/\.git/*" -not -path "*/venv/*" | sed -e "s/[^-][^\/]*\// |/g" -e "s/|\([^ ]\)/|-\1/"
その他の設定
あとは追加設定マニフェストを用意してプッシュします。
アドレスに関しては、同じサブネットより未使用かつDHCPでもカバーされていないレンジを好きに指定すれば良いと思います。
ここでは192.168.1.200-192.168.1.220をmetallbが払い出せるレンジとして指定しています。
# clusters/my-cluster/metallb/config.yaml
---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: lan-addr-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.200-192.168.1.220
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2adv-addr-pool
namespace: metallb-system
プッシュすると、それぞれ以下のようにクラスタ内に適用されていることが確認できると思います。
❯ kubectl get IPAddressPool -n metallb-system
NAME AGE
lan-addr-pool 4d21h
❯ kubectl get L2Advertisement -n metallb-system
NAME AGE
l2adv-addr-pool 4d21h
以上でMetalLBの導入は完了です。
SOPS
次にSOPSを導入します。一言でいうと暗号化のためのツールです。
GitOpsでkubernetesクラスタを管理すると、
secretマニフェストの内容もそのままファイルでレポジトリに載ります。
正直に言えば、自分で管理しているGitLabサーバとkubernetesクラスタで
おうちのLAN上で遊んでいるので心配はないのですが、
本来ならばどうすべきかというところも知りたかったのでSOPSを利用したsecret暗号復号環境をセットアップしました。
おおまかな流れは次のとおりです。
また、細かい流れもこれから紹介していきますが、
上のリンク、fluxのドキュメント通りにやっているだけとなります。
私はクラスタのcontrol planeノード、ラズパイで実施しました。
- install gnupg and sops cli
- generate rsa 4096 gpg keypair
- create a secret on flux-system namespace using the generated private key
- create a new project on GitLab
- "gitops/my-secrets"
- generate deploy token with repo-read privilege
- pass on the deploy token to flux as secret
- setup the repository as an additional git source for flux to watch
- configure to use SOPS to decrypt the manifests in the repository
- configure to use the private key secret generated in the third step
- set
./clusters/my-cluster
as the path for flux to watch
- create and push the SOPS configuration file on this new repository
- test
- import the gpg keypair on any other machine that needs to encrypt manifest files
GNUPGとSOPS CLIのインストール
GnuPGとSOPSをインストールする必要があります。
# on rpi, the control plane node
# gnupg, if not installed
sudo apt install gnupg
# sops
mkdir ~/dnld && cd ~/dnld
curl -LO https://github.com/getsops/sops/releases/download/v3.8.1/sops-v3.8.1.darwin.arm64
sudo mv sops-v3.8.1.darwin.arm64 /usr/local/bin/sops
sudo chmod u+x /usr/local/bin/sops
# check sops
sops --version
キー生成
gpgでキーを生成します。
変数としてKEY_NAMEとKEY_COMMENTをセットしますが、内容は自由にしてください。
私は自分のドメイン名で変数をセットして進めました。
# export env vars
export KEY_NAME="my-cluster.yourdomain.com"
export KEY_COMMENT="flux secrets"
# generate keypair
gpg --batch --full-generate-key <<EOF
%no-protection
Key-Type: 1
Key-Length: 4096
Subkey-Type: 1
Subkey-Length: 4096
Expire-Date: 0
Name-Comment: ${KEY_COMMENT}
Name-Real: ${KEY_NAME}
EOF
プライベートキーをSecretとしてFluxへ渡す
生成したキーペアのプライベートキーをSecretとしてflux-system namespace上に作成します。
まずはプライベートキーのfinger printを取得しますが、
gpg --list-secret-keys "${KEY_NAME}"
の出力結果、
以下の例では"123451234512345..."などの英数字文字列がfinger printです。
そしてKEY_FPという変数をその文字列でセットします。
# find the private key finger print
gpg --list-secret-keys "${KEY_NAME}"
# output should be like this
gpg: checking the trustdb
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
sec rsa4096 2023-11-08 [SCEA]
12345123451234512345
uid [ultimate] KEY_NAME_here (flux secrets)
ssb rsa4096 2023-11-08 [SEA]
# set env var using the finger print
export KEY_FP=123451234512345
そして次のコマンドで"sops-gpg"という名称のsecretを、
flux-system namespace上に作成します。
# generate secret
gpg --export-secret-keys --armor "${KEY_FP}" |
kubectl create secret generic sops-gpg \
--namespace=flux-system \
--from-file=sops.asc=/dev/stdin
Secret用の別レポジトリの用意
次に、一度ターミナルから離れ、GitLabに移りましょう。
"gitops"グループ下に"my-secrets"という新プロジェクトを作成しましょう。
このレポジトリがflux用のsecret暗号復号専用レポジトリとなります。
deploy tokenからsecretを作成
この新レポジトリのdeploy tokenを作成しましょう。スコープはread_repository
とします。
ユーザ名と文字列が払い出されますので控えておきます。次のステップで使います。
Secret用レポジトリをFluxの追加gitソースとして設定
まずfluxのために、deploy tokenのsecretを作成します。
# create a flux git secret named "deploy-token-my-secrets"
flux create secret git deploy-token-my-secrets \
--url=https://{gitlab server}/gitops/my-secrets \
--namespace=flux-system \
--username=DEPLOY_TOKEN_USERNAME_HERE \
--password=DEPLOY_TOKEN_PASSWORD_HERE
次に、"gitops/flux"レポジトリ上に専用ディレクトリを用意して、
flux用のGitRepositoryマニフェスト、Kustomizationマニフェストを用意します。
ここでも、flux CLIでマニフェストファイルを用意するスクリプトを用意しておきます。
どうやってマニフェストファイルを生成したのか、いちいちコマンドを控えておかないと忘れてしまいますので!
# git clone the new "my-secrets" repo
cd clusters/my-cluster
mkdir my-secrets && cd my-secrets
# create "generate.sh" script file
chmod u+x generate.sh
./generate.sh
generate.sh
ファイルの中身は次のとおりです。
1つ目のセクションは
"my-secrets"という名前のGitRepositoryを作成するためのマニフェスト生成コマンドですが、
このレポジトリにアクセスするために、先程作成したsecretを参照しています。
2つ目のセクションは
"my-secrets"という名前のKustomizationを作成するためのマニフェスト生成コマンドですが、
ソースとして上で作成したgit sourceを指定、
pathは./clusters/my-cluster
を指定、
そしてdecryptはSOPSを使うこと、
decryptに使うsecretは少し前のステップで作成した"sops-gpg"という名称の
generic secretを指定しています。
❯ cat generate.sh
#!/bin/bash
# git source
flux create source git my-secrets \
--url=https://{gitlab server}/gitops/my-secrets.git \
--namespace=flux-system \
--branch=main \
--secret-ref=deploy-token-my-secrets \
--export >> my-secrets.yaml
# kustomization
flux create kustomization my-secrets \
--namespace=flux-system \
--source=GitRepository/my-secrets \
--path="./clusters/my-cluster" \
--prune=true \
--interval=10m \
--decryption-provider=sops \
--decryption-secret=sops-gpg \
--export >> my-secrets.yaml
git pushしてしばらくすると、以下のようにgit sourceとkustomizationが作成されていることが確認できると思います。
❯ flux get source git
NAME REVISION SUSPENDED READY MESSAGE
flux-system main@sha1:05a8e81b False True stored artifact for revision 'main@sha1:05a8e81b'
my-secrets main@sha1:7d4ffbda False True stored artifact for revision 'main@sha1:7d4ffbda'
❯ flux get kustomization
NAME REVISION SUSPENDED READY MESSAGE
flux-system main@sha1:05a8e81b False True Applied revision: main@sha1:05a8e81b
my-secrets main@sha1:7d4ffbda False True Applied revision: main@sha1:7d4ffbda
SOPS設定ファイル
最後のステップとして、secret専用レポジトリとして用意した"gitops/my-secrets"内、
./clusters/my-cluster
内に、
.sops.yaml
という名称でSOPS設定ファイルを用意します。
ファイル内容は次のとおりで、"pgp: ..."のところには、
上のステップで確認した、生成したGPGプライベートキーのfinger printを記載してください。
# ./clusters/my-cluster/.sops.yaml
creation_rules:
- path_regex: .*.yaml
encrypted_regex: ^(data|stringData)$
pgp: 1234512345...KEY_FP_HERE
完成!そしてテスト!
以上でGPG、SOPSを用いたGitOps用暗号復号レポジトリの完成です。
引き続きfluxのドキュメントどおりに試験してみましょう。
まず"my-secrets"レポジトリをcloneし、
テスト用にsecretマニフェストファイルを作成します。
# cd {wherever you want to git clone "my-secrets" repo}
git clone https://{gitlab server}/gitops/my-secrets.git
cd my-secrets/clusters/my-cluster
# create a test secret file "basic-auth.yaml" here
kubectl -n default create secret generic basic-auth \
--from-literal=user=admin \
--from-literal=password=change-me \
--dry-run=client \
-o yaml > basic-auth.yaml
中身は次のとおりです。dataのvalueがbase64 encodeされている状態です。decodeは容易です。
# see how this secret manifest file looks like
❯ cat basic-auth.yaml
apiVersion: v1
data:
password: Y2hhbmdlLW1l
user: YWRtaW4=
kind: Secret
metadata:
creationTimestamp: null
name: basic-auth
namespace: default
# can be easily decoded
echo Y2hhbmdlLW1l | base64 --decode
ではこのファイルをsopsで暗号化しましょう。実行コマンドと結果は次のとおりです。
# now, encrypt it
sops --encrypt --in-place basic-auth.yaml
# and check the file again
# values in the data section are all encrypted
❯ cat basic-auth.yaml
apiVersion: v1
data:
password: ENC[AES256_GCM,data:a3McsktUMZwJuec1,iv:6JfvLCswr4rLPEkSWcFwhfcRNz2hqSywfgMjt9pxMgA=,tag:0sXQlOONDTACj5molxHDtA==,type:str]
user: ENC[AES256_GCM,data:cYfdqW7GnGY=,iv:iPqbulmWXCVjdoyKwx6tGsd04Tnk1tU4ZE61bqNMp+A=,tag:3fgwzlvMJBNt+xLMLONmhw==,type:str]
kind: Secret
metadata:
creationTimestamp: null
name: basic-auth
namespace: default
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age: []
lastmodified: "2023-11-09T04:42:52Z"
mac: ENC[AES256_GCM,data:F5ydPO6F+vH/V28FEDW8SOLZRaqZdsqLDy8cwkf3DxM1GTsfg77Ww1uS0/BO+xNEJq2y92ldQP95CWdipUG+gK77uv+sMrTHXXUfomB2Fpd1hSOxhoL7MsX+mpKymc6OjFfEqOqYTZ6RyoHQ/3UZQM2lfL76VRlOcVYYMEzjs4s=,iv:u8cKwG3eGssFy0TNTilNlc7khrfQEn0OYN0i/WnM5KI=,tag:Vb9j1/7ztfVetR7hNpxrjg==,type:str]
pgp:
- created_at: "2023-11-09T04:42:52Z"
enc: |-
-----BEGIN PGP MESSAGE-----
hQIMA2trso+bhZyNAQ/+K+uI52xtfFtgARXkobixrc+SG99vRKEGCKCRJVjMPLrH
FF9C5DJBRbOYadF4PNLL8vMl23ebdUkrqCDzd2GJkxRXzs/6TwajVQ/l3hXxuD6K
KOHnOkPWwo3gyBaG2GAu+rjt/DN9mKa32uhzmxBtcUSGPGRGU2XwRoso0B4WhEbl
nUo9d5QS4x8njH3kf8PspzUEI9F8pJ2CVsYuAwq0r83Lw2gs6DGZWiT0pTogWsOT
KLmSFTkPvu6B7aN6fdz1f/IxmVi6aykq6M3v7ChxCZxfyLZMTSOHS5N4bu4V3R4r
naf7O94/q3Wq8bGMv/Pyq+r0g+dTHVaxzO0dtxUA6pJs/D5gFEyKpnSjlxUQXfFF
I1sDiu0O/zuyZUSt3x6Kb2FbilsvpdJSJ4YMzpFMkGsepEO9KXEO1y7mIjCMqKsJ
8Arr83YsnRskmBHU3V5C6nEjwlQCKatuPoW4oJReHyH1NZJ2N64aP+sAHpwNt1pN
V6Ge/9yP28CpYLvREV2g9k0glLcbi9evClpz1ziyqHdizS4n2VRflSakEFUSyK0r
43O/1kKr6AxKr1ukewScV3IMt/CNbAAkinPAe63Zqi6DTMNHLZP/Rcr86OsaRLlb
KtotGHeTh9Az/mvbqeXraT0Gq0Xr9KEZjTXT7nsE7GRi/1d+KYfGwS3hKM9bDu3S
XAFGXDdU7/Ik3eFRPFtpimeRGVJpjEsF2jrMSKDYHmi6has/U0mmsjQy/HpaD44Q
wigKrzdDMRkNveMzgaAcbcuC+n7o/dvff9sFepW8MXyBPh5doXY8xnO4YpVa
=QQiK
-----END PGP MESSAGE-----
fp: 8F337122315C3F0A14F9635AE5A930D3A26B2675
encrypted_regex: ^(data|stringData)$
version: 3.8.1
これをgit pushするとfluxがsopsで復号化し、
default namespace上にbasic-authという名称のsecretを
作成してくれていることが確認できます。
data以下、valueもkubernetes secret本来のbase64 encode状態になっています。
❯ kubectl get secret basic-auth -o yaml
apiVersion: v1
data:
password: Y2hhbmdlLW1l
user: YWRtaW4=
kind: Secret
metadata:
creationTimestamp: "2023-11-08T14:25:59Z"
labels:
kustomize.toolkit.fluxcd.io/name: my-secrets
kustomize.toolkit.fluxcd.io/namespace: flux-system
name: basic-auth
namespace: default
resourceVersion: "1741119"
uid: 76c9399b-7670-4eea-a26a-a7646296b797
type: Opaque
Gateway APIを試そう
やっと本題です。なんということでしょう。長かったです。
それでは、Gateway APIです。listenerやHTTP routingが設定できるものです。Gateway Controllerを併せて導入して利用します。
公式ドキュメントによる一言説明はこちらです。
It is an API (collection of resources) that model service networking in Kubernetes.
登場人物紹介
まずは登場人物を紹介します。
- Gateway API
- 公式のインストールマニフェスト/CRDsをクラスタにapplyします
- Gateway Controller
- NGINX Gateway Fabric, NGFをコントローラとして導入、利用します
- 通信の最初の受け口としてのServiceが作られます
.spec.type = LoadBalancer
- 今回のセットアップではMetalLBより実LANからアクセス可能なサブネットよりIPアドレスを割り当てます
- Serviceの先としては、GatewayおよびGatewayと関連付けられたHTTPRouteの設定に従って通信を処理します
- Gateway Class
- 今回はNGFコントローラを導入することでnginxというgateway classが用意されます
- Gatewayの定義
- Gateway
- NGFのserviceがどのように動作すべきかを定義します
- port, protocol, どういったリソースからアクセス可能であるかの設定など
- 実際にマニフェストファイルを書くことになります
- HTTPRoute
- http通信に関するrouterです
- 親としてgatewayを指定します
- upstream/backendとしてアプリケーションのServiceを指定します
- その他pathなどのルールが指定できます
- Service
- アプリケーションへのアクセス口です
- HTTPRouteの宛先となります
- Deployment/Pods
- 実際のアプリケーション
レポジトリリスト
そしてこれから用意するものに関して、こちらがレポジトリの観点からのまとめです。
- "gitops/flux"
- the main gitops repository
- install Kubernetes Gateway API v1 CRDs
- flux helmrepo and helmrelease to deploy NGINX Gateway Fabric
- [NEW] "gitops/gateway"
- external repository to manage "Gateway" manifest
- [NEW] "gitops/demo-cafe"
- external repository for a demo application
- Deployment/Pods
- Service
- HTTPRoute
- "gitops/my-secrets"
- SOPS enc/dec repo to store secret manifest files
- stores tls cert/key secret for the gateway to handle https
Namespaceリスト
次にnamespaceのまとめです。
- nginx-gateway
- Service listener of the gateway
- certificate
- Stores the TLS certificate on "gitops/my-secrets" repo, encrypted
- gateway
- Gateway maniest, the definition on how the NGF should behave
- Uses selector to define which namespace can use the gateway
- demo-cafe
- Demo app - deployment and service
- HTTPRoute
セットアップ手順
セットアップを進めていきますが、おおまかな流れは次のとおりです。
- create GitLab repositories, "gitops/gateway" and "gitops/demo-cafe"
- generate read_repo deploy token on each repo
- create the deploy token secret for each repo
- add both repositories to flux using git source and kustomization
- install Kubernetes Gateway API v1.0.0 CRDs
- install NGINX Gateway Fabric using flux helm repo and helm release
- create a gateway
- create demo app deployment and service
- create HTTPRoute
新プロジェクト作成
"gitops/gateway"と"gitops/demo-cafe"プロジェクトをGitLab上に作成します。my-secretsプロジェクトと同様に、read-repo権限のdeploy tokenをそれぞれ生成し、fluxのgit sourceとkustomizationもセットしましょう。
# new project "gitops/gateway"
project name: gateway
token name: deploy-token-gateway
scopes: read_repository
token username: {TOKEN_USERNAME}
token password: {TOKEN_PASSWORD}
# new project "gitops/demo-cafe"
project name: demo-cafe
token name: deploy-token-demo-cafe
scopes: read_repository
token username: {TOKEN_USERNAME}
token password: {TOKEN_PASSWORD}
deploy tokenのsecret作成
プロジェクトを作り、deploy tokenを生成したら、せっかくなので"gitops/my-secrets"レポジトリで暗号化したマニフェストでsecretを作成しましょう。
# create deploy token secret for each
# on "gitops/my-secrets" repository
cd clusters/my-cluster
flux create secret git deploy-token-gateway \
--url=https://{gitlab server}/gitops/gateway.git \
--namespace=flux-system \
--username={TOKEN_USERNAME} \
--password={TOKEN_PASSWORD} \
--export > deploy-token-gateway.yaml
flux create secret git deploy-token-demo-cafe \
--url=https://{gitlab server}/gitops/demo-cafe.git \
--namespace=flux-system \
--username={TOKEN_USERNAME} \
--password={TOKEN_PASSWORD} \
--export > deploy-token-demo-cafe.yaml
# encrypt them
sops --encrypt --in-place deploy-token-gateway.yaml
sops --encrypt --in-place deploy-token-demo-cafe.yaml
# commit and push
# confirm the secrets are created
kubectl get secret -n flux-system
fluxに作成したレポジトリを追加
fluxへ追加git sourceとkustomizationをセットします。
なおgatewayのkustomizationに関してはnamespaceを指定しておらず、demo-cafeはkustomizationでtarget-namespaceを"demo-cafe"であると指定しています。ついでに"demo-cafe" namespaceの作成マニフェストもメインの"gitops/flux"レポジトリ上で用意を済ませておきます。
こうすると、"gitops/demo-cafe"レポジトリから別のnamespaceになにか作ろうとしてもできません。
また、"demo-cafe" namespaceには一つgateway-enabled=yes
というラベルをセットしておきます。Gateway作成時にまた触れますが、このラベルを利用してどのnamespaceからはgatewayが利用できる・できないを制御します。
# on "gitops/flux" repository
# create a dedicated dir for gateway and demo-cafe
cd clusters/my-cluster
mkdir gateway demo-cafe
cd gateway
# create generate.sh to generate flux manifests using flux CLI
# git source for "gitops/gateway"
cat > generate.sh <<"EOF"
flux create source git gateway \
--url=https://{gitlab server}/gitops/gateway.git \
--namespace=flux-system \
--branch=main \
--secret-ref=deploy-token-gateway \
--export >> gateway.yaml
# kustomization
flux create kustomization gateway-kustomization \
--namespace=flux-system \
--source=GitRepository/gateway \
--path="./" \
--prune=true \
--interval=1m \
--export >> gateway.yaml
EOF
chmod u+x generate.sh
./generate.sh
# git commit and push
# and then do the same for "gitops/demo-cafe" repo
cd ../demo-cafe
cat <<"EOF" > generate.sh
# git source
flux create source git demo-cafe \
--url=https://{gitlab server}/gitops/demo-cafe.git \
--namespace=flux-system \
--branch=main \
--secret-ref=deploy-token-demo-cafe \
--export > demo-cafe.yaml
# kustomization
flux create kustomization demo-cafe-kustomization \
--namespace=flux-system \
--target-namespace=demo-cafe \
--source=GitRepository/demo-cafe \
--path="./" \
--prune=true \
--interval=1m \
--export >> demo-cafe.yaml
EOF
# additionally, create the "demo-cafe" namespace
cat <<EOF > ns.yaml
---
apiVersion: v1
kind: Namespace
metadata:
name: demo-cafe
labels:
gateway-enabled: yes
EOF
chmod u+x generate.sh
./generate.sh
# git commit and push
# confirm that the new items are shown for both GitRepository and Kustomization
flux get all
Kubernetes Gateway API v1.0.0 CRD
Gateway APIはメインレポジトリ"gitops/flux"にマニフェストを設置してインストールさせておきます。
# on "gitops/flux" repository
cd clusters/my-cluster/gateway
# download the manifest
curl -LO https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
# git commit and push
インストール完了するとkubectl api-resources
でこれらが追加されていることが確認できます。
❯ kubectl api-resources | grep gateway.networking
gatewayclasses gc gateway.networking.k8s.io/v1 false GatewayClass
gateways gtw gateway.networking.k8s.io/v1 true Gateway
httproutes gateway.networking.k8s.io/v1 true HTTPRoute
referencegrants refgrant gateway.networking.k8s.io/v1beta1 true ReferenceGrant
NGINX Gateway Fabric
次にgateway controllerとしてNGINX Gateway Fabricを導入します。
# on "gitops/flux" repository
cd clusters/my-cluster
mkdir ngf && cd ngf
# download values.yaml file
helm show values oci://ghcr.io/nginxinc/charts/nginx-gateway-fabric > default_values.yaml
cp default_values.yaml values.yaml
# flux CLI script
cat > generate.sh <<"EOF"
#!/bin/bash
# helm source
flux create source helm nginx-gateway-fabric \
--url=oci://ghcr.io/nginxinc/charts \
--interval=1h0m0s \
--export > nginx-gateway-fabric.yaml
# helm release
flux create helmrelease nginx-gateway-fabric \
--interval=10m \
--target-namespace=nginx-gateway \
--create-target-namespace=true \
--source=HelmRepository/nginx-gateway-fabric \
--chart=nginx-gateway-fabric \
--chart-version=1.0.0 \
--values=values.yaml \
--export >> nginx-gateway-fabric.yaml
EOF
chmod u+x generate.sh
./generate.sh
# commit and push
順序が前後してしまいますが、values.yaml
は変更せずとも問題なく利用できると思います。ただ私の場合は特定のIPアドレスをgatewayに振りたかったので、以下のような行を追加しています。これはMetalLBに特定のIPアドレスを利用させるためのものです。IPAddressPoolマニフェスト内、.spec.addresses
のリストより追加できます。
❯ diff values.yaml default_values.yaml
95,97c81
< # EDIT: specify metallb static IP address
< annotations:
< metallb.universe.tf/loadBalancerIPs: 192.168.1.54
# existing clusters/my-cluster/metallb/config.yaml, IPAddressPool manifest
spec:
addresses:
- 192.168.1.200-192.168.1.220
- 192.168.1.54
Fluxがhelm source, helm chart, helm releaseと処理し終えると、以下のようにpodなどが作られていることが確認できます。
❯ kubectl get all -n nginx-gateway
NAME READY STATUS RESTARTS AGE
pod/nginx-gateway-nginx-gateway-fabric-8459c977d8-8kx58 2/2 Running 1 (2m25s ago) 2m41s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx-gateway-nginx-gateway-fabric LoadBalancer 10.102.18.210 192.168.1.54 80:31941/TCP,443:32119/TCP 2m41s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx-gateway-nginx-gateway-fabric 1/1 1 1 2m41s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-gateway-nginx-gateway-fabric-8459c977d8 1 1 1 2m41s
Gateway作成
以降の手順ですが、おおよそNGFのGitHubレポジトリ上にある例の通りとなります。主な違いとしては、どのnamespaceに何を置くかが異なり、また先に触れている通りどのnamespaceのリソースがgatewayを利用できるかの制限もかけるため、必要な設定を追加しています。
https://github.com/nginxinc/nginx-gateway-fabric/tree/main/examples/https-termination
Gateway本体
gatewayを"gitops/gateway"レポジトリ上で作成します。
"gateway"というnamespaceと"my-gateway"というgatewayを作成するファイルを用意します。
おおよそ例のままですが、今回の構築ではgatewayは"gateway"というnamespaceにあり、アプリケーションは"demo-cafe"という別namespaceに用意します。Gatewayはデフォルトでは自身と同じnamespaceからのみ利用できます。従って今回はselectorでgateway-enabled=yes
とあるnamespaceはgateway利用可能というセットアップにしていきます。
---
kind: Namespace
apiVersion: v1
metadata:
name: gateway
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: my-gateway
namespace: gateway
spec:
gatewayClassName: nginx
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
gateway-enabled: yes
- name: https
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: tls-secret
namespace: certificate
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
gateway-enabled: yes
TLS secretの用意
今回用意するgatewayはhttpsも扱う為に、"certificate" namespace上の"tls-secret"を証明書として参照させています。そのsecretは例によって"gitops/my-secrets"上にSOPSで暗号化して作成します。
https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets
---
apiVersion: v1
kind: Namespace
metadata:
name: certificate
---
apiVersion: v1
kind: Secret
metadata:
name: tls-secret
namespace: certificate
type: kubernetes.io/tls
data:
tls.crt: abcdefgcert...
tls.key: abcdeftkey...
Secretの.data.tls.crt
および.data.tls.key
は実際の証明書・鍵情報から作成します。例えばletsencryptのfullchain.pemという証明書のファイルと、privkey.pemという鍵のファイルがある場合は、例えば次のようにしたら良いかと思います。
# at the directory with the tls cert and key files
# first, create the ns portion
cat >tls-secret.yaml <<EOF
---
apiVersion: v1
kind: Namespace
metadata:
name: certificate
---
EOF
# and then append secret
kubectl create secret tls tls-secret \
--cert=fullchain.pem \
--key=privkey.pem \
--namespace=certificate \
--dry-run=client \
-o yaml >> tls-secret.yaml
# and copy over this tls-secret.yaml file
# to `./clusters/my-cluster/tls-secret.yaml` in "gitops/my-secrets" repo
# encrypt
sops --encrypt --in-place tls-secret.yaml
tls-secret参照許可
Gatewayは"gateway" namespaceにあり、tls-secretは"certificate" namespaceにあります。このgatewayからsecretへの参照許可もマニフェストとして用意する必要があります。
# "gitops/gateway" repo
cat > tls-ref-grant.yaml <<EOF
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-gtw
namespace: certificate
spec:
to:
- group: ""
kind: Secret
name: tls-secret
from:
- group: gateway.networking.k8s.io
kind: Gateway
namespace: gateway
EOF
Gateway完成
すべてfluxに渡して実装してもらうと、以下のようにあれこれできていることが確認できます。
❯ kubectl get secret,refgrant -n certificate
NAME TYPE DATA AGE
secret/tls-secret kubernetes.io/tls 2 2d5h
NAME AGE
referencegrant.gateway.networking.k8s.io/allow-gtw 2m58s
❯ kubectl get gtw -n gateway
NAME CLASS ADDRESS PROGRAMMED AGE
my-gateway nginx 192.168.1.54 True 3m3s
デモのアプリ
こちらはもう例の通りそのまま使ってみましょう。
元々このマニフェストにはnamespace設定行がないのですが、fluxでkustomizationとしてこの"gitops/demo-cafe"レポジトリをセットした際にtarget-namespace=demo-cafe
と指定してあるので、デフォルトでそのnamespace上に作ってくれます。
# "gitops/demo-cafe" repo
# "./cafe.yaml"
curl -LO https://raw.githubusercontent.com/nginxinc/nginx-gateway-fabric/main/examples/https-termination/cafe.yaml
結果は次のとおりです。
❯ kubectl get all -n demo-cafe
NAME READY STATUS RESTARTS AGE
pod/coffee-6b8b6d6486-5lw7t 1/1 Running 0 21m
pod/tea-9d8868bb4-9r5bp 1/1 Running 0 21m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/coffee ClusterIP 10.110.249.177 <none> 80/TCP 21m
service/tea ClusterIP 10.103.212.76 <none> 80/TCP 21m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/coffee 1/1 1 1 21m
deployment.apps/tea 1/1 1 1 21m
NAME DESIRED CURRENT READY AGE
replicaset.apps/coffee-6b8b6d6486 1 1 1 21m
replicaset.apps/tea-9d8868bb4 1 1 1 21m
HTTPRoute作成
こちらも例をダウンロードしてきましょう。今回は少し編集します。
# "gitops/demo-cafe" repo
# download the example httproute manifest
curl -LO https://github.com/nginxinc/nginx-gateway-fabric/raw/main/examples/https-termination/cafe-routes.yaml
編集する点は3点です。Gatewayの名称とhostnameの変更、そしてgateway参照時にnamespace指定です。出来上がり内容としては次のとおりです。
hostnames
部分は用意した証明書に合わせてください。
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: cafe-tls-redirect
spec:
parentRefs:
- name: my-gateway
sectionName: http
namespace: gateway
hostnames:
- "cafe.example.com"
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
port: 443
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: coffee
spec:
parentRefs:
- name: my-gateway
sectionName: https
namespace: gateway
hostnames:
- "cafe.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /coffee
backendRefs:
- name: coffee
port: 80
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: tea
spec:
parentRefs:
- name: my-gateway
sectionName: https
namespace: gateway
hostnames:
- "cafe.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /tea
backendRefs:
- name: tea
port: 80
Fluxによる処理が完了したら、まずはgatewayの現在の状態を確認します。kubectl get gtw -n gateway -o yaml
で見られますが、以下がフィルターした出力です。
❯ kubectl get gtw -n gateway -o=jsonpath='{.items[].status.addresses[].value}{"\n"}{range .items[].status.listeners[*]}{@.name} - {@.attachedRoutes} {@.supportedKinds[].kind}{"\n"}{end}'
192.168.1.54
http - 1 HTTPRoute
https - 2 HTTPRoute
IPアドレスは192.168.1.54、そしてlistenerはhttpとhttpsという名称のものがあります。httpに関してはhttpsへリダイレクトするHTTPRouteが付いており、httpsに関しては"coffee"と"tea"というルールのHTTPRouteが付いています。
完成!!
これでセットアップは完成です。
それではアクセスしてみましょう。以下コマンド例で、cafe.example.com
部分は証明書情報、HTTPRouteのhostnameと一致するようにして試してください。またIPアドレスもGatewayのものを指定してください。
# 302 redirect to https
curl --resolve cafe.example.com:80:192.168.1.54 http://cafe.example.com:80/ --include
# 200 for https access to the coffee app
curl --resolve cafe.example.com:443:192.168.1.54 https://cafe.example.com:443/coffee --include
# and tea app
curl --resolve cafe.example.com:443:192.168.1.54 https://cafe.example.com:443/tea --include
出力例はこのような感じです。自身のサーバアドレスを返してくれていますが、teaアプリのpodに設定されているIPアドレスであることが確認できます。
# accessing tea app
❯ curl --resolve cafe.example.com:443:192.168.1.54 https://cafe.example.com:443/tea
Server address: 10.244.211.139:8080
Server name: tea-9d8868bb4-9r5bp
Date: 11/Nov/2023:15:52:48 +0000
URI: /tea
Request ID: a271a84b8deff6b322f9849e2727e5d2
# tea app pod
❯ kubectl get pods -n demo-cafe -l app=tea -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
tea-9d8868bb4-9r5bp 1/1 Running 0 91m 10.244.211.139 node2 <none> <none>
以上です!
もう少しあれこれ作って慣れていって、現在Dockerコンテナでリバースプロキシやらせているものをすべて載せ替えられたらいいかなと思っています。
Discussion