hirano00o's blog

技術的な記録、日常の記録

CockroachDBの導入とユーザ証明書の作成

Kubernetesクラスタ(on Raspberry Pi5)にCockroachDBを導入する。DB側がTLS有効かつクライアント側でsslmode=verify-fullで接続しようとした際、ユーザの証明書作成に少し手間取ったので、(正しい方法かはわからないが)その手順も示す。

CockroachDBとは

CockroachDBは、PostgreSQL互換を持つ分散データベースでNewSQLと呼ばれているものの1つ。NewSQLは、スケーラビリティを持ちながらもACIDトランザクションが可能で、RDBのスケーラビリティの低さやNoSQLの一貫性の低さといった欠点を解決したデータベース。CockroachDBの他には、TiDBやGoogleCloud Spanner、YugabyteDBなどがある。 中でもCockroachDBの特徴としては、ノード間での役割の差がなく、どのノードがダウンしても最小の待ち時間で利用し続けることができる。システムデータ含むほぼ全てのデータはRangeと呼ばれるチャンクに分割され、各ノードに配置される。またRangeはノード障害に備えてレプリカがデフォルトで3つ作成、いずれかのノードに配置される。クエリを受けたノードがもしデータを持っていなければ他ノードにクエリがルーティングされる。ただし、場合によってはネットワークホップ数が増えてしまい遅延に繋がる。

Kubernetesクラスタにデプロイする

https://github.com/cockroachdb/cockroach-operator?tab=readme-ov-file#install-the-operator に沿って実施する。デプロイの流れは下記の通り。公式のオペレータはARM64に対応していないため、サードパーティを利用する。

  1. CRDのApply
  2. オペレータのApply
  3. CockroachDBのApply
# CRD
kubectl apply -f https://raw.githubusercontent.com/cockroachdb/cockroach-operator/master/install/crds.yaml

# Operator
## OperatorのyamlのimageはARM64に対応していないため、サードパーティのimageに変更する
curl -fsSL -o cockroach-operator.yaml https://raw.githubusercontent.com/cockroachdb/cockroach-operator/master/install/operator.yaml
## image: cockroachdb/cockroach-operator:v2.14.0をghcr.io/jrcichra/cockroach-operator:v2.12.0に変更する
sed -i.old 's/image:.*$/image: ghcr.io\/jrcichra\/cockroach-operator:v2.12.0/' cockroach-operator.yaml
kubectl create -f cockroach-operator.yaml

kubectl get po -n cockroach-operator-system 
NAME                                          READY   STATUS    RESTARTS   AGE
cockroach-operator-manager-7fcf99bc96-6kvq9   1/1     Running   0          57s

CockroachDBは https://github.com/cockroachdb/cockroach-operator/blob/master/examples/example.yaml を元にマニフェストを作成する。 PersistentVolumeが必要なので同時に作成する。共有ストレージである必要はないため、今回は各ノードにLocalで作成する。下記のマニフェストはホスト名で絞ってボリュームおよびDBを作成しているため、参考にする場合は適宜変更してください。 商用の環境でCockroachDBを利用する場合は、SANを構築してPVを作成するのがいいかもしれない。

cockroach-pv.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: cockroach-sample
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: crdb-pv1
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /opt/k8s/localpv
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: In
            values:
            - rs01 # ホスト名は適宜修正する
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: crdb-pv2
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /opt/k8s/localpv
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: In
            values:
            - rs02 # ホスト名は適宜修正する
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: crdb-pv3
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /opt/k8s/localpv
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: In
            values:
            - rs03 # ホスト名は適宜修正する

cockroach.yaml

apiVersion: crdb.cockroachlabs.com/v1alpha1
kind: CrdbCluster
metadata:
  name: cockroachdb
  namespace: cockroach-sample
spec:
  dataStore:
    pvc:
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: "5Gi"
        volumeMode: Filesystem
        storageClassName: local-storage
  resources:
    requests:
      cpu: 500m
      memory: 2Gi
    limits:
      cpu: 2
      memory: 8Gi
  tlsEnabled: true
  image:
    name: cockroachdb/cockroach:v24.2.0
  nodes: 3
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: In
            values: 
            - rs01 # ホスト名は適宜修正する
            - rs02 # ホスト名は適宜修正する
            - rs03 # ホスト名は適宜修正する

# PV用のディレクトリ作成は各ノードで実施
sudo mkdir -p /opt/k8s/localpv

kubectl create -f cockroach-pv.yaml
kubectl create -f cockroach.yaml

kubectl get po -n cockroach-sample
# NAME          READY   STATUS    RESTARTS   AGE
# cockroachdb   1/1     Running   0          13h
# cockroachdb   1/1     Running   0          13h
# cockroachdb   1/1     Running   0          13h

デプロイ時には証明書も作成されている。この証明書を利用してこの後rootでログインする。

kubectl get secret -n cockroach-sample 
# NAME               TYPE     DATA   AGE
# cockroachdb-ca     Opaque   1      5s
# cockroachdb-node   Opaque   3      3s
# cockroachdb-root   Opaque   3      2s

