Configurar uma malha de vários clusters no Cloud Service Mesh gerenciado

<0x0

Neste guia, explicamos como mesclar dois clusters em um único Cloud Service Mesh usando a AC da malha ou o Certificate Authority Service e ativar o balanceamento de carga entre clusters. É possível ampliar facilmente esse processo para incorporar qualquer quantidade de clusters na malha.

Uma configuração do Cloud Service Mesh de vários clusters pode resolver vários cenários empresariais cruciais, como escalonamento, local e isolamento. Para mais informações, consulte Casos de uso de vários clusters.

Pré-requisitos

Este guia pressupõe que você tenha dois ou mais Google Cloud clusters do GKE que atendam aos seguintes requisitos:

  • O Cloud Service Mesh instalado nos clusters. Você precisa de asmcli, da ferramenta istioctl e de amostras que asmcli faz o download para o diretório especificado em --output_dir.
  • Os clusters na malha precisam ter conectividade entre todos os pods antes de configurar o Cloud Service Mesh. Além disso, você mesclar clusters que não estejam no mesmo projeto, eles vão precisar ser registrados no mesmo projeto host da frota, e será preciso que os clusters estejam em uma configuração do projeto de VPC compartilhada na mesma rede. Também recomendamos que você tenha um projeto para hospedar a VPC compartilhada e dois projetos de serviço para criar clusters. Para mais informações, consulte Como configurar clusters com a VPC compartilhada.
  • Se você usar o Certificate Authority Service, todos os clusters precisarão ter a respectiva cadeia de pools de CAs subordinados ao mesmo pool de CAs raiz. Caso contrário, todos precisarão usar o mesmo pool de CAs.

Como definir variáveis de projeto e de cluster

  1. Crie as seguintes variáveis de ambiente para o ID do projeto, a zona ou região do cluster, o nome do cluster e o contexto.

    export PROJECT_1=PROJECT_ID_1
    export LOCATION_1=CLUSTER_LOCATION_1
    export CLUSTER_1=CLUSTER_NAME_1
    export CTX_1="gke_${PROJECT_1}_${LOCATION_1}_${CLUSTER_1}"
    
    export PROJECT_2=PROJECT_ID_2
    export LOCATION_2=CLUSTER_LOCATION_2
    export CLUSTER_2=CLUSTER_NAME_2
    export CTX_2="gke_${PROJECT_2}_${LOCATION_2}_${CLUSTER_2}"
    
  2. Se esses forem clusters recém-criados, busque credenciais para cada cluster com os seguintes comandos gcloud. Caso contrário, o context associado não estará disponível para uso nas próximas etapas deste guia.

    Os comandos dependem do tipo de cluster, por região ou por zona:

    Regional

    gcloud container clusters get-credentials ${CLUSTER_1} --region ${LOCATION_1}
    gcloud container clusters get-credentials ${CLUSTER_2} --region ${LOCATION_2}
    

    Zonal

    gcloud container clusters get-credentials ${CLUSTER_1} --zone ${LOCATION_1}
    gcloud container clusters get-credentials ${CLUSTER_2} --zone ${LOCATION_2}
    

Criar regra de firewall

Em alguns casos, você precisa criar uma regra de firewall para permitir o tráfego entre clusters. Por exemplo, você precisa criar uma regra de firewall se:

  • Você usa sub-redes diferentes para os clusters na malha.
  • Os pods abrem portas diferentes de 443 e 15002.

O GKE adiciona automaticamente regras de firewall a cada nó para permitir o tráfego dentro da mesma sub-rede. Se a malha contiver várias sub-redes, será necessário configurar explicitamente as regras de firewall para permitir o tráfego entre as sub-redes. Você precisa adicionar uma nova regra de firewall para cada sub-rede para permitir os bloqueios de CIDR de IP de origem e segmentar as portas de todo o tráfego de entrada.

