AWS PrivateLink を使った Snowflake のアウトバウンド接続を試してみた #SnowflakeDB
はじめに
2024年10月のアップデートで、Snowpark からの外部ネットワーク アクセス時の AWS PrivateLink と Azure Private Link の使用が一般提供となりました。
AWS PrivateLink について、こちらを試してみましたので本記事で内容をまとめてみます。
アップデートの概要
もともと 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 上のリソースへのアクセス
アップデートの概要については、以下の公式ブログがとても参考になりますので、ぜひこちらもご参照ください。
制約
- 本機能の使用には、Business Critical 以上のエディションが必要です
- Snowflake アカウントごとに 5 つ以上のプライベート エンドポイントを持つことはできません
- AWS の場合、この制限はサービスごとに適用される
- S3 バケットへのエンドポイントが 1 つある場合、別の S3 バケットへの異なるエンドポイントを持つことはできない
- 同じ AWS サービスまたは Azure サブリソースに複数のエンドポイントを持つことはできません
コスト
本機能を使用する際は、Snowflake アカウント側にプライベート接続用のエンドポイントを作成します。このエンドポイントに対して以下の費用がかかります。 ※AWS Tokyo の場合
- エンドポイント一つあたりの料金:$14.00/1,000時間
- エンドポイントで処理されたデータの料金:$10.24/TB
詳細は以下をご参照ください。
検証環境
本記事では、以下の環境で検証を行いました。
AWS 側のリソースには RDS PostgreSQL を使用し、PrivateLink 経由で UDF から PostgreSQL のデータを参照することを目指します。
- Snowflake
- Business Ciritical
- PrivateLink の設定には Business Ciritical 以上のエディションが必要
- クラウドリージョン:AWS_AP_NORTHEAST_1
- Business Ciritical
- AWS
- Snowflake アカウントと同一のリージョンに VPC を作成
下図のような構成です。
事前準備
前提として下図の構成がある状態からはじめます。
サンプルデータの作成
踏み台サーバから 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);
AWS PrivateLink 設定
一連の手順は以下に記載があるので、こちらを参考に進めます。
PrivateLink 用のターゲットグループを設定
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
NLB 用にセキュリティグループを作成
次の手順で作成する NLB に関連づけるためのセキュリティグループを作成します。セキュリティグループの内容はデフォルトのままとします(インバウンドルールは空、アウトバウンドルールはすべてのトラフィックを 0.0.0.0/0 に送信)。
セキュリティグループを作成後、RDS のセキュリティグループのインバウンドルールに以下の内容でインバウンドルールを追加します。
- プロトコル:TCP
- ポート範囲:5432
- ソース:NLB 用のセキュリティグループ
NLB の作成
ロードバランサーのメニューの [ロードバランサーの作成] から Network Load Balancer を作成します。基本設定は、内部向けのロードバランサーとして作成し、ネットワークマッピングは、作成済みの VPC とプライベートサブネットを指定します。
セキュリティグループには、NLB 用に作成したセキュリティグループを割り当てます。
リスナーとルーティングは、以下の通り設定します。
- プロトコル:TCP
- ポート:5432
- デフォルトアクションに作成済みのターゲットグループを指定
その他はデフォルトとし、 NLB を作成します。完了後、対象の NLB の [セキュリティ > 編集] からセキュリティ設定を開き、「Privatelink トラフィックにインバウンドルールを適用する」のチェックを外し、変更を保存します。
エンドポイントサービスを作成
続けて、AWS 側でエンドポイントサービスを作成します。エンドポイントサービスのメニューから [エンドポイントサービスを作成] をクリックし、以下の設定とします。
- ロードバランサーのタイプ:ネットワーク
- ロードバランサー:上記の手順で作成した NLB を選択
追加設定では「エンドポイントの承諾が必要」にチェックを入れエンドポイントサービスを作成します。
作成後に詳細メニューから確認できるサービス名を後ほどの手順で使用します。
プリンシパルを許可する
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
の値を入力し [プリンシパルを許可] をクリックします。
プライベートエンドポイントをプロビジョン
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 が表示されます。
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 が表示されます。こちらを選択し [アクション > エンドポイント接続リクエストの承諾] をクリックします。
続けて、下図の表示になるのでフィールドに承諾と入力し [承諾] をクリックします。
問題なければ下図の表示となります。
以上で 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 = EGRESS
、TYPE = 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');
シークレットの作成
PostgreSQL にはユーザー名・パスワードで認証する設定とし、そのための認証資格情報を保持するシークレットオブジェクトを作成します。
USE SCHEMA SECURITY.SECRETS;
CREATE OR REPLACE SECRET secret_password
TYPE = PASSWORD
USERNAME = '<ユーザー名>'
PASSWORD = '<パスワード>';
外部アクセス統合の作成
さいごに外部アクセス統合を作成し、これまでに作成した各種オブジェクトを紐づけます。注意点として、現時点ではトライアルアカウントでの使用はサポートされていません。デフォルトでは 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;
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
が含まれる
- PostgreSQL への接続には
psycopg2
を使用しステージに配置したRDS の証明書を指定- パスの指定には IMPORTS 句にファイル名とステージ名を指定することで、ファイルを読み込み
sys._xoptions
メソッドとsnowflake_import_directory
システムオプションを使用して、 UDF のホームディレクトリの位置を取得
UDF を実行します。
SELECT query_postgres();
この場合、以下の通り出力され Snowflake から RDS PostgreSQL のデータを取得できました。
見やすく整形してみます。
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;
さいごに
Snowpark からの外部ネットワーク アクセス時の AWS PrivateLink の使用を試してみました。
設定手順としては、これまでの Snowflake 向けの PrivateLink 設定時に Snowflake 側で行ってくれていた作業をユーザー側で行うイメージです。エンドポイントサービスそのものに対してもコストがかかる点やプロビジョニングできる数に制限がある点は注意が必要です。
こちらの内容が何かの参考になれば幸いです。
参考