Workflows 권장사항

Workflows를 사용하여 서비스를 조정할 때 여기에 나열된 권장사항을 참조할 수 있습니다.

여기 나열된 권장사항은 전체 목록이 아니며 기본적인 Workflows 사용 방법을 알려주지 않습니다. 이 문서에서는 사용자가 전반적인 Google Cloud 환경 및 Workflows을 일반적으로 이해하고 있다고 가정합니다. 자세한 내용은 Google Cloud 아키텍처 프레임워크Workflows 개요를 참조하세요.

최적의 통신 패턴 선택

여러 서비스를 배포하기 위해 마이크로서비스 아키텍처를 설계할 때는 다음 커뮤니케이션 패턴 중에서 선택할 수 있습니다.

  • 직접적인 서비스 간 통신

  • 간접적인 이벤트 기반 통신(코레오그래피라고도 함)

  • 자동화된 구성, 조정, 관리(조정이라고도 함)

앞에 표시된 각 옵션의 장점과 단점을 고려하고 사용사례에 맞는 최적 패턴을 선택합니다. 예를 들어 직접적인 서비스 간 통신은 다른 옵션보다 간단하게 구현할 수 있지만 서비스를 긴밀하게 결합합니다. 반면에 이벤트 중심 아키텍처를 사용하면 서비스를 느슨하게 결합할 수 있지만 모니터링 및 디버깅이 복잡할 수 있습니다. 마지막으로 Workflows와 같은 중앙 조정자를 사용하면 유연성이 낮더라도 직접적인 서비스 간 통신의 긴밀한 결합 또는 코레오그래피 이벤트의 복잡성 없이 서비스 간 통신을 조정할 수 있습니다.

또한 통신 패턴을 조합할 수 있습니다. 예를 들어 이벤트 중심 조정에서는 느슨하게 연결된 서비스가 이벤트로 트리거되는 조정에 따라 관리됩니다. 마찬가지로 한 번의 조정으로 다른 조정된 시스템에 대해 Pub/Sub 메시지가 발생하는 시스템을 설계할 수 있습니다.

일반적인 도움말

Workflows를 서비스 조정자로 사용하도록 결정한 후에는 다음과 같은 유용한 팁을 참조하세요.

URL 하드코딩 방지

URL 하드코딩을 방지하여 여러 환경 간에 이식 가능하고 더 쉽게 유지보수할 수 있는 워크플로를 지원할 수 있습니다. 이렇게 하려면 다음 방법을 따르면 됩니다.

  • URL을 런타임 인수로 정의합니다.

    이렇게 하면 워크플로가 클라이언트 라이브러리 또는 API를 통해 호출될 때 도움이 될 수 있습니다. (하지만 워크플로가 Eventarc 이벤트로 트리거되고 전달 가능한 유일한 인수가 이벤트 페이로드인 경우에는 작동하지 않습니다.)

    예시

    main:
      params: [args]
      steps:
        - init:
            assign:
              - url1: ${args.urls.url1}
              - url2: ${args.urls.url2}

    워크플로를 실행할 때 URL을 지정할 수 있습니다. 예를 들면 다음과 같습니다.

    gcloud workflows run multi-env --data='{"urls":{"url1": "URL_ONE", "url2": "URL_TWO"}}'
  • 환경 변수를 사용하고 배포 환경에 따라 동적으로 구성되는 워크플로를 만듭니다. 또는 템플릿으로 재사용되고 별도로 유지 관리되는 환경 변수에 따라 구성할 수 있는 워크플로를 만듭니다.

  • 단일 워크플로 정의 파일을 만들 수 있는 대체 기법을 사용하지만 워크플로에서 자리표시자를 대체하는 도구를 사용하여 변수를 배포합니다. 예를 들어 Cloud Build를 사용하여 워크플로를 배포하고 Cloud Build 구성 파일에서 워크플로의 자리표시자 URL을 대체하는 단계를 추가할 수 있습니다.

    예시

    steps:
    ‐ id: 'replace-urls'
      name: 'gcr.io/cloud-builders/gcloud'
      entrypoint: bash
      args:
        - -c
        - |
          sed -i -e "s~REPLACE_url1~$_URL1~" workflow.yaml
          sed -i -e "s~REPLACE_url2~$_URL2~" workflow.yaml
    ‐ id: 'deploy-workflow'
      name: 'gcr.io/cloud-builders/gcloud'
      args: ['workflows', 'deploy', 'multi-env-$_ENV', '--source', 'workflow.yaml']

    그런 후 빌드 시간에 변수 값을 대체할 수 있습니다. 예를 들면 다음과 같습니다.

    gcloud builds submit --config cloudbuild.yaml \
        --substitutions=_ENV=staging,_URL1="URL_ONE",_URL2="URL_TWO"

    자세한 내용은 CLI 및 API를 통해 빌드 제출을 참조하세요.

    또는 Terraform을 사용하여 인프라를 프로비저닝하고 입력 변수를 사용해서 각 환경에 대해 워크플로를 만드는 구성 파일을 정의할 수 있습니다.

    예시

    variable "project_id" {
      type = string
    }
    
    variable "url1" {
      type = string
    }
    
    variable "url2" {
      type = string
    }
    
    locals {
      env = ["staging", "prod"]
    }
    
    # Define and deploy staging and production workflows
    resource "google_workflows_workflow" "multi-env-workflows" {
      for_each = toset(local.env)
    
      name            = "multi-env-${each.key}"
      project         = var.project_id
      region          = "us-central1"
      source_contents = templatefile("${path.module}/workflow.yaml", { url1 : "${var.url1}-${each.key}", url2 : "${var.url2}-${each.key}" })
    }

    구성의 루트 모듈에 변수를 선언할 때는 여러 방법으로 변수에 값을 할당할 수 있습니다. 예를 들면 다음과 같습니다.

    terraform apply -var="project_id=PROJECT_ID" -var="url1=URL_ONE" -var="url2=URL_TWO"
  • Secret Manager 커넥터를 사용하여 Secret Manager에 URL을 안전하게 저장하고 검색합니다.

