docker Snowflake

Snowpark Container Services上でWebアプリ(FastAPI/React/TypeScript)を動かしてみた

投稿日:

シンプルな Multi-Container App を動かしている以下の記事にインスパイアされてみました。
以下の記事では、Docker networkを前提にフロントがサーバの名前解決を行っています。
これをデプロイすると、ブラウザで動くフロントコードがサーバの名前を解決できません(SPCS無関係)。
リバースプロキシを挟んでプライベートなダウンストリームにAPIを配置する方法が良さそうです。
今回の記事はSPCSの動作確認をすることが目的なので凝ったことはせず、
ViteをそのままデプロイしてProxyで解決してみたのでご紹介します。

SPCSのアーキテクチャ

Image Registryはイメージリポジトリです。AWSのECR、AzureのACRと似た感じで操作できます。
Container Serviceはデプロイメントの単位で、1個以上のコンテナをデプロイできます。
Compute Poolは計算資源で、複数のContainer Serviceが共有します。

Serviceは基本的にはPrivateなリソースとなりますが、SpecでEndpointsを定義することで、
Publicリソースとすることができます。自動的に80にmapされます。

Serviceには以下のフォーマットでDNS名が付きます。
Service同士は以下のDNS使ってプライベート通信できます。
同一DB、Schema内に作成したServiceは先頭のServiceNameが異なるだけとなりますが、
その場合に限り、ServiceNameだけで互いの名前解決ができます。
Service間連携については、公式で”Service-to-Service”というパターンで紹介されています。

<Service Name>-<Schema Name>-<DB Name>.snowflakecomputing.internal

1つのService内に複数のコンテナを配置することができます。
同一Service内で各コンテナ内の通信したいと考えたのですが、方法を見つけることができませんでした。
(出来ないはずはなさそうで方法はあるのかもしれません..)

今回作るもの

フロント側、サーバ側の2つのコンテナを、それぞれ別々のServiceとしてデプロイします。
フロント側をPublic、サーバ側はPrivateとします。概要は以下の通りです。

  • フロント側
    • Vite
    • React/TypeScript
    • ReactからのAPIリクエストは一旦ViteのProxyで受けて、ProxyからAPIに流す
    • サーバ側にGETリクエストして応答を表示するだけ
  • サーバ側
    • uvicorn
    • FastAPI
    • poetry
    • GETリクエストを受けて”Hello World”をJSONで返すだけ

思いっきり開発用な感じですが、SPCSの動作確認が目的ですのでこれでいきます。
ローカルで動作確認をして、SPCSにデプロイします。ファイル構成は以下の通りです。

compose.yml

サーバ側、フロント側の2つのコンテナが、サーバ側->フロント側の順に起動するように書きます。
それぞれ、8080、5173 で待つようにします。

サーバ側

Snowflakeの公式のチュートリアルだとFlaskが使われています。
今回は、最近使い始めたFastAPIを使ってAPIサーバを立ててみようと思います。
FlaskとFastAPIの比較はこちらが詳しいです。
FastAPIの特徴はPydanticによるデータ検証とAsyncI/O。TypeScriptのように型チェックできます。
パッケージマネージャにはpoetryを使います。デファクトが無いPythonのパッケージ管理界隈で
npmやcomposer的な使い勝手が提供されます。

FastAPIの公式では、Python用Webサーバのuvicornを使ってホストされています。
uvicornでFastAPIを動かすコンテナを1個立てていきます。

Dockerfile

最新のPythonのイメージにpoetryをインストールします。
公式がインストーラを配布していて公式の手順通りに叩けばインストールできます。
公式のガイドの通り、POETRY_HOME=/etc/poetry とします。
Installation with the official installer

pyproject.toml

バニラのPythonとpyproject.tomlだけで依存関係を考慮したパッケージ管理が出来ますが、
要はnpmやbundle,composer的な使い勝手に寄せたパッケージ管理に対する需要があります。
poetry用の依存関係を書いていきます。fastapi、uvicornの現在(2/16)の最新を指定します。

api.py

[GET] /hello に対して Hello World 的な JSON を返す FastAPI の Hello Worldです。
後のapp.pyで FastAPIインスタンスに紐付けます。app.pyと分離することでロジックを分離できます。

app.py

FastAPIのHello Worldコードです。Dockerfileから開始します。

main.py

pythonコードからuvicornを起動します。uvicorn.runの公式の仕様はこちら
第1引数の”app:app”は”app”モジュールの中の”app”オブジェクトという表記です。
app.pyに記述したFastAPI()のインスタンスappを指します。stringで渡す必要があります。
hostは”0.0.0.0″を指定します。なぜ”127.0.0.1″でないのかはこちらが参考になります。
今回はPort=8080で起動します。reload=Trueとすると、HotReload機能が有効になります。便利。

起動してみる

docker compose up して http://localhost:8080/docs を開くと以下が表示されます。
ちゃんとJSONでHello Worldが戻りました。

フロント側

VueとReactの開発用に使われるローカル開発用のサーバ Vite をホストするコンテナを立てます。
Viteは Vue.jsの開発者Evan You氏が開発したJavaScript/TypeScriptで、ヴィートと読み、
フランス語で”素早い”という意味だそう。(webpackのように)リソースバンドルが不要で起動が速い。
(Laravelも9.xでwebpackを捨ててViteになってた..)
素の状態でTypeScriptを扱えるため、すぐにTypeScriptを書き始められる特徴があります。

Dockerfile

