小ネタ: Cloud Run で server IP address をとる方法

背景: 事情により Cloud Run がどの IP でコードを実行しているのか知りたかった

結論、 Cloud Run への POST/GET のエントリを開いて、 httpRequest.serverIp を見ればわかる (リクエストの一番最初につくられるエントリ)。

これをたまたま見つけたとき、、ほんとに Cloud Run の IP なのか気になったので探してみたら↓のドキュメントが見つかった。

https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#HttpRequest

serverIp は

The IP address (IPv4 or IPv6) of the origin server that the request was sent to. This field can include port information. Examples: "192.168.1.1", "10.0.0.1:80", "FE80::0202:B3FF:FE1E:8329".

とのことなので、たぶん探してるもののはず... この文章だと厳密にいえばリクエストが「送信された」サーバと実行するサーバが別の場合、探してるものではない可能性があるけど、もし違ったらおしえてほしい :pleading_face:

あと、地味に知らなかったのが Cloud Run の region と IP address の region は全然関係ないということ。

この SO だと Google のrecognized 人から回答来てる↓

All Google Cloud IPs (including the Compute Engine VMs) will seem like they're coming out of "Mountain View, CA, USA" regardless of the region they're running in. This is because they belong to the same global IP block that Google has.

This IP pool is also likely used by other Google products like Cloud Run, Cloud Functions, App Engine and Compute Engine as well.

Most probably, Google doesn’t dedicate certain IP ranges to specific regions, as Google’s IP blocks are meant to be routed to the nearest Google datacenter, then enter Google’s private network, and be routed internally.

GCP の IP は基本カリフォルニアの Mountain View の IP になる

Cloudflare Pages にデプロイした Sveltekit app で TypeError: Unsupported cache mode: no-cache を踏んだ

TL;DR

原因は Cloudflare pages が Node.js runtime ではないことで、 wrangler.tomlcompatibility_flags = ["nodejs_compat"] を書いてデプロイしたら治った。

see: https://discord.com/channels/1066956501299777596/1239118579509628929/1239252696566075593

起きていたこと

前提: Trigger.dev はサーバレスの background job platform (とてもおすすめ)

  • Sveltekit
  • Trigger.dev (background jobs)
  • Supabase (DB + auth)
  • Cloudflare Pages

というスタックのアプリで、ある日突然特定の server action が 500 を変えすようになった。初回のデプロイは普通に通って、 1-2ヶ月ふつうに動いていたのにある日突然動かなくなるというなかなか怖い現象があった。

調査

問題のアプリはツイッター的なものなんだけど、「投稿」時にまずエラーになっていた。この時点で、

  • 画面を更新すると投稿は増えている → supabase との接続は問題なし
  • 削除処理は普通に動く → create server action の何かしらのコードが悪さをしている

とわかっていたので、途中途中に console.log を仕込んでいった。

で、デプロイして投稿をすると、どうやら xxJob.invoke() の手前で落ちてるとわかった。たしかに Trigger.dev のダッシュボードを見ると、prod だと直近の発火履歴がなかった。

もろもろ Trigger.dev のコードを見たりググったりしたものの、ローカルだと普通に動くという問題もあって Trigger.dev のディスコ鯖に投稿した。あと、ググっても結構ヒットしなかった。

fix

夕方 JST くらいに投稿して、 2AM くらいにさっそく公式から回答が来て、結論

  1. Trigger.dev client を呼ぶときに API キーを明示的に指定してね (process.env がないから SDK 側でよしなに処理...というのができない)
  2. wrangler.tomlcompatibility_flags = ["nodejs_compat"] を指定してね

とのことだった。1はすでにやってたので、2を実施したら一瞬で治った。

一応 chat gpt の話をめも的に貼っとく。

残る謎

まじで今までなんで動いていたのかだけが謎。 たしかに Trigger.dev は最初から no-cache ヘッダをつけてはなかったんだけど、追加したコミットは 5-6mo 前なので自分のアプリをデプロイするよりずっと前のはずで、パッケージもアプリ構築当時の最新を使っていた気がする。

https://github.com/triggerdotdev/trigger.dev/blame/2081cb15b89dd4de2fd34a7ea0a0171d9cc69d2f/packages/trigger-sdk/src/apiClient.ts#L854

ジョブが静かに死んでいたという形跡もないので、あとは Cloudflare Pages がよしなに処理していてくれたとかなのかもしれない。ウーン...

