iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🌊

Exploring Kubernetes Multi-Cluster Management with Karmada

に公開

What is Karmada

Karmada is an open-source project for achieving k8s multi-cluster management. It currently has about 4k GitHub stars and is a CNCF Incubating project.

https://karmada.io/

Originally developed by HUAWEI Cloud, the cloud division of the well-known smartphone manufacturer Huawei, it was donated to the CNCF in September 2021. An article was also posted on the CNCF blog in December 2023.

https://www.cncf.io/blog/2023/12/12/karmada-brings-kubernetes-multi-cloud-capabilities-to-cncf-incubator/

Karmada's architecture achieves multi-cluster management by configuring a Karmada control plane to manage multiple clusters, deploying resources to those managed clusters, and performing pod scheduling.


Karmada architecture. Quoted from https://karmada.io/docs/core-concepts/architecture

Karmada's main features are summarized in Key Features.

  • Multi-cloud and Multi-cluster Management
    • Communication from the Karmada control plane to managed clusters uses either kube-apiserver or a Karmada agent, so it is not restricted to specific cloud providers. Therefore, it is possible to collectively manage AWS, Azure, and on-premises k8s clusters.
  • Deployment to Multi-clusters
    • Resources can be deployed to managed clusters simultaneously, and properties can be dynamically changed according to the destination cluster using a CRD called Override Policy.
  • Advanced Scheduling and Failover across Clusters
    • By applying Affinity, Splitting/Rebalancing, etc., between clusters, pod placement strategies in regular k8s clusters and failover during node failures can be realized at the cluster level.
  • Cross-cluster Service Discovery and AutoScaling
    • By using external features that enable cross-cluster networking, such as Submariner, cross-cluster service discovery and auto-scaling can be achieved.

Note that while Kubernetes Federation V2 (kubefed) existed for multi-cluster management in k8s, Karmada seems to be developed as its successor.

Notice: this project is developed in continuation of Kubernetes Federation v1 and v2. Some basic concepts are inherited from these two versions.

Setting up Karmada

Let's actually prepare a Karmada control plane and managed clusters to experience what kind of multi-cluster management is possible.

Environment

To verify the multi-cluster management behavior with Karmada, we will prepare three k8s clusters: one for the Karmada control plane and the remaining two as clusters managed by Karmada. Each cluster will have a simple configuration of one control-plane node and one worker node.


Configuration of verification clusters

Installing Karmada

As described in the installation documentation, Karmada can be installed using various methods, including setup scripts, Helm, and an operator. Here, we will build the environment by installing the Karmada operator and then creating a Karmada instance via a CRD.

Installing the Operator

Operator Installation Steps
The operator can be installed using Helm, but since it is not registered in a chart repository, the procedure involves cloning the git repository containing the chart and installing it from the local path. First, perform the clone.

git clone https://github.com/karmada-io/karmada.git
cd karmada

You can then install it using helm install. However, as I encountered errors that prevented a successful installation with the current latest version, I will use version v1.10.0 this time. To change the operator version, modify the image tag in the operator's chart values as follows:

charts/karmada-operator/values.yaml
operator:
  image:
    registry: docker.io
      repository: karmada/karmada-operator
-      tag: latest
+      tag: v1.10.0

Run helm install:

helm install karmada-operator -n karmada-system  --create-namespace --dependency-update ./charts/karmada-operator --debug

This creates an operator pod in the karmada-system namespace. Next, create a secret containing the kubeconfig content to be used during installation.

kubectl create secret generic my-kubeconfig --from-file=$HOME/.kube/config -n karmada-system

After the operator starts, you can build the necessary components for the Karmada control plane by creating a CRD called a Karmada Instance.

kubectl create namespace test
kubectl apply -f - <<EOF
apiVersion: operator.karmada.io/v1alpha1
kind: Karmada
metadata:
  name: karmada-demo
  namespace: test
EOF

However, to align each Karmada component with v1.10.0, we will rewrite the image tags for each component to v1.10.0 based on the sample Karmada Instance manifest at https://github.com/karmada-io/karmada/blob/master/operator/config/samples/karmada.yaml.

karmada.yml
apiVersion: operator.karmada.io/v1alpha1
kind: Karmada
metadata:
  name: karmada
  namespace: karmada
spec:
  components:
    etcd:
      local:
        imageRepository: registry.k8s.io/etcd
        imageTag: 3.5.13-0
        replicas: 1
        volumeData:
          # hostPath:
          #   type: DirectoryOrCreate
          #   path: /var/lib/karmada/etcd/karmada-demo
          volumeClaim:
            metadata:
              name: etcd-data
            spec:
              accessModes:
                - ReadWriteOnce
              resources:
                requests:
                  storage: 3Gi
    karmadaAPIServer:
      imageRepository: registry.k8s.io/kube-apiserver
      imageTag: v1.28.9
      replicas: 1
      serviceType: NodePort
      serviceSubnet: 10.96.0.0/12
    karmadaAggregatedAPIServer:
      imageRepository: docker.io/karmada/karmada-aggregated-apiserver
      imageTag: v1.10.0
      replicas: 1
    karmadaControllerManager:
      imageRepository: docker.io/karmada/karmada-controller-manager
      imageTag: v1.10.0
      replicas: 1
    karmadaScheduler:
      imageRepository: docker.io/karmada/karmada-scheduler
      imageTag: v1.10.0
      replicas: 1
    karmadaWebhook:
      imageRepository: docker.io/karmada/karmada-webhook
      imageTag: v1.10.0
      replicas: 1
    kubeControllerManager:
      imageRepository: registry.k8s.io/kube-controller-manager
      imageTag: v1.28.9
      replicas: 1
    karmadaMetricsAdapter:
      imageRepository: docker.io/karmada/karmada-metrics-adapter
      imageTag: v1.10.0
      replicas: 2
    # karmadaSearch: # the component `Karmadasearch` is not installed by default, if you need to install it, uncomment it and note the formatting
      # imageRepository: docker.io/karmada/karmada-search
      # imageTag: v1.10.0
      # replicas: 1
    # karmadaDescheduler: # the component `KarmadaDescheduler` is not installed by default, if you need to install it, uncomment it and note the formatting
      # imageRepository: docker.io/karmada/karmada-descheduler
      # imageTag: v1.10.0
      # replicas: 1
  hostCluster:
    networking:
      dnsDomain: cluster.local

Deploy this.

kubectl create ns karmada
kubectl apply -f karmada.yml

