Spark로 Workday 데이터에 액세스

Managed Service for Apache Spark를 사용하면 Spark 워크로드에서 직접 외부 비즈니스 데이터를 쿼리하고 분석할 수 있습니다. Workday Live Data Query JDBC 드라이버를 사용하여 Workday 테넌트에 연결하고, 실시간 Workday 데이터 세트를 Spark DataFrame에 로드하고, 해당 데이터를 Google Cloud의 분석 파이프라인에 통합할 수 있습니다.

이 가이드에서는 Workday Live Data Query를 구성하고, Secret Manager에 비공개 키를 안전하게 저장하고, Google Cloud Data Agent Kit 확장 프로그램을 사용하여 Visual Studio Code 또는 Cloud Shell 편집기에서 Managed Service for Apache Spark를 대상으로 PySpark 노트북을 실행합니다.

비용

Data Agent Kit 확장 프로그램은 추가 비용 없이 사용할 수 있습니다. 하지만 이 가이드에서는 비용이 청구될 수 있는 다음과 같은 Google Cloud구성요소를 사용합니다.

프로젝트 사용량을 기준으로 예상 비용을 산출하려면 가격 계산기를 사용하세요.

시작하기 전에

  1. Google Cloud 계정에 로그인합니다. Google Cloud를 처음 사용하는 경우 계정을 만들고 Google 제품의 실제 성능을 평가해 보세요. 신규 고객에게는 워크로드를 실행, 테스트, 배포하는 데 사용할 수 있는 $300의 무료 크레딧이 제공됩니다.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that you have the permissions required to complete this guide.

  4. Verify that billing is enabled for your Google Cloud project.

  5. Enable the Dataproc, Dataplex, Cloud Storage, and Secret Manager APIs, if any are not already enabled.

    Roles required to enable APIs

    To enable APIs, you need the serviceusage.services.enable permission. If you created the project, then you likely already have this permission through the Owner role (roles/owner). Otherwise, you can get this permission through the Service Usage Admin role (roles/serviceusage.serviceUsageAdmin). Learn how to grant roles.

    Enable the APIs

  6. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  7. Verify that you have the permissions required to complete this guide.

  8. Verify that billing is enabled for your Google Cloud project.

  9. Enable the Dataproc, Dataplex, Cloud Storage, and Secret Manager APIs, if any are not already enabled.

    Roles required to enable APIs

    To enable APIs, you need the serviceusage.services.enable permission. If you created the project, then you likely already have this permission through the Owner role (roles/owner). Otherwise, you can get this permission through the Service Usage Admin role (roles/serviceusage.serviceUsageAdmin). Learn how to grant roles.

    Enable the APIs

필요한 Identity and Access Management (IAM) 역할

이 페이지의 예시를 실행하려면 특정 IAM 역할이 필요합니다. 조직 정책에 따라 이러한 역할이 이미 부여되었을 수 있습니다. 역할 부여를 확인하려면 역할을 부여해야 하나요?를 참조하세요. 역할 부여에 대한 자세한 내용은 프로젝트, 폴더, 조직에 대한 액세스 관리를 참조하세요.

필요한 역할은 워크로드 실행 방식에 따라 다릅니다. Managed Service for Apache Spark 서버리스 2.3 이하 서버리스 런타임은 워크로드를 서비스 계정으로 실행합니다. Managed Service for Apache Spark 서버리스 런타임 3.0 이상은 기본적으로 최종 사용자 인증 정보(EUC)를 사용하여 워크로드를 실행하므로 서비스 계정 대신 사용자 계정에 데이터 액세스 역할을 부여합니다. 3.0 이상 런타임에 커스텀 서비스 계정을 지정하면 워크로드가 서비스 계정 사용자 인증 정보로 실행됩니다. 자세한 내용은 서버리스 배포 역할을 참조하세요.

사용자 역할

Managed Service for Apache Spark 대화형 노트북 세션을 만들고 리소스에 액세스하는 데 필요한 권한을 얻으려면 관리자에게 다음 IAM 역할을 부여해 달라고 요청하세요.

  • 서버리스 런타임 2.3 이하를 사용합니다.
  • 서버리스 런타임 3.0 이상 사용: 프로젝트에 대한 서버리스 Dataproc 편집자(roles/dataproc.serverlessEditor)
  • 최종 사용자 인증 정보로 Cloud Storage에서 JDBC 드라이버 읽기: 드라이버가 포함된 버킷의 스토리지 객체 뷰어(roles/storage.objectViewer)
  • 최종 사용자 인증 정보를 사용하여 Secret Manager에서 비공개 키 보안 비밀 읽기: 보안 비밀 또는 프로젝트의 Secret Manager 보안 비밀 접근자(roles/secretmanager.secretAccessor)

서비스 계정 역할

워크로드를 실행하는 서비스 계정에 다음 역할을 부여합니다. Managed Service for Apache Spark 서버리스 런타임 3.0 이상에서는 워크로드가 기본 최종 사용자 인증 정보(EUC) 대신 서비스 계정 사용자 인증 정보로 실행되는 경우에만 이러한 역할이 필요합니다.

Compute Engine 기본 서비스 계정 또는 커스텀 서비스 계정에 Managed Service for Apache Spark 워크로드를 실행하고, 드라이버를 읽고, 보안 비밀에 액세스하는 데 필요한 권한이 있는지 확인하려면 관리자에게 Compute Engine 기본 서비스 계정 또는 커스텀 서비스 계정에 다음 IAM 역할을 부여해 달라고 요청하세요.

Workday 기본 요건

실시간 데이터 쿼리를 사용하여 Workday에 연결하기 전에 다음 설정을 완료하세요.

  1. Workday에서 Workday Live Data Query JDBC 드라이버를 다운로드합니다.

  2. 다운로드한 JDBC 드라이버 JAR 파일을 프로젝트의 Cloud Storage 버킷에 업로드합니다.

  3. Workday 실시간 데이터 쿼리 문서에 설명된 대로 Workday 테넌트에서 실시간 데이터 쿼리를 설정하는 구성 단계를 완료합니다.

    Workday에서 구성하는 동안 통합 시스템 사용자(ISU)를 만들고, API 클라이언트를 등록하고, 비공개 키 쌍을 생성합니다.

  4. 다음 Workday 구성 값을 확인합니다.

    • Workday 호스트: Workday 테넌트 호스트(예: services1.workday.com)

    • 테넌트 이름: Workday 테넌트 이름입니다.

    • 클라이언트 ID: Workday 테넌트의 등록된 API 클라이언트 ID입니다.

    • ISU 사용자 이름: 통합 시스템 사용자 이름입니다.

Secret Manager에 비공개 키 저장

Workday 실시간 데이터 쿼리 구성 중에 생성된 비공개 키 파일을 Secret Manager에 안전하게 저장합니다.

  1. Cloud Shell에서 다음 명령어를 실행하여 x509 키 쌍을 생성합니다.

    openssl req -x509 -newkey rsa:2048 -keyout private-key.pem -out public-key.cert -sha256 -days 365 -nodes
    
  2. 비공개 키를 보려면 다음 명령어를 실행합니다.

    cat private-key.pem
    
  3. Secret Manager에 비공개 키를 저장합니다.

    1. Google Cloud 콘솔에서 Secret Manager 페이지로 이동합니다.

      Secret Manager로 이동

    2. 보안 비밀 만들기를 클릭합니다.

    3. 이름 필드에 보안 비밀의 이름을 입력합니다(예: workday-private-key).

    4. 보안 비밀 값에서 private-key.pem를 업로드하거나 -----BEGIN PRIVATE KEY----- 및 -----END PRIVATE KEY----- 헤더 및 바닥글 줄을 포함한 비공개 키 콘텐츠를 붙여넣습니다.

    5. 보안 비밀 만들기를 클릭합니다.

  4. 공개 키를 보려면 다음 명령어를 실행합니다.

    cat public-key.cert
    
  5. Workday에서 Create x509 Public Key(x509 공개 키 만들기)를 검색하고 public-key.cert의 공개 키를 붙여넣은 다음 OK(확인)를 클릭합니다.

  6. 이전에 만든 Workday API 클라이언트에서 x509 Certificate(x509 인증서)를 새 x509 키로 업데이트합니다.

IDE에서 Data Agent Kit 설정

Visual Studio Code 또는 다른 지원되는 IDE에 확장 프로그램을 설치하거나 Cloud Shell 편집기에 사전 설치된 확장 프로그램을 사용하여 Data Agent Kit를 설정할 수 있습니다.

IDE 또는 Cloud Shell 편집기에서 Data Agent Kit를 구성하려면 다음 단계를 완료하세요.

  1. Data Agent Kit를 설정하고 구성합니다.

    1. Data Agent Kit 확장 프로그램을 설치합니다.

    2. Data Agent Kit를 구성합니다.

노트북 만들기

