Работа с Cilium ClusterMesh#

Описание#

Cilium ClusterMesh (ClusterMesh) — это компонент Cilium, предназначенный для объединения нескольких Kubernetes-кластеров в единую сеть. Он позволяет обеспечить прозрачное сетевое взаимодействие между pod, работающими в разных кластерах, а также централизованное управление политиками безопасности и сервисами.

ClusterMesh особенно полезен при работе с географически распределенными системами, где требуется высокая доступность, отказоустойчивость и единая логика маршрутизации.

Контекст использования#

Объединение кластеров#

ClusterMesh позволяет нескольким Kubernetes-кластерам работать как единая система. Pod из одного кластера может напрямую взаимодействовать с pod из другого кластера, организуя единое сетевое пространство.

Например, кластеры расположены в Москве, Минске и Астане. С помощью ClusterMesh можно организовать прозрачную связь между ними, независимо от их физического местонахождения.

Global Services#

ClusterMesh поддерживает создание глобальных сервисов (сервис с аннотациями service.cilium.io/global: "true" и service.cilium.io/global-sync-endpoint-slices: "true"), которые балансируют трафик между экземплярами одного и того же сервиса в разных кластерах.

Описание работы:

  • Микросервис orders запущен в каждом кластере.

  • Через глобальный параметр Service запросы равномерно распределяются между кластерами.

  • При отключении одного кластера нагрузка автоматически перераспределяется на оставшиеся.

Например, в случае разработки интернет-магазина с кластерами, размещенными в регионах Европа, США, Cilium ClusterMesh позволяет:

  • Реплицировать данные между кластерами.

  • Сделать сервис payments доступным из любого кластера.

  • Обеспечить отказоустойчивость, при которой в случае выхода из строя одного кластера второй берет на себя его нагрузку.

Централизованное управление#

С помощью ClusterMesh можно применять сетевые политики (NetworkPolicy) ко всем кластерам сразу, что упрощает управление безопасностью и маршрутизацией.

Возможности сетевой политики:

  • Единое пространство имен для всех кластеров.

  • Поддержка DNS-запросов между кластерами.

  • Возможность управления через Kubectl из одного места.

Безопасность#

Все соединения между кластерами шифруются с использованием mTLS (mutual TLS, mutual Transport Layer Security) . Это гарантирует, что только доверенные кластеры могут обмениваться данными и передавать информацию безопасно.

Конфигурация DropAppConfiguration для объединения кластеров в одну сеть#

Для проверки работы ClusterMesh воспользуйтесь сценарием создания трех кластеров DropApp с помощью Dactl и объедения их в один кластер.

Генерация общего набора сертификатов TLS#

