Cloud Functions, Cloud Scheduler, Cloud Monitoring으로 비용 최적화 자동화


이 문서에서는 Cloud Functions를 사용하여 낭비된 클라우드 리소스를 식별 및 정리하고 Cloud Scheduler로 실행할 함수를 예약하는 방법, Cloud Monitoring 알림 정책을 사용하여 관찰된 사용량을 기반으로 실행하는 방법을 설명합니다. 이 문서는 체계적이고 자동화된 접근 방식으로 낭비될 수 있는 클라우드 지출을 찾아서 줄이고 싶은 개발자, SRE, 클라우드 설계자, 클라우드 인프라 관리자를 대상으로 합니다.

이 문서에서는 사용자가 다음에 익숙하다고 가정합니다.

목표

  • 사용되지 않는 IP 주소 삭제: Google Cloud에서 고정 IP 주소는 부하 분산기 또는 가상 머신(VM) 인스턴스에 연결되었을 때 무료 리소스입니다. 고정 IP 주소가 예약된 경우에는 사용되지 않더라도 시간당 비용이 누적됩니다. 고정 IP 주소 및 대규모 동적 프로비저닝에 크게 의존하는 앱의 경우 시간이 지날수록 이러한 낭비가 커질 수 있습니다.
  • 분리되었거나 사용되지 않는 영구 디스크 삭제: 영구 디스크는 생성되었지만 다른 VM에 연결되지 않은 경우 또는 머신에 여러 디스크가 있고 하나 이상의 디스크가 분리되었을 때 사용되지 않거나 분리됩니다.
  • 비용이 낮은 스토리지 클래스로 마이그레이션: Google Cloud는 여러 클래스의 객체 스토리지를 제공합니다. 사용자 요구에 가장 적합한 클래스를 사용하세요.

아키텍처

다음 다이어그램은 사용되지 않는 IP 주소를 식별하고 삭제하도록 Cloud 함수를 예약하는 배포의 첫 번째 부분을 설명합니다.

사용되지 않은 IP 주소를 찾아서 삭제하는 Cloud 함수의 아키텍처

첫 번째 예시에는 다음 내용이 포함됩니다.

  • 고정 외부 IP 주소로 Compute Engine VM을 만들고 사용되지 않는 개별 고정 외부 IP 주소를 만듭니다.
  • 사용되지 않은 주소를 찾기 위한 Cloud 함수를 배포합니다.
  • HTTP 트리거를 사용해서 함수를 실행하도록 예약하는 Cloud Scheduler 작업을 만듭니다.

다음 다이어그램에서는 연결되지 않았고 분리된 영구 디스크를 찾아서 삭제하도록 Cloud 함수를 예약합니다.

사용되지 않은 영구 디스크를 찾아서 삭제하는 Cloud 함수의 아키텍처

두 번째 예시에는 다음 내용이 포함됩니다.

  • 2개의 영구 디스크와 1개의 연결되지 않은 개별 영구 디스크로 Compute Engine VM을 만듭니다. VM에서 분리하여 디스크 중 하나를 분리시킵니다.
  • 연결되지 않고 분리된 영구 디스크를 찾도록 Cloud 함수를 배포합니다.
  • HTTP 트리거를 사용해서 Cloud 함수의 실행을 예약하도록 Cloud Scheduler 작업을 만듭니다.

다음 다이어그램에서는 Monitoring 알림 정책에 따라 덜 비싼 스토리지 클래스로 스토리지 버킷을 마이그레이션하도록 Cloud 함수를 트리거합니다.

스토리지 버킷을 마이그레이션하는 Cloud 함수의 아키텍처

세 번째 예시에는 다음 내용이 포함됩니다.

  • 스토리지 버킷 2개를 만들어서 서비스 제공 버킷에 파일을 추가하고 이에 대해 트래픽을 생성합니다.
  • 버킷 사용률을 시각화하기 위해 Monitoring 대시보드를 만듭니다.
  • 유휴 버킷을 덜 비싼 스토리지 클래스로 마이그레이션하도록 Cloud 함수를 배포합니다.
  • Monitoring 알림 정책으로부터 수신된 알림을 시뮬레이션하도록 의도된 페이로드를 사용하여 함수를 트리거합니다.

비용

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

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

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

