PostgreSQL on Kubernetes を 試してみた
この記事は MICIN Advent Calendar 2022 の2日目の記事です。
前回は、あの日見たerbを僕達はまだ知らない。でした。
そして PostgreSQLアドベントカレンダー 2022 の2日目とのマルチポストでもあります。
PostgreSQLアドベントカレンダーの前回の記事は、noborusさんのPostgreSQL 15 日本語マニュアルあり〼でした。
はじめに
先日、PostgreSQL Conference Japan 2022 に一般参加をしてきました。PostgreSQL Conferenceに参加し始めて5年ほどたちますが、普段業務ではキャッチアップしきれないような最新情報のキャッチアップをはじめ、勉強になることが多く、非常に刺激的な1日を過ごすことができました。
その中で、【B2】PGOを用いたPostgreSQL on Kubernetes入門 というセッションを聴講し、「自分もKubernetesでPostgreSQLを動かしてみたい!」と思いました。
MICINでは通常RDS for PostgreSQLを利用していますし、なかなか自前のPostgreSQLを、しかもKubernetes上で運用するということもなさそうではあるのですが、自分の中にいろいろな選択肢を持っておきたいというのが主なモチベーションです。
せっかくなので、セッションで学んだPGOそのままではなく、その次くらいにStarがついているZalandoのpostgres_operator を使ってやってみようかな。
そんな感じで、PostgreSQLアドベントカレンダーの記事としてはあまり目新しさがないかもしれませんが、やってみようと思います。
この記事で扱うこと
-
kind + postgres_operator で PostgreSQL on Kubernetes を試してみる。
-
(+アルファでなにか)
この記事で扱わないこと
-
Kubernetesとはなにか
-
Kubernetes の 基本操作
-
PostgreSQL on Kubernetes のメリット・デメリット(の詳細)
1.kindのinstall
https://kind.sigs.k8s.io/docs/user/quick-start/
まずはローカルでkubernetesを動かす環境を用意しなければなりません。
kindというDocker上でKubernetesクラスタを構築できるツールがあるようなので、これを利用します。
こちらはKubernetesの公式プロジェクトで、マルチノードクラスタを簡単に作成できる、テストやCIのために使いやすいツールです。
リンク先の指示に従ってインストールしてみます。
コマンド一発でkindのクラスタを作ったらあとはkubectlでマニュアル操作するだけなので、「Kubernetesの使い方を試してみる」には非常にお手軽でうれしいツールです。
$ kind create cluster
$ kubectl get all
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 58s
これだけでローカルのKubernetesクラスタが動くので非常にうれしいですね。
2022/11/23 現在、こちらのissueが改善されていないようです。
kindでクラスタを作成する際にベースイメージを指定してKubernetes 1.23以前で動くようにしました。
kind create cluster --image kindest/node:v1.23.13
2. postgres_operator by zalando を使ってみる
今回は、https://github.com/zalando/postgres-operator で公開されている postgres_operator を使ってみようと思います。
ざっくりと調べてみたところ、UIがあるのがPGOとの大きな違いのようです。
また、PostgreSQLの 自動フェイルオーバークラスタを構築するのに有用な Patroni もZalandoが開発しているので、そのあたりの連携なんかも期待できるかもしれません。
readme に書かれている機能から抜粋すると、
-
ローリングアップデートでマイナーバージョンアップを迅速にできます
-
メジャーバージョンアップの場合にはインプレースアップグレードをサポートしています
-
podの再起動を伴わずにボリュームのリサイズができます(AWS EBS, PVC)
-
PGBouncerを利用したコネクションプーリングを利用できます
-
論理バックアップをS3やGCSに保存できます。
-
S3やGCSにあるWALアーカイブからstandby クラスタを構築できます
-
Patroniを利用したストリーミングレプリケーション構成を構築できます。
-
Spiloとpg_basebackup / WAL-E を利用してPoint-In-Time-Recoveryができます。
といったところで、非常に魅力的に思えます。
postgres-operatorを構築する
$ git clone https://github.com/zalando/postgres-operator.git .
$ cd postgres-operator
まずはclone してきて、quickstart に沿ってコマンドを打ってみましょう。
$ kubectl create -f manifests/configmap.yaml
configmap/postgres-operator created
$ kubectl create -f manifests/operator-service-account-rbac.yaml
serviceaccount/postgres-operator created
clusterrole.rbac.authorization.k8s.io/postgres-operator created
clusterrolebinding.rbac.authorization.k8s.io/postgres-operator created
clusterrole.rbac.authorization.k8s.io/postgres-pod created
$ kubectl create -f manifests/postgres-operator.yaml
deployment.apps/postgres-operator created
$ kubectl create -f manifests/api-service.yaml
service/postgres-operator created
ここまで適用したら、状況を確認してみましょう。
create した オブジェクトが稼働している様子がうかがえます。
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/postgres-operator-5c7d5d756c-bqqnf 1/1 Running 1 (10s ago) 22s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2m35s
service/postgres-operator ClusterIP 10.96.160.71 <none> 8080/TCP 8s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/postgres-operator 1/1 1 1 22s
NAME DESIRED CURRENT READY AGE
replicaset.apps/postgres-operator-5c7d5d756c 1 1 1 22s
operator UI を デプロイする
zalandoのpostgres_operatorには管理UIがついているらしいので使ってみましょう。
以下のコマンドでデプロイできます。
$ kubectl apply -k ui/manifests
serviceaccount/postgres-operator-ui created
clusterrole.rbac.authorization.k8s.io/postgres-operator-ui created
clusterrolebinding.rbac.authorization.k8s.io/postgres-operator-ui created
service/postgres-operator-ui created
deployment.apps/postgres-operator-ui created
ingress.networking.k8s.io/postgres-operator-ui created
$ kubectl get pod -l name=postgres-operator-ui
NAME READY STATUS RESTARTS AGE
postgres-operator-ui-64898f986c-qx54m 1/1 Running 0 98s
uiのpodが立ち上がりました。local にポートフォワーディングして、localからアクセスできるようにしてみましょう。
※ kubectl port-forward
は リターンせずにそのままターミナルを占有するので、必ず別のタブなりウィンドウなりを開いて実行しましょう。 &
もききませんでした……
https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/
Note: kubectl port-forward does not return. To continue with the exercises, you will need to open another terminal.
$ kubectl port-forward svc/postgres-operator-ui 8081:80
Forwarding from 127.0.0.1:8081 -> 8081
Forwarding from [::1]:8081 -> 8081
アクセスするとこんな感じの画面が出てきます。
フォームに入力すると、右側のyamlが動的に更新されていきます。
IaCを推進するにあたってこういうのがあるととっつきやすくて非常によいですね。
PostgreSQL Cluster を作ってみる。
quick start の続きを見ると、以下のコマンドで postgres cluster を作ってみようとあります。
$ kubectl create -f manifests/minimal-postgres-manifest.yaml
多分これを実行すると PostgreSQLのクラスタが立ち上がるのだとは思いますが、まずはこのyamlに何がかいてあるのか見てみましょう。
postgres_operatorの細かい設定は必要になってから調べてもいいですが、PostgreSQLの設定はある程度認識しておきたいですからね。
apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
name: acid-minimal-cluster
namespace: default
spec:
teamId: "acid"
volume:
size: 1Gi
numberOfInstances: 2
users:
zalando: # database owner
- superuser
- createdb
foo_user: [] # role for application foo
databases:
foo: zalando # dbname: owner
preparedDatabases:
bar: {}
postgresql:
version: "14"
というわけでこんな感じです。
リファレンスはこちら
PostgreSQL14 のインスタンスが2台、zalandoというユーザーにfooというデータベースができそうですね。ボリュームも1Giということでカジュアルに実行してもローカルのリソースを圧迫したりしなそうです。
ちょっと実行してみましょう。
$ kubectl create -f manifests/minimal-postgres-manifest.yaml
postgresql.acid.zalan.do/acid-minimal-cluster created
$ kubectl get postgresql
NAME TEAM VERSION PODS VOLUME CPU-REQUEST MEMORY-REQUEST AGE STATUS
acid-minimal-cluster acid 14 2 1Gi 2m16s Creating
version 14, pods が2つ、Volumeは1Gi、たしかに書かれている通りですね。StatusがCreatingなのでしばらく待ってみましょう。
$ kubectl get postgresql
NAME TEAM VERSION PODS VOLUME CPU-REQUEST MEMORY-REQUEST AGE STATUS
acid-minimal-cluster acid 14 2 1Gi 4m5s Running
数分待っていたら、無事Runningになりました。
こちらの記述を元に接続をしてみましょう。
$ export PGMASTER=$(kubectl get pods -o jsonpath={.items..metadata.name} -l application=spilo,cluster-name=acid-minimal-cluster,spilo-role=master -n default)
$ kubectl port-forward $PGMASTER 6432:5432 -n default
$ export PGPASSWORD=$(kubectl get secret postgres.acid-minimal-cluster.credentials.postgresql.acid.zalan.do -o 'jsonpath={.data.password}' | base64 -d)
$ psql -U postgres -h localhost -p 6432
psql (14.5)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
postgres=#
無事につながりました。
schemaやテーブルをみると、ログやcron、メトリックや管理情報などがテーブルになっているようで、非常にうれしいです。
postgres=# \dn
List of schemas
Name | Owner
-----------------+----------
cron | postgres
metric_helpers | postgres
public | postgres
user_management | postgres
zmon_utils | postgres
(5 rows)
postgres=# \dt
List of relations
Schema | Name | Type | Owner
--------+--------------+-------+----------
public | postgres_log | table | postgres
(1 row)
設定を管理する
PostgreSQLの設定と言えば、 postgresql.conf
や、 pg_hba.conf
ですよね。
manifestでそのあたりも指定できるみたいです。
こんな感じで my-postgres-manifest.yaml
を作って適用してみましょう。
ついでに、いかにも(公式)サンプルっぽい名前のところを「俺のマニフェストだ!」って感じに変更してみます。
気をつけたいところとしては、クラスタ名は teamId-
で始まる必要があるという規約でしょうか。
それから、pg_hba.confの設定を追加するときには、replicationの行を入れておかないとstandbyのPodからプライマリのPodに接続できなくてクラスタの構築に失敗してしまいます。
自前でレプリケーションを構築する手順を思い出せば当たり前なのですが、意識せずにレプリケーションが設定されていることもあってうっかりしてしまいました。
apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
name: sugai-postgresql-cluster
namespace: default
spec:
teamId: "sugai"
volume:
size: 1Gi
numberOfInstances: 2
users:
sugai: # database owner
- superuser
- createdb
reader: []
databases:
my_database: sugai # dbname: owner
preparedDatabases:
prepared: {}
postgresql:
version: "14"
parameters:
shared_buffers: "64MB"
max_connections: "20"
log_statement: "all"
patroni:
initdb:
encoding: "UTF8"
locale: "C"
data-checksums: "true"
pg_hba:
- local all all trust
- hostssl all all 0.0.0.0/0 md5
- host all all 0.0.0.0/0 md5
- host all all ::1/128 md5
- host replication standby 0.0.0.0/0 md5
$ kubectl create -f manifests/my-postgres-manifest.yaml
このyamlを適用するとこんな感じになりました。
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/postgres-operator-7c854475f8-cqfth 1/1 Running 1 (3h ago) 3h
pod/postgres-operator-ui-b9dd88cf4-b6272 1/1 Running 0 3h
pod/sugai-postgresql-cluster-0 1/1 Running 0 15m
pod/sugai-postgresql-cluster-1 1/1 Running 0 14m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3h1m
service/postgres-operator ClusterIP 10.96.129.152 <none> 8080/TCP 3h
service/postgres-operator-ui ClusterIP 10.96.247.37 <none> 80/TCP 3h
service/sugai-postgresql-cluster ClusterIP 10.96.123.62 <none> 5432/TCP 15m
service/sugai-postgresql-cluster-config ClusterIP None <none> <none> 14m
service/sugai-postgresql-cluster-repl ClusterIP 10.96.134.139 <none> 5432/TCP 15m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/postgres-operator 1/1 1 1 3h
deployment.apps/postgres-operator-ui 1/1 1 1 3h
NAME DESIRED CURRENT READY AGE
replicaset.apps/postgres-operator-7c854475f8 1 1 1 3h
replicaset.apps/postgres-operator-ui-b9dd88cf4 1 1 1 3h
NAME READY AGE
statefulset.apps/sugai-postgresql-cluster 2/2 15m
NAME TEAM VERSION PODS VOLUME CPU-REQUEST MEMORY-REQUEST AGE STATUS
postgresql.acid.zalan.do/sugai-postgresql-cluster sugai 14 2 1Gi 15m Running
postgres_operator の機能をいくつか試してみる
standbyノードの追加/削除
kubectl edit
コマンドを使ってもいいのですが、IaCの観点からいくとmanifestを修正してapplyする方がよいでしょう。
< numberOfInstances: 2
---
> numberOfInstances: 4
として、適用してみましょう。
$ kubectl apply -f manifests/my-postgres-manifest.yaml
postgresql.acid.zalan.do/sugai-postgresql-cluster configured
$ kubectl get postgresql
NAME TEAM VERSION PODS VOLUME CPU-REQUEST MEMORY-REQUEST AGE STATUS
sugai-postgresql-cluster sugai 14 4 1Gi 20m Running
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
postgres-operator-7c854475f8-cqfth 1/1 Running 1 (3h5m ago) 3h5m
postgres-operator-ui-b9dd88cf4-b6272 1/1 Running 0 3h5m
sugai-postgresql-cluster-0 1/1 Running 0 20m
sugai-postgresql-cluster-1 1/1 Running 0 20m
sugai-postgresql-cluster-2 1/1 Running 0 29s
sugai-postgresql-cluster-3 1/1 Running 0 19s
さすがKubernetesです。細かい設定など何一つせずにシュッとPodを追加できました。
numberOfInstances
を減らしてapplyするとちゃんとレプリカが減ります。
DBのレプリケーションができているかどうかも試してみましょう。
以前に試した以下のポートフォワーディング設定があります。
$ kubectl port-forward $PGMASTER 6432:5432 -n default
$ psql -U postgres -h localhost -p 6432
postgres=# create table test (id serial primary key, value text );
CREATE TABLE
postgres=# insert into test(value)values('hoge'),('fuga'),('piyo');
INSERT 0 3
postgres=# select * from test;
id | value
----+-------
1 | hoge
2 | fuga
3 | piyo
(3 rows)
追加したレプリカにもポートフォワードしてみましょう
$ kubectl port-forward sugai-postgresql-cluster-3 7432:5432 -n default
% psql -U postgres -h localhost -p 7432
postgres=# select * from test;
id | value
----+-------
1 | hoge
2 | fuga
3 | piyo
(3 rows)
先ほど追加したテーブルとレコードが入っています。
postgres=# insert into test(value)values('hogehoge');
ERROR: cannot execute INSERT in a read-only transaction
ストリーミングレプリケーションでは、もちろんレプリカにINSERTはできません。
自動フェイルオーバーのテスト
masterのpostmasterプロセスをkillしてみましょう。
$ kubectl exec -it sugai-postgresql-cluster-0 -- bash
でPodに入って
# kill 1
としてみましょう。
$ kubectl get pods -l application=spilo -L spilo-role
NAME READY STATUS RESTARTS AGE SPILO-ROLE
sugai-postgresql-cluster-0 1/1 Running 0 55m
sugai-postgresql-cluster-1 1/1 Running 0 55m replica
sugai-postgresql-cluster-2 1/1 Running 0 35m replica
sugai-postgresql-cluster-3 1/1 Running 0 35m master
master pod のプロセスがクラッシュしたことを受けて、即座に3番がmasterに昇格しています。
$ kubectl get pods -l application=spilo -L spilo-role
NAME READY STATUS RESTARTS AGE SPILO-ROLE
sugai-postgresql-cluster-0 1/1 Running 1 (107s ago) 57m replica
sugai-postgresql-cluster-1 1/1 Running 0 57m replica
sugai-postgresql-cluster-2 1/1 Running 0 37m replica
sugai-postgresql-cluster-3 1/1 Running 0 37m master
1分ほどで元masterだったpodもreplicaとしてrestartしています。
$ psql -U postgres -h localhost -p 6432
postgres=# insert into test(value)values('hoge');
ERROR: cannot execute INSERT in a read-only transaction
replicaになったので sugai-postgresql-cluster-0
にはInsert できません。
$ psql -U postgres -h localhost -p 7432
postgres=# insert into test(value)values('hoge');
INSERT 0 1
昇格した sugai-postgresql-cluster-3
にはInsertできるようになっています。
やり残したこと
そろそろ分量も長くなってきましたし、この記事の締め切りの時間も近づいて参りました。
本来であれば、以下のような部分もきちんと試してみたかったのですが、これは冬休みの宿題にでもしようと思います。
-
クラウドへの論理バックアップ
-
PITR
-
クラスタ間HA構成とフェイルオーバー
-
コネクションプーリング(PGBouncer)
-
オートスケーリング
-
sidecarを使ったログ収集や監視の構築
-
EKSやGKEを使って本格的に運用するときへの道しるべを作る
おわりの前に
検証用に使ったkind cluster は以下のコマンドで削除することができます。
kind delete cluster
使い始めるのも、やめるのも気軽にできてうれしいです。
おわりに
最近ではRDSやCloud SQLを業務利用することが一般的になっており、MICINでもその例に漏れず通常はAmazon RDS for PostgreSQLを利用しています。
今回はサンプル程度の検証までしかできませんでしたが、PostgreSQL on Kubernetes を構築してみることによってDBaaSの裏側を少しだけですが想像できるようになったような気がします。
また、Kubernetes や PostgreSQLのHAクラスタがこんなに手軽にローカルに作ってみることができるというのを体験できたのは非常によかったです。
MICIN Advent Calendar で明日は次田さんの【Apollo Client で複数の GraphQL API を扱う】です。
PostgreSQLアドベントカレンダーの明日は kasa_zip さんです。
MICINではメンバーを大募集しています。
「とりあえず話を聞いてみたい」でも大歓迎ですので、お気軽にご応募ください!
MICIN採用ページ:https://recruit.micin.jp/
Discussion