AWS PrivateLink を使った Snowflake のアウトバウンド接続を試してみた #SnowflakeDB

AWS PrivateLink を使った Snowflake のアウトバウンド接続を試してみた #SnowflakeDB

Clock Icon2024.11.16

はじめに

2024年10月のアップデートで、Snowpark からの外部ネットワーク アクセス時の AWS PrivateLink と Azure Private Link の使用が一般提供となりました。
AWS PrivateLink について、こちらを試してみましたので本記事で内容をまとめてみます。

https://docs.snowflake.com/en/release-notes/2024/8_41

アップデートの概要

もともと Snowflake は、各クラウドサービスとのプライベート接続機能を提供していました。この機能は、インバウンド アクセスに対応しており、顧客管理の VPC から Snowflake にデータを送信したり、Snowflake に接続するリクエストを PrivateLink 経由で安全に行うためのものでした。

今回のアップデートにより、Snowflake アカウントからの送信接続(アウトバウンド接続)にも PrivateLink のサポートが拡張されました。これにより、 AWS および Microsoft Azure上のリソースへの接続時のトラフィックがプライベート エンドポイントを経由し、AI/ML サービスなど様々な機能への通信がプライベート ネットワーク内で保護されるようになります。

Snowflake は AWS, Microsoft Azure および Google Cloud のいずれかでホストできますが、今回一般提供となったのは以下になります。

  • Snowpark 外部ネットワークアクセスを使用した AWS PrivateLink 経由での AWS 上のリソースへのアクセス
  • Snowpark 外部ネットワークアクセスを使用した Azure Private Link 経由での Azure 上のリソースへのアクセス

アップデートの概要については、以下の公式ブログがとても参考になりますので、ぜひこちらもご参照ください。

https://www.snowflake.com/engineering-blog/secure-communications-outbound-private-link/

制約

  • 本機能の使用には、Business Critical 以上のエディションが必要です
  • Snowflake アカウントごとに 5 つ以上のプライベート エンドポイントを持つことはできません
    • AWS の場合、この制限はサービスごとに適用される
    • S3 バケットへのエンドポイントが 1 つある場合、別の S3 バケットへの異なるエンドポイントを持つことはできない
  • 同じ AWS サービスまたは Azure サブリソースに複数のエンドポイントを持つことはできません

https://docs.snowflake.com/user-guide/private-connectivity-outbound#scaling-considerations

コスト

本機能を使用する際は、Snowflake アカウント側にプライベート接続用のエンドポイントを作成します。このエンドポイントに対して以下の費用がかかります。 ※AWS Tokyo の場合

  • エンドポイント一つあたりの料金:$14.00/1,000時間
  • エンドポイントで処理されたデータの料金:$10.24/TB

詳細は以下をご参照ください。

https://www.snowflake.com/legal-files/CreditConsumptionTable.pdf
https://docs.snowflake.com/user-guide/private-connectivity-outbound#outbound-private-connectivity-costs

検証環境

本記事では、以下の環境で検証を行いました。
AWS 側のリソースには RDS PostgreSQL を使用し、PrivateLink 経由で UDF から PostgreSQL のデータを参照することを目指します。

  • Snowflake
    • Business Ciritical
      • PrivateLink の設定には Business Ciritical 以上のエディションが必要
    • クラウドリージョン:AWS_AP_NORTHEAST_1
  • AWS
    • Snowflake アカウントと同一のリージョンに VPC を作成

下図のような構成です。

image

事前準備

前提として下図の構成がある状態からはじめます。

image 1

サンプルデータの作成

踏み台サーバから PostgreSQL にログインし以下のコマンドでサンプルデータを用意しました。

-- データベースを作成
CREATE DATABASE sample_db;

\c sample_db;

-- テーブル1: customers 
CREATE TABLE customers (
    customer_id SERIAL PRIMARY KEY,  
    name VARCHAR(100),               
    email VARCHAR(100),              
    age INT                          
);