시작하기 전에

  1. Google Cloud 계정에 로그인합니다. Google Cloud를 처음 사용하는 경우 계정을 만들고 Google 제품의 실제 성능을 평가해 보세요. 신규 고객에게는 워크로드를 실행, 테스트, 배포하는 데 사용할 수 있는 $300의 무료 크레딧이 제공됩니다.
  2. Google Cloud Console의 프로젝트 선택기 페이지에서 Google Cloud 프로젝트를 선택하거나 만듭니다.

    프로젝트 선택기로 이동

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

  4. API Compute Engine, Cloud Functions, and Cloud Storage 사용 설정

    API 사용 설정

  5. Google Cloud Console의 프로젝트 선택기 페이지에서 Google Cloud 프로젝트를 선택하거나 만듭니다.

    프로젝트 선택기로 이동

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

  7. API Compute Engine, Cloud Functions, and Cloud Storage 사용 설정

    API 사용 설정

  8. Google Cloud 콘솔에서 Cloud Shell을 활성화합니다.

    Cloud Shell 활성화

    Google Cloud 콘솔 하단에서 Cloud Shell 세션이 시작되고 명령줄 프롬프트가 표시됩니다. Cloud Shell은 Google Cloud CLI가 사전 설치된 셸 환경으로, 현재 프로젝트의 값이 이미 설정되어 있습니다. 세션이 초기화되는 데 몇 초 정도 걸릴 수 있습니다.

  9. Cloud Shell에서 이 문서의 모든 명령어를 실행합니다.

환경 설정

이 섹션에서는 이 아키텍처에 필요한 인프라 및 ID를 구성합니다.

  1. Cloud Shell에서 저장소를 클론하고 gcf-automated-resource-cleanup 디렉터리로 변경합니다.

    git clone https://github.com/GoogleCloudPlatform/gcf-automated-resource-cleanup.git && cd gcf-automated-resource-cleanup/
    
  2. 환경 변수를 설정하고 저장소 폴더를 $WORKDIR 폴더로 만듭니다. 여기서 모든 명령어를 실행합니다.

    export PROJECT_ID=$(gcloud config list \
        --format 'value(core.project)' 2>/dev/null)
        WORKDIR=$(pwd)
    
  3. 오픈소스 부하 생성 도구인 Apache Bench를 설치합니다.

    sudo apt-get install apache2-utils
    

사용되지 않은 IP 주소 삭제

이 섹션에서는 다음 단계를 완료합니다.

  • 2개의 고정 IP 주소를 만듭니다.
  • 고정 IP 주소를 사용하는 VM을 만듭니다.
  • Cloud Functions 코드를 검토합니다.
  • Cloud 함수를 배포합니다.
  • Cloud Scheduler 작업을 사용하여 Cloud 함수를 테스트합니다.

IP 주소 만들기

  1. Cloud Shell에서 unused-ip 디렉터리로 변경합니다.

    cd $WORKDIR/unused-ip
    
  2. IP 주소 이름을 변수로 내보냅니다.

    export USED_IP=used-ip-address
    export UNUSED_IP=unused-ip-address
    
  3. 2개의 고정 IP 주소를 만듭니다.

    gcloud compute addresses create $USED_IP \
        --project=$PROJECT_ID --region=us-central1
    gcloud compute addresses create $UNUSED_IP \
        --project=$PROJECT_ID --region=us-central1
    

    이 예시에서는 us-central1 리전이 사용되지만 다른 리전을 선택하고 이 문서의 나머지 부분에서 일관되게 참조할 수 있습니다.

  4. 2개 주소가 생성되었는지 확인합니다.

    gcloud compute addresses list --filter="region:(us-central1)"
    

    출력에서 RESERVED 상태는 IP 주소가 사용 중이 아님을 나타냅니다.

    NAME               ADDRESS/RANGE  TYPE      REGION       SUBNET  STATUS
    unused-ip-address  35.232.144.85  EXTERNAL  us-central1          RESERVED
    used-ip-address    104.197.56.87  EXTERNAL  us-central1          RESERVED
    
  5. 사용된 IP 주소를 환경 변수로 설정합니다.

    export USED_IP_ADDRESS=$(gcloud compute addresses describe $USED_IP \
        --region=us-central1 --format=json | jq -r '.address')
    

VM 만들기

  1. Cloud Shell에서 인스턴스를 만듭니다.

    gcloud compute instances create static-ip-instance \
        --zone=us-central1-a \
        --machine-type=n1-standard-1 \
        --subnet=default \
        --address=$USED_IP_ADDRESS
    
  2. 이제 IP 주소 중 하나가 사용 중인지 확인합니다.

    gcloud compute addresses list --filter="region:(us-central1)"
    

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

    NAME               ADDRESS/RANGE  TYPE      REGION       SUBNET  STATUS
    unused-ip-address  35.232.144.85  EXTERNAL  us-central1          RESERVED
    used-ip-address    104.197.56.87  EXTERNAL  us-central1          IN_USE
    