Чтобы clustermesh-apiserver мог корректно использовать mTLS, необходимо сгенерировать единый набор сертификатов, которые будут использоваться в каждом кластере.

  1. Создайте файл конфигурации cilium-ca.conf с информацией о корневом сертификате:

    cilium-ca.conf
    [req]
    distinguished_name = req_distinguished_name
    prompt             = no
    x509_extensions    = ca_x509_extensions
     
    [ca_x509_extensions]
    basicConstraints = CA:TRUE
    keyUsage         = cRLSign, keyCertSign
     
    [req_distinguished_name]
    CN  = Cilium
    C  = RU
    ST = Moscow
    L  = Moscow
     
    [clustermesh-server]
    distinguished_name = clustermesh-server_distinguished_name
    prompt             = no
    req_extensions     = clustermesh-server_req_extensions
     
    [clustermesh-server_req_extensions]
    basicConstraints     = CA:FALSE
    extendedKeyUsage     = clientAuth, serverAuth
    keyUsage             = critical, digitalSignature, keyEncipherment
    nsCertType           = client
    nsComment            = "Clustermesh Server Certificate"
    subjectAltName       = @clustermesh-server_alt_names
    subjectKeyIdentifier = hash
     
    [clustermesh-server_distinguished_name]
    C  = RU
    ST = Moscow
    L  = Moscow
     
    [clustermesh-server_alt_names]
    IP.0  = <clustermesh-ip>
    DNS.0 = clustermesh-apiserver.cilium.io
    DNS.1 = *.mesh.cilium.io
    DNS.2 = localhost
    DNS.3 = clustermesh-apiserver.kube-system.svc.cluster.local
    DNS.4 = clustermesh-apiserver.kube-system
    DNS.5 = clustermesh-apiserver
    DNS.6 = clustermesh-apiserver.kube-system.svc
     
    # Clustermesh Admin Cert
     
    [clustermesh-admin]
    distinguished_name = clustermesh-admin_distinguished_name
    prompt             = no
    req_extensions     = clustermesh-admin_req_extensions
     
    [clustermesh-admin_req_extensions]
    basicConstraints     = CA:FALSE
    extendedKeyUsage     = clientAuth, serverAuth
    keyUsage             = critical, digitalSignature, keyEncipherment
    nsCertType           = client
    nsComment            = "Clustermesh Admin Certificate"
    subjectAltName       = @clustermesh-admin_alt_names
    subjectKeyIdentifier = hash
     
    [clustermesh-admin_distinguished_name]
    CN = root
    O  = Cilium
    C  = RU
    ST = Moscow
    L  = Moscow
     
    [clustermesh-admin_alt_names]
    IP.0  = <clustermesh-ip>
    DNS.0 = localhost
    DNS.1 = clustermesh-apiserver.kube-system.svc.cluster.local
    DNS.2 = clustermesh-apiserver.kube-system
    DNS.3 = clustermesh-apiserver
    DNS.4 = clustermesh-apiserver.kube-system.svc
     
    # Clustermesh Client Cert
     
    [clustermesh-client]
    distinguished_name = clustermesh-client_distinguished_name
    prompt             = no
    req_extensions     = clustermesh-client_req_extensions
     
    [clustermesh-client_req_extensions]
    basicConstraints     = CA:FALSE
    extendedKeyUsage     = clientAuth, serverAuth
    keyUsage             = critical, digitalSignature, keyEncipherment
    nsCertType           = client
    nsComment            = "Clustermesh Client Certificate"
    subjectKeyIdentifier = hash
     
    [clustermesh-client_distinguished_name]
    CN = remote
    O  = Cilium
    C  = RU
    ST = Moscow
    L  = Moscow
     
    [clustermesh-client_alt_names]
    IP.0  = <clustermesh-ip>
    DNS.0 = localhost
    DNS.1 = clustermesh-apiserver.kube-system.svc.cluster.local
    DNS.2 = clustermesh-apiserver.kube-system
    DNS.3 = clustermesh-apiserver
    DNS.4 = clustermesh-apiserver.kube-system.svc
    
  2. Создайте скрипт generate-cilium-certs.sh:

    generate-cilium-certs.sh
    #!/bin/bash
    set -e
     
    OUTPUT_DIR="cilium-cluster-certs"
    rm -rf "$OUTPUT_DIR"
    mkdir -p "$OUTPUT_DIR"
     
    echo "### Генерация корневого CA"
    openssl genrsa -out "$OUTPUT_DIR/ca.key" 4096
    openssl req -x509 -new -sha512 -noenc \
    -key "$OUTPUT_DIR/ca.key" -days 3653 \
    -config cilium-ca.conf \
    -out "$OUTPUT_DIR/ca.crt"
     
    echo "### Генерация server admin client"
    certs=(
        "clustermesh-server" "clustermesh-admin" "clustermesh-client"
    )
     
    for i in ${certs[*]}; do
      openssl genrsa -out "$OUTPUT_DIR/${i}.key" 4096
     
      openssl req -new -key "$OUTPUT_DIR/${i}.key" -sha256 \
        -config "cilium-ca.conf" -section ${i} \
        -out "$OUTPUT_DIR/${i}.csr"
       
      openssl x509 -req -days 3653 -in "$OUTPUT_DIR/${i}.csr" \
        -copy_extensions copyall \
        -sha256 -CA "$OUTPUT_DIR/ca.crt" \
        -CAkey "$OUTPUT_DIR/ca.key" \
        -CAcreateserial \
        -out "$OUTPUT_DIR/${i}.crt"
       cat "$OUTPUT_DIR/${i}.crt" | base64 | tr -d '\n' > "$OUTPUT_DIR/${i}.crt.b64"
       cat "$OUTPUT_DIR/${i}.key" | base64 | tr -d '\n' > "$OUTPUT_DIR/${i}.key.b64"
       
    done
     
    # --- Сохраняем корневой ca.crt тоже в base64 ---
    cat "$OUTPUT_DIR/ca.crt" | base64 | tr -d '\n' > "$OUTPUT_DIR/ca.crt.b64"
    cat "$OUTPUT_DIR/ca.key" | base64 | tr -d '\n' > "$OUTPUT_DIR/ca.key.b64"
     
    # --- Сохраняем все данные в values.yaml ---
    cat > "values.yaml" <<EOF
    tls:
      ca:
        cert: $(cat "$OUTPUT_DIR/ca.crt.b64")
        key: $(cat "$OUTPUT_DIR/ca.key.b64")
    clustermesh:
      useAPIServer: true
      apiserver:
        tls:
          auto:
            enabled: false
          server:
            cert: $(cat "$OUTPUT_DIR/clustermesh-server.crt.b64")
            key: $(cat "$OUTPUT_DIR/clustermesh-server.key.b64")
          admin:
            cert: $(cat "$OUTPUT_DIR/clustermesh-admin.crt.b64")
            key: $(cat "$OUTPUT_DIR/clustermesh-admin.key.b64")
          client:
            cert: $(cat "$OUTPUT_DIR/clustermesh-client.crt.b64")
            key: $(cat "$OUTPUT_DIR/clustermesh-client.key.b64")
          remote:
            cert: $(cat "$OUTPUT_DIR/clustermesh-client.crt.b64")
            key: $(cat "$OUTPUT_DIR/clustermesh-client.key.b64")
    EOF
     
    echo "✅ Все сертификаты готовы в папке: $OUTPUT_DIR"
    echo "👉 Используйте содержимое файла: ./values.yaml"
    
  3. Сделайте файл исполняемым:

    chmod +x ./generate-cilium-certs.sh
    
  4. Запустите скрипт:

    sudo ./generate-cilium-certs.sh
    