Once the deployment is successfully completed, the pods constituting the Karmada control plane will start.

$ kubectl get pod
NAME                                               READY   STATUS    RESTARTS   AGE
karmada-aggregated-apiserver-58b4558c96-ld8dq      1/1     Running   0          2m54s
karmada-apiserver-86648f5476-g6kn9                 1/1     Running   0          3m5s
karmada-controller-manager-754496f5bf-xnr68        1/1     Running   0          2m50s
karmada-etcd-0                                     1/1     Running   0          3m6s
karmada-kube-controller-manager-74f76db4c8-9fdg5   1/1     Running   0          2m50s
karmada-metrics-adapter-69cc7c74f5-mpml9           1/1     Running   0          2m50s
karmada-metrics-adapter-69cc7c74f5-zt7st           1/1     Running   0          2m50s
karmada-scheduler-76bb67cb4d-ptvk7                 1/1     Running   0          2m50s
karmada-webhook-7f78445f89-s42l6                   1/1     Running   0          2m50s

Also, since operations related to Karmada are basically performed using the dedicated Karmada CLI (karmadactl), install it as well. There are several installation methods, but if you are using krew, it is easiest to install as follows:

kubectl krew install karmada

About karmada-apiserver

Once the construction of the Karmada control plane is complete, a secret named karmada-admin-config is created in the specified namespace. Its content is a configuration file for communicating with the karmada-apiserver. It is basically the same as a kubeconfig, but there are differences such as the server pointing to the karmada-apiserver (the nodePort of the karmada-apiserver in the example below) and the certificate-related information being the authentication credentials from when Karmada was built.

$ k get secrets karmada-admin-config -o yaml | yq -r ".data.kubeconfig"  | base64 -d
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data:
    ...
    server: https://10.0.0.40:30238
  name: karmada-apiserver

contexts:
- context:
    cluster: karmada-apiserver
    user: karmada-admin
  name: karmada-admin@karmada-apiserver
current-context: karmada-admin@karmada-apiserver
kind: Config
preferences: {}
users:
- name: karmada-admin
  user:
    client-certificate-data:
    ...
    client-key-data:
    ...

In the documentation, where --kubeconfig=<karmada kubeconfig> is written, it basically refers to this config for the karmada-apiserver. Since there are many situations where you will switch between the default kubeconfig and the Karmada kubeconfig in subsequent tasks, it is convenient to copy the content from the secret and save it to a file on the host side to facilitate access.

Let's briefly touch upon the karmada-apiserver here. In the Karmada control plane, the architecture is designed so that the karmada-apiserver handles Karmada-related APIs, just like the kube-apiserver in a regular k8s cluster. Furthermore, it also maintains compatibility with the regular Kubernetes API, as described below:

Karmada API server directly uses the implementation of kube-apiserver from Kubernetes, which is the reason why Karmada is naturally compatible with Kubernetes API. That makes integration with the Kubernetes ecosystem very simple for Karmada, such as allowing users to use kubectl to operate Karmada, integrating with ArgoCD, integrating with Flux and so on.


karmada-apiserver plays the role of kube-apiserver in a regular k8s cluster. Quoted from https://karmada.io/docs/core-concepts/components#karmada-apiserver

When performing operations such as deployment to clusters managed by Karmada using the Karmada API, it is necessary to communicate with the karmada-apiserver, which is why a kubeconfig for the karmada-apiserver is required as mentioned above. Also, while the architecture diagram shows data being saved to etcd, this is saved to a pod created within the Karmada control plane, which is a separate storage location from the etcd that exists by default in the k8s cluster. Therefore, care should be taken as the pods displayed will differ when switching contexts between the kubeconfig and the Karmada kubeconfig.

Registering Clusters

Now that the Karmada control plane construction is complete, the next step is to register the clusters to be managed. Two Cluster Modes are provided for cluster registration and management: Push mode and Pull mode. https://karmada.io/docs/userguide/clustermanager/cluster-registration

  • Push mode
    • The Karmada control plane communicates directly with the kube-apiserver on the managed k8s cluster.
  • Pull mode
    • A karmada-agent is installed on the managed k8s cluster, and the karmada-agent communicates with the Karmada control plane to perform registration and management.

Cluster registration is possible using either method, but the Pull mode requires additional configuration to enable communication between the Karmada control plane and the external cluster. Since this involves more steps and is somewhat tedious, we will register the clusters using the Push mode here.
Cluster registration can be performed using the karmada join command as described in the documentation, but a kubeconfig for accessing the kube-apiserver of the target cluster is required as <member1 kubeconfig>, so copy the content to your working environment.

In this case, we use the control-plane node of the Karmada cluster as the working environment and save the Karmada kubeconfig and the target cluster's kubeconfig at the following paths on the node:

  • Karmada kubeconfig: ~/.kube/karmada.config
  • Target cluster's kubeconfig: ~/.kube/sub1.config

The command format for adding a cluster to the management targets is as follows:

kubectl karmada join <cluster_name> --kubeconfig=<karmada kubeconfig> --cluster-kubeconfig=<member1 kubeconfig>

This time, we will register the target cluster with the name member1. If joined successfully is displayed after running the command, registration was successful.

$ kubectl karmada join member1 --kubeconfig=/home/ubuntu/.kube/karmada.config --cluster-kubeconfig=/home/ubuntu/.kube/sub1.config  -v 5
I0615 06:13:22.627225  312437 join.go:158] Joining cluster. cluster name: member1
I0615 06:13:22.627268  312437 join.go:159] Joining cluster. cluster namespace: karmada-cluster
I0615 06:13:22.628820  312437 join.go:183] Joining cluster config. endpoint: https://10.0.0.30:6443
cluster(member1) is joined successfully

When performing operations related to the Karmada API, you generally need to specify the Karmada config with --kubeconfig. Since specifying it every time is cumbersome, it's easier to set it in export KUBECONFIG (though you could also add it to a context and switch with kubectx).

export KUBECONFIG=/home/ubuntu/.kube/karmada.config

In the same way, copy the kubeconfig of another cluster as ~/.kube/sub2.config to the node and register it as member2.

$ kubectl karmada join member2 --kubeconfig=/home/ubuntu/.kube/karmada.config --cluster-kubeconfig=/home/ubuntu/.kube/sub2.config  -v 5

With this, two clusters have been added as managed targets. You can check the registration status of the clusters with kubectl get clusters.

