🍣

Building OpenMetadata on Local Kubernetres

に公開

自宅のKubernetes環境にOpenMetadataを構築し、Cloudflare Tunnelで外部公開して遊んでみたので備忘録です。

ローカルKubernetesにOpenMetadataをデプロイする

手順については、次の2つの公式ドキュメントを参照し、一部修正を行い構築しました。
https://docs.open-metadata.org/latest/quick-start/local-kubernetes-deployment
https://docs.open-metadata.org/latest/deployment/kubernetes/on-prem

ローカルマシンスペックについて

公式ドキュメントには、4vCPUと8GiBのメモリを備えた minikube クラスターをスペック条件と記載があります。

minikube start --cpus=4 --memory=8192

最低でもこのスペックは確保しておきます。
今回はminikubeではなく、kubeadmで構築したクラスターで各ノードは4coreのRAM16GiBで構築しています。

事前準備

Helmをインストールする

すでにHelmをインストールされている場合はこの手順はスキップしてOKです。
今回の構成では全ノードでHelmを利用するので、各ノードで実行します。

$ curl -O https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
$ bash ./get-helm-3
$ helm version
version.BuildInfo{Version:"v3.16.4", GitCommit:"7877b45b63f95635153b29a42c0c2f4273ec45ca", GitTreeState:"clean", GoVersion:"go1.22.7"}

StorageClass を使用した動的プロビジョニング

今回Helmを使ったOpenMetadata構築を行います。
OpenMetadata HelmチャートはAirflowに依存しており、AirflowはReadWriteMany(ボリュームは多くのノードによって読み取り/書き込みとしてマウントできます)をサポートする永続ディスクを想定しています。
そのため、nfs-shareを作成し、それを永続的なストレージとして使用してます。
StorageClassを使用してPersistentVolumeを動的にプロビジョニングするには、NFSプロビジョナーをインストールする必要があります。
今回は、公式推奨のnfs-subdir-external-provisioner Helmチャートを使用します。
ただし、すでに動的ストレージが存在する場合はこの手順はスキップしてOKです。

NFSサーバの構築

私の環境にNFSサーバが存在しないので、作っていきます。
今回はワーカーノードにNFSサーバを用意しています。

$ sudo apt update
$ sudo apt install nfs-kernel-server -y
$ sudo mkdir -p /airflow
$ sudo chmod 777 /airflow
$ sudo mkdir -p /airflow/airflow-dags /airflow/airflow-logs
$ sudo chown -R nobody:nogroup /airflow
$ sudo chmod -R 777 /airflow

/etc/exportsにマウント時に指定するパスを設定
この後Airflow用のPersistentVolumeClaimを作成しますが、そのマウントパスが/airflow-dagsや/airflow-logsとするので、ルートのマウントパスは/airflowとし、その配下にそれぞれのPersistentVolumeClaimのパスが来るようにしています。

$ sudo cat /etc/exports
# /etc/exports: the access control list for filesystems which may be exported
#		to NFS clients.  See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes       hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4        gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes  gss/krb5i(rw,sync,no_subtree_check)
#
/airflow *(rw,sync,no_root_squash,no_subtree_check) #追記

設定の反映を行う

$ sudo exportfs -ra

nfs-subdir-external-provisionerのインストール

パラメータ説明

パラメータ 概要
nfs.server NFSサーバのホスト名もしくはIPアドレスを指定
nfs.path NFSサーバ側で/etc/exportsに設定したマウントパスを指定
$ sudo helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner --create-namespace --namespace nfs-provisioner --set nfs.server=k8s-worker01 --set nfs.path=/airflow

nfs-serverを有効にする

$ sudo systemctl enable nfs-server
$ sudo systemctl start nfs-server
$ sudo exportfs -v
/airflow      	<world>(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)

PersistentVolumeClaimを作成

動的プロビジョニングでAirflowのdagとlogsのPersistentVolumeClaimを作成します。
dag用とlog用のPVCは以下のGithubの通りに作成しました。
https://github.com/kyamisama/custom_openmetadata_helm_values/blob/main/openmetadata_custom_values/airflow_storage/dags-pvc.yaml
https://github.com/kyamisama/custom_openmetadata_helm_values/blob/main/openmetadata_custom_values/airflow_storage/logs-pvc.yml
作成した定義ファイルからPVCを作成する。

