Running Spark jobs on Amazon EMR on EKS

2021-08-16 AWS, Kubernetes

這是在去年 AWS re:Invent 2020 發佈的新功能之一「New – Amazon EMR on Amazon Elastic Kubernetes Service (EKS)」讓 Amazon EMR 可以跑在 Amazon EKS 上,直到現在才有空來把文章補齊,順手紀錄這中間的差異。

在這之前 Amazon EMR 都是基於 Amazon EC2 跑一組 Hadoop MapReduce Cluster 這讓整個 bootstrap 時間拉的很長,光是等待時間就很惱人。而 Amazon EMR on EKS 大幅減少了 bootstrap 的時間以外,跑在 Amazon EKS 上就意味著 Data engineer 只需專注在 Jobs 上的運算。

Architecture

從左右明顯的比較當需要執行不同的 MapReduce / Spark 版本時 Amazon EMR on EKS 能以更好的空間利用率跑在 Amazon EKS 上,而 Amazon EMR on EC2 則會有許多冗余的資源沒有利用到以外 Bootstrap 等待時間更是問題。

Amazon EMR on EKS 提供標準的 API interface 也因為底層由 Kubernetes 實作,Amazon EMR 基本上不需管理底層而是由 Kubernetes 負責 HA 和調度的工作。

Prerequisites

Amazon EMR on EKS 會用到 eksctlawsclikubectl 三個不同的 Command line Tools 以及預先準備好的 Amazon EKS Cluster。除此之外 IRSARBAC 也是在這個實作過程中必須知道的實作概念。

How to submit Spark job on Amazon EMR on EKS

在 step-by-step 的部分我就不提太多細節,詳細步驟可以參考 EMR on EKS Setting upEMR on EKS Workshop 或是「一小時內搭建好 Amazon EMR on EKS 運行 Spark on Kuberentes」,在這篇我主要提起幾個步驟中需要注意的部分。

設定 RBAC configuration 授與 Amazon EMR submit jobs 到 Amazon EKS

$ eksctl create iamidentitymapping --cluster eks-sample --namespace <kubernetes-namespace>--service-name "emr-containers"

eksctl create iamidentitymapping 這個動作會完成幾件事:

  • aws-auth 加入一個 mapRoles 允 AWSServiceRoleForAmazonEMRContainers 這個 IAM Role 訪問 Amazon EKS
  • 在 Kubernetes 建立 Role, RoleBinding emr-containers
apiVersion: v1
data:
  mapRoles: |
      - rolearn: arn:aws:iam::${accountId}:role/AWSServiceRoleForAmazonEMRContainers
        username: emr-containers
...

而在 AWSServiceRoleForAmazonEMRContainers 這個 ServiceRole 必須 trust emr-containers.amazonaws.com 這個服務的授權以及掛上 AmazonEMRContainersServiceRolePolicy Managed Policy。

設定 IRSA 授與 Spark jobs 擁有 AWS 服務訪問權限

AmazonEMRContainersJobExecutionRole 這個 IAM Role 通常至少會有 Amazon S3 和 CloudWatch 寫入 Logs 的權限

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:PutLogEvents",
                "logs:CreateLogStream",
                "logs:DescribeLogGroups",
                "logs:DescribeLogStreams"
            ],
            "Resource": [
                "arn:aws:logs:*:*:*"
            ]
        }
    ]
}

IRSA 則是可以用 eksctl create iamserviceaccountaws emr-containers 來建立

$ eksctl create iamserviceaccount \
    --name aws-emr-on-eks-job-execution \
    --namespace amazon-emr \
    --cluster eks-sample \
    --attach-policy-arn arn:aws:iam::<accountId>:policy/AmazonEMRContainersJobExecutionRolePolicy \
    --approve \
    --override-existing-serviceaccounts


$ aws emr-containers update-role-trust-policy --cluster-name eks-sample --namespace amazon-emr --role-name AmazonEMRContainersJobExecutionRole

aws emr-containers 會更新 trust policy 加上第二個 sts:AssumeRoleWithWebIdentity 允許來自 Spark job 的 ServiceAccount

Service Account

emr-containers-sa--${accountId}-*

IRSA 可以檢查幾個資源:

  • Kubernetes ServiceAccount:aws-emr-on-eks-job-execution 並且 mapping 到 IAM Role
  • IAM Role AmazonEMRContainersJobExecutionRole 將信任來自 Kubernetes ServiceAccount 的 sts:AssumeRoleWithWebIdentity

