blob: 9ae1ccf20b8f6425186c0b21804e640e4205d133 [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
59 # If set to true, resources will be suffixed with postgres version (and will have versioned labels)
60 # This exists solely for backwards compatibility with old postgres_v libsonnet
61 # and should not be used in new deployments
62 versionedNames: false,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020063 },
64
radex33fbaed2023-11-16 22:27:02 +010065 safeVersion:: std.strReplace(cfg.version, ".", "-"),
66 makeName(suffix):: cfg.prefix + suffix + (if cfg.versionedNames then postgres.safeVersion else ""),
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020067
68 metadata:: {
69 namespace: cfg.namespace,
70 labels: {
71 "app.kubernetes.io/name": cfg.appName,
72 "app.kubernetes.io/managed-by": "kubecfg",
radex33fbaed2023-11-16 22:27:02 +010073 "app.kubernetes.io/component": if cfg.versionedNames then "postgres_v" else "postgres",
74 [if cfg.versionedNames then "hswaw.net/postgres-version" else null]: postgres.safeVersion,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020075 },
76 },
77
78 volumeClaim: kube.PersistentVolumeClaim(postgres.makeName("postgres")) {
79 metadata+: postgres.metadata,
radex36964dc2023-11-24 11:19:46 +010080 storage:: cfg.storageSize,
81 storageClass:: cfg.storageClassName,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020082 },
83 deployment: kube.Deployment(postgres.makeName("postgres")) {
84 metadata+: postgres.metadata,
85 spec+: {
86 replicas: 1,
87 template+: {
88 spec+: {
89 volumes_: {
radex4ffc64d2023-11-24 13:28:57 +010090 data: postgres.volumeClaim.volume,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020091 },
92 containers_: {
93 postgres: kube.Container(postgres.makeName("postgres")) {
94 image: cfg.image,
95 ports_: {
96 client: { containerPort: 5432 },
97 },
98 env_: {
99 POSTGRES_DB: cfg.database,
100 POSTGRES_USER: cfg.username,
101 POSTGRES_PASSWORD: cfg.password,
102 PGDATA: "/var/lib/postgresql/data/pgdata",
Piotr Dobrowolski1816f582021-01-30 11:53:38 +0100103 } + if cfg.initdbArgs != null then {
104 POSTGRES_INITDB_ARGS: cfg.initdbArgs,
105 } else {},
Piotr Dobrowolski3b8f6672021-02-08 22:44:56 +0100106
107 args: std.flatMap(
108 function(k) ["-c", "%s=%s" % [k, cfg.opts[k]]],
109 std.objectFields(cfg.opts),
110 ),
111
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200112 volumeMounts_: {
113 data: { mountPath: "/var/lib/postgresql/data" },
114 },
115 },
116 },
Piotr Dobrowolskiea8e3f92023-10-10 00:41:50 +0200117
118 initContainers_: if cfg.pgupgrade.enable then {
119 pgupgrade: kube.Container(postgres.makeName("pgupgrade")) {
120 image: "tianon/postgres-upgrade:%s-to-%s" % [cfg.pgupgrade.from, cfg.pgupgrade.to],
121 command: [
122 "bash", "-c", |||
123 set -e -x -o pipefail
124
125 CURRENT_VERSION="$(cat "$PGDATA/PG_VERSION")"
126 if [[ "$CURRENT_VERSION" == "$VERSION_TO" ]]; then
127 echo "Already running target version ($VERSION_TO)"
128 exit 0
129 fi
130
131 if [[ "$CURRENT_VERSION" != "$VERSION_FROM" ]]; then
132 echo "Running unexpected source version, wanted $VERSION_FROM, got $CURRENT_VERSION"
133 exit 1
134 fi
135
136 rm -rf $PGDATANEXT || true
137
138 if [ ! -s "$PGDATANEXT/PG_VERSION" ]; then
139 echo "Initializing new database..."
140 PGDATA="$PGDATANEXT" eval "initdb $POSTGRES_INITDB_ARGS"
141 fi
142
143 chmod 700 $PGDATA $PGDATANEXT
144
145 echo "Running upgrade..."
146 pg_upgrade --link --old-datadir $PGDATA --new-datadir $PGDATANEXT || (sleep 3600 ; exit 1)
147
148 echo "Copying pg_hba.conf"
149 cp $PGDATA/pg_hba.conf $PGDATANEXT/pg_hba.conf
150
151 echo "Done, swapping..."
152 mv $PGDATA $PGDATAOLD
153 mv $PGDATANEXT $PGDATA
154 |||
155 ],
156 env_: postgres.deployment.spec.template.spec.containers_.postgres.env_ + {
157 VERSION_TO: cfg.pgupgrade.to,
158 VERSION_FROM: cfg.pgupgrade.from,
159
160 # pg_upgrade target directory, swapped with
161 # PGDATA after cluster data upgrade is finished
162 PGDATANEXT: "/var/lib/postgresql/data/pgdata-next",
163
164 # Directory used to stash previous pgdata
165 # version
166 PGDATAOLD: "/var/lib/postgresql/data/pgdata-old",
167 },
168 volumeMounts_: {
169 data: { mountPath: "/var/lib/postgresql/data" },
170 },
171 },
172 } else {},
Sergiusz Bazanskia2ee8652020-01-22 21:48:48 +0100173 securityContext: {
174 runAsUser: 999,
175 },
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200176 },
177 },
178 },
179 },
Serge Bazanskic0c037a2020-08-23 01:24:03 +0000180
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200181 svc: kube.Service(postgres.makeName("postgres")) {
182 metadata+: postgres.metadata,
radex8b8f3872023-11-24 11:09:46 +0100183 target:: postgres.deployment,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200184 spec+: {
185 ports: [
186 { name: "client", port: 5432, targetPort: 5432, protocol: "TCP" },
187 ],
188 type: "ClusterIP",
189 },
190 },
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100191
192 bouncer: {
193 deployment: kube.Deployment(postgres.makeName("bouncer")) {
194 metadata+: postgres.metadata {
195 labels+: {
196 "app.kubernetes.io/component": "bouncer",
197 }
198 },
199 spec+: {
200 replicas: 1,
201 template+: {
202 spec+: {
203 containers_: {
204 bouncer: kube.Container(postgres.makeName("bouncer")) {
205 image: "edoburu/pgbouncer:1.11.0",
206 ports_: {
207 client: { containerPort: 5432 },
208 },
209 env: [
210 { name: "POSTGRES_PASSWORD", valueFrom: cfg.password },
211 { name: "DATABASE_URL", value: "postgres://%s:$(POSTGRES_PASSWORD)@%s/%s" % [cfg.username, postgres.svc.host, cfg.database] },
212 ],
213 },
214 },
215 },
216 },
217 },
218 },
219 svc: kube.Service(postgres.makeName("bouncer")) {
220 metadata+: postgres.metadata {
221 labels+: {
222 "app.kubernetes.io/component": "bouncer",
223 }
224 },
radex8b8f3872023-11-24 11:09:46 +0100225 target:: postgres.bouncer.deployment,
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100226 spec+: {
227 ports: [
228 { name: "client", port: 5432, targetPort: 5432, protocol: "TCP" },
229 ],
230 type: "ClusterIP",
231 },
232 },
233 },
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200234}