As instruções a seguir permitem a comunicação entre todos os clusters no projeto ou apenas entre $CLUSTER_1 e $CLUSTER_2.

  1. Reúna informações sobre a rede dos clusters.

    Todos os clusters de projeto

    Se os clusters estiverem no mesmo projeto, use o seguinte comando para permitir a comunicação entre todos os clusters no projeto. Se você não quiser expor alguns clusters no seu projeto, use o comando na guia Clusters específicos.

    function join_by { local IFS="$1"; shift; echo "$*"; }
    ALL_CLUSTER_CIDRS=$(gcloud container clusters list --project $PROJECT_1 --format='value(clusterIpv4Cidr)' | sort | uniq)
    ALL_CLUSTER_CIDRS=$(join_by , $(echo "${ALL_CLUSTER_CIDRS}"))
    ALL_CLUSTER_NETTAGS=$(gcloud compute instances list --project $PROJECT_1 --format='value(tags.items.[0])' | sort | uniq)
    ALL_CLUSTER_NETTAGS=$(join_by , $(echo "${ALL_CLUSTER_NETTAGS}"))
    

    Clusters específicos

    O comando a seguir permite a comunicação entre $CLUSTER_1 e $CLUSTER_2 e não expõe outros clusters no projeto.

    function join_by { local IFS="$1"; shift; echo "$*"; }
    ALL_CLUSTER_CIDRS=$(for P in $PROJECT_1 $PROJECT_2; do gcloud --project $P container clusters list --filter="name:($CLUSTER_1,$CLUSTER_2)" --format='value(clusterIpv4Cidr)'; done | sort | uniq)
    ALL_CLUSTER_CIDRS=$(join_by , $(echo "${ALL_CLUSTER_CIDRS}"))
    ALL_CLUSTER_NETTAGS=$(for P in $PROJECT_1 $PROJECT_2; do gcloud --project $P compute instances list  --filter="name:($CLUSTER_1,$CLUSTER_2)" --format='value(tags.items.[0])' ; done | sort | uniq)
    ALL_CLUSTER_NETTAGS=$(join_by , $(echo "${ALL_CLUSTER_NETTAGS}"))
    
  2. Crie a regra de firewall.

    GKE

    gcloud compute firewall-rules create istio-multicluster-pods \
        --allow=tcp,udp,icmp,esp,ah,sctp \
        --direction=INGRESS \
        --priority=900 \
        --source-ranges="${ALL_CLUSTER_CIDRS}" \
        --target-tags="${ALL_CLUSTER_NETTAGS}" --quiet \
        --network=YOUR_NETWORK
    

    Piloto automático

    TAGS=""
    for CLUSTER in ${CLUSTER_1} ${CLUSTER_2}
    do
        TAGS+=$(gcloud compute firewall-rules list --filter="Name:$CLUSTER*" --format="value(targetTags)" | uniq) && TAGS+=","
    done
    TAGS=${TAGS::-1}
    echo "Network tags for pod ranges are $TAGS"
    
    gcloud compute firewall-rules create asm-multicluster-pods \
        --allow=tcp,udp,icmp,esp,ah,sctp \
        --network=gke-cluster-vpc \
        --direction=INGRESS \
        --priority=900 --network=VPC_NAME \
        --source-ranges="${ALL_CLUSTER_CIDRS}" \
        --target-tags=$TAGS
    

Configurar a descoberta de endpoints

Ativar a descoberta de endpoints entre clusters públicos ou particulares com a API declarativa

Ativar o Cloud Service Mesh gerenciado com a API Fleet vai ativar a descoberta de endpoints para este cluster. Se você provisionou o Cloud Service Mesh gerenciado com uma ferramenta diferente, é possível ativar manualmente a descoberta de endpoints em clusters públicos ou particulares em uma frota aplicando a configuração "multicluster_mode":"connected" no configmap asm-options. Os clusters com essa configuração ativada na mesma frota terão a descoberta de serviços entre clusters ativada automaticamente entre si.

Essa é a única maneira de configurar a descoberta de endpoints em vários clusters se você tiver a implementação do plano de controle gerenciada (TD) e a maneira recomendada de configurá-la se você tiver a implementação gerenciada (Istiod).

Antes de continuar, crie uma regra de firewall.

Ativar

Se o configmap asm-options já existe no seu cluster, ative a descoberta de endpoints para o cluster:

      kubectl patch configmap/asm-options -n istio-system --type merge -p '{"data":{"multicluster_mode":"connected"}}'

Se o configmap asm-options ainda não existe no cluster, crie-o com os dados associados e ative a descoberta de endpoints para o cluster:

      kubectl --context ${CTX_1} create configmap asm-options -n istio-system --from-file <(echo '{"data":{"multicluster_mode":"connected"}}')

Desativar

Desativar a descoberta de endpoints para um cluster:

      kubectl patch configmap/asm-options -n istio-system --type merge -p '{"data":{"multicluster_mode":"manual"}}'

