cluster: fix metallb, add nginx ingress controller
diff --git a/cluster/kube/lib/nginx.libsonnet b/cluster/kube/lib/nginx.libsonnet
new file mode 100644
index 0000000..a6d10f1
--- /dev/null
+++ b/cluster/kube/lib/nginx.libsonnet
@@ -0,0 +1,212 @@
+# Deploy a per-cluster Nginx Ingress Controller
+
+local kube = import "../../../kube/kube.libsonnet";
+
+{
+    Environment: {
+        local env = self,
+        local cfg = env.cfg,
+        cfg:: {
+            image: "quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.23.0",
+            namespace: "nginx-system",
+        },
+
+        metadata:: {
+            namespace: cfg.namespace,
+            labels: {
+                "app.kubernetes.io/name": "ingress-nginx",
+                "app.kubernetes.io/part-of": "ingress-nginx",
+            },
+        },
+
+        namespace: kube.Namespace(cfg.namespace),
+
+        maps: {
+            make(name):: kube.ConfigMap(name) {
+                metadata+: env.metadata,
+            },
+            configuration: env.maps.make("nginx-configuration"),
+            tcp: env.maps.make("tcp-services"),
+            udp: env.maps.make("udp-services"),
+        },
+
+        sa: kube.ServiceAccount("nginx-ingress-serviceaccount") {
+            metadata+: env.metadata,
+        },
+
+        cr: kube.ClusterRole("nginx-ingress-clusterrole") {
+            metadata+: env.metadata {
+                namespace:: null,
+            },
+            rules: [
+                {
+                    apiGroups: [""],
+                    resources: ["configmaps", "endpoints", "nodes", "pods", "secrets"],
+                    verbs: ["list", "watch"],
+                },
+                {
+                    apiGroups: [""],
+                    resources: ["nodes"],
+                    verbs: ["get"],
+                },
+                {
+                    apiGroups: [""],
+                    resources: ["services"],
+                    verbs: ["get", "list", "watch"],
+                },
+                {
+                    apiGroups: ["extensions"],
+                    resources: ["ingresses"],
+                    verbs: ["get", "list", "watch"],
+                },
+                {
+                    apiGroups: [""],
+                    resources: ["events"],
+                    verbs: ["create", "patch"],
+                },
+                {
+                    apiGroups: ["extensions"],
+                    resources: ["ingresses/status"],
+                    verbs: ["update"],
+                },
+            ],
+        },
+
+        crb: kube.ClusterRoleBinding("nginx-ingress-clusterrole-nisa-binding") {
+            metadata+: env.metadata {
+                namespace:: null,
+            },
+            roleRef: {
+                apiGroup: "rbac.authorization.k8s.io",
+                kind: "ClusterRole",
+                name: env.cr.metadata.name,
+            },
+            subjects: [
+                {
+                    kind: "ServiceAccount",
+                    name: env.sa.metadata.name,
+                    namespace: env.sa.metadata.namespace,
+                },
+            ],
+        },
+
+        role: kube.Role("nginx-ingress-role") {
+            metadata+: env.metadata,
+            rules : [
+                {
+                    apiGroups: [""],
+                    resources: ["configmaps", "pods", "secrets", "namespaces"],
+                    verbs: ["get"],
+                },
+                {
+                    apiGroups: [""],
+                    resources: ["configmaps"],
+                    resourceNames: ["ingress-controller-leader-nginx"],
+                    verbs: ["get", "update"],
+                },
+                {
+                    apiGroups: [""],
+                    resources: ["configmaps"],
+                    verbs: ["create"],
+                },
+                {
+                    apiGroups: [""],
+                    resources: ["endpoints"],
+                    verbs: ["get"],
+                },
+            ],
+        },
+
+        roleb: kube.RoleBinding("nginx-ingress-role-nisa-binding") {
+            metadata+: env.metadata,
+            roleRef: {
+                apiGroup: "rbac.authorization.k8s.io",
+                kind: "Role",
+                name: env.role.metadata.name,
+            },
+            subjects: [
+                {
+                    kind: "ServiceAccount",
+                    name: env.sa.metadata.name,
+                    namespace: env.sa.metadata.namespace,
+                },
+            ],
+        },
+
+        service: kube.Service("ingress-nginx") {
+            metadata+: env.metadata,
+            target_pod:: env.deployment.spec.template,
+            spec+: {
+                type: "LoadBalancer",
+                ports: [
+                    { name: "http", port: 80, targetPort: 80, protocol: "TCP" },
+                    { name: "https", port: 443, targetPort: 443, protocol: "TCP" },
+                ],
+            },
+        },
+
+        deployment: kube.Deployment("nginx-ingress-controller") {
+            metadata+: env.metadata,
+            spec+: {
+                replicas: 1,
+                template+: {
+                    spec+: {
+                        serviceAccountName: env.sa.metadata.name,
+                        containers_: {
+                            controller: kube.Container("nginx-ingress-controller") {
+                                image: cfg.image,
+                                args: [
+                                    "/nginx-ingress-controller",
+                                    "--configmap=%s/%s" % [cfg.namespace, env.maps.configuration.metadata.name],
+                                    "--tcp-services-configmap=%s/%s" % [cfg.namespace, env.maps.tcp.metadata.name],
+                                    "--udp-services-configmap=%s/%s" % [cfg.namespace, env.maps.udp.metadata.name],
+                                    "--publish-service=%s/%s" % [cfg.namespace, env.service.metadata.name],
+                                    "--annotations-prefix=nginx.ingress.kubernetes.io",
+                                ],
+                                env_: {
+                                    POD_NAME: kube.FieldRef("metadata.name"),
+                                    POD_NAMESPACE: kube.FieldRef("metadata.namespace"),
+                                },
+                                ports_: {
+                                    http: { containerPort: 80 },
+                                    https: { containerPort: 443 },
+                                },
+                                livenessProbe: {
+                                    failureThreshold: 3,
+                                    httpGet: {
+                                        path: "/healthz",
+                                        port: 10254,
+                                        scheme: "HTTP",
+                                    },
+                                    initialDelaySeconds: 10,
+                                    periodSeconds: 10,
+                                    successThreshold: 1,
+                                    timeoutSeconds: 10,
+                                },
+                                readinessProbe: {
+                                    failureThreshold: 3,
+                                    httpGet: {
+                                        path: "/healthz",
+                                        port: 10254,
+                                        scheme: "HTTP",
+                                    },
+                                    periodSeconds: 10,
+                                    successThreshold: 1,
+                                    timeoutSeconds: 10,
+                                },
+                                securityContext: {
+                                    allowPrivilegeEscalation: true,
+                                    capabilities: {
+                                        drop: ["ALL"],
+                                        add: ["NET_BIND_SERVICE"],
+                                    },
+                                    runAsUser: 33,
+                                },
+                            },
+                        },
+                    },
+                },
+            },
+        },
+    },
+}