blob: 67033816380ad0c63a82deb7848f49d9b01224a7 [file] [log] [blame]
Sergiusz Bazanski6f773e02019-10-02 20:46:48 +02001# Mirko, an abstraction layer for hscloud kubernetes services.
2
3local kube = import "kube.libsonnet";
4
5{
6 Environment(name): {
7 local env = self,
8 local cfg = env.cfg,
9 cfg:: {
10 name: name,
11 namespace: cfg.name,
12 },
13
14 namespace: kube.Namespace(cfg.namespace),
15
16 components: {}, // type: mirko.Component
17
18 // Currently hardcoded!
19 // This might end up being something passed part of kubecfg evaluation,
20 // when we get to supporting multiple/federated clusters.
21 // For now, this is goog enough.
22 pkiRealm:: "hswaw.net",
23 pkiClusterFQDN:: "k0.hswaw.net",
24
25 // Generate an ingress if we have any public ports.
26 publicHTTPPorts:: std.flattenArrays([
27 [
28 {
29 local component = env.components[c],
30
31 service: component.svc,
32 port: component.cfg.ports.publicHTTP[p].port,
33 dns: component.cfg.ports.publicHTTP[p].dns,
Sergiusz Bazanski91e1a8c2020-06-25 12:16:29 +020034 // Extra headers to set.
35 // BUG(q3k): these headers are applied to all components in the environment!
36 // We should be splitting up ingresses where necessary to combat this.
37 setHeaders: [],
38 // Extra paths to add to ingress. These are bare HTTPIngressPaths.
39 extraPaths: component.cfg.extraPaths,
Sergiusz Bazanski6f773e02019-10-02 20:46:48 +020040 }
41 for p in std.objectFields(env.components[c].cfg.ports.publicHTTP)
42 ]
43 for c in std.objectFields(env.components)
44 ]),
45
46 ingress: if std.length(env.publicHTTPPorts) > 0 then kube.Ingress("mirko-public") {
47 metadata+: {
48 namespace: env.cfg.namespace,
49 labels: {
50 "app.kubernetes.io/name": cfg.name,
51 "app.kubernetes.io/managed-by": "kubecfg-mirko",
52 "app.kubernetes.io/component": cfg.name,
53 "mirko.hscloud.hackerspace.pl/environment": env.cfg.name,
54 "mirko.hscloud.hackerspace.pl/component": "mirko-public-ingress",
55 },
56 annotations+: {
57 "kubernetes.io/tls-acme": "true",
58 "certmanager.k8s.io/cluster-issuer": "letsencrypt-prod",
Sergiusz Bazanski91e1a8c2020-06-25 12:16:29 +020059 [if env.ingressServerSnippet != null]: "nginx.ingress.kubernetes.io/server-snippet": env.ingressServerSnippet,
60 [if std.length(env.extraHeaders) > 0 then "nginx.ingress.kubernetes.io/configuration-snippet"]:
61 std.join("\n", ["proxy_set_header %s;" % [h] for h in env.extraHeaders]),
Sergiusz Bazanski6f773e02019-10-02 20:46:48 +020062 },
63 },
64 spec+: {
65 tls: [
66 {
67 hosts: [p.dns for p in env.publicHTTPPorts],
68 secretName: "mirko-public-tls",
69 },
70 ],
71 rules: [
72 {
73 host: p.dns,
74 http: {
75 paths: [
76 { path: "/", backend: { serviceName: p.service.metadata.name, servicePort: p.port }},
Sergiusz Bazanski91e1a8c2020-06-25 12:16:29 +020077 ] + p.extraPaths,
Sergiusz Bazanski6f773e02019-10-02 20:46:48 +020078 },
79 }
80 for p in env.publicHTTPPorts
81 ],
82 },
Sergiusz Bazanski91e1a8c2020-06-25 12:16:29 +020083 } else {},
84
85 // Nginx Ingress Controller server configuration snippet to add.
86 ingressServerSnippet:: null,
87
88 // Extra request headers to add to ingress
89 extraHeaders:: std.flattenArrays([
90 std.flattenArrays([
91
92 local portc = env.components[c].cfg.ports.publicHTTP[p];
93 if std.objectHas(portc, "setHeaders") then portc.setHeaders else []
94 for p in std.objectFields(env.components[c].cfg.ports.publicHTTP)
95 ])
96 for c in std.objectFields(env.components)
97 ]),
Sergiusz Bazanski6f773e02019-10-02 20:46:48 +020098 },
99
100 Component(env, name): {
101 local component = self,
102 local cfg = component.cfg,
103
104 makeName(suffix):: "%s%s%s" % [cfg.prefix, cfg.name, suffix],
Sergiusz Bazanski74818e12020-02-18 22:56:21 +0100105 makeNameGlobal(suffix):: "%s-%s" % [env.cfg.namespace, component.makeName(suffix)],
Sergiusz Bazanski6f773e02019-10-02 20:46:48 +0200106
107 metadata:: {
108 namespace: env.cfg.namespace,
109 labels: {
110 "app.kubernetes.io/name": env.cfg.name,
111 "app.kubernetes.io/managed-by": "kubecfg-mirko",
112 "app.kubernetes.io/component": cfg.name,
113 "mirko.hscloud.hackerspace.pl/environment": env.cfg.name,
114 "mirko.hscloud.hackerspace.pl/component": cfg.name,
115 },
116 },
117
118
119 # Tunables for users.
120 cfg:: {
121 name: name,
122
123 prefix:: "",
124 image:: env.image,
125 volumes:: {},
126 containers:: {
127 main: cfg.container,
128 },
Sergiusz Bazanski92b48d62020-01-08 13:59:04 +0100129 nodeSelector: null,
Sergiusz Bazanskiaa8c2b02020-02-15 12:38:39 +0100130 securityContext: {},
Sergiusz Bazanski6f773e02019-10-02 20:46:48 +0200131 container:: error "container(s) must be set",
132 ports:: {
133 publicHTTP: {}, // name -> { port: no, dns: fqdn }
134 grpc: { main: 4200 }, // name -> port no
135 },
Sergiusz Bazanski91e1a8c2020-06-25 12:16:29 +0200136 extraPaths:: [],
Sergiusz Bazanski6f773e02019-10-02 20:46:48 +0200137 },
138
139 allPorts:: {
140 ['grpc-' + p]: cfg.ports.grpc[p]
141 for p in std.objectFields(cfg.ports.grpc)
142 } + {
143 ['pubhttp-' + p] : cfg.ports.publicHTTP[p].port
144 for p in std.objectFields(cfg.ports.publicHTTP)
145 },
146
147 Container(name):: kube.Container(component.makeName(name)) {
148 image: cfg.image,
149 volumeMounts_: {
150 pki: { mountPath: "/mnt/pki" },
151 },
152 ports_: {
153 [p]: { containerPort: component.allPorts[p] }
154 for p in std.objectFields(component.allPorts)
155 },
156 resources: {
157 requests: {
158 cpu: "25m",
159 memory: "64Mi",
160 },
161 limits: {
162 cpu: "500m",
163 memory: "128Mi",
164 },
165 },
166 },
167
168 GoContainer(name, binary):: component.Container(name) {
169 command: [
170 binary,
171 "-hspki_realm", env.pkiRealm,
172 "-hspki_cluster", env.pkiClusterFQDN,
173 "-hspki_tls_ca_path", "/mnt/pki/ca.crt",
174 "-hspki_tls_certificate_path", "/mnt/pki/tls.crt",
175 "-hspki_tls_key_path", "/mnt/pki/tls.key",
176 "-logtostderr",
177 "-listen_address", "0.0.0.0:4200",
178 ],
179 },
180
181 deployment: kube.Deployment(component.makeName("-main")) {
182 metadata+: component.metadata,
183 spec+: {
184 template+: {
185 spec+: {
186 volumes_: {
187 pki: {
188 secret: { secretName: component.pki.cert.spec.secretName },
189 },
190 } + cfg.volumes,
191 containers_: cfg.containers,
Sergiusz Bazanski92b48d62020-01-08 13:59:04 +0100192 nodeSelector: cfg.nodeSelector,
Sergiusz Bazanski6f773e02019-10-02 20:46:48 +0200193
194 serviceAccountName: component.sa.metadata.name,
Sergiusz Bazanskiaa8c2b02020-02-15 12:38:39 +0100195 securityContext: cfg.securityContext,
Sergiusz Bazanski6f773e02019-10-02 20:46:48 +0200196 },
197 },
198 },
199 },
200
201 svc: kube.Service(component.makeName("")) { // No suffix, name part of DNS entry.
202 metadata+: component.metadata,
203 target_pod:: component.deployment.spec.template,
204 spec+: {
205 ports: [
206 {
207 name: p,
208 port: component.allPorts[p],
209 targetPort: component.allPorts[p],
210 }
211 for p in std.objectFields(component.allPorts)
212 ],
213 },
214 },
215
216 sa: kube.ServiceAccount(component.makeName("-main")) {
217 metadata+: component.metadata,
218 },
219
220 pki: {
221 cert: kube.Certificate(component.makeName("-cert")) {
222 metadata+: component.metadata,
223
224 spec: {
225 secretName: component.makeName("-cert"),
226 duration: "35040h0m0s", // 4 years
227 issuerRef: {
228 // Contract with cluster/lib/pki.libsonnet.
229 name: "pki-ca",
230 kind: "ClusterIssuer",
231 },
232 commonName: "%s.%s.svc.%s" % [component.svc.metadata.name, component.svc.metadata.namespace, env.pkiClusterFQDN ],
233 dnsNames: [
234 "%s" % [component.svc.metadata.name ],
235 "%s.%s" % [component.svc.metadata.name, component.svc.metadata.namespace ],
236 "%s.%s.svc" % [component.svc.metadata.name, component.svc.metadata.namespace ],
237 "%s.%s.svc.cluster.local" % [component.svc.metadata.name, component.svc.metadata.namespace ],
238 "%s.%s.svc.%s" % [component.svc.metadata.name, component.svc.metadata.namespace, env.pkiClusterFQDN ],
239 ],
240 },
241 },
242 },
243 },
244}