我想在我的服务器上发布一个小型爱好网站。为此,我选择使用 kubernetes,因为我在工作中也部分使用它,所以我想熟悉一下。我购买了hetzner debian服务器并安装了k3s。现在正在按照此教程部署 PostgreSQL 容器(版本 15.2,不是集群)。 (我做了一些从其他教程中看到的小改动,应该不相关)。
运行良好,我对此感到满意。但我尝试重新启动部署,以确保如果服务器因某种原因出现故障,数据不会丢失。重新启动几次后,数据库已损坏。
我一看到:
PANIC: invalid magic number 0000 in log segment 000000010000000000000000, offset 0
另一次:
invalid contrecord length 1174 (expected 48430224) at 0/195BC90
另一次:
PANIC: could not locate a valid checkpoint record
当我尝试用谷歌搜索如何从中恢复时,我没有找到任何安全的选项,大多数建议是恢复备份。
所以我的问题是,如何安全地重新启动/关闭 PostgreSQL 容器?我是否缺少 k8s 中 PostgreSQL pod 的一些关闭配置?
更新1:
我使用
r
命令从 k9s 重新启动部署。我认为 UI 让它看起来像是立即旋转的,但这可能需要一些时间。所以我认为我每 10 秒触发多次重新启动,这可能会损坏数据库。无论如何,我添加了 terminationGracePeriodSeconds: 60
并使用了答案中的 preStop
钩子。谢谢
更新2: 我导入了数据库,重新启动并再次出现同样的问题:
找不到有效的检查点记录
更新3: 我将
Deployment
替换为 StatefulSet
,它似乎可以更好地处理重启。尝试了 10 多次重启,没有任何问题。而在第四次重新启动时崩溃之前。
当然,最佳实践是使用像 cloudnative-pg 或 postgres-operator 这样的运算符,但它们相当大,并且对于简单的工作负载可能具有更多功能。这是解决您问题的简单方法。
将以下内容添加到您的 pod 规格中
preStop:
exec:
command: ["/usr/local/bin/pg_ctl stop -D /var/lib/postgresql/data -w -t 60 -m fast"]
基本上,当您杀死一个 pod 时,Kubernetes 会发出信号
SIGTERM
并为您的 pod 提供 30 秒的时间,在那之后它会发送 SIGKILL
。当 postgres 收到 SIGTERM
时,它不会接受网络连接,但不会终止现有的终止,因此任何客户端都会阻止 db 的终止,并且 30 秒后 pod 将收到 SIGKILL
,这对 postgres doc 非常不利。所以你需要以某种方式安全地关闭 postgres,使用 preStop
钩子就可以。
这是您的 Pod 的确切时间顺序:
state=Terminating
terminationGracePeriodSeconds
计时器启动(默认为30秒)preStop
挂钩:pg_cli ...
SIGTERM
已发送:Postgres 不会接受新连接terminationGracePeriods
(可从 yaml 配置)SIGKILL
已发送还需要在部署中设置
.spec.strategy.type==Recreate
。
对于
pg_cli
命令,您可以参考此摘要,对您来说最有用的命令看起来像 -m fast
。
SIGTERM
:
SIGINT
:
SIGTERM
到现有服务器进程(?)它们会立即退出SIGQUIT
:
SIGQUIT
到所有子进程,如果它们在 5 秒内没有终止,则发送 SIGKILL
编辑:
显然
Recreate
仅保证更新时的娱乐(旧RS到新RS),但如果Pod随机死亡,它不能保证一次有1个Pod。当新 Pod 正在创建旧 Pod 时,旧 Pod 可能处于终止阶段,并且由于竞争条件,数据可能会损坏。 相关文件
这只能保证 Pod 在创建升级之前终止。如果升级 Deployment,旧版本的所有 Pod 将立即终止。在创建新修订版的任何 Pod 之前,需要等待成功删除。如果你手动删除一个 Pod,生命周期由 ReplicaSet 控制,并且会立即创建替换的 Pod(即使旧 Pod 仍处于 Terminate 状态)。 如果您的 Pod 需要“最多”保证,您应该考虑使用 StatefulSet。
作为对 @umut-gerçek 优秀答案的补充,在我使用的 docker 镜像 postgres 中,pg_ctl 的正确路径是:
/usr/lib/postgresql/16/bin/pg_ctl
验证这一点的一个简单方法是在正在运行的 postgres pod 中运行 shell:
> kubectl get pods
> kubectl exec --stdin --tty pod_name -- /bin/bash
root@pod_name:/# find / -name pg_ctl
/usr/lib/postgresql/16/bin/pg_ctl
遵循 @umut-gerçek 的建议,我的 StatefulSet 清单类似于此:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgresql-deployment
labels:
app: postgresql
spec:
replicas: 1
selector:
matchLabels:
app: postgresql # has to match .spec.template.metadata.labels
serviceName: "postgresql"
replicas: 1
template:
metadata:
labels:
app: postgresql # has to match .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 30
volumes:
- name: data
persistentVolumeClaim:
claimName: postgresql-pvc
containers:
- name: postgresql
image: postgres:16
lifecycle:
preStop:
exec:
command: ["/usr/lib/postgresql/16/bin/pg_ctl stop -D /var/lib/postgresql/data -w -t 15 -m fast"]
ports:
- name: postgres
containerPort: 5432
protocol: TCP
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: data
env:
[...]