$ k get clusters
NAME      VERSION   MODE   READY   AGE
member1   v1.29.6   Push   True    2d6h
member2   v1.29.6   Push   True    2d6h

Deploying Resources to Multiple Clusters

When deploying resources to multiple clusters in Karmada, it is achieved using a CRD called PropagationPolicy. To summarize the procedure, it consists of the following simple steps:

  1. Define the resource you want to deploy and create it.
  2. Specify the above resource in a PropagationPolicy and deploy it to the target clusters.

There is a simple tutorial in Resource Propagating, so let's try it out.

First, create a manifest for the nginx deployment, which is the resource to be deployed. There are no Karmada-specific descriptions for this, and it can be created as a regular k8s manifest.

nginx.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80

Deploy this to the Karmada control plane side.

$ export KUBECONFIG=/home/ubuntu/.kube/karmada.config
$ k apply -f nginx.yml

At this point, pods are not created in either the Karmada cluster or the member clusters. On the Karmada side, the deployment resource itself has been created, but the number of replicas is set to 0.

$ k get deployments.apps
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
nginx   0/2     0            0           95s

Next, create a PropagationPolicy CRD to deploy the above deployment to the member clusters.

  • Specify the resource to be deployed in resourceSelectors. Here, you specify the kind and name of the target resource.
  • In namespace, specify the namespace where the resource should be created in the target clusters.
  • The destination clusters are specified in placement. You can specify cluster names directly or use a label selector, similar to pod placement.
propagate.yml
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
  name: nginx # The default namespace is `default`.
  namespace: default
spec:
  resourceSelectors:
    - apiVersion: apps/v1
      kind: Deployment
      name: nginx # If no namespace is specified, the namespace is inherited from the parent object scope.
      namespace: default
  placement:
    clusterAffinity:
      clusterNames:
        - member1
        - member2

Deploy.

$ k apply -f propagate.yml
propagationpolicy.policy.karmada.io/nginx created

Resources deployed to managed clusters can be retrieved using kubectl karmada get [resource], just like regular kubectl. While this is how it should normally be retrieved, an error occurred when I tried it.

$ k karmada get pod
cluster(member2) is inaccessible, please check authorization or network
cluster(member1) is inaccessible, please check authorization or network

Karmada commands allow you to set the detail level using -v, just like kubectl. If an error occurs, it is recommended to specify -v 5 or higher, as there is too little information without it.
Below is the result of running with -v 10.

I0621 08:02:46.549252 2522618 round_trippers.go:553] GET https://10.0.0.40:30238/apis/cluster.karmada.io/v1alpha1/clusters/member1/proxy/api 403 Forbidden in 7 milliseconds
I0621 08:02:46.549351 2522618 round_trippers.go:570] HTTP Statistics: GetConnection 0 ms ServerProcessing 7 ms Duration 7 ms
I0621 08:02:46.549363 2522618 round_trippers.go:577] Response Headers:
I0621 08:02:46.549370 2522618 round_trippers.go:580]     Cache-Control: no-cache, private
I0621 08:02:46.549375 2522618 round_trippers.go:580]     Cache-Control: no-cache, private
I0621 08:02:46.549380 2522618 round_trippers.go:580]     Cache-Control: no-cache, private
I0621 08:02:46.549385 2522618 round_trippers.go:580]     Content-Type: application/json
I0621 08:02:46.549390 2522618 round_trippers.go:580]     Date: Fri, 21 Jun 2024 08:02:46 GMT
I0621 08:02:46.549448 2522618 round_trippers.go:580]     Content-Length: 344
I0621 08:02:46.549513 2522618 round_trippers.go:580]     Audit-Id: a2ef0b6e-420d-44dd-8d2b-63d349e3922d
I0621 08:02:46.549574 2522618 round_trippers.go:580]     Audit-Id: a2ef0b6e-420d-44dd-8d2b-63d349e3922d
I0621 08:02:46.549633 2522618 round_trippers.go:580]     Audit-Id: a2ef0b6e-420d-44dd-8d2b-63d349e3922d
I0621 08:02:46.549692 2522618 round_trippers.go:580]     X-Content-Type-Options: nosniff
I0621 08:02:46.549769 2522618 request.go:1212] Response Body: {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"users \"system:admin\" is forbidden: User \"system:serviceaccount:karmada-cluster:karmada-impersonator\" cannot impersonate resource \"users\" in API group \"\" at the cluster scope","reason":"Forbidden","details":{"name":"system:admin","kind":"users"},"code":403}

It seems that a 403 error occurs when retrieving information from member clusters, and as indicated in the Response Body, the system:serviceaccount:karmada-cluster:karmada-impersonator ServiceAccount is lacking permission for system:admin.
Checking the SA and ClusterRole on the member1 cluster side indeed confirms that the permissions are insufficient.

$ k get clusterrole karmada-impersonator -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    resourcetemplate.karmada.io/managed-annotations: resourcetemplate.karmada.io/managed-annotations,resourcetemplate.karmada.io/managed-labels,resourcetemplate.karmada.io/uid,work.karmada.io/name,work.karmada.io/namespace
    resourcetemplate.karmada.io/managed-labels: karmada.io/managed,work.karmada.io/permanent-id
    resourcetemplate.karmada.io/uid: ""
    work.karmada.io/name: karmada-impersonator-7cbb6bd5c9
    work.karmada.io/namespace: karmada-es-member1
  creationTimestamp: "2024-06-21T08:00:21Z"
  labels:
    karmada.io/managed: "true"
    work.karmada.io/permanent-id: ae904d66-7c0f-4347-ba4d-a65c26e25688
  name: karmada-impersonator
  resourceVersion: "2073"
  uid: d44d0d8e-a765-49b7-b7a9-b18032939361
rules:
- apiGroups:
  - ""
  resourceNames:
  - system:kube-controller-manager
  resources:
  - users
  verbs:
  - impersonate
- apiGroups:
  - ""
  resourceNames:
  - generic-garbage-collector
  - namespace-controller
  - resourcequota-controller
  resources:
  - serviceaccounts
  verbs:
  - impersonate
- apiGroups:
  - ""
  resourceNames:
  - system:masters
  resources:
  - groups
  verbs:
  - impersonate

While it's unclear why it's insufficient, since this is for verification, I will directly edit the ClusterRole to add the permission. However, because resources are managed by Karmada internally, even if you execute edit, the changes won't persist unless you remove the karmada.io/managed: "true" label.

$ k edit clusterrole karmada-impersonator