중첩 단계 사용

모든 워크플로에는 단계가 하나 이상 있어야 합니다. 기본적으로 Workflows는 단계가 순서가 지정된 목록에 있는 것처럼 단계를 처리하고 모든 단계가 실행될 때까지 한 번에 하나씩 실행합니다. 그러나 일부 단계를 논리적으로 그룹화해야 하는 경우에는 steps 블록을 사용하여 일련의 단계를 중첩시킬 수 있습니다. 이렇게 하면 일련의 단계를 처리하기 위해 올바른 원자적 단계를 지정할 수 있기 때문에 편리합니다.

예시

main:
    params: [input]
    steps:
    - callWikipedia:
        steps:
        - checkSearchTermInInput:
            switch:
                - condition: ${"searchTerm" in input}
                  assign:
                    - searchTerm: ${input.searchTerm}
                  next: readWikipedia
        - getCurrentDate:
            call: http.get
            args:
                url: https://timeapi.io/api/Time/current/zone?timeZone=Europe/Amsterdam
            result: currentDate
        - setFromCallResult:
            assign:
                - searchTerm: ${currentDate.body.dayOfWeek}
        - readWikipedia:
            call: http.get
            args:
                url: https://en.wikipedia.org/w/api.php
                query:
                    action: opensearch
                    search: ${searchTerm}
            result: wikiResult
    - returnOutput:
            return: ${wikiResult.body[1]}

표현식 묶음 표시

모든 표현식$로 시작하고 중괄호로 묶어야 합니다.

${EXPRESSION}

YAML 파싱 문제를 방지하려면 표현식을 따옴표로 묶을 수 있습니다. 예를 들어 콜론이 포함된 표현식은 콜론이 맵 정의로 해석될 때 예기치 않은 동작을 일으킬 수 있습니다. YAML 표현식을 작은따옴표로 묶으면 이 문제를 해결할 수 있습니다.

'${"Name: " + myVar}'

또한 여러 줄에 걸쳐 있는 표현식을 사용할 수 있습니다. 예를 들어 Workflows BigQuery 커넥터를 사용할 때 SQL 쿼리를 따옴표로 묶어야 할 수 있습니다.

예시

