CustomResourceでPostgresのDatabaseとUser(とPassword)を作成したい

PostgreSQLやMySQLを使っていると、アプリケーションごとにDatabaseやUser、Passwordを作成して権限制御したい場合が多々あると思う。特にKubernetesを利用している場合は、アプリケーションのリソース定義と一緒に、PostgreSQLのDatabaseやUserも合わせて定義できたら大変便利である。
Kubernetes上でDBクラスタ自体を管理するOperatorの場合は、上記のような仕組みを併せ持っている場合も多いと思うが、今回はDBクラスタはAWS上のAurora Serverless v2で運用し、Kubernetes上で管理するのはあくまでクラスタ内のDatabseとUser, Passwordになる。
使えそうなライブラリとしては3点見つけた。
- https://github.com/crossplane-contrib/provider-sql
- https://github.com/movetokube/postgres-operator
- https://github.com/EasyMile/postgresql-operator
1はcrossplaneのプラグインの1つで、PostgreSQLに限らすMySQLとかも管理できそう。2と3はPostgreSQL専用。2の方がstarが多いが、ソースコードとかは3のほうがキレイに見える(ロクにGo書けないので直感)。
1はcrossplane本体をいれるのがちょっと面倒というか大げさな感じがする。
というわけでスターは少ないが企業が管理している3を採用してみる。

Operatorを入れてみる(EasyMile/postgresql-operator)
Helm ChartがGithub内に用意されているが、Chart用の公開リポジトリの記載がない。
ローカルに落として来る必要がありそうだ。面倒だぞ。Issueを立ててみた。時間がないのだ許してくれ。
Operatorを入れてみる(movetokube/postgres-operator)
というわけで急遽Operatorを変更する。なんて技術力、解決力のなさ。
以下で入った気がする。valuesのところにAWSで立てたAuroraに接続するための情報を渡す。
なお、接続情報をもたせたSecretを作っておいて、そのSecretの名前を渡す方式も可能らしい。
import * as k8s from "@pulumi/kubernetes"
import * as pulumi from "@pulumi/pulumi"
export type KubernetesPostgresOperatorArgs = {
host: pulumi.Input<string>
user: pulumi.Input<string>
password: pulumi.Input<string>
defaultDatabase: pulumi.Input<string>
}
export class KubernetesPostgresOperator extends pulumi.ComponentResource {
public opts: pulumi.ResourceOptions
public namespace: k8s.core.v1.Namespace
public release: k8s.helm.v3.Release
constructor(
name: string,
args: KubernetesPostgresOperatorArgs,
opts?: pulumi.ResourceOptions,
) {
super("stack8:kubernetes:PostgresOperator", name, undefined, opts)
this.opts = { ...opts, parent: this }
this.namespace = new k8s.core.v1.Namespace(
"namespace",
{
metadata: {
name: "postgres-operator",
},
},
this.opts,
)
this.release = new k8s.helm.v3.Release(
"release",
{
chart: "ext-postgres-operator",
namespace: this.namespace.metadata.name,
version: "1.2.6",
repositoryOpts: {
repo: "https://movetokube.github.io/postgres-operator/",
},
values: {
postgres: {
host: args.host,
user: args.user,
password: args.password,
cloud_provider: "AWS",
default_database: args.defaultDatabase,
},
},
},
{
...this.opts,
// FIXME:
// It always comes up as a diff, so we'll make it an ignore target once.
// We want to investigate.
ignoreChanges: ["checksum"],
},
)
}
}

Databaseを作ってみる
以下のようなCustomResourceを定義することで、PostgresのDatabaseとUser(Role)、Passwordが生成されるらしい。Pulumi(TypeScriptなのは許して欲しい)からの抜粋なので見づらいのはご勘弁。
his.postgresDatabse = new k8s.apiextensions.CustomResource(
"postgres-database",
{
apiVersion: "db.movetokube.com/v1alpha1",
kind: "Postgres",
metadata: {
namespace: this.namespace.metadata.name,
name: "example-app",
},
spec: {
database: "example-app",
dropOnDelete: true,
extensions: ["pgcrypto", "pg_bigm"],
},
},
this.opts,
)
this.postgresUser = new k8s.apiextensions.CustomResource(
"postgres-user",
{
apiVersion: "db.movetokube.com/v1alpha1",
kind: "PostgresUser",
metadata: {
namespace: this.namespace.metadata.name,
name: "example-app",
},
spec: {
role: "example-app",
database: "example-app",
secretName: "secret",
privileges: "OWNER",
},
},
this.opts,
)
適応すると接続情報を持ったSecretが生成されるっぽい。
HOST名が含まれるとこだけ念の為マスクしている。
✗ kubectl get secrets -n example-app secret-example-app -o yaml
apiVersion: v1
data:
DATABASE_NAME: ZXhhbXBsZS1hcHA=
HOST: xxx
LOGIN: ZXhhbXBsZS1hcHAtdUpFMGZR
PASSWORD: VkdzNU5kSmxaSEx5UXkx
POSTGRES_DOTNET_URL: xxx
POSTGRES_JDBC_URL: xxx
POSTGRES_URL: xxx
ROLE: ZXhhbXBsZS1hcHAtdUpFMGZR
kind: Secret
metadata:
creationTimestamp: "2024-03-15T05:30:43Z"
labels:
app: example-app
name: secret-example-app
namespace: example-app
type: Opaque
試しに接続してみる。
指定したpgcrypto
とpg_bigm
が入っていることも確認できた。
なぜplpgsqlが入っているのかは不明。Auroraでは必須なのか、ParameterGroupとかで指定しているのか。きっと何かしら理由があるだろうが、いまは一旦良し。
✗ kubectl run -it --rm --restart=Never postgres-cli --image=postgres -- sh
# psql postgresql://example.com/example-app
psql (16.2 (Debian 16.2-1.pgdg120+2), server 15.4)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)
Type "help" for help.
example-app=> select * from pg_extension;
oid | extname | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition
-------+----------+----------+--------------+----------------+------------+-----------+--------------
14498 | plpgsql | 10 | 11 | f | 1.0 | |
24736 | pgcrypto | 10 | 2200 | t | 1.3 | |
24773 | pg_bigm | 10 | 2200 | t | 1.2 | |
(3 rows)
schemaは指定しなかったが以下のような感じになっている。
デフォルトではpublicのみらしい。
example-app=> \dn
List of schemas
Name | Owner
--------+-------------------
public | pg_database_owner
(1 row)
以下のように指定することで作成されるSchemaを指定できるらしい。
変更して適用してみる。
this.postgresDatabse = new k8s.apiextensions.CustomResource(
"postgres-database",
{
apiVersion: "db.movetokube.com/v1alpha1",
kind: "Postgres",
metadata: {
namespace: this.namespace.metadata.name,
name: "example-app",
},
spec: {
database: "example-app",
dropOnDelete: true,
+ schemas: ["public", "dev", "test"],
extensions: ["pgcrypto", "pg_bigm"],
},
},
this.opts,
)
適応後Schemaを確認すると増えている。
Ownerはpg_database_owner
ではなく、直接example-app-group
になっていた。
example-app=> \dn
List of schemas
Name | Owner
--------+-------------------
dev | example-app-group
public | pg_database_owner
test | example-app-group

まとめ
https://github.com/movetokube/postgres-operator でやりたいことはできた!
https://github.com/EasyMile/postgresql-operator はHelm Chartのリポジトリ待ち。
Preview環境を作るときなども専用のDatabsae, Userを用意してあげると助かるケースはあると思うので、他にも利用されている方が居たらノウハウなど教えて欲しい。