metadata:
  labels:
-    karmada.io/managed: "true"
    work.karmada.io/permanent-id: ae904d66-7c0f-4347-ba4d-a65c26e25688
  name: karmada-impersonator
  resourceVersion: "3496"
  uid: d44d0d8e-a765-49b7-b7a9-b18032939361
rules:
  - apiGroups:
    - ""
    resourceNames:
    - system:kube-controller-manager
+   - system:admin
    resources:
    - users
    verbs:
    - impersonate

By applying the above fix to both the member1 and member2 clusters, communication becomes possible, and you can see the pods deployed in each cluster. You can confirm that two pods each are deployed on the worker nodes of member1 and member2, as specified in the manifest's replica count.

$ k karmada get pod -o wide
NAME                     CLUSTER   READY   STATUS    RESTARTS   AGE    IP             NODE      NOMINATED NODE   READINESS GATES   ADOPTION
nginx-77d8468669-2d4tr   member1   1/1     Running   0          4d3h   10.244.1.122   k8s-w1    <none>           <none>            N
nginx-77d8468669-c2v8v   member1   1/1     Running   0          4d3h   10.244.1.121   k8s-w1    <none>           <none>            N
nginx-86dcfdf4c6-kzrd5   member2   1/1     Running   0          4d3h   10.244.1.8     sub1-w1   <none>           <none>            N
nginx-86dcfdf4c6-pdxb9   member2   1/1     Running   0          4d3h   10.244.1.7     sub1-w1   <none>           <none>            N

In this way, you can use PropagationPolicy to deploy resources collectively to managed clusters.

Testing Multi-cluster Failover

Failover in Karmada is a feature that migrates workloads, such as pods, to surviving clusters when one of the managed clusters goes down. It is easy to understand if you imagine it as a cluster-level extension of the behavior in a regular k8s cluster where pods are restarted on remaining nodes when a worker node fails.


Failover overview diagram. When Cluster member1 goes down, new pods start on member2 or member3 based on the PropagationPolicy settings. Quoted from Failover Overview.

Let's verify this feature as well.
First, we create a manifest to launch 10 nginx pods and a PropagationPolicy to deploy them to the member clusters.

failover.yml
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
  name: nginx # The default namespace is `default`.
  namespace: default
spec:
  resourceSelectors:
    - apiVersion: apps/v1
      kind: Deployment
      name: nginx # If no namespace is specified, the namespace is inherited from the parent object scope.
      namespace: default
  placement:
    replicaScheduling:
      replicaDivisionPreference: Weighted
      replicaSchedulingType: Divided
      weightPreference:
        staticWeightList:
          - targetCluster:
              clusterNames:
                - member1
            weight: 1
          - targetCluster:
              clusterNames:
                - member2
            weight: 1

The difference from the previous PropagationPolicy is that placement.replicaScheduling is configured to distribute pods equally across each cluster (for details, see Multiple strategies of replica Scheduling). By setting up replicaScheduling as shown above, pods are split and launched based on the weight of the clusters specified in targetCluster. In this case, since the weights for both clusters are set to 1, 5 pods will be launched in each cluster out of the 10 pods total.
When you actually deploy the PropagationPolicy, you can confirm that 5 pods are running in each of the member1 and member2 clusters.

$ k karmada get pod -o wide
NAME                     CLUSTER   READY   STATUS    RESTARTS   AGE   IP             NODE      NOMINATED NODE   READINESS GATES   ADOPTION
nginx-76749dc54c-846qb   member1   1/1     Running   0          35s   10.244.1.98    sub1-w1   <none>           <none>            N
nginx-76749dc54c-b4p9k   member1   1/1     Running   0          35s   10.244.1.99    sub1-w1   <none>           <none>            N
nginx-76749dc54c-hm9lj   member1   1/1     Running   0          35s   10.244.1.100   sub1-w1   <none>           <none>            N
nginx-76749dc54c-mmlpq   member1   1/1     Running   0          35s   10.244.1.97    sub1-w1   <none>           <none>            N
nginx-76749dc54c-tnj4k   member1   1/1     Running   0          35s   10.244.1.101   sub1-w1   <none>           <none>            N
nginx-76749dc54c-76n6w   member2   1/1     Running   0          35s   10.244.1.89    sub2-w1   <none>           <none>            N
nginx-76749dc54c-9d54j   member2   1/1     Running   0          35s   10.244.1.90    sub2-w1   <none>           <none>            N
nginx-76749dc54c-hm96h   member2   1/1     Running   0          35s   10.244.1.92    sub2-w1   <none>           <none>            N
nginx-76749dc54c-jl25p   member2   1/1     Running   0          35s   10.244.1.93    sub2-w1   <none>           <none>            N
nginx-76749dc54c-rqm2n   member2   1/1     Running   0          35s   10.244.1.91    sub2-w1   <none>           <none>            N

Internally, it appears that deployments with divided replicas based on weights are created in each cluster.

$ k get deployments.apps
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
nginx   10/10   10           10          63s

$ k karmada get deployments.apps
NAME    CLUSTER   READY   UP-TO-DATE   AVAILABLE   AGE   ADOPTION
nginx   member1   5/5     5            5           71s   Y
nginx   member2   5/5     5            5           71s   Y

Next, to simulate a situation where the member2 cluster becomes unreachable due to a cluster failure, we will shut down the member2 control-plane node by running sudo shutdown now on it.
After waiting a moment, communication from the Karmada cluster to the member2 cluster will fail, and the status of the member2 cluster will become READY: False.

$ k get cluster
NAME      VERSION   MODE   READY   AGE
member1   v1.29.6   Push   True    3d5h
member2   v1.29.6   Push   False   3d5h

When communication with the member2 cluster is lost, the pods that were running on member2 are started on member1 via Failover to satisfy the deployment's replica count of 10. Simultaneously, a taint is set on the member2 cluster to prevent new pods from being placed there. This can be confirmed in the Events of k describe cluster member2.

$ k describe cluster member2

Events:
  Type    Reason               Age                 From                Message
  ----    ------               ----                ----                -------
  Normal  TaintClusterSucceed  46s (x3 over 3d5h)  cluster-controller  Taint cluster succeed: cluster now has taints([{Key:cluster.karmada.io/not-ready,Effect:NoSchedule}]).
  Normal  TaintClusterSucceed  15s (x2 over 3d4h)  cluster-controller  Taint cluster succeed: cluster now has taints([{Key:cluster.karmada.io/not-ready,Effect:NoSchedule},{Key:cluster.karmada.io/not-ready,Effect:NoExecute}]).

