blob: 259a63ee904b9ef7c7901c772db771880640e166 [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,
Piotr Dobrowolskieea0e5e2023-12-29 22:47:08 +010074
75 resources: {},
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020076 },
77
radex33fbaed2023-11-16 22:27:02 +010078 safeVersion:: std.strReplace(cfg.version, ".", "-"),
79 makeName(suffix):: cfg.prefix + suffix + (if cfg.versionedNames then postgres.safeVersion else ""),
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020080
81 metadata:: {
82 namespace: cfg.namespace,
83 labels: {
84 "app.kubernetes.io/name": cfg.appName,
85 "app.kubernetes.io/managed-by": "kubecfg",
radex33fbaed2023-11-16 22:27:02 +010086 "app.kubernetes.io/component": if cfg.versionedNames then "postgres_v" else "postgres",
87 [if cfg.versionedNames then "hswaw.net/postgres-version" else null]: postgres.safeVersion,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020088 },
89 },
90
91 volumeClaim: kube.PersistentVolumeClaim(postgres.makeName("postgres")) {
92 metadata+: postgres.metadata,
radex36964dc2023-11-24 11:19:46 +010093 storage:: cfg.storageSize,
94 storageClass:: cfg.storageClassName,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020095 },
radexad91bd22023-11-16 22:55:45 +010096
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020097 deployment: kube.Deployment(postgres.makeName("postgres")) {
98 metadata+: postgres.metadata,
99 spec+: {
100 replicas: 1,
101 template+: {
102 spec+: {
103 volumes_: {
radex4ffc64d2023-11-24 13:28:57 +0100104 data: postgres.volumeClaim.volume,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200105 },
Piotr Dobrowolskieea0e5e2023-12-29 22:47:08 +0100106 terminationGracePeriodSeconds: 120,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200107 containers_: {
108 postgres: kube.Container(postgres.makeName("postgres")) {
109 image: cfg.image,
110 ports_: {
111 client: { containerPort: 5432 },
112 },
Piotr Dobrowolskieea0e5e2023-12-29 22:47:08 +0100113 lifecycle: {
114 preStop: {
115 exec: {
116 command: ["/bin/sh", "-c", "pg_ctl stop -w -t 60 -m fast"]
117 }
118 },
119 },
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200120 env_: {
121 POSTGRES_DB: cfg.database,
122 POSTGRES_USER: cfg.username,
123 POSTGRES_PASSWORD: cfg.password,
124 PGDATA: "/var/lib/postgresql/data/pgdata",
Piotr Dobrowolski1816f582021-01-30 11:53:38 +0100125 } + if cfg.initdbArgs != null then {
126 POSTGRES_INITDB_ARGS: cfg.initdbArgs,
127 } else {},
Piotr Dobrowolski3b8f6672021-02-08 22:44:56 +0100128
129 args: std.flatMap(
130 function(k) ["-c", "%s=%s" % [k, cfg.opts[k]]],
131 std.objectFields(cfg.opts),
132 ),
133
Piotr Dobrowolskieea0e5e2023-12-29 22:47:08 +0100134 resources: cfg.resources,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200135 volumeMounts_: {
136 data: { mountPath: "/var/lib/postgresql/data" },
137 },
radexad91bd22023-11-16 22:55:45 +0100138 resources: cfg.resources,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200139 },
140 },
Piotr Dobrowolskiea8e3f92023-10-10 00:41:50 +0200141
142 initContainers_: if cfg.pgupgrade.enable then {
143 pgupgrade: kube.Container(postgres.makeName("pgupgrade")) {
144 image: "tianon/postgres-upgrade:%s-to-%s" % [cfg.pgupgrade.from, cfg.pgupgrade.to],
145 command: [
146 "bash", "-c", |||
147 set -e -x -o pipefail
148
149 CURRENT_VERSION="$(cat "$PGDATA/PG_VERSION")"
150 if [[ "$CURRENT_VERSION" == "$VERSION_TO" ]]; then
151 echo "Already running target version ($VERSION_TO)"
152 exit 0
153 fi
154
155 if [[ "$CURRENT_VERSION" != "$VERSION_FROM" ]]; then
156 echo "Running unexpected source version, wanted $VERSION_FROM, got $CURRENT_VERSION"
157 exit 1
158 fi
159
160 rm -rf $PGDATANEXT || true
161
162 if [ ! -s "$PGDATANEXT/PG_VERSION" ]; then
163 echo "Initializing new database..."
164 PGDATA="$PGDATANEXT" eval "initdb $POSTGRES_INITDB_ARGS"
165 fi
166
167 chmod 700 $PGDATA $PGDATANEXT
168
169 echo "Running upgrade..."
170 pg_upgrade --link --old-datadir $PGDATA --new-datadir $PGDATANEXT || (sleep 3600 ; exit 1)
171
172 echo "Copying pg_hba.conf"
173 cp $PGDATA/pg_hba.conf $PGDATANEXT/pg_hba.conf
174
175 echo "Done, swapping..."
176 mv $PGDATA $PGDATAOLD
177 mv $PGDATANEXT $PGDATA
178 |||
179 ],
180 env_: postgres.deployment.spec.template.spec.containers_.postgres.env_ + {
181 VERSION_TO: cfg.pgupgrade.to,
182 VERSION_FROM: cfg.pgupgrade.from,
183
184 # pg_upgrade target directory, swapped with
185 # PGDATA after cluster data upgrade is finished
186 PGDATANEXT: "/var/lib/postgresql/data/pgdata-next",
187
188 # Directory used to stash previous pgdata
189 # version
190 PGDATAOLD: "/var/lib/postgresql/data/pgdata-old",
191 },
192 volumeMounts_: {
193 data: { mountPath: "/var/lib/postgresql/data" },
194 },
195 },
196 } else {},
Sergiusz Bazanskia2ee8652020-01-22 21:48:48 +0100197 securityContext: {
198 runAsUser: 999,
199 },
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200200 },
201 },
202 },
203 },
Serge Bazanskic0c037a2020-08-23 01:24:03 +0000204
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200205 svc: kube.Service(postgres.makeName("postgres")) {
206 metadata+: postgres.metadata,
radex8b8f3872023-11-24 11:09:46 +0100207 target:: postgres.deployment,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200208 },
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100209
radex5a12c402023-11-16 22:44:58 +0100210 bouncer: if cfg.bouncer.enable then {
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100211 deployment: kube.Deployment(postgres.makeName("bouncer")) {
212 metadata+: postgres.metadata {
213 labels+: {
214 "app.kubernetes.io/component": "bouncer",
215 }
216 },
217 spec+: {
218 replicas: 1,
219 template+: {
220 spec+: {
221 containers_: {
222 bouncer: kube.Container(postgres.makeName("bouncer")) {
radex5a12c402023-11-16 22:44:58 +0100223 image: cfg.bouncer.image,
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100224 ports_: {
225 client: { containerPort: 5432 },
226 },
227 env: [
228 { name: "POSTGRES_PASSWORD", valueFrom: cfg.password },
229 { name: "DATABASE_URL", value: "postgres://%s:$(POSTGRES_PASSWORD)@%s/%s" % [cfg.username, postgres.svc.host, cfg.database] },
230 ],
231 },
232 },
233 },
234 },
235 },
236 },
radex5a12c402023-11-16 22:44:58 +0100237 host:: self.svc.host,
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100238 svc: kube.Service(postgres.makeName("bouncer")) {
239 metadata+: postgres.metadata {
240 labels+: {
241 "app.kubernetes.io/component": "bouncer",
242 }
243 },
radex8b8f3872023-11-24 11:09:46 +0100244 target:: postgres.bouncer.deployment,
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100245 },
radex5a12c402023-11-16 22:44:58 +0100246 } else {},
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200247}