若由 eksctl 建立則 IAM Role name 為 eksctl-*,但在此篇姑且稱為 AmazonEMRContainersJobExecutionRole
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${accountId}:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/0B05CA2EC6D6EC592EB9900DB42673DA"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.us-east-1.amazonaws.com/id/0B05CA2EC6D6EC592EB9900DB42673DA:sub": "system:serviceaccount:amazon-emr:aws-emr-on-eks-job-execution",
          "oidc.eks.us-east-1.amazonaws.com/id/0B05CA2EC6D6EC592EB9900DB42673DA:aud": "sts.amazonaws.com"
        }
      }
    },
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${accountId}:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/0B05CA2EC6D6EC592EB9900DB42673DA"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringLike": {
          "oidc.eks.us-east-1.amazonaws.com/id/0B05CA2EC6D6EC592EB9900DB42673DA:sub": "system:serviceaccount:amazon-emr:emr-containers-sa-*-*-${accountId}-gae37tpvfjmnnjvlpdmfzgzvzxeu1sdge2kmsimjra0hmaa6tam0yt"
        }
      }
    }
  ]
}

Create Virtual Cluster for Amazon EMR

在撰文的當下 Amazon EMR on EKS 還沒有支援 Management Console 的方法,所以多數操作必須仰賴 aws emr-containers 來建立 Virtual cluster。



$ aws emr-containers create-virtual-cluster \                                                          
--name eks-sample \        
--container-provider '{
    "id": "eks-sample",        
    "type": "EKS",
    "info": {
        "eksInfo": {
            "namespace": "amazon-emr"
        }
    }
}'

--container-provider 指定的是 Amazon EKS 顆粒度可以到 namespace,假設有 Multitenancy 需求可以多利用 namespace 來切割不同 EMR Virtual cluster

Running a Map Reduce Job

當 Virtual cluster 建立完成後會拿到 virtual cluster id,如果不小心洗掉的話可以用 describe-virtual-cluster 查到

$ aws emr-containers start-job-run \
--virtual-cluster-id <emr-virtual-cluster-id> \
--name spark-sample-job \
--execution-role-arn arn:aws:iam::381354187112:role/AmazonEMRContainersJobExecutionRole \
--release-label emr-6.3.0-latest \
--job-driver '{
    "sparkSubmitJobDriver": {
        "entryPoint": "local:///usr/lib/spark/examples/src/main/python/pi.py",
        "sparkSubmitParameters": "--conf spark.executor.instances=1 --conf spark.executor.memory=2G --conf spark.executor.cores=1 --conf spark.driver.cores=1"
    }}' \
--configuration-overrides '{
    "applicationConfiguration": [{
        "classification": "spark-defaults",
        "properties": {
            "spark.driver.memory": "2G"
        }
    }],
    "monitoringConfiguration": {
        "cloudWatchMonitoringConfiguration": {
            "logGroupName": "/aws/emr-eks/sample",
            "logStreamNamePrefix": "pi"
        },
    "persistentAppUI":"ENABLED",
    "s3MonitoringConfiguration": {
        "logUri": "s3://aws-emr-on-eks-us-east-1-381354187112"
    }}}'

Submit Job 在 Amazon EKS 上可以找到在 amazon-emr namespace 開始連續跑三個 Pods 分別是 job-runner, spark-driver 以及 execute job,運行資訊也可以在 Management Console 查到。

Summary tips for Amazon EMR on EKS

  • Amazon EMR 以 IAM Role AWSServiceRoleForAmazonEMRContainers 透過 RBAC 方法授權 Submit job 到 Amazon EKS
  • Amazon EKS Spark job 透過 IRSA 方法以 IAM AmazonEMRContainersJobExecutionRole 訪問 AWS 服務
  • 一個 Amazon EMR Spark job 會運行 Amazon EKS pods:
    • job-runner 包含 job-runner, emr-container-fluentd 以及 fluentd-data-collector
    • spark-driver 包含 spark-kubernetes-driver, emr-container-fluentd
    • execute job 包含 spark-kubernetes-executor, emr-container-fluentd

Performance of Amazon EMR with EC2 and Fargate

在最後我做了一個簡單的效能測試,用上述的 Spark job 跑在 Amazon EC2 和 AWS Fargate 時 bootstrap 的時間:

  • 跑在 Amazon EC2 有 pre-warm worker node 時 bootstrap 大約 1 分鐘
  • 跑在 Amazon EC2 沒有 pre-warm worker node 時 bootstrap 大約 7 分鐘
  • 跑在 AWS Fargate 沒有 pre-warm worker node 時 bootstrap 大約 5 分鐘

以上資訊提供參考,以原本 Amazon EMR 需要 15 分鐘以上 bootstrap 時間來說,大幅減少啟動時間。

給 Mr. 沙先生一點建議

彙整

分類

展開全部 | 收合全部

License

訂閱 Mr. 沙先生 的文章

輸入你的 email 用於訂閱