From a2c04aa341413d6d55ff28ac40df39920a45aa63 Mon Sep 17 00:00:00 2001 From: Minae Lee Date: Mon, 2 Feb 2026 16:10:40 -0700 Subject: [PATCH] doc: add cluster manager reference architecture Signed-off-by: Minae Lee --- doc/.custom_wordlist.txt | 8 + doc/images/cluster_manager_architecture.png | Bin 0 -> 39974 bytes doc/reference/cluster-manager-architecture.md | 150 ++++++++++++++++++ doc/reference/index.md | 22 ++- 4 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 doc/images/cluster_manager_architecture.png create mode 100644 doc/reference/cluster-manager-architecture.md diff --git a/doc/.custom_wordlist.txt b/doc/.custom_wordlist.txt index 7067c6bfc..e1c22706b 100644 --- a/doc/.custom_wordlist.txt +++ b/doc/.custom_wordlist.txt @@ -29,6 +29,7 @@ GbE GiB HA HMAC +HTTPS HWE init intra @@ -52,13 +53,17 @@ Netplan NIC NICs NVMe +OIDC OptiPNG OSDs OVN OVS PNG +Postgres pre preseed +PV +PVC QSFP RBD Replicable @@ -69,12 +74,15 @@ scalable SDN snapd SSD +SSL subnet subnets SVG +TCP Terraform TinyPNG TLS +Traefik UI's uplink upstream's diff --git a/doc/images/cluster_manager_architecture.png b/doc/images/cluster_manager_architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..48b5d040b998d9b2651ca7c0d240216b68ba30a9 GIT binary patch literal 39974 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 AK>z>% literal 0 HcmV?d00001 diff --git a/doc/reference/cluster-manager-architecture.md b/doc/reference/cluster-manager-architecture.md new file mode 100644 index 000000000..34dbfe3f9 --- /dev/null +++ b/doc/reference/cluster-manager-architecture.md @@ -0,0 +1,150 @@ +--- +myst: + html_meta: + description: Reference architecture for MicroCloud Cluster Manager, a Kubernetes-based web application for viewing and managing multiple MicroCloud deployments. +--- + +(ref-cluster-manager-architecture)= +# Architecture of the MicroCloud Cluster Manager + +The MicroCloud Cluster Manager is a centralized tool that provides an overview of MicroCloud deployments. In its initial implementation, it provides an overview of resource usage and availability for all clusters. Future implementations will include centralized cluster management capabilities. + +Cluster Manager stores the data from registered clusters in Postgres and Prometheus databases. This data can be displayed in the Cluster Manager UI, which can be extended to link to Grafana dashboards for each MicroCloud. + +(ref-cluster-manager-architecture-overview)= +## Architecture overview + +Cluster Manager is a distributed web application that runs inside a Kubernetes cluster to achieve high availability. The diagram below illustrates its system architecture: + +```{figure} ../images/cluster_manager_architecture.png + :alt: A diagram of Cluster Manager architecture + :align: center +``` + +Inside the Kubernetes cluster are the following system components: + +DNS and static external IP +: The Domain Name Server (DNS) must be set up by the user to resolve their domain names to a static external IP. +That static external IP acts as the gateway to route user traffic to the appropriate Kubernetes load balancers. + +TCP load balancers +: A TCP load balancer (using a [Traefik](https://traefik.io/) instance) distributes traffic to the Management API and Cluster Connector deployments without terminating TLS. Instead, TLS termination is handled directly within each deployment application. This approach is particularly crucial for the Cluster Connector deployment, as it relies on mutual TLS (mTLS) authentication for secure communication. + +Certificate manager +: Manages TLS/SSL certificates for secure communication within the Kubernetes cluster. It stores secrets in Kubernetes to be used by various components. The certificates are used by both the Management API and Cluster Connector deployments for HTTPS encryption. + +Postgres deployment +: A PostgreSQL database deployed within the Kubernetes cluster. It provides persistent storage for system data. Both the Management API and Cluster Connector will communicate with the Postgres database for CRUD operations. + +Persistent Volume (PV) and Persistent Volume Claim (PVC) +: The Persistent Volume is the storage resource provisioned for the Postgres deployment. The Persistent Volume Claim is the request for storage by the Postgres deployment to ensure data persistence. + +{ref}`ref-cluster-manager-architecture-management` +: Responsible for serving the static UI assets, exposing API endpoints for the UI to communicate with the Cluster Manager backend. Requests from the UI are authenticated using OIDC. + +{ref}`ref-cluster-manager-architecture-connector` +: Responsible for handling requests from MicroCloud clusters, authenticated using mTLS. + +(ref-cluster-manager-architecture-management)= +## Management API + +The management API handles local operations in Cluster Manager, including: + +- Listing active MicroCloud clusters +- Creating cluster join tokens +- Approving join requests from clusters +- Serving the UI's static assets + +(ref-cluster-manager-architecture-management-ingress)= +### Management API ingress + +The Management API configures and runs an HTTPS server to make API endpoints available. Traffic to the server passes through a TCP load balancer. + +(ref-cluster-manager-architecture-management-oidc)= +### OIDC authentication + +The Management API is secured using OIDC authentication, using the [`microcloud-cluster-manager-k8s`](https://charmhub.io/microcloud-cluster-manager-k8s) charm configurations. The charm handles providing OIDC information to the Kubernetes cluster and its `configMap`. + +The OIDC flow is similar to the {ref}`approach implemented in LXD `: +- A user initiates the login flow from the UI. This makes a request to the `/oidc/login` endpoint, which redirects the user to the identity provider's authentication screen. At this stage, a callback endpoint (`*/oidc/callback`) is set in the redirect request. +- The user then enters their credentials to authenticate with the identity provider. +Upon successful authentication, the identity provider sends a request to the callback endpoint `*/oidc/callback` set in step 1. +- The request includes an authorization code. The callback endpoint uses this code to initiate the token exchange process with the identity provider and acquire the ID, access, and refresh tokens for the authenticated user. +- These tokens are set in the session cookie and the user is redirected to the base route of the UI. +- Subsequent requests use the session cookie to validate authentication. + +(ref-cluster-manager-architecture-management-registering)= +### Registering clusters + +To register a MicroCloud cluster with a Cluster Manager, the user first creates a remote cluster join token in the Manager. This token is base64-encoded and includes the following information: + +| Key | Description | +| ------------| ----------- | +| secret | A secret to be used by a MicroCloud cluster for creating a [HMAC](https://en.wikipedia.org/wiki/HMAC) signature for the join request. | +| expires_at | Expiry date for the remote cluster join token. | +| address | The address at which the MicroCloud Cluster Manager is reachable. This address can be a domain name or external static IP. | +| server_name | This is unique and stored for reference purposes in Cluster Manager to map which cluster the token belongs to. | +| fingerprint | The public key from the Cluster Connector deployment certificate (secret in Kubernetes cluster). Used to establish mTLS between Cluster Manager and the cluster. | + +On a member of the registering cluster, the token is used in the command `microcloud cluster-manager join `. The join request is sent to the Cluster Manager for approval. The request payload includes the registering cluster's name and cluster certificate. + +The registering cluster begins sending periodic heartbeats along with status updates to the Cluster Manager. These heartbeats and updates begin as soon as a join request response is received from the Manager. + +This is due to the unidirectional nature of communication initialization between clusters and the Cluster Manager. The Cluster Manager can only receive and respond to data from the cluster; it cannot initiate a message to tell the cluster when the join request is approved. For details about this, see {ref}`ref-cluster-manager-architecture-connector-ingress`. + +Once the Cluster Manager receives a join request, it tries to match the cluster name in the payload to an entry in its `remote_cluster_tokens` table. If it finds a match, it uses the corresponding token secret stored in that table to verify the join request HMAC signature. The validity of the remote cluster join token is also checked against its expiry date. + +If a valid match is found, the matched token is removed and the cluster is registered with the following information: + +- `name` - extracted from the token +- `cluster_certificate` - received from the join request from the registering cluster. This is the MicroCloud cluster certificate for establishing mutual TLS authentication with the Cluster Manager. + +A corresponding entry in the remote_clusters table is created, adding the following information: + +- `status` - `ACTIVE` + +Once a cluster has been successfully registered, Cluster Manager begins storing status update data sent by the cluster. + +(ref-cluster-manager-architecture-management-ui)= +### UI + +The Management API handles serving static assets for the UI. Users access information about clusters through the UI. Through it, users can create remote cluster join tokens and view information about existing tokens. The UI also serves warnings and metric insights on a high level. + +(ref-cluster-manager-architecture-connector)= +## Cluster Connector deployment + +The Cluster Connector deployment handles operations between MicroCloud clusters and the Cluster Manager. + +(ref-cluster-manager-architecture-connector-ingress)= +### Cluster Connector deployment ingress + +We expect each cluster to be able to reach the Cluster Manager. However, we do not expect the Manager to be able to reach each remote cluster directly. This is because clusters might not be exposed on an internet-facing IP, or they might run behind a firewall or NAT. Therefore, operations consist of ingress traffic only. + +(ref-cluster-manager-architecture-connector-mtls)= +### mTLS authentication + +During the initial join request of a cluster, each cluster presents a dedicated certificate to the Cluster Manager. The Manager uses this certificate to authenticate all subsequent requests from the cluster, using mTLS. For efficiency considerations, these certificates are cached after the first authenticated request. + +Due to the mTLS requirement, the TCP load balancer passes through TLS traffic and the Cluster Connector terminates TLS itself. + +(ref-cluster-manager-architecture-connector-heartbeats)= +### Heartbeats + +The `db-leader` of each connected MicroCloud cluster sends periodic heartbeats to the Cluster Manager, along with data about resource usage and availability. A heartbeat update includes the following information: + +- Cluster level details including: + - Number of cluster-wide instances and distribution of instance status (such as how many instances are stopped or started) + - Number of cluster members and distribution of member status (number of members online, number of members in error status, and so on) + - CPU, memory, and disk utilization for each cluster, as aggregated totals across all cluster members +- MicroCloud certificate within the request context for mTLS authentication. The certificate fingerprint is used to look up the cluster ID in a cache for updating cluster details. + +For each heartbeat it receives, the Cluster Manager performs the following tasks: + +- Match status update request by certificate fingerprint; check that the cluster exists and is marked as active. +- mTLS authentication check against the matched certificate +- Store and overwrite data in the `remote_cluster_details` table + +(ref-cluster-manager-architecture-rate-limited)= +## Rate limited endpoints + +To avoid overwhelming the Cluster Manager, all its endpoints are rate limited. When any endpoint receives a request from a cluster, the response from Cluster Manager includes a delay period (in seconds) that must pass before the next request to that endpoint. diff --git a/doc/reference/index.md b/doc/reference/index.md index ea7d42b5a..4b82c6895 100644 --- a/doc/reference/index.md +++ b/doc/reference/index.md @@ -1,10 +1,30 @@ +--- +myst: + html_meta: + description: Reference guides for MicroCloud such as deployment requirements, releases and snaps details, and the architecture of the MicroCloud Cluster Manager. +--- + (reference)= # Reference The reference material in this section provides technical information about MicroCloud. +(ref-microcloud)= +## MicroCloud + +Find out about requirements for a MicroCloud deployment, as well as information about its release cycles, release types, and snaps. + ```{toctree} :maxdepth: 1 - MicroCloud requirements /reference/releases-snaps +``` + +## MicroCloud Cluster Manager + +Learn about the MicroCloud Cluster Manager's architecture and how it connects multiple clusters to a centralized UI tool. + +```{toctree} +:maxdepth: 1 +Cluster Manager architecture +```