Se você cancelar o registro de um cluster na frota sem desativar a descoberta de endpoints, os secrets poderão permanecer no cluster. Você precisa limpar manualmente todos os secrets restantes.

  1. Execute o seguinte comando para encontrar secrets que precisam de limpeza:

    kubectl get secrets -n istio-system -l istio.io/owned-by=mesh.googleapis.com,istio/multiCluster=true
    
  2. Exclua cada secret:

    kubectl delete secret SECRET_NAME
    

    Repita essa etapa para cada secret restante.

Verificar a conectividade entre vários clusters

Nesta seção, você verá como implantar os serviços de amostra HelloWorld e Sleep no ambiente de vários clusters para verificar se o balanceamento de carga entre eles funciona.

Definir variável para diretório de amostras

  1. Navegue até o local onde asmcli foi salvo e execute o seguinte comando para configurar o ASM_VERSION

    export ASM_VERSION="$(./asmcli --version)"
    
  2. Defina uma pasta de trabalho para as amostras que você usa para verificar se o balanceamento de carga entre clusters funciona. As amostras estão localizadas em um subdiretório, no diretório --output_dir que você especificou no comando asmcli install. No comando a seguir, altere OUTPUT_DIR para o diretório especificado em --output_dir.

    export SAMPLES_DIR=OUTPUT_DIR/istio-${ASM_VERSION%+*}
    

Ative a injeção de sidecar

  1. Crie o namespace de amostra em cada cluster.

    for CTX in ${CTX_1} ${CTX_2}
    do
        kubectl create --context=${CTX} namespace sample
    done
    
  2. Ativar o namespace para injeção. As etapas dependem da sua implementação do plano de controle.

    Gerenciado (TD)

    1. Aplique o rótulo de injeção padrão ao namespace:
    for CTX in ${CTX_1} ${CTX_2}
    do
       kubectl label --context=${CTX} namespace sample \
          istio.io/rev- istio-injection=enabled --overwrite
    done
    

    Gerenciado (Istiod)

    Recomendado:execute o comando a seguir para aplicar o rótulo de injeção padrão ao namespace:

     for CTX in ${CTX_1} ${CTX_2}
     do
        kubectl label --context=${CTX} namespace sample \
           istio.io/rev- istio-injection=enabled --overwrite
     done
    

    Se você já usa o plano de controle gerenciado do Istiod:recomendamos usar a injeção padrão, mas a injeção baseada em revisão é compatível. Siga estas instruções:

    1. Execute o comando a seguir para localizar os canais de lançamento disponíveis:

      kubectl -n istio-system get controlplanerevision
      

      O resultado será o seguinte:

      NAME                AGE
      asm-managed-rapid   6d7h
      

      Na saída, o valor na coluna NAME é o rótulo de revisão que corresponde ao canal de lançamento disponível para a versão do Cloud Service Mesh.

    2. Aplique o rótulo de revisão ao namespace:

      for CTX in ${CTX_1} ${CTX_2}
      do
        kubectl label --context=${CTX} namespace sample \
           istio-injection- istio.io/rev=REVISION_LABEL --overwrite
      done
      

Instalar o serviço HelloWorld

  • Crie o serviço HelloWorld em ambos os clusters:

    kubectl create --context=${CTX_1} \
        -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \
        -l service=helloworld -n sample
    
    kubectl create --context=${CTX_2} \
        -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \
        -l service=helloworld -n sample
    

Implantar o HelloWorld v1 e v2 em cada cluster

  1. Implante HelloWorld v1 em CLUSTER_1 e v2 em CLUSTER_2. Isso ajudará a verificar o balanceamento de carga entre clusters posteriormente:

    kubectl create --context=${CTX_1} \
      -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \
      -l version=v1 -n sample
    kubectl create --context=${CTX_2} \
      -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \
      -l version=v2 -n sample
  2. Confirme se HelloWorld v1 e v2 estão em execução usando os seguintes comandos. Verifique se a saída é semelhante a esta:

    kubectl get pod --context=${CTX_1} -n sample
    NAME                            READY     STATUS    RESTARTS   AGE
    helloworld-v1-86f77cd7bd-cpxhv  2/2       Running   0          40s
    kubectl get pod --context=${CTX_2} -n sample
    NAME                            READY     STATUS    RESTARTS   AGE
    helloworld-v2-758dd55874-6x4t8  2/2       Running   0          40s

