Anthos Service Mesh의 멀티 클러스터 GKE 구성

이 가이드에서는 Mesh CA 또는 Citadel을 사용하여 두 개의 클러스터를 단일 Anthos Service Mesh로 조인하고 클러스터 간 부하 분산을 사용 설정하는 방법을 설명합니다. 이 프로세스를 손쉽게 확장하여 여러 개의 클러스터를 메시에 조인할 수 있습니다.

멀티 클러스터 Anthos Service Mesh 구성은 확장, 위치, 격리와 같은 중요한 기업 문제를 해결할 수 있습니다. 자세한 내용은 멀티 클러스터 사용 사례를 참조하세요. 또한 서비스 메시의 이점을 최대한 활용하려면 애플리케이션을 최적화해야 합니다. 자세한 내용은 Anthos Service Mesh용 애플리케이션 준비를 참조하세요.

기본 요건

이 가이드에서는 다음 요구사항을 충족하는 Google Cloud GKE 클러스터가 2개 이상 있다고 가정합니다.

  • 클러스터에 설치된 Anthos Service Mesh 버전 1.6.8 이상
    • 클러스터가 동일한 프로젝트에 있으면 설치 개요를 참조하여 클러스터를 필수 버전으로 설치하거나 업그레이드합니다.
    • 클러스터가 서로 다른 프로젝트에 있는 경우 다중 프로젝트 설치 및 마이그레이션을 참조하여 클러스터를 필수 버전으로 설치하거나 업그레이드합니다.
  • 동일한 프로젝트에 없는 클러스터를 조인하는 경우 해당 클러스터는 asm-gcp-multiproject 프로필을 사용해 설치되고 동일한 네트워크의 공유 VPC 구성에 있어야 합니다. 또한 공유 VPC를 호스트하는 프로젝트 1개와 클러스터를 생성하는 서비스 프로젝트 2개를 사용하는 것이 좋습니다. 자세한 내용은 공유 VPC를 사용하여 클러스터 설정을 참조하세요.
  • Citadel CA를 사용하는 경우 두 클러스터에 동일한 커스텀 루트 CA를 사용하세요.
  • Anthos Service Mesh가 비공개 클러스터에 빌드된 경우 동일한 VPC에 단일 서브넷을 생성하는 것이 좋습니다. 그렇지 않으면 다음을 확인합니다.
    1. 제어 영역은 클러스터 비공개 IP를 통해 원격 비공개 클러스터 제어 영역에 연결할 수 있습니다.
    2. 호출 제어 영역의 IP 범위를 원격 비공개 클러스터의 승인된 네트워크에 추가할 수 있습니다. 자세한 내용은 비공개 클러스터 간 엔드포인트 디스커버리 구성을 참조하세요.

프로젝트 및 클러스터 변수 설정

  1. 편의를 위해 작업 폴더를 설정합니다. 이 폴더는 기본 요건 단계인 Anthos Service Mesh 설치 준비 단계에서 Anthos Service Mesh 파일을 다운로드하여 추출한 폴더입니다.

    export PROJECT_DIR=YOUR_WORKING_FOLDER
  2. 다음과 같은 프로젝트 ID, 클러스터 영역 또는 리전, 클러스터 이름, 컨텍스트의 환경 변수를 만듭니다.

    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}"
    
  3. 새로 생성된 클러스터가 있으면 다음 gcloud 명령어로 각 클러스터에 대해 사용자 인증 정보를 가져와야 합니다. 그렇지 않으면 연결된 context를 이 가이드의 다음 단계에서 사용할 수 없습니다.

    gcloud container clusters get-credentials ${CLUSTER_1}
    gcloud container clusters get-credentials ${CLUSTER_2}
    

방화벽 규칙 만들기

일부 경우에는 클러스터 간 트래픽이 허용되도록 방화벽 규칙을 만들어야 합니다. 예를 들어 다음과 같은 경우 방화벽 규칙을 만들어야 합니다.

  • 메시의 클러스터에 다른 서브넷을 사용하는 경우
  • pod가 443 및 15002 외의 포트를 여는 경우