Скрипт создаст сертификаты и values.yaml, содержимое которого необходимо добавить в DropAppConfiguration для каждого кластера.

Создание кластеров DropApp#

Создайте 3 кластера DropApp, например, c именами: cluster-rio, cluster-bravo, cluster-marco.

Для того чтобы визуально наблюдать общую сеть кластеров, необходимо настроить ClusterMesh и включить Hubble-ui.

Предварительные требования к кластерам#

Предварительными требованиями являются:

  • Кластеры должны иметь:

    • разный cilium.cluster.id и разный cilium.cluster.name;

    • один общий набор сертификатов;

    • разный, не пересекающийся podSubnetCIDR.

  • Сервис для clustermesh-apiserver должен иметь тип LoadBalancer и для него Metallb должен выделить пул из двух адресов (один для сервиса clustermesh-apiserver, второй - для сервиса ingress-nginx-controller).

  • Все узлы всех кластеров должны иметь сетевую связность. Если они географически распределены, то должен быть VPN или иной способ туннелирования между сетями кластеров.

Кластер cluster-rio#

Пример конфигурации da-cluster-rio.yaml:

da-cluster-rio.yaml
apiVersion: config.dropapp.ru/v1alpha1
kind: DropAppConfiguration
metadata:
  name: cluster-rio
