blob: 402ebc10bfa4f52d3e4ebe8eb0307d582844e72a [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",
radexad91bd22023-11-16 22:55:45 +010010 appName: error "appName must be set",
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020011 prefix: "", # if set, should be 'foo-'
12
radexad91bd22023-11-16 22:55:45 +010013 # valid version tag for https://hub.docker.com/_/postgres/
14 version: error "version must be set (to a valid docker hub tag, e.g. 10.4)",
radex33fbaed2023-11-16 22:27:02 +010015 image: "postgres:" + self.version,
16
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020017 database: error "database must be set",
18 username: error "username must be set",
19 # not literal, instead ref for env (like { secretKeyRef: ... })
20 password: error "password must be set",
Serge Bazanskic0c037a2020-08-23 01:24:03 +000021
22 storageSize: "30Gi",
radexad91bd22023-11-16 22:55:45 +010023 storageClassName: error "storageClassName must be set",
24
25 # Override this to set postgres resource requests and limits.
26 resources: {},
Piotr Dobrowolski1816f582021-01-30 11:53:38 +010027
28 # This option can be used to customize initial database creation. For
29 # available options see: https://www.postgresql.org/docs/9.5/app-initdb.html
30 # Changing this option in already existing deployments will not affect
31 # existing database.
32 initdbArgs: null,
Piotr Dobrowolski3b8f6672021-02-08 22:44:56 +010033
34 # Extra postgres configuration options passed on startup. Accepts only
35 # string values.
36 # Example: { max_connections: "300" }
37 opts: {},
Piotr Dobrowolskiea8e3f92023-10-10 00:41:50 +020038
39 # Postgres cluster upgrade automation. In order to update running
40 # postgres version:
41 # * set image to target postgres version
42 # * pgupgrade.from to previous/current major version
43 # * pgupgrade.to to target major version number (optional, this should
44 # be figured out from image version)
45 # * switch pgupgrade.enable to true.
46 #
47 # While we do have some countermeasures to prevent stupid typos, you
48 # should still probably make a database backup, eg. using:
49 # kubectl exec deploy/postgres -- pg_dumpall > dump.sql
50 #
51 # After succesful upgrade /var/lib/postgresql/data/pgdata-old directory
52 # needs to be removed by hand. In case a rollback is needed pgdata needs
53 # to be swapped with the pgdata-old directory and the postgres image
54 # needs to be adjusted accordingly.
55 pgupgrade: {
56 enable: false,
radexad91bd22023-11-16 22:55:45 +010057 from: error 'pgupgrade.from must be set to previous major version, e.g. 10',
Piotr Dobrowolskiea8e3f92023-10-10 00:41:50 +020058 # Extract target version from image name, supported:
59 # postgres:1.2-suffix, postgres:1-suffix, postgres:1.2, postgres:1
60 to: std.native('regexSubst')("^[^:]+:([^.]+).*$", cfg.image, "${1}"),
61 },
radex33fbaed2023-11-16 22:27:02 +010062
radex5a12c402023-11-16 22:44:58 +010063 # Optional pgbouncer
64 # if enabled, use `postgres.bouncer.host` as database host
65 bouncer: {
66 enable: false,
67 image: "edoburu/pgbouncer:1.11.0",
68 },
69
radex33fbaed2023-11-16 22:27:02 +010070 # If set to true, resources will be suffixed with postgres version (and will have versioned labels)
71 # This exists solely for backwards compatibility with old postgres_v libsonnet
72 # and should not be used in new deployments
73 versionedNames: false,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020074 },
75
radex33fbaed2023-11-16 22:27:02 +010076 safeVersion:: std.strReplace(cfg.version, ".", "-"),
77 makeName(suffix):: cfg.prefix + suffix + (if cfg.versionedNames then postgres.safeVersion else ""),
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020078
79 metadata:: {
80 namespace: cfg.namespace,
81 labels: {
82 "app.kubernetes.io/name": cfg.appName,
83 "app.kubernetes.io/managed-by": "kubecfg",
radex33fbaed2023-11-16 22:27:02 +010084 "app.kubernetes.io/component": if cfg.versionedNames then "postgres_v" else "postgres",
85 [if cfg.versionedNames then "hswaw.net/postgres-version" else null]: postgres.safeVersion,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020086 },
87 },
88
89 volumeClaim: kube.PersistentVolumeClaim(postgres.makeName("postgres")) {
90 metadata+: postgres.metadata,
radex36964dc2023-11-24 11:19:46 +010091 storage:: cfg.storageSize,
92 storageClass:: cfg.storageClassName,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020093 },
radexad91bd22023-11-16 22:55:45 +010094
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020095 deployment: kube.Deployment(postgres.makeName("postgres")) {
96 metadata+: postgres.metadata,
97 spec+: {
98 replicas: 1,
99 template+: {
100 spec+: {
101 volumes_: {
radex4ffc64d2023-11-24 13:28:57 +0100102 data: postgres.volumeClaim.volume,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200103 },
Piotr Dobrowolskieea0e5e2023-12-29 22:47:08 +0100104 terminationGracePeriodSeconds: 120,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200105 containers_: {
106 postgres: kube.Container(postgres.makeName("postgres")) {
107 image: cfg.image,
108 ports_: {
109 client: { containerPort: 5432 },
110 },
Piotr Dobrowolskieea0e5e2023-12-29 22:47:08 +0100111 lifecycle: {
112 preStop: {
113 exec: {
114 command: ["/bin/sh", "-c", "pg_ctl stop -w -t 60 -m fast"]
115 }
116 },
117 },
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200118 env_: {
119 POSTGRES_DB: cfg.database,
120 POSTGRES_USER: cfg.username,
121 POSTGRES_PASSWORD: cfg.password,
122 PGDATA: "/var/lib/postgresql/data/pgdata",
Piotr Dobrowolski1816f582021-01-30 11:53:38 +0100123 } + if cfg.initdbArgs != null then {
124 POSTGRES_INITDB_ARGS: cfg.initdbArgs,
125 } else {},
Piotr Dobrowolski3b8f6672021-02-08 22:44:56 +0100126
127 args: std.flatMap(
128 function(k) ["-c", "%s=%s" % [k, cfg.opts[k]]],
129 std.objectFields(cfg.opts),
130 ),
131
Piotr Dobrowolskieea0e5e2023-12-29 22:47:08 +0100132 resources: cfg.resources,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200133 volumeMounts_: {
134 data: { mountPath: "/var/lib/postgresql/data" },
135 },
136 },
137 },
Piotr Dobrowolskiea8e3f92023-10-10 00:41:50 +0200138
139 initContainers_: if cfg.pgupgrade.enable then {
140 pgupgrade: kube.Container(postgres.makeName("pgupgrade")) {
141 image: "tianon/postgres-upgrade:%s-to-%s" % [cfg.pgupgrade.from, cfg.pgupgrade.to],
142 command: [
143 "bash", "-c", |||
144 set -e -x -o pipefail
145
146 CURRENT_VERSION="$(cat "$PGDATA/PG_VERSION")"
147 if [[ "$CURRENT_VERSION" == "$VERSION_TO" ]]; then
148 echo "Already running target version ($VERSION_TO)"
149 exit 0
150 fi
151
152 if [[ "$CURRENT_VERSION" != "$VERSION_FROM" ]]; then
153 echo "Running unexpected source version, wanted $VERSION_FROM, got $CURRENT_VERSION"
154 exit 1
155 fi
156
157 rm -rf $PGDATANEXT || true
158
159 if [ ! -s "$PGDATANEXT/PG_VERSION" ]; then
160 echo "Initializing new database..."
161 PGDATA="$PGDATANEXT" eval "initdb $POSTGRES_INITDB_ARGS"
162 fi
163
164 chmod 700 $PGDATA $PGDATANEXT
165
166 echo "Running upgrade..."
167 pg_upgrade --link --old-datadir $PGDATA --new-datadir $PGDATANEXT || (sleep 3600 ; exit 1)
168
169 echo "Copying pg_hba.conf"
170 cp $PGDATA/pg_hba.conf $PGDATANEXT/pg_hba.conf
171
172 echo "Done, swapping..."
173 mv $PGDATA $PGDATAOLD
174 mv $PGDATANEXT $PGDATA
175 |||
176 ],
177 env_: postgres.deployment.spec.template.spec.containers_.postgres.env_ + {
178 VERSION_TO: cfg.pgupgrade.to,
179 VERSION_FROM: cfg.pgupgrade.from,
180
181 # pg_upgrade target directory, swapped with
182 # PGDATA after cluster data upgrade is finished
183 PGDATANEXT: "/var/lib/postgresql/data/pgdata-next",
184
185 # Directory used to stash previous pgdata
186 # version
187 PGDATAOLD: "/var/lib/postgresql/data/pgdata-old",
188 },
189 volumeMounts_: {
190 data: { mountPath: "/var/lib/postgresql/data" },
191 },
192 },
193 } else {},
Sergiusz Bazanskia2ee8652020-01-22 21:48:48 +0100194 securityContext: {
195 runAsUser: 999,
196 },
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200197 },
198 },
199 },
200 },
Serge Bazanskic0c037a2020-08-23 01:24:03 +0000201
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200202 svc: kube.Service(postgres.makeName("postgres")) {
203 metadata+: postgres.metadata,
radex8b8f3872023-11-24 11:09:46 +0100204 target:: postgres.deployment,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200205 },
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100206
radex5a12c402023-11-16 22:44:58 +0100207 bouncer: if cfg.bouncer.enable then {
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100208 deployment: kube.Deployment(postgres.makeName("bouncer")) {
209 metadata+: postgres.metadata {
210 labels+: {
211 "app.kubernetes.io/component": "bouncer",
212 }
213 },
214 spec+: {
215 replicas: 1,
216 template+: {
217 spec+: {
218 containers_: {
219 bouncer: kube.Container(postgres.makeName("bouncer")) {
radex5a12c402023-11-16 22:44:58 +0100220 image: cfg.bouncer.image,
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100221 ports_: {
222 client: { containerPort: 5432 },
223 },
224 env: [
225 { name: "POSTGRES_PASSWORD", valueFrom: cfg.password },
226 { name: "DATABASE_URL", value: "postgres://%s:$(POSTGRES_PASSWORD)@%s/%s" % [cfg.username, postgres.svc.host, cfg.database] },
227 ],
228 },
229 },
230 },
231 },
232 },
233 },
radex5a12c402023-11-16 22:44:58 +0100234 host:: self.svc.host,
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100235 svc: kube.Service(postgres.makeName("bouncer")) {
236 metadata+: postgres.metadata {
237 labels+: {
238 "app.kubernetes.io/component": "bouncer",
239 }
240 },
radex8b8f3872023-11-24 11:09:46 +0100241 target:: postgres.bouncer.deployment,
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100242 },
radex5a12c402023-11-16 22:44:58 +0100243 } else {},
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200244}