GKE는 각 노드에 방화벽 규칙을 자동으로 추가하여 동일한 서브넷 내의 트래픽을 허용합니다. 메시에 여러 서브넷이 포함된 경우, 서브넷 간 트래픽을 허용하도록 방화벽 규칙을 명시적으로 설정해야 합니다. 각 서브넷에 대해 새 방화벽 규칙을 추가하여 소스 IP CIDR 블록과 모든 수신 트래픽의 타겟 포트를 허용해야 합니다.

다음 안내를 따라 프로젝트의 모든 클러스터 간 통신 또는 $CLUSTER_1$CLUSTER_2 간 통신만 허용할 수 있습니다.

  1. 클러스터 네트워크에 대한 정보를 수집합니다.

    모든 프로젝트 클러스터

    클러스터가 같은 프로젝트에 있으면 다음 명령어를 사용하여 프로젝트의 모든 클러스터 간에 통신할 수 있습니다. 프로젝트에서 노출하지 않으려는 클러스터가 있으면 특정 클러스터 탭의 명령어를 사용합니다.

    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}"))
    

    특정 클러스터

    다음 명령어는 $CLUSTER_1$CLUSTER_2 간 통신을 허용하며 프로젝트의 다른 클러스터를 노출하지 않습니다.

    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. 방화벽 규칙 만들기

    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
    

    오토파일럿

    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
    

클러스터 간 엔드포인트 검색 구성

다음 명령어를 사용하여 클러스터 간 부하 분산의 엔드포인트 검색을 구성합니다. 이 단계에서는 다음 태스크를 수행합니다.

  • istioctl 명령어는 클러스터의 Kube API 서버에 대한 액세스 권한을 부여하는 보안 비밀을 만듭니다.
  • kubectl 명령어는 보안 비밀을 또 다른 클러스터에 적용하여 두 번째 클러스터에서 첫 번째 클러스터의 서비스 엔드포인트를 읽을 수 있도록 합니다.
istioctl x create-remote-secret --context=${CTX_1} --name=${CLUSTER_1} | \
  kubectl apply -f - --context=${CTX_2}
istioctl x create-remote-secret --context=${CTX_2} --name=${CLUSTER_2} | \
  kubectl apply -f - --context=${CTX_1}

비공개 클러스터 간 엔드포인트 디스커버리 구성

비공개 클러스터를 사용할 때는 공개 IP에 액세스할 수 없으므로 공개 IP 대신 원격 클러스터의 비공개 IP를 구성해야 합니다.

  1. 공개 IP를 사용하여 보안 비밀을 임시 파일에 씁니다.

    istioctl x create-remote-secret --context=${CTX_1} --name=${CLUSTER_1} > ${CTX_1}.secret
    
    istioctl x create-remote-secret --context=${CTX_2} --name=${CLUSTER_2} > ${CTX_2}.secret
    
  2. 비공개 클러스터의 비공개 IP를 검색하고 임시 파일의 보안 비밀에서 공개 IP를 비공개 IP로 바꿉니다.

    IFS="_" read -r -a VALS <<< ${CTX_1}
    PROJECT_1=${VALS[1]}
    LOCATION_1=${VALS[2]}
    CLUSTER_1=${VALS[3]}
    PRIV_IP=`gcloud container clusters describe "${CLUSTER_1}" --project "${PROJECT_1}" \
        --zone "${LOCATION_1}" --format "value(privateClusterConfig.privateEndpoint)"`
    sed -i 's/server\:.*/server\: https:\/\/'"${PRIV_IP}"'/' ${CTX_1}.secret
    
    IFS="_" read -r -a VALS <<< ${CTX_2}
    PROJECT_2=${VALS[1]}
    LOCATION_2=${VALS[2]}
    CLUSTER_2=${VALS[3]}
    PRIV_IP=`gcloud container clusters describe "${CLUSTER_2}" --project "${PROJECT_2}" \
        --zone "${LOCATION_2}" --format "value(privateClusterConfig.privateEndpoint)"`
    sed -i 's/server\:.*/server\: https:\/\/'"${PRIV_IP}"'/' ${CTX_2}.secret
    
  3. 클러스터에 새 보안 비밀을 적용합니다.

    kubectl apply -f ${CTX_1}.secret --context=${CTX_2}
    
    kubectl apply -f ${CTX_2}.secret --context=${CTX_1}
    

