チュートリアル1: Snowpark Container Servicesサービスを作成する¶
紹介¶
共通セットアップ が完了すると、サービスを作成する準備が整います。このチュートリアルでは、入力されたテキストを単純にエコーバックするサービス(echo_service
という名前)を作成します。たとえば、入力文字列が「Hello World」の場合、サービスは「I said, Hello World」を返します。
このチュートリアルには2つのパートがあります。
パート1: サービスを作成してテストする。 このチュートリアルで提供されるコードをダウンロードし、ステップバイステップの手順に従います。
このチュートリアルのサービスコードをダウンロードします。
Snowpark Container Services用のDockerイメージをビルドし、アカウントのリポジトリにイメージをアップロードします。
サービスを作成し、サービス仕様ファイルとサービスを実行するコンピューティングプールを提供します。
サービスと通信するためのサービス関数を作成します。
サービスを使用します。サービスにエコーリクエストを送り、その応答を検証します。
パート2: サービスを理解する。このセクションでは、サービスコードの概要を説明し、さまざまなコンポーネントがどのように連携しているかを明らかにします。
1: サービスコードをダウンロードする¶
Echoサービスを作成するためのコード(Pythonアプリケーション)が提供されます。
zipファイル をディレクトリにダウンロードします。
ファイルを解凍します。チュートリアルごとに1つのディレクトリが含まれています。
Tutorial-1
ディレクトリには以下のファイルがあります。Dockerfile
echo_service.py
templates/basic_ui.html
2: イメージをビルドしてアップロードする¶
Snowpark Container Servicesがサポートするlinux/amd64プラットフォーム用のイメージをビルドし、アカウントのイメージリポジトリにイメージをアップロードします(共通セットアップ を参照)。
イメージをビルドしてアップロードする前に、リポジトリに関する情報(リポジトリ URL とレジストリのホスト名)が必要です。詳細については、 レジストリおよびリポジトリ をご参照ください。
リポジトリに関する情報を取得する
リポジトリ URL を取得するには、 SHOW IMAGE REPOSITORIES SQL コマンドを実行します。
SHOW IMAGE REPOSITORIES;
出力の
repository_url
列は、 URL を提供します。以下に例を示します。<orgname>-<acctname>.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository
リポジトリ URL のホスト名はレジストリのホスト名です。以下に例を示します。
<orgname>-<acctname>.registry.snowflakecomputing.com
イメージをビルドし、リポジトリにアップロードする
ターミナルウィンドウを開き、解凍したファイルのあるディレクトリに移動します。
Dockerイメージをビルドするには、Docker CLI を使用して以下の
docker build
コマンドを実行します。このコマンドは、イメージのビルドに使用するファイルのPATH
として、現在の作業ディレクトリ(.)を指定していることに注意してください。docker build --rm --platform linux/amd64 -t <repository_url>/<image_name> .
image_name
には、my_echo_service_image:latest
を使用します。
例
docker build --rm --platform linux/amd64 -t myorg-myacct.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository/my_echo_service_image:latest .
Snowflakeアカウントのリポジトリにイメージをアップロードします。Dockerがあなたの代わりにリポジトリにイメージをアップロードするには、まずSnowflakeでDockerを認証する必要があります。
SnowflakeレジストリでDockerを認証するには、以下のコマンドを実行します。
docker login <registry_hostname> -u <username>
username
には、Snowflakeのユーザー名を指定します。Dockerは、パスワードの入力を求めるプロンプトを表示します。
イメージをアップロードするには、以下のコマンドを実行します。
docker push <repository_url>/<image_name>
例
docker push myorg-myacct.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository/my_echo_service_image:latest
3: サービスを作成する¶
このセクションでは、サービスを作成し、サービスと通信するためのサービス関数も作成します。
サービスを作成するには、以下が必要です。
コンピューティングプール。Snowflakeは指定したコンピューティングプールでサービスを実行します。共通セットアップの一環としてコンピューティングプールを作成しました。
サービス仕様。この仕様は、サービスの構成と実行に必要な情報をSnowflakeに提供します。詳細については、 Snowpark Container Services: サービスの操作 をご参照ください。このチュートリアルでは、 CREATE SERVICE コマンドで仕様をインラインで提供します。また、チュートリアル2に示すように、仕様をSnowflakeステージのファイルに保存し、 CREATE SERVICE コマンドでファイル情報を提供することもできます。
サービス関数は、サービスと通信するために利用できる方法の1つです。サービス関数は、サービスエンドポイントに関連付けるユーザー定義関数(UDF)です。サービス関数が実行されると、サービスエンドポイントにリクエストを送り、応答を受け取ります。
コンピューティングプールの準備ができ、サービスを作成するのに適切なコンテキストにいることを確認します。
以前は 共通セットアップ ステップでコンテキストを設定しました。このステップの SQL ステートメントのコンテキストが正しいことを確認するために、以下を実行します。
USE ROLE test_role; USE DATABASE tutorial_db; USE SCHEMA data_schema; USE WAREHOUSE tutorial_warehouse;
共通セットアップ で作成したコンピューティングプールが準備できていることを確認するために、
DESCRIBE COMPUTE POOL
を実行し、state
がACTIVE
またはIDLE
であることを確認します。state
がSTARTING
の場合は、state
がACTIVE
かIDLE
に変わるまで待つ必要があります。
DESCRIBE COMPUTE POOL tutorial_compute_pool;
サービスを作成するには、
test_role
を使用して以下のコマンドを実行します。CREATE SERVICE echo_service IN COMPUTE POOL tutorial_compute_pool FROM SPECIFICATION $$ spec: containers: - name: echo image: /tutorial_db/data_schema/tutorial_repository/my_echo_service_image:latest env: SERVER_PORT: 8000 CHARACTER_NAME: Bob readinessProbe: port: 8000 path: /healthcheck endpoints: - name: echoendpoint port: 8000 public: true $$ MIN_INSTANCES=1 MAX_INSTANCES=1;
注釈
その名前のサービスがすでに存在する場合は、 DROP SERVICE コマンドを使用して以前に作成したサービスを削除してから、このサービスを作成します。
以下の SQL コマンドを実行して、先ほど作成したサービスの詳細情報を取得します。詳細については、 Snowpark Container Services: サービスの操作 をご参照ください。
アカウント内のサービスをリストするには、 SHOW SERVICES コマンドを実行します。
SHOW SERVICES;
サービスのステータスを取得するには、システム関数 SYSTEM$GET_SERVICE_STATUS を呼び出します。
SELECT SYSTEM$GET_SERVICE_STATUS('echo_service');
サービスに関する情報を取得するには、 DESCRIBE SERVICE コマンドを実行します。
DESCRIBE SERVICE echo_service;
サービス関数を作成するには、以下のコマンドを実行します。
CREATE FUNCTION my_echo_udf (InputText varchar) RETURNS varchar SERVICE=echo_service ENDPOINT=echoendpoint AS '/echo';
次の点に注意してください。
SERVICE プロパティは、 UDF と
echo_service
サービスを関連付けます。ENDPOINT プロパティは、サービス内の UDF と
echoendpoint
エンドポイントを関連付けます。AS 「/echo」は、エコーサーバーへの HTTP パスを指定します。このパスは、サービスコード
echo_service.py
にあります。
4: サービスを使用する¶
まず、このセクションの SQL ステートメントのコンテキストを設定し、以下を実行します。
USE ROLE test_role;
USE DATABASE tutorial_db;
USE SCHEMA data_schema;
USE WAREHOUSE tutorial_warehouse;
これで、Echoサービスと通信できるようになりました。
サービス関数の使用: クエリ内でサービス関数を呼び出すことができます。例のサービス関数(
my_echo_udf
)は、単一の文字列または文字列のリストを入力として受け取ることができます。例1.1: 単一の文字列を渡す
my_echo_udf
サービス関数を呼び出すには、次の SELECT ステートメントを実行し、1つの入力文字列('hello'
)を渡します。SELECT my_echo_udf('hello!');
Snowflakeは、 POST リクエストをサービスエンドポイント(
echoendpoint
)に送信します。リクエストを受信すると、サービスは応答に入力文字列をエコーします。+--------------------------+ | **MY_ECHO_UDF('HELLO!')**| |------------------------- | | Bob said hello! | +--------------------------+
例1.2: 文字列のリストを渡す
サービス関数に文字列のリストを渡すと、Snowflakeはこれらの入力文字列を一括して、一連の POST リクエストをサービスに送信します。サービスがすべての文字列を処理した後、Snowflakeは結果を結合して返します。
以下の例では、テーブル列を入力としてサービス関数に渡します。
複数の文字列からなるテーブルを作成します。
CREATE TABLE messages (message_text VARCHAR) AS (SELECT * FROM (VALUES ('Thank you'), ('Hello'), ('Hello World')));
テーブルが作成されたことを確認します。
SELECT * FROM messages;
サービス関数を呼び出すには、次の SELECT ステートメントを実行し、入力としてテーブル行を渡します。
SELECT my_echo_udf(message_text) FROM messages;
出力:
+---------------------------+ | MY_ECHO_UDF(MESSAGE_TEXT) | |---------------------------| | Bob said Thank you | | Bob said Hello | | Bob said Hello World | +---------------------------+
ウェブブラウザーの使用: サービスはエンドポイントを公開します(CREATE SERVICE コマンドで提供されるインライン仕様を参照)。したがって、サービスがインターネットに公開しているウェブ UI にログインし、ウェブブラウザーからサービスにリクエストを送信できます。
サービスが公開しているパブリックエンドポイントの URL を見つけます。
SHOW ENDPOINTS IN SERVICE echo_service;
応答の
ingress_url
列は URL を提供します。例
p6bye-myorg-myacct.snowflakecomputing.app
/ui
をエンドポイント URL に追加し、ウェブブラウザーに貼り付けます。これにより、サービスはui()
関数を実行します(echo_service.py
を参照)。エンドポイント URL に初めてアクセスするときは、Snowflakeにログインするようにリクエストされることに注意してください。このテストでは、サービスの作成に使用したユーザーと同じユーザーを使用し、そのユーザーが必要な権限を持っていることを確認します。
Input ボックスに文字列「Hello」を入力し、 Return を押します。
注釈
イングレスエンドポイントにはプログラムでアクセスできます。サンプルコードについては、 公開エンドポイントアクセスおよび認証 をご参照ください。Snowflakeがリクエストをサービスコード内の
ui()
関数にルーティングできるように、コード内のエンドポイント URL に/ui
を追加する必要があることに注意してください。
5: クリーンアップする¶
チュートリアル2 または チュートリアル3 に進む予定がない場合は、作成した請求対象リソースを削除する必要があります。詳細については、 チュートリアル3 のステップ5をご参照ください。
6: サービスコードの確認¶
このセクションでは、以下のトピックを取り上げます。
チュートリアル1コードの検証: Echoサービスを実装しているコードファイルを確認します。
サービス関数に対する理解: このセクションでは、このチュートリアルのサービス関数がどのようにサービスとリンクしているかを説明します。
ローカルでのイメージの構築とテスト。このセクションでは、Snowflakeアカウントのリポジトリにアップロードする前に、ローカルでDockerイメージをテストする方法について説明します。
チュートリアル1コードの検証¶
ステップ1でダウンロードしたzipファイルには、以下のファイルが含まれています。
Dockerfile
echo_service.py
templates/basic_ui.html
サービスを作成する際にもサービス仕様を使用します。次のセクションでは、これらのコードコンポーネントがどのように連携してサービスを作成するかを説明します。
echo_service.pyファイル¶
このPythonファイルには、入力テキストを返す(エコーバックする)最小の HTTP サーバーを実装するコードが含まれています。このコードでは主に、Snowflakeサービス関数からのエコーリクエストの処理と、エコーリクエストを送信するためのウェブユーザーインターフェイス(UI)の提供という2つのタスクを実行します。
from flask import Flask
from flask import request
from flask import make_response
from flask import render_template
import logging
import os
import sys
SERVICE_HOST = os.getenv('SERVER_HOST', '0.0.0.0')
SERVER_PORT = os.getenv('SERVER_PORT', 8080)
CHARACTER_NAME = os.getenv('CHARACTER_NAME', 'I')
def get_logger(logger_name):
logger = logging.getLogger(logger_name)
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
handler.setFormatter(
logging.Formatter(
'%(name)s [%(asctime)s] [%(levelname)s] %(message)s'))
logger.addHandler(handler)
return logger
logger = get_logger('echo-service')
app = Flask(__name__)
@app.get("/healthcheck")
def readiness_probe():
return "I'm ready!"
@app.post("/echo")
def echo():
'''
Main handler for input data sent by Snowflake.
'''
message = request.json
logger.debug(f'Received request: {message}')
if message is None or not message['data']:
logger.info('Received empty message')
return {}
# input format:
# {"data": [
# [row_index, column_1_value, column_2_value, ...],
# ...
# ]}
input_rows = message['data']
logger.info(f'Received {len(input_rows)} rows')
# output format:
# {"data": [
# [row_index, column_1_value, column_2_value, ...}],
# ...
# ]}
output_rows = [[row[0], get_echo_response(row[1])] for row in input_rows]
logger.info(f'Produced {len(output_rows)} rows')
response = make_response({"data": output_rows})
response.headers['Content-type'] = 'application/json'
logger.debug(f'Sending response: {response.json}')
return response
@app.route("/ui", methods=["GET", "POST"])
def ui():
'''
Main handler for providing a web UI.
'''
if request.method == "POST":
# getting input in HTML form
input_text = request.form.get("input")
# display input and output
return render_template("basic_ui.html",
echo_input=input_text,
echo_reponse=get_echo_response(input_text))
return render_template("basic_ui.html")
def get_echo_response(input):
return f'{CHARACTER_NAME} said {input}'
if __name__ == '__main__':
app.run(host=SERVICE_HOST, port=SERVER_PORT)
コードで、
echo
関数は、Snowflakeサービス関数がサービスと通信できるようにします。この関数は、@app.post()
装飾を次のように指定します。@app.post("/echo") def echo():
エコーサーバーが HTTP POST リクエストと
/echo
パスを受信すると、サーバーはリクエストをこの関数にルーティングします。この関数は実行され、リクエスト本文の文字列を応答にエコーバックします。Snowflakeサービス関数からの通信をサポートするために、このサーバーは外部関数を実装します。つまり、サーバー実装は、 SQL 関数を処理するために特定の入出力データ形式に従います。これは 外部関数 が使用する 入出力データ形式 と同じです。
コードの
ui
関数セクションは、ウェブフォームを表示し、ウェブフォームから送信されたエコーリクエストを処理します。この関数は、@app.route()
デコレーターを使用して、/ui
に対するリクエストをこの関数で処理するように指定します。@app.route("/ui", methods=["GET", "POST"]) def ui():
Echoサービスは、
echoendpoint
エンドポイントを公開し(サービス仕様を参照)、ウェブ上でサービスと通信できるようにします。ブラウザーで/uiを付加したパブリックエンドポイントの URL をロードすると、ブラウザーはこのパスに対する HTTP GET リクエストを送信し、サーバーはリクエストをこの関数にルーティングします。この関数は実行され、ユーザーが文字列を入力するための単純な HTML フォームを返します。ユーザーが文字列を入力してフォームを送信した後、ブラウザーはこのパスに対する HTTP ポストリクエストを送信し、サーバーはリクエストをこの同じ関数にルーティングします。この関数は実行され、元の文字列を含む HTTP 応答を返します。
readiness_probe
関数は、@app.get()
デコレーターを使用して、/healthcheck
に対するリクエストがこの関数で処理されることを指定します。@app.get("/healthcheck") def readiness_probe():
この関数により、Snowflakeはサービスの準備状況を確認することができます。コンテナーが起動すると、Snowflakeはアプリケーションが動作していることと、サービスがリクエストに対応する準備ができていることを確認します。Snowflakeは、このパスで HTTP GET リクエストを送信し(healthプローブ、readinessプローブとして)、正常なコンテナーだけがトラフィックを提供できるようにします。この関数は何でもできます。
get_logger
関数はログのセットアップに役立ちます。
Dockerfile¶
このファイルには、Dockerを使用してイメージを構築するためのすべてのコマンドが含まれています。
ARG BASE_IMAGE=python:3.10-slim-buster
FROM $BASE_IMAGE
COPY echo_service.py ./
COPY templates/ ./templates/
RUN pip install --upgrade pip && \\
pip install flask
CMD ["python", "echo_service.py"]
Dockerfileには、FlaskライブラリをDockerコンテナーにインストールする手順が含まれています。 echo_service.py
のコードは、 HTTP のリクエストを処理するためにFlaskライブラリに依存しています。
/template/basic_ui.html¶
Echoサービスは、 echoendpoint
エンドポイントを公開し(サービス仕様を参照)、ウェブ上でサービスと通信できるようにします。ブラウザーで /ui
を付加したパブリックエンドポイント URL をロードすると、Echoサービスはこのフォームを表示します。フォームに文字列を入力して送信すると、サービスはその文字列を HTTP 応答として返します。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Welcome to echo service!</title>
</head>
<body>
<h1>Welcome to echo service!</h1>
<form action="{{ url_for("ui") }}" method="post">
<label for="input">Input:<label><br>
<input type="text" id="input" name="input"><br>
</form>
<h2>Input:</h2>
{{ echo_input }}
<h2>Output:</h2>
{{ echo_reponse }}
</body>
</html>
サービス仕様¶
Snowflakeは、この仕様で提供された情報を使用して、サービスを構成および実行します。
spec:
containers:
- name: echo
image: /tutorial_db/data_schema/tutorial_repository/my_echo_service_image:latest
env:
SERVER_PORT: 8000
CHARACTER_NAME: Bob
readinessProbe:
port: 8000
path: /healthcheck
endpoints:
- name: echoendpoint
port: 8000
public: true
サービス仕様で、
containers.image
は、Snowflakeがコンテナーを開始するためのイメージを指定します。オプションの
endpoints
フィールドは、サービスが公開するエンドポイントを指定します。name
は、コンテナーがリッスンしている TCP ネットワークポートのユーザーフレンドリーな名前を指定します。このユーザーフレンドリーなエンドポイント名を使用して、対応するポートにリクエストを送信します。env.SERVER_PORT
は、このポート番号を制御することに注意してください。エンドポイントも
public
として構成されています。これにより、パブリックウェブからこのエンドポイントへのトラフィックが許可されます。
オプションの
containers.env
フィールドは、Snowflakeがコンテナー内のすべてのプロセスに渡す環境変数を上書きする方法を説明するために追加されています。たとえば、サービスコード(echo_service.py
)は、以下のようにデフォルト値で環境変数を読み取ります。CHARACTER_NAME = os.getenv('CHARACTER_NAME', 'I') SERVER_PORT = os.getenv('SERVER_PORT', 8080)
仕組みは次のとおりです。
Echoサービスは、リクエスト本文に文字列(例: 「Hello」)を含む HTTP POST リクエストを受信すると、デフォルトで「I said Hello」を返します。このコードでは、環境変数
CHARACTER_NAME
を使用して、「said」の前の単語を決定しています。デフォルトでは、CHARACTER_NAME
は「I」に設定されています。サービス仕様の CHARACTER_NAME デフォルト値は上書きすることができます。たとえば、値を「Bob」に設定すると、Echoサービスは「Bob said Hello」という応答を返します。
同様に、サービス仕様は、サービスがリッスンするポート(SERVER_PORT)をデフォルトのポート8080から8000に上書きします。
readinessProbe
フィールドは、Snowflakeがreadinessプローブに HTTP GET リクエストを送信するために使用できるport
とpath
を識別して、サービスがトラフィックを処理する準備ができていることを確認します。サービスコード(
echo_python.py
)は、以下のようにreadinessプローブを実装します。@app.get("/healthcheck") def readiness_probe():
そのため、仕様ファイルにはそれに応じて
container.readinessProbe
フィールドが含まれます。
サービス仕様の詳細については、 サービス仕様リファレンス をご参照ください。
サービス関数に対する理解¶
サービス関数は、サービスと通信する方法の1つです(サービスの使用 を参照)。サービス関数は、サービスエンドポイントに関連付けるユーザー定義関数(UDF)です。サービス関数が実行されると、関連するサービスエンドポイントにリクエストを送信し、応答を受信します。
以下のパラメーターを使用して CREATE FUNCTION コマンドを実行し、以下のサービス関数を作成します。
CREATE FUNCTION my_echo_udf (InputText VARCHAR)
RETURNS VARCHAR
SERVICE=echo_service
ENDPOINT=echoendpoint
AS '/echo';
次の点に注意してください。
my_echo_udf
関数は、入力として文字列を取り、文字列を返します。SERVICE プロパティはサービス(
echo_service
)を識別し、 ENDPOINT プロパティはユーザーフレンドリーなエンドポイント名(echoendpoint
)を識別します。AS 「/echo」は、サービスへのパスを指定します。
echo_service.py
では、@app.post
デコレーターがこのパスをecho
関数に関連付けます。
この関数は、指定された SERVICE の特定の ENDPOINT に接続します。この関数を呼び出すと、Snowflakeはサービスコンテナー内の /echo
パスにリクエストを送信します。
ローカルでのイメージの構築とテスト¶
Snowflakeアカウントのリポジトリにアップロードする前に、ローカルでDockerイメージをテストすることができます。ローカルテストでは、コンテナーはスタンドアロンで実行されます(Snowflakeが実行するサービスではありません)。
チュートリアル1のDockerイメージをテストするには、
Dockerイメージを作成するには、Docker CLI で以下のコマンドを実行します。
docker build --rm -t my_service:local .
コードを起動するには、以下のコマンドを実行します。
docker run --rm -p 8080:8080 my_service:local
以下のいずれかの方法で、サービスにエコーリクエストを送信します。
cURL コマンドの使用:
別のターミナルウィンドウで、 cURL を使用し、ポート8080に以下の POST リクエストを送信します。
curl -X POST http://localhost:8080/echo \ -H "Content-Type: application/json" \ -d '{"data":[[0, "Hello friend"], [1, "Hello World"]]}'
リクエスト本文には2つの文字列が含まれていることに注意してください。この cURL コマンドは、サービスがリッスンしているポート8080に POST リクエストを送信します。データ中の0は、リスト中の入力文字列のインデックスです。Echoサービスは、入力された文字列を次のようにエコーします。
{"data":[[0,"I said Hello Friend"],[1,"I said Hello World"]]}
ウェブブラウザーの使用:
次の内容¶
これでジョブを実行する チュートリアル2 をテストできます。