まとめ

  • TypeError: Unsupported cache mode: no-cache を踏んだ
  • Cloudflare runtime が Node.js でないことが原因
  • wrangler.toml に compatibility_flags = ["nodejs_compat"] を追加したらなおった

めも: GCP の API key について

まずそもそも API key はによる認証はサポートされてないことが多い。

Most Google Cloud APIs don't support API keys. Check that the API that you want to use supports API keys before using this authentication method.

https://cloud.google.com/docs/authentication/api-keys

サポート有無の一覧的なものは見つけられなかったけど、今回対象にしてた Cloud Text-to-Speech については REST API Reference の「試してみよう」的なところに API key があったので、そうやって確認するしかないのかも?

https://cloud.google.com/text-to-speech/docs/reference/rest/v1beta1/text/synthesize

API を叩くときに key の情報は ?key=<YOUR_API_KEY> とやるっぽい。こちらもドキュメントの記載は見つけられなかったけど、

  • 上の画面から試してみる
  • Network タブから確認

すると、どんな形で API を叩いているかわかる

あとは、 API キーを使えるドメインを制限している場合、 Referer ヘッダに値を入れないと 403 になる

Cloudflare Pages に Cloudflare Access をつける

ユースケース: 雑につくったアプリを Cloudflare Pages にアップしたが、有象無象にアクセスされたくないので雑に認証をつけたい

毎回微妙に手順を忘れるので書いておく

まず Cloudflare Pages (以下 Pages) に Cloudflare Access (以下 Access) をつけるとき、 Access 側のページからは設定できない。かわりに、 Pages から Manage タブ > Access Policy のとこから "Enable access policy" をクリックして設定をはじめる。

Access 側では、デフォルトのドメイン設定だと preview のページにしかアクセス制限がかからないので、subdomain なしのドメインを追加する。

ここまでやれば、 email x OTP でのアクセス制限はかけられる。なんだけど、Access からメールが来るのにやたら時間がかかるので Google 認証もつけたい。 Google 認証はアプリケーション単位ではなく、 Cloudflare のアカウント単位で設定するもので、 Access の Settings ページからやる。

手順はここ↓ で、 GCP 側に OAuth consent screen と OAuth client をつくる。

consent screen のポイント:

  • user type: External
  • authorized domains: client を作るときに勝手に追加されるので、consent screen を作るときに設定しなくて ok
  • scopes: .../auth/userinfo.email を追加するといいよ、と上の docs に書いてある
  • test users: ... はいらない (これも docs に書いてある)

OAuth client のポイント:

  • type: Web application
  • origin/redirect URI: docs に書いてある & ためしに Cloudflare Access のログイン画面を自分のアプリに行って開けばわかる

ここも参考になった、あざます https://egashira.dev/blog/uses-google-oauth-for-cloudflare-pages#google-cloud-console-%E3%81%A7-oauth-%E5%90%8C%E6%84%8F%E7%94%BB%E9%9D%A2%E3%81%AE%E8%A8%AD%E5%AE%9A

ここまでやったら Cloudflare Access 全体の設定に Google が IdP として追加されたので、最後に Pages の AccessGoogle を追加する。

やってみた: Shopify → Fivetran → Snowflake → dbt Cloud → steep PART 2

pre-amble

PART 1 はこちら↓

前回は dbt Cloud にデータを乗せるところまでやって、今回は

  • Fivetran の Shopify 用 dbt package を使う
  • semantic layer の設定
  • semantic layer の接続設定をして steep につなぐ

という作業があった。実は3番目が一生うまくいかなくていちばんキツかった。あと、今回やってみたと同じくらい「よーわからん」ポイントが多かった。

steep にした理由

... は特にないんだけど、 UI が白くてとっつきやすかったから、が一つかも? thoughtspot の mode とかもいいらしい。

あとは、後出しだけど classmethod さんが記事出してたのも心強かった。

1. Shopify models の生成

前回までで、 Snowflake に Shopify データがある・ dbt Cloud のアカウントを作ったという状態で、このやってみたのお題をもらった方から「Shopify x Fivetran は dbt パッケージがあるのでいいですよ〜」と教えてもらってたので、それを使うところから始まった。パッケージの GitHub はここ↓で、作成されるモデルの dbt docs も README からリンクが貼ってあって神。

https://github.com/fivetran/dbt_shopify

モデルを作ってくれる dbt package を使ったことがなかった + dbt Cloud もよくわからんところからはじめたので 、README を見てもイマイチよくわからなかったんだけど

  • package.yml に追加
  • dbt run を実行