動作確認

専用のSQLクライアントが入ったPodのマニフェストが用意されているので利用する。 https://github.com/cockroachdb/cockroach-operator?tab=readme-ov-file#access-the-sql-shell に従って進める。

kubectl create -f https://raw.githubusercontent.com/cockroachdb/cockroach-operator/master/examples/client-secure-operator.yaml -n cockroach-sample

kubectl exec -it cockroachdb-client-secure -n cockroach-sample -- ./cockroach sql --certs-dir=/cockroach/cockroach-certs --host=cockroachdb-public

rootでログインできたのでユーザ作成しつつ、pg_databaseを見てみる。defaultdbというDBが作られている。テストや幾つかの内部データベースで利用されると記載があるので、削除しないほうが良さそうである。

CREATE USER roach WITH PASSWORD 'Q7gc8rEdS';
SELECT * FROM pg_database;
--   oid |  datname  |   datdba   | encoding | datcollate |  datctype  | datistemplate | datallowconn | datconnlimit | datlastsysoid | datfrozenxid | datminmxid | dattablespace | datacl
-- ------+-----------+------------+----------+------------+------------+---------------+--------------+--------------+---------------+--------------+------------+---------------+---------
--   100 | defaultdb | 1546506610 |        6 | en_US.utf8 | en_US.utf8 |       f       |      t       |           -1 |             0 |         NULL |       NULL |             0 | NULL
--   102 | postgres  | 1546506610 |        6 | en_US.utf8 | en_US.utf8 |       f       |      t       |           -1 |             0 |         NULL |       NULL |             0 | NULL
--     1 | system    | 3233629770 |        6 | en_US.utf8 | en_US.utf8 |       f       |      t       |           -1 |             0 |         NULL |       NULL |             0 | NULL
-- (3 rows)

-- SQLコマンドは普通に利用できる。
CREATE DATABASE IF NOT EXISTS bank;
USE bank;
CREATE TABLE accounts (id INT8 PRIMARY KEY, balance DECIMAL);
INSERT INTO accounts (balance, id) VALUES (25000.00, 2);
SELECT * FROM accounts;
--   id | balance
-- -----+-----------
--    2 | 25000.00
-- (1 row)
GRANT SELECT ON TABLE bank.accounts TO roach;
\q

TLSを無効にしてみる

先ほどのPodを利用してinsecureでログインしようとすると、ノードがsecure modeで起動しているため失敗する。

kubectl exec -it cockroachdb-client-secure -n cockroach-sample -- ./cockroach sql --certs-dir=/cockroach/cockroach-certs --host=cockroachdb-public --
insecure
# #
# # Welcome to the CockroachDB SQL shell.
# # All statements must be terminated by a semicolon.
# # To exit, type: \q.
# #
# ERROR: SSL authentication error while connecting.
# failed to connect to `host=cockroachdb-public user=root database=`: server error (ERROR: node is running secure mode, SSL connection required (SQLSTATE 08P01))
# Failed running "sql"
# command terminated with exit code 1

ここで一度DBクラスタを削除して、先ほどのcockroach.yamltlsEnabledfalseにしてapplyしてみる。

kubectl delete -f cockroach.yaml
sed 's/tlsEnabled: true/tlsEnabled: false/' cockroach.yaml | kubectl apply -f -
# crdbcluster.crdb.cockroachlabs.com/cockroachdb created

kubectl get crdbclusters.crdb.cockroachlabs.com -n cockroach-sample -o yaml | grep -i tls
# {"apiVersion":"crdb.cockroachlabs.com/v1alpha1","kind":"CrdbCluster","metadata":{"annotations":{},"name":"cockroachdb","namespace":"cockroach-sample"},"spec":{"affinity":{"nodeAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"kubernetes.io/hostname","operator":"In","values":["rs01","rs02","rs03"]}]}]}}},"dataStore":{"pvc":{"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"5Gi"}},"storageClassName":"local-storage","volumeMode":"Filesystem"}}},"image":{"name":"cockroachdb/cockroach:v24.2.0"},"nodes":3,"resources":{"limits":{"cpu":2,"memory":"8Gi"},"requests":{"cpu":"500m","memory":"2Gi"}},"tlsEnabled":false}}

kubectl exec -it cockroachdb-client-secure -n cockroach-sample -- ./cockroach sql --certs-dir=/cockroach/cockroach-certs --host=cockroachdb-public --insecure
USE bank;
SELECT * FROM accounts;
--   id | balance
-- -----+-----------
--    2 | 25000.00
-- (1 row)

insecureでログインでき、データも残っていた。引き続き再度ユーザ作成をしてみる。

-- パスワード有りではユーザ作成できない
CREATE USER roach2 WITH PASSWORD 'Q7gc8rEdS';
-- ERROR: setting or updating a password is not supported in insecure mode
-- SQLSTATE: 28P01
CREATE USER roach2;
-- CREATE ROLE

