blob: d5ffad4e5b20d3608f779db5963e574539c32828 [file] [log] [blame]
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +02001# PostgreSQL on Kubernetes.
2
3local kube = import "kube.libsonnet";
4
5{
6 local postgres = self,
7 local cfg = postgres.cfg,
8 cfg:: {
9 namespace: error "namespace must be set",
10 appName: error "app name must be set",
Sergiusz Bazanskid07861b2019-08-08 17:48:25 +020011 storageClassName: "waw-hdd-paranoid-2",
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020012 prefix: "", # if set, should be 'foo-'
13
14 image: "postgres:10.4",
15 database: error "database must be set",
16 username: error "username must be set",
17 # not literal, instead ref for env (like { secretKeyRef: ... })
18 password: error "password must be set",
Serge Bazanskic0c037a2020-08-23 01:24:03 +000019
20 storageSize: "30Gi",
Piotr Dobrowolski1816f582021-01-30 11:53:38 +010021
22 # This option can be used to customize initial database creation. For
23 # available options see: https://www.postgresql.org/docs/9.5/app-initdb.html
24 # Changing this option in already existing deployments will not affect
25 # existing database.
26 initdbArgs: null,
Piotr Dobrowolski3b8f6672021-02-08 22:44:56 +010027
28 # Extra postgres configuration options passed on startup. Accepts only
29 # string values.
30 # Example: { max_connections: "300" }
31 opts: {},
Piotr Dobrowolskiea8e3f92023-10-10 00:41:50 +020032
33 # Postgres cluster upgrade automation. In order to update running
34 # postgres version:
35 # * set image to target postgres version
36 # * pgupgrade.from to previous/current major version
37 # * pgupgrade.to to target major version number (optional, this should
38 # be figured out from image version)
39 # * switch pgupgrade.enable to true.
40 #
41 # While we do have some countermeasures to prevent stupid typos, you
42 # should still probably make a database backup, eg. using:
43 # kubectl exec deploy/postgres -- pg_dumpall > dump.sql
44 #
45 # After succesful upgrade /var/lib/postgresql/data/pgdata-old directory
46 # needs to be removed by hand. In case a rollback is needed pgdata needs
47 # to be swapped with the pgdata-old directory and the postgres image
48 # needs to be adjusted accordingly.
49 pgupgrade: {
50 enable: false,
51 from: "10",
52 # Extract target version from image name, supported:
53 # postgres:1.2-suffix, postgres:1-suffix, postgres:1.2, postgres:1
54 to: std.native('regexSubst')("^[^:]+:([^.]+).*$", cfg.image, "${1}"),
55 },
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020056 },
57
58 makeName(suffix):: cfg.prefix + suffix,
59
60 metadata:: {
61 namespace: cfg.namespace,
62 labels: {
63 "app.kubernetes.io/name": cfg.appName,
64 "app.kubernetes.io/managed-by": "kubecfg",
65 "app.kubernetes.io/component": "postgres",
66 },
67 },
68
69 volumeClaim: kube.PersistentVolumeClaim(postgres.makeName("postgres")) {
70 metadata+: postgres.metadata,
radex36964dc2023-11-24 11:19:46 +010071 storage:: cfg.storageSize,
72 storageClass:: cfg.storageClassName,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020073 },
74 deployment: kube.Deployment(postgres.makeName("postgres")) {
75 metadata+: postgres.metadata,
76 spec+: {
77 replicas: 1,
78 template+: {
79 spec+: {
80 volumes_: {
81 data: kube.PersistentVolumeClaimVolume(postgres.volumeClaim),
82 },
83 containers_: {
84 postgres: kube.Container(postgres.makeName("postgres")) {
85 image: cfg.image,
86 ports_: {
87 client: { containerPort: 5432 },
88 },
89 env_: {
90 POSTGRES_DB: cfg.database,
91 POSTGRES_USER: cfg.username,
92 POSTGRES_PASSWORD: cfg.password,
93 PGDATA: "/var/lib/postgresql/data/pgdata",
Piotr Dobrowolski1816f582021-01-30 11:53:38 +010094 } + if cfg.initdbArgs != null then {
95 POSTGRES_INITDB_ARGS: cfg.initdbArgs,
96 } else {},
Piotr Dobrowolski3b8f6672021-02-08 22:44:56 +010097
98 args: std.flatMap(
99 function(k) ["-c", "%s=%s" % [k, cfg.opts[k]]],
100 std.objectFields(cfg.opts),
101 ),
102
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200103 volumeMounts_: {
104 data: { mountPath: "/var/lib/postgresql/data" },
105 },
106 },
107 },
Piotr Dobrowolskiea8e3f92023-10-10 00:41:50 +0200108
109 initContainers_: if cfg.pgupgrade.enable then {
110 pgupgrade: kube.Container(postgres.makeName("pgupgrade")) {
111 image: "tianon/postgres-upgrade:%s-to-%s" % [cfg.pgupgrade.from, cfg.pgupgrade.to],
112 command: [
113 "bash", "-c", |||
114 set -e -x -o pipefail
115
116 CURRENT_VERSION="$(cat "$PGDATA/PG_VERSION")"
117 if [[ "$CURRENT_VERSION" == "$VERSION_TO" ]]; then
118 echo "Already running target version ($VERSION_TO)"
119 exit 0
120 fi
121
122 if [[ "$CURRENT_VERSION" != "$VERSION_FROM" ]]; then
123 echo "Running unexpected source version, wanted $VERSION_FROM, got $CURRENT_VERSION"
124 exit 1
125 fi
126
127 rm -rf $PGDATANEXT || true
128
129 if [ ! -s "$PGDATANEXT/PG_VERSION" ]; then
130 echo "Initializing new database..."
131 PGDATA="$PGDATANEXT" eval "initdb $POSTGRES_INITDB_ARGS"
132 fi
133
134 chmod 700 $PGDATA $PGDATANEXT
135
136 echo "Running upgrade..."
137 pg_upgrade --link --old-datadir $PGDATA --new-datadir $PGDATANEXT || (sleep 3600 ; exit 1)
138
139 echo "Copying pg_hba.conf"
140 cp $PGDATA/pg_hba.conf $PGDATANEXT/pg_hba.conf
141
142 echo "Done, swapping..."
143 mv $PGDATA $PGDATAOLD
144 mv $PGDATANEXT $PGDATA
145 |||
146 ],
147 env_: postgres.deployment.spec.template.spec.containers_.postgres.env_ + {
148 VERSION_TO: cfg.pgupgrade.to,
149 VERSION_FROM: cfg.pgupgrade.from,
150
151 # pg_upgrade target directory, swapped with
152 # PGDATA after cluster data upgrade is finished
153 PGDATANEXT: "/var/lib/postgresql/data/pgdata-next",
154
155 # Directory used to stash previous pgdata
156 # version
157 PGDATAOLD: "/var/lib/postgresql/data/pgdata-old",
158 },
159 volumeMounts_: {
160 data: { mountPath: "/var/lib/postgresql/data" },
161 },
162 },
163 } else {},
Sergiusz Bazanskia2ee8652020-01-22 21:48:48 +0100164 securityContext: {
165 runAsUser: 999,
166 },
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200167 },
168 },
169 },
170 },
Serge Bazanskic0c037a2020-08-23 01:24:03 +0000171
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200172 svc: kube.Service(postgres.makeName("postgres")) {
173 metadata+: postgres.metadata,
radex8b8f3872023-11-24 11:09:46 +0100174 target:: postgres.deployment,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200175 spec+: {
176 ports: [
177 { name: "client", port: 5432, targetPort: 5432, protocol: "TCP" },
178 ],
179 type: "ClusterIP",
180 },
181 },
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100182
183 bouncer: {
184 deployment: kube.Deployment(postgres.makeName("bouncer")) {
185 metadata+: postgres.metadata {
186 labels+: {
187 "app.kubernetes.io/component": "bouncer",
188 }
189 },
190 spec+: {
191 replicas: 1,
192 template+: {
193 spec+: {
194 containers_: {
195 bouncer: kube.Container(postgres.makeName("bouncer")) {
196 image: "edoburu/pgbouncer:1.11.0",
197 ports_: {
198 client: { containerPort: 5432 },
199 },
200 env: [
201 { name: "POSTGRES_PASSWORD", valueFrom: cfg.password },
202 { name: "DATABASE_URL", value: "postgres://%s:$(POSTGRES_PASSWORD)@%s/%s" % [cfg.username, postgres.svc.host, cfg.database] },
203 ],
204 },
205 },
206 },
207 },
208 },
209 },
210 svc: kube.Service(postgres.makeName("bouncer")) {
211 metadata+: postgres.metadata {
212 labels+: {
213 "app.kubernetes.io/component": "bouncer",
214 }
215 },
radex8b8f3872023-11-24 11:09:46 +0100216 target:: postgres.bouncer.deployment,
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100217 spec+: {
218 ports: [
219 { name: "client", port: 5432, targetPort: 5432, protocol: "TCP" },
220 ],
221 type: "ClusterIP",
222 },
223 },
224 },
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200225}