- runQuery:
    call: googleapis.bigquery.v2.jobs.query
    args:
        projectId: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
        body:
            useLegacySql: false
            useQueryCache: false
            timeoutMs: 30000
            # Find top 100 titles with most views on Wikipedia
            query: ${
                "SELECT TITLE, SUM(views)
                FROM `bigquery-samples.wikipedia_pageviews." + table + "`
                WHERE LENGTH(TITLE) > 10
                GROUP BY TITLE
                ORDER BY SUM(VIEWS) DESC
                LIMIT 100"
                }
    result: queryResult

전체 워크플로 정의는 여러 BigQuery 작업 동시 실행을 참조하세요.

선언적 호출 사용

Workflows를 사용하여 워크플로 자체에서 서비스를 호출하고 결과를 처리하며 HTTP 호출 수행과 같은 간단한 태스크를 실행합니다. Workflows는 서비스를 호출하고 응답을 파싱하며 다른 연결된 서비스의 입력을 구성할 수 있습니다. 서비스를 호출하면 추가 호출, 추가 종속 항목, 서비스 호출 서비스의 복잡성을 방지할 수 있습니다. 비즈니스 로직이 없는 서비스를 선언적 API 호출로 바꾸고 Workflows를 사용해서 복잡성을 추상화할 수 있습니다.

하지만 Workflows 표현식과 표준 라이브러리에서 지원하지 않는 재사용 가능한 비즈니스 로직, 복잡한 계산 또는 변환을 구현하는 등 Workflows에서 복잡한 작업을 수행할 수 있도록 서비스를 만들어야 합니다. 복잡한 사례에서는 일반적으로 YAML 또는 JSON과 Workflows 구문을 사용하는 대신 코드로 구현하기가 더 쉽습니다.

필요한 항목만 저장

리소스 제한 또는 ResourceLimitError, MemoryLimitExceededError, ResultSizeLimitExceededError와 같이 이를 나타내는 오류가 발생하지 않도록 메모리 소비를 관리합니다.

변수에 저장할 대상을 선별적으로 지정하고 필요한 항목만 필터링하고 저장합니다. 서비스가 너무 큰 페이로드를 반환하는 경우에는 개별 함수를 사용해서 호출을 수행하고 필요한 것만 반환합니다.

변수를 삭제하여 메모리를 확보할 수 있습니다. 예를 들어 후속 단계에 필요한 메모리를 확보할 수 있습니다. 또는 관심 없는 결과가 포함된 호출이 있을 수 있으며 해당 결과를 모두 생략할 수 있습니다.

null을 할당하여 변수를 삭제할 수 있습니다. 또한 YAML에서 빈 값 또는 ~를 변수에 할당할 수 있습니다. 이렇게 하면 안전하게 확보할 수 있는 메모리가 식별됩니다.

예시

  - step:
      assign:
        - bigVar:

하위 워크플로 및 외부 워크플로 사용

하위 워크플로를 사용하면 여러 번 호출하려는 일련의 논리 또는 단계 집합을 정의하여 워크플로 정의를 단순화할 수 있습니다. 하위 워크플로는 프로그래밍 언어의 함수 또는 루틴과 비슷합니다. 매개변수 및 반환 값을 사용할 수 있기 때문에 다양한 애플리케이션에 사용 가능한 복잡한 워크플로를 만들 수 있습니다.

하위 워크플로는 해당 워크플로 정의로 한정되며 다른 워크플로에서 재사용할 수 없습니다. 하지만 다른 워크플로에서 워크플로를 호출할 수 있습니다. Workflows 커넥터는 이를 수행하는 데 도움을 줄 수 있습니다. 자세한 내용은 Workflow Executions APIWorkflows API의 커넥터 개요를 참조하세요.

Workflows 커넥터 사용

Workflows는 워크플로 내에서 다른 Google Cloud 제품에 쉽게 액세스할 수 있게 해주는 여러 커넥터를 제공합니다. 커넥터는 Google Cloud API의 세부정보를 알 필요가 없도록 요청의 형식 지정을 처리하여 메서드와 인수를 제공하므로 호출 서비스를 단순화합니다. 또한 커넥터에는 재시도장기 실행 작업을 처리하기 위한 기본 제공 동작이 포함되므로, 반복 작업을 수행하고 호출이 완료될 때까지 기다릴 필요가 없습니다. 커넥터가 이 작업을 자동으로 수행합니다.

Google Cloud API를 호출해야 하면 먼저 이에 대한 Workflows 커넥터가 있는지 확인합니다. 그리고 Google Cloud 제품의 커넥터가 보이지 않으면 이를 요청할 수 있습니다.