Cloud 함수 코드 검토

  • Cloud Shell에서 코드의 기본 섹션을 출력합니다.

    cat $WORKDIR/unused-ip/function.js | grep "const compute" -A 31
    

    출력은 다음과 같습니다.

    const compute = new Compute();
    compute.getAddresses(function(err, addresses){ // gets all addresses across regions
         if(err){
             console.log("there was an error: " + err);
         }
         if (addresses == null) {
             console.log("no addresses found");
             return;
         }
         console.log("there are " + addresses.length + " addresses");
    
         // iterate through addresses
         for (let item of addresses){
    
              // get metadata for each address
              item.getMetadata(function(err, metadata, apiResponse) {
    
                  // if the address is not used AND if it's at least ageToDelete days old:
                  if ((metadata.status=='RESERVED') & (calculateAge(metadata.creationTimestamp) >= ageToDelete)){
                      // delete address
                      item.delete(function(err, operation, apiResponse2){
                          if (err) {
                              console.log("could not delete address: " + err);
                          }
                      })
                  }
              })
          }
           // return number of addresses evaluated
          res.send("there are " + addresses.length + " total addresses");
      });
    }
    

    앞의 코드 샘플에서 다음 사항에 유의하세요.

    • compute.getAddresses(function(err, addresses){ // gets all addresses across regions
      

      getAddresses 메서드를 사용하여 프로젝트의 모든 리전에서 IP 주소를 검색합니다.

    • // get metadata for each address
      item.getMetadata(function(err, metadata, apiResponse) {
         // if the address is not used:
             if (metadata.status=='RESERVED'){
      

      각 IP 주소에 대한 메타데이터를 가져오고 해당 STATUS 필드를 확인합니다.

    • if ((metadata.status=='RESERVED') &
      (calculateAge(metadata.creationTimestamp) >= ageToDelete)){
      

      IP 주소가 사용 중인지 확인하고 도우미 함수를 사용해서 해당 사용 기간을 계산하고, 이 기간을 상수(이 문서에서는 0으로 설정됨)와 비교합니다.

    • // delete address
      item.delete(function(err, operation, apiResponse2){
      

      IP 주소를 삭제합니다.

Cloud 함수 배포

  1. Cloud Shell에서 Cloud 함수를 배포합니다.

    gcloud functions deploy unused_ip_function --trigger-http --runtime=nodejs8
    
  2. 트리거 URL을 환경 변수로 설정합니다.

    export FUNCTION_URL=$(gcloud functions describe unused_ip_function \
        --format=json | jq -r '.httpsTrigger.url')
    

Cloud 함수 예약 및 테스트

  1. Cloud Shell에서 매일 오전 2시에 이 Cloud 함수를 실행하도록 Cloud Scheduler 태스크를 만듭니다.

    gcloud scheduler jobs create http unused-ip-job \
        --schedule="* 2 * * *" \
        --uri=$FUNCTION_URL
    
  2. 수동으로 트리거하여 작업을 테스트합니다.

    gcloud scheduler jobs run unused-ip-job
    
  3. 사용되지 않은 IP 주소가 삭제되었는지 확인합니다.

    gcloud compute addresses list --filter="region:(us-central1)"
    

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

    NAME             ADDRESS/RANGE  TYPE      REGION       SUBNET  STATUS
    used-ip-address  104.197.56.87  EXTERNAL  us-central1          IN_USE
    

사용되지 않고 분리된 영구 디스크 삭제

이 섹션에서는 다음 단계를 완료합니다.

  • 2개의 영구 디스크를 만듭니다.
  • 디스크 중 하나를 사용하는 VM을 만듭니다.
  • VM에서 디스크를 분리합니다.
  • Cloud 함수 코드를 검토합니다.
  • Cloud 함수를 배포합니다.
  • Cloud Scheduler 작업을 사용하여 Cloud 함수를 테스트합니다.

영구 디스크 만들기

  1. Cloud Shell에서 unattached-pd 디렉터리로 변경합니다.

    cd $WORKDIR/unattached-pd
    
  2. 디스크 이름을 환경 변수로 내보냅니다.

    export ORPHANED_DISK=orphaned-disk
    export UNUSED_DISK=unused-disk
    
  3. 2개의 디스크를 만듭니다.

    gcloud beta compute disks create $ORPHANED_DISK \
       --project=$PROJECT_ID \
       --type=pd-standard \
       --size=500GB \
       --zone=us-central1-a
    gcloud beta compute disks create $UNUSED_DISK \
        --project=$PROJECT_ID \
        --type=pd-standard \
        --size=500GB \
        --zone=us-central1-a
    
  4. 2개의 디스크가 생성되었는지 확인합니다.

    gcloud compute disks list
    

    출력은 다음과 같습니다.

    NAME                LOCATION       LOCATION_SCOPE SIZE_GB TYPE         STATUS
    orphaned-disk       us-central1-a  zone           500     pd-standard  READY
    static-ip-instance  us-central1-a  zone           10      pd-standard  READY
    unused-disk         us-central1-a  zone           500     pd-standard  READY
    

VM 만들기 및 디스크 검사

  1. Cloud Shell에서 인스턴스를 만듭니다.

    gcloud compute instances create disk-instance \
        --zone=us-central1-a \
        --machine-type=n1-standard-1 \
        --disk=name=$ORPHANED_DISK,device-name=$ORPHANED_DISK,mode=rw,boot=no
    
  2. VM에 연결된 디스크를 검사합니다.

    gcloud compute disks describe $ORPHANED_DISK \
        --zone=us-central1-a \
        --format=json | jq
    

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

    {
      "creationTimestamp": "2019-06-12T12:21:25.546-07:00",
      "id": "7617542552306904666",
      "kind": "compute#disk",
      "labelFingerprint": "42WmSpB8rSM=",
      "lastAttachTimestamp": "2019-06-12T12:24:53.989-07:00",
      "name": "orphaned-disk",
      "physicalBlockSizeBytes": "4096",
      "selfLink": "https://www.googleapis.com/compute/v1/projects/automating-cost-optimization/zones/us-central1-a/disks/orphaned-disk",
      "sizeGb": "500",
      "status": "READY",
      "type": "https://www.googleapis.com/compute/v1/projects/automating-cost-optimization/zones/us-central1-a/diskTypes/pd-standard",
      "users": [
        "https://www.googleapis.com/compute/v1/projects/automating-cost-optimization/zones/us-central1-a/instances/disk-instance"
      ],
      "zone": "https://www.googleapis.com/compute/v1/projects/automating-cost-optimization/zones/us-central1-a"
    }
    

    앞의 코드 샘플에서 다음 사항에 유의하세요.

    • users는 디스크가 연결된 VM을 식별합니다.
    • lastAttachTimestamp는 디스크가 VM에 마지막으로 연결된 시간을 식별합니다.
  3. VM에 연결되지 않은 디스크를 검사합니다.

    gcloud compute disks describe $UNUSED_DISK \
        --zone=us-central1-a \
        --format=json | jq
    

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

    {
      "creationTimestamp": "2019-06-12T12:21:30.905-07:00",
      "id": "1313096191791918677",
      "kind": "compute#disk",
      "labelFingerprint": "42WmSpB8rSM=",
      "name": "unused-disk",
      "physicalBlockSizeBytes": "4096",
      "selfLink": "https://www.googleapis.com/compute/v1/projects/automating-cost-optimization/zones/us-central1-a/disks/unused-disk",
      "sizeGb": "500",
      "status": "READY",
      "type": "https://www.googleapis.com/compute/v1/projects/automating-cost-optimization/zones/us-central1-a/diskTypes/pd-standard",
      "zone": "https://www.googleapis.com/compute/v1/projects/automating-cost-optimization/zones/us-central1-a"
    }
    

    앞의 코드 샘플에서는 다음 내용이 중요합니다.

    • 현재 VM에서 사용되지 않기 때문에 디스크에 users가 나열되지 않습니다.
    • 지금까지 사용된 적이 없기 때문에 디스크에 lastAttachedTimestamp가 없습니다.
  4. VM에서 분리된 영구 디스크를 분리합니다.

    gcloud compute instances detach-disk disk-instance \
        --device-name=$ORPHANED_DISK \
        --zone=us-central1-a
    
  5. 분리된 디스크를 검사합니다.

    gcloud compute disks describe $ORPHANED_DISK \
        --zone=us-central1-a \
        --format=json | jq
    

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

    {
      "creationTimestamp": "2019-06-12T12:21:25.546-07:00",
      "id": "7617542552306904666",
      "kind": "compute#disk",
      "labelFingerprint": "42WmSpB8rSM=",
      "lastAttachTimestamp": "2019-06-12T12:24:53.989-07:00",
      "lastDetachTimestamp": "2019-06-12T12:34:56.040-07:00",
      "name": "orphaned-disk",
      "physicalBlockSizeBytes": "4096",
      "selfLink": "https://www.googleapis.com/compute/v1/projects/automating-cost-optimization/zones/us-central1-a/disks/orphaned-disk",
      "sizeGb": "500",
      "status": "READY",
      "type": "https://www.googleapis.com/compute/v1/projects/automating-cost-optimization/zones/us-central1-a/diskTypes/pd-standard",
      "zone": "https://www.googleapis.com/compute/v1/projects/automating-cost-optimization/zones/us-central1-a"
    }
    

    앞의 코드 샘플에서는 다음 내용이 중요합니다.

    • 현재 사용 중이 아니므로 디스크에 users가 나열되지 않습니다.
    • 이제 디스크가 VM에서 마지막으로 분리된 시간 즉, 마지막으로 사용된 시간을 나타내는 lastDetachTimestamp 항목이 표시됩니다.
    • lastAttachTimestamp 필드는 계속 있습니다.

Cloud 함수 코드 검토

  1. Cloud Shell에서 프로젝트의 모든 영구 디스크를 검색하는 코드 섹션을 출력합니다.

    cat $WORKDIR/unattached-pd/main.py | grep "(request)" -A 12
    

    출력은 다음과 같습니다.

    def delete_unattached_pds(request):
        # get list of disks and iterate through it:
        disksRequest = compute.disks().aggregatedList(project=project)
        while disksRequest is not None:
            diskResponse = disksRequest.execute()
            for name, disks_scoped_list in diskResponse['items'].items():
                if disks_scoped_list.get('warning') is None:
                    # got disks
                    for disk in disks_scoped_list['disks']: # iterate through disks
                        diskName = disk['name']
                        diskZone = str((disk['zone'])).rsplit('/',1)[1]
                        print (diskName)
                        print (diskZone)
    

    이 함수는 aggregatedList 메서드를 사용하여 실행되는 Google Cloud 프로젝트의 모든 영구 디스크를 가져오고, 각 디스크에 대해 작업을 반복합니다.

  2. lastAttachTimestamp 필드를 확인하고 존재하지 않으면 디스크를 삭제하는 코드 섹션을 출력합니다.

    cat $WORKDIR/unattached-pd/main.py | grep "handle never" -A 11
    

    출력은 다음과 같습니다.

    # handle never attached disk - delete it
    # lastAttachedTimestamp is not present
    if disk.get("lastAttachTimestamp") is None:
           print ("disk " + diskName + " was never attached - deleting")
           deleteRequest = compute.disks().delete(project=project,
                  zone=diskZone,
                  disk=diskName)
           deleteResponse = deleteRequest.execute()
           waitForZoneOperation(deleteResponse, project, diskZone)
           print ("disk " + diskName + " was deleted")
           Continue
    

    이 섹션에서는 lastAttachTimestamp가 없는 경우 즉, 이 디스크가 사용된 적이 없는 경우, 디스크를 삭제합니다.

  3. 디스크가 분리된 경우 디스크의 사용 기간을 계산하고, 디스크의 스냅샷을 만들고, 디스크를 삭제하는 코드 섹션을 출력합니다.

    cat $WORKDIR/unattached-pd/main.py | grep "handle detached" -A 32
    

    출력은 다음과 같습니다.

    # handle detached disk - snapshot and delete
    # lastAttachTimestamp is present AND users is not present AND it meets the age criterium
    if disk.get("users") is None \
        and disk.get("lastDetachTimestamp") is not None \
        and diskAge(disk['lastDetachTimestamp'])>=deleteAge:
    
        print ("disk " + diskName + " has no users and has been detached")
        print ("disk meets age criteria for deletion")
    
        # take a snapshot
        snapShotName = diskName + str(int(time.time()))
        print ("taking snapshot: " + snapShotName)
        snapshotBody = {
            "name": snapShotName
        }
        snapshotRequest = compute.disks().createSnapshot(project=project,
             zone=diskZone,
             disk=diskName,
             body=snapshotBody)
        snapshotResponse = snapshotRequest.execute()
        waitForZoneOperation(snapshotResponse, project, diskZone)
        print ("snapshot completed")
    
        # delete the disk
        print ("deleting disk " + diskName)
        deleteRequest = compute.disks().delete(project=project,
            zone=diskZone,
            disk=diskName)
        deleteResponse = deleteRequest.execute()
        waitForZoneOperation(deleteResponse, project, diskZone)
        print ("disk " + diskName + " was deleted")
        continue
    

    이 코드 섹션은 디스크에 users가 나열되고 lastDetachTimestamp가 있어서 디스크를 현재 사용 중이 아니지만 한 때 사용되었음을 나타내는 경우에 사용됩니다. 이 경우 Cloud 함수는 데이터를 보존하기 위해 디스크의 스냅샷을 만들고 나서 디스크를 삭제합니다.

Cloud 함수 배포

  1. Cloud Shell에서 Cloud 함수를 배포합니다.

    gcloud functions deploy delete_unattached_pds \
        --trigger-http --runtime=python37
    
  2. Cloud 함수의 트리거 URL을 환경 변수로 설정합니다.

    export FUNCTION_URL=$(gcloud functions describe delete_unattached_pds \
        --format=json | jq -r '.httpsTrigger.url')
    

Cloud 함수 예약 및 테스트

  1. Cloud Shell에서 매일 오전 2시에 이 Cloud 함수를 실행하도록 Cloud Scheduler 태스크를 만듭니다.

    gcloud scheduler jobs create http unattached-pd-job \
        --schedule="* 2 * * *" \
        --uri=$FUNCTION_URL
    
  2. 작업을 테스트합니다.

    gcloud scheduler jobs run unattached-pd-job
    
  3. 분리된 디스크의 스냅샷이 생성되었는지 확인합니다.

    gcloud compute snapshots list
    

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

    NAME                     DISK_SIZE_GB  SRC_DISK                           STATUS
    orphaned-disk1560455894  500           us-central1-a/disks/orphaned-disk  READY
    
  4. 사용되지 않은 디스크 및 분리된 디스크가 삭제되었는지 확인합니다.

    gcloud compute disks list
    

    출력은 다음과 같습니다.

    NAME                LOCATION       LOCATION_SCOPE SIZE_GB  TYPE         STATUS
    disk-instance       us-central1-a  zone           10       pd-standard  READY
    static-ip-instance  us-central1-a  zone           10       pd-standard  READY
    

스토리지 버킷을 덜 비싼 스토리지 클래스로 마이그레이션

Google Cloud는 해당 생성 날짜 또는 활성 상태와 같은 속성 집합을 기준으로 서로 다른 스토리지 클래스로 자동으로 객체를 이동하기 위해 사용할 수 있는 스토리지 객체 수명 주기 규칙를 제공합니다. 하지만 이러한 규칙은 객체가 액세스되었는지 여부를 알 수 없습니다. 일부 경우에는 특정 기간 동안 액세스되지 않은 경우 새 객체를 Nearline Storage로 이동해야 할 수 있습니다.

이 섹션에서는 다음 단계를 완료합니다.

  • 2개의 Cloud Storage 버킷을 만듭니다.
  • 버킷 중 하나에 객체를 추가합니다.
  • 버킷 객체 액세스를 관찰하도록 Monitoring을 구성합니다.
  • Regional Storage 버킷에서 Nearline Storage 버킷으로 객체를 마이그레이션하는 Cloud 함수 코드를 검토합니다.
  • Cloud 함수를 배포합니다.
  • Monitoring 알림을 사용하여 Cloud 함수를 테스트합니다.

Cloud Storage 버킷 만들기 및 파일 추가

  1. Cloud Shell에서 migrate-storage 디렉터리로 변경합니다.

    cd $WORKDIR/migrate-storage
    
  2. 나중에 스토리지 클래스를 변경하기 위해 사용되는 serving-bucket Cloud Storage 버킷을 만듭니다.

    export PROJECT_ID=$(gcloud config list \
        --format 'value(core.project)' 2>/dev/null)
    gsutil mb -c regional -l us-central1 gs://${PROJECT_ID}-serving-bucket
    
  3. 버킷을 공개로 만듭니다.

    gsutil acl ch -u allUsers:R gs://${PROJECT_ID}-serving-bucket
    
  4. 텍스트 파일을 버킷에 추가합니다.

    gsutil cp $WORKDIR/migrate-storage/testfile.txt  \
        gs://${PROJECT_ID}-serving-bucket
    
  5. 파일을 공개합니다.

    gsutil acl ch -u allUsers:R gs://${PROJECT_ID}-serving-bucket/testfile.txt
    
  6. 파일에 액세스할 수 있는지 확인합니다.

    curl http://storage.googleapis.com/${PROJECT_ID}-serving-bucket/testfile.txt
    

    출력은 다음과 같습니다.

    this is a test
    
  7. 데이터를 제공하지 않는 idle-bucket이라는 두 번째 버킷을 만듭니다.

    gsutil mb -c regional -l us-central1 gs://${PROJECT_ID}-idle-bucket
    

Cloud Monitoring 작업공간 설정

이 섹션에서는 버킷 객체가 사용되지 않을 때를 파악하기 위해 버킷 사용량을 측정하도록 Cloud Monitoring을 구성합니다. 서비스 제공 버킷이 사용되지 않을 때는 Cloud 함수가 해당 버킷을 Regional Storage 클래스에서 Nearline Storage 클래스로 마이그레이션합니다.

  1. Google Cloud 콘솔에서 Monitoring으로 이동합니다.

    Cloud Monitoring으로 이동

  2. 새 작업공간을 클릭한 후 추가를 클릭합니다.

    초기 구성이 완료될 때까지 기다립니다.

Cloud Monitoring 대시보드 만들기

  1. Monitoring에서 대시보드로 이동한 후 대시보드 만들기를 클릭합니다.

  2. 차트 추가를 클릭합니다.

  3. 이름 필드에 Bucket Access를 입력합니다.

  4. Cloud Storage 버킷에 대한 요청 콘텐츠 측정항목을 찾으려면 리소스 및 측정항목 찾기 필드에서 request를 입력한 후 gcs_bucket 리소스에 대해 요청 수 측정항목을 선택합니다.

  5. 버킷 이름으로 측정항목을 그룹으로 묶으려면 그룹화 기준 드롭다운 목록에서 bucket_name을 클릭합니다.

  6. 메서드 이름으로 필터링하려면 필터 필드에서 ReadObject를 입력한 후 적용을 누릅니다.

  7. 저장을 클릭합니다.

  8. 이름 필드에 Bucket Usage를 입력합니다.

  9. 대시보드에 액세스할 수 있는지 확인하려면 포인터를 대시보드 위에 두고 버킷 사용량이 표시되는지 확인합니다.

    버킷에서 객체 액세스를 관찰하도록 Monitoring을 구성했습니다. Cloud Storage 버킷에 대한 트래픽이 없기 때문에 이 차트에 데이터가 표시되지 않습니다.

서비스 제공 버킷에서 부하 생성

이제 모니터링이 구성되었으므로 Apache Bench를 사용하여 서비스 제공 버킷으로 트래픽을 전송할 수 있습니다.

  1. Cloud Shell에서 서비스 제공 버킷의 객체로 요청을 전송합니다.

    ab -n 10000 \
        http://storage.googleapis.com/$PROJECT_ID-serving-bucket/testfile.txt
    
  2. Google Cloud 콘솔에서 Monitoring으로 이동합니다.

    Cloud Monitoring으로 이동

  3. 버킷 사용량 대시보드를 선택하려면 포인터를 대시보드 위에 두고 버킷 사용량을 선택합니다. 서비스 제공 버킷에만 트래픽이 있는지 확인합니다. 유휴 버킷에 트래픽이 없기 때문에 request_count metric 시계열이 서비스 제공 버킷에 대해서만 표시됩니다.

Cloud 함수 검토 및 배포

  1. Cloud Shell에서 Cloud 함수를 사용해서 스토리지 버킷을 Nearline Storage 클래스로 마이그레이션하는 코드를 출력합니다.

    cat $WORKDIR/migrate-storage/main.py | grep "migrate_storage(" -A 15
    

    출력은 다음과 같습니다.

    def migrate_storage(request):
        # process incoming request to get the bucket to be migrated:
        request_json = request.get_json(force=True)
        # bucket names are globally unique
        bucket_name = request_json['incident']['resource_name']
    
        # create storage client
        storage_client = storage.Client()
    
        # get bucket
        bucket = storage_client.get_bucket(bucket_name)
    
        # update storage class
        bucket.storage_class = "NEARLINE"
        bucket.patch()
    

    Cloud 함수는 요청에 전달된 버킷 이름을 사용하여 해당 스토리지 클래스를 Nearline Storage로 변경합니다.

  2. Cloud 함수를 배포합니다.

    gcloud functions deploy migrate_storage --trigger-http --runtime=python37
    
  3. 트리거 URL을 다음 섹션에서 사용하는 환경 변수로 설정합니다.

    export FUNCTION_URL=$(gcloud functions describe migrate_storage \
        --format=json | jq -r '.httpsTrigger.url')
    

알림 자동화 테스트 및 검사

  1. 유휴 버킷 이름을 설정합니다.

    export IDLE_BUCKET_NAME=$PROJECT_ID-idle-bucket
    
  2. incident.json 파일을 사용하여 배포한 Cloud 함수로 테스트 알림을 전송합니다.

    envsubst < $WORKDIR/migrate-storage/incident.json | curl -X POST \
        -H "Content-Type: application/json" $FUNCTION_URL -d @-
    

    출력은 다음과 같습니다.

    OK
    

    출력이 줄바꿈으로 종료되지 않으며, 따라서 바로 다음에 명령 프롬프트가 옵니다.

  3. 유휴 버킷이 Nearline Storage로 마이그레이션되었는지 확인합니다.

    gsutil defstorageclass get gs://$PROJECT_ID-idle-bucket
    

    출력은 다음과 같습니다.

    gs://automating-cost-optimization-idle-bucket: NEARLINE
    

프로덕션 환경 고려사항

고유 Google Cloud 환경에서 비용 최적화를 자동화할 때는 다음을 고려하세요.

  • 일반 고려사항: Google Cloud 리소스를 수정하거나 삭제할 수 있는 권한이 있는 Cloud Functions에 대해 보안을 강화해야 합니다.
  • 낭비 확인: 이 문서에서는 낭비된 지출에 대한 몇 가지 예시를 보여줍니다. 이외에도 이 세 가지 범주 중 하나에 포함되는 예시가 많이 있습니다.
    • 초과 프로비저닝된 리소스: CPU 및 메모리가 필요한 것보다 많이 설정된 VM과 같이, 제공된 워크로드에 대해 필요한 것보다 많이 프로비저닝된 리소스입니다.
    • 유휴 리소스: 완전히 사용되지 않는 리소스입니다.
    • 시간제 유휴 리소스: 업무 시간 중에만 사용되는 리소스입니다.
  • 삭제 자동화: 이 문서에서는 디스크를 스냅샷하고 삭제하기 위해 여러 비동기 작업이 포함된 다단계 프로세스가 필요합니다. 사용되지 않는 IP 주소와 같은 다른 Google Cloud 리소스는 동기 작업을 사용할 수 있습니다.
  • 대규모 배포: 이 문서에서는 Google Cloud 프로젝트 ID가 Cloud 함수 코드에 정의됩니다. 대규모로 이러한 솔루션을 배포하기 위해서는 Cloud Billing 또는 Cloud Resource Manager API를 사용하여 청구 계정 또는 조직의 프로젝트 목록을 가져올 수 있습니다. 그런 후 이러한 Google Cloud 프로젝트 ID를 함수에 대한 변수로 전달합니다. 이러한 구성에서는 Cloud 함수의 서비스 계정을 리소스 정리 또는 삭제가 가능한 프로젝트에 추가해야 합니다. Cloud Deployment Manager 또는 Terraform과 같은 자동화된 배포 프레임워크를 사용하는 것이 좋습니다.
  • 알림 자동화: 이 문서에서는 Monitoring 알림에서 모의 페이로드를 사용하여 스토리지 클래스 마이그레이션을 트리거하는 방법을 보여줍니다. Monitoring 알림 정책은 최대 23시간 59분 내에 평가할 수 있습니다. 프로덕션 환경에서 이러한 제한은 버킷을 유휴 상태로 판단해서 해당 스토리지 클래스를 마이그레이션하는 데 충분히 길지 않을 수 있습니다. Cloud Storage 버킷에서 데이터 액세스 감사 로그를 사용 설정하고 버킷이 이전 30일 동안 서비스 제공을 위해 사용되었는지 여부를 확인하기 위해 이러한 감사 로그를 사용하는 파이프라인을 만드는 것이 좋습니다. 자세한 내용을 보려면 감사 로그 이해를 검토하고, 처리를 위해 Pub/Sub 및 Dataflow 파이프라인에 로그를 전송하도록 집계 싱크를 만듭니다.

삭제

이 튜토리얼에서 사용된 리소스 비용이 Google Cloud 계정에 청구되지 않도록 하려면 리소스가 포함된 프로젝트를 삭제하거나 프로젝트를 유지하고 개별 리소스를 삭제하세요.

프로젝트 삭제

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

    리소스 관리로 이동

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

다음 단계