Sparkアクセス制御拡張を使用したSparkアプリケーション投稿の強化
適用対象 : スパークエンジン
watsonx.dataに登録された外部ストレージバケットを使用するSparkアプリケーションをサブミットすると、Sparkのアクセス制御拡張機能によって追加の認証が可能になり、セキュリティが強化されます。 sparkコンフィギュレーションでエクステンションを有効にすると、watsonx.dataカタログにアクセスし、操作できるのは許可されたユーザーのみになります。
Iceberg、 Hive、Hudi、 Delta Lake カタログの Spark アクセスコントロールエクステンションを有効にできます。
ユーザー、ユーザーグループ、カタログ(Iceberg、 Hive、Hudi、 Delta Lake )、スキーマ、テーブル、およびカラムに対するアクセスを許可または拒否するには、Ranger またはアクセス管理システム(AMS)データポリシーのいずれかを使用できます。 データレベルの権限に加えて、ストレージ権限も考慮される。 カタログ(Iceberg、 Hive、Hudi、 Delta Lake )、バケット、スキーマ、テーブルでのAMSの使用に関する詳細は、 ロールと権限の管理を参照してください。 Ranger ポリシー( Hadoop SQL サービスで定義)を作成し、カタログ(Iceberg、 Hive、Hudi)、バケット、スキーマ、テーブルで有効にする方法の詳細については、 Ranger ポリシーの管理を参照してください。
前提条件
- Spark アプリケーションで使用するデータを保存するために、Cloud Object Storage を作成します。 Cloud Object Storageとバケットを作成するには、ストレージバケットの作成 を参照してください。 watsonx.dataテーブルを格納するdata-bucketと、Sparkアプリケーションコードを保持するapplication bucketです。
- watsonx.dataにCloud Object Storageバケットを登録します。 詳しくは、バケットカタログペアの追加 を参照してください。
- Sparkアプリケーションをストレージにアップロードします。データのアップロード を参照してください。
- watsonx.data内にスキーマまたはテーブルを作成するには、IAM管理者ロールまたはMetastoreAdminロールが必要です。
手順
Spark access control extensionはネイティブのSparkエンジンをサポートしています。
-
Sparkのアクセスコントロールエクステンションを有効にするには、Sparkの設定を
add authz.IBMSparkACExtension to spark.sql.extensionsで更新する必要があります。 -
次のPythonアプリケーションをiceberg.pyとして保存します。
アイスバーグがその例だ。 Hive、Hudi、 Delta Lake カタログもご利用ください。
from pyspark.sql import SparkSession
import os
def init_spark():
spark = SparkSession.builder \
.appName("lh-spark-app") \
.enableHiveSupport() \
.getOrCreate()
return spark
def create_database(spark):
# Create a database in the lakehouse catalog
spark.sql("create database if not exists lakehouse.demodb LOCATION 's3a://lakehouse-bucket/'")
def list_databases(spark):
# list the database under lakehouse catalog
spark.sql("show databases from lakehouse").show()
def basic_iceberg_table_operations(spark):
# demonstration: Create a basic Iceberg table, insert some data and then query table
spark.sql("create table if not exists lakehouse.demodb.testTable(id INTEGER, name VARCHAR(10), age INTEGER, salary DECIMAL(10, 2)) using iceberg").show()
spark.sql("insert into lakehouse.demodb.testTable values(1,'Alan',23,3400.00),(2,'Ben',30,5500.00),(3,'Chen',35,6500.00)")
spark.sql("select * from lakehouse.demodb.testTable").show()
def create_table_from_parquet_data(spark):
# load parquet data into dataframe
df = spark.read.option("header",True).parquet("file:///spark-vol/yellow_tripdata_2022-01.parquet")
# write the dataframe into an Iceberg table
df.writeTo("lakehouse.demodb.yellow_taxi_2022").create()
# describe the table created
spark.sql('describe table lakehouse.demodb.yellow_taxi_2022').show(25)
# query the table
spark.sql('select * from lakehouse.demodb.yellow_taxi_2022').count()
def ingest_from_csv_temp_table(spark):
# load csv data into a dataframe
csvDF = spark.read.option("header",True).csv("file:///spark-vol/zipcodes.csv")
csvDF.createOrReplaceTempView("tempCSVTable")
# load temporary table into an Iceberg table
spark.sql('create or replace table lakehouse.demodb.zipcodes using iceberg as select * from tempCSVTable')
# describe the table created
spark.sql('describe table lakehouse.demodb.zipcodes').show(25)
# query the table
spark.sql('select * from lakehouse.demodb.zipcodes').show()
def ingest_monthly_data(spark):
df_feb = spark.read.option("header",True).parquet("file:///spark-vol/yellow_tripdata_2022-02.parquet")
df_march = spark.read.option("header",True).parquet("file:///spark-vol/yellow_tripdata_2022-03.parquet")
df_april = spark.read.option("header",True).parquet("file:///spark-vol/yellow_tripdata_2022-04.parquet")
df_may = spark.read.option("header",True).parquet("file:///spark-vol/yellow_tripdata_2022-05.parquet")
df_june = spark.read.option("header",True).parquet("file:///spark-vol/yellow_tripdata_2022-06.parquet")
df_q1_q2 = df_feb.union(df_march).union(df_april).union(df_may).union(df_june)
df_q1_q2.write.insertInto("lakehouse.demodb.yellow_taxi_2022")
def perform_table_maintenance_operations(spark):
# Query the metadata files table to list underlying data files
spark.sql("SELECT file_path, file_size_in_bytes FROM lakehouse.demodb.yellow_taxi_2022.files").show()
# There are many smaller files compact them into files of 200MB each using the
# `rewrite_data_files` Iceberg Spark procedure
spark.sql(f"CALL lakehouse.system.rewrite_data_files(table => 'demodb.yellow_taxi_2022', options => map('target-file-size-bytes','209715200'))").show()
# Again, query the metadata files table to list underlying data files; 6 files are compacted
# to 3 files
spark.sql("SELECT file_path, file_size_in_bytes FROM lakehouse.demodb.yellow_taxi_2022.files").show()
# List all the snapshots
# Expire earlier snapshots. Only latest one with compacted data is required
# Again, List all the snapshots to see only 1 left
spark.sql("SELECT committed_at, snapshot_id, operation FROM lakehouse.demodb.yellow_taxi_2022.snapshots").show()
#retain only the latest one
latest_snapshot_committed_at = spark.sql("SELECT committed_at, snapshot_id, operation FROM lakehouse.demodb.yellow_taxi_2022.snapshots").tail(1)[0].committed_at
print (latest_snapshot_committed_at)
spark.sql(f"CALL lakehouse.system.expire_snapshots(table => 'demodb.yellow_taxi_2022',older_than => TIMESTAMP '{latest_snapshot_committed_at}',retain_last => 1)").show()
spark.sql("SELECT committed_at, snapshot_id, operation FROM lakehouse.demodb.yellow_taxi_2022.snapshots").show()
# Removing Orphan data files
spark.sql(f"CALL lakehouse.system.remove_orphan_files(table => 'demodb.yellow_taxi_2022')").show(truncate=False)
# Rewriting Manifest Files
spark.sql(f"CALL lakehouse.system.rewrite_manifests('demodb.yellow_taxi_2022')").show()
def evolve_schema(spark):
# demonstration: Schema evolution
# Add column fare_per_mile to the table
spark.sql('ALTER TABLE lakehouse.demodb.yellow_taxi_2022 ADD COLUMN(fare_per_mile double)')
# describe the table
spark.sql('describe table lakehouse.demodb.yellow_taxi_2022').show(25)
def clean_database(spark):
# clean-up the demo database
spark.sql('drop table if exists lakehouse.demodb.testTable purge')
spark.sql('drop table if exists lakehouse.demodb.zipcodes purge')
spark.sql('drop table if exists lakehouse.demodb.yellow_taxi_2022 purge')
spark.sql('drop database if exists lakehouse.demodb cascade')
def main():
try:
spark = init_spark()
create_database(spark)
list_databases(spark)
basic_iceberg_table_operations(spark)
# demonstration: Ingest parquet and csv data into a watsonx.data Iceberg table
create_table_from_parquet_data(spark)
ingest_from_csv_temp_table(spark)
# load data for the month of February to June into the table yellow_taxi_2022 created above
ingest_monthly_data(spark)
# demonstration: Table maintenance
perform_table_maintenance_operations(spark)
# demonstration: Schema evolution
evolve_schema(spark)
finally:
# clean-up the demo database
clean_database(spark)
spark.stop()
if __name__ == '__main__':
main()
- Sparkアプリケーションをサブミットするには、パラメータ値を指定して以下のcurlコマンドを実行する。 次の例は、iceberg.py アプリケーションをサブミットするコマンドです。
curl --request POST --url https://<region>/lakehouse/api/<api_version>/spark_engines/<spark_engine_id>/applications \
--header 'Authorization: Bearer <token>' --header 'Content-Type: application/json' --header 'Lhinstanceid: <instance_id>' --data '{
"application_details": {
"conf": {
"spark.hadoop.fs.s3a.bucket.<wxd-data-bucket-name>.endpoint": "<wxd-data-bucket-endpoint>",
"spark.hadoop.fs.cos.<COS_SERVICE_NAME>.endpoint": "<COS_ENDPOINT>",
"spark.hadoop.fs.cos.<COS_SERVICE_NAME>.secret.key": "<COS_SECRET_KEY>",
"spark.hadoop.fs.cos.<COS_SERVICE_NAME>.access.key": "<COS_ACCESS_KEY>"
"spark.hadoop.wxd.apiKey":"Basic xxx",
"spark.sql.extensions":"<required-storage-support-extension>,authz.IBMSparkACExtension"
},
"application": "cos://<BUCKET_NAME>.<COS_SERVICE_NAME>/<python_file_name>",
}
}
パラメーター値:
<token>サービスインスタンスのアクセストークンを取得する。 トークンの生成方法の詳細については 、「トークンの生成」 を参照してください。<instance_id>watsonx.data クラスタ・インスタンス URL のインスタンス ID。 例、 crn:v1:staging:public:lakehouse:us-south:a/7bb9e380dc0c4bc284592b97d5095d3c:5b602d6a-847a-469d-bece-0a29124588c0::。<wxd-data-bucket-endpoint>: 上記のデータバケットにアクセスするためのエンドポイントのホスト名。 例: s3.us-south.cloud-object-storage.appdomain.cloud は、us-southリージョンのクラウドオブジェクトストレージバケットです。<COS_SERVICE_NAME>:クラウドオブジェクトストレージサービスの名前を入力します。<COS_ENDPOINT>パブリックエンドポイントを提供する。 詳細は 「エンドポイント」 を参照してください。<access_key>: access_key_idを指定します。 詳細は 「認証情報」 を参照してください。<secret_key>: secret_access_keyを指定します。 詳細は 「認証情報」 を参照してください。<BUCKET_NAME>: アプリケーションファイルが存在するストレージバケット。<python_file_name>Spark アプリケーションのファイル名。<api_version>: v2 APIを使用する場合は、<api_version>パラメータをv2に設定する。 v3 APIを使用する場合は、v3に設定する。
制限事項
- ユーザはスキーマとテーブルを作成するためのフルアクセス権を持っていなければならない。
- データ ポリシーを作成するには、カタログを Presto エンジンに関連付ける必要があります。
- 存在しないスキーマを表示しようとすると、nullpointer問題がスローされます。
- Iceberg、 Hive、Hudi、 Delta Lake カタログの Spark アクセスコントロールエクステンションを有効にできます。