Checking the pods in the member1 cluster, the pod count has reached 10, and those with AGE 3m29s are the pods that were newly started.

$ k karmada get pod -o wide
NAME                     CLUSTER   READY   STATUS    RESTARTS   AGE     IP             NODE      NOMINATED NODE   READINESS GATES   ADOPTION
nginx-76749dc54c-27fxb   member1   1/1     Running   0          3m29s   10.244.1.104   sub1-w1   <none>           <none>            N
nginx-76749dc54c-846qb   member1   1/1     Running   0          12m     10.244.1.98    sub1-w1   <none>           <none>            N
nginx-76749dc54c-8zt7h   member1   1/1     Running   0          3m29s   10.244.1.102   sub1-w1   <none>           <none>            N
nginx-76749dc54c-b4p9k   member1   1/1     Running   0          12m     10.244.1.99    sub1-w1   <none>           <none>            N
nginx-76749dc54c-hm9lj   member1   1/1     Running   0          12m     10.244.1.100   sub1-w1   <none>           <none>            N
nginx-76749dc54c-kj5hs   member1   1/1     Running   0          3m29s   10.244.1.106   sub1-w1   <none>           <none>            N
nginx-76749dc54c-lp9nn   member1   1/1     Running   0          3m29s   10.244.1.105   sub1-w1   <none>           <none>            N
nginx-76749dc54c-mmlpq   member1   1/1     Running   0          12m     10.244.1.97    sub1-w1   <none>           <none>            N
nginx-76749dc54c-tnj4k   member1   1/1     Running   0          12m     10.244.1.101   sub1-w1   <none>           <none>            N
nginx-76749dc54c-zb98z   member1   1/1     Running   0          3m29s   10.244.1.103   sub1-w1   <none>           <none>            N
error: cluster(member2) is inaccessible, please check authorization or network

Note that while the control-plane node including the kube-apiserver is down in the member2 cluster, the pods (containers) running on the worker node remain active. This can be confirmed by checking the containers on the node.

On member2 cluster worker node
$ sudo nerdctl -n k8s.io ps | grep nginx:latest
19b82ddf0372    docker.io/library/nginx:latest                                                                                      "/docker-entrypoint.…"    18 minutes ago    Up                 k8s://default/nginx-76749dc54c-rqm2n/nginx
614267ce0264    docker.io/library/nginx:latest                                                                                      "/docker-entrypoint.…"    18 minutes ago    Up                 k8s://default/nginx-76749dc54c-hm96h/nginx
8d7684a71103    docker.io/library/nginx:latest                                                                                      "/docker-entrypoint.…"    18 minutes ago    Up                 k8s://default/nginx-76749dc54c-76n6w/nginx
9620b39596e5    docker.io/library/nginx:latest                                                                                      "/docker-entrypoint.…"    18 minutes ago    Up                 k8s://default/nginx-76749dc54c-9d54j/nginx
b7d10132fedc    docker.io/library/nginx:latest                                                                                      "/docker-entrypoint.…"    18 minutes ago    Up                 k8s://default/nginx-76749dc54c-jl25p/nginx

Therefore, the number of pods in the deployment status becomes (the original 10 pods) + (5 pods newly started by Failover) = 15 pods, confirming that it temporarily exceeds the 10 pods defined in the manifest.

$ k get deployments.apps nginx
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
nginx   15/10   15           15          14m

$ k karmada get deployments.apps
NAME    CLUSTER   READY   UP-TO-DATE   AVAILABLE   AGE   ADOPTION
nginx   member1   10/10   10           10          14m   Y
error: cluster(member2) is inaccessible, please check authorization or network

In this way, when an active cluster goes down due to a failure, pods are started in other clusters via Failover.

Now, let's try restarting the node of the member2 cluster that we shut down earlier. Once communication between the Karmada control plane and the target cluster's kube-apiserver is restored, the cluster status returns to READY: True.

$ k get cluster
NAME      VERSION   MODE   READY   AGE
member1   v1.29.6   Push   True    3d5h
member2   v1.29.6   Push   True    3d5h

Even after the failed cluster becomes available again, pod re-placement is not automatically executed. Thus, the number of pods on the member1 cluster remains at 10, and the 5 pods that were running before the failure on the member2 cluster also remain as they are.

NAME                     CLUSTER   READY   STATUS    RESTARTS   AGE   IP             NODE      NOMINATED NODE   READINESS GATES   ADOPTION
nginx-76749dc54c-27fxb   member1   1/1     Running   0          16m   10.244.1.104   sub1-w1   <none>           <none>            N
nginx-76749dc54c-846qb   member1   1/1     Running   0          25m   10.244.1.98    sub1-w1   <none>           <none>            N
nginx-76749dc54c-8zt7h   member1   1/1     Running   0          16m   10.244.1.102   sub1-w1   <none>           <none>            N
nginx-76749dc54c-b4p9k   member1   1/1     Running   0          25m   10.244.1.99    sub1-w1   <none>           <none>            N
nginx-76749dc54c-hm9lj   member1   1/1     Running   0          25m   10.244.1.100   sub1-w1   <none>           <none>            N
nginx-76749dc54c-kj5hs   member1   1/1     Running   0          16m   10.244.1.106   sub1-w1   <none>           <none>            N
nginx-76749dc54c-lp9nn   member1   1/1     Running   0          16m   10.244.1.105   sub1-w1   <none>           <none>            N
nginx-76749dc54c-mmlpq   member1   1/1     Running   0          25m   10.244.1.97    sub1-w1   <none>           <none>            N
nginx-76749dc54c-tnj4k   member1   1/1     Running   0          25m   10.244.1.101   sub1-w1   <none>           <none>            N
nginx-76749dc54c-zb98z   member1   1/1     Running   0          16m   10.244.1.103   sub1-w1   <none>           <none>            N
nginx-76749dc54c-76n6w   member2   1/1     Running   0          25m   10.244.1.89    sub2-w1   <none>           <none>            N
nginx-76749dc54c-9d54j   member2   1/1     Running   0          25m   10.244.1.90    sub2-w1   <none>           <none>            N
nginx-76749dc54c-hm96h   member2   1/1     Running   0          25m   10.244.1.92    sub2-w1   <none>           <none>            N
nginx-76749dc54c-jl25p   member2   1/1     Running   0          25m   10.244.1.93    sub2-w1   <none>           <none>            N
nginx-76749dc54c-rqm2n   member2   1/1     Running   0          25m   10.244.1.91    sub2-w1   <none>           <none>            N