$ sudo kubectl apply -f dags-pvc.yml
persistentvolumeclaim/openmetadata-dependencies-dags created
$ sudo kubectl apply -f logs-pvc.yml
persistentvolumeclaim/openmetadata-dependencies-logs created
$ sudo kubectl get pvc
NAME                             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS               VOLUMEATTRIBUTESCLASS   AGE
openmetadata-dependencies-dags   Bound    pvc-82f91642-b6ce-4a97-930c-************   1Gi        RWX            nfs-client                 <unset>                 46s
openmetadata-dependencies-logs   Bound    pvc-d0c781f9-3784-41ef-a503-************   1Gi        RWX            nfs-client                 <unset>                 18s

ディスクの所有者と権限を手動で変更する

Airflowポッドは非ルートユーザーとして実行されるため、NFSサーバー ボリュームへの書き込みアクセス権がありません。
ここで権限を修正するには、永続ボリュームが接続されたポッドを起動し、それを1回実行します。

定義ファイル
permissions_pod.yml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: my-permission-pod
  name: my-permission-pod
spec:
  containers:
  - image: busybox
    name: my-permission-pod
    volumeMounts:
    - name: airflow-dags
      mountPath: /airflow-dags
    - name: airflow-logs
      mountPath: /airflow-logs
    command:
    - "chown -R 50000 /airflow-dags /airflow-logs"
    # if needed
    - "chmod -R a+rwx /airflow-dags"
  volumes:
  - name: airflow-logs
    persistentVolumeClaim:
      claimName: openmetadata-dependencies-logs
  - name: airflow-dags
    persistentVolumeClaim:
      claimName: openmetadata-dependencies-dags
  dnsPolicy: ClusterFirst
  restartPolicy: Always

権限を修正するためにmy-permission-podを起動します。

$ sudo kubectl create -f permissions_pod.yml

Podの起動以外にNFSサーバにログインして以下のコマンドを実行するでもOKです。

$ sudo chown -R 50000 /airflow/airflow-dags /airflow/airflow-logs
$ sudo chmod -R a+rwx /airflow/airflow-dags

OpenMetadata依存関係値を作成する

openmetadata依存関係のAirflow Helm値をオーバーライドして、DAGとログのnfs永続ボリュームをバインドします。
https://github.com/kyamisama/custom_openmetadata_helm_values/blob/main/openmetadata_custom_values/openmetadata_dependencies/values-dependencies.yaml

Opensearch/MySQL用のローカルストレージを用意

ワーカーノードにてOpensearch/MySQL用のローカルストレージ領域を確保します。

$ sudo mkdir -p /mnt/data/mysql
$ sudo chmod 777 /mnt/data/mysql
$ sudo mkdir -p /mnt/data/opensearch
$ sudo chmod 777 /mnt/data/opensearch

OpensearchのPVとPVC定義ファイル

https://github.com/kyamisama/custom_openmetadata_helm_values/blob/main/openmetadata_custom_values/opensearch_storage/opensearch-pv.yaml
https://github.com/kyamisama/custom_openmetadata_helm_values/blob/main/openmetadata_custom_values/opensearch_storage/opensearch-pvc.yaml

MySQLのPVとPVC定義ファイル

https://github.com/kyamisama/custom_openmetadata_helm_values/blob/main/openmetadata_custom_values/mysql_storage/mysql-pv.yaml
https://github.com/kyamisama/custom_openmetadata_helm_values/blob/main/openmetadata_custom_values/mysql_storage/mysql-pvc.yaml

$ sudo kubectl apply -f mysql-pv.yaml
$ sudo kubectl apply -f mysql-pvc.yaml
$ sudo kubectl apply -f opensearch-pv.yaml
$ sudo kubectl apply -f opensearch-pvc.yaml
$ sudo kubectl get pv
NAME            CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                    STORAGECLASS               VOLUMEATTRIBUTESCLASS   REASON   AGE
mysql-pv        50Gi       RWO            Retain           Bound    default/mysql-pvc        mysql-local-storage        <unset>                          24s
opensearch-pv   30Gi       RWO            Retain           Bound    default/opensearch-pvc   opensearch-local-storage   <unset>                          16s
$ sudo kubectl get pvc
NAME                             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS               VOLUMEATTRIBUTESCLASS   AGE
mysql-pvc                        Bound    mysql-pv                                   50Gi       RWO            mysql-local-storage        <unset>                 25s
opensearch-pvc                   Bound    opensearch-pv                              30Gi       RWO            opensearch-local-storage   <unset>                 17s
openmetadata-dependencies-dags   Bound    pvc-82f91642-b6ce-4a97-930c-************   1Gi        RWX            nfs-client                 <unset>                 46s
openmetadata-dependencies-logs   Bound    pvc-d0c781f9-3784-41ef-a503-************   1Gi        RWX            nfs-client                 <unset>                 18s

