blob: a3c6415a3e100dbb82d7cf74e452ee393a41ed0e [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
radex33fbaed2023-11-16 22:27:02 +010014 version: "10.4", # valid version tag for https://hub.docker.com/_/postgres/
15 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",
Piotr Dobrowolski1816f582021-01-30 11:53:38 +010023
24 # This option can be used to customize initial database creation. For
25 # available options see: https://www.postgresql.org/docs/9.5/app-initdb.html
26 # Changing this option in already existing deployments will not affect
27 # existing database.
28 initdbArgs: null,
Piotr Dobrowolski3b8f6672021-02-08 22:44:56 +010029
30 # Extra postgres configuration options passed on startup. Accepts only
31 # string values.
32 # Example: { max_connections: "300" }
33 opts: {},
Piotr Dobrowolskiea8e3f92023-10-10 00:41:50 +020034
35 # Postgres cluster upgrade automation. In order to update running
36 # postgres version:
37 # * set image to target postgres version
38 # * pgupgrade.from to previous/current major version
39 # * pgupgrade.to to target major version number (optional, this should
40 # be figured out from image version)
41 # * switch pgupgrade.enable to true.
42 #
43 # While we do have some countermeasures to prevent stupid typos, you
44 # should still probably make a database backup, eg. using:
45 # kubectl exec deploy/postgres -- pg_dumpall > dump.sql
46 #
47 # After succesful upgrade /var/lib/postgresql/data/pgdata-old directory
48 # needs to be removed by hand. In case a rollback is needed pgdata needs
49 # to be swapped with the pgdata-old directory and the postgres image
50 # needs to be adjusted accordingly.
51 pgupgrade: {
52 enable: false,
53 from: "10",
54 # Extract target version from image name, supported:
55 # postgres:1.2-suffix, postgres:1-suffix, postgres:1.2, postgres:1
56 to: std.native('regexSubst')("^[^:]+:([^.]+).*$", cfg.image, "${1}"),
57 },
radex33fbaed2023-11-16 22:27:02 +010058
radex5a12c402023-11-16 22:44:58 +010059 # Optional pgbouncer
60 # if enabled, use `postgres.bouncer.host` as database host
61 bouncer: {
62 enable: false,
63 image: "edoburu/pgbouncer:1.11.0",
64 },
65
radex33fbaed2023-11-16 22:27:02 +010066 # If set to true, resources will be suffixed with postgres version (and will have versioned labels)
67 # This exists solely for backwards compatibility with old postgres_v libsonnet
68 # and should not be used in new deployments
69 versionedNames: false,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020070 },
71
radex33fbaed2023-11-16 22:27:02 +010072 safeVersion:: std.strReplace(cfg.version, ".", "-"),
73 makeName(suffix):: cfg.prefix + suffix + (if cfg.versionedNames then postgres.safeVersion else ""),
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020074
75 metadata:: {
76 namespace: cfg.namespace,
77 labels: {
78 "app.kubernetes.io/name": cfg.appName,
79 "app.kubernetes.io/managed-by": "kubecfg",
radex33fbaed2023-11-16 22:27:02 +010080 "app.kubernetes.io/component": if cfg.versionedNames then "postgres_v" else "postgres",
81 [if cfg.versionedNames then "hswaw.net/postgres-version" else null]: postgres.safeVersion,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020082 },
83 },
84
85 volumeClaim: kube.PersistentVolumeClaim(postgres.makeName("postgres")) {
86 metadata+: postgres.metadata,
radex36964dc2023-11-24 11:19:46 +010087 storage:: cfg.storageSize,
88 storageClass:: cfg.storageClassName,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020089 },
90 deployment: kube.Deployment(postgres.makeName("postgres")) {
91 metadata+: postgres.metadata,
92 spec+: {
93 replicas: 1,
94 template+: {
95 spec+: {
96 volumes_: {
radex4ffc64d2023-11-24 13:28:57 +010097 data: postgres.volumeClaim.volume,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020098 },
99 containers_: {
100 postgres: kube.Container(postgres.makeName("postgres")) {
101 image: cfg.image,
102 ports_: {
103 client: { containerPort: 5432 },
104 },
105 env_: {
106 POSTGRES_DB: cfg.database,
107 POSTGRES_USER: cfg.username,
108 POSTGRES_PASSWORD: cfg.password,
109 PGDATA: "/var/lib/postgresql/data/pgdata",
Piotr Dobrowolski1816f582021-01-30 11:53:38 +0100110 } + if cfg.initdbArgs != null then {
111 POSTGRES_INITDB_ARGS: cfg.initdbArgs,
112 } else {},
Piotr Dobrowolski3b8f6672021-02-08 22:44:56 +0100113
114 args: std.flatMap(
115 function(k) ["-c", "%s=%s" % [k, cfg.opts[k]]],
116 std.objectFields(cfg.opts),
117 ),
118
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200119 volumeMounts_: {
120 data: { mountPath: "/var/lib/postgresql/data" },
121 },
122 },
123 },
Piotr Dobrowolskiea8e3f92023-10-10 00:41:50 +0200124
125 initContainers_: if cfg.pgupgrade.enable then {
126 pgupgrade: kube.Container(postgres.makeName("pgupgrade")) {
127 image: "tianon/postgres-upgrade:%s-to-%s" % [cfg.pgupgrade.from, cfg.pgupgrade.to],
128 command: [
129 "bash", "-c", |||
130 set -e -x -o pipefail
131
132 CURRENT_VERSION="$(cat "$PGDATA/PG_VERSION")"
133 if [[ "$CURRENT_VERSION" == "$VERSION_TO" ]]; then
134 echo "Already running target version ($VERSION_TO)"
135 exit 0
136 fi
137
138 if [[ "$CURRENT_VERSION" != "$VERSION_FROM" ]]; then
139 echo "Running unexpected source version, wanted $VERSION_FROM, got $CURRENT_VERSION"
140 exit 1
141 fi
142
143 rm -rf $PGDATANEXT || true
144
145 if [ ! -s "$PGDATANEXT/PG_VERSION" ]; then
146 echo "Initializing new database..."
147 PGDATA="$PGDATANEXT" eval "initdb $POSTGRES_INITDB_ARGS"
148 fi
149
150 chmod 700 $PGDATA $PGDATANEXT
151
152 echo "Running upgrade..."
153 pg_upgrade --link --old-datadir $PGDATA --new-datadir $PGDATANEXT || (sleep 3600 ; exit 1)
154
155 echo "Copying pg_hba.conf"
156 cp $PGDATA/pg_hba.conf $PGDATANEXT/pg_hba.conf
157
158 echo "Done, swapping..."
159 mv $PGDATA $PGDATAOLD
160 mv $PGDATANEXT $PGDATA
161 |||
162 ],
163 env_: postgres.deployment.spec.template.spec.containers_.postgres.env_ + {
164 VERSION_TO: cfg.pgupgrade.to,
165 VERSION_FROM: cfg.pgupgrade.from,
166
167 # pg_upgrade target directory, swapped with
168 # PGDATA after cluster data upgrade is finished
169 PGDATANEXT: "/var/lib/postgresql/data/pgdata-next",
170
171 # Directory used to stash previous pgdata
172 # version
173 PGDATAOLD: "/var/lib/postgresql/data/pgdata-old",
174 },
175 volumeMounts_: {
176 data: { mountPath: "/var/lib/postgresql/data" },
177 },
178 },
179 } else {},
Sergiusz Bazanskia2ee8652020-01-22 21:48:48 +0100180 securityContext: {
181 runAsUser: 999,
182 },
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200183 },
184 },
185 },
186 },
Serge Bazanskic0c037a2020-08-23 01:24:03 +0000187
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200188 svc: kube.Service(postgres.makeName("postgres")) {
189 metadata+: postgres.metadata,
radex8b8f3872023-11-24 11:09:46 +0100190 target:: postgres.deployment,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200191 spec+: {
192 ports: [
193 { name: "client", port: 5432, targetPort: 5432, protocol: "TCP" },
194 ],
195 type: "ClusterIP",
196 },
197 },
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100198
radex5a12c402023-11-16 22:44:58 +0100199 bouncer: if cfg.bouncer.enable then {
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100200 deployment: kube.Deployment(postgres.makeName("bouncer")) {
201 metadata+: postgres.metadata {
202 labels+: {
203 "app.kubernetes.io/component": "bouncer",
204 }
205 },
206 spec+: {
207 replicas: 1,
208 template+: {
209 spec+: {
210 containers_: {
211 bouncer: kube.Container(postgres.makeName("bouncer")) {
radex5a12c402023-11-16 22:44:58 +0100212 image: cfg.bouncer.image,
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100213 ports_: {
214 client: { containerPort: 5432 },
215 },
216 env: [
217 { name: "POSTGRES_PASSWORD", valueFrom: cfg.password },
218 { name: "DATABASE_URL", value: "postgres://%s:$(POSTGRES_PASSWORD)@%s/%s" % [cfg.username, postgres.svc.host, cfg.database] },
219 ],
220 },
221 },
222 },
223 },
224 },
225 },
radex5a12c402023-11-16 22:44:58 +0100226 host:: self.svc.host,
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100227 svc: kube.Service(postgres.makeName("bouncer")) {
228 metadata+: postgres.metadata {
229 labels+: {
230 "app.kubernetes.io/component": "bouncer",
231 }
232 },
radex8b8f3872023-11-24 11:09:46 +0100233 target:: postgres.bouncer.deployment,
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100234 spec+: {
235 ports: [
236 { name: "client", port: 5432, targetPort: 5432, protocol: "TCP" },
237 ],
238 type: "ClusterIP",
239 },
240 },
radex5a12c402023-11-16 22:44:58 +0100241 } else {},
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200242}