-- customers テーブルにサンプルデータを挿入
INSERT INTO customers (name, email, age) VALUES
('Alice Smith', '[email protected]', 30),
('Bob Johnson', '[email protected]', 25),
('Charlie Brown', '[email protected]', 35);

一連の手順は以下に記載があるので、こちらを参考に進めます。

https://docs.snowflake.com/developer-guide/external-network-access/creating-using-private-aws#set-up-private-connectivity-to-an-external-amazon-bedrock-service

RDS のプライベート IP アドレスを取得し、ターゲットタイプを IP アドレスとするターゲットグループを作成します。

$ host database-1.xxxxx.ap-northeast-1.rds.amazonaws.com
database-1.xxxxx.ap-northeast-1.rds.amazonaws.com has address 10.xx.xx.xx

image 2

NLB 用にセキュリティグループを作成

次の手順で作成する NLB に関連づけるためのセキュリティグループを作成します。セキュリティグループの内容はデフォルトのままとします(インバウンドルールは空、アウトバウンドルールはすべてのトラフィックを 0.0.0.0/0 に送信)。
セキュリティグループを作成後、RDS のセキュリティグループのインバウンドルールに以下の内容でインバウンドルールを追加します。

  • プロトコル:TCP
  • ポート範囲:5432
  • ソース:NLB 用のセキュリティグループ

NLB の作成

ロードバランサーのメニューの [ロードバランサーの作成] から Network Load Balancer を作成します。基本設定は、内部向けのロードバランサーとして作成し、ネットワークマッピングは、作成済みの VPC とプライベートサブネットを指定します。

image 3

セキュリティグループには、NLB 用に作成したセキュリティグループを割り当てます。

リスナーとルーティングは、以下の通り設定します。

  • プロトコル:TCP
  • ポート:5432
  • デフォルトアクションに作成済みのターゲットグループを指定

image 4

その他はデフォルトとし、 NLB を作成します。完了後、対象の NLB の [セキュリティ > 編集] からセキュリティ設定を開き、「Privatelink トラフィックにインバウンドルールを適用する」のチェックを外し、変更を保存します。

image 5

エンドポイントサービスを作成

続けて、AWS 側でエンドポイントサービスを作成します。エンドポイントサービスのメニューから [エンドポイントサービスを作成] をクリックし、以下の設定とします。

  • ロードバランサーのタイプ:ネットワーク
  • ロードバランサー:上記の手順で作成した NLB を選択

image 6

追加設定では「エンドポイントの承諾が必要」にチェックを入れエンドポイントサービスを作成します。

image 7

作成後に詳細メニューから確認できるサービス名を後ほどの手順で使用します。

image 8

プリンシパルを許可する

Snowflake 側で ACCOUNTADMIN として SYSTEM$GET_PRIVATELINK_CONFIGを実行します。

SELECT SYSTEM$GET_PRIVATELINK_CONFIG();

出力は以下のようになるので、このうちprivatelink-account-principalの値を控えておきます。

{
"privatelink-account-principal":"arn:aws:iam::xxx:root",
"regionless-snowsight-privatelink-url":"xxx",
"privatelink-account-name":"xxx",
"privatelink-vpce-id":"xxx",
"snowsight-privatelink-url":"xxx",
"regionless-privatelink-ocsp-url":"xxx",
"privatelink-account-url":"xxx",
"app-service-privatelink-url":"xxx",
"regionless-privatelink-account-url":"xxx",
"privatelink_ocsp-url":"xxx"
}

AWS 側に戻り、作成したエンドポイントサービスのメニュー で [アクション > プリンシパルを許可] を選択し、「追加するプリンシパル」に Snowflake 側で取得したprivatelink-account-principalの値を入力し [プリンシパルを許可] をクリックします。

image 9

プライベートエンドポイントをプロビジョン

Snowflake 側で ACCOUNTADMIN としてSYSTEM$PROVISION_PRIVATELINK_ENDPOINTを以下のように実行します。

USE ROLE ACCOUNTADMIN;
SELECT SYSTEM$PROVISION_PRIVATELINK_ENDPOINT(
  'com.amazonaws.vpce.ap-northeast-1.vpce-svc-xxxxx', --エンドポイントサービスのDNS名
  'database-1.xxxxx.ap-northeast-1.rds.amazonaws.com' --rdsのエンドポイント
);

はじめの引数には、AWS側で確認できるエンドポイントサービスのサービス名を指定します。2つ目の引数には、ホスト名として VPC 内のリソースにアクセスするための完全修飾名を指定します。ここでは RDS のエンドポイントを指定しました。

実行結果は下図のようになり、Snowflake 側のエンドポイントの ID が表示されます。

image 10

Private endpoint with ID "vpce-xxxxx" to resource "com.amazonaws.vpce.ap-northeast-1.vpce-svc-xxxxx" has been provisioned successfully. If Azure resource or an AWS endpoint service, please note down the endpoint ID and approve the connection from it on the CSP portal.

エンドポイント接続リクエストの承諾

Snowflake 側でエンドポイントをプロビジョニング後、AWS 側のエンドポイントサービスの画面で「エンドポイント接続」タブを開くと、Snowflake 側で作成したエンドポイントの ID が表示されます。こちらを選択し [アクション > エンドポイント接続リクエストの承諾] をクリックします。

image 11

続けて、下図の表示になるのでフィールドに承諾と入力し [承諾] をクリックします。

image 12

問題なければ下図の表示となります。

image 13

以上で AWS 側の作業は完了です。

Snowflake 側:各種オブジェクトの作成

Snowflake 側で外部ネットワークアクセスに必要な各種オブジェクトを定義します。

事前準備

はじめに各種スキーマレベルのセキュリティオブジェクトの作成先となるデータベース・スキーマと、UDF の作成先となるデータベースを作成しておきました。
UDF 作成先は別データベースの PUBLIC スキーマとし、あわせて UDF から参照するファイルを配置するステージも作成しておきました。

--オブジェクト作成用
USE ROLE SYSADMIN;
----セキュリティオブジェクト格納用
CREATE DATABASE security;
CREATE SCHEMA security.network_rules;
CREATE SCHEMA security.secrets;
----UDF作成用
CREATE DATABASE test;
----ステージを作成
CREATE STAGE my_int_stage;

ネットワークルールの作成

はじめにMODE = EGRESSTYPE = PRIVATE_HOST_PORTとするネットワークルールを作成します。これにより外部サービスへのプライベートエンドポイント経由でのアクセスの許可・制限を行います。
VALUE_LISTには、RDS のエンドポイントとポートを指定します。注意点として、ポートを指定しない場合は、デフォルトで 443 となり、サービスや設定よっては接続できなくなってしまいます。

USE DATABASE security;
USE SCHEMA network_rules;

CREATE OR REPLACE NETWORK RULE aws_rds_postgres_network_rule
    MODE = EGRESS
    TYPE = PRIVATE_HOST_PORT
    VALUE_LIST = ('database-1.xxxxx.ap-northeast-1.rds.amazonaws.com:5432');

https://docs.snowflake.com/en/sql-reference/sql/create-network-rule

シークレットの作成

PostgreSQL にはユーザー名・パスワードで認証する設定とし、そのための認証資格情報を保持するシークレットオブジェクトを作成します。

USE SCHEMA SECURITY.SECRETS;
CREATE OR REPLACE SECRET secret_password
  TYPE = PASSWORD
  USERNAME = '<ユーザー名>'
  PASSWORD = '<パスワード>';

https://docs.snowflake.com/en/sql-reference/sql/create-secret#aws-iam-required-parameters

外部アクセス統合の作成

さいごに外部アクセス統合を作成し、これまでに作成した各種オブジェクトを紐づけます。注意点として、現時点ではトライアルアカウントでの使用はサポートされていません。デフォルトでは ACCOUNTADMIN のみ実行可能です。

CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION aws_rds_postgres_external_access_integration
  ALLOWED_NETWORK_RULES = (security.network_rules.aws_rds_postgres_network_rule)
  ALLOWED_AUTHENTICATION_SECRETS = (security.secrets.secret_password)
  ENABLED = TRUE;