커넥터 사용 방법을 알아보고 사용 가능한 커넥터의 세부 참조를 보려면 커넥터 참조를 확인합니다.

워크플로 단계 동시 실행

Workflows는 단계를 순차적으로 실행할 수 있지만 사용자가 개별 단계를 동시에 실행할 수도 있습니다. 일부 경우에는 이렇게 해서 워크플로 실행 속도를 크게 향상시킬 수 있습니다. 자세한 내용은 워크플로 단계 동시 실행을 참조하세요.

재시도 및 Saga 패턴 적용

복원력이 우수하고 임시 및 영구 서비스 오류를 모두 처리할 수 있는 워크플로를 설계합니다. 예를 들어 Workflows 오류는 실패한 HTTP 요청, 함수, 커넥터에 의해 발생하거나 자체 워크플로 코드에 의해 생성될 수 있습니다. 한 단계의 오류로 인해 전체 워크플로가 실패하지 않도록 오류 처리 및 재시도를 추가합니다.

일부 비즈니스 트랜잭션은 여러 서비스에 걸쳐 있으므로 여러 서비스를 포함하는 트랜잭션을 구현하는 메커니즘이 필요합니다. Saga 설계 패턴은 분산된 트랜잭션 시나리오에서 마이크로서비스 간 데이터 일관성을 관리하는 한 가지 방법입니다. Saga는 모든 트랜잭션에 대해 이벤트를 게시하고 다음 트랜잭션을 트리거하는 일련의 트랜잭션입니다. 트랜잭션이 실패하면 Saga가 시퀀스에서 앞에 언급된 오류에 대처하는 보상 트랜잭션을 실행합니다. GitHub에서 Workflows의 재시도 및 Saga 패턴 튜토리얼을 사용해 보세요.

자세한 내용은 Google Cloud에서 마이크로서비스 아키텍처의 트랜잭션 워크플로를 참조하세요.

콜백을 사용하여 대기

콜백을 사용하면 다른 서비스가 콜백 엔드포인트에 워크플로 실행을 재개하는 요청을 보낼 때까지 워크플로 실행을 대기시킬 수 있습니다.

콜백을 통해 지정된 이벤트가 발생했음을 워크플로에 알리고 폴링 없이 이벤트를 기다릴 수 있습니다. 예를 들어 제품이 다시 입고되거나 상품이 배송되었을 때 알림을 제공하거나 주문 검토, 번역 검증과 같은 인적 상호작용을 위해 대기하는 워크플로를 만들 수 있습니다. 또한 콜백 및 Eventarc 트리거를 사용하여 이벤트를 대기할 수 있습니다.

장기 실행 작업 조정

장기 실행 일괄 처리를 실행하려면 Batch 또는 Cloud Run 작업을 사용하고 Workflows를 사용해서 서비스를 관리할 수 있습니다. 이렇게 하면 이점을 결합하고 전체 프로세스를 효율적으로 프로비저닝 및 조정할 수 있습니다.

Batch는 Compute Engine 가상 머신(VM) 인스턴스에서 일괄 처리 워크로드를 예약, 큐에 추가, 실행할 수 있게 해주는 완전 관리형 서비스입니다. Batch용 Workflows 커넥터를 사용하여 Batch 작업을 예약하고 실행할 수 있습니다. 자세한 내용은 튜토리얼를 참조하세요.

Cloud Run 작업은 작업을 수행하고 작업이 완료되면 종료되는 코드를 실행하기 위해 사용됩니다. Workflows를 사용하면 워크플로의 일부로 Cloud Run 작업을 실행하여 보다 복잡한 데이터 처리를 수행하거나 기존 작업 시스템을 조정할 수 있습니다. 튜토리얼에서 Workflows를 사용해서 Cloud Run 작업을 실행하는 방법을 확인할 수 있습니다.

장기 실행 태스크 컨테이너화

Workflows 및 Compute Engine을 사용해서 장기 실행 컨테이너 실행을 자동화할 수 있습니다. 예를 들어 어디에서나 실행될 수 있도록 장기 실행 태스크를 컨테이너화한 후 최대 워크플로 실행 기간(1년) 동안 Compute Engine VM에서 컨테이너를 실행할 수 있습니다.