spec:
  skipComponentsInstall: false
  network:
    publicDomain: dapp.test.un.sbt
    metallb:
      pools:
        - addresses: "<rio-cluster-pool-ip>"
          name: "dapp"
          l2Advertisement: true
    hostAliases:
      - hostnames:
          - console.cluster-rio.dapp.test.un.sbt
          - auth.cluster-rio.dapp.test.un.sbt
          - alertmanager.cluster-rio.dapp.test.un.sbt
          - metrics.cluster-rio.dapp.test.un.sbt
        ip: <rio-cluster-ip2>
  artifacts:
    imageRegistry:
      host: <host>
      path: <path>
      credentials: <image-cred>
  compute:
    sshAccess:
      user: dapp
      privateKeyPath: "~/.ssh/id_k8s"
    userProvisioned:
      nodes:
        controlPlane:
          - ip: "<rio-control-plane-ip>"
            nodePool: "control-plane"
        worker:
          - ip: "<rio-worker1-ip>"
            nodePool: "worker"
          - ip: "<rio-worker2-ip>"
            nodePool: "worker"
  k8s:
    podSubnetCIDR: <rio-podSubnetCIDR-ip>
    serviceSubnetCIDR: <serviceSubnetCIDR-ip>
    cni:
      cilium:
        values:
          cluster:
            name: cluster-rio
            id: 1
          hubble:
            enabled: true
            ui:
              enabled: true
            relay:
              enabled: true
          tls:
            ca:
              cert: <rio-tls-cert>
              key: <rio-tls-key>
          clustermesh:
            useAPIServer: true
            apiserver:
              tls:
                auto:
                  enabled: false
                server:
                  cert: <rio-clustermesh-tls-server-cert>
                  key: <rio-clustermesh-tls-server-key>
                admin:
                  cert: <rio-clustermesh-tls-admin-cert>
                  key: <rio-clustermesh-tls-admin-key>
                client:
                  cert: <rio-clustermesh-tls-client-cert>
                  key: <rio-clustermesh-tls-client-key>
                remote:
                  cert: <rio-clustermesh-tls-remote-cert>
                  key: <rio-clustermesh-tls-remote-key>
              service:
                type: LoadBalancer
                loadBalancerIP: <rio-cluster-ip1>
              replicas: 1
            config:
              enabled: true
              clusters:
                - name: cluster-rio
                  port: 2379
                  ips:
                    - <rio-cluster-ip1>
                - name: cluster-bravo
                  port: 2379
                  ips:
                    - <bravo-cluster-ip1>
                - name: cluster-marco
                  port: 2379
                  ips:
                    - <marco-cluster-ip1>

Кластер cluster-bravo#

Пример конфигурации da-cluster-bravo.yaml:

da-cluster-bravo.yaml
apiVersion: config.dropapp.ru/v1alpha1
kind: DropAppConfiguration
metadata:
  name: cluster-bravo
spec:
  skipComponentsInstall: false
  network:
    publicDomain: dapp.test.un.sbt
    metallb:
      pools:
        - addresses: "<bravo-cluster-pool-ip>"
          name: "dapp"
          l2Advertisement: true
    hostAliases:
      - hostnames:
          - console.cluster-bravo.dapp.test.un.sbt
          - auth.cluster-bravo.dapp.test.un.sbt
          - alertmanager.cluster-bravo.dapp.test.un.sbt
          - metrics.cluster-bravo.dapp.test.un.sbt
        ip: <bravo-cluster-ip2>
  artifacts:
    imageRegistry:
      host: <host>
      path: <path>
      credentials: <image-cred>
  compute:
    sshAccess:
      user: dapp
      privateKeyPath: "~/.ssh/id_k8s"
    userProvisioned:
      nodes:
        controlPlane:
          - ip: "<bravo-control-plane-ip>"
            nodePool: "control-plane"
        worker:
          - ip: "<bravo-worker1-ip>"
            nodePool: "worker"
          - ip: "<bravo-worker2-ip>"
            nodePool: "worker"
  k8s:
    podSubnetCIDR: <rio-podSubnetCIDR-ip>
    serviceSubnetCIDR: <serviceSubnetCIDR-ip>
    cni:
      cilium:
        values:
          cluster:
            name: cluster-bravo
            id: 2
          hubble:
            enabled: true
            ui:
              enabled: true
            relay:
              enabled: true
          tls:
            ca:
              cert: <bravo-tls-cert>
              key: <bravo-tls-key>
          clustermesh:
            useAPIServer: true
            apiserver:
              tls:
                auto:
                  enabled: false
                server:
                  cert: <bravo-clustermesh-tls-server-cert>
                  key: <bravo-clustermesh-tls-server-key>
                admin:
                  cert: <bravo-clustermesh-tls-admin-cert>
                  key: <bravo-clustermesh-tls-admin-key>
                client:
                  cert: <bravo-clustermesh-tls-client-cert>
                  key: <bravo-clustermesh-tls-client-key>
                remote:
                  cert: <bravo-clustermesh-tls-remote-cert>
                  key: <bravo-clustermesh-tls-remote-key>
              service:
                type: LoadBalancer
                loadBalancerIP: <bravo-cluster-ip1>
              replicas: 1
            config:
              enabled: true
              clusters:
                - name: cluster-rio
                  port: 2379
                  ips:
                    - <rio-cluster-ip1>
                - name: cluster-bravo
                  port: 2379
                  ips:
                    - <bravo-cluster-ip1>
                - name: cluster-marco
                  port: 2379
                  ips:
                    - <marco-cluster-ip1>