Helm Chartsに必要なKubernetes Secretを作成する

$ sudo kubectl create secret generic mysql-secrets --from-literal=openmetadata-mysql-password=openmetadata_password
$ sudo kubectl create secret generic airflow-secrets --from-literal=openmetadata-airflow-password=admin
$ sudo kubectl create secret generic airflow-mysql-secrets --from-literal=airflow-mysql-password=airflow_pass

ローカル展開用のHelmリポジトリを追加する

$ sudo helm repo add open-metadata https://helm.open-metadata.org/

Helmリポジトリが追加されたことを確認

$ sudo helm repo list
NAME         	URL
open-metadata	https://helm.open-metadata.org/

OpenMetadata Dependencies Helm Chart をインストールする

前提条件で作成したAirflowの

$ sudo helm install openmetadata-dependencies open-metadata/openmetadata-dependencies --values ./values-dependencies.yml
NAME: openmetadata-dependencies
LAST DEPLOYED: Tue Dec 24 12:51:11 2024
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

Pod起動確認

kyami@k8s-master:~/openmetadata$ sudo kubectl get pods
NAME                                                       READY   STATUS    RESTARTS   AGE
mysql-0                                                    1/1     Running   0          2m18s
openmetadata-dependencies-db-migrations-5b5d54cf78-kbx4q   1/1     Running   0          2m18s
openmetadata-dependencies-scheduler-699ff94db6-jck27       1/1     Running   0          2m18s
openmetadata-dependencies-sync-users-86f7fd7978-fgddp      1/1     Running   0          2m18s
openmetadata-dependencies-triggerer-7d968c7955-jcbgf       1/1     Running   0          2m18s
openmetadata-dependencies-web-6b9b65dbb6-dvx6q             1/1     Running   0          2m18s
opensearch-0                                               1/1     Running   0          2m18s
kyami@k8s-master:~/openmetadata$

OpenMetadata Helm Chartをインストールする

https://github.com/open-metadata/openmetadata-helm-charts/blob/main/charts/openmetadata/values.yaml
公式のHelmだと、OpenMetadataのサインイン画面上に「CREATE ACCOUNT」のボタンがあり、誰でもユーザを作成できてしまいます。
それを回避するために、Helmの「openmetadata.config.authentication.enableSelfSignup」をfalseに変更する定義ファイルを作成し、Helmインストール時に定義ファイルを指定します。

kyami@k8s-master:~/openmetadata$ sudo helm install openmetadata open-metadata/openmetadata --values ./openmetadata_values.yml
W0406 03:00:19.364696    9052 warnings.go:70] unknown field "spec.jobTemplate.spec.app.kubernetes.io/instance"
W0406 03:00:19.364875    9052 warnings.go:70] unknown field "spec.jobTemplate.spec.app.kubernetes.io/name"
W0406 03:00:19.364891    9052 warnings.go:70] unknown field "spec.jobTemplate.spec.matchLabels"
W0406 03:00:19.364698    9052 warnings.go:70] unknown field "spec.jobTemplate.spec.app.kubernetes.io/instance"
W0406 03:00:19.365000    9052 warnings.go:70] unknown field "spec.jobTemplate.spec.app.kubernetes.io/name"
W0406 03:00:19.365013    9052 warnings.go:70] unknown field "spec.jobTemplate.spec.matchLabels"
NAME: openmetadata
LAST DEPLOYED: Sun Apr  6 03:00:18 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=openmetadata,app.kubernetes.io/instance=openmetadata" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8585 to use your application"
  kubectl --namespace default port-forward $POD_NAME 8585:$CONTAINER_PORT

Pod起動確認