After some time, the pods on the member2 cluster are terminated, returning to the 10 pods defined in the manifest.

NAME                     CLUSTER   READY   STATUS    RESTARTS   AGE   IP             NODE      NOMINATED NODE   READINESS GATES   ADOPTION
nginx-76749dc54c-27fxb   member1   1/1     Running   0          23m   10.244.1.104   sub1-w1   <none>           <none>            N
nginx-76749dc54c-846qb   member1   1/1     Running   0          32m   10.244.1.98    sub1-w1   <none>           <none>            N
nginx-76749dc54c-8zt7h   member1   1/1     Running   0          23m   10.244.1.102   sub1-w1   <none>           <none>            N
nginx-76749dc54c-b4p9k   member1   1/1     Running   0          32m   10.244.1.99    sub1-w1   <none>           <none>            N
nginx-76749dc54c-hm9lj   member1   1/1     Running   0          32m   10.244.1.100   sub1-w1   <none>           <none>            N
nginx-76749dc54c-kj5hs   member1   1/1     Running   0          23m   10.244.1.106   sub1-w1   <none>           <none>            N
nginx-76749dc54c-lp9nn   member1   1/1     Running   0          23m   10.244.1.105   sub1-w1   <none>           <none>            N
nginx-76749dc54c-mmlpq   member1   1/1     Running   0          32m   10.244.1.97    sub1-w1   <none>           <none>            N
nginx-76749dc54c-tnj4k   member1   1/1     Running   0          32m   10.244.1.101   sub1-w1   <none>           <none>            N
nginx-76749dc54c-zb98z   member1   1/1     Running   0          23m   10.244.1.103   sub1-w1   <none>           <none>            N

When the member2 cluster recovers, the taints that were set during the failure are automatically removed, so pods newly created after the recovery will also be placed on member2.

Events:
  Type    Reason               Age                 From                Message
  ----    ------               ----                ----                -------
  Normal  TaintClusterSucceed  24m (x3 over 3d5h)  cluster-controller  Taint cluster succeed: cluster now has taints([{Key:cluster.karmada.io/not-ready,Effect:NoSchedule}]).
  Normal  TaintClusterSucceed  23m (x2 over 3d5h)  cluster-controller  Taint cluster succeed: cluster now has taints([{Key:cluster.karmada.io/not-ready,Effect:NoSchedule},{Key:cluster.karmada.io/not-ready,Effect:NoExecute}]).
  Normal  TaintClusterSucceed  11m (x2 over 3d4h)  cluster-controller  Taint cluster succeed: cluster now has taints([{Key:cluster.karmada.io/not-ready,Effect:NoExecute}]).
  Normal  TaintClusterSucceed  10m (x3 over 3d5h)  cluster-controller  Taint cluster succeed: cluster now does not have taints.

In this way, Karmada automatically adjusts the number of pods after a cluster recovery. However, as seen above, it does not perform re-placement if the distribution of pods across clusters is skewed after the adjustment. In the situation above, the nginx deployment itself on the member2 cluster has been deleted, so commands like pod deletion or kubectl rollout restart will not place pods on the member2 cluster; it is necessary to delete and then recreate the deployment.

$ k karmada get deployments.apps  -C member1
NAME    CLUSTER   READY   UP-TO-DATE   AVAILABLE   AGE   ADOPTION
nginx   member1   10/10   10           10          45m   Y

$ k karmada get deployments.apps  -C member2
No resources found in  namespace.

Other Features

Karmada has many other features, and since there are quite a few, I will briefly introduce some features that I found personally interesting.

Override Policy

Override Policy is a feature that dynamically adds, deletes, or replaces certain properties based on the conditions of the target cluster when deploying resources to multiple clusters. Basically, it can be used for cases where you want to deploy the same deployment but want to change or add labels or annotations for each cluster. The documentation lists the following types of overrides:

Override Type Description
ImageOverrider Changes container images
CommandOverrider Changes container commands
ArgsOverrider Changes container arguments
LabelsOverrider Changes resource labels
AnnotationsOverrider Changes resource annotations
PlaintextOverrider Corresponds to a kubectl path and changes any property of the resource

Deployment with Argo CD

In the previous verification, we manually deployed the target resources and the PropagationPolicy manifests, but in actual production environments, you would likely use a deployment tool like Argo CD to automate the deployment process. The documentation provides instructions on how to deploy with Karmada using Argo CD and Flux.

However, as we saw in the verification, the process basically involves deploying the PropagationPolicy and the target resource manifests separately. Therefore, even when using Argo CD, the flow involves saving these manifests in a GitHub repository and having Argo CD execute the deployment.

Cross-cluster Networking

Since member clusters managed by Karmada are strictly individual clusters, cross-cluster communication requires considering network reachability in a standard network, rather than a flat internal network using a CNI. However, by utilizing OSS that enables cross-cluster networking, you can extend the flat network within a cluster to the cluster level, enabling cross-cluster Service Discovery, AutoScaling, and more.
Note that this functionality is not built directly into Karmada; it requires the use of external components. The documentation describes how to use Submariner, an OSS that provides flat network communication between clusters.

https://submariner.io/

I was also testing cross-cluster Service Discovery using Submariner, but I gave up after struggling to get communication from one cluster to a service in another cluster working (I might try again if I have the chance).
The progress of that verification is documented in the Cross-cluster Networking with Submariner section of the verification notes.

Verification Notes

Notes during verification

Cross-cluster Networking with Submariner

As mentioned in Karmada's cross-cluster networking, using Submariner enables communication between pods and services across clusters. In Submariner, a component called the Broker manages the metadata of the cross-cluster network, and each cluster achieves cross-cluster communication via the Gateway Engine.


Submariner architecture. Quoted from https://submariner.io/getting-started/architecture/globalnet/

Furthermore, the default setup method has a prerequisite that the ServiceCIDR of each cluster must not overlap; if they do, a mechanism called Globalnet must be used.

However, by default, a limitation of Submariner is that it doesn’t handle overlapping CIDRs (ServiceCIDR and ClusterCIDR) across clusters. Each cluster must use distinct CIDRs that don’t conflict or overlap with any other cluster that is going to be part of the ClusterSet.