Кластер cluster-marco#

Пример конфигурации da-cluster-marco.yaml:

da-cluster-marco.yaml
apiVersion: config.dropapp.ru/v1alpha1
kind: DropAppConfiguration
metadata:
  name: cluster-marco
spec:
  skipComponentsInstall: false
  network:
    publicDomain: dapp.test.un.sbt
    metallb:
      pools:
        - addresses: "<bravo-cluster-pool-ip>"
          name: "dapp"
          l2Advertisement: true
    hostAliases:
      - hostnames:
          - console.cluster-marco.dapp.test.un.sbt
          - auth.cluster-marco.dapp.test.un.sbt
          - alertmanager.cluster-marco.dapp.test.un.sbt
          - metrics.cluster-marco.dapp.test.un.sbt
        ip: <marco-cluster-ip2>
  artifacts:
    imageRegistry:
      host: <host>
      path: <path>
      credentials: <image-cred>
  compute:
    sshAccess:
      user: dapp
      privateKeyPath: "~/.ssh/id_k8s"
    userProvisioned:
      nodes:
        controlPlane:
          - ip: "<marco-control-plane-ip>"
            nodePool: "control-plane"
        worker:
          - ip: "<marco-worker1-ip>"
            nodePool: "worker"
          - ip: "<marco-worker2-ip>"
            nodePool: "worker"
  k8s:
    podSubnetCIDR: <rio-podSubnetCIDR-ip>
    serviceSubnetCIDR: <serviceSubnetCIDR-ip>
    cni:
      cilium:
        values:
          cluster:
            name: cluster-marco
            id: 3
          hubble:
            enabled: true
            ui:
              enabled: true
            relay:
              enabled: true
          tls:
            ca:
              cert: <marco-tls-cert>
              key: <marco-tls-key>
          clustermesh:
            useAPIServer: true
            apiserver:
              tls:
                auto:
                  enabled: false
                server:
                  cert: <marco-clustermesh-tls-server-cert>
                  key: <marco-clustermesh-tls-server-key>
                admin:
                  cert: <marco-clustermesh-tls-admin-cert>
                  key: <marco-clustermesh-tls-admin-key>
                client:
                  cert: <marco-clustermesh-tls-client-cert>
                  key: <marco-clustermesh-tls-client-key>
                remote:
                  cert: <marco-clustermesh-tls-remote-cert>
                  key: <marco-clustermesh-tls-remote-key>
              service:
                type: LoadBalancer
                loadBalancerIP: <marco-cluster-ip1>
              replicas: 1
            config:
              enabled: true
              clusters:
                - name: cluster-rio
                  port: 2379
                  ips:
                    - <rio-cluster-ip1>
                - name: cluster-bravo
                  port: 2379
                  ips:
                    - <bravo-cluster-ip1>
                - name: cluster-marco
                  port: 2379
                  ips:
                    - <marco-cluster-ip1>

Запуск Dactl#

Запустите создание кластеров:

dactl create cluster --config ./da-cluster-rio.yaml
dactl create cluster --config ./da-cluster-bravo.yaml
dactl create cluster --config ./da-cluster-marco.yaml

Проверка ClusterMesh#

Проверка стабильности состояния общей сети#

