diff --git a/platform/spire/BUILD.bazel b/platform/spire/BUILD.bazel index 5b733cf41..3c0018914 100644 --- a/platform/spire/BUILD.bazel +++ b/platform/spire/BUILD.bazel @@ -17,6 +17,7 @@ py_binary( "//upload:uploadlib", "//user-grant:kubelib", "//version:versionlib", + "//website:kubelib", ], main = "__main__.py", ) diff --git a/platform/spire/resources/prometheus.yaml b/platform/spire/resources/prometheus.yaml index 5de9af554..8b52f0a25 100644 --- a/platform/spire/resources/prometheus.yaml +++ b/platform/spire/resources/prometheus.yaml @@ -135,3 +135,13 @@ scrape_configs: regex: (.+) target_label: __metrics_path__ replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor + + - job_name: 'website-internal' + scheme: https + tls_config: + ca_file: /etc/homeworld/authorities/kubernetes.pem + cert_file: /etc/homeworld/keys/kubernetes-supervisor.pem + key_file: /etc/homeworld/keys/kubernetes-supervisor.key + static_configs: + - targets: ['{{APISERVER}}:443'] + metrics_path: '/api/v1/namespaces/homeworld-website/services/homeworld-website/proxy/metrics' diff --git a/platform/spire/src/deploy.py b/platform/spire/src/deploy.py index f55bc4c59..15b65e13b 100644 --- a/platform/spire/src/deploy.py +++ b/platform/spire/src/deploy.py @@ -89,10 +89,17 @@ def launch_user_grant(export: bool=False): }, export=export) +@command.wrap +def launch_website(export: bool=False): + "deploy the specifications to run the self-hosting website" + launch_spec("//website:kubernetes.yaml", export=export) + + main_command = command.Mux("commands to deploy systems onto the kubernetes cluster", { "flannel": launch_flannel, "flannel-monitor": launch_flannel_monitor, "dns-addon": launch_dns_addon, "dns-monitor": launch_dns_monitor, "user-grant": launch_user_grant, + "website": launch_website, }) diff --git a/platform/spire/src/seq.py b/platform/spire/src/seq.py index 3d1f15dee..ba5fd0a9b 100644 --- a/platform/spire/src/seq.py +++ b/platform/spire/src/seq.py @@ -56,6 +56,8 @@ def sequence_supervisor(ops: command.Operations, skip_verify_keygateway: bool=Fa else: ops.add_operation("skip pre-deploying user-grant (not configured)", lambda: None) + ops.add_command(deploy.launch_website) + for node in config.nodes: if node.kind == 'supervisor': ops.add_subcommand(infra.infra_sync, node.hostname) @@ -124,6 +126,8 @@ def sequence_cluster(ops: command.Operations) -> None: else: ops.add_operation("verify that user-grant is working properly", iterative_verifier(verify.check_user_grant, 120.0)) + ops.add_command(iterative_verifier(verify.check_website, 120.0)) + main_command = command.SeqMux("commands about running large sequences of cluster bring-up automatically", { "keysystem": sequence_keysystem, diff --git a/platform/spire/src/verify.py b/platform/spire/src/verify.py index 905334821..21a350a44 100644 --- a/platform/spire/src/verify.py +++ b/platform/spire/src/verify.py @@ -354,6 +354,14 @@ def check_user_grant(): print("autogenerated rolebinding for user", repr(authority.UPSTREAM_USER_NAME), "passed basic check!") + +@command.wrap +def check_website(): + "verify that the self-hosted website is running" + expect_prometheus_query_exact('sum(up{job="website-internal"})', 1, "website is accessible from inside the cluster") + print("self-hosted website is running") + + main_command = command.Mux("commands about verifying the state of a cluster", { "keystatics": check_keystatics, "keygateway": check_keygateway, @@ -369,4 +377,5 @@ def check_user_grant(): "flannel": check_flannel, "dns-addon": check_dns, "user-grant": check_user_grant, + "website": check_website, }) diff --git a/platform/upload/BUILD.bazel b/platform/upload/BUILD.bazel index 88bd3f81b..89e1ced16 100644 --- a/platform/upload/BUILD.bazel +++ b/platform/upload/BUILD.bazel @@ -9,6 +9,7 @@ ocis = { "dnsmasq-nanny": "//dnsmasq:dnsmasq-nanny", "flannel": "//flannel:oci", "flannel-monitor": "//flannel-monitor:oci", + "homeworld-website": "//website:oci", "kube-dns-main": "//kube-dns:kube-dns-main", "kube-dns-sidecar": "//kube-dns:kube-dns-sidecar", "pause": "//cri-o/pause:oci", diff --git a/platform/website/BUILD.bazel b/platform/website/BUILD.bazel new file mode 100644 index 000000000..a9df72e46 --- /dev/null +++ b/platform/website/BUILD.bazel @@ -0,0 +1,27 @@ +load("//bazel:package.bzl", "homeworld_oci") +load("//bazel:substitute.bzl", "substitute") +load("//python:resources.bzl", "py_resources") + +homeworld_oci( + name = "oci", + bin = { + "//website/server": "/usr/bin/website-server", + }, + exec = ["/usr/bin/website-server"], + visibility = ["//visibility:public"], +) + +substitute( + name = "kubernetes.yaml", + kfs = { + "digest": ":oci.ocidigest", + }, + template = ":kubernetes.yaml.in", + visibility = ["//visibility:public"], +) + +py_resources( + name = "kubelib", + data = [":kubernetes.yaml"], + visibility = ["//visibility:public"], +) diff --git a/platform/website/kubernetes.yaml.in b/platform/website/kubernetes.yaml.in new file mode 100644 index 000000000..683a4bddc --- /dev/null +++ b/platform/website/kubernetes.yaml.in @@ -0,0 +1,43 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + app: homeworld-website + name: homeworld-website +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: homeworld-website + name: homeworld-website + namespace: homeworld-website +spec: + replicas: 1 + selector: + matchLabels: + app: homeworld-website + template: + metadata: + labels: + app: homeworld-website + spec: + containers: + - image: homeworld.private/homeworld-website@{digest} + name: server +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: homeworld-website + name: homeworld-website + namespace: homeworld-website +spec: + selector: + app: homeworld-website + type: LoadBalancer + ports: + - protocol: TCP + port: 80 + targetPort: 8080 diff --git a/platform/website/server/BUILD.bazel b/platform/website/server/BUILD.bazel new file mode 100644 index 000000000..1ed812a55 --- /dev/null +++ b/platform/website/server/BUILD.bazel @@ -0,0 +1,21 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "go_default_library", + srcs = ["server.go"], + importpath = "github.com/sipb/homeworld/platform/website/server", + visibility = ["//visibility:private"], + deps = [ + "@com_github_prometheus_client_golang//prometheus:go_default_library", + "@com_github_prometheus_client_golang//prometheus/promauto:go_default_library", + "@com_github_prometheus_client_golang//prometheus/promhttp:go_default_library", + ], +) + +go_binary( + name = "server", + embed = [":go_default_library"], + importpath = "github.com/sipb/homeworld/platform/website/server", + pure = "on", + visibility = ["//visibility:public"], +) diff --git a/platform/website/server/server.go b/platform/website/server/server.go new file mode 100644 index 000000000..4a5637ffc --- /dev/null +++ b/platform/website/server/server.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" + "log" + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +var requestsServed = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: "website", + Name: "requests_served", + Help: "Number of main page requests served by the website instance", +}) + +func page(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + fmt.Fprintln(w, "Welcome to the homeworld self-hosting website.") + requestsServed.Inc() +} + +func main() { + http.HandleFunc("/", page) + http.Handle("/metrics", promhttp.Handler()) + log.Println("Listening on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +}