DatabricksでCustemer Managed VPCにClusterをプロビジョニングする

2021.09.14

ナカヤマです。

DatabricksというSaaSサービスでユーザーの所有するAWSアカウントにあるVPCにClusterをデプロイしてみました。 その流れを記録します。

Databricksとは?

Databricksは、データサイエンティスト・データエンジニア・ビジネスアナリストのためのLakehouse Platformです。

Databricks - The Data and AI Company

このサービスがどのような課題を解決するかについては、AWS Summit Online (2021) のプレゼンテーションを見て頂くのが早いと思いますので、ここでは割愛します。

PAR-25 データ分析におけるデータサイロ、機械学習モデル管理、BI の制限 − これらの課題を解決します!

資料

動画

Databricksは、並列分散処理を行うワーカーノードやドライバーノード、Delta lakeを活用したデータレイクのプロビジョニングや管理等を行います。 その他、可視化やデータへのアクセス手段を提供するためのWorkspaceや機械学習モデルを管理するための仕組みも提供しています。

Databricks Data Science & Engineering concepts

このワーカーノードやドライバーノードによって構成されるクラスターはユーザーの所有するAWSアカウント上に、なおかつ既存のVPCにプロビジョニングすることも可能です。 既存のデータソースを活用したり、Amazon Kinesisと連携したストリーミングデータの取り扱いが可能なようです。

この記事では、Databricksアカウントのサインアップからユーザーが独自に作成したVPCにクラスターを作成して簡単なクエリを実行するまでの手順を確認したいと思います。

やってみた

大まかな手順は以下の通りです。

  • アカウントのサインアップ
  • Network configurationの作成
  • Credential configurationの作成
  • Storage configurationの作成
  • Workspaceの作成
  • Clusterの作成
  • Notebookの作成
  • 動作確認

アカウントのサインアップ

まずはDatabricksアカウントのサインアップを行います。 Databricksは、無料で2週間のトライアルを行うことが可能です。

DatabricksのWebサイトにアクセスし、TRY DATABRICKSボタンをクリックします。

必要事項を入力します。

有償版の機能を利用する場合は、どのクラウドプラットフォームを利用するか選択します。 今回はAWSを選択します。

選択すると設定したメールアドレスにメールが届くため、リンクをクリックしてパスワードを設定します。

パスワード設定後、プランを選択します。トライアル中はいずれのプランでも無料で使えるようです。 今回は最上位のEnterpriseを選択します。

これでサインアップは完了です。

Network configurationの作成

次に、ノードをプロビジョニングするVPCを指定します。

必要事項を入力するダイアログには、VPCが満たすべき要件が記載されています。 現時点ではプロビジョニング先のVPCがないので、要件を確認しつつVPCを作成したいと思います。

Customer-managed VPC

このドキュメントを確認すると、DataBricksにVPCの作成を含めて委任するパターン(Databricks-Managed VPC)とユーザーの管理下にあるVPCを利用する2つのパターンがあるようです。 今回は後者のパターンで作業を進めます。

VPCが満たすべき要件は以下のリンク先を確認してください。

VPC requirements

この要件を踏まえてVPCおよび関連リソースを作成します。 手作業はめんどくさいので、今回はCloudFormationで作成します。 以下のテンプレートを作成の上でリソースをプロビジョニングしました。

AWSTemplateFormatVersion: "2010-09-09"
Description: A sample template for Databricks (Customer-managed VPC / for ap-northeast-1)

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: Databricks-VPC

  IGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: Databricks-IGW

  AttachIGW:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref IGW
  
  PrivateSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      VpcId: !Ref VPC
      CidrBlock: 10.0.0.0/24
      Tags:
        - Key: Name
          Value: Databricks-Private-Subnet-a

  PrivateSubnetC:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1c
      VpcId: !Ref VPC
      CidrBlock: 10.0.1.0/24
      Tags:
        - Key: Name
          Value: Databricks-Private-Subnet-c

  PublicSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      VpcId: !Ref VPC
      CidrBlock: 10.0.2.0/24
      Tags:
        - Key: Name
          Value: Databricks-Public-Subnet-a

  PublicSubnetC:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1c
      VpcId: !Ref VPC
      CidrBlock: 10.0.3.0/24
      Tags:
        - Key: Name
          Value: Databricks-Public-Subnet-c

  EIPA:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NATGWA:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt EIPA.AllocationId
      ConnectivityType: public
      SubnetId: !Ref PublicSubnetA
      Tags:
        - Key: Name
          Value: Databricks-NATGW-a

  EIPC:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NATGWC:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt EIPC.AllocationId
      ConnectivityType: public
      SubnetId: !Ref PublicSubnetC
      Tags:
        - Key: Name
          Value: Databricks-NATGW-c


  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: Databricks-Public-RouteTable

  PrivateRouteTableA:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: Databricks-Private-RouteTable-a
  
  PrivateRouteTableC:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: Databricks-Private-RouteTable-c


  PublicDefaultRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref IGW
  
  PrivateDefaultRouteA:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTableA
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGWA
  
  PrivateDefaultRouteC:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTableC
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGWC


  PublicRouteTableAssocA:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetA
      RouteTableId: !Ref PublicRouteTable
  
  PublicRouteTableAssocC:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetC
      RouteTableId: !Ref PublicRouteTable
  
  PrivateRouteTableAssocA:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnetA
      RouteTableId: !Ref PrivateRouteTableA
  
  PrivateRouteTableAssocC:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnetC
      RouteTableId: !Ref PrivateRouteTableC


  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: DataBricks-SecurityGroup
      GroupDescription: "Databricks SecurityGroup"
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: Databricks-SecurityGroup

Outputs:
  VPCID:
    Value: !Ref VPC
    Export: 
      Name: DataBricksVPCID

  PrivateSubnerIDA:
    Value: !Ref PrivateSubnetA
    Export: 
      Name: DataBricksPrivateSubnetIDA

  PrivateSubnerIDC:
    Value: !Ref PrivateSubnetC
    Export: 
      Name: DataBricksPrivateSubnetIDC

  SecurityGroupID:
    Value: !Ref SecurityGroup
    Export: 
      Name: DatabricksSecurityGroupID

(要件に応じてVPC Endpointを構成するケースもありますが、今回は割愛します)

スタックの作成が完了したら、リソースIDを設定します。

Network Configurationは以上です。

Credential configurationの作成

次にCredential configurationを作成します。

DatabricksはユーザーのAWSアカウントにクラスターをプロビジョニングするなどコントロールプレーンとして機能します。 ここではそのために必要な権限を設定します。 具体的にはDatabricksの所有する特定のAWSアカウントに対して権限を委任します。

まず、以下の画面に遷移して設定を開始します。

すると、IAM Roleに設定するための外部IDが表示されます。

この外部IDを利用してIAM Roleを作成します。 作成手順は以下のドキュメントを参照してください(作成手順自体は2つめのリンク先に記載されています)。

Manage delegated credential configurations using the account console (E2)

Create a cross-account IAM role

信頼するAWSアカウントはドキュメントに記載されたものを指定します。 また、付与する権限は [Your VPC, custom] を利用してください。 ここに記載されているポリシーは、一部の変数をユーザーが置き換える必要があります。

  • ACCOUNTID
    • クラスターやデータレイクをプロビジョニングするAWSアカウント
  • VPCID
    • クラスターをプロビジョニングするVPC ID(CloudFormationでプロビジョニングしたVPC)
  • REGION
    • クラスターやデータレイクをプロビジョニングするAWSリージョン
  • SECURITYGROUPID
    • VPC上のノードに割り当てるSecurity Group

置き換えた結果は以下の通りです(ACCOUNTIDのみ伏せています)。

{
	"Version":"2012-10-17",
	"Statement":[
		{
			"Sid":"NonResourceBasedPermissions",
			"Effect":"Allow",
			"Action":[
				"ec2:CancelSpotInstanceRequests",
				"ec2:DescribeAvailabilityZones",
				"ec2:DescribeIamInstanceProfileAssociations",
				"ec2:DescribeInstanceStatus",
				"ec2:DescribeInstances",
				"ec2:DescribeInternetGateways",
				"ec2:DescribeNatGateways",
				"ec2:DescribeNetworkAcls",
				"ec2:DescribePrefixLists",
				"ec2:DescribeReservedInstancesOfferings",
				"ec2:DescribeRouteTables",
				"ec2:DescribeSecurityGroups",
				"ec2:DescribeSpotInstanceRequests",
				"ec2:DescribeSpotPriceHistory",
				"ec2:DescribeSubnets",
				"ec2:DescribeVolumes",
				"ec2:DescribeVpcAttribute",
				"ec2:DescribeVpcs",
				"ec2:CreateTags",
				"ec2:DeleteTags",
				"ec2:RequestSpotInstances"
			],
			"Resource":[
				"*"
			]
		},
		{
			"Sid":"InstancePoolsSupport",
			"Effect":"Allow",
			"Action":[
				"ec2:AssociateIamInstanceProfile",
				"ec2:DisassociateIamInstanceProfile",
				"ec2:ReplaceIamInstanceProfileAssociation"
			],
			"Resource":"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:instance/*",
			"Condition":{
				"StringEquals":{
					"ec2:ResourceTag/Vendor":"Databricks"
				}
			}
		},
		{
			"Sid":"AllowEc2RunInstancePerTag",
			"Effect":"Allow",
			"Action":"ec2:RunInstances",
			"Resource":[
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:volume/*",
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:instance/*"
			],
			"Condition":{
				"StringEquals":{
					"aws:RequestTag/Vendor":"Databricks"
				}
			}
		},
		{
			"Sid":"AllowEc2RunInstanceImagePerTag",
			"Effect":"Allow",
			"Action":"ec2:RunInstances",
			"Resource":[
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:image/*"
			],
			"Condition":{
				"StringEquals":{
					"aws:ResourceTag/Vendor":"Databricks"
				}
			}
		},
		{
			"Sid":"AllowEc2RunInstancePerVPCid",
			"Effect":"Allow",
			"Action":"ec2:RunInstances",
			"Resource":[
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:network-interface/*",
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:subnet/*",
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:security-group/*"
			],
			"Condition":{
				"StringEquals":{
					"ec2:vpc":"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:vpc/vpc-008706f8895dd6fa6"
				}
			}
		},
		{
			"Sid":"AllowEc2RunInstanceOtherResources",
			"Effect":"Allow",
			"Action":"ec2:RunInstances",
			"NotResource":[
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:image/*",
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:network-interface/*",
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:subnet/*",
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:security-group/*",
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:volume/*",
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:instance/*"
			]
		},
		{
			"Sid":"EC2TerminateInstancesTag",
			"Effect":"Allow",
			"Action":[
				"ec2:TerminateInstances"
			],
			"Resource":[
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:instance/*"
			],
			"Condition":{
				"StringEquals":{
					"ec2:ResourceTag/Vendor":"Databricks"
				}
			}
		},
		{
			"Sid":"EC2AttachDetachVolumeTag",
			"Effect":"Allow",
			"Action":[
				"ec2:AttachVolume",
				"ec2:DetachVolume"
			],
			"Resource":[
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:instance/*",
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:volume/*"
			],
			"Condition":{
				"StringEquals":{
					"ec2:ResourceTag/Vendor":"Databricks"
				}
			}
		},
		{
			"Sid":"EC2CreateVolumeByTag",
			"Effect":"Allow",
			"Action":[
				"ec2:CreateVolume"
			],
			"Resource":[
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:volume/*"
			],
			"Condition":{
				"StringEquals":{
					"aws:RequestTag/Vendor":"Databricks"
				}
			}
		},
		{
			"Sid":"EC2DeleteVolumeByTag",
			"Effect":"Allow",
			"Action":[
				"ec2:DeleteVolume"
			],
			"Resource":[
				"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:volume/*"
			],
			"Condition":{
				"StringEquals":{
					"ec2:ResourceTag/Vendor":"Databricks"
				}
			}
		},
		{
			"Effect":"Allow",
			"Action":[
				"iam:CreateServiceLinkedRole",
				"iam:PutRolePolicy"
			],
			"Resource":"arn:aws:iam::*:role/aws-service-role/spot.amazonaws.com/AWSServiceRoleForEC2Spot",
			"Condition":{
				"StringLike":{
					"iam:AWSServiceName":"spot.amazonaws.com"
				}
			}
		},
		{
			"Sid":"VpcNonresourceSpecificActions",
			"Effect":"Allow",
			"Action":[
				"ec2:AuthorizeSecurityGroupEgress",
				"ec2:AuthorizeSecurityGroupIngress",
				"ec2:RevokeSecurityGroupEgress",
				"ec2:RevokeSecurityGroupIngress"
			],
			"Resource":"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:security-group/sg-04baecd6fd67b711f",
			"Condition":{
				"StringEquals":{
					"ec2:vpc":"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:vpc/vpc-008706f8895dd6fa6"
				}
			}
		}
	]
}

これらを利用してIAM Roleを作成します。

IAM Roleを作成したら、ARNを指定してCredential Configurationの設定は完了となります。

Storage configurationの作成

次にStorage Configurationを作成します。

DatabricksではDelta Lakeという独自のストレージレイヤーによって「トランザクションによる高信頼性」「インデクシングによる高速なデータ処理」「きめ細かいアクセスコントロールによるデータガバナンス」を実現しています。 実際のデータの保存にはS3を利用できるようになっており、ここではそのための設定を行います。

ここでは、予め新しく作成しておいたS3バケットの指定と、DatabricksがS3バケットを利用するためのバケットポリシーを設定します。

Workspaceの作成

次にWorkspacesを作成します。

WorkspaceはDatabricksのアセットにアクセスするための環境で、これ以降に作成するClusterやNotebook等のリソースはWorkspace内で作成します。

今回は、既に作成済みのVPCを利用します。

Workspace名の他、ここまでに作成した3種類のConfigurationを指定してWorkspaceを作成します。

Provisioningが行われるのです少し待ちます。

完了したら、Workspaceを開きます。

Workspaceに入る際には改めて認証が必要となるようです。

Workspaceのコンソールはこのような感じです。

Clusterの作成

それでは、Workspace上でClusterを作成します。

Clusterの作成にあたり、必要となるパラメーターを設定します。 今回はデフォルト設定とします。

細かいパラメーターとして、Spotインスタンスをどの程度利用するかも指定できます。 今回は詳細は割愛します。

Clusterの作成を開始すると、AWSアカウントにEC2インスタンスが作成されました。

しばらく待つと、Clusterを利用できるようになります。

Notebookの作成

最後に、Notebookを作成します。

デフォルトの言語やどのCluster用のNotebookなのか、等を指定します。

作成できると、以下のような画面が表示されます。

構築作業は以上です。

動作確認

それでは簡単ではありますが動作を確認します。

以下のドキュメントの参考に、サンプルデータのロード・テーブルの作成を行います。

Step 4: Create a table

DROP TABLE IF EXISTS diamonds;

CREATE TABLE diamonds USING CSV OPTIONS (path "/databricks-datasets/Rdatasets/data-001/csv/ggplot2/diamonds.csv", header "true")

成功したらクエリを行います。

SELECT * FROM diamonds LIMIT 10

なお、クエリを行ったデータに対して各種Chartを表示させることもできます。

Step 5: Query the table

SELECT color, avg(price) AS price FROM diamonds GROUP BY color ORDER BY COLOR

Step 6: Display the data

簡単ですが、確認は以上です。

まとめ

ユーザーの所有するAWSアカウントかつユーザー管理下のVPCにClusterをプロビジョニングしてみました。 インフラエンジニアの視点で所感をまとめます。

まず、ユーザーインタフェースがシンプルで非常にわかりやすいという印象を受けました。 ドキュメントにはチュートリアル(Getting started)があり、一通り触ってみればどのような概念・リソースが登場してそのような関係性なのかはすぐに分かるんじゃないかと思います。

また、ドキュメントも丁寧に書かれており、知りたいことはきちんと書かれているように思います。 IAM Roleに付与する権限はCustomer-managed VPCの場合にはリソースレベルで制御できるポリシーがドキュメントに記載されていましたので安心感がありましたし、VPCの要件も明確でVPCの構築について迷うことはほぼありませんでした。

Clusterの作成に関してもSpotインスタンスの利用が簡単に行えるようになっており、コストのコントロールもしやすそうでした。

今回はClusterをプロビジョニングしてみるだけでしたが、機会があれば機械学習やデータパイプライン関連の機能も試してみたいと思います。