kyami@k8s-master:~/openmetadata$ sudo kubectl get pods
NAME                                                       READY   STATUS    RESTARTS   AGE
mysql-0                                                    1/1     Running   0          4m26s
openmetadata-6cbbc9d994-p4xsm                              1/1     Running   0          99s
openmetadata-dependencies-db-migrations-5b5d54cf78-kbx4q   1/1     Running   0          4m26s
openmetadata-dependencies-scheduler-699ff94db6-jck27       1/1     Running   0          4m26s
openmetadata-dependencies-sync-users-86f7fd7978-fgddp      1/1     Running   0          4m26s
openmetadata-dependencies-triggerer-7d968c7955-jcbgf       1/1     Running   0          4m26s
openmetadata-dependencies-web-6b9b65dbb6-dvx6q             1/1     Running   0          4m26s
opensearch-0                                               1/1     Running   0          4m26s
kyami@k8s-master:~/openmetadata$

Cloudflare Tunnelを使った外部公開

Cloudflare Tunnel設定は以下の記事を参考ください。
https://zenn.dev/kyami/articles/28deef1967b1eb

cloudflared.yamlの定義ファイル
cloudflared.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cloudflared
spec:
  selector:
    matchLabels:
      app: cloudflared
  replicas: 2 # You could also consider elastic scaling for this deployment
  template:
    metadata:
      labels:
        app: cloudflared
    spec:
      containers:
      - name: cloudflared
        image: cloudflare/cloudflared:2022.3.0
        args:
        - tunnel
        # Points cloudflared to the config file, which configures what
        # cloudflared will actually do. This file is created by a ConfigMap
        # below.
        - --config
        - /etc/cloudflared/config/config.yaml
        - run
        livenessProbe:
          httpGet:
            # Cloudflared has a /ready endpoint which returns 200 if and only if
            # it has an active connection to the edge.
            path: /ready
            port: 2000
          failureThreshold: 1
          initialDelaySeconds: 10
          periodSeconds: 10
        volumeMounts:
        - name: config
          mountPath: /etc/cloudflared/config
          readOnly: true
        # Each tunnel has an associated "credentials file" which authorizes machines
        # to run the tunnel. cloudflared will read this file from its local filesystem,
        # and it'll be stored in a k8s secret.
        - name: creds
          mountPath: /etc/cloudflared/creds
          readOnly: true
      volumes:
      - name: creds
        secret:
          # By default, the credentials file will be created under ~/.cloudflared/<tunnel ID>.json
          # when you run `cloudflared tunnel create`. You can move it into a secret by using:
          # ```sh
          # kubectl create secret generic tunnel-credentials \
          # --from-file=credentials.json=/Users/yourusername/.cloudflared/<tunnel ID>.json
          # ```
          secretName: openmetadata-tunnel-credentials
      # Create a config.yaml file from the ConfigMap below.
      - name: config
        configMap:
          name: cloudflared
          items:
          - key: config.yaml
            path: config.yaml
---
# This ConfigMap is just a way to define the cloudflared config.yaml file in k8s.
# It's useful to define it in k8s, rather than as a stand-alone .yaml file, because
# this lets you use various k8s templating solutions (e.g. Helm charts) to
# parameterize your config, instead of just using string literals.
apiVersion: v1
kind: ConfigMap
metadata:
  name: cloudflared
data:
  config.yaml: |
    # Name of the tunnel you want to run
    tunnel: openmetadata-tunnel
    credentials-file: /etc/cloudflared/creds/credentials.json
    # Serves the metrics server under /metrics and the readiness server under /ready
    metrics: 0.0.0.0:2000
    no-autoupdate: true
    ingress:
    - hostname: omd.kyamisama.com
      service: http://openmetadata:8585
    - service: http_status:404
$ sudo kubectl apply -f cloudflared.yaml

ブラウザからアクセスしてみる


デフォルトのログイン資格情報はadmin@open-metadata.org:adminです。

おわり

私自身そこまでKuberneteに詳しくないのですが、それでも公式ドキュメントやググりながらなんとか構築できたので、もし興味がある人は是非お試しください。
あといつの間にか機能が増えていたので別記事で色々と検証してみたいと思います!
その前に次回はOpenMetadataの認証認可周りをkeycloakでやる記事を書こうと思います!

Discussion