すると、たしかに Snowflake 上にモデルが出来ててすっごい便利だった。

疑問

(1) models/ 以下に新しくできた Shopify の stg/int/mart モデルは増えてないけどいいんだよね...?

fivetran/shopify パッケージが model の定義を持ってるから、自分のコードには定義は追加されないってこと? といったん納得してみた。dbt docs の方には反映されてたのでよさそう

(2) project, database/schema がめちゃめちゃ分かれちゃったんだけど

project に関しては、Shopify だけで shopify_sourceshopify でわかれちゃったし、Snowflake 側も

  • fivetran_data.shopify schema 以下に source のテーブル
  • pc_dbt_db Database の中に dbt_sobacha dbt_sobacha_shopify dbt_sobacha_stg_shopify schema の中に各 stg/int/mart モデル用のテーブル

という感じになって、これでいいんだっけ...? という気持ちになった。とはいえ、 fivetran/dbt の docs を見ると project は source とそれ以外で分かれているのでそういうものなのかも?

2. semantic layer の設定

このガイドの step 8 から進める感じにした。step 8 では、 orders と customers テーブルを作っていて、これを staging 層から自分で作るか、それとも fivetran/shopify パッケージが作ってくれた mart テーブルの上に作るかで迷ったんだけど、以外と order_date とか total を計算するのが大変そうだったので後者にした。

ガイドにある fct_ordersdim_customers のモデルを作ったあとは、ガイドの metrics ファイルをコピペした。

疑問というか

コピペして、だいたいは BI で表示するところまで行けるんだけど、 large_orders measure に関してだけこういうエラーが出た。疲れてこれはトラシューしてない。たぶん Dimension('order_id__order_count_dim') みたいなやつが怪しいと思うんだけど...

3. semantic layer の接続設定

とりまガイドの続きとクラメソさんの記事を読めばいいんだけど、なんかいろいろうまくいかなくて大変だった。

やらかし 1: クエリが "No active warehouse selected" になる

起こったことは、接続はうまくいって steep 上に metrics 一覧は出るんだけど、 metrics のグラフを表示しようとするとエラーメッセージが出る、っていう。エラー画面はこう↓

steep のエラーメッセージは普通に謎だけど、 Snowflake 側のクエリ実行履歴を見ると、

No active warehouse selected in the current session. Select an active warehouse with the 'use warehouse' command.

ということだった。ようは use warehouse hoge_warehouse できてないよ、ってことっぽくて、SL の接続用につくった Snowflake user を確認すると、 default warehouse が設定されてなかった。

やらかし? 2: user の設定を変えても反映されない

ということで SL 用 user に default warehouse をつけたんだけど、引き続き同じエラーが出続けてガン萎えした。明確に解決してないんだけど、

  • 別のユーザをつくる
  • dbt Cloud で Service Token を発行しなおす
  • Google Sheets でも試して同じエラーになることを確認 (i.e. steep 固有の問題でない)

あたりを試したら、時間経過のせいかとりあえず別のユーザで接続できるようになった。どっかでキャッシュしてるのかな...

なんやかんやで BI は表示できた

わーい。前のステップでバリ疲れたので、もう表示できただけで嬉しかった。

こういう↓感じで time grain を daily/monthly とかで切り替えられるし 28 days の moving average とかも使えるので、うまく導入できれば Spreadsheet でサグラダファミリアを作らなくてすみそう。

おまけ: Snowflake で readonly/select 権限をつける

User/Role を作るにあたって結局なんの権限をどのレイヤーにつければいいのか混乱したんだけど、ここ↓がわかりやすかった。

たとえば、 my_db の my_schema 以下にあるすべての table/view に権限をつけたい場合は

grant usage on database my_db to role hoge_role;
grant usage on schema my_schema to role hoge_role;

grant select on all tables in database my_db to role hoge_role;
grant select on future tables in database my_db to role hoge_role;
grant select on all views in database my_db to role hoge_role;
grant select on future tables in database my_db to role hoge_role;

だと思う、試行錯誤して発行してたのであとで修正するかも。なんか、今回みたいな database も schema もいっぱいあるよ、ってときだとちょっとめんどくさいな... と思った。

やってみた: Shopify → Fivetran → Snowflake → dbt Cloud → steep PART 1

pre-amble

「Shopify のデータを Fivetran で ETL して、 semantic layer を使って BI にデータ出してみよう」というお題をいただいたのでやってみた。今回の記事は dbt Cloud に持ってくとこまで。