https://docs.snowflake.com/en/sql-reference/sql/create-external-access-integration

Python UDF の作成

RDS にクエリを送信し結果を取得する Python UDF を定義します。ここでは以下の内容で UDF を作成しました。

CREATE OR REPLACE function query_postgres()
RETURNS VARIANT
LANGUAGE PYTHON
RUNTIME_VERSION = 3.11
IMPORTS=('@my_int_stage/ap-northeast-1-bundle.pem')
PACKAGES = ('psycopg2')
HANDLER = 'main'
EXTERNAL_ACCESS_INTEGRATIONS = (aws_rds_postgres_external_access_integration)
SECRETS = ('cred' = security.secrets.secret_password)
AS $$
import psycopg2
import sys
import os
import json
from _snowflake import 

# AWS RDS情報を設定
ENDPOINT = "database-1.xxx.ap-northeast-1.rds.amazonaws.com"
DBNAME = "sample_db"
PORT = 5432

# Snowflake UDFのインポートディレクトリから証明書ファイルのパスを取得
IMPORT_DIRECTORY_NAME = "snowflake_import_directory"
import_dir = sys._xoptions[IMPORT_DIRECTORY_NAME]
ssl_cert_path = os.path.join(import_dir, 'ap-northeast-1-bundle.pem')  # 証明書ファイルのパス

def main():
    # シークレットからユーザー名とパスワードを取得
    username_password = get_username_password('cred')
    user = username_password.username
    password = username_password.password

      # PostgreSQLに接続
    conn = psycopg2.connect(
        host=ENDPOINT,
        port=PORT,
        database=DBNAME,
        user=user,
        password=password,
        sslmode='verify-ca',
        sslrootcert=ssl_cert_path #インポートディレクトリから取得した証明書ファイルを指定
    )

    # クエリを実行
    cur = conn.cursor()
    cur.execute("SELECT * FROM customers")
    result = cur.fetchall()

    # 接続を閉じる
    conn.close()
    return result
$$;

ポイントは以下です。

  • Snowflake 内に保存されているシークレットデータ(認証情報やAPIトークンなど)の取得には_snowflakeモジュールを使用
    • get_username_password(username_password_secret_name)で指定されたシークレットからユーザー名とパスワードを取得
    • 属性として、username,passwordが含まれる

https://docs.snowflake.com/en/developer-guide/external-network-access/secret-api-reference#python-api-for-secret-access

  • PostgreSQL への接続にはpsycopg2を使用しステージに配置したRDS の証明書を指定
    • パスの指定には IMPORTS 句にファイル名とステージ名を指定することで、ファイルを読み込み
    • sys._xoptions メソッドと snowflake_import_directory システムオプションを使用して、 UDF のホームディレクトリの位置を取得

https://docs.snowflake.com/ja/developer-guide/udf/python/udf-python-examples#reading-a-statically-specified-file-using-imports

UDF を実行します。

SELECT query_postgres();

この場合、以下の通り出力され Snowflake から RDS PostgreSQL のデータを取得できました。

image 14

見やすく整形してみます。

WITH postgres_data AS (
    SELECT query_postgres() AS raw_data
)
SELECT
    value[0]::INTEGER AS id,
    value[1]::STRING AS name,
    value[2]::STRING AS email,
    value[3]::INTEGER AS age
FROM postgres_data, LATERAL FLATTEN(input => raw_data) AS t;

image 15

さいごに

Snowpark からの外部ネットワーク アクセス時の AWS PrivateLink の使用を試してみました。
設定手順としては、これまでの Snowflake 向けの PrivateLink 設定時に Snowflake 側で行ってくれていた作業をユーザー側で行うイメージです。エンドポイントサービスそのものに対してもコストがかかる点やプロビジョニングできる数に制限がある点は注意が必要です。
こちらの内容が何かの参考になれば幸いです。

参考

https://dev.classmethod.jp/articles/cross-account-rds-access-vial-privatelink-nlb/
https://dev.classmethod.jp/articles/aws-vpcendpoint-privatelink-beginner/

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.