EMR on EKS は EKS 上で Spark を動かす機能。 通常の EMR が Hadoop クラスタの管理も行うのに対して、EMR on EKS はコンテナの起動のみを担う。
AWS CLIでEMRクラスタを立ち上げSparkのアプリケーションを実行する - sambaiz-net
Kubernetes で動かすことで、管理やモニタリングの際に Kubernetes 用のツールや機能を利用でき、既存のクラスタがあれば余っているリソースを有効活用できる。 他のアプリケーションでの Kubernetes の知見を集計や分析で活かしたり、その逆もあるかもしれない。 ただ、Kubernetes および EKS の知識はある程度必要。通常の EMR は YARN の知識がなくとも使い始められるが、こちらはないと動かすのも大変だと思う。
ちなみに、Docker については EMR 6.x (Hadoop 3) から サポートされている。
料金はリクエストした vCPU とメモリに対してかかる。
クラスタの立ち上げ
CDK で EKS クラスタを立ち上げる。
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as eks from 'aws-cdk-lib/aws-eks';
const vpc = new ec2.Vpc(this, 'vpc', {
cidr: '10.0.0.0/16',
maxAzs: 2,
subnetConfiguration: [
{
cidrMask: 18,
name: 'public',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 18,
name: 'private',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
]
})
cdk.Tags.of(vpc).add("Name", this.stackName)
const mastersRole = new iam.Role(this, 'masters-role', {
assumedBy: new iam.CompositePrincipal(
new iam.ServicePrincipal('eks.amazonaws.com'),
new iam.AccountRootPrincipal(),
),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSClusterPolicy'),
],
inlinePolicies: {
'eksctl-iamidentitymapping': new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['eks:DescribeCluster'],
resources: ['*'],
})
]
})
}
})
return new eks.Cluster(this, 'cluster', {
vpc,
mastersRole,
clusterName: 'emr-on-eks-test',
version: eks.KubernetesVersion.V1_23,
defaultCapacity: 3,
defaultCapacityInstance: new ec2.InstanceType('m5.large')
})
この後実行する eksctl create iamidentitymapping で Kubernetes の権限に加えて eks:DescribeCluster が必要になるので master role にこれを追加し、assume する profile を config に追加した。 代わりにユーザーの role を aws-auth に追加して Kubernetes の権限を与えてもよい。
$ brew tap weaveworks/tap
$ brew install weaveworks/tap/eksctl
$ eksctl version
0.124.0
$ cat ~/.aws/config
...
[profile eks-master]
region = ap-northeast-1
role_arn = arn:aws:iam::xxxxx:role/EmrOnEksTestStack-clusterMastersRoleEABFBB9C-10Q5X4BE7QN51
source_profile = <user_profile_name>
クラスタへのアクセス権限の付与
EMR on EKS の service-linked role に紐づいた User に Kubernetes role を bind する。
RBACが有効なGKEでHelmを使う - sambaiz-net
マニュアルで行うこともできるが、次のコマンドを実行するとリソースや設定が自動で追加される。
$ CLUSTER_NAME=emr-on-eks-test
$ NAMESPACE=default
$ eksctl create iamidentitymapping \
--cluster $CLUSTER_NAME \
--namespace $NAMESPACE \
--service-name emr-containers \
--profile eks-master
2022-12-31 17:40:34 [ℹ] created "default:Role.rbac.authorization.k8s.io/emr-containers"
2022-12-31 17:40:34 [ℹ] created "default:RoleBinding.rbac.authorization.k8s.io/emr-containers"
2022-12-31 17:40:34 [ℹ] adding identity "arn:aws:iam::xxxxx:role/AWSServiceRoleForAmazonEMRContainers" to auth ConfigMap
bind されていることが確認できる。
$ kubectl describe -n kube-system configmap aws-auth
...
mapRoles:
----
...
- rolearn: arn:aws:iam::xxxxx:role/AWSServiceRoleForAmazonEMRContainers
username: emr-containers
...
$ kubectl get rolebinding -n $NAMESPACE emr-containers
NAME ROLE AGE
emr-containers Role/emr-containers 14m
クラスタの登録
EMR にクラスタを登録する。
new emrcontainers.CfnVirtualCluster(this, 'virtual-cluster', {
name: 'test_virtual_cluster',
containerProvider: {
id: cluster.clusterName,
type: 'EKS',
info: {
eksInfo: {
namespace: 'default'
}
}
}
})
Execution role の作成
Job の実行に必要な Policy を付与した Role を作成する。
new iam.Role(this, 'job-exectuion-role', {
roleName: "emr-on-eks-test-job-execution-role",
assumedBy: new iam.AccountRootPrincipal(), // update later
inlinePolicies: {
'job-execution-policy': new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
's3:PutObject',
's3:GetObject',
's3:ListBucket',
],
resources: ['arn:aws:s3:::*'],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'logs:PutLogEvents',
'logs:CreateLogStream',
'logs:DescribeLogGroups',
'logs:DescribeLogStreams'
],
resources: ['arn:aws:logs:*:*:*'],
})
]
})
}
})
次のコマンドを実行すると ジョブの送信時に作成される EMR on EKS の service account に対する trust policy が追加される。
$ EXECUTION_ROLE_NAME=emr-on-eks-test-job-execution-role
$ aws emr-containers update-role-trust-policy \
--cluster-name $CLUSTER_NAME \
--namespace $NAMESPACE \
--role-name $EXECUTION_ROLE_NAME
Successfully updated trust policy of role emr-on-eks-test-job-execution-role
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::xxxxx:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/aaaaa"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringLike": {
"oidc.eks.ap-northeast-1.amazonaws.com/id/aaaaa:sub": "system:serviceaccount:default:emr-containers-sa-*-*-xxxxx-<BASE36_ENCODED_ROLE_NAME>"
}
}
}
クラスタ の issuer url は次のコマンドで取得できる。
$ aws eks describe-cluster --name $CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text
https://oidc.eks.ap-northeast-1.amazonaws.com/id/aaaaa
IAM OIDC プロバイダーに登録する。
GitHub ActionsからOIDCでassumeできるRoleをCDKで作成する - sambaiz-net
$ eksctl utils associate-iam-oidc-provider --cluster $CLUSTER_NAME --approve
2023-01-02 13:39:28 [ℹ] will create IAM Open ID Connect provider for cluster "emr-on-eks-test" in "ap-northeast-1"
2023-01-02 13:39:29 [✔] created IAM Open ID Connect provider for cluster "emr-on-eks-test" in "ap-northeast-1"
$ aws iam list-open-id-connect-providers
{
"OpenIDConnectProviderList": [
{
"Arn": "arn:aws:iam::xxxxx:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/aaaaa"
},
....
]
}
Spark ジョブの実行
start-job-run すると pod が立ち上がる。リソースが足りないと pending する。 driver が S3 の AccessDenied で失敗する場合は execution-role が assume できていない可能性があるので、trust policy や IAM OIDC プロバイダーが登録されているか確認する。
$ VIRTUAL_CLUSTER_ID=xxxxx
$ EXECUTION_ROLE_ARN=arn:aws:iam::xxxxx:role/emr-on-eks-test-job-execution-role
$ aws emr-containers start-job-run \
--virtual-cluster-id $VIRTUAL_CLUSTER_ID \
--name pi-2 \
--execution-role-arn $EXECUTION_ROLE_ARN \
--release-label emr-6.7.0-latest \
--job-driver '{
"sparkSubmitJobDriver": {
"entryPoint": "s3://aws-data-analytics-workshops/emr-eks-workshop/scripts/pi.py",
"sparkSubmitParameters": "--conf spark.executor.instances=1 --conf spark.executor.memory=2G --conf spark.executor.cores=1 --conf spark.driver.cores=1"
}
}'
{
"id": "000000031bdlkdo0q1k",
"name": "pi-2",
"arn": "arn:aws:emr-containers:ap-northeast-1:xxxxx:/virtualclusters/xxxxx/jobruns/000000031bdlkdo0q1k",
"virtualClusterId": "xxxxx"
}
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
000000031bdlkdo0q1k-sn72c 2/2 Running 0 21s
pythonpi-c4ae29856dad6d33-exec-1 0/1 ContainerCreating 0 1s
spark-000000031bdlkdo0q1k-driver 2/2 Running 0 12s
$ kubectl describe pod spark-000000031bdlkdo0q1k-driver
...
spark-kubernetes-driver:
Image: 059004520145.dkr.ecr.ap-northeast-1.amazonaws.com/spark/emr-6.7.0:latest
Args:
driver
--properties-file
/usr/lib/spark/conf/spark.properties
--class
org.apache.spark.deploy.PythonRunner
local:///usr/lib/spark/examples/src/main/python/pi.py
...