Data Agent Kit를 사용하여 대화형 PySpark 노트북을 만듭니다.

  1. IDE 또는 Cloud Shell 편집기에서 Data Agent Kit 패널을 엽니다.

  2. Apache Spark에서 새 노트북을 선택한 다음 로컬 커널을 선택합니다.

  3. 사용 가능한 런타임(세션 템플릿이라고도 함) 목록에서 런타임을 선택합니다.

  4. 새 노트북으로 시작하기를 선택합니다.

    Spark Connect 세션 초기화 코드로 채워진 초기 셀이 있는 새 노트북이 열립니다.

  5. 노트북 편집기 상단에서 커널 선택을 클릭한 다음 노트북의 로컬 Python 커널을 선택합니다.

PySpark에서 Workday 데이터 쿼리

  1. 첫 번째 노트북 셀에서 필요한 클라이언트 라이브러리를 설치하고 환경 변수를 설정합니다.

    %pip install dataproc-spark-connect google-cloud-dataproc google-cloud-secret-manager google-cloud-storage
    
    #@title Set up all environment variables
    
    # Replace with your Project ID
    %env PROJECT_ID=PROJECT_ID
    
    # Replace with the name of the Secret Manager global secret in the project
    %env SECRET_NAME=workday-private-key
    # Replace with the version of the secret to use
    %env SECRET_VERSION=latest
    
    # Replace with the Cloud Storage bucket where you have the Workday Data Cloud JDBC Driver file stored
    %env JDBC_DRIVER_GCS_BUCKET=BUCKET_NAME
    # Replace with the name of the Workday Data Cloud JDBC Driver file in the Cloud Storage bucket
    %env JDBC_DRIVER_FILE_NAME=ldq-jdbc-driver-1.1.0-all.jar
    # Replace with the local directory of the notebook to copy the JDBC Driver File to
    %env JDBC_DRIVER_LOCAL_DIRECTORY=/tmp
    # Replace with the Driver Class for the Workday Data Cloud JDBC Driver
    %env WORKDAY_DRIVER_CLASS=com.workday.ldq.jdbc.Driver
    
    # ------------------------------------------------------------------------------
    
    # Replace with the Workday URL of your tenant
    %env WORKDAY_HOST=WORKDAY_HOST
    # Replace with your Workday tenant name
    %env TENANT_NAME=TENANT_NAME
    # Replace with the Client ID of the "API Client"
    %env CLIENT_ID=CLIENT_ID
    # Replace with the Integration System User username
    %env ISU_USERNAME=ISU_USERNAME
    
  2. 새 셀에서 Compute Engine 기본 서비스 계정에 Secret Manager 보안 비밀 접근자 역할(roles/secretmanager.secretAccessor)을 부여합니다.

    %%bash
    COMPUTE_ENGINE_DEFAULT_SERVICE_ACCOUNT=$(gcloud iam service-accounts list --project="${PROJECT_ID}" \
      --filter="email ~ -compute@developer.gserviceaccount.com" \
      --format="value(email)")
    
    echo "COMPUTE_ENGINE_DEFAULT_SERVICE_ACCOUNT: ${COMPUTE_ENGINE_DEFAULT_SERVICE_ACCOUNT}"
    
    gcloud secrets add-iam-policy-binding ${SECRET_NAME} \
      --project="${PROJECT_ID}" \
      --member="serviceAccount:${COMPUTE_ENGINE_DEFAULT_SERVICE_ACCOUNT}" \
      --role="roles/secretmanager.secretAccessor"
    
  3. 새 셀에서 JDBC 드라이버를 Cloud Storage에서 로컬 디렉터리로 복사합니다.

    gsutil cp gs://$JDBC_DRIVER_GCS_BUCKET/$JDBC_DRIVER_FILE_NAME $JDBC_DRIVER_LOCAL_DIRECTORY
    
  4. 새 셀에서 Secret Manager 클라이언트 라이브러리를 설치합니다.

    %%bash
    pip install google-cloud-secret-manager
    
  5. 새 셀에서 드라이버 JAR 파일이 로컬에 다운로드되었는지 확인합니다.

    !gsutil cp gs://$JDBC_DRIVER_GCS_BUCKET/$JDBC_DRIVER_FILE_NAME /tmp/
    !ls -lh /tmp/ldq-jdbc-driver-1.1.0-all.jar
    
  6. 새 셀에서 Workday 드라이버 JAR을 PySpark jars 디렉터리에 삽입합니다.

    %%bash
    
    echo "Downloading Workday JAR from Cloud Storage..."
    # Downloads your specific 1.1.0 driver JAR into the notebook's /tmp folder
    gcloud storage cp gs://$JDBC_DRIVER_GCS_BUCKET/$JDBC_DRIVER_FILE_NAME /tmp/
    
    echo "Dynamically finding the PySpark jars directory..."
    PYSPARK_JARS=$(python -c "import os, pyspark; print(os.path.join(os.path.dirname(pyspark.__file__), 'jars'))")
    echo "Found PySpark jars at: $PYSPARK_JARS"
    
    echo "Injecting Workday driver into PySpark core..."
    cp /tmp/$JDBC_DRIVER_FILE_NAME "$PYSPARK_JARS/"
    
    echo "Success! The driver is now a native part of PySpark."
    
  7. 새 셀에서 Spark 환경을 구성하고 Secret Manager에서 x509 비공개 키를 가져옵니다.

    #@title Configure the Spark environment and retrieve the x509 private key from Secret Manager
    
    import os
    import pyspark
    from pyspark.sql import SparkSession
    from google.cloud import secretmanager
    
    PROJECT_ID = os.environ["PROJECT_ID"]
    SECRET_NAME = os.environ["SECRET_NAME"]
    SECRET_VERSION = os.environ["SECRET_VERSION"]
    JDBC_DRIVER_LOCAL_DIRECTORY = os.environ["JDBC_DRIVER_LOCAL_DIRECTORY"]
    JDBC_DRIVER_FILE_NAME = os.environ["JDBC_DRIVER_FILE_NAME"]
    LOCAL_JAR_PATH = JDBC_DRIVER_LOCAL_DIRECTORY + "/" + JDBC_DRIVER_FILE_NAME
    WORKDAY_HOST = os.environ["WORKDAY_HOST"]
    TENANT_NAME = os.environ["TENANT_NAME"]
    CLIENT_ID = os.environ["CLIENT_ID"]
    ISU_USERNAME = os.environ["ISU_USERNAME"]
    WORKDAY_DRIVER_CLASS = os.environ["WORKDAY_DRIVER_CLASS"]
    
    jdbc_url = f"jdbc:workday://{WORKDAY_HOST}:443/system/information_schema?SSLVerification=CA"
    
    def access_private_key_secret():
        print(f"Fetching secret contents from Secret Manager: {SECRET_NAME}...")
        client = secretmanager.SecretManagerServiceClient()
        secret_name = f"projects/{PROJECT_ID}/secrets/{SECRET_NAME}/versions/{SECRET_VERSION}"
        response = client.access_secret_version(request={"name": secret_name})
        print(f"Fetched secret contents from Secret Manager: {SECRET_NAME}")
        return response.payload.data.decode("UTF-8")
    
    private_key_content = access_private_key_secret()
    
  8. 새 셀에서 클래스 경로 구성으로 Spark 세션을 초기화합니다.

    # Stop the old cached Spark session so your newly downloaded JAR is not ignored!
    try:
        spark.stop()
        print("Stopped existing Spark session.")
    except Exception:
        pass
    
    print("Initializing fresh Spark session with Classpath configuration...")
    print(f"Loading JAR from: {LOCAL_JAR_PATH}")
    
    # Rebuild Spark session with explicit Driver & Executor classpaths
    spark = SparkSession.builder \
        .appName("WorkdayJDBC Connection Example") \
        .config("spark.jars", LOCAL_JAR_PATH) \
        .config("spark.driver.extraClassPath", LOCAL_JAR_PATH) \
        .config("spark.executor.extraClassPath", LOCAL_JAR_PATH) \
        .getOrCreate()
    print("Spark Session successfully booted with JAR!")
    
  9. 마지막 셀에서 연결을 시작하고 Workday를 쿼리합니다.

    import os
    import subprocess
    import zipfile
    from pyspark.sql import SparkSession
    
    LOCAL_JAR_PATH = "/tmp/ldq-jdbc-driver-1.1.0-all.jar"
    
    # Load bucket and file name from your Cell 1 environment variables
    GCS_BUCKET = os.environ.get("JDBC_DRIVER_GCS_BUCKET", "BUCKET_NAME")
    GCS_FILE = os.environ.get("JDBC_DRIVER_FILE_NAME", "ldq-jdbc-driver-1.1.0-all.jar")
    GCS_URI = f"gs://{GCS_BUCKET}/{GCS_FILE}"
    
    # 1. Ensure the JAR file is in the notebook's /tmp directory
    if not os.path.exists(LOCAL_JAR_PATH):
        print(f"Downloading {GCS_URI} to {LOCAL_JAR_PATH}...")
        subprocess.run(["gsutil", "cp", GCS_URI, LOCAL_JAR_PATH], check=True)
    print(f"JAR verified on disk: {LOCAL_JAR_PATH} ({os.path.getsize(LOCAL_JAR_PATH):,} bytes)")
    
    # 2. Read the real JDBC driver class name (skipping any '#' comment lines!)
    with zipfile.ZipFile(LOCAL_JAR_PATH, "r") as z:
        raw_lines = (
            z.read("META-INF/services/java.sql.Driver")
            .decode("utf-8")
            .splitlines()
        )
        # Filter out empty lines and '#' comment lines
        valid_classes = [
            line.strip()
            for line in raw_lines
            if line.strip() and not line.strip().startswith("#")
        ]
        driver_class = valid_classes[0]
    
    print(f"Real Driver Class inside JAR: {driver_class}")
    
    # 3. Get or create the Spark session
    spark = SparkSession.builder.appName("WorkdayJDBC Connection Example").getOrCreate()
    
    # 4. Inject the JAR directly into the running Java URLClassLoader (and Scala SparkContext)
    jvm = spark.sparkContext._jvm
    jar_url = jvm.java.io.File(LOCAL_JAR_PATH).toURI().toURL()
    
    # Add to both the Spark classloader and the current thread's context classloader
    jvm.org.apache.spark.util.Utils.getContextOrSparkClassLoader().addURL(jar_url)
    jvm.java.lang.Thread.currentThread().getContextClassLoader().addURL(jar_url)
    spark.sparkContext._jsc.sc().addJar(LOCAL_JAR_PATH)
    
    # Register the driver class with Spark's JDBC DriverRegistry
    jvm.org.apache.spark.sql.execution.datasources.jdbc.DriverRegistry.register(driver_class)
    print(f"Successfully registered {driver_class} in Spark's live JVM!")
    
    # 5. Query Workday
    print("Querying Workday...")
    df_data = (
        spark.read.format("jdbc")
        .option("driver", driver_class)
        .option("url", jdbc_url)
        .option("wd.authn.clientId", CLIENT_ID)
        .option("wd.authn.isu", ISU_USERNAME)
        .option(
            "wd.authn.accessTokenEndpoint",
            f"https://{WORKDAY_HOST}/ccx/oauth2/{TENANT_NAME}/token",
        )
        .option("wd.authn.privateKey", private_key_content)
        .option("query", "SELECT * FROM system.jdbc.catalogs")
        .load()
    )
    
    print("Connection Successful! Data Retrieved:")
    df_data.show(50, truncate=False)
    

    다음을 바꿉니다.

    • PROJECT_ID: Google Cloud 프로젝트 ID입니다.
    • LOCATION: Managed Service for Apache Spark 세션 템플릿의 Google Cloud 리전(예: us-central1)
    • RUNTIME_ID: Managed Service for Apache Spark 세션 템플릿의 ID입니다.
    • BUCKET_NAME: Workday JDBC 드라이버가 포함된 Cloud Storage 버킷의 이름입니다.
    • PATH/workday-live-data-query-jdbc-driver.jar: Cloud Storage 버킷에 있는 Workday JDBC 드라이버 JAR의 객체 경로입니다.
    • WORKDAY_HOST: Workday 테넌트 호스트(예: services1.workday.com)
    • TENANT_NAME: Workday 테넌트 이름입니다.
    • CLIENT_ID: Workday 테넌트의 등록된 API 클라이언트 ID입니다.
    • ISU_USERNAME: Workday 테넌트의 통합 시스템 사용자 이름입니다.
  10. 셀 옆에 있는 셀 실행(재생 아이콘)을 클릭하거나 Shift+Enter를 누릅니다.

    Managed Service for Apache Spark가 쿼리를 실행하고 셀 아래에 Workday DataFrame 출력을 표시합니다.

삭제

이 가이드에서 사용한 리소스에 대한 비용이 Google Cloud 계정에 청구되지 않도록 하려면 활성 대화형 세션을 종료하거나 생성된 리소스를 삭제하세요.

대화형 세션 종료

  1. IDE 또는 Cloud Shell 편집기에서 워크로드가 완료되면 활성 노트북 세션을 종료합니다.

  2. 선택사항: 더 이상 필요하지 않은 경우 Cloud Storage 버킷 또는 JDBC 드라이버 JAR 파일을 삭제합니다.

  3. 선택사항: 더 이상 필요하지 않은 경우 Secret Manager에서 보안 비밀을 삭제합니다.

    gcloud secrets delete workday-private-key
    

다음 단계