Anthos Service Mesh를 사용한 GKE Enterprise 클러스터 간 통신 보안 및 암호화

Last reviewed 2021-04-30 UTC

이 튜토리얼에서는 Anthos Service Mesh 이그레스 및 인그레스 게이트웨이를 사용하여 상호 전송 계층 보안(mTLS)을 통해 클러스터 간 트래픽의 보안을 강화하는 방법을 설명합니다. 이 튜토리얼은 네트워크, 보안, 플랫폼 측면을 담당하는 Kubernetes 클러스터 관리자를 대상으로 합니다. 여기에서 설명하는 컨트롤은 보안 요구사항이 철저하거나 규제 요건을 충족해야 하는 조직에 특히 유용할 수 있습니다. 이 튜토리얼은 상호 보완적인 개념 가이드와 함께 제공됩니다.

이 튜토리얼에서는 사용자가 Kubernetes 및 Anthos Service Mesh에 익숙하다고 가정합니다.

목표

  • Terraform을 사용하여 인프라를 설정합니다.
    • 비공개 서브넷 2개가 포함된 커스텀 VPC 네트워크를 만듭니다.
    • Anthos Service Mesh가 사용 설정된 2개의 Kubernetes 클러스터를 만듭니다.
    • 클러스터를 GKE 허브에 등록합니다.
  • GKE 클러스터에 MySQL 클라이언트를 배포합니다.
  • kOps 클러스터에 MySQL 서버를 배포합니다.
  • mTLS를 사용하여 서버를 노출하도록 이그레스 및 인그레스 게이트웨이를 구성합니다.
  • 다른 클러스터 또는 VPC에서 실행되는 MySQL 클라이언트를 사용하여 MySQL 서버 액세스를 테스트합니다.

비용

이 문서에서는 비용이 청구될 수 있는 다음과 같은 Google Cloud 구성요소를 사용합니다.

프로젝트 사용량을 기준으로 예상 비용을 산출하려면 가격 계산기를 사용하세요. Google Cloud를 처음 사용하는 사용자는 무료 체험판을 사용할 수 있습니다.

이 문서에 설명된 태스크를 완료했으면 만든 리소스를 삭제하여 청구가 계속되는 것을 방지할 수 있습니다. 자세한 내용은 삭제를 참조하세요.

시작하기 전에

이 가이드에는 Google Cloud 프로젝트가 필요합니다. 새 프로젝트를 만들거나 기존에 만든 프로젝트를 선택할 수 있습니다.

  1. Google Cloud Console에서 프로젝트 선택기 페이지로 이동합니다.

    프로젝트 선택기로 이동

  2. Google Cloud 프로젝트를 선택하거나 만듭니다.

  3. Google Cloud 프로젝트에 결제가 사용 설정되어 있는지 확인합니다.

  4. Google Cloud 콘솔에서 Cloud Shell로 이동합니다.

    Cloud Shell로 이동

    Google Cloud 콘솔 하단에서 Cloud Shell 세션이 열리고 명령줄 프롬프트가 표시됩니다. Cloud Shell은 Google Cloud CLI가 포함되고 Google Cloud CLI가 사전 설치된 셸 환경입니다. 세션이 초기화되는 데 몇 초 정도 걸릴 수 있습니다.

  5. Cloud Shell에서 자신이 만들거나 선택한 프로젝트로 작업 중인지 확인합니다.
    export PROJECT_ID=PROJECT_ID
    gcloud config set project ${PROJECT_ID}
    

    PROJECT_ID를 프로젝트 ID로 바꿉니다.

  6. Google Cloud에 사용하는 이메일 주소의 환경 변수를 만듭니다.
    export GOOGLE_CLOUD_EMAIL_ADDRESS=GOOGLE_CLOUD_EMAIL_ADDRESS
    

    GOOGLE_CLOUD_EMAIL_ADDRESS를 Google Cloud에서 사용하는 이메일 주소로 바꿉니다.

  7. 컴퓨팅 리소스의 리전 및 영역을 설정합니다.
    export REGION=us-central1
    export ZONE=us-central1-b
    gcloud config set compute/region ${REGION}
    gcloud config set compute/zone ${ZONE}
    

    이 튜토리얼에서는 us-central1 리전과 us-central1-b 영역이 사용됩니다. 원하는 리전에 배포할 수 있습니다.

  8. 필요한 ID 및 액세스 관리(IAM) 역할을 설정합니다. 프로젝트 소유자인 경우 설치를 완료하는 데 필요한 모든 권한이 있습니다. 그렇지 않으면 Cloud Shell에서 다음 명령어를 실행하여 관리자에게 Identity and Access Management(IAM) 역할을 부여해 달라고 요청하세요.
    ROLES=(
    'roles/container.admin' \
    'roles/gkehub.admin' \
    'roles/iam.serviceAccountAdmin' \
    'roles/iam.serviceAccountKeyAdmin' \
    'roles/resourcemanager.projectIamAdmin' \
    'roles/compute.securityAdmin' \
    'roles/compute.instanceAdmin' \
    'roles/storage.admin' \
    'roles/serviceusage.serviceUsageAdmin'
    )
    for role in "${ROLES[@]}"
    do
     gcloud projects add-iam-policy-binding ${PROJECT_ID} \
      --member "user:${GOOGLE_CLOUD_EMAIL_ADDRESS}" \
      --role="$role"
    done
    
  9. 튜토리얼에 필요한 API를 사용 설정합니다.
    gcloud services enable \
        anthos.googleapis.com \
        anthosgke.googleapis.com \
        anthosaudit.googleapis.com \
        compute.googleapis.com \
        container.googleapis.com \
        cloudresourcemanager.googleapis.com \
        serviceusage.googleapis.com \
        stackdriver.googleapis.com \
        monitoring.googleapis.com \
        logging.googleapis.com \
        cloudtrace.googleapis.com \
        meshca.googleapis.com \
        meshconfig.googleapis.com \
        iamcredentials.googleapis.com \
        gkeconnect.googleapis.com \
        gkehub.googleapis.com
    