Workflows를 사용하여 VM 만들기, VM에서 컨테이너 실행, VM 삭제를 자동화할 수 있습니다. 이렇게 하면 서버를 사용하고 컨테이너를 실행할 수 있지만 둘 다 관리하는 데 따른 복잡성을 없앨 수 있으며 Cloud Functions 또는 Cloud Run과 같은 서비스를 사용할 때 시간이 제한적인 경우에 유용할 수 있습니다. GitHub에서 Workflows 및 Compute Engine의 장기 실행 컨테이너 튜토리얼을 참조하세요.

Workflows에서 명령줄 도구 실행

Cloud Build는 Google Cloud에서 빌드를 일련의 빌드 단계로 실행하고, 각 빌드 단계가 Docker 컨테이너에서 실행되는 서비스입니다. 빌드 단계를 실행하는 것은 스크립트에서 명령을 실행하는 것과 유사합니다.

Google Cloud CLI에는 gcloud, gsutil, bq, kubectl 명령줄 도구가 포함되지만 Workflows에서 gcloud CLI 명령어를 실행할 수 있는 직접적인 방법이 없습니다. 하지만 Cloud Build는 gcloud CLI가 포함된 컨테이너 이미지를 제공합니다. Cloud Build 단계의 이러한 컨테이너에서 gcloud CLI 명령어를 실행하고 Cloud Build 커넥터를 사용하여 Workflows에서 해당 단계를 만들 수 있습니다.

예시

워크플로에서 gcloud를 실행합니다.

# This example shows how to execute gcloud commands from Workflows
# using Cloud Build and returns the output

main:
  steps:
  - execute_command:
      call: gcloud
      args:
          args: "workflows list"
      result: result
  - return_result:
      return: ${result}

gcloud:
  params: [args]
  steps:
  - create_build:
      call: googleapis.cloudbuild.v1.projects.builds.create
      args:
        projectId: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
        parent: ${"projects/" + sys.get_env("GOOGLE_CLOUD_PROJECT_ID") + "/locations/global"}
        body:
          serviceAccount: ${sys.get_env("GOOGLE_CLOUD_SERVICE_ACCOUNT_NAME")}
          options:
            logging: CLOUD_LOGGING_ONLY
          steps:
          - name: gcr.io/google.com/cloudsdktool/cloud-sdk
            entrypoint: /bin/bash
            args: ${["-c", "gcloud " + args + " > $$BUILDER_OUTPUT/output"]}
      result: result_builds_create
  - return_build_result:
      return: ${text.split(text.decode(base64.decode(result_builds_create.metadata.build.results.buildStepOutputs[0])), "\n")}

워크플로에서 kubectl을 실행합니다.

# This example shows how to execute kubectl commands from Workflows
# using Cloud Build and returns the output

main:
  steps:
  - execute_command:
      call: kubectl
      args:
          args: "--help"
      result: result
  - return_result:
      return: ${result}

kubectl:
  params: [args]
  steps:
  - create_build:
      call: googleapis.cloudbuild.v1.projects.builds.create
      args:
        projectId: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
        parent: ${"projects/" + sys.get_env("GOOGLE_CLOUD_PROJECT_ID") + "/locations/global"}
        body:
          serviceAccount: ${sys.get_env("GOOGLE_CLOUD_SERVICE_ACCOUNT_NAME")}
          options:
            logging: CLOUD_LOGGING_ONLY
          steps:
          - name: gcr.io/cloud-builders/kubectl
            entrypoint: /bin/bash
            args: ${["-c", "kubectl " + args + " > $$BUILDER_OUTPUT/output"]}
      result: result_builds_create
  - return_build_result:
      return: ${text.split(text.decode(base64.decode(result_builds_create.metadata.build.results.buildStepOutputs[0])), "\n")}

Terraform을 사용하여 워크플로 만들기

Terraform은 코드를 사용해서 클라우드 인프라를 예측적으로 만들고, 변경하고, 개선할 수 있게 해주는 코드형 인프라 도구입니다.

Terraform google_workflows_workflow 리소스를 사용하여 워크플로를 정의하고 배포할 수 있습니다. 자세한 내용은 Terraform을 사용하여 워크플로 만들기를 참조하세요.

