

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# Amazon EMR on EKS에서 Apache Spark의 사용자 지정 스케줄러로 YuniKorn 사용
<a name="tutorial-yunikorn"></a>

Amazon EMR on EKS을 사용하면 Spark 운영자 또는 spark-submit을 사용하여 Kubernetes 사용자 지정 스케줄러로 Spark 작업을 실행할 수 있습니다. 이 자습서에서는 사용자 지정 대기열 및 단체 예약(gang scheduling)에서 YuniKorn 스케줄러를 통해 Spark 작업을 실행하는 방법을 다룹니다.

## 개요
<a name="tutorial-yunikorn-overview"></a>

[Apache YuniKorn](https://yunikorn.apache.org/)은 앱 인식 예약 기능으로 Spark 예약 관리를 지원하므로, 이를 통해 리소스 할당량 및 우선순위를 세밀하게 제어할 수 있습니다. 단체 예약 기능을 통해 YuniKorn은 앱에 대한 최소한의 리소스 요청을 충족할 수 있는 경우에만 앱을 예약합니다. 자세한 내용은 Apache YuniKorn 설명서 사이트에서 [What is gang scheduling](https://yunikorn.apache.org/docs/user_guide/gang_scheduling/)을 참조하세요.

## 클러스터를 생성하고 YuniKorn 설정
<a name="tutorial-yunikorn-setup"></a>

다음 단계에 따라 Amazon EKS 클러스터를 배포합니다. AWS 리전 (`region`) 및 가용 영역(`availabilityZones`)을 변경할 수 있습니다.

1. Amazon EKS 클러스터를 정의합니다.

   ```
   cat <<EOF >eks-cluster.yaml
   ---
   apiVersion: eksctl.io/v1alpha5
   kind: ClusterConfig
   
   metadata:
     name: emr-eks-cluster
     region: eu-west-1
   
   vpc:
     clusterEndpoints:
       publicAccess: true
       privateAccess: true
   
   iam:
     withOIDC: true
     
   nodeGroups:
     - name: spark-jobs
       labels: { app: spark }
       instanceType: m5.xlarge
       desiredCapacity: 2
       minSize: 2
       maxSize: 3
       availabilityZones: ["eu-west-1a"]
   EOF
   ```

1. 클러스터를 생성합니다.

   ```
   eksctl create cluster -f eks-cluster.yaml
   ```

1. Spark 작업을 실행할 `spark-job` 네임스페이스를 생성합니다.

   ```
   kubectl create namespace spark-job
   ```

1. 다음으로 Kubernetes 역할 및 역할 바인딩을 생성합니다. 이는 Spark 작업 실행에서 사용하는 서비스 계정에 필요합니다.

   1. Spark 작업에 대한 서비스 계정, 역할 및 역할 바인딩을 정의합니다.

      ```
      cat <<EOF >emr-job-execution-rbac.yaml
      ---
      apiVersion: v1
      kind: ServiceAccount
      metadata:
        name: spark-sa
        namespace: spark-job
      automountServiceAccountToken: false
      ---
      apiVersion: rbac.authorization.k8s.io/v1
      kind: Role
      metadata:
        name: spark-role
        namespace: spark-job
      rules:
        - apiGroups: ["", "batch","extensions"]
          resources: ["configmaps","serviceaccounts","events","pods","pods/exec","pods/log","pods/portforward","secrets","services","persistentvolumeclaims"]
          verbs: ["create","delete","get","list","patch","update","watch"]
      ---
      apiVersion: rbac.authorization.k8s.io/v1
      kind: RoleBinding
      metadata:
        name: spark-sa-rb
        namespace: spark-job
      roleRef:
        apiGroup: rbac.authorization.k8s.io
        kind: Role
        name: spark-role
      subjects:
        - kind: ServiceAccount
          name: spark-sa
          namespace: spark-job
      EOF
      ```

   1. 다음 명령을 사용하여 Kubernetes 역할 및 역할 바인딩 정의를 적용합니다.

      ```
      kubectl apply -f emr-job-execution-rbac.yaml
      ```

## YuniKorn 설치 및 설정
<a name="tutorial-yunikorn-install"></a>

1. 다음 kubectl 명령을 사용하여 Yunikorn 스케줄러를 배포하기 위해 네임스페이스(`yunikorn`)를 생성합니다.

   ```
   kubectl create namespace yunikorn
   ```

1. 스케줄러를 설치하려면 다음 Helm 명령을 실행합니다.

   ```
   helm repo add yunikorn https://apache.github.io/yunikorn-release
   ```

   ```
   helm repo update
   ```

   ```
   helm install yunikorn yunikorn/yunikorn --namespace yunikorn
   ```

## Spark 운영자를 사용하여 YuniKorn 스케줄러에서 Spark 애플리케이션 실행
<a name="tutorial-yunikorn-sparkoperator"></a>

1. 아직 실행하지 않았다면, 다음 단계를 완료하여 설정합니다.

   1. [클러스터를 생성하고 YuniKorn 설정](#tutorial-yunikorn-setup)

   1. [YuniKorn 설치 및 설정](#tutorial-yunikorn-install)

   1. [Amazon EMR on EKS에 대한 Spark 운영자 구성](spark-operator-setup.md)

   1. [Spark 운영자 설치](spark-operator-gs.md#spark-operator-install)

      `helm install spark-operator-demo` 명령을 실행할 때 다음 인수를 포함합니다.

      ```
      --set batchScheduler.enable=true 
      --set webhook.enable=true
      ```

1. `SparkApplication` 정의 파일 `spark-pi.yaml`을 생성합니다.

   YuniKorn을 작업 스케줄러로 사용하려면 애플리케이션 정의에 특정 주석과 레이블을 추가해야 합니다. 주석과 레이블은 작업 대기열 및 사용하려는 예약 전략을 지정합니다.

   다음 예제에서는 `schedulingPolicyParameters` 주석을 사용하여 애플리케이션에 대한 단체 예약을 설정합니다. 그런 다음, 이 예제에서는 **작업 그룹**(즉 작업 '단체')을 생성하여 작업 실행을 위해 포드를 예약하기 전에 사용할 수 있어야 하는 최소 용량을 지정합니다. 마지막으로, [클러스터를 생성하고 YuniKorn 설정](#tutorial-yunikorn-setup) 섹션에 정의된 대로 작업 그룹 정의에서 `"app": "spark"` 레이블의 노드 그룹을 사용하도록 지정합니다.

   ```
   apiVersion: "sparkoperator.k8s.io/v1beta2"
   kind: SparkApplication
   metadata:
     name: spark-pi
     namespace: spark-job
   spec:
     type: Scala
     mode: cluster
     image: "895885662937.dkr.ecr.us-west-2.amazonaws.com/spark/emr-6.10.0:latest"
     imagePullPolicy: Always
     mainClass: org.apache.spark.examples.SparkPi
     mainApplicationFile: "local:///usr/lib/spark/examples/jars/spark-examples.jar"
     sparkVersion: "3.3.1"
     restartPolicy:
       type: Never
     volumes:
       - name: "test-volume"
         hostPath:
           path: "/tmp"
           type: Directory
     driver:
       cores: 1
       coreLimit: "1200m"
       memory: "512m"
       labels:
         version: 3.3.1
       annotations:
         yunikorn.apache.org/schedulingPolicyParameters: "placeholderTimeoutSeconds=30 gangSchedulingStyle=Hard"
         yunikorn.apache.org/task-group-name: "spark-driver"
         yunikorn.apache.org/task-groups: |-
           [{
               "name": "spark-driver",
               "minMember": 1,
               "minResource": {
                 "cpu": "1200m",
                 "memory": "1Gi"
               },
               "nodeSelector": {
                 "app": "spark"
               }
             },
             {
               "name": "spark-executor",
               "minMember": 1,
               "minResource": {
                 "cpu": "1200m",
                 "memory": "1Gi"
               },
               "nodeSelector": {
                 "app": "spark"
               }
           }]
       serviceAccount: spark-sa
       volumeMounts:
         - name: "test-volume"
           mountPath: "/tmp"
     executor:
       cores: 1
       instances: 1
       memory: "512m"
       labels:
         version: 3.3.1
       annotations:
         yunikorn.apache.org/task-group-name: "spark-executor"
       volumeMounts:
         - name: "test-volume"
           mountPath: "/tmp"
   ```

1. 다음 명령을 사용하여 Spark 애플리케이션을 제출합니다. 이렇게 하면 `spark-pi`라고 하는 `SparkApplication` 객체도 생성됩니다.

   ```
   kubectl apply -f spark-pi.yaml
   ```

1. 다음 명령을 사용하여 `SparkApplication` 객체에 대한 이벤트를 확인합니다.

   ```
   kubectl describe sparkapplication spark-pi --namespace spark-job
   ```

   첫 번째 포드 이벤트는 YuniKorn에서 해당 포드를 예약했음을 보여줍니다.

   ```
   Type    Reason            Age   From                          Message
   ----    ------            ----  ----                          -------
   Normal Scheduling        3m12s yunikorn   spark-operator/org-apache-spark-examples-sparkpi-2a777a88b98b8a95-driver is queued and waiting for allocation
   Normal GangScheduling    3m12s yunikorn   Pod belongs to the taskGroup spark-driver, it will be scheduled as a gang member
   Normal Scheduled         3m10s yunikorn   Successfully assigned spark
   Normal PodBindSuccessful 3m10s yunikorn   Pod spark-operator/
   Normal TaskCompleted     2m3s  yunikorn   Task spark-operator/
   Normal Pulling           3m10s kubelet    Pulling
   ```

## `spark-submit`을 사용하여 YuniKorn 스케줄러에서 Spark 애플리케이션 실행
<a name="tutorial-yunikorn-sparksubmit"></a>

1. 먼저, [Amazon EMR on EKS에서 spark-submit 설정](spark-submit-setup.md) 섹션에 나온 단계를 완료합니다.

1. 다음과 같은 환경 변수의 값을 설정합니다.

   ```
   export SPARK_HOME=spark-home
   export MASTER_URL=k8s://Amazon-EKS-cluster-endpoint
   ```

1. 다음 명령을 사용하여 Spark 애플리케이션을 제출합니다.

   다음 예제에서는 `schedulingPolicyParameters` 주석을 사용하여 애플리케이션에 대한 단체 예약을 설정합니다. 그런 다음, 이 예제에서는 **작업 그룹**(즉 작업 '단체')을 생성하여 작업 실행을 위해 포드를 예약하기 전에 사용할 수 있어야 하는 최소 용량을 지정합니다. 마지막으로, [클러스터를 생성하고 YuniKorn 설정](#tutorial-yunikorn-setup) 섹션에 정의된 대로 작업 그룹 정의에서 `"app": "spark"` 레이블의 노드 그룹을 사용하도록 지정합니다.

   ```
   $SPARK_HOME/bin/spark-submit \
    --class org.apache.spark.examples.SparkPi \
    --master $MASTER_URL \
    --conf spark.kubernetes.container.image=895885662937.dkr.ecr.us-west-2.amazonaws.com/spark/emr-6.10.0:latest \
    --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark-sa \
    --deploy-mode cluster \
    --conf spark.kubernetes.namespace=spark-job \
    --conf spark.kubernetes.scheduler.name=yunikorn \
    --conf spark.kubernetes.driver.annotation.yunikorn.apache.org/schedulingPolicyParameters="placeholderTimeoutSeconds=30 gangSchedulingStyle=Hard" \
    --conf spark.kubernetes.driver.annotation.yunikorn.apache.org/task-group-name="spark-driver" \
    --conf spark.kubernetes.executor.annotation.yunikorn.apache.org/task-group-name="spark-executor" \
    --conf spark.kubernetes.driver.annotation.yunikorn.apache.org/task-groups='[{
               "name": "spark-driver",
               "minMember": 1,
               "minResource": {
                 "cpu": "1200m",
                 "memory": "1Gi"
               },
               "nodeSelector": {
                 "app": "spark"
               }
             },
             {
               "name": "spark-executor",
               "minMember": 1,
               "minResource": {
                 "cpu": "1200m",
                 "memory": "1Gi"
               },
               "nodeSelector": {
                 "app": "spark"
               }
           }]' \
    local:///usr/lib/spark/examples/jars/spark-examples.jar 20
   ```

1. 다음 명령을 사용하여 `SparkApplication` 객체에 대한 이벤트를 확인합니다.

   ```
   kubectl describe pod spark-driver-pod --namespace spark-job
   ```

   첫 번째 포드 이벤트는 YuniKorn에서 해당 포드를 예약했음을 보여줍니다.

   ```
   Type    Reason           Age   From                          Message
   ----    ------           ----  ----                          -------
   Normal Scheduling        3m12s yunikorn   spark-operator/org-apache-spark-examples-sparkpi-2a777a88b98b8a95-driver is queued and waiting for allocation
   Normal GangScheduling    3m12s yunikorn   Pod belongs to the taskGroup spark-driver, it will be scheduled as a gang member
   Normal Scheduled         3m10s yunikorn   Successfully assigned spark
   Normal PodBindSuccessful 3m10s yunikorn   Pod spark-operator/
   Normal TaskCompleted     2m3s  yunikorn   Task spark-operator/
   Normal Pulling           3m10s kubelet    Pulling
   ```