Implantar o serviço Sleep

  1. Implante o serviço Sleep nos dois clusters. Esse pod gera tráfego de rede artificial para fins de demonstração:

    for CTX in ${CTX_1} ${CTX_2}
    do
        kubectl apply --context=${CTX} \
            -f ${SAMPLES_DIR}/samples/sleep/sleep.yaml -n sample
    done
    
  2. Aguarde a inicialização do serviço Sleep em cada cluster. Verifique se a saída é semelhante a esta:

    kubectl get pod --context=${CTX_1} -n sample -l app=sleep
    NAME                             READY   STATUS    RESTARTS   AGE
    sleep-754684654f-n6bzf           2/2     Running   0          5s
    kubectl get pod --context=${CTX_2} -n sample -l app=sleep
    NAME                             READY   STATUS    RESTARTS   AGE
    sleep-754684654f-dzl9j           2/2     Running   0          5s

Verificar o balanceamento de carga entre clusters

Chame o serviço HelloWorld várias vezes e confira o resultado para verificar as respostas alternadas da v1 e da v2:

  1. Chame o serviço HelloWorld:

    kubectl exec --context="${CTX_1}" -n sample -c sleep \
        "$(kubectl get pod --context="${CTX_1}" -n sample -l \
        app=sleep -o jsonpath='{.items[0].metadata.name}')" \
        -- /bin/sh -c 'for i in $(seq 1 20); do curl -sS helloworld.sample:5000/hello; done'
    

    A resposta será semelhante a esta:

    Hello version: v2, instance: helloworld-v2-758dd55874-6x4t8
    Hello version: v1, instance: helloworld-v1-86f77cd7bd-cpxhv
    ...
  2. Chame o serviço HelloWorld novamente:

    kubectl exec --context="${CTX_2}" -n sample -c sleep \
        "$(kubectl get pod --context="${CTX_2}" -n sample -l \
        app=sleep -o jsonpath='{.items[0].metadata.name}')" \
        -- /bin/sh -c 'for i in $(seq 1 20); do curl -sS helloworld.sample:5000/hello; done'
    

    A resposta será semelhante a esta:

    Hello version: v2, instance: helloworld-v2-758dd55874-6x4t8
    Hello version: v1, instance: helloworld-v1-86f77cd7bd-cpxhv
    ...

Parabéns, você verificou seu balanceamento de carga com vários clusters do Cloud Service Mesh!

Manter o tráfego no cluster

Em alguns casos, o comportamento padrão de balanceamento de carga entre clusters não é desejável. Para manter o tráfego "local do cluster" (ou seja, o tráfego enviado de cluster-a só vai chegar a destinos em cluster-a), marque nomes de host ou caracteres curinga como clusterLocal usando MeshConfig.serviceSettings.

Por exemplo, é possível aplicar o tráfego local do cluster a um serviço individual, a todos os serviços em um namespace específico ou globalmente a todos os serviços na malha, da seguinte maneira:

por serviço

serviceSettings:
- settings:
    clusterLocal: true
  hosts:
  - "mysvc.myns.svc.cluster.local"

per-namespace

serviceSettings:
- settings:
    clusterLocal: true
  hosts:
  - "*.myns.svc.cluster.local"

global

serviceSettings:
- settings:
    clusterLocal: true
  hosts:
  - "*"

Também é possível refinar o acesso ao serviço definindo uma regra global local do cluster e adicionando exceções explícitas, que podem ser específicas ou de caractere curinga. No exemplo a seguir, todos os serviços no cluster serão mantidos locais para o cluster, exceto qualquer serviço no namespace myns:

serviceSettings:
- settings:
    clusterLocal: true
  hosts:
  - "*"
- settings:
    clusterLocal: false
  hosts:
  - "*.myns.svc.cluster.local"