비공개 클러스터에 승인된 네트워크 구성

다음 조건이 모두 메시에 적용되는 경우에만 이 섹션을 따르세요.

Anthos Service Mesh에 여러 클러스터를 배포할 때 각 클러스터의 Istiod가 원격 클러스터의 GKE 제어 영역을 호출해야 합니다. 트래픽을 허용하려면 호출 클러스터의 pod 주소 범위를 원격 클러스터의 승인된 네트워크에 추가해야 합니다.

  1. 각 클러스터의 pod IP CIDR 블록을 가져옵니다.

    POD_IP_CIDR_1=`gcloud container clusters describe ${CLUSTER_1} --project ${PROJECT_1} --zone ${LOCATION_1} \
      --format "value(ipAllocationPolicy.clusterIpv4CidrBlock)"`
    
    POD_IP_CIDR_2=`gcloud container clusters describe ${CLUSTER_2} --project ${PROJECT_2} --zone ${LOCATION_2} \
      --format "value(ipAllocationPolicy.clusterIpv4CidrBlock)"`
    
  2. Kubernetes 클러스터 pod IP CIDR 블록을 원격 클러스터에 추가합니다.

    EXISTING_CIDR_1=`gcloud container clusters describe ${CLUSTER_1} --project ${PROJECT_1} --zone ${LOCATION_1} \
     --format "value(masterAuthorizedNetworksConfig.cidrBlocks.cidrBlock)"`
    gcloud container clusters update ${CLUSTER_1} --project ${PROJECT_1} --zone ${LOCATION_1} \
    --enable-master-authorized-networks \
    --master-authorized-networks ${POD_IP_CIDR_2},${EXISTING_CIDR_1//;/,}
    
    EXISTING_CIDR_2=`gcloud container clusters describe ${CLUSTER_2} --project ${PROJECT_2} --zone ${LOCATION_2} \
     --format "value(masterAuthorizedNetworksConfig.cidrBlocks.cidrBlock)"`
    gcloud container clusters update ${CLUSTER_2} --project ${PROJECT_2} --zone ${LOCATION_2} \
    --enable-master-authorized-networks \
    --master-authorized-networks ${POD_IP_CIDR_1},${EXISTING_CIDR_2//;/,}
    

    자세한 내용은 승인된 네트워크로 클러스터 만들기를 참조하세요.

  3. 승인된 네트워크가 업데이트되었는지 확인합니다.

    gcloud container clusters describe ${CLUSTER_1} --project ${PROJECT_1} --zone ${LOCATION_1} \
     --format "value(masterAuthorizedNetworksConfig.cidrBlocks.cidrBlock)"
    
    gcloud container clusters describe ${CLUSTER_2} --project ${PROJECT_2} --zone ${LOCATION_2} \
     --format "value(masterAuthorizedNetworksConfig.cidrBlocks.cidrBlock)"
    

제어 영역 전역 액세스 사용 설정

다음 조건이 모두 메시에 적용되는 경우에만 이 섹션을 따르세요.

  • 비공개 클러스터를 사용 중임
  • 메시의 클러스터에 다른 리전을 사용합니다.

각 클러스터의 istiod가 원격 클러스터의 GKE 제어 영역을 호출할 수 있도록 제어 영역 전역 액세스를 사용 설정해야 합니다.

  1. 제어 영역 전역 액세스 사용 설정

    gcloud container clusters update ${CLUSTER_1} --project ${PROJECT_1} --zone ${LOCATION_1} \
     --enable-master-global-access
    
    gcloud container clusters update ${CLUSTER_2} --project ${PROJECT_2} --zone ${LOCATION_2} \
     --enable-master-global-access
    
  2. 제어 영역 전역 액세스가 사용 설정되었는지 확인합니다.

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

    출력의 privateClusterConfig 섹션에는 masterGlobalAccessConfig의 상태가 표시됩니다.

배포 확인하기

이 섹션에서는 샘플 HelloWorld 서비스를 멀티 클러스터 환경에 배포하여 클러스터 간 부하 분산이 작동하는지 확인하는 방법을 설명합니다.

사이드카 삽입 사용 설정

  1. 다음 명령어를 사용하여 istiod 서비스에서 이후 단계에 사용할 버전 라벨 값을 찾습니다.

    kubectl -n istio-system get pods -l app=istiod --show-labels

    출력은 다음과 유사합니다.

    NAME                                READY   STATUS    RESTARTS   AGE   LABELS
    istiod-asm-173-3-5788d57586-bljj4   1/1     Running   0          23h   app=istiod,istio.io/rev=asm-173-3,istio=istiod,pod-template-hash=5788d57586
    istiod-asm-173-3-5788d57586-vsklm   1/1     Running   1          23h   app=istiod,istio.io/rev=asm-173-3,istio=istiod,pod-template-hash=5788d57586

    출력의 LABELS 열 아래에서 istio.io/rev= 프리픽스 다음에 있는 istiod 버전 라벨의 값을 확인합니다. 이 예시에서 값은 asm-173-3입니다. 다음 섹션의 단계에서 버전 값을 사용하세요.

HelloWorld 서비스 설치

각 클러스터에서 샘플 네임스페이스와 서비스 정의를 만듭니다. 다음 명령어에서 REVISION을 이전 단계에서 기록한 istiod 버전 라벨로 대체합니다.

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

여기서 REVISION은 이전에 기록한 istiod 버전 라벨입니다.

출력은 다음과 같습니다.

   label "istio-injection" not found.
   namespace/sample labeled
   

label "istio-injection" not found.를 안전하게 무시할 수 있습니다.

  1. 두 클러스터에서 HelloWorld 서비스를 만듭니다.

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

HelloWorld v1 및 v2를 각 클러스터에 배포

  1. 나중에 클러스터 간 부하 분산을 확인하는 데 도움이 되도록 HelloWorld v1CLUSTER_1에, v2CLUSTER_2에 배포합니다.

    kubectl create --context=${CTX_1} \
      -f ${PROJECT_DIR}/samples/helloworld/helloworld.yaml \
      -l version=v1 -n sample
    kubectl create --context=${CTX_2} \
      -f ${PROJECT_DIR}/samples/helloworld/helloworld.yaml \
      -l version=v2 -n sample
  2. 다음 명령어를 사용하여 HelloWorld v1v2가 실행 중인지 확인합니다. 출력이 다음과 비슷한지 확인합니다.

    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

Sleep 서비스 배포

  1. 두 클러스터에 Sleep 서비스를 배포합니다. 이 포드는 데모용으로 인위적인 네트워크 트래픽을 생성합니다.

    for CTX in ${CTX_1} ${CTX_2}
      do
        kubectl apply --context=${CTX} \
          -f ${PROJECT_DIR}/samples/sleep/sleep.yaml -n sample
      done
  2. 각 클러스터에서 Sleep 서비스가 시작될 때까지 기다립니다. 출력이 다음과 비슷한지 확인합니다.

    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

클러스터 간 부하 분산 확인

HelloWorld 서비스를 여러 번 호출하고 출력을 확인하여 v1과 v2에서 번갈아 응답을 보내는지 확인합니다.

  1. 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}')" \
        -- curl -sS helloworld.sample:5000/hello
    

    출력은 다음과 비슷합니다.

    Hello version: v2, instance: helloworld-v2-758dd55874-6x4t8
    Hello version: v1, instance: helloworld-v1-86f77cd7bd-cpxhv
    ...
  2. HelloWorld 서비스를 다시 호출합니다.

    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}')" \
        -- curl -sS helloworld.sample:5000/hello
    

    출력은 다음과 비슷합니다.

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

수고하셨습니다. 멀티 클러스터 Anthos Service Mesh의 부하 분산이 성공적으로 완료되었습니다.

HelloWorld 서비스 삭제

부하 분산이 확인되면 클러스터에서 HelloWorldSleep 모드를 삭제합니다.

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