Dataproc は Google Cloud が提供しているサービスのひとつ。Dataproc を使うことで、Apache Spark や Hadoop を利用するためのインフラを簡単に用意することができる。
そして Python で Apache Spark を使うための仕組みが PySpark 。
Dataproc を利用する際には複数のサービスアカウントが必要になるのだが、それぞれに必要な権限を与えないと Dataproc を利用することはできない。
また、Dataproc API を有効にすると複数のサービスアカウントが自動的に作成される。サービスアカウントを適切に管理するためには、これらの内容や用途も理解しておく必要がある。
この記事では、「Dataproc クラスタで PySpark ジョブを実行する」ことを題材にして、サービスアカウントにはどのような種類があるのか、そしてそれはどのように使われるのか見ていく。
基本的に gcloud CLI を使って操作していく。
gcloud CLI の基本的な使い方については以下に書いた。
また、作業の都合上、オーナー権限(roles/owner
)を持ったユーザーアカウントで gcloud CLI の認証を行っている。
途中で認証するアカウントを変えるが、その際は明示する。
プロジェクトの準備
どのような操作をするとどのようなサービスアカウントが作られるのか分かりやすくするために、今回は Google Cloud プロジェクトを作るところから始める。
以下のコマンドでsample-pj
プロジェクトを作成する。
$ gcloud projects create sample-pj
作成したプロジェクトを gcloud CLI のカレントプロジェクトに設定する。
$ gcloud config set core/project sample-pj
Dataproc クラスタを利用するためには請求先アカウントを設定する必要があるが、プロジェクト作成直後は設定されていない。
$ gcloud billing projects describe sample-pj billingAccountName: '' billingEnabled: false name: projects/sample-pj/billingInfo projectId: sample-pj
今回は予め用意しておいた請求先アカウントを使う。既存の請求先アカウントの ID は$ gcloud billing accounts list
で確認できる。
$ gcloud billing accounts list
ACCOUNT_ID NAME OPEN MASTER_ACCOUNT_ID
XXXXXX-XXXXXX-XXXXXX 請求先アカウント 1 True
以下のコマンドで請求先アカウントをプロジェクトに設定できる。
$ gcloud billing projects link sample-pj --billing-account=XXXXXX-XXXXXX-XXXXXX billingAccountName: billingAccounts/XXXXXX-XXXXXX-XXXXXX billingEnabled: true name: projects/sample-pj/billingInfo projectId: sample-pj
sample-pj
の IAM ポリシーで使われているサービスアカウントを以下のコマンドで調べてみる。
$ gcloud projects get-iam-policy sample-pj --flatten="bindings[].members" --filter="bindings.members:serviceAccount" --format="table(bindings.role, bindings.members)"
何も表示されないので、この時点ではまだ存在しないことが分かる。
自動生成されるサービスアカウント
Dataproc を使うために Dataproc API を有効にする。
$ gcloud services enable dataproc.googleapis.com
そうすると新たに以下の API が有効になる。
- compute.googleapis.com
- dataproc-control.googleapis.com
- dataproc.googleapis.com
- oslogin.googleapis.com
Compute Engine も有効になるのは、Dataproc クラスタは Compute Engine のインスタンス上に作られるため。
Google Kubernetes Engine のクラスタ上に Dataproc クラスタを作るパターンもあるが、この記事では扱わない。
そして Dataproc API を有効にしたこのタイミングで、複数のサービスアカウントが設定される。
$ gcloud projects get-iam-policy sample-pj --flatten="bindings[].members" --filter="bindings.members:serviceAccount" --format="table(bindings.role, bindings.members)" ROLE MEMBERS roles/compute.serviceAgent serviceAccount:service-123456789@compute-system.iam.gserviceaccount.com roles/dataproc.serviceAgent serviceAccount:service-123456789@dataproc-accounts.iam.gserviceaccount.com roles/editor serviceAccount:123456789-compute@developer.gserviceaccount.com roles/editor serviceAccount:123456789@cloudservices.gserviceaccount.com
上記のサービスアカウントを全て使う必要はない。バックグラウンドで Google Cloud が使うためのものであるため、ユーザーは特に意識しなくてもよいサービスアカウントもある。
そのことを理解するためにはまず、「Dataproc を使用する際に必要なサービスアカウントは何か、それはどのように使われるか」を先に理解したほうがよいので、それを見ていく。
サービスアカウントを使用する 3 つの要素
Dataproc クラスタを利用する際、3 つの要素でサービスアカウントが使われる。
- Dataproc API ユーザー
- Dataproc サービスを呼び出して、クラスタの作成やジョブの送信などを行う
- コントロールプレーン
- Compute Engine リソースの作成、Cloud Storage のバケットの作成とそれに対する読み書き、などを行う
- データプレーン
- 以下を行う
- コントロールパネルと通信
- Cloud Storage のバケットへの読み書き
- BigQuery などとのやり取りが必要なら、それもデータプレーンが行う
- 以下を行う
公式ドキュメントに掲載されている図も載せておく。
Dataproc クラスタを利用するためには、この 3 つそれぞれに対して必要な権限を持ったサービスアカウント(またはユーザーアカウント)を接続する必要がある。
コントロールプレーンについては、意識しなくてよい。
コントロールプレーンのために必要なサービスアカウントは Google Cloud が作成し管理するからだ。
このような「Google Cloud が作成し管理する」サービスアカウントはサービスエージェントと呼ばれる。
先ほど見た 4 つのサービスアカウントのうち以下の 3 つがサービスエージェントに該当する。
123456789@cloudservices.gserviceaccount.com
- 公式ドキュメントによれば「お客様に代わって内部の Google Cloud プロセスを実行」するのが、このサービスエージェントの役割
- 恐らく特定のサービスに紐づくわけではない処理を担当しているのだと思われる
service-123456789@compute-system.iam.gserviceaccount.com
- Compute Engine のサービスエージェント
- 既に述べたように Dataproc は Compute Engine を利用するので、このサービスエージェントも作られる
service-123456789@dataproc-accounts.iam.gserviceaccount.com
- Dataproc のサービスエージェント
- これが自動的にコントロールプレーンに接続される
- そのためユーザーは特に意識しなくてよい
3 つの要素のひとつである「Dataproc API ユーザー」についてはこの記事の最後で説明するので、一旦スキップする。
データプレーンが使用するサービスアカウントは、 Dataproc クラスタを作成するときに指定することができる。
「指定する」ではなく「指定することができる」と書いたのは、指定は必須ではないため。
指定しなかった場合は、「デフォルトのサービスアカウント」が Dataproc クラスタに接続される。
デフォルトのサービスアカウントは、特定の Google Cloud サービスを有効にするとき、または使用するときに Google Cloud によって自動的に作成される。
以下のサービスが、デフォルトのサービスアカウントを作成する。
サービス | サービスアカウント名 | メールアドレス |
---|---|---|
App Engine、および App Engine を使用する Google Cloud サービス | App Engine default service account | <PROJECT_ID>@appspot.gserviceaccount.com |
Compute Engine、および Compute Engine を使用する Google Cloud サービス | Compute Engine default service account | <PROJECT_NUMBER>-compute@developer.gserviceaccount.com |
既に説明したように Dataproc は「Compute Engine を使用する Google Cloud サービス」であるため、123456789-compute@developer.gserviceaccount.com
が作られた。
デフォルトのサービスアカウントはこのように自動的に作成されるが、サービスエージェントとは異なり管理はユーザーが行う必要がある。
今回はデフォルトのサービスアカウントは使わず、データプレーンが使用するためのサービスアカウントを自分で作成することにする。
worker-for-data-plane
という名前のサービスアカウントを作る。
$ gcloud iam service-accounts create worker-for-data-plane
この時点では何のロールも付与されていないので付与する。今回はroles/dataproc.worker
を付与する。
$ gcloud projects add-iam-policy-binding sample-pj --member="serviceAccount:worker-for-data-plane@sample-pj.iam.gserviceaccount.com" --role="roles/dataproc.worker"
worker-for-data-plane@sample-pj.iam.gserviceaccount.com
がroles/dataproc.worker
で追加されている。
$ gcloud projects get-iam-policy sample-pj --flatten="bindings[].members" --filter="bindings.members:serviceAccount" --format="table(bindings.role, bindings.members)" ROLE MEMBERS roles/compute.serviceAgent serviceAccount:service-123456789@compute-system.iam.gserviceaccount.com roles/dataproc.serviceAgent serviceAccount:service-123456789@dataproc-accounts.iam.gserviceaccount.com roles/dataproc.worker serviceAccount:worker-for-data-plane@sample-pj.iam.gserviceaccount.com roles/editor serviceAccount:123456789-compute@developer.gserviceaccount.com roles/editor serviceAccount:123456789@cloudservices.gserviceaccount.com
Dataproc クラスタの作成
以下のコマンドでmy-cluster
という名前の Dataproc クラスタを作成できる。
データプレーンで使用するサービスアカウントは--service-account
で指定できる。省略した場合、既に説明したように<PROJECT_NUMBER>-compute@developer.gserviceaccount.com
が使われる。
$ gcloud dataproc clusters create my-cluster --service-account=worker-for-data-plane@sample-pj.iam.gserviceaccount.com --region=asia-northeast1 --single-node --master-machine-type=e2-standard-2
以下のエラーが出る。
ERROR: (gcloud.dataproc.clusters.create) INVALID_ARGUMENT: Subnetwork 'default' does not support Private Google Access which is required for Dataproc clusters when 'internal_ip_only' is set to 'true'. Enable Private Google Access on subnetwork 'default' or set 'internal_ip_only' to 'false'.
Compute Engine のインスタンス上に Dataproc クラスタが作られるわけだが、その Dataproc クラスタは Dataproc API を利用する。しかし Compute Engine インスタンスが内部 IP アドレスしか持たないため API にアクセスできず、エラーになってしまった。
ネットワークの設定を変えて「限定公開の Google アクセス」と呼ばれる設定を有効にすることで、このエラーを解消できる。
この設定を有効にすると、内部 IP アドレスしか持たないインスタンスでも Google Cloud の API にアクセスできるようになるためだ。
現在このプロジェクトにはdefault
というネットワークがある。
$ gcloud compute networks list NAME SUBNET_MODE BGP_ROUTING_MODE IPV4_RANGE GATEWAY_IPV4 default AUTO REGIONAL
これはプロジェクトに最初から存在するネットワークで、Dataproc クラスタ作成時にネットワークを明示しない場合はこれが使われる。
今回はこのネットワークを使うので、このネットワークの設定を変える。
まずは現在の設定を確認する。
$ gcloud compute networks subnets describe default --region=asia-northeast1 | grep 'privateIpGoogleAccess' privateIpGoogleAccess: false
privateIpGoogleAccess
が無効になっているので有効にする。
$ gcloud compute networks subnets update default --region=asia-northeast1 --enable-private-ip-google-access
有効になっている。
$ gcloud compute networks subnets describe default --region=asia-northeast1 | grep 'privateIpGoogleAccess' privateIpGoogleAccess: true
これで Dataproc クラスタを作成できるようになるので、改めて以下のコマンドを実行する。このとき--network
や--subnet
を使うことで、使用するネットワークを指定できる。今回は指定していないので、先述したようにdefault
ネットワークが使われる。
$ gcloud dataproc clusters create my-cluster --service-account=worker-for-data-plane@sample-pj.iam.gserviceaccount.com --region=asia-northeast1 --single-node --master-machine-type=e2-standard-2
Dataproc クラスタ一覧を見ると、確かにmy-cluster
が作られている。
$ gcloud dataproc clusters list --region=asia-northeast1 NAME PLATFORM PRIMARY_WORKER_COUNT SECONDARY_WORKER_COUNT STATUS ZONE SCHEDULED_DELETE my-cluster GCE RUNNING asia-northeast1-b
そしてmy-cluster
にはworker-for-data-plane@sample-pj.iam.gserviceaccount.com
が接続されている。
$ gcloud dataproc clusters describe my-cluster --region=asia-northeast1 --format="get(config.gce_cluster_config.service_account)" worker-for-data-plane@sample-pj.iam.gserviceaccount.com
Compute Engine インスタンスが作られていることも確認しておく。
$ gcloud compute instances list NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS my-cluster-m asia-northeast1-b e2-standard-2 10.146.0.2 RUNNING
そしてインスタンスmy-cluster-m
にもworker-for-data-plane@sample-pj.iam.gserviceaccount.com
が接続されている。
$ gcloud compute instances describe my-cluster-m --zone=asia-northeast1-b --format="get(serviceAccounts.email)" worker-for-data-plane@sample-pj.iam.gserviceaccount.com
ジョブの送信
Dataproc クラスタを作成できたので、次はいよいよジョブを送信する。
まずは Dataproc クラスタに処理してもらいたいスクリプトを用意する。
以下の内容のexample.py
を用意した。
#!/usr/bin/python import pyspark # SparkContext を作成 sc = pyspark.SparkContext() data = [('Alice', 34), ('Bob', 45), ('Cathy', 29), ('David', 40), ('Eve', 22), ('Frank', 38)] # データを RDD に変換 rdd = sc.parallelize(data) # 年齢が30以上の人をフィルタリング filtered_sorted_rdd = rdd.filter(lambda x: x[1] >= 30).sortBy(lambda x: x[0]) # 結果を収集して出力 result = filtered_sorted_rdd.collect() print(result)
以下のコマンドで、このスクリプトを処理するジョブを送信できる。
$ gcloud dataproc jobs submit pyspark ./example.py --cluster=my-cluster --region=asia-northeast1
送信すると様々なログが流れるが、そのなかに[('Alice', 34), ('Bob', 45), ('David', 40), ('Frank', 38)]
というログが混じっているはず。
正しくフィルタリングされており、ジョブが実行されたことがわかる。
ここで、Dataproc クラスタに接続しているサービスアカウントworker-for-data-plane@sample-pj.iam.gserviceaccount.com
のロールを変えてみる。
現在roles/dataproc.worker
が付与されているので、これを外して代わりにroles/logging.viewer
を付与する。
$ gcloud projects remove-iam-policy-binding sample-pj --member="serviceAccount:worker-for-data-plane@sample-pj.iam.gserviceaccount.com" --role="roles/dataproc.worker" $ gcloud projects add-iam-policy-binding sample-pj --member="serviceAccount:worker-for-data-plane@sample-pj.iam.gserviceaccount.com" --role="roles/logging.viewer"
この状態でジョブを送信するとエラーになる。
$ gcloud dataproc jobs submit pyspark ./example.py --cluster=my-cluster --region=asia-northeast1 WARNING: Job terminated, but output did not finish streaming. ERROR: (gcloud.dataproc.jobs.submit.pyspark) Job [ed6de677d45a467c9e912e5ac78b263f] failed with error: Task was not acquired
このように、接続しているサービスアカウントに適切な権限が付与されていないと Dataproc クラスタにジョブを処理させることはできない。
roles/logging.viewer
は外し再びroles/dataproc.worker
を付与しておく。
「Dataproc API ユーザー」について
冒頭で述べたように gcloud CLI にはオーナー権限(roles/owner
)を持ったユーザーアカウントで認証しており、その状態で gcloud CLI でジョブを送信したので、このユーザーアカウントが「Dataproc API ユーザー」にあたる。
このユーザーアカウントで Dataproc クラスタを作成し、このユーザーアカウントでジョブを送信した。
だが実際の開発現場では、セキュリティ的な観点から、オーナー権限を持ったプリンシパルで操作を行うことは稀なはず。
適切な権限を持たせたプリンシパルを用意してそれを使うことが多いと思う。
先ほど示したスクリプトの処理を Dataproc クラスタに実行させるためには、以下の 2 つのロールが付与されていればよい。
roles/dataproc.editor
roles/storage.objectUser
そのことを確認するために、上記のロールが付与されているサービスアカウントと、何のロールも付与されていないサービスアカウントを用意して、動作検証してみる。
まず、ロールが付与されていないサービスアカウントempty-for-api-user
とそのキーを用意する。
$ gcloud iam service-accounts create empty-for-api-user Created service account [empty-for-api-user]. $ gcloud iam service-accounts keys create empty-for-api-user-key.json --iam-account=empty-for-api-user@sample-pj.iam.gserviceaccount.com
次に、必要なロールが付与されたサービスアカウントviable-for-api-user
とそのキーを作る。
$ gcloud iam service-accounts create viable-for-api-user Created service account [viable-for-api-user]. $ gcloud iam service-accounts keys create viable-for-api-user-key.json --iam-account=viable-for-api-user@sample-pj.iam.gserviceaccount.com $ gcloud projects add-iam-policy-binding sample-pj --member="serviceAccount:viable-for-api-user@sample-pj.iam.gserviceaccount.com" --role="roles/dataproc.editor" $ gcloud projects add-iam-policy-binding sample-pj --member="serviceAccount:viable-for-api-user@sample-pj.iam.gserviceaccount.com" --role="roles/storage.objectUser"
まずはempty-for-api-user
を使ってジョブを送信してみる。
$ gcloud auth login --cred-file=empty-for-api-user-key.json $ gcloud dataproc jobs submit pyspark ./example.py --cluster=my-cluster --region=asia-northeast1 ERROR: (gcloud.dataproc.jobs.submit.pyspark) PERMISSION_DENIED: Permission 'dataproc.clusters.get' denied on resource '//dataproc.googleapis.com/projects/sample-pj/regions/asia-northeast1/clusters/my-cluster' (or it may not exist). This command is authenticated as empty-for-api-user@sample-pj.iam.gserviceaccount.com which is the active account specified by the [core/account] property. - '@type': type.googleapis.com/google.rpc.ErrorInfo domain: dataproc.googleapis.com metadata: permission: dataproc.clusters.get resource: projects/sample-pj/regions/asia-northeast1/clusters/my-cluster reason: IAM_PERMISSION_DENIED
予想通り権限不足でエラーになった。
次にviable-for-api-user
でジョブを送信すると上手くいくため、このサービスアカウントは必要な権限を持っていることを確認できた。
$ gcloud auth login --cred-file=viable-for-api-user-key.json $ gcloud dataproc jobs submit pyspark ./example.py --cluster=my-cluster --region=asia-northeast1
このように、コントロールプレーン、データプレーン、Dataproc API ユーザーという 3 つの要素全てに対して適切な権限が割り当てられていないと、Dataproc クラスタを利用することはできないのである。
Dataproc クラスタを放置し続けるとその分だけ費用が発生するので、削除しておく。
$ gcloud dataproc clusters delete my-cluster --region=asia-northeast1
Dataproc クラスタや Compute Engine インスタンスが削除されていることを確認する。
$ gcloud dataproc clusters list --region=asia-northeast1 Listed 0 items. $ gcloud compute instances list Listed 0 items.