blob: 2935213a9cfd76f2b61622eef591ecbd2b5c57d1 [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,
71 spec+: {
72 storageClassName: cfg.storageClassName,
73 accessModes: [ "ReadWriteOnce" ],
74 resources: {
75 requests: {
Serge Bazanskic0c037a2020-08-23 01:24:03 +000076 storage: cfg.storageSize,
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +020077 },
78 },
79 },
80 },
81 deployment: kube.Deployment(postgres.makeName("postgres")) {
82 metadata+: postgres.metadata,
83 spec+: {
84 replicas: 1,
85 template+: {
86 spec+: {
87 volumes_: {
88 data: kube.PersistentVolumeClaimVolume(postgres.volumeClaim),
89 },
90 containers_: {
91 postgres: kube.Container(postgres.makeName("postgres")) {
92 image: cfg.image,
93 ports_: {
94 client: { containerPort: 5432 },
95 },
96 env_: {
97 POSTGRES_DB: cfg.database,
98 POSTGRES_USER: cfg.username,
99 POSTGRES_PASSWORD: cfg.password,
100 PGDATA: "/var/lib/postgresql/data/pgdata",
Piotr Dobrowolski1816f582021-01-30 11:53:38 +0100101 } + if cfg.initdbArgs != null then {
102 POSTGRES_INITDB_ARGS: cfg.initdbArgs,
103 } else {},
Piotr Dobrowolski3b8f6672021-02-08 22:44:56 +0100104
105 args: std.flatMap(
106 function(k) ["-c", "%s=%s" % [k, cfg.opts[k]]],
107 std.objectFields(cfg.opts),
108 ),
109
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200110 volumeMounts_: {
111 data: { mountPath: "/var/lib/postgresql/data" },
112 },
113 },
114 },
Piotr Dobrowolskiea8e3f92023-10-10 00:41:50 +0200115
116 initContainers_: if cfg.pgupgrade.enable then {
117 pgupgrade: kube.Container(postgres.makeName("pgupgrade")) {
118 image: "tianon/postgres-upgrade:%s-to-%s" % [cfg.pgupgrade.from, cfg.pgupgrade.to],
119 command: [
120 "bash", "-c", |||
121 set -e -x -o pipefail
122
123 CURRENT_VERSION="$(cat "$PGDATA/PG_VERSION")"
124 if [[ "$CURRENT_VERSION" == "$VERSION_TO" ]]; then
125 echo "Already running target version ($VERSION_TO)"
126 exit 0
127 fi
128
129 if [[ "$CURRENT_VERSION" != "$VERSION_FROM" ]]; then
130 echo "Running unexpected source version, wanted $VERSION_FROM, got $CURRENT_VERSION"
131 exit 1
132 fi
133
134 rm -rf $PGDATANEXT || true
135
136 if [ ! -s "$PGDATANEXT/PG_VERSION" ]; then
137 echo "Initializing new database..."
138 PGDATA="$PGDATANEXT" eval "initdb $POSTGRES_INITDB_ARGS"
139 fi
140
141 chmod 700 $PGDATA $PGDATANEXT
142
143 echo "Running upgrade..."
144 pg_upgrade --link --old-datadir $PGDATA --new-datadir $PGDATANEXT || (sleep 3600 ; exit 1)
145
146 echo "Copying pg_hba.conf"
147 cp $PGDATA/pg_hba.conf $PGDATANEXT/pg_hba.conf
148
149 echo "Done, swapping..."
150 mv $PGDATA $PGDATAOLD
151 mv $PGDATANEXT $PGDATA
152 |||
153 ],
154 env_: postgres.deployment.spec.template.spec.containers_.postgres.env_ + {
155 VERSION_TO: cfg.pgupgrade.to,
156 VERSION_FROM: cfg.pgupgrade.from,
157
158 # pg_upgrade target directory, swapped with
159 # PGDATA after cluster data upgrade is finished
160 PGDATANEXT: "/var/lib/postgresql/data/pgdata-next",
161
162 # Directory used to stash previous pgdata
163 # version
164 PGDATAOLD: "/var/lib/postgresql/data/pgdata-old",
165 },
166 volumeMounts_: {
167 data: { mountPath: "/var/lib/postgresql/data" },
168 },
169 },
170 } else {},
Sergiusz Bazanskia2ee8652020-01-22 21:48:48 +0100171 securityContext: {
172 runAsUser: 999,
173 },
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200174 },
175 },
176 },
177 },
Serge Bazanskic0c037a2020-08-23 01:24:03 +0000178
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200179 svc: kube.Service(postgres.makeName("postgres")) {
180 metadata+: postgres.metadata,
181 target_pod:: postgres.deployment.spec.template,
182 spec+: {
183 ports: [
184 { name: "client", port: 5432, targetPort: 5432, protocol: "TCP" },
185 ],
186 type: "ClusterIP",
187 },
188 },
Sergiusz Bazanskic622a192020-02-15 12:39:14 +0100189
190 bouncer: {
191 deployment: kube.Deployment(postgres.makeName("bouncer")) {
192 metadata+: postgres.metadata {
193 labels+: {
194 "app.kubernetes.io/component": "bouncer",
195 }
196 },
197 spec+: {
198 replicas: 1,
199 template+: {
200 spec+: {
201 containers_: {
202 bouncer: kube.Container(postgres.makeName("bouncer")) {
203 image: "edoburu/pgbouncer:1.11.0",
204 ports_: {
205 client: { containerPort: 5432 },
206 },
207 env: [
208 { name: "POSTGRES_PASSWORD", valueFrom: cfg.password },
209 { name: "DATABASE_URL", value: "postgres://%s:$(POSTGRES_PASSWORD)@%s/%s" % [cfg.username, postgres.svc.host, cfg.database] },
210 ],
211 },
212 },
213 },
214 },
215 },
216 },
217 svc: kube.Service(postgres.makeName("bouncer")) {
218 metadata+: postgres.metadata {
219 labels+: {
220 "app.kubernetes.io/component": "bouncer",
221 }
222 },
223 target_pod:: postgres.bouncer.deployment.spec.template,
224 spec+: {
225 ports: [
226 { name: "client", port: 5432, targetPort: 5432, protocol: "TCP" },
227 ],
228 type: "ClusterIP",
229 },
230 },
231 },
Sergiusz Bazanski5f2dc852019-04-02 02:36:22 +0200232}