特別なことをしてないのでわりとチュートリアル通りだけど、概念の整理とかトラシューとかを書いていく。

ETL セットアップの流れとしてはこんな感じ↓

  1. Fivetran に Snowflake destination を作成
  2. Shopify の Fivetran connector を作る
  3. Snowflakeスキーマ確認
  4. dbt Cloud の接続

Snowflake 入門

Fivetran に destination を作る前に、そもそも Snowflake が初見なので 30分くらいで入門してみた。30分は盛ってるかもだけど、半日はかけなかった。

アカウント作成

Snowflake は free trial ができるので、とりあえずアカウントを作る。すると、自動的に Getting started 系のデータや Worksheets っていうやつを作ってくれるので、そこで UI の操作とか概念を学べる。

ちなみに、アカウント選択時に「よく使う言語はなんですか?」と聞かれて、そこで選択した言語に応じてサンプル Worksheets を作ってくれる。自分は PythonSQL を選択したので、こういう感じで作ってもらえた↓

各種リソース

自分の理解だと、リソースはだいたいこんな感じ↓

  • Worksheets
  • Data
  • Warehouse
  • Users, Roles

Worksheets

Worksheets っていうのは「ファイル」的なもので、 Python, SQL, Java など各種言語で記述できる。

驚きポイントとしては、 Worksheet 1枚で ETL が実現できることで、 Getting started の Worksheet だと S3 の public データから Snowflake にデータを流すことまでやってた。

SQL の Worksheet のコードを抜粋するとこうなる↓

-------------------------------------------------------------------------------------------
    -- Step 3: To connect to the Blob Storage, let's create a Stage
        -- Creating an S3 Stage: https://docs.snowflake.com/en/user-guide/data-load-s3-create-stage
-------------------------------------------------------------------------------------------

---> create the Stage referencing the Blob location and CSV File Format
CREATE OR REPLACE STAGE tasty_bytes_sample_data.public.blob_stage
url = 's3://sfquickstarts/tastybytes/'
file_format = (type = csv);

---> query the Stage to find the Menu CSV file
LIST @tasty_bytes_sample_data.public.blob_stage/raw_pos/menu/;


-------------------------------------------------------------------------------------------
    -- Step 4: Now let's Load the Menu CSV file from the Stage
        -- COPY INTO <table>: https://docs.snowflake.com/en/sql-reference/sql/copy-into-table
-------------------------------------------------------------------------------------------

---> copy the Menu file into the Menu table
COPY INTO tasty_bytes_sample_data.raw_pos.menu
FROM @tasty_bytes_sample_data.public.blob_stage/raw_pos/menu/;

Data

データ系のリソースは階層になってて、 Database > Schema > Table という感じ。たとえば、下の Fivetran → Shopify 用の Database だと、

  • FIVETRAN_DATA という Database の中に
  • SHOPIFY という Schema があって、
  • その中に ORDER CUSTOMER PRODUCT という各種テーブルがある、という構造になる。

自分は BigQuery の2層構造に慣れていたので Snowflake の3層構造に若干戸惑ったけど、 SQL を書くときは

select count(*) from "FIVETRAN_DATA"."SHOPIFY"."ORDER"

と、 BQ の <project>.<dataset>.<table or view> というのとあんまり変わらないのでそこまで違和感はないかも?

Warehouse

ここが一番わかりにくかったんだけど、 Warehouse とはざっくりいうと "compute server" である、という Reddit の投稿がわかりやすかった。

In summary, a warehouse is just a computer server. In Snowflake you need one because a warehouse does the work of running a query.

Databases and schemas are just an organizational structure for tables. There are other things that can live inside schemas, like views and UDFs. You need to know about this structure because Snowflake wants you to be specific about which tables to access. A well used Snowflake can have thousands of tables, so this structure does help.

https://www.reddit.com/r/snowflake/comments/13xjxly/can_someone_please_explain_accessing_warehouses/?rdt=52733

Warehouse の画面は↓の2枚のような感じで、たしかにメニューも Data のところじゃなくて Admin のところにあるし、 "Size" という項目もあるので、「サーバっぽい」っていうのは納得できた。名前的に最初 BQ の dataset なのか...? って思ってたけど全然違った、ワラ。

Users/Roles

ここは割と名前の通りで、ブラウザでログインしている自分用の User や、 Fivetran とか dbt に接続する User がいる。 Role も名前の通り User に対して付与するもの。

ちなみに、 Roles の画面だとこういうグラフも見れて感動した↓

