Snowpark Container Services奮闘記～Docker初心者がデータクラウドでコンテナを起動するまで


この記事は[Snowflake Advent Calendar 2023](https://qiita.com/advent-calendar/2023/snowflake)シリーズ3の24日目です。
おまけのシリーズ3！

SnowflakeでSnowpark Container ServicesがPublic Preview(PuPr)になりました。  
Snowpark Container ServicesはSnowflakeのインフラ上でKubernetesクラスタを構築できる機能です。  
データをSnowflakeの外に出さなくても無限に機能拡張できます。GPUインスタンスを選択できるのも注目ポイント！  
略してSPCSと呼ばれることもあります。  

## この記事について

この記事は初心者がSnowpark Container Servicesの[最初のチュートリアル](https://docs.snowflake.com/en/developer-guide/snowpark-container-services/overview-tutorials)を試してみたという記事です。  
有志の方とペアプロで試していたのですが、「Dockerのコマンド間違ってますよ」「それ多分パスワード間違ってます」などの初歩的な指摘を受けながら、30分未満で終わるであろうチュートリアルを2時間くらいかけてやっていました。  

Snowpark Container Servicesについては素晴らしい記事が既にありますのでそちらもぜひご確認ください。  

https://zenn.dev/tmasuo/articles/ab21cf8ac1c76f

https://zenn.dev/t_koreeda/articles/3437ca8c0eddfc

## 準備

Snowpark Container ServicesのチュートリアルはDocker DesktopとSnowSQLをあらかじめ準備しておく必要があります。  
Docker Desktop：動作確認するためのコンテナイメージを自前でビルドするため  
SnowSQL：ステージにファイルをアップロードするため。ただし**今回のチュートリアルでは使いません**

## アカウント作成

2023年12月時点ではロンドンおよびムンバイのみで利用可能です。検証用のアカウントとしていずれかのリージョンにアカウントを作成しましょう。  
残念ながらトライアルアカウントは対応していないということなので、既に利用しているアカウントをORGADMIN(組織管理者)として、追加のアカウントを作成することになります。  
追加のアカウントの作成はSnowsightから可能です。  

![spcs-create-account](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/58029425-1682-b8b3-776c-62a869aacbeb.png)

ログインするときはSnowsight左下のメニューから「別のアカウントにサインイン」を選びます。  
このとき入力するアカウント識別子は、ログインURLが```https://xxxxxxx-test_mumbai.snowflakecomputing.com```だとすれば```xxxxxxx-test_mumbai```です。  

![spcs-change-account](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/f1fa8b95-8b15-e5f1-06ba-bf03fcb3c4ab.png)

なお、初回ログインのときにパスワードの変更を強制されます。  
チュートリアルの過程でパスワードを入力するシーンがあるので変更したパスワードを覚えておきましょう。

アカウントを作成した場合、そのアカウントはまっさらな状態なのでコスト管理の設定が何もされていません。  
一方で、Snowpark Container Servicesは明示的に停止するまでクレジットを消費し続けます。いい感じに一時停止してくれるSnowflakeのウェアハウスに慣れていると停止を忘れがちなので気を付けましょう。  
検証用のアカウントにBudgetsの設定を行っておくと安心です。ただし、アカウントを作成した直後だとBudgetsの設定ができないようなので、いったんそのまま進めます。  

## 共通設定

チュートリアルのコピペで進めます。  
注意事項はロールをこのチュートリアル専用で作らないとセキュリティ上よろしくないこと、  
ACCOUNTADMINで進めると後で困るらしいことくらいでしょうか。  

```sql:Snowsight
-- リージョンを確認する
SHOW REGIONS LIKE 'AWS_EU_WEST%';
SHOW REGIONS LIKE 'AWS_AP_SOUTH%';
SELECT CURRENT_REGION();

-- アカウント管理者になる
USE ROLE ACCOUNTADMIN;

-- XSMALLウェアハウスを作る
CREATE OR REPLACE WAREHOUSE COMPUTE_WH WAREHOUSE_SIZE = XSMALL;
USE WAREHOUSE COMPUTE_WH;

-- チュートリアル手順：ロール作成
CREATE ROLE test_role;
GRANT ROLE test_role TO USER sakatoku;
ALTER USER sakatoku SET DEFAULT_ROLE = test_role;
USE ROLE accountadmin;

-- チュートリアル手順：COMPUTE POOLを作成する
-- https://docs.snowflake.com/en/sql-reference/sql/create-compute-pool
CREATE COMPUTE POOL tutorial_compute_pool
  MIN_NODES = 1
  MAX_NODES = 1
  INSTANCE_FAMILY = CPU_X64_XS; -- 最小のCPUインスタンスで試す
-- COMPUTE POOLに関する権限設定
GRANT OWNERSHIP ON COMPUTE POOL tutorial_compute_pool TO ROLE ACCOUNTADMIN;
GRANT ALL ON COMPUTE POOL tutorial_compute_pool TO ROLE test_role;

-- チュートリアル手順：ウェアハウスを作成する
CREATE OR REPLACE WAREHOUSE tutorial_warehouse WITH
  WAREHOUSE_SIZE='X-SMALL'
  AUTO_SUSPEND = 180
  AUTO_RESUME = true
  INITIALLY_SUSPENDED=false;
-- ウェアハウスに関する権限設定
GRANT ALL ON WAREHOUSE tutorial_warehouse TO ROLE test_role;

-- チュートリアル手順：データベースを作成する
CREATE DATABASE tutorial_db;
-- データベースに関する権限設定
GRANT OWNERSHIP ON DATABASE tutorial_db TO ROLE test_role;

-- チュートリアル手順：test_roleからエンドポイントをバインドできるようにする
GRANT BIND SERVICE ENDPOINT ON ACCOUNT TO ROLE test_role;

-- チュートリアル手順：セキュリティインテグレーションを有効にする
-- パブリックURLでアクセスする(ingress)ときにSnowflakeのユーザを使って認証する。ユーザのロールがACCOUNTADMINだとこの認証が通らない
CREATE SECURITY INTEGRATION IF NOT EXISTS snowservices_ingress_oauth
  TYPE=oauth
  OAUTH_CLIENT=snowservices_ingress
  ENABLED=true;
```

## ステージとリポジトリの作成

ここもチュートリアルのコピペで進めます。  

```sql:Snowsight
-- チュートリアル手順：ロールを切り替える
USE ROLE test_role;
USE DATABASE tutorial_db;
USE WAREHOUSE tutorial_warehouse;

-- チュートリアル手順：スキーマを作成する
CREATE SCHEMA data_schema;
USE SCHEMA data_schema;

-- チュートリアル手順：スキーマにリポジトリとステージを作成する
CREATE OR REPLACE IMAGE REPOSITORY tutorial_repository;
CREATE STAGE tutorial_stage DIRECTORY = ( ENABLE = true );

-- すべて正しく設定できたか確認する
SHOW COMPUTE POOLS;
SHOW WAREHOUSES;
SHOW IMAGE REPOSITORIES; -- ここでコンテナイメージをアップロードするリポジトリのパスを確認できる
SHOW STAGES;
```

## コンテナイメージのビルドとアップロード

コンテナイメージをアップロードするリポジトリのパスを前述の```SHOW IMAGE REPOSITORIES```で確認しておきます。  

コンテナイメージをビルドしていきます。  

```sh
# リポジトリのパスがxxxxxxx-test-mumbai.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repositoryだったとする

# コンテナイメージをビルド
docker build --rm --platform linux/amd64 -t my_echo_service_image:tutorial .
docker images

# タグにリポジトリのパスを指定する
docker tag my_echo_service_image:tutorial xxxxxxx-test-mumbai.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository/my_echo_service_image:tutorial
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/my_echo_service_image:tutorial
```

## サービスの起動

これでコンテナイメージがアップロードされたはずです。Snowsightからコンテナイメージを使ったサービスを起動しましょう。  

```sql:Snowsight
-- サービスを起動する
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:tutorial
        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;

-- サービスを確認する
SHOW SERVICES;
SELECT SYSTEM$GET_SERVICE_STATUS('echo_service');
DESCRIBE SERVICE echo_service;

-- このサービスのパブリックURLを確認する
SHOW ENDPOINTS IN SERVICE echo_service;
```

## 動作確認：UDF

UDF(ユーザ定義関数)から呼び出すときはサービスとエンドポイントを指定すればいいようです。  
チュートリアルのコピペで試してみましょう。  

```sql:Snowsight
-- SQLから呼び出すためのUDFを作成する
CREATE FUNCTION my_echo_udf (text varchar)
  RETURNS varchar
  SERVICE=echo_service
  ENDPOINT=echoendpoint
  AS '/echo';

-- UDFを実行してみる
SELECT my_echo_udf('hello!');
SELECT my_echo_udf('good bye!');

-- テーブルを作ってその各行にUDFを実行してみる
CREATE TABLE messages (message_text VARCHAR)
  AS (SELECT * FROM (VALUES ('Thank you'), ('Hello'), ('Hello World')));
SELECT * FROM messages;
SELECT my_echo_udf(message_text) FROM messages;
```

問題なく表示されましたね！  

![spcs-udf](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/515e8492-5f6c-2312-0cf6-060a0ac950d7.png)

## 動作確認：ブラウザアクセス

コンテナの中身はFlaskで作った簡単なアプリになっていて、```/ui```にアクセスすると簡易なUIが表示されるようになっています。  
このサービスのパブリックURLを確認してブラウザアクセスしてみましょう。  

チュートリアルには記載がないのですが、2023年12月時点だとサービスのパブリックURLを確認するためには```DESCRIBE SERVICE```ではなく```SHOW ENDPOINTS IN SERVICE```を使うようです。[^1]  

[^1]: PrPrのときは```DESCRIBE SERVICE```でも確認できたという噂はあるのですが、[ドキュメント](https://docs.snowflake.com/en/sql-reference/sql/desc-service)上は```DESCRIBE SERVICE```では確認できないことになっています。  

https://docs.snowflake.com/en/sql-reference/sql/show-endpoints

```sql:Snowsight
-- このサービスのパブリックURLを確認する
SHOW ENDPOINTS IN SERVICE echo_service;
```

なお、サービスを起動したばかりだとまだエンドポイントが準備中になることがあるようです。その場合はパブリックURLではなく以下のようなエラーメッセージが表示されます。1～2分待てば解消するかと思います。  

> Endpoints provisioning in progress... check back in a few minutes

確認したパブリックURLに```/ui```を付け足してブラウザアクセスしてみると、認証画面が表示されます！  
ここまで構築を進めてきたSnowflakeのユーザでログインしましょう。  
なお、認証が通るようになるまで、数分かかることがあるようです。認証が通らず403エラーが表示されたら少しだけ待ってリトライしてみるといいかもしれません。  

![spcs-oauth](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/3d768370-12aa-bbfd-0510-b6a2a65ef762.png)

![spcs-web](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/7488c7ab-423b-d78b-bea6-387fef98a600.png)

こちらも問題なく表示されましたね！  

## 後片付け

COMPUTE POOLは起動したままになってしまうので、試した後は手動で停止しましょう。  

```sql:Snowsight
-- COMPUTE POOLを停止
ALTER COMPUTE POOL tutorial_compute_pool STOP ALL;
SHOW COMPUTE POOLS;

-- COMPUTE POOLを削除
DROP COMPUTE POOL tutorial_compute_pool;
SHOW COMPUTE POOLS;
-- サービスも同時に削除される
SHOW SERVICES;

-- その他のリソースを削除
DROP IMAGE REPOSITORY tutorial_repository;
SHOW IMAGE REPOSITORIES;
DROP STAGE tutorial_stage;
SHOW STAGES;
```

## Budgetsの設定

Budgetsを設定しておけばCOMPUTE POOLを停止し忘れてクレジットが…という悲劇が防げそうですね。  
設定しておきましょう。  

https://docs.snowflake.com/ja/user-guide/budgets

Snowsightからポチポチで設定できるようです。  

![spcs-budgets-setting](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/74416b22-772b-262a-89e5-5d4d2ee42b6a.png)

## コストについて

コストについては[こちらの記事](https://zenn.dev/t_koreeda/articles/3437ca8c0eddfc
)が分かりやすいです。  
今回のチュートリアルだと0.1～0.2クレジット程度で試せるんじゃないでしょうか。  

コスト管理の画面でCOMPUTE POOLのクレジット使用も表示できるはずなんですが…なぜか表示されない。  

![spcs-budgets-error](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/a2ea9f2b-b760-f7d5-8952-d3acfbc30157.png)

## 感想

今回のチュートリアルでは特にSnowflakeでなくてもいいレベルでしたが、コンテナ内の自作プログラムからSnowflakeのデータにアクセスする、ということも当然可能なので、Snowflakeというプラットフォームだけでなんでもできるようになりそうですね。  
日本で利用できるようになるのを待ちつつ、活用事例や運用ノウハウを収集していきたいです。