Since the three clusters used in this verification all have overlapping Pod CIDRs and ServiceCIDRs, we will build the environment using Globalnet.


Here, we will build the cross-cluster network following the Submariner documentation EXTERNAL NETWORK (EXPERIMENTAL). The documentation prepares two cluster nodes and one non-cluster VM, but the VM is not needed this time, so we will skip it.

Submariner uses the subctl CLI for various operations, so we'll install it.

curl -Ls https://get.submariner.io | bash
export PATH=$PATH:~/.local/bin
echo export PATH=\$PATH:~/.local/bin >> ~/.profile

First, we'll configure the Broker. In Submariner, the Broker is the component that exchanges communication between clusters, and it needs to be built on an API server accessible to all clusters in the cross-cluster network. In this verification, since the Karmada cluster is capable of communicating with each member cluster, we will build the broker on the regular cluster side of the Karmada cluster (not the Karmada control plane) by executing the following command:

export KUBECONFIG=/home/ubuntu/.kube/config
subctl deploy-broker  --globalnet

Next, we will join the other clusters to the Broker, but before that, I'll list the nodes used this time.

Node Name Cluster Node IP Address Pod CIDR Role
main-m1 karmada 10.0.0.40 10.244.0.0/16 control-plane
main-w1 karmada 10.0.0.41 - worker
sub1-m1 member1 10.0.0.50 10.244.0.0/16 control-plane
sub1-w1 member1 10.0.0.51 - worker
sub2-m1 member2 10.0.0.30 10.244.0.0/16 control-plane
sub2-w1 member2 10.0.0.31 - worker
  • Set CLUSTER_CIDR to 10.244.0.0/16 as per the documentation.
  • Set EXTERNAL_CIDR such that it does not overlap with the node IP address ranges of all clusters. Here, 10.0.0.96/27 is used.

Join the karmada cluster to the broker based on the CIDRs above.

CLUSTER_CIDR=10.244.0.0/16
EXTERNAL_CIDR=10.0.0.96/27
subctl join broker-info.subm --clusterid karmada --natt=false --clustercidr=${CLUSTER_CIDR},${EXTERNAL_CIDR}

Next, join the member1 and member2 clusters to the broker.

subctl join --kubeconfig /home/ubuntu/.kube/sub1.config  broker-info.subm --clusterid member1 --natt=false
subctl join --kubeconfig /home/ubuntu/.kube/sub2.config  broker-info.subm --clusterid member2 --natt=false

You can check the current connection status of the clusters with subctl show all. It is OK if connections and gateways are displayed and no errors appear.

$ subctl show all

Cluster "main_k8s"
 ✓ Detecting broker(s)
NAMESPACE               NAME                COMPONENTS                        GLOBALNET   GLOBALNET CIDR   DEFAULT GLOBALNET SIZE   DEFAULT DOMAINS
submariner-k8s-broker   submariner-broker   service-discovery, connectivity   yes         242.0.0.0/8      65536

 ✓ Showing Connections
GATEWAY   CLUSTER   REMOTE IP   NAT   CABLE DRIVER   SUBNETS        STATUS      RTT avg.
sub1-w1   member1   10.0.0.51   no    libreswan      242.1.0.0/16   connected   852.419µs
sub2-w1   member2   10.0.0.31   no    libreswan      242.2.0.0/16   connected   835.722µs

 ✓ Showing Endpoints
CLUSTER   ENDPOINT IP   PUBLIC IP        CABLE DRIVER   TYPE
karmada   10.0.0.41     118.158.73.193   libreswan      local
member1   10.0.0.51     118.158.73.193   libreswan      remote
member2   10.0.0.31     118.158.73.193   libreswan      remote

 ✓ Showing Gateways
NODE      HA STATUS   SUMMARY
main-w1   active      All connections (2) are established

 ✓ Showing Network details
    Discovered network details via Submariner:
        Network plugin:  generic
        Service CIDRs:   [10.96.0.0/12]
        Cluster CIDRs:   [10.244.0.0/16,10.0.0.96/27]
        Global CIDR:     242.0.0.0/16

 ✓ Showing versions
COMPONENT                       REPOSITORY           CONFIGURED   RUNNING                     ARCH
submariner-gateway              quay.io/submariner   0.17.1       release-0.17-fdfcc8797bc2   amd64
submariner-routeagent           quay.io/submariner   0.17.1       release-0.17-fdfcc8797bc2   amd64
submariner-globalnet            quay.io/submariner   0.17.1       release-0.17-fdfcc8797bc2   amd64
submariner-metrics-proxy        quay.io/submariner   0.17.1       release-0.17-1e49b8404147   amd64
submariner-operator             quay.io/submariner   0.17.1       release-0.17-0133a99b08a6   amd64
submariner-lighthouse-agent     quay.io/submariner   0.17.1       release-0.17-b9c6b562f556   amd64
submariner-lighthouse-coredns   quay.io/submariner   0.17.1       release-0.17-b9c6b562f556   amd64

Upon completion of the setup, the following pods are created in the submariner-operator namespace:

$ k get pod -n submariner-operator
NAME                                             READY   STATUS    RESTARTS   AGE
submariner-gateway-s9hcc                         1/1     Running   0          3d3h
submariner-globalnet-gklkd                       1/1     Running   0          3d3h
submariner-lighthouse-agent-754bfcf58c-klf5q     1/1     Running   0          3d3h
submariner-lighthouse-coredns-7fb549f74d-jq2zx   1/1     Running   0          3d3h
submariner-lighthouse-coredns-7fb549f74d-vrgf5   1/1     Running   0          3d3h
submariner-metrics-proxy-dmg4w                   2/2     Running   0          3d3h
submariner-operator-ffc859cfd-w9sfw              1/1     Running   0          3d3h
submariner-routeagent-7jlwn                      1/1     Running   0          3d3h
submariner-routeagent-h8dlk                      1/1     Running   0          3d3h

Now that the cross-cluster network is configured, we will verify the operation according to Karmada's Multi-cluster Service Discovery.

First, create the serviceexports and serviceimport CRDs on the member clusters. Create a ClusterPropagationPolicy manifest as shown below.

svc-crd.yml
# propagate ServiceExport CRD
apiVersion: policy.karmada.io/v1alpha1
kind: ClusterPropagationPolicy
metadata:
  name: serviceexport-policy
spec:
  resourceSelectors:
    - apiVersion: apiextensions.k8s.io/v1
      kind: CustomResourceDefinition
      name: serviceexports.multicluster.x-k8s.io
  placement:
    clusterAffinity:
      clusterNames:
        - member1
        - member2