Для проверки стабильности состояния сети между кластерами в Cilium выполните шаги:

  1. Скачайте бинарный файл Cilium:

    CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
    GOOS=$(go env GOOS)
    GOARCH=$(go env GOARCH)
    curl -L --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-${GOOS}-${GOARCH}.tar.gz{,.sha256sum}
    sha256sum --check cilium-${GOOS}-${GOARCH}.tar.gz.sha256sum
    sudo tar -C /usr/local/bin -xzvf cilium-${GOOS}-${GOARCH}.tar.gz
    rm cilium-${GOOS}-${GOARCH}.tar.gz{,.sha256sum}
    
  2. Проверьте статус кластера cluster-rio:

    ./cilium-clustermesh/cilium clustermesh status --kubeconfig ~/.dropapp/cluster-rio/kubeconfig --wait
    

    Пример вывода:

     Service "clustermesh-apiserver" of type "LoadBalancer" found
    ✅ Cluster access information is available:
      - <bravo-cluster-ip1>:2379
    ✅ Deployment clustermesh-apiserver is ready
    ℹ️  KVStoreMesh is enabled
    ...
    ⌛ Waiting (2m36s) for clusters to be connected: 3 nodes are not ready
    ...
    ⌛ Waiting (3m48s) for clusters to be connected: 1 nodes are not ready
      All 3 nodes are connected to all clusters [min:2 / avg:2.0 / max:2] All 1 KVStoreMesh replicas are connected to all clusters [min:2 / avg:2.0 / max:2]
     
    🔌 Cluster Connections:
      - cluster-marco: 3/3 configured, 3/3 connected - KVStoreMesh: 1/1 configured, 1/1 connected
      - cluster-bravo: 3/3 configured, 3/3 connected - KVStoreMesh: 1/1 configured, 1/1 connected
     
    🔀 Global services: [ min:0 / avg:0.0 / max:0 ]
    
  3. Повторите команду для каждого из оставшихся кластеров:

    ./cilium-clustermesh/cilium clustermesh status --kubeconfig ~/.dropapp/cluster-bravo/kubeconfig --wait
    
    ./cilium-clustermesh/cilium clustermesh status --kubeconfig ~/.dropapp/cluster-marco/kubeconfig --wait
    

    Вывод команд должен быть аналогичным cluster-rio.