환경 준비

  1. Cloud Shell에서 다음 저장소를 클론합니다.

    git clone https://github.com/GoogleCloudPlatform/anthos-service-mesh-samples
    cd anthos-service-mesh-samples/docs/mtls-egress-ingress
    
  2. 환경에 대해 Terraform을 업데이트합니다. 기본적으로 Google Cloud 콘솔에서는 Terraform 0.12가 함께 제공됩니다. 이 튜토리얼에서는 Terraform 0.13.5 이상이 설치되었다고 가정합니다. 다음 명령어를 사용하면 일시적으로 다른 Terraform 버전을 사용할 수 있습니다.

    mkdir ~/bin
    curl https://releases.hashicorp.com/terraform/0.13.5/terraform_0.13.5_linux_amd64.zip -o ~/bin/terraform.zip
    unzip ~/bin/terraform.zip -d ~/bin/
    
  3. terraform 하위 폴더로 이동하고 Terraform을 초기화합니다.

    cd terraform/
    ~/bin/terraform init
    
  4. 앞에서 만든 환경 변수를 기준으로 terraform.tfvars 파일을 만듭니다.

    cat << EOF > terraform.tfvars
    project_id = "${PROJECT_ID}"
    region = "${REGION}"
    zones = ["${ZONE}"]
    EOF
    

    다음 단계에서 초기 인프라를 만듭니다. 이를 위해 이 구성에 대해 Terraform 실행 계획을 만들고 적용합니다. 이 계획의 스크립트 및 모듈은 다음을 만듭니다.

    • 비공개 서브넷 2개가 포함된 커스텀 VPC 네트워크
    • Anthos Service Mesh가 사용 설정된 Kubernetes 클러스터 2개
    • GKE 클러스터 1개
    • 커스텀 VPC 네트워크에서 실행되는 kOps 클러스터 1개

    실행 계획은 또한 GKE 허브에 클러스터를 등록합니다.

  5. 실행 계획을 실행합니다.

    ~/bin/terraform plan -out mtls-terraform-plan
    ~/bin/terraform apply "mtls-terraform-plan"
    

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

    Apply complete! Resources: 27 added, 0 changed, 0 destroyed.
    Outputs:
    server_token = <sensitive>
    

    <sensitive> 부분은 콘솔에 표시되지 않지만 쿼리될 수 있는 Terraform 출력 변수입니다. 예를 들면 ~/bin/terraform output server_token입니다.

  6. terraform 디렉터리에서 서버 클러스터 kubeconfig 파일을 가져옵니다. 그런 후 이를 client-cluster 구성 파일과 병합합니다.

    cd ..
    export KUBECONFIG=client-server-kubeconfig
    cp ./terraform/server-kubeconfig $KUBECONFIG
    gcloud container clusters get-credentials client-cluster --zone ${ZONE} --project ${PROJECT_ID}
    

    이제 client-server-kubeconfig 파일에 두 클러스터 모두의 구성이 포함됩니다. 다음 명령어를 실행하여 이를 확인할 수 있습니다.

    kubectl config view -ojson | jq -r '.clusters[].name'
    

    출력은 다음과 같습니다.

    gke_PROJECT_ID_us-central1-c_client-cluster
    server-cluster.k8s.local
    
  7. 나중에 사용할 수 있도록 클러스터 2개의 컨텍스트를 가져옵니다.

    export CLIENT_CLUSTER=$(kubectl config view -ojson | jq -r '.clusters[].name' | grep client)
    export SERVER_CLUSTER=$(kubectl config view -ojson | jq -r '.clusters[].name' | grep server)
    echo -e "${CLIENT_CLUSTER}\n${SERVER_CLUSTER}"
    

    출력은 다음과 같이 동일합니다.

    gke_PROJECT_ID_us-central1-c_client-cluster
    server-cluster.k8s.local
    

    이제 이 클러스터 이름을 추가 kubectl 명령어의 컨텍스트로 사용할 수 있습니다.

  8. 클라이언트 클러스터를 참조합니다.

    kubectl --context ${CLIENT_CLUSTER} get pods -n istio-system
    
  9. 서버 클러스터를 참조합니다.

    kubectl --context ${SERVER_CLUSTER} get pods -n istio-system
    

클라이언트 측 구성

개념 가이드의 설명과 같이 클라이언트 측에서는 Anthos Service Mesh에 이그레스 게이트웨이를 구성해야 합니다.