---
# propagate ServiceImport CRD
apiVersion: policy.karmada.io/v1alpha1
kind: ClusterPropagationPolicy
metadata:
  name: serviceimport-policy
spec:
  resourceSelectors:
    - apiVersion: apiextensions.k8s.io/v1
      kind: CustomResourceDefinition
      name: serviceimports.multicluster.x-k8s.io
  placement:
    clusterAffinity:
      clusterNames:
        - member1
        - member2

By deploying this on Karmada, the CRDs will be created on both member clusters at once.

export KUBECONFIG=/home/ubuntu/.kube/karmada.config
k apply -f svc-crd.yml

Next, create and deploy the following manifests as described in the documentation.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: serve
spec:
  replicas: 1
  selector:
    matchLabels:
      app: serve
  template:
    metadata:
      labels:
        app: serve
    spec:
      containers:
      - name: serve
        image: jeremyot/serve:0a40de8
        args:
        - "--message='hello from cluster member1 (Node: {{env \"NODE_NAME\"}} Pod: {{env \"POD_NAME\"}} Address: {{addr}})'"
        env:
          - name: NODE_NAME
            valueFrom:
              fieldRef:
                fieldPath: spec.nodeName
          - name: POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
---
apiVersion: v1
kind: Service
metadata:
  name: serve
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: serve
---
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
  name: mcs-workload
spec:
  resourceSelectors:
    - apiVersion: apps/v1
      kind: Deployment
      name: serve
    - apiVersion: v1
      kind: Service
      name: serve
  placement:
    clusterAffinity:
      clusterNames:
        - member1
apiVersion: multicluster.x-k8s.io/v1alpha1
kind: ServiceExport
metadata:
  name: serve
---
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
  name: serve-export-policy
spec:
  resourceSelectors:
    - apiVersion: multicluster.x-k8s.io/v1alpha1
      kind: ServiceExport
      name: serve
  placement:
    clusterAffinity:
      clusterNames:
        - member1
apiVersion: multicluster.x-k8s.io/v1alpha1
kind: ServiceImport
metadata:
  name: serve
spec:
  type: ClusterSetIP
  ports:
  - port: 80
    protocol: TCP
---
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
  name: serve-import-policy
spec:
  resourceSelectors:
    - apiVersion: multicluster.x-k8s.io/v1alpha1
      kind: ServiceImport
      name: serve
  placement:
    clusterAffinity:
      clusterNames:
        - member2

Deploying the above will result in the following behavior:

  1. serve pods and services are created on the member1 cluster.
  2. A serve service accessible across clusters is created via the ServiceExport CRD.
  3. A service for accessing serve is created on the member2 cluster via the ServiceImport CRD.

Due to the nature of Submariner, exposing a specific service with the subctl export command or creating a ServiceExport CRD results in the creation of a service that is globally accessible across clusters. This can be verified as a globalingressips resource.

$ k karmada get globalingressips.submariner.io -A
NAMESPACE   NAME            CLUSTER   IP              ADOPTION
default     serve           member1   242.1.255.253   N
default     derived-serve   member2   242.2.255.253   N

The serve entry above corresponds to the global endpoint of the service created on the member1 cluster, and the IP address is used to connect to that endpoint. Additionally, checking the services on the member1 cluster reveals that a submariner-.. service has been created, with the IP address confirmed above set as the EXTERNAL-IP.

$ k get svc
NAME                                          TYPE        CLUSTER-IP       EXTERNAL-IP     PORT(S)   AGE
kubernetes                                    ClusterIP   10.96.0.1        <none>          443/TCP   26h
serve                                         ClusterIP   10.102.199.123   <none>          80/TCP    98s
submariner-etcfrt5unwnek3jrjr4joya6kfly4q6b   ClusterIP   10.99.110.190    242.1.255.253   80/TCP    98s

By accessing this IP address, the serve pod on the member1 cluster can be reached not only from the nodes of the member1 cluster but also from the nodes of the karmada and member2 clusters.

$ curl 242.1.255.253
'hello from cluster member1 (Node: sub1-w1 Pod: serve-8765f9f98-7n4zm Address: 10.244.1.91)'

Furthermore, the created global endpoint can be resolved within the pod network using [service_name].[namespace].svc.clusterset.local. This can be verified by launching a temporary pod in the member2 cluster and running nslookup or dig from within the pod.

Executed inside a pod in member cluster 2
root@nginx-76749dc54c-czzsk:/# nslookup  serve.default.svc.clusterset.local
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   serve.default.svc.clusterset.local
Address: 242.1.255.253

# dig
root@nginx-76749dc54c-czzsk:/# dig  serve.default.svc.clusterset.local

; <<>> DiG 9.18.24-1-Debian <<>> serve.default.svc.clusterset.local
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 27186
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: f738fcff83c7111c (echoed)
;; QUESTION SECTION:
;serve.default.svc.clusterset.local. IN A

;; ANSWER SECTION:
serve.default.svc.clusterset.local. 5 IN A      242.1.255.253

;; Query time: 0 msec
;; SERVER: 10.96.0.10#53(10.96.0.10) (UDP)
;; WHEN: Sat Jun 22 10:10:13 UTC 2024
;; MSG SIZE  rcvd: 125

In this way, Submariner assigns a globally valid IP address to a k8s service across clusters.

The relationship between the services created above is illustrated in the diagram below. When a service is exposed via service export, a corresponding submariner-... service is created and assigned an EXTERNAL-IP (globalingressips). Through this, communication between clusters belonging to the Submariner network is realized.


Conceptual diagram

In Step 3: Access the service from member2 cluster, it is mentioned that as part of Karmada's Service Discovery feature, routing to the member1 cluster's pod should be possible by accessing derived-serve. However, when I tried it myself, the service and pods were not correctly linked, and it did not work. It is possible that my configuration was incorrect.

Conclusion

We have verified the behavior of multi-cluster resource deployment and Failover using the k8s multi-cluster management OSS, Karmada. While there are many unique concepts and components in Karmada, leading to a somewhat high learning curve, and the management costs and operational methods for full-scale multi-cluster management can be complex, I felt that it is definitely an option if you want to achieve multi-cluster management using open-source software. Nevertheless, since it is a CNCF incubating project, further development and functional expansions are expected in response to the growing demand for multi-cloud and multi-cluster management in recent years.

Discussion