Тест ClusterMesh#

  1. Создайте манифест test-rio.yaml для cluster-rio:

    test-rio.yaml
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: rebel-base
    spec:
      selector:
        matchLabels:
          name: rebel-base
      replicas: 2
      template:
        metadata:
          labels:
            name: rebel-base
        spec:
          containers:
          - name: rebel-base
            image: docker.io/nginx:1.27.1
            volumeMounts:
              - name: html
                mountPath: /usr/share/nginx/html/
            livenessProbe:
              httpGet:
                path: /
                port: 80
              periodSeconds: 1
            readinessProbe:
              httpGet:
                path: /
                port: 80
          volumes:
            - name: html
              configMap:
                name: rebel-base-response
                items:
                  - key: message
                    path: index.html
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: rebel-base-response
    data:
      message: "{\"Galaxy\": \"Rio\", \"Cluster\": \"cluster-rio\"}\n"
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: x-wing
    spec:
      selector:
        matchLabels:
          name: x-wing
      replicas: 2
      template:
        metadata:
          labels:
            name: x-wing
        spec:
          containers:
          - name: x-wing-container
            image: quay.io/cilium/json-mock:v1.3.8@sha256:<cilium-sha>
            livenessProbe:
              exec:
                command:
                - curl
                - -sS
                - -o
                - /dev/null
                - localhost
            readinessProbe:
              exec:
                command:
                - curl
                - -sS
                - -o
                - /dev/null
                - localhost
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: rebel-base
      annotations:
        service.cilium.io/global: "true"
    spec:
      type: ClusterIP
      ports:
      - port: 80
      selector:
        name: rebel-base
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: rebel-base-headless
      annotations:
        service.cilium.io/global: "true"
        service.cilium.io/global-sync-endpoint-slices: "true"
    spec:
      type: ClusterIP
      clusterIP: None
      ports:
      - port: 80
      selector:
        name: rebel-base
    

    Примените манифест:

    kubectl apply -f ./test-rio.yaml --kubeconfig ~/.dropapp/cluster-rio/kubeconfig
    
  2. Создайте манифест test-bravo.yaml для cluster-bravo:

    test-bravo.yaml
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: rebel-base
    spec:
      selector:
        matchLabels:
          name: rebel-base
      replicas: 2
      template:
        metadata:
          labels:
            name: rebel-base
        spec:
          containers:
          - name: rebel-base
            image: docker.io/nginx:1.27.1
            volumeMounts:
              - name: html
                mountPath: /usr/share/nginx/html/
            livenessProbe:
              httpGet:
                path: /
                port: 80
              periodSeconds: 1
            readinessProbe:
              httpGet:
                path: /
                port: 80
          volumes:
            - name: html
              configMap:
                name: rebel-base-response
                items:
                  - key: message
                    path: index.html
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: rebel-base-response
    data:
      message: "{\"Galaxy\": \"Bravo\", \"Cluster\": \"cluster-bravo\"}\n"
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: x-wing
    spec:
      selector:
        matchLabels:
          name: x-wing
      replicas: 2
      template:
        metadata:
          labels:
            name: x-wing
        spec:
          containers:
          - name: x-wing-container
            image: quay.io/cilium/json-mock:v1.3.8@sha256:<cilium-sha>
            livenessProbe:
              exec:
                command:
                - curl
                - -sS
                - -o
                - /dev/null
                - localhost
            readinessProbe:
              exec:
                command:
                - curl
                - -sS
                - -o
                - /dev/null
                - localhost
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: rebel-base
      annotations:
        service.cilium.io/global: "true"
    spec:
      type: ClusterIP
      ports:
      - port: 80
      selector:
        name: rebel-base
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: rebel-base-headless
      annotations:
        service.cilium.io/global: "true"
        service.cilium.io/global-sync-endpoint-slices: "true"
    spec:
      type: ClusterIP
      clusterIP: None
      ports:
      - port: 80
      selector:
        name: rebel-base
    

    Примените манифест:

    kubectl apply -f ./test-bravo.yaml --kubeconfig ~/.dropapp/cluster-bravo/kubeconfig
    
  3. Создайте манифест test-marco.yaml для cluster-marco:

    test-marco.yaml
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: rebel-base
    spec:
      selector:
        matchLabels:
          name: rebel-base
      replicas: 2
      template:
        metadata:
          labels:
            name: rebel-base
        spec:
          containers:
          - name: rebel-base
            image: docker.io/nginx:1.27.1
            volumeMounts:
              - name: html
                mountPath: /usr/share/nginx/html/
            livenessProbe:
              httpGet:
                path: /
                port: 80
              periodSeconds: 1
            readinessProbe:
              httpGet:
                path: /
                port: 80
          volumes:
            - name: html
              configMap:
                name: rebel-base-response
                items:
                  - key: message
                    path: index.html
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: rebel-base-response
    data:
      message: "{\"Galaxy\": \"Marco\", \"Cluster\": \"cluster-marco\"}\n"
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: x-wing
    spec:
      selector:
        matchLabels:
          name: x-wing
      replicas: 2
      template:
        metadata:
          labels:
            name: x-wing
        spec:
          containers:
          - name: x-wing-container
            image: quay.io/cilium/json-mock:v1.3.8@sha256:<cilium-sha>
            livenessProbe:
              exec:
                command:
                - curl
                - -sS
                - -o
                - /dev/null
                - localhost
            readinessProbe:
              exec:
                command:
                - curl
                - -sS
                - -o
                - /dev/null
                - localhost
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: rebel-base
      annotations:
        service.cilium.io/global: "true"
    spec:
      type: ClusterIP
      ports:
      - port: 80
      selector:
        name: rebel-base
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: rebel-base-headless
      annotations:
        service.cilium.io/global: "true"
        service.cilium.io/global-sync-endpoint-slices: "true"
    spec:
      type: ClusterIP
      clusterIP: None
      ports:
      - port: 80
      selector:
        name: rebel-base
    

    Примените манифест:

    kubectl apply -f ./test-marco.yaml --kubeconfig ~/.dropapp/cluster-marco/kubeconfig
    
  4. Для проверки работоспособности ClusterMesh, на любом кластере, например, cluster-rio, запустите pod (netshoot), который будет слать запросы в «свой» сервис rebel-base:

    kubectl --kubeconfig ~/.dropapp/cluster-rio/kubeconfig run --restart Never --rm -it --image nicolaka/netshoot netshoot -- /bin/sh -c 'for i in $$(seq 1 100); do curl http://rebel-base/; done'
    

    В примере вывода ответы идут из разных кластеров:

    Пример вывода
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Rio", "Cluster": "cluster-rio"}
    {"Galaxy": "Marco", "Cluster": "cluster-marco"}
    {"Galaxy": "Bravo", "Cluster": "cluster-bravo"}
    pod "netshoot" deleted
    

    ClusterMesh работает.

  5. Убедитесь, что в Hubble-ui данные о пакетах учитывают все 3 кластера:

Hubble-ui ClusterMesh