Users, Roles & Privileges with Worksheets

privilege って単語、毎回綴りを毎回間違えるんだけど

Snowflake の Worksheet 画面では右上で実行する Role と Warehouse を設定できて、これを Context とよぶ。実行する「権限」と実行する「環境」を選んでるっていうのが自分の理解。

Role は右上から選ぶ以外にも、 Worksheet の中で

use role hoge_role;
create database hoge_db;

のように途中で role を切り替えることもできるし、 use warehouse hoge_warehouse も構文としてあるので、 warehouse も切り替えられそう (実際 Getting started だと use roleuse warehouse もあった)。

User には Role を付与し、 Role には Privilege を付与することで誰が何をできるっていうのを制御できて、たとえば

grant select on database hoge_db to role hoge_role;

のような SQL 文で管理できる。次の ETL の設定ではこの権限周りが結構だるかった。

steps

1. Snowflake destination として作成

基本 Fivetran のガイド通りにつくるんだけど、ちょくちょくトラブって、 Fivetran の提供する Worksheet 用のコードをいじる必要があった。

ガイド: https://fivetran.com/docs/destinations/snowflake/setup-guide

今回はとりあえずつなぐことが目的だったので、プライベート接続的なセキュアにするオプションは全部すっとばしたところ、ガイドにある Worksheet コードを動かすところがここの作業の9割だった。

トラブったところは GRANT 系の SQL で、適宜 use role の位置をずらしたり、実行している Role へ GRANT を発行したりしたのだが、正しいのかちょっと怪しいので作業内容は割愛 (次回整理するという目的もこめつつ...)。

2. Shopify connector を作成

これもガイド通りに Shopify の自分のストアに Fivetran app をインストールすれば OK。ポイントとして、接続する前に Shopify にブラウザでログインしておくとスムーズかも。画面遷移的には

Shopify connector を新しくつくる画面
→ install Fivetran app のリンクをクリック
→ Shopify に遷移
→ また Shopify connector を新しくつくる画面

という流れなので、若干 janky ではあった。

3. dbt Cloud プロジェクトの作成

多分 dbt core でもできるような気がするが、そっちは前にやったことがあるのでマネージド版を使ってみたくて dbt Cloud の方にした。

基本は↓の Quickstart に沿いつつ、構成が若干違うので適宜アレンジする。
https://docs.getdbt.com/guides/snowflake

Quickstart の 1-3 はSnowflake にデータをいれる話なので、 4 の "Connect dbt Cloud to Snowflake" からやる。接続には manual と Partner Connect っていう自動で接続設定を作ってくれる機能を使う2つの方法があって、今回は Partner Connect を使ってみた。

Partner Connect は Snowflake ↔ dbt の partner で、 Snowflake 側の market place で dbt を選択すると、自動で dbt Cloud に Snowflake の接続設定を持ったアカウントを作ってくれる。

この「アカウント」っていうのがポイントで、実はもともとメールアドレス1で Snowflake 、メールアドレス2で dbt Cloud のアカウントをそれぞれ持っていて、てっきり Snowflake で Partner connect をクリックしたら dbt Cloud の認証画面に行くのかと思ったんだけど、いきなり Snowflake のメールアドレス1で dbt Cloud のアカウントごと作成されて若干戸惑った。まあ、別にどちらのメアドもプライベート用なのでいいんだけど一瞬混乱した。

あとは、 QuickStart だと Partner Connect をつくるときに dbt が Snowflake の Database から読めるように権限をつけているんだけど、自分はそれをしていなかったので↓の GRANT を発行した。

grant usage on database fivetran_data to pc_dbt_role;
grant usage on schema fivetran_data.shopify to pc_dbt_role;
grant select on all tables in schema fivetran_data.shopify to pc_dbt_role;
grant select on future tables in schema fivetran_data.shopify to pc_dbt_role;

ポイントは

  • ALL TABLES = 今存在するテーブル、 FUTURE TABLES = これから作られるテーブル という理解なので両方発行が必要っぽい
  • database/schema/table のすべての階層に権限が必要で、たとえば schema に権限がないと SELECT は発行できない

以上が終わったら、 select count(*) from "FIVETRAN_DATA"."SHOPIFY"."ORDER"Snowflake → dbt Cloud IDE で実行できるか確認する。

感想

Snowflake さわれてよかった!次は Fivetran の Shopify 用 dbt package を入れたり semantic layer やるけどこっちも大変そう。ひえー