nodeのrelease scheduleはこちら
2/17のnodeのActiveLTSのMajor Versionは20で、2026-04-30がEnd of lifeとなっています。
これを使いたいので node:20 を指定します。

entry.sh

Dockerfile の ENTRYPOINT で npm run dev するだけのshです。

package.json

npm create vite@latest で Prjディレクトリ内に様々なファイルが作られます。
package.jsonも作られます。 Hello World で必要なもの以外を削ってみました。
npm install後、npm run devで viteを実行します。

TS用のconfigは別です。
他に生成されるpackage-lock.jsonが必要ですが省略します。

tsconfig.json

viteのPrj生成で自動生成されるTS用のconfigファイルです。
手をつけずに配置します。

tsconfig.node.json

viteのPrj生成で自動生成されるTS用のconfigファイルです。
手をつけずに配置します。

vite.config.ts

viteのPrj生成で自動生成されるvite用のconfigファイルです。
設定ファイルが.tsなところが凄いです。普通にimport文を書けます。
上のuvicornの起動で127.0.0.1ではなく0.0.0.0を指定したのと同様に、
viteも127.0.0.1ではなく0.0.0.0で待たせる必要があります。
serverオプションのhostにtrueを設定すると、0.0.0.0となります。公式

FastAPIの同一パスと対応するProxyを設定します。
以下で、server.proxy.api.target は SPCS上のAPIコンテナのPrivateエンドポイント を表します。
DNS名はサービス単位で作られます。本来長いFQDNを指定する必要がありますが、
同一スキーマに作られたサービスに限り、サービス名だけで解決できるようです。
DNS名はアンダースコア(_)がハイフン(-)に置き換わります。6時間くらいハマりました..
後で ikuty_api_service サービスを作りますが、ikuty-api-serviceを 使います。

詳細は以下を参照してください。
Service-to-service communications

index.html

Reactのコンポーネントを表示するガワとなるhtmlです。

src/hello.tsx

ようやくHello WorldするReactコンポーネントの本体です。
画面にはvalueというStateを表示しています。APIのURLは上記の通りproxyとします。
今回作成した/api/hello APIの応答を受けた後、setValueによりStateを更新します。

起動してみる

docker compose up すると、ほとんど一瞬でviteが起動します。
http://localhost:5173 を開きます。
Waiting…という表示が一瞬で Hello World に書き変わります。

ロールの作成

SPCSの各リソースの作成に必要な権限はこちらにあります。
ゴリ押ししただけなので間違っている可能性大です..
行ったり来たりしたので足りないものがあるかもしれません。

Image Repositoryの作成

SPCSで使用するイメージを配置するリポジトリを作成します。
AWSのECR、AzureのACR的に、dockerコマンドから透過的にpushできるようです。
公式は以下。
CREATE IMAGE REPOSITORY

SHOW IMAGEを叩くと repository_url が返ってます。

Image Repositoryにプッシュする

作成したImage Repositoryにローカルで作成したイメージをpushしていきます。
pushは指定されたタグを送信するという仕様のため、docker tagコマンドでイメージにタグを付けます。
docker tagの仕様はこちら

ローカルで以下を行います。(サニタイズのため分かりづらいですが補完してください..)

Compute Poolを作成する

Compute Poolを作成します。
CREATE COMPUTE POOL

以下でCREATEしたCompute poolをDESCRIBEできます。
DESCRIBE COMPUTE POOL

自分の環境だと、CREATE COMPUTE POOLしてから15分ほどステータスがSTARTINGでした。
15分ぐらいして叩くとステータスがACTIVEに変わりました。(結構かかるイメージ)
以下、公式の実行例です。

Serviceの作成

フロント側、サーバ側の2つのServiceを作成していきます。
specについて、ステージにファイルを配置してそれを指定するスタイルのほかに、
以下のようにCREATE SERVICEに含めるスタイルがあるようです。
CREATE SERVICE

フロント側のServiceは以下です。

サーバ側のServiceは以下です。

SERVICE用のシステム関数

SaaSで動くコンテナの動作を確認するのは結構面倒なことなのかなと思います。
自分の操作に対してSaaS側で何が行われているのか知りたいことは結構あるのかなと思います。
SPCSには以下のコマンドがあるようです。

エンドポイントURLの取得

SHOW ENDPOINTS すると、Specで指定したpublicなendpointを得られました。
ingress_url に なんとかかんとか.snowflakecomputing.app というURLが入っています。
SHOW ENDPOINTS

動作確認

Computing poolのStatusがACTIVEになってから、エンドポイントURLをブラウザで開くと、
期待通り、Reactで作ったHello Worldアプリが表示されます。

SYSEM$GET_SERVICE_LOGS()でフロントサービスのログを覗くと、viteの起動ログが出ていました。
そうえいば 5173 を 80 に mapping する記述をどこにもしていないのですが、そうなっています。

同様に、サーバ側のログを覗くと、uvicornの起動ログが出ていました。
ViteのProxyから8080で繋がるので、こちらは8080が開いています。

まとめ

SnowflakeをWebサーバのインフラにするだけの内容で正直意味がないです。
しかし、APIでSnowflakeに触ったり、Reactで格好良い可視化をしたり、夢は広がります。
FastAPI,React,TypeScriptの恩恵ゼロなので、今後ちょっと凝ったものを作ってみます。

-docker, Snowflake
-

Copyright© ikuty.com , 2024 AllRights Reserved Powered by AFFINGER4.