Ativar o serviço de cluster local

  1. Verifique o mapa de configuração do MeshConfig no cluster

    kubectl get configmap -n istio-system
    

    Você vai encontrar um mapa de configuração com um dos nomes istio-asm-managed, istio-asm-managed-rapid ou istio-asm-managed-stable.

    Se você migrou da implementação ISTIOD para a TRAFFIC_DIRECTOR, talvez veja mais de um mapa de configuração. Nesse caso, execute o seguinte comando para determinar o canal:

    kubectl get controlplanerevision -n istio-system
    

    O canal da revisão reconciliada do plano de controle é o que você quer escolher.

  2. Atualizar o ConfigMap

    cat <<EOF > config.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: CONFIGMAP_NAME
      namespace: istio-system
    data:
      mesh: |-
        serviceSettings:
        - settings:
            clusterLocal: true
          hosts:
          - "*"
    EOF
    

    Substitua CONFIGMAP_NAME pelo nome do mapa de configuração encontrado na etapa 1 e atualize o mapa.

    kubectl apply --context=${CTX_1} -f config.yaml
    
  3. Confirme se o recurso de cluster local está funcionando conforme o esperado usando os comandos a seguir. A saída ao chamar HelloWorld com CTX_1 é semelhante a esta:

    kubectl exec --context="${CTX_1}" -n sample -c sleep \
        "$(kubectl get pod --context="${CTX_1}" -n sample -l \
        app=sleep -o jsonpath='{.items[0].metadata.name}')" \
        -- /bin/sh -c 'for i in $(seq 1 20); do curl -sS helloworld.sample:5000/hello; done'
    

    Você vai encontrar Only v1 is response na saída:

    Hello version: v1, instance: helloworld-v2-758dd55874-6x4t8
    Hello version: v1, instance: helloworld-v1-86f77cd7bd-cpxhv
    ...
    

    Se você ligar para o HelloWorld com o CTX_2:

    kubectl exec --context="${CTX_2}" -n sample -c sleep \
        "$(kubectl get pod --context="${CTX_2}" -n sample -l \
        app=sleep -o jsonpath='{.items[0].metadata.name}')" \
        -- /bin/sh -c 'for i in $(seq 1 20); do curl -sS helloworld.sample:5000/hello; done'
    

    Você vai ver respostas alternadas da v1 e da v2 na saída.

    Hello version: v2, instance: helloworld-v2-758dd55874-6x4t8
    Hello version: v1, instance: helloworld-v1-86f77cd7bd-cpxhv
    ...
    

Limpar o serviço HelloWorld

Quando terminar de verificar o balanceamento de carga, remova os serviços HelloWorld e Sleep do seu cluster.

kubectl delete ns sample --context ${CTX_1}
kubectl delete ns sample --context ${CTX_2}

Configurar o balanceamento de carga da região administrativa

Com o Cloud Service Mesh, é possível controlar como o tráfego é distribuído entre diferentes regiões e zonas usando comportamentos de roteamento principais:

  • SPRAY_TO_WORLD (padrão):o tráfego é distribuído usando o round-robin em todos os endpoints íntegros globalmente, independente de onde o cliente esteja localizado.
  • WATERFALL_BY_ZONE:o tráfego é roteado para a zona íntegra mais próxima. Se a zona local não estiver íntegra, o tráfego será encaminhado para a próxima zona íntegra mais próxima.

Este guia explica como configurar esses comportamentos usando os recursos DestinationRule e MeshConfig do Istio.

Ativar o roteamento em cascata

Para acionar WATERFALL_BY_ZONE, os usuários precisam configurar DestinationRule.TrafficPolicy.outlierDetection E uma das seguintes opções em DestinationRule (ou globalmente em MeshConfig):

  • localityLbSetting.enabled como true
  • localityLbSetting.failover.from
  • localityLbSetting.failover.to

Como ativar a hierarquia usando DestinationRule

A seguir, descrevemos uma configuração que ativa o roteamento em cascata para um serviço específico usando um DestinationRule:

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: backend-waterfall-rule
spec:
  host: my-backend.default.svc.cluster.local
  trafficPolicy:
    outlierDetection: {}
    loadBalancer:
      localityLbSetting:
        enabled: true # or failover: []

Como ativar a hierarquia usando MeshConfig + DestinationRule

Como alternativa, é possível ativar o balanceamento de carga de localidade globalmente para toda a malha no cluster usando o MeshConfig (por exemplo, no ConfigMap istio-asm-managed). Quando localityLbSetting é aplicado a toda a malha, os DestinationRules individuais só precisam de um bloco outlierDetection vazio para acionar o roteamento em cascata.

Os arquivos YAML a seguir fornecem um exemplo de MeshConfig e DestinationRule para essa configuração:

MeshConfig (Global):

apiVersion: v1
kind: ConfigMap
metadata:
  name: istio-asm-managed
  namespace: istio-system
data:
  mesh: |-
    localityLbSetting:
      enabled: true # or failover: []

DestinationRule (específico do serviço):

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: backend-waterfall-rule
spec:
  host: my-backend.default.svc.cluster.local
  trafficPolicy:
    outlierDetection: {}

Como aplicar configurações de localidade por versão da carga de trabalho

Como as configurações de localidade específicas de subconjunto e porta não são compatíveis com o DestinationRule, é possível usar várias definições de serviço do Kubernetes para fornecer configurações de roteamento por serviço e alcançar um comportamento de localidade mista.