Snowpark Container ServicesはSnowflakeのインフラ上でKubernetesクラスタを構築できる機能です。
略してSPCSと呼ばれることもあります。
本記事のサマリ
Snowpark Container Servicesのコンテナでパスワードのようなシークレットを扱いたい場合はシークレットオブジェクトを使う。
シークレットオブジェクトを作成して、そのシークレットオブジェクトをCREATE SERVICE
するときに指定すると、コンテナにはその指定のオプションに応じて環境変数かローカルファイルとしてシークレットが渡される。
問題
コンテナからSnowflakeや外部サービスに接続するときにユーザ名とパスワードを使いたいのですが、組織のルールでそれらをコンテナ内、あるいはコンテナを起動するためのコマンドリストに記載できないことがあります。
このような組織では多くの場合、コンテナを起動するときにユーザ名とパスワードを手動で入力するか、セキュアな場所に記載しておいたユーザ名とパスワードをコンテナに自動的に渡すことが求められます。
公式ドキュメント
シークレットオブジェクトを使うことで対応できます。
ここに記載があります。
CREATE SECRET
についてはこちら。
試してみる
シークレットを環境変数として渡す方法を試してみます。
まず、コンテナ内の環境変数を確認するためのPythonプログラムを作成しておきます。
今回はSPCSのチュートリアルのサンプルプログラムを改造してブラウザから確認できるものを作成してみました。
from flask import Flask
from flask import request
from flask import make_response
from flask import render_template
import os
import sys
# ホストOSからアクセスするために0.0.0.0でListenする
SERVICE_HOST = os.getenv('SERVER_HOST', '0.0.0.0')
# デフォルトポート:8080
SERVICE_PORT = os.getenv('SERVER_PORT', 8080)
app = Flask(__name__)
# 環境変数を取得して配列に追加する
def enum_env():
result = []
for env_key, env_value in os.environ.items():
result.append([env_key, env_value])
return result
# トップディレクトリにアクセスしたときに呼び出される関数を定義
@app.route("/", methods=["GET", "POST"])
def show_env():
# 出力データを生成
output_rows = enum_env()
# 出力データをレスポンスに変換
response = make_response({"data": output_rows})
response.headers['Content-type'] = 'application/json'
return response
if __name__ == '__main__':
app.run(host=SERVICE_HOST, port=SERVICE_PORT)
このPythonプログラムをコンテナ化します。
ARG BASE_IMAGE=python:3.10-slim-buster
FROM $BASE_IMAGE
COPY test.py ./
RUN pip install --upgrade pip && \
pip install flask
CMD ["python3", "test.py"]
コンテナをビルドしてSnowflakeのイメージリポジトリにプッシュします。ローカルで動作確認してもいいかもしれません。
# ビルドする
docker build --rm=true --tag=test:latest .
docker images
# ローカルで動作確認する
docker run -p 8080:8080 test:latest
# SHOW IMAGE REPOGITORIESでリポジトリURLを確認してタグに付与する
docker tag test:latest xxxxxxx-test-mumbai.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository/test:latest
docker images
# イメージリポジトリにログインする
docker login xxxxxxx-test-mumbai.registry.snowflakecomputing.com -u sakatoku
# プッシュする
docker push xxxxxxx-test-mumbai.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository/test:latest
次からはSnowflakeでの操作です。
まず、シークレットオブジェクトを作成します。
シークレットオブジェクトはいずれかのスキーマの配下に作成することになります。
CREATE SECRET TUTORIAL_DB.DATA_SCHEMA.SECRET_CONNECTION_MUMBAI
TYPE = password
USERNAME = 'sakatoku'
PASSWORD = 'fugafuga';
CREATE SERVICE
するときにシークレットオブジェクトと参照するキー、そのシークレットをどの環境変数に渡すかを指定します。
参照するキーというのは上記のシークレットオブジェクトであればUSERNAME
とPASSWORD
です。
今回はそれぞれMUMBAI_USERNAME
とMUMBAI_PASSWORD
という環境変数に渡すように指定します。
-- サービスを起動する
CREATE SERVICE test
IN COMPUTE POOL tutorial_compute_pool
FROM SPECIFICATION $$
spec:
containers:
- name: test
image: /tutorial_db/data_schema/tutorial_repository/test:latest
secrets:
- snowflakeSecret: TUTORIAL_DB.DATA_SCHEMA.SECRET_CONNECTION_MUMBAI
secretKeyRef: USERNAME
envVarName: MUMBAI_USERNAME
- snowflakeSecret: TUTORIAL_DB.DATA_SCHEMA.SECRET_CONNECTION_MUMBAI
secretKeyRef: PASSWORD
envVarName: MUMBAI_PASSWORD
endpoints:
- name: testendpoint
port: 8080
public: true
$$
MIN_INSTANCES=1
MAX_INSTANCES=1;
-- このサービスのパブリックURL(ingress_url)を確認する。サービスを起動してから1~2分で確認できるようになる
SHOW ENDPOINTS IN SERVICE test;
確認したパブリックURLにブラウザでアクセスすると、コンテナ内の環境変数を列挙したJSONが得られました。
MUMBAI_USERNAME
とMUMBAI_PASSWORD
が渡されていることが分かります。
{
"data": [
[ "PATH", "/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ],
[ "HOSTNAME", "statefulset-0" ],
[ "LANG", "C.UTF-8" ],
[ "GPG_KEY", "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCD" ],
[ "PYTHON_VERSION", "3.10.12" ],
[ "PYTHON_PIP_VERSION", "23.0.1" ],
[ "PYTHON_SETUPTOOLS_VERSION", "65.5.1" ],
[ "PYTHON_GET_PIP_URL", "https://github.com/pypa/get-pip/raw/0d8570dc44796f4369b652222cf176b3db6ac70e/public/get-pip.py" ],
[ "PYTHON_GET_PIP_SHA256", "96461deced5c2a487ddc65207ec5a9cffeca0d34e7af7ea1afc470ff0d746207" ],
[ "NVIDIA_VISIBLE_DEVICES", "none" ],
[ "SNOWFLAKE_DATABASE", "TUTORIAL_DB" ],
[ "SNOWFLAKE_HOST", "snowflake.ap-south-1.aws.snowflakecomputing.com" ],
[ "SNOWFLAKE_SERVICE_NAME", "TEST" ],
[ "MUMBAI_USERNAME", "sakatoku" ],
[ "MUMBAI_PASSWORD", "fugafuga" ],
[ "SNOWFLAKE_ACCOUNT", "XX12345" ],
[ "SNOWFLAKE_PORT", "443" ],
[ "SNOWFLAKE_SCHEMA", "DATA_SCHEMA" ],
[ "KUBERNETES_PORT_443_TCP_PORT", "443" ],
[ "KUBERNETES_PORT_443_TCP_ADDR", "10.96.0.1" ],
[ "SERVICE_PORT_8080_TCP_PORT", "8080" ],
[ "KUBERNETES_PORT", "tcp://10.96.0.1:443" ],
[ "KUBERNETES_PORT_443_TCP_PROTO", "tcp" ],
[ "SERVICE_PORT_8080_TCP_ADDR", "10.99.11.197" ],
[ "KUBERNETES_SERVICE_PORT", "443" ],
[ "KUBERNETES_PORT_443_TCP", "tcp://10.96.0.1:443" ],
[ "SERVICE_SERVICE_HOST", "10.99.11.197" ],
[ "SERVICE_SERVICE_PORT_TESTENDPOINT", "8080" ],
[ "SERVICE_PORT", "tcp://10.99.11.197:8080" ],
[ "SERVICE_SERVICE_PORT", "8080" ],
[ "SERVICE_PORT_8080_TCP", "tcp://10.99.11.197:8080" ],
[ "SERVICE_PORT_8080_TCP_PROTO", "tcp" ],
[ "KUBERNETES_SERVICE_HOST", "10.96.0.1" ],
[ "KUBERNETES_SERVICE_PORT_HTTPS", "443" ],
[ "HOME", "/root" ],
[ "WERKZEUG_SERVER_FD", "3" ]
]
}
上記は見やすいように整形しています。
別解
Snowflakeに接続するだけであれば、コンテナ内に自動的に生成されるトークンを利用することが推奨されているようです。
def get_login_token():
with open("/snowflake/session/token", "r") as f:
return f.read()
もちろん、ローカルで動作確認するときには動きません。
そのため、SPCSのチュートリアルではローカルで動作確認するときにはdocker
コマンドのオプションでユーザ名とパスワードを渡しています。これに対応してサンプルプログラムでは/snowflake/session/token
の有無で接続処理を分岐させています。
おわりに
シークレットオブジェクトを使うことで、パスワードのハードコーディングが避けられるので安心ですね。
本記事について
本記事はアウトプットガチ勢が作った高速記事作成フレームワークを参考にしています。
高速アウトプットを身に着けたい!