cluster: start implementing metallb
diff --git a/cluster/kube/cluster.jsonnet b/cluster/kube/cluster.jsonnet
index 4eb9eee..c79a827 100644
--- a/cluster/kube/cluster.jsonnet
+++ b/cluster/kube/cluster.jsonnet
@@ -4,6 +4,7 @@
 local coredns = import "lib/coredns.libsonnet";
 local metrics = import "lib/metrics.libsonnet";
 local calico = import "lib/calico.libsonnet";
+local metallb = import "lib/metallb.libsonnet";
 
 local Cluster(fqdn) = {
     local cluster = self,
@@ -48,6 +49,8 @@
     dns: coredns.Environment {},
     // Metrics Server
     metrics: metrics.Environment {},
+    // Metal Load Balancer
+    metallb: metallb.Environment {},
 };
 
 
diff --git a/cluster/kube/lib/metallb.libsonnet b/cluster/kube/lib/metallb.libsonnet
new file mode 100644
index 0000000..a00163b
--- /dev/null
+++ b/cluster/kube/lib/metallb.libsonnet
@@ -0,0 +1,110 @@
+# Deploy MetalLB
+
+local kube = import "../../../kube/kube.libsonnet";
+
+local bindServiceAccountClusterRole(sa, cr) = kube.ClusterRoleBinding(cr.metadata.name) {
+    roleRef: {
+        apiGroup: "rbac.authorization.k8s.io",
+        kind: "ClusterRole",
+        name: cr.metadata.name,
+    },
+    subjects: [
+        {
+            kind: "ServiceAccount",
+            name: sa.metadata.name,
+            namespace: sa.metadata.namespace,
+        },
+    ],
+};
+
+{
+    Environment: {
+        local env = self,
+        local cfg = env.cfg,
+        cfg:: {
+            namespace: "metallb-system",
+            namespaceCreate: true,
+        },
+
+        ns: if cfg.namespaceCreate then kube.Namespace(cfg.namespace),
+
+        saController: kube.ServiceAccount("controller") {
+            metadata+: {
+                namespace: cfg.namespace,
+            },
+        },
+
+        saSpeaker: kube.ServiceAccount("speaker") {
+            metadata+: {
+                namespace: cfg.namespace,
+            },
+        },
+
+        crController: kube.ClusterRole("%s:controller" % cfg.namespace) {
+            rules: [
+                {
+                    apiGroups: [""],
+                    resources: ["services"],
+                    verbs: ["get", "list", "watch", "update"],
+                },
+                {
+                    apiGroups: [""],
+                    resources: ["services/status"],
+                    verbs: ["update"],
+                },
+                {
+                    apiGroups: [""],
+                    resources: ["events"],
+                    verbs: ["create", "patch"],
+                },
+            ],
+        },
+
+        crbController: bindServiceAccountClusterRole(env.saController, env.crController),
+
+        crSpeaker: kube.ClusterRole("%s:speaker" % cfg.namespace) {
+            rules: [
+                {
+                    apiGroups: [""],
+                    resources: ["services", "endpoints", "nodes"],
+                    verbs: ["get", "list", "watch"],
+                },
+            ],
+        },
+
+        crbSpeaker: bindServiceAccountClusterRole(env.saSpeaker, env.crSpeaker),
+
+        roleWatcher: kube.Role("config-watcher") {
+            metadata+: {
+                namespace: cfg.namespace,
+            },
+            rules: [
+                {
+                    apiGroups: [""],
+                    resources: ["configmaps"],
+                    verbs: ["get", "list", "watch"],
+                },
+                {
+                    apiGroups: [""],
+                    resources: ["events"],
+                    verbs: ["create"],
+                },
+            ],
+        },
+
+        rbWatcher: kube.RoleBinding("config-watcher") {
+            metadata+: {
+                namespace: cfg.namespace,
+            },
+            subjects: [
+                { kind: "ServiceAccount", name: env.saController.metadata.name },
+                { kind: "ServiceAccount", name: env.saSpeaker.metadata.name },
+            ],
+            roleRef: {
+                apiGroup: "rbac.authorization.k8s.io",
+                kind: "Role",
+                name: env.roleWatcher.metadata.name,
+            },
+        },
+    },
+}