이 섹션에서는 원점을 기준으로 외부 트래픽을 식별하고 커스텀 인증서를 사용해서 통신을 암호화하도록 Anthos Service Mesh 요소를 구성합니다. 또한 특히 해당 대상(컨테이너에 있는 MySQL DB)으로만 트래픽을 라우팅해야 할 수 있습니다. 일반적으로 이렇게 하려면 Kubernetes의 서비스를 사용합니다. 이 경우 메시 통신 내에서 트래픽을 포착해야 합니다. 트래픽을 포착하려면 Istio 요소를 사용해서 서비스의 특수 정의를 만듭니다. 다음 요소를 정의합니다.

  • 이그레스 게이트웨이
  • 서비스 항목
  • 가상 서비스
  • TLS 인증서(보안 비밀)
  • 대상 규칙
  1. Cloud Shell에서 서버 측 컨텍스트(이전에 만든 $SERVER_CLUSTER)를 사용하여 istio-ingressgateway 서비스의 부하 분산기 IP 주소 쿼리를 실행하여 서버 측에서 인그레스 게이트웨이의 IP 주소를 가져옵니다.

    INGRESS_HOST=$(kubectl -n istio-system --context ${SERVER_CLUSTER} get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    

    INGRESS_HOST는 호스트의 IP 주소 부분일 뿐이므로 정규화된 도메인 이름(FQDN)을 만들어야 합니다. 정상적인 작동을 위해서는 인증서에 도메인 이름이 있어야 하므로 이 단계가 필요합니다.

    이 튜토리얼에서는 와일드 카드 DNS 서비스 nip.io를 사용하여 인그레스 IP 주소에 대해 FQDN을 만듭니다. 이 서비스를 사용하면 도메인을 소유하지 않고 FQDN을 만들 수 있습니다.

  2. FQDN 서비스 URL을 환경 변수에 저장합니다.

    export SERVICE_URL="${INGRESS_HOST}.nip.io"
    

    이제 SERVICE_URL이 FQDN으로 정의되어 있으면 클라이언트 클러스터의 Istio 부분을 정의할 수 있습니다.

이그레스 게이트웨이 만들기

먼저 외부 서비스용 트래픽을 리슨할 이그레스 게이트웨이를 만듭니다.

외부 서비스를 대상으로 하는 트래픽을 리슨하는 이그레스 게이트웨이

  1. Cloud Shell에서 다음 YAML 파일을 만들고 이름을 client-egress-gateway.yaml로 지정합니다.

    cat <<EOF > client-egress-gateway.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
     name: istio-egressgateway-mysql
    spec:
     selector:
       istio: egressgateway
     servers:
     - port:
         number: 15443
         name: tls
         protocol: TLS
       hosts:
       - $SERVICE_URL
       tls:
         mode: ISTIO_MUTUAL
    EOF
    
  2. 클라이언트 클러스터에 앞의 YAML 파일을 적용합니다.

    kubectl --context ${CLIENT_CLUSTER} apply -f client-egress-gateway.yaml
    

    포트에 주의하세요. 여기에서는 이그레스 서버 스위치에 대해 default 포트가 사용되었습니다. 이 포트는 15443에 해당합니다. 다른 포트를 사용하려면 이그레스 게이트웨이 service 객체를 수정하여 커스텀 포트를 추가해야 합니다.

    hosts 스위치는 엔드포인트, 즉 트래픽의 목적지를 정의합니다.

서비스 항목 정의

다음 단계는 외부 서비스에 대해 서비스 메시를 알리는 것입니다. Istio에는 메시에 대해 서비스 엔드포인트를 저장하는 고유 레지스트리가 있습니다. Istio가 Kubernetes 위에 설치된 경우 클러스터에 정의된 서비스가 Istio 레지스트리에 자동으로 추가됩니다. 서비스 항목 정의에 따라 다음 다이어그램에 표시된 대로 Istio 레지스트리에 새 엔드포인트를 추가합니다.

서비스 항목 정의를 사용하여 Istio 레지스트리에 엔드포인트를 추가합니다.

  1. Cloud Shell에서 다음 YAML 파일을 만들고 이름을 client-service-entry.yaml로 지정합니다.

    cat <<EOF > client-service-entry.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
     name: mysql-external
    spec:
     hosts:
       - $SERVICE_URL
     location: MESH_EXTERNAL
     ports:
       - number: 3306
         name: tcp
         protocol: TCP
       - number: 13306
         name: tls
         protocol: TLS
     resolution: DNS
     endpoints:
       - address: $SERVICE_URL
         ports:
           tls: 13306
    EOF
    
  2. 클라이언트 클러스터에 앞의 YAML 파일을 적용합니다.

    kubectl --context ${CLIENT_CLUSTER} apply -f client-service-entry.yaml
    

    이 YAML 파일의 클라이언트 서비스 정의는 서비스에 예상되는 트래픽 유형을 알려줍니다(MySQL L4 - 네트워크 레이어 4, 포트 3306을 사용). 또한 통신이 '메시 외부'로 전환될지 정의합니다. 엔드포인트 섹션에서 앞에서 설정한 서버 클러스터(kOps)의 인그레스 게이트웨이에 매핑된 FQDN 주소 $SERVICE_URL로 흐름이 가야 한다고 정의합니다.

가상 서비스 정의

가상 서비스는 호스트가 처리될 때 적용할 트래픽 라우팅 규칙 집합입니다. 각 라우팅 규칙은 특정 프로토콜의 트래픽에 대해 일치 규칙을 정의합니다. 트래픽이 일치하면 레지스트리에 정의된 이름 지정된 대상 서비스(또는 해당 서비스의 하위 집합 또는 버전)에 전송됩니다. 자세한 내용은 Istio 문서를 참조하세요.

외부 서비스에 연결하는 트래픽에 대해 라우팅을 적용하는 방법을 Istio에 알려주는 가상 서비스를 정의합니다.

가상 서비스 정의는 Istio가 외부 서비스에 도달하는 트래픽에 라우팅을 적용하는 방법을 Istio에 알려줍니다. 다음 정의를 통해 클라이언트에서 포트 15443의 이그레스 게이트웨이로 트래픽을 라우팅하도록 메시에 지시합니다. 이그레스 게이트웨이에서 서버 측 인그레스 게이트웨이가 리슨하는 포트 13306에서 호스트 $SERVICE_URL로 트래픽을 라우팅합니다.

  1. 다음 YAML 파일을 만들고 이름을 client-virtual-service.yaml로 지정합니다.

    cat <<EOF > client-virtual-service.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
     name: direct-mysql-through-egress-gateway
    spec:
     hosts:
       - $SERVICE_URL
     gateways:
       - istio-egressgateway-mysql
       - mesh
     tcp:
       - match:
           - gateways:
               - mesh
             port: 3306
         route:
           - destination:
               host: istio-egressgateway.istio-system.svc.cluster.local
               subset: mysql
               port:
                 number: 15443
             weight: 100
       - match:
           - gateways:
               - istio-egressgateway-mysql
             port: 15443
         route:
           - destination:
               host: $SERVICE_URL
               port:
                 number: 13306
             weight: 100
    EOF
    
  2. 클라이언트 클러스터에 YAML 정의를 적용합니다.

    kubectl --context ${CLIENT_CLUSTER} apply -f client-virtual-service.yaml
    

    YAML 파일에서 gateways 스위치를 수정하여 구성을 적용할 게이트웨이를 지정할 수 있습니다.

    이 정의에서 중요한 부분은 메시에서 모든 사이드카를 암시하는 예약어 mesh의 사용입니다. Istio 문서에 따라, 이 필드를 생략하면 기본 게이트웨이(메시)가 사용되고, 메시의 모든 사이드카에 규칙이 적용됩니다. 게이트웨이 이름 목록을 제공하면 규칙이 해당 게이트웨이에만 적용됩니다. 규칙을 게이트웨이 및 사이드카에 적용하려면 게이트웨이 이름 중 하나로 mesh를 지정합니다.

다음 섹션에서는 클라이언트 프로덕션의 프록시 match.gateways.mesh에서 나가는 트래픽의 처리 방법을 정의합니다. 또한 match.gateways.istio-egressgateway-mysql 스위치를 사용하여 이그레스에서 외부 서비스로 트래픽을 라우팅하는 방법을 정의합니다.

대상 규칙 정의(클라이언트에서 이그레스 게이트웨이로)

외부 서비스에 대한 트래픽 라우팅 방법을 정의했으면 이제 적용할 트래픽 정책을 정의해야 합니다. 바로 전에 정의한 가상 서비스는 한 번에 2개의 라우팅 케이스를 처리합니다. 하나는 사이드카 프록시에서 이그레스 게이트웨이로의 트래픽을 처리하고, 다른 하나는 이그레스 게이트웨이에서 외부 서비스로의 트래픽을 처리합니다.

이러한 케이스를 대상 규칙과 일치시키려면 2개의 개별 규칙이 필요합니다. 다음 다이어그램은 프록시에서 이그레스 게이트웨이로의 트래픽을 처리하는 첫 번째 규칙을 보여줍니다. 이 정의에서는 Anthos Service Mesh에 mTLS 통신을 위해 기본 인증서를 사용하도록 지정합니다.

사이드카 프록시에서 이그레스 게이트웨이로 향하는 트래픽을 처리하는 방법을 정의하는 대상 규칙

  1. Cloud Shell에서 다음 YAML 파일을 만들고 이름을 client-destination-rule-to-egress-gateway.yaml로 지정합니다.

    cat <<EOF > client-destination-rule-to-egress-gateway.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: egressgateway-for-mysql
    spec:
      host: istio-egressgateway.istio-system.svc.cluster.local
      subsets:
        - name: mysql
          trafficPolicy:
            loadBalancer:
              simple: ROUND_ROBIN
            portLevelSettings:
              - port:
                  number: 15443
                tls:
                  mode: ISTIO_MUTUAL
                  sni: $SERVICE_URL
    EOF
    
  2. 클라이언트 클러스터에 앞의 YAML 정의를 적용합니다.

    kubectl --context ${CLIENT_CLUSTER} apply -f client-destination-rule-to-egress-gateway.yaml
    

    앞의 YAML 파일에서는 hosts 스위치를 사용하여 클라이언트 프록시에서 이그레스 게이트웨이로의 트래픽 라우팅 방법을 정의했습니다. 또한 이그레스 게이트웨이에서 자동으로 열리는 포트 중 하나인 포트 15443으로 ISTIO_MUTUAL을 사용하도록 메시를 구성했습니다.

대상 규칙 만들기(이그레스 게이트웨이에서 외부 서비스로)

다음 다이어그램은 이그레스 게이트웨이에서 외부 서비스로의 트래픽을 처리하는 방법을 메시에 알려주는 두 번째 대상 규칙을 보여줍니다.

이그레스 게이트웨이에서 외부 서비스로 트래픽을 처리하는 방법을 정의하는 두 번째 대상 규칙입니다.

메시가 외부 서비스와의 상호 TLS 통신에 삽입된 인증서를 사용하도록 지시해야 합니다.

  1. Cloud Shell에서 anthos-service-mesh-samples/docs/mtls-egress-ingress 디렉터리에 인증서를 만듭니다.

     ./create-keys.sh
    

    스크립트에서 요청이 표시되면 비밀번호를 제공해야 합니다.

  2. 생성된 파일을 현재 디렉터리에 복사합니다.

    cp ./certs/2_intermediate/certs/ca-chain.cert.pem ca-chain.cert.pem
    cp ./certs/4_client/private/$SERVICE_URL.key.pem client-$SERVICE_URL.key.pem
    cp ./certs/4_client/certs/$SERVICE_URL.cert.pem client-$SERVICE_URL.cert.pem
    
  3. 나중에 게이트웨이에서 참조할 수 있도록 인증서를 저장하는 Kubernetes 보안 비밀을 만듭니다.

     kubectl --context ${CLIENT_CLUSTER} create secret -n istio-system \
      generic client-credential \
      --from-file=tls.key=client-$SERVICE_URL.key.pem \
      --from-file=tls.crt=client-$SERVICE_URL.cert.pem \
      --from-file=ca.crt=ca-chain.cert.pem
    

    앞의 명령어는 다음 인증서 파일을 보안 비밀에 추가합니다.

    client-$SERVICE_URL.key.pem
    client-$SERVICE_URL.cert.pem
    ca-chain.cert.pem
    

    여기에서는 인증서 배포에 대한 최신 권장사항에 따라 파일 마운트 대신 보안 비밀 검색 서비스(SDS)를 사용합니다. 이렇게 하면 새 인증서를 추가할 때 pod를 다시 시작할 필요가 없습니다. Istio 1.8/1.9부터는 이 방법을 사용하면 게이트웨이의 보안 비밀에 대한 읽기 액세스 권한(RBAC)이 더 이상 필요하지 않습니다.

  4. DestinationRule에 인증서를 추가하고 이름을 client-destination-rule-to-external-service.yaml로 지정합니다.

    cat <<EOF > client-destination-rule-to-external-service.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
     name: originate-mtls-for-mysql
    spec:
     host: $SERVICE_URL
     trafficPolicy:
       loadBalancer:
         simple: ROUND_ROBIN
       portLevelSettings:
       - port:
           number: 13306
         tls:
           mode: MUTUAL
           credentialName: client-credential
           sni: $SERVICE_URL
    EOF
    
  5. 클라이언트 클러스터에 앞의 YAML 정의를 적용합니다.

    kubectl --context ${CLIENT_CLUSTER} apply -f client-destination-rule-to-external-service.yaml
    

    이 규칙은 보안 비밀을 미리 만든 경우에만 적용됩니다. 보안 비밀은 이그레스 게이트웨이에서 외부 엔드포인트로의 mTLS 암호화에 인증서가 사용되도록 보장합니다.

이 단계를 완료하면 클라이언트 측 설정이 완료되어 다음 다이어그램과 같아집니다.

최종 클라이언트 측 설정입니다.

서버 측 구성

개념 가이드에 설명된 대로 서버 측에서는 Anthos Service Mesh에서 인그레스 게이트웨이를 구성해야 합니다. 필요한 파일을 만들려면 먼저 서버 측 솔루션을 실현하기 위해 필요한 구성요소가 무엇인지 검토해보는 것이 좋습니다.

기본적으로 출처 및 인증서를 기준으로 수신 트래픽을 식별하려고 합니다. 또한 해당 트래픽만 대상(컨테이너의 MySQL DB)으로 라우팅하려고 합니다. 일반적으로는 Kubernetes의 서비스를 사용하여 이 작업을 수행하지만, 이 예시에서는 메시 통신 내부에서 수신 트래픽을 식별하므로 다음을 포함하는 서비스 특수 정의가 필요합니다.

  • TLS 인증서(보안 비밀)
  • 인그레스 게이트웨이
  • 가상 서비스

인그레스 게이트웨이의 보안 비밀 만들기

이그레스 게이트웨이에서와 같이, 인그레스 게이트웨이의 통신을 보호하기 위해 동일한 인증서가 필요합니다. 이를 위해서는 보안 비밀에 인증서를 저장하고 이 보안 비밀을 인그레스 게이트웨이 객체와 함께 정의합니다.

  1. Cloud Shell에서 생성된 파일을 다음 명령어를 실행할 위치로 복사합니다.

    cp ./certs/3_application/private/$SERVICE_URL.key.pem server-$SERVICE_URL.key.pem
    cp ./certs/3_application/certs/$SERVICE_URL.cert.pem server-$SERVICE_URL.cert.pem
    
  2. 서버 보안 비밀을 만듭니다.

    kubectl --context ${SERVER_CLUSTER} create secret -n istio-system \
        generic mysql-credential \
        --from-file=tls.key=server-$SERVICE_URL.key.pem \
        --from-file=tls.crt=server-$SERVICE_URL.cert.pem \
        --from-file=ca.crt=ca-chain.cert.pem
    

    보안 비밀에 다음 인증서 파일을 추가했습니다.

    server-$SERVICE_URL.key.pem
    server-$SERVICE_URL.cert.pem
    ca-chain.cert.pem
    

인그레스 게이트웨이 정의

클라이언트 측 클러스터에서 트래픽을 수신하려면 인증서를 사용하여 TLS 통신을 해독 및 확인할 수 있는 인그레스 게이트웨이를 지정해야 합니다.

보안 및 전달 기준에 적합한지 확인하기 위해 트래픽을 검사하는 인그레스 게이트웨이를 정의합니다.

이 다이어그램은 클러스터 내에서 인그레스 게이트웨이의 위치를 보여줍니다. 트래픽이 보안 및 전달 기준에 부합하면 통과되고 검사됩니다.

  1. Cloud Shell에서 다음 YAML 파일을 사용하고 이름을 server-ingress-gatway.yaml로 지정합니다.

    cat <<EOF > server-ingress-gatway.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
     name: gateway-mysql
    spec:
     selector:
       istio: ingressgateway # Istio default gateway implementation
     servers:
     - port:
         number: 13306
         name: tls-mysql
         protocol: TLS
       tls:
         mode: MUTUAL
         credentialName: mysql-credential
       hosts:
       - "$SERVICE_URL"
    EOF
    
  2. 클라이언트 클러스터에 앞의 YAML 정의를 적용합니다.

    kubectl --context ${SERVER_CLUSTER} apply -f server-ingress-gatway.yaml
    

    특히 중요한 tls: 섹션에 주의하세요. 이 섹션에서는 mTLS를 사용하도록 정의합니다. 올바르게 작동하는지 확인하기 위해서는 인증서가 포함된 생성된 보안 비밀을 제공해야 합니다.

  3. 인그레스 서비스를 패치하여 포트 13306을 사용 설정합니다. 다음 JSON 파일을 만들고 이름을 gateway-patch.json으로 지정하여 이 포트를 사용 설정합니다.

    cat <<EOF > gateway-patch.json
    [{
      "op": "add",
      "path": "/spec/ports/0",
      "value": {
        "name": "tls-mysql",
        "protocol": "TCP",
        "targetPort": 13306,
        "port": 13306
      }
    }]
    EOF
    
  4. 게이트웨이 서비스에 패치를 적용합니다.

    kubectl --context ${SERVER_CLUSTER} -n istio-system patch --type=json svc istio-ingressgateway -p "$(cat gateway-patch.json)"
    

이제 인그레스 게이트웨이에 포트를 열었으므로 새 인그레스 게이트웨이에서 들어오는 트래픽을 선택하여 데이터베이스 pod로 전달해야 합니다. 이렇게 하려면 메시 내부 객체인 가상 서비스를 사용합니다.

가상 서비스 정의

앞에서 설명한 것처럼 가상 서비스는 메시 내에서 트래픽을 구성하는 트래픽 일치 패턴에 대한 정의입니다. 다음 다이어그램에 표시된 것처럼 트래픽을 올바르게 식별하고 인그레스 게이트웨이에서 MySQL DB 서비스로 전달해야 합니다.

인그레스 게이트웨이에서 MySQL DB 서비스로 향하는 트래픽 식별 및 전달

  1. Cloud Shell에서 다음 YAML 파일을 만들고 이름을 server-virtual-service.yaml로 지정합니다.

    cat <<EOF > server-virtual-service.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
     name: mysql-virtual-service
    spec:
     hosts:
       - "$SERVICE_URL"
     gateways:
       - gateway-mysql
     tcp:
       - route:
         - destination:
             port:
               number: 3306
             host: mysql.default.svc.cluster.local
    EOF
    

    이 가상 서비스가 트래픽이 시작되는 인그레스 게이트웨이를 참조한다는 것이 중요합니다. 이 게이트웨이는 이름이 gateway-mysql입니다.

    이 가상 서비스는 L4에 적용되므로 tcp 플래그를 제공하여 MySQL 트래픽을 설명해야 합니다. 이 플래그는 기본적으로 이 트래픽에 L4가 사용됨을 메시에 알려줍니다.

    인그레스 서비스가 포트 13306을 사용하여 트래픽을 전달함을 확인할 수 있습니다. 가상 서비스가 이를 감지하여 다시 3306으로 변환합니다.

    마지막으로 서버 Kubernetes 클러스터에서 MySQL 서버로 트래픽을 전달합니다. 이 예시에서는 서버가 표준 MySQL 포트 3306에서 리슨합니다.

  2. 서버 클러스터에 YAML 정의를 적용합니다.

    kubectl --context ${SERVER_CLUSTER} apply -f server-virtual-service.yaml
    

이러한 2개 정의는 mTLS로 캡슐화된 MySQL 클라이언트 요청을 해독하고 이를 메시 내의 MySQL 데이터베이스로 전달합니다.

메시 내부 전달도 암호화를 사용하여 수행되지만 이 경우에는 메시 내부 인증서를 기반으로 암호화한다는 점이 중요합니다. mTLS 종료는 게이트웨이에서 발생합니다.

이제 MySQL 서버와 완전한 상호 암호화 방식으로 통신할 수 있습니다. 이러한 형태의 암호화는 MySQL 클라이언트 및 서버에 구애받지 않으므로 애플리케이션을 변경할 필요가 없습니다. 따라서 인증서 변경 또는 순환 같은 작업이 간단합니다. 또한 매우 다양한 시나리오에 이 통신 방식을 사용할 수 있습니다.

설정 테스트

이제 클라이언트 측과 서버 측에서 준비가 끝났으므로 설정을 테스트할 수 있습니다.

클라이언트 측에서 서버 측으로 트래픽 흐름을 테스트합니다.

클라이언트 측에서 서버 측으로 향하는 트래픽을 생성할 차례입니다. 트래픽 흐름을 따라가면서 트래픽이 의도한 대로 라우팅, 암호화, 복호화되는지 확인하려고 합니다.

  1. Cloud Shell에서 서버 클러스터에 MySQL 서버를 배포합니다.

    kubectl --context ${SERVER_CLUSTER} apply -f server/mysql-server/mysql.yaml
    
  2. 클라이언트 클러스터에서 MySQL 클라이언트를 시작합니다.

    kubectl run --context ${CLIENT_CLUSTER} --env=SERVICE_URL=$SERVICE_URL -it --image=mysql:5.6 mysql-client-1 --restart=Never -- /bin/bash
    

    컨테이너가 시작되면 다음과 같이 표시되는 셸이 제공됩니다.

    root@mysql-client-1:/#
    
  3. MySQL 서버에 연결합니다.

    mysql -pyougottoknowme -h $SERVICE_URL
    
  4. 다음 명령어를 사용하여 DB 및 테이블을 추가합니다.

    CREATE DATABASE test_encrypted_connection;
    USE test_encrypted_connection;
    CREATE TABLE message (id INT, content VARCHAR(20));
    INSERT INTO message (id,content) VALUES(1,"Crypto Hi");
    
  5. 테이블을 연결하고 추가한 후에 MySQL 연결과 pod를 종료합니다.

    exit
    exit
    

    exit을 두 번 입력해야 합니다. 먼저 DB 연결을 종료한 후 pod를 종료합니다. 종료 시 pod가 응답하지 않으면 Control+C를 눌러 bash 셸을 취소합니다.

이 단계를 수행하면 의미 있는 로깅 출력을 생성할 수 있으며, 이 출력은 이제 추가로 분석할 수 있습니다.

다음 섹션에서 클라이언트 측 트래픽이 프록시 및 이그레스 게이트웨이를 통과하는지 확인합니다. 또한 트래픽이 인그레스 게이트웨이를 통해 서버 측에 들어오는 것을 확인할 수 있는지도 테스트합니다.

클라이언트 측 프록시 및 이그레스 게이트웨이 테스트

  1. 클라이언트 측 프록시의 Cloud Shell에서 Istio 프록시 로그가 보이는지 확인합니다.

    kubectl --context ${CLIENT_CLUSTER} logs -l run=mysql-client-1 -c istio-proxy -f
    

    디버그 출력은 다음과 유사합니다.

    [2021-02-10T21:19:08.292Z] "- - -" 0 - "-" "-" 176 115 10 - "-" "-" "-" "-" "192.168.1.4:15443" outbound|15443|mysql|istio-egressgateway.istio-system.svc.cluster.local 192.168.1.12:58614 34.66.165.46:3306 192.168.1.12:39642 - -
    

    Control+C를 눌러 로그 출력을 종료합니다.

    이 로그 항목의 경우 클라이언트가 포트 3306에서 IP 주소 34.66.165.46으로 실행 중인 서버를 요청하는 것을 확인할 수 있습니다. 요청은 IP 주소 192.168.1.4 포트 15443에서 리슨하는 istio-egressgateway로 전달됩니다(outbound). 이 전달은 가상 서비스(client-virtual-service.yaml)에서 정의했습니다.

  2. 이그레스 게이트웨이 프록시 로그를 읽습니다.

    kubectl --context ${CLIENT_CLUSTER} logs -n istio-system -l app=istio-egressgateway -f
    

    디버그 출력은 다음과 유사합니다.

    [2021-02-10T21:19:08.292Z] "- - -" 0 - "-" "-" 176 115 19 - "-" "-" "-" "-" "34.66.165.46:13306" outbound|13306||34.66.165.46.nip.io 192.168.1.4:53542 192.168.1.4:15443 192.168.1.12:58614 34.66.165.46.nip.io -
    

    Control+C를 눌러 로그 출력을 종료합니다.

    이 로그 항목을 보면 IP 주소 192.168.1.4 포트 15443에서 리슨하는 istio-egressgateway로 라우팅된 클라이언트 요청이 서비스 메시 외부에서 13306. 포트의 IP 주소 34.66.165.46에서 리슨하는 외부 서비스로 자세히 라우팅됩니다. 이 전달은 가상 서비스(client-virtual-service.yaml)의 두 번째 부분에서 정의했습니다.

서버 측 인그레스 게이트웨이 테스트

  1. 서버 측의 Cloud Shell에서 인그레스 게이트웨이 프록시 로그를 확인합니다.

    kubectl --context ${SERVER_CLUSTER} logs -n istio-system -l app=istio-ingressgateway -f
    

    로그의 출력이 다음과 비슷하게 표시됩니다.

    [2021-02-10T21:22:27.381Z] "- - -" 0 - "-" "-" 0 78 5 - "-" "-" "-" "-" "100.96.4.8:3306" outbound|3306||mysql.default.svc.cluster.local 100.96.1.3:55730 100.96.1.3:13306 100.96.1.1:42244 34.66.165.46.nip.io -
    

    Control+C를 눌러 로그 출력을 종료합니다.

    이 로그 항목을 보면 IP 주소 34.66.165.46 포트 13306에서 리슨하는 istio-ingressgateway로 라우팅된 외부 클라이언트 요청이 3306. 포트의 서비스 이름 mysql.default.svc.cluster.local에서 식별하는 메시 내부의 MySQL 서비스로 자세히 라우팅됩니다. 이 전달은 인그레스 게이트웨이(server-ingress-gateway.yaml)에서 정의했습니다.

  2. MySQL 서버에 대해 Istio 프록시 로그를 확인합니다.

    kubectl --context ${SERVER_CLUSTER} logs -l app=mysql -c istio-proxy -f
    

    결과는 다음과 유사합니다.

    [2021-02-10T21:22:27.382Z] "- - -" 0 - "-" "-" 1555 1471 4 - "-" "-" "-" "-" "127.0.0.1:3306" inbound|3306|mysql|mysql.default.svc.cluster.local 127.0.0.1:45894 100.96.4.8:3306 100.96.1.3:55730 outbound_.3306_._.mysql.default.svc.cluster.local -
    

    Control+C를 눌러 로그 출력을 종료합니다.

    이 로그 항목에는 IP 주소 100.96.4.8 포트 3306에서 리슨하는 MySQL 데이터베이스 서버에 대한 인바운드 호출이 표시됩니다. IP 주소 100.96.1.3을 사용해서 인그레스 pod에서 호출이 발생합니다.

    Envoy 로깅 형식에 대한 자세한 내용은 Envoy 액세스 로그 가져오기를 참조하세요.

  3. 데이터베이스를 테스트하여 생성된 입력을 확인합니다.

    MYSQL=$(kubectl --context ${SERVER_CLUSTER} get pods -n default | tail -n 1 | awk '{print $1}')
    kubectl --context ${SERVER_CLUSTER} exec $MYSQL -ti -- /bin/bash
    
  4. 생성된 데이터베이스를 확인합니다.

    mysql -pyougottoknowme
    USE test_encrypted_connection;
    SELECT * from message;
    

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

    +------+-----------+
    | id   | content   |
    +------+-----------+
    |    1 | Crypto Hi |
    +------+-----------+
    1 row in set (0.00 sec)
    
  5. MySQL 데이터베이스를 종료합니다.

    exit
    exit
    

    exit을 두 번 입력해야 합니다. 먼저 DB 연결을 종료한 후 pod를 종료합니다.

인증서를 생략하여 액세스 테스트

삽입된 인증서를 사용하여 액세스가 작동하는지 테스트하고 확인했습니다. 이제 반대의 경우인 이그레스 게이트웨이와 인증서 삽입을 생략하면 어떻게 되는지 테스트합니다. 이 테스트를 음성 테스트라고도 합니다.

이 프록시는 사이드 프록시 삽입을 사용 설정하지 않고 네임스페이스에서 다른 pod를 실행하여 수행할 수 있습니다.

  1. Cloud Shell에서 새 네임스페이스를 만듭니다.

    kubectl --context ${CLIENT_CLUSTER} create ns unencrypted
    
  2. pod를 만들고 컨테이너 내부에서 대화형 셸을 시작합니다.

    kubectl --context ${CLIENT_CLUSTER} run -it --image=mysql:5.6 \
    mysql-client-2 --env=SERVICE_URL=$SERVICE_URL \
    -n unencrypted --restart=Never -- /bin/bash
    
  3. 대화형 셸이 시작된 후 데이터베이스에 연결을 시도합니다.

    mysql -pyougottoknowme -h $SERVICE_URL
    

    30초 후 다음과 비슷한 출력이 표시됩니다.

    Warning: Using a password on the command line interface can be insecure.
    ERROR 2003 (HY000): Can't connect to MySQL server on '104.154.164.12.nip.io' (110)
    

    이 경고는 이 pod가 이그레스 게이트웨이를 생략하고 인터넷을 통해 직접 인그레스 게이트웨이($SERVICE_URL)에 연결을 시도하기 때문에 예상됩니다.

  4. 서비스 IP 주소를 확인합니다.

    resolveip $SERVICE_URL
    

    출력은 다음과 비슷합니다. IP 주소가 다를 것입니다.

    IP address of 104.154.164.12.nip.io is 104.154.164.12
    

    이렇게 하면 FQDN을 확인할 수 있고 누락된 인증서 삽입으로 인해 연결이 실패했음을 알 수 있습니다.

  5. MySQL 연결 및 MySQL 서버 pod를 종료합니다.

    exit
    exit
    

세부 조사

이 튜토리얼에서는 다루지 않은 주제이지만, 이그레스 구성은 일반적으로 istio-system 네임스페이스에 호스팅되므로 회사의 다른 역할 또는 조직에서 소유합니다. 네트워크 관리자만 이 튜토리얼에서 설명하는 리소스를 직접 만들고 수정할 수 있도록 Kubernetes RBAC 권한을 구성합니다.

이제 보안 통신을 위해 서비스 메시를 사용하는 방법을 확인했으므로, 안전한 데이터 교환이 필요하고 인증서 레이어까지 암호화를 제어해야 하는 애플리케이션으로 이를 시험해볼 수 있습니다. 시작하려면 Anthos Service Mesh를 설치합니다.

GKE 클러스터 두 개를 사용하고 이 가이드의 기법을 사용하여 두 클러스터를 결합합니다. 이 기법은 두 개의 외래 Kubernetes 클러스터 사이의 GKE Enterprise 플랫폼에서도 작동합니다.

서비스 메시는 외부 서비스뿐 아니라 클러스터 내에서 보안을 강화하는 훌륭한 방법입니다. 마지막으로 시도해 볼 사용 사례는 두 번째 Kubernetes 클러스터가 아닌 제3자 제공업체(예: 결제 시스템 공급자)인 mTLS 엔드포인트입니다.

삭제

이 튜토리얼에서 사용한 리소스의 비용이 Google Cloud 계정에 청구되지 않도록 하려면 프로젝트를 삭제하면 됩니다.

프로젝트 삭제

  1. Google Cloud 콘솔에서 리소스 관리 페이지로 이동합니다.

    리소스 관리로 이동

  2. 프로젝트 목록에서 삭제할 프로젝트를 선택하고 삭제를 클릭합니다.
  3. 대화상자에서 프로젝트 ID를 입력한 후 종료를 클릭하여 프로젝트를 삭제합니다.

다음 단계