このように、insecure modeだとパスワードの生成ができない。ドキュメントをよくみると記載がある。他にもできないことがあるかもしれないが、insercure modeでできない一覧は探し当てることができなかった。insecure modeで運用するのであれば、初期構築時にはsecure modeで起動し、ユーザ作成など必要なことが終わったら一度DBクラスタを削除して、insecure modeで再作成するのが良いのだろう。ただしユーザ作成のたびに再作成が必要なため、他に切り替える良い方法があるかは引き続き模索していきたい。

ユーザの証明書作成

secure modeで運用する場合、かつ証明書の検証をする場合(verify-ca/verify-full)は、ログインに必要な証明書を作成する必要がある。証明書の作成には、opensslcockroach CLIを利用できる。(https://www.cockroachlabs.com/docs/stable/cockroach-cert 参照) ユーザの証明書作成には、root CAが必要だが、DBクラスタと同時に作成されており、secretに格納されている。

今回は、cockroach CLIを利用する。ローカルにインストールするのではなく、先ほどのPodのマニフェストを修正して利用する。

client-secure-operator.yaml

apiVersion: v1
kind: Pod
metadata:
  name: cockroachdb-client-secure
  namespace: cockroach-sample
spec:
  serviceAccountName: cockroachdb-sa
  containers:
    - name: cockroachdb-client-secure
      image: cockroachdb/cockroach:v24.2.0
      imagePullPolicy: IfNotPresent
      volumeMounts:
        - name: client-certs
          mountPath: /cockroach/cockroach-certs/
      command:
        - sleep
        - "2147483648" # 2^31
  terminationGracePeriodSeconds: 0
  volumes:
    - name: client-certs
      projected:
        sources:
          - secret:
              name: cockroachdb-ca
              items:
                - key: ca.key
                  path: ca.key
          - secret:
              name: cockroachdb-node
              items:
                - key: ca.crt
                  path: ca.crt
          - secret:
              name: cockroachdb-root
              items:
                - key: tls.crt
                  path: client.root.crt
                - key: tls.key
                  path: client.root.key

kubectl delete po -n cockroach-sample cockroachdb-client-secure
kubectl create -f client-secure-operator.yaml

# secure modeにするため再作成する
kubectl delete -f cockroach.yaml
kubectl create -f cockroach.yaml
kubectl get crdbclusters.crdb.cockroachlabs.com -n cockroach-sample -o yaml | grep -i tls
#     tlsEnabled: true

kubectl exec -it cockroachdb-client-secure -n cockroach-sample -- bash -c 'cp -rp /cockroach/cockroach-certs /cockroach/tmp-certs && chmod -R 400 /cockroach/tmp-certs && cockroach cert create-client roach --certs-dir=/cockroach/tmp-certs --ca-key=/cockroach/tmp-certs/ca.key'
kubectl cp -n cockroach-sample cockroachdb-client-secure:tmp-certs/client.roach.key ./client.roach.key
kubectl cp -n cockroach-sample cockroachdb-client-secure:tmp-certs/client.roach.crt ./client.roach.crt
ls -l client.roach.*
# -rw-r--r-- 1 hirano00o hirano00o 1147  8月 29 19:28 client.roach.crt
# -rw-r--r-- 1 hirano00o hirano00o 1679  8月 29 19:28 client.roach.key

必要なものはDBクラスタ構築時にsecretにあるため、それらをマウントしている。マウントしたディレクトリはread-onlyなためコピーした上で権限を400に変更している。 cockroach cert create-client コマンドで証明書を作成している。

作成した証明書が利用できるか試してみる。CockroachDBの待受ポートは26257なのでport-forwardし、別ターミナルでCAの証明書を出力し、Podから取得したユーザの鍵の権限を変更しておく。

kubectl port-forward -n cockroach-sample services/cockroachdb-public 26257

# another terminal
kubectl get secret -n cockroach-sample cockroachdb-root -o='jsonpath={.data.ca\.crt}' | base64 -d > ca.crt
chmod 600 client.roach.key

下記の簡単なクエリを発行するプログラムを実行する。インサートしたid値である2が表示され、正しく証明書が作成されていることがわかる。

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/lib/pq"
)

func main() {
    db, err := sql.Open("postgres", "host=localhost port=26257 user=roach password=Q7gc8rEdS dbname=bank sslmode=verify-full sslrootcert=ca.crt sslcert=client.roach.crt sslkey=client.roach.key")
    if err != nil {
        panic(err)
    }
    var id int
    if err := db.QueryRow("select id from accounts").Scan(&id); err != nil {
        panic(err)
    }
    fmt.Println(id)
}
go mod init cockroach-sample
go mod tidy
go run main.go 
# 2

終わりに

オンプレのKubernetesにCockroachDBを導入し、TLS有無それぞれでの動作を確認した。TLSが有効の場合はパスワードが生成できないことがわかった。DBクラスタでTLS有無を切り替える場合は、現状再作成が必要なように伺える。ユーザ作成毎にDBクラスタを再作成することに抵抗がある場合は、TLS有効のままsslmode=requireで接続すると良い。もしverify-ca/verify-fullで接続したい場合は、cockroachCLIでユーザ証明書を簡易に作成できる。