Skip to main content

2. Connect a second control plane

This section adds a second kind cluster to the Hub you installed in part one, installs hub-connector in it, and registers it with your running Hub.

Create the second cluster​

Create a second kind cluster. kind attaches it to the same kind Docker network as hub-quickstart, so the two clusters' nodes can reach each other.

kind create cluster --name hub-quickstart-extra

Route the Hub address to the Hub cluster​

important

A production install gives the Hub a hostname every cluster resolves. This step exists only because both clusters run on your laptop.

The connector reaches the Hub at the same HTTPS address your browser uses, https://hub.127.0.0.1.nip.io:8443, because the Hub's tokens name that address as their issuer. Inside the second cluster, that name resolves to the Pod itself. Point it at the Hub cluster instead.

First, create a Service in the second cluster that forwards port 8443 to the Hub node's gateway port 30443 over the kind network:

HUB_NODE_IP=$(docker inspect hub-quickstart-control-plane \
--format '{{.NetworkSettings.Networks.kind.IPAddress}}')

kubectl --context kind-hub-quickstart-extra create namespace hub

kubectl --context kind-hub-quickstart-extra -n hub apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: hub-gateway
spec:
ports:
- name: https
port: 8443
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: hub-gateway
labels:
kubernetes.io/service-name: hub-gateway
addressType: IPv4
ports:
- name: https
port: 30443
endpoints:
- addresses:
- "$HUB_NODE_IP"
EOF

Next, add a CoreDNS rule that resolves the Hub hostname to that Service. This replaces kind's default CoreDNS configuration with the same configuration plus one rewrite line:

kubectl --context kind-hub-quickstart-extra -n kube-system replace -f - <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
rewrite name exact hub.127.0.0.1.nip.io hub-gateway.hub.svc.cluster.local answer auto
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30 {
disable success cluster.local
disable denial cluster.local
}
loop
reload
loadbalance
}
EOF

kubectl --context kind-hub-quickstart-extra -n kube-system rollout restart deployment coredns
kubectl --context kind-hub-quickstart-extra -n kube-system rollout status deployment coredns

Trust the demo certificate​

The connector only talks to the Hub over HTTPS and verifies its certificate. Copy the demo gateway's self-signed certificate into the second cluster:

kubectl --context kind-hub-quickstart -n hub get secret hub-demo-gateway-tls \
-o jsonpath='{.data.tls\.crt}' | base64 --decode > hub-ca.crt

kubectl --context kind-hub-quickstart-extra -n hub create configmap hub-ca \
--from-file=ca.crt=hub-ca.crt

Only the certificate leaves the Hub cluster. Its private key stays in the Secret.

Create a registration token​

Every control plane gets its own registration token. The connector presents that token the first time it contacts the Hub API, which tells the Hub which control plane the connector speaks for.

  1. Open the control planes page and sign in as admin / admin.
  2. Register a ControlPlane with the name extra in the default realm.
  3. Copy the registration token. The Hub displays it once.

The Register a Control Plane dialog with realm and name fields The Register a Control Plane dialog with realm and name fields

  1. Save it for the next step:
export HUB_EXTRA_CTP_TOKEN=<paste-registration-token-here>

Install hub-connector​

The connector dials the Hub at its public address. extraVolumes and extraEnv mount the certificate you copied and point the connector's trust store at it.

Store the registration token and write the connector values:

kubectl --context kind-hub-quickstart-extra -n hub create secret generic hub-connector-credentials \
--from-literal=registrationToken="$HUB_EXTRA_CTP_TOKEN"

cat > hub-connector-values.yaml <<'EOF'
connector:
hub:
url: https://hub.127.0.0.1.nip.io:8443
extraVolumes:
- name: hub-ca
configMap:
name: hub-ca
extraVolumeMounts:
- name: hub-ca
mountPath: /etc/hub-ca
readOnly: true
extraEnv:
- name: SSL_CERT_FILE
value: /etc/hub-ca/ca.crt
EOF

Install the connector:

helm upgrade --install hub-connector oci://xpkg.upbound.io/upbound/hub-connector \
--kube-context kind-hub-quickstart-extra \
--version 1.2.0 \
--namespace hub \
--values hub-connector-values.yaml \
--wait --timeout 3m

Refresh the Hub and confirm the second cluster is online.

Navigate to the resources page. You can filter by control plane to list only the resources in your default or extra control plane.

In the control planes page lists extra next to default. It shows Pending until the connector registers, then Ready.

The control planes page listing default as Ready and extra as Pending The control planes page listing default as Ready and extra as Pending

Select extra to see its resource counts and details:

The extra control plane detail panel with claim, composite, and managed resource counts The extra control plane detail panel with claim, composite, and managed resource counts

tip

If extra doesn't turn Ready, read the connector's registration log:

kubectl --context kind-hub-quickstart-extra -n hub logs deployment/hub-connector -c resolve-identity

x509: certificate signed by unknown authority means the hub-ca ConfigMap or the connector values are missing. A timeout or no such host means the CoreDNS rule or the hub-gateway Service isn't in place. If you restarted Docker, the Hub node's IP address can change. Rerun the Service step with a fresh HUB_NODE_IP.

Next steps​

If you're stopping here, follow Clean up to delete what you created.

  • Builders workshop for real cloud resources.
  • Explore the Hub for the centralized management installation.