-
Notifications
You must be signed in to change notification settings - Fork 12
Add multi-cluster utilities & Readme #457
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
bruelea
merged 9 commits into
netbox-community:test/multicluster-runtime
from
gdask:test/multicluster-runtime
Nov 4, 2025
Merged
Changes from 2 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
6576b23
Add multi-cluster utilities & Readme
gdask e888b40
Add copy info on script
gdask d181edb
Apply suggestions from code review
gdask 763d820
Add support for cluster deployment
gdask 2866a90
fix cluster names in readme
gdask a1d01d8
Add comment on copied file from multicluster runtime project
gdask 568fade
adding new lines at the end of files
gdask 163cfbb
fix spelling mistakes
gdask d3f7f9b
fix yaml lint error
gdask File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # Multicluster Configuration | ||
| The create-kubeconfig-secret.sh is copied from [multicluster-runtime project](https://github.com/kubernetes-sigs/multicluster-runtime/tree/main), especially, from the `Kubeconfig Provider Example`. | ||
|
|
||
| This Readme cover only what is relevant for setting up a `Management cluster` which runs the controller, and `Resource Clusters` which host the Netbox Operator Resources. For more information over the scripts, and how a multicluster setup could be setup with Kubeconfig provider please read [this](https://github.com/kubernetes-sigs/multicluster-runtime/blob/main/examples/kubeconfig/README.md) | ||
|
|
||
| ## 1. Create Management Cluster | ||
|
|
||
| Follow the guide in project's root folder. | ||
| If all the prerequisites are in place, then `make create-kind` creates the cluster which is going to be used for the all the controller's depedencies (netbox backend, databases etc). | ||
| This cluster also contains RBAC for handling the netbox relates CRs, but those RBACs should not be necessary for our example. | ||
bruelea marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## 2. Create Resource Clusters | ||
|
|
||
| Create your Resouce Clusters & Provision Netbox-Operator Custom Resource Definitions. For each resource cluster you want to create, execute: | ||
|
|
||
| - `kind create cluster --name <res-cluster-name>` | ||
| - `make install` | ||
|
|
||
| Sidenote: kind create command changes the default context in which the kubectl commands point to. When you are done from this step, the kubectl context will be set at the last cluster you created. | ||
|
|
||
| ## 3. Establish cross-cluster access | ||
|
|
||
| Set kubectl context back to `management cluster` | ||
| `kubectl config use-context kind-kind` | ||
|
|
||
| Execute RBAC scripts for kubeconfig provider, towards each cluster you created in the previous step. | ||
| - `./create-kubeconfig-secret.sh -c <res-cluster-name>` | ||
gdask marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| For each cluster that gets configures as a 'Resource' cluster, a secret is populated in the 'Management' cluster. | ||
gdask marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Make sure that the appropriate secrets are populated in the kind-kind cluster, with names `kind-<resource cluster name>`. | ||
gdask marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## Limitations | ||
| Currently the controller could be executed locally, not from the managment cluster. | ||
gdask marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| In order to make it executable from the management cluster: | ||
| - ClusterRole `manager-role` needs to allow reading, listing and watching secrets. | ||
| - The secrets generated from the `create-kubeconfig-secret` needs to point to the correct ip. currently it's pointing a localhost ip. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,314 @@ | ||
| # File copied from https://github.com/kubernetes-sigs/multicluster-runtime/blob/main/examples/kubeconfig/README.md | ||
| #!/bin/bash | ||
|
|
||
| # Script to create a kubeconfig secret for the pod lister controller | ||
|
|
||
| set -e | ||
|
|
||
| # Default values | ||
| NAMESPACE="default" | ||
| SERVICE_ACCOUNT="multicluster-kubeconfig-provider" | ||
| KUBECONFIG_CONTEXT="" | ||
| SECRET_NAME="" | ||
| ROLE_TYPE="clusterrole" | ||
| RULES_FILE="" | ||
| CREATE_RBAC="true" | ||
|
|
||
| # Check for yq | ||
| if ! command -v yq &>/dev/null; then | ||
| echo "ERROR: 'yq' is required but not installed. Please install yq (https://mikefarah.gitbook.io/yq/) and try again." | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Function to display usage information | ||
| function show_help { | ||
| echo "Usage: $0 [options]" | ||
| echo " -c, --context CONTEXT Kubeconfig context to use (required)" | ||
| echo " --name NAME Name for the secret (defaults to context name)" | ||
| echo " -n, --namespace NS Namespace to create the secret in (default: ${NAMESPACE})" | ||
| echo " -a, --service-account SA Service account name to use (default: ${SERVICE_ACCOUNT})" | ||
| echo " -t, --role-type TYPE Create Role or ClusterRole (role|clusterrole) (default: clusterrole)" | ||
| echo " -r, --rules-file FILE Path to rules file (default: rules.yaml in script directory)" | ||
| echo " --skip-create-rbac Skip creating RBAC resources (Role/ClusterRole and bindings)" | ||
| echo " -h, --help Show this help message" | ||
| echo "" | ||
| echo "Examples:" | ||
| echo " $0 -c prod-cluster" | ||
| echo " $0 -c prod-cluster -t role -r ./custom-rules.yaml" | ||
| echo " $0 -c prod-cluster -t clusterrole" | ||
| echo " $0 -c prod-cluster --skip-create-rbac" | ||
| } | ||
|
|
||
| # Function to create Role or ClusterRole | ||
| function create_rbac { | ||
| local role_type="$1" | ||
| local rules_file="$2" | ||
| local role_name="$3" | ||
| local namespace="$4" | ||
|
|
||
| if [ ! -f "$rules_file" ]; then | ||
| echo "ERROR: Rules file not found: $rules_file" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "Creating ${role_type} '${role_name}'..." | ||
|
|
||
| if [ "$role_type" = "role" ]; then | ||
| # Create Role | ||
| ROLE_YAML=$(cat <<EOF | ||
| apiVersion: rbac.authorization.k8s.io/v1 | ||
| kind: Role | ||
| metadata: | ||
| name: ${role_name} | ||
| namespace: ${namespace} | ||
| rules: | ||
| $(yq '.rules' "$rules_file") | ||
| EOF | ||
| ) | ||
|
|
||
| echo "$ROLE_YAML" | kubectl --context=${KUBECONFIG_CONTEXT} apply -f - | ||
|
|
||
| # Create RoleBinding | ||
| ROLEBINDING_YAML=$(cat <<EOF | ||
| apiVersion: rbac.authorization.k8s.io/v1 | ||
| kind: RoleBinding | ||
| metadata: | ||
| name: ${role_name}-binding | ||
| namespace: ${namespace} | ||
| subjects: | ||
| - kind: ServiceAccount | ||
| name: ${SERVICE_ACCOUNT} | ||
| namespace: ${namespace} | ||
| roleRef: | ||
| kind: Role | ||
| name: ${role_name} | ||
| apiGroup: rbac.authorization.k8s.io | ||
| EOF | ||
| ) | ||
|
|
||
| echo "$ROLEBINDING_YAML" | kubectl --context=${KUBECONFIG_CONTEXT} apply -f - | ||
|
|
||
| else | ||
| # Create ClusterRole | ||
| CLUSTERROLE_YAML=$(cat <<EOF | ||
| apiVersion: rbac.authorization.k8s.io/v1 | ||
| kind: ClusterRole | ||
| metadata: | ||
| name: ${role_name} | ||
| rules: | ||
| $(yq '.rules' "$rules_file") | ||
| EOF | ||
| ) | ||
|
|
||
| echo "$CLUSTERROLE_YAML" | kubectl --context=${KUBECONFIG_CONTEXT} apply -f - | ||
|
|
||
| # Create ClusterRoleBinding | ||
| CLUSTERROLEBINDING_YAML=$(cat <<EOF | ||
| apiVersion: rbac.authorization.k8s.io/v1 | ||
| kind: ClusterRoleBinding | ||
| metadata: | ||
| name: ${role_name}-binding | ||
| subjects: | ||
| - kind: ServiceAccount | ||
| name: ${SERVICE_ACCOUNT} | ||
| namespace: ${namespace} | ||
| roleRef: | ||
| kind: ClusterRole | ||
| name: ${role_name} | ||
| apiGroup: rbac.authorization.k8s.io | ||
| EOF | ||
| ) | ||
|
|
||
| echo "$CLUSTERROLEBINDING_YAML" | kubectl --context=${KUBECONFIG_CONTEXT} apply -f - | ||
| fi | ||
|
|
||
| echo "$(tr '[:lower:]' '[:upper:]' <<< ${role_type:0:1})${role_type:1} '${role_name}' created successfully!" | ||
| } | ||
|
|
||
| # Function to create service account if it doesn't exist | ||
| function ensure_service_account { | ||
| local namespace="$1" | ||
| local service_account="$2" | ||
|
|
||
| echo "Checking if service account '${service_account}' exists in namespace '${namespace}'..." | ||
|
|
||
| # Check if service account exists | ||
| if ! kubectl --context=${KUBECONFIG_CONTEXT} get serviceaccount ${service_account} -n ${namespace} &>/dev/null; then | ||
| echo "Service account '${service_account}' not found in namespace '${namespace}'. Creating..." | ||
|
|
||
| # Create the service account | ||
| SERVICE_ACCOUNT_YAML=$(cat <<EOF | ||
| apiVersion: v1 | ||
| kind: ServiceAccount | ||
| metadata: | ||
| name: ${service_account} | ||
| namespace: ${namespace} | ||
| EOF | ||
| ) | ||
|
|
||
| echo "$SERVICE_ACCOUNT_YAML" | kubectl --context=${KUBECONFIG_CONTEXT} apply -f - | ||
| echo "Service account '${service_account}' created successfully in namespace '${namespace}'" | ||
| else | ||
| echo "Service account '${service_account}' already exists in namespace '${namespace}'" | ||
| fi | ||
| } | ||
|
|
||
| # Parse command line options | ||
| while [[ $# -gt 0 ]]; do | ||
| key="$1" | ||
| case $key in | ||
| --name) | ||
| SECRET_NAME="$2" | ||
| shift 2 | ||
| ;; | ||
| -n|--namespace) | ||
| NAMESPACE="$2" | ||
| shift 2 | ||
| ;; | ||
| -c|--context) | ||
| KUBECONFIG_CONTEXT="$2" | ||
| shift 2 | ||
| ;; | ||
| -a|--service-account) | ||
| SERVICE_ACCOUNT="$2" | ||
| shift 2 | ||
| ;; | ||
| -t|--role-type) | ||
| ROLE_TYPE="$2" | ||
| shift 2 | ||
| ;; | ||
| -r|--rules-file) | ||
| RULES_FILE="$2" | ||
| shift 2 | ||
| ;; | ||
| --skip-create-rbac) | ||
| CREATE_RBAC="false" | ||
| shift | ||
| ;; | ||
| -h|--help) | ||
| show_help | ||
| exit 0 | ||
| ;; | ||
| *) | ||
| echo "Unknown option: $1" | ||
| show_help | ||
| exit 1 | ||
| ;; | ||
| esac | ||
| done | ||
|
|
||
| # Validate required arguments | ||
| if [ -z "$KUBECONFIG_CONTEXT" ]; then | ||
| echo "ERROR: Kubeconfig context is required (-c, --context)" | ||
| show_help | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Validate role type if specified | ||
| if [ -n "$ROLE_TYPE" ] && [ "$ROLE_TYPE" != "role" ] && [ "$ROLE_TYPE" != "clusterrole" ]; then | ||
| echo "ERROR: Invalid role type '$ROLE_TYPE'. Must be 'role' or 'clusterrole'" | ||
| show_help | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Set default rules file if not specified | ||
| if [ -z "$RULES_FILE" ]; then | ||
| RULES_FILE="$(dirname "$0")/rules.yaml" | ||
| fi | ||
|
|
||
| # Set secret name to context if not specified | ||
| if [ -z "$SECRET_NAME" ]; then | ||
| SECRET_NAME="$KUBECONFIG_CONTEXT" | ||
| fi | ||
|
|
||
| # Create RBAC resources by default (unless --no-create-role is specified) | ||
| if [ "$CREATE_RBAC" = "true" ]; then | ||
| create_rbac "$ROLE_TYPE" "$RULES_FILE" "$SECRET_NAME" "$NAMESPACE" | ||
| fi | ||
|
|
||
| # Get the cluster CA certificate from the remote cluster | ||
| CLUSTER_CA=$(kubectl --context=${KUBECONFIG_CONTEXT} config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.certificate-authority-data}') | ||
| if [ -z "$CLUSTER_CA" ]; then | ||
| echo "ERROR: Could not get cluster CA certificate" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Get the cluster server URL from the remote cluster | ||
| CLUSTER_SERVER=$(kubectl --context=${KUBECONFIG_CONTEXT} config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.server}') | ||
| if [ -z "$CLUSTER_SERVER" ]; then | ||
| echo "ERROR: Could not get cluster server URL" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Ensure service account exists | ||
| ensure_service_account "$NAMESPACE" "$SERVICE_ACCOUNT" | ||
|
|
||
| # Get the service account token from the remote cluster | ||
| SA_TOKEN=$(kubectl --context=${KUBECONFIG_CONTEXT} -n ${NAMESPACE} create token ${SERVICE_ACCOUNT} --duration=8760h) | ||
| if [ -z "$SA_TOKEN" ]; then | ||
| echo "ERROR: Could not create service account token" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Create a new kubeconfig using the service account token | ||
| NEW_KUBECONFIG=$(cat <<EOF | ||
| apiVersion: v1 | ||
| kind: Config | ||
| clusters: | ||
| - name: ${SECRET_NAME} | ||
| cluster: | ||
| server: ${CLUSTER_SERVER} | ||
| certificate-authority-data: ${CLUSTER_CA} | ||
| contexts: | ||
| - name: ${SECRET_NAME} | ||
| context: | ||
| cluster: ${SECRET_NAME} | ||
| user: ${SERVICE_ACCOUNT} | ||
| current-context: ${SECRET_NAME} | ||
| users: | ||
| - name: ${SERVICE_ACCOUNT} | ||
| user: | ||
| token: ${SA_TOKEN} | ||
| EOF | ||
| ) | ||
|
|
||
| # Save kubeconfig temporarily for testing | ||
| TEMP_KUBECONFIG=$(mktemp) | ||
| echo "$NEW_KUBECONFIG" > "$TEMP_KUBECONFIG" | ||
|
|
||
| # Verify the kubeconfig works | ||
| echo "Verifying kubeconfig..." | ||
| if ! kubectl --kubeconfig="$TEMP_KUBECONFIG" version &>/dev/null; then | ||
| rm "$TEMP_KUBECONFIG" | ||
| echo "ERROR: Failed to verify kubeconfig - unable to connect to cluster." | ||
| echo "- Ensure that the service account '${NAMESPACE}/${SERVICE_ACCOUNT}' on cluster '${KUBECONFIG_CONTEXT}' exists and is properly configured." | ||
| echo "- You may specify a namespace using the -n flag." | ||
| echo "- You may specify a service account using the -a flag." | ||
| exit 1 | ||
| fi | ||
| echo "Kubeconfig verified successfully!" | ||
|
|
||
| # Encode the verified kubeconfig | ||
| KUBECONFIG_B64=$(cat "$TEMP_KUBECONFIG" | base64 -w0) | ||
| rm "$TEMP_KUBECONFIG" | ||
|
|
||
| # Generate and apply the secret | ||
| SECRET_YAML=$(cat <<EOF | ||
| apiVersion: v1 | ||
| kind: Secret | ||
| metadata: | ||
| name: ${SECRET_NAME} | ||
| namespace: ${NAMESPACE} | ||
| labels: | ||
| sigs.k8s.io/multicluster-runtime-kubeconfig: "true" | ||
| type: Opaque | ||
| data: | ||
| kubeconfig: ${KUBECONFIG_B64} | ||
| EOF | ||
| ) | ||
|
|
||
| echo "Creating kubeconfig secret..." | ||
| echo "$SECRET_YAML" | kubectl apply -f - | ||
|
|
||
| echo "Secret '${SECRET_NAME}' created in namespace '${NAMESPACE}'" | ||
| echo "The operator should now be able to discover and connect to this cluster" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.