큰 워크플로를 관리하고 유지보수하기 위해 개별 YAML 파일에 워크플로를 만들고 templatefile 함수를 사용해서 이 파일을 Terraform으로 가져올 수 있습니다. 이 함수는 지정된 경로의 파일을 읽고 해당 콘텐츠를 템플릿으로 렌더링합니다.

예시

  # Define a workflow
  resource "google_workflows_workflow" "workflows_example" {
    name            = "sample-workflow"
    region          = var.region
    description     = "A sample workflow"
    service_account = google_service_account.workflows_service_account.id
    # Import main workflow YAML file
    source_contents = templatefile("${path.module}/workflow.yaml",{})
  }

마찬가지로 여러 하위 워크플로를 호출하는 기본 워크플로가 있으면 기본 워크플로 및 하위 워크플로를 별도의 파일에 정의하고 templatefile 함수를 사용해서 이를 가져올 수 있습니다.

예시

  # Define a workflow
  resource "google_workflows_workflow" "workflows_example" {
    name            = "sample-workflow"
    region          = var.region
    description     = "A sample workflow"
    service_account = google_service_account.workflows_service_account.id
    # Import main workflow and subworkflow YAML files
    source_contents = join("", [
      templatefile(
        "${path.module}/workflow.yaml",{}
      ),

      templatefile(
        "${path.module}/subworkflow.yaml",{}
      )])
  }

워크플로를 디버깅할 때 줄 번호를 참조하는 경우에는 Terraform 구성 파일을 통해 가져온 모든 YAML 파일이 병합되고 단일 워크플로로 배포됩니다.

Git 저장소에서 워크플로 배포

Cloud Build빌드 트리거를 사용하여 CI/CD 자동화를 사용 설정합니다. 새 커밋을 저장소에 푸시할 때 또는 pull 요청이 시작될 때와 같은 수신 이벤트를 리슨하고 새 이벤트가 수신되면 빌드를 자동으로 실행하도록 트리거를 구성할 수 있습니다.

Cloud Build 트리거를 사용하여 Git 저장소에서 자동으로 빌드를 시작하고 워크플로를 배포할 수 있습니다. 소스 저장소의 변경사항에 따라 워크플로를 배포하도록 트리거를 구성하거나 변경사항이 특정 기준과 일치할 때만 워크플로를 배포할 수 있습니다.

이 방식은 배포 수명 주기를 관리하는 데 유용합니다. 예를 들어 스테이징 환경의 워크플로에 변경사항을 배포하고 해당 환경에서 테스트를 실행한 후 이러한 변경사항을 프로덕션 환경에 점진적으로 도입할 수 있습니다. 자세한 내용은 Cloud Build를 사용하여 Git 저장소에서 워크플로 배포를 참조하세요.

사용 최적화

워크플로 실행 비용은 최소한으로 적용됩니다. 하지만 볼륨 사용량이 높은 경우 사용을 최적화고 비용을 줄이기 위해 다음 안내를 따르세요.

  • 커스텀 도메인을 사용하는 대신 모든 Google Cloud 서비스 호출에 *.appspot.com, *.cloud.goog, *.cloudfunctions.net, *.run.app이 사용되도록 해서 외부 단계가 아닌 내부 단계에 대해 비용이 청구되도록 합니다.

  • 지연 시간 및 신뢰성 요구와 비용을 균형적으로 조정하는 커스텀 재시도 정책을 적용합니다. 재시도 간격을 줄이면 지연 시간이 감소하고 신뢰성이 향상되지만 비용도 높아집니다.

  • 장기 실행 작업을 기다리는 커넥터를 사용할 때는 비용에 따라 지연 시간을 최적화하는 커스텀 폴링 정책을 설정합니다. 예를 들어 작업이 1시간 넘게 걸릴 것으로 예상되면 중간에 오류가 발생할 것을 대비해서 처음 1분 후에 폴링을 수행하고 이후 15분 간격으로 폴링을 수행하는 정책을 만들 수 있습니다.

  • 할당을 하나의 단계로 조합합니다.

  • sys.log 단계를 과도하게 사용하지 않습니다. 대신 호출 로깅을 사용하세요.

권장사항 요약

다음 표에서는 이 문서에서 추천하는 일반적인 팁과 권장사항을 요약해서 보여줍니다.

일반적인 도움말
권장사항

다음 단계