k3sとprefectで冗長構成のワークフロー基盤を構築する
k3sとprefectでワークフロー基盤を構築する
概要
PrefectはPythonでワークフローを記述可能なオーケストレーションツールです。本記事では、軽量Kubernetesであるk3s上にPrefectServerを構築し、冗長構成のワークフロー基盤を構築する方法を紹介します。
環境設計
- コンピューティング: k3s Node (3台)
- OS: Ubuntu Server 24.04 LTS
- CPU: 2CPU
- MEM: 8GB
- ネットワーキング
- k3s Node同士が同一のL2ネットワークで疎通可能な環境
- k3s Nodeがそれぞれインターネットに疎通可能な環境
- k3s NodeがそれぞれIPアドレスを1個ずつ持っていること
- MetalLB用に代表IPアドレスを1個用意すること
- 代表IPアドレスに対してドメインを割り当てること
構成図
Whoamiサービス(接続テスト用)
flowchart TB
Client["Client<br/>Browser"]
VIP["代表IP/32<br/>MetalLB VIP"]
subgraph K3s["K3s Cluster(3 Nodes)"]
subgraph MetalLB["ns:metallb-system"]
Pool["IPAddressPool<br/>代表IP/32"]
end
subgraph EnvoyGWSystem["ns:envoy-gateway-system"]
GC["GatewayClass<br/>envoy"]
end
subgraph Public["ns:public"]
Secret["Secret<br/>whoami-tls<br/>tls.crt / tls.key"]
GW["envoy-gateway<br/>spec.addresses:代表IP"]
Route["HTTPRoute Host/Path Any<br/>parentRefs: envoy-gateway"]
SVC["whoami Service"]
subgraph Pods["DaemonSet"]
Pod1["whoami Pod1 :80"]
Pod2["whoami Pod2 :80"]
Pod3["whoami Pod3 :80"]
end
end
end
Client -->|"HTTPS :443"| VIP
Pool -.->|"IP Pool allocation"| EnvoyGWSystem
Pool -.->|"IP Pool advertisement"| VIP
VIP -->|"HTTPS :443"| GW
GC -.->|"gatewayClassName: envoy"| GW
Secret -.->|"certificateRefs"| GW
GW -->|"HTTP :80"| Route
Route -->|"backendRef: svc:80"| SVC
SVC --> Pod1
SVC --> Pod2
SVC --> Pod3
Prefect基盤
flowchart TB
Client["Client<br/>Browser"]
VIP["代表IP/32<br/>MetalLB VIP"]
DNS["DNS"]
subgraph K3s["K3s Cluster(3 Nodes)"]
subgraph MetalLB["ns:metallb-system"]
Pool["IPAddressPool<br/>代表IP/32"]
end
subgraph EnvoyGWSystem["ns:envoy-gateway-system"]
GC["GatewayClass<br/>envoy"]
end
subgraph Public["ns:public"]
Secret["Secret<br/>whoami-tls<br/>tls.crt / tls.key"]
GW["envoy-gateway<br/>spec.addresses:代表IP"]
Route["HTTPRoute<br/>Host: PrefectUIホスト名<br/>Path: Any<br/>parentRefs: envoy-gateway"]
SVC["Prefect Service"]
subgraph Pods["ReplicaSet"]
Pod1["Prefect Server Pod :80"]
Pod2["Prefect Worker Pod :80"]
end
end
end
Client -->|"HTTPS :443"| VIP
Pool -.->|"IP Pool allocation"| EnvoyGWSystem
Pool -.->|"IP Pool advertisement"| VIP
DNS -.->|"Resolve<br/>prefect.example.local<br/>代表IP"| VIP
VIP -->|"HTTPS :443"| GW
GC -.->|"gatewayClassName: envoy"| GW
Secret -.->|"certificateRefs"| GW
GW -->|"HTTP :80"| Route
Route -->|"backendRef: svc:80"| SVC
SVC --> Pod1
SVC --> Pod2
環境構築
k3sクラスタの作成
まず、初めに3台のうち適当な1台のサーバでk3sクラスタを作成します。この1台で最初にクラスタを作り、他の2台をクラスタに参加させます。
Warning
- 全部rootで実行します。 - NW環境で10.0.0.0/8,172.16.0.0/12が使用済みであったのでクラスタの内部ネットワークはCIDRを192.168.0.0/16の範囲におさめます。(--cluster-cidr,--service-cidr) - k3sのLBに今回はmetallbを使うため、デフォルトのlb(servicelb)を無効化(--disable)します。 - k3sのingressに今回はenvoyを使い、gatewayを使用するため、デフォルトのingress(traefik)を無効化(--disable)します。 - 各サーバ筐体に複数のインターフェースがある為、念の為、外部I/Fをeth0に指定しいます。(--flannel-iface)
1代目の設定
#!/bin/bash
# encoding: utf-8
openssl rand -hex 32 > /tmp/k3s_token.txt
curl -sfL https://get.k3s.io | K3S_TOKEN=$(cat /tmp/k3s_token.txt) sh -s - server \
--cluster-init \
--cluster-cidr=192.168.0.0/17 \
--service-cidr=192.168.128.0/17 \
--disable servicelb \
--disable traefik \
--flannel-iface="L2ネットワークインターフェース名"
# /tmp/k3s_token.txt の内容はクラスタに参加する際に必要なので、他の2台のサーバにコピーしておきます。
2台目,3台目の設定
#!/bin/bash
# encoding: utf-8
curl -sfL https://get.k3s.io | K3S_TOKEN="1台目で設定したk3s_tokent.txtの内容" sh -s - server \
--server https://"1台目のIP":6443 \
--cluster-cidr=192.168.0.0/17 \
--service-cidr=192.168.128.0/17 \
--disable servicelb \
--disable traefik \
--flannel-iface="L2ネットワークインターフェース名"
Warning
- k3sのアンインストールをするには `/usr/local/bin/k3s-uninstall.sh` を実行します。ただし、ローカルにPersistent Volume (PV)を作っている場合はそれらの手動削除(rm)が必要です。
以後のコマンドは全て1台目のサーバで行います
Helmのインストール
HelmはKubernetesのパッケージマネージャです。 Helmを使うことでKubernetes上にアプリケーションを簡単にデプロイできます。 以下のコマンドでHelmをインストールします。
#!/bin/bash
# encoding: utf-8
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash
MetalLBのインストール
MetalLBはL2ネットワークでKubernetesのLoadBalancerを実現するためのプラグインです。 ActiveなNodeがクラスタの代表IPアドレスを名乗る事により冗長化を実現します。
#!/bin/bash
# encoding: utf-8
helm repo add metallb https://metallb.github.io/metallb
helm repo update
helm pull --untar metallb/metallb
helm upgrade --install metallb ./metallb --namespace metallb-system --create-namespace
MetalLBの設定
MetalLBが代表するIPアドレスプールを設定します。 以下のYAMLファイルを作成し、kubectl apply -fで適用します。
# metallb-envoy-public.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: metallb-public-ip-pool
namespace: metallb-system
spec:
addresses:
- "代表IPアドレス/32"
serviceAllocation:
priority: 10
namespaces:
- envoy-gateway-system
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: envoy-gateway-system-public-l2-advertisement
namespace: metallb-system
spec:
ipAddressPools:
- metallb-public-ip-pool
#!/bin/bash
# encoding: utf-8
kubectl apply -f metallb-envoy-public.yaml
これで Namespace が envoy-gateway-system のものに対して MetalLBが代表IPアドレスプールを割り当てるようになります。
Warning
- MetalLBが割り当てるのはアドレスプールのみである事に注意してください。実際に割り当てるIPアドレスはServiceのspec.addressesで指定する必要があります。
Envoy Gatewayのインストール
Envoy GatewayはKubernetes上で動作するL7ロードバランサです。MetaLBのIPアドレスプールを制御してインストールします。 MetalLB側で指定したnamespaceをここで作成します。
#!/bin/bash
# encoding: utf-8
helm pull --untar oci://docker.io/envoyproxy/gateway-helm --version v0.0.0-latest
helm install envoygateway ./gateway-helm -n envoy-gateway-system --create-namespace --set deployment.replicas=2
Gatewayの作成
代表IPアドレスを指定してGatewayを作成します。 代表IPアドレスを利用可能なNamespaceを “public” とします。
# public-envoy-gateway.yaml
apiVersion: v1
kind: Namespace
metadata:
name: public
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: envoy
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: envoy-gateway
namespace: public
spec:
gatewayClassName: envoy
addresses:
- type: IPAddress
value: "代表IPアドレス"
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
#!/bin/bash
# encoding: utf-8
kubectl apply -f public-envoy-gateway.yaml
テスト用HTTPRouteの作成
代表IPアドレスのGatewayで受けた通信のHTTPRouteを作成します。 whoamiというテスト用サーバーにルーティングするように設定します。
# whoami-http-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: whoami-http-route
namespace: public
spec:
parentRefs:
- name: envoy-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: whoami
port: 80
#!/bin/bash
# encoding: utf-8
kubectl apply -f whoami-http-route.yaml
テスト用サーバー(DaemonSet)のデプロイ
テスト用サーバーをデプロイします。 whoamiサービスはDaemonSetでデプロイすることで クラスタ内の全てのNodeにwhoami Podが配置され 代表IPアドレスにアクセスした際にどのNodeのPodに ルーティングされるかを確認することができます。
# whoami-daemonset.yaml
apiVersion: v1
kind: Service
metadata:
name: whoami
namespace: public
spec:
type: ClusterIP
selector:
app: whoami
ports:
- name: http
port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: whoami
namespace: public
spec:
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: traefik/whoami
ports:
- containerPort: 80
#!/bin/bash
# encoding: utf-8
kubectl apply -f whoami-daemonset.yaml
確認
#!/bin/bash
# encoding: utf-8
curl "http://<代表IPアドレス>/"
# 3回ほど実行してwhoamiのPodが3台のNodeに分散していることを確認。
補足: TLS終端(自己署名証明書)
ワイルドカード証明書設定
# openssl.cnf
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
[req_distinguished_name]
C = JP
ST = Tokyo
L = Shinjuku
O = MyOrg
OU = MyOrgUnit
CN = *.example.local
[v3_req]
basicConstraints = critical, CA:false
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectKeyIdentifier = hash
subjectAltName = @alt_names
[alt_names]
DNS.1 = *.example.local
DNS.2 = example.local
IP.1 = 代表IPアドレス
証明書の作成とSecretの作成
#!/bin/bash
# encoding: utf-8
openssl genrsa -out tls.key 2048
openssl req \
-x509 \
-new \
-key tls.key \
-sha256 \
-days 3650 \
-out tls.crt \
-config openssl.cnf \
-extensions v3_req
kubectl create secret tls public-tls \
-n public \
--cert=tls.crt \
--key=tls.key
TLS終端Gatewayの作成
# public-envoy-gateway.yaml
apiVersion: v1
kind: Namespace
metadata:
name: public
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: envoy
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: envoy-gateway
namespace: public
spec:
gatewayClassName: envoy
addresses:
- type: IPAddress
value: "代表IPアドレス"
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: public-tls
CloudNative-PG(PostgreSQL)のインストール
CloudNative-PGはKubernetes上で動作するPostgreSQLのOperatorです。冗長構成のPostgreSQLクラスタを簡単に構築できます。
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm repo update
helm pull --untar cnpg/cloudnative-pg
helm upgrade --install cnpg ./cloudnative-pg --namespace cnpg-system --create-namespace
CloudNative-PGクラスタの設定
今回はPrefect用にそれぞれ64GiBのPersistent Volumeを3台のNodeに配置するように設定します。
# cloudnative-pg.yaml
--- # Create Namespace cnpg
kind: Namespace
apiVersion: v1
metadata:
name: cnpg-system
--- # Create Secret for Initial Database
apiVersion: v1
data:
username: ZGVmYXVsdA== # default
password: ZGVmYXVsdA== # default
kind: Secret
metadata:
name: default-postgres-secret
namespace: cnpg-system
type: kubernetes.io/basic-auth
--- # Create Secret for prefect
apiVersion: v1
data:
username: cHJlZmVjdA== # prefect
password: cHJlZmVjdA== # prefect
kind: Secret
metadata:
name: prefect-postgres-secret
namespace: cnpg-system
type: kubernetes.io/basic-auth
--- # create Cluster and Initial Database and Additional Roles
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cnpgcluster
namespace: cnpg-system
spec:
instances: 3
storage:
size: 64Gi
bootstrap:
initdb:
database: default
owner: default
secret:
name: default-postgres-secret
managed:
roles:
- name: prefect
ensure: present
comment: PrefectServer
login: true
passwordSecret:
name: prefect-postgres-secret
--- # Create prefect Database
apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
name: prefect-db
namespace: cnpg-system
spec:
name: prefect
owner: prefect
cluster:
name: cnpgcluster