1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloudflare Workersに環境変数が正常に反映されないときに確認すべき設定と、Wranglerの仕様について

1
Posted at

はじめに

自分の個人開発アプリで、ログイン後のダッシュボードで統計情報を取得しようとしたら TypeError: Failed to fetch が発生しました。開発環境と本番環境で原因がまったく別だったのと、Wranglerの環境変数の挙動に誤解があったのとで、地味に時間を溶かしたのでまとめておきます。

問題

開発環境(localhost)と本番環境、それぞれで別の原因が絡んでいました。

開発環境(localhost)

まずはNetworkタブを見ると net::ERR_CONNECTION_REFUSED が出ていました。これは単純に、APIサーバー(wrangler dev のデフォルトポート8787)を起動し忘れていただけでした。

本番環境

本番URL(以下、Webアプリを https://app.example.com、APIを https://api.example.com とします)にアクセスすると、コンソールに以下のエラーが出ていました。

Access to fetch at 'https://api.example.com/...' from origin 'https://app.example.com' has been blocked by CORS policy: ... No 'Access-Control-Allow-Origin' header is present

APIサーバー側は hono/cors を使っていて、CORS_ORIGIN_WEBCORS_ORIGIN_EXTENSION という2つの環境変数から許可オリジンを組み立てる実装になっていました。wrangler.jsonc はこんな感じで、トップレベルの vars(開発用)と env.production.vars(本番用)を分けて定義していました。

{
  "vars": {
    "CORS_ORIGIN_WEB": "http://localhost:5173"
  },
  "env": {
    "production": {
      "vars": {
        "CORS_ORIGIN_WEB": "https://app.example.com"
      }
    }
  }
}

一見よさそうに見えるのですが、実際に本番Workerへデプロイされていたのはトップレベルの(=開発用の)vars の方でした。つまり env.production.vars に書いた本番オリジンはデプロイに反映されておらず、本番オリジンがCORS許可リストに入っていなかった、というオチです。

Chrome拡張機能からのアクセスについて

このAPIはChrome拡張機能からも呼ばれる作りになっていて、CORS_ORIGIN_EXTENSION が設定されていれば許可オリジンに追加される実装でした。.dev.vars.example にも

CORS_ORIGIN_EXTENSION=chrome-extension://<拡張機能ID>

という記載があり、ローカルでは .dev.vars で個別に設定する想定になっていました。ただし本番の wrangler.jsoncvars には CORS_ORIGIN_EXTENSION が入っていませんでした。

これには経緯があって、初版リリース時はストアの審査に提出する前で拡張機能IDがまだ確定しておらず、デプロイ時点では値を用意できませんでした。そのため先にAPIだけデプロイしておき、審査が通って拡張機能IDが判明してから、Cloudflareのダッシュボード上で手動で環境変数を追加する、という順序で対応していました。ここまでは仕方のない対応だったのですが、その後 wrangler.jsonc 側への追記を忘れてしまい、ダッシュボードにしか値が存在しない状態のまま残っていました。

解決方法

本番環境のCORS修正

環境ごとに vars を分ける構成自体をやめて、wrangler.jsoncenv.production ブロックを廃止し、本番用の値をトップレベルの vars に統合しました。

{
  "vars": {
    "CORS_ORIGIN_WEB": "https://app.example.com",
    "CORS_ORIGIN_EXTENSION": "chrome-extension://<拡張機能ID>"
  }
}

ローカル開発用の値(CORS_ORIGIN_WEB=http://localhost:5173 など)は .dev.vars に寄せる運用にしました。.dev.varswrangler dev 実行時にのみ読み込まれ、vars の値をローカルでだけ上書きしてくれるので、本番の wrangler deploy には一切影響しません。

wrangler deploy はデフォルトで環境変数を上書きする

この対応をする過程で、Wranglerの環境変数まわりについていくつか事実を確認しました。

  • wrangler deploy はデフォルトで、Cloudflareダッシュボード側で手動変更した環境変数をデプロイ時に上書きする。この挙動を抑制したい場合は --keep-vars フラグを使う。ただしsecretsはデプロイで削除されない。
  • .dev.varswrangler dev 実行時にのみ読み込まれ、vars の値をローカルでだけ上書きする。wrangler deploy には一切影響しない。

真犯人: Cloudflare Workers Buildsはpackage.jsondeployスクリプトを使っていなかった

「トップレベルの varsenv.production.vars はどう使い分けるべきか」を公式ドキュメントで調べると

  • env.<name> の設定は wrangler dev/deploy--env <name> を明示的に渡したときだけ反映される
  • named environmentを定義すると別Workerが作られる

といった説明が出てきます。

しかし手元の環境では --env production の有無で挙動が変わっている様子がなく、ドキュメント通りには動いていませんでした。

決定打になったのは、GitHubのmainブランチへのマージをトリガーに実行される、Cloudflare Workers Builds(GitHub連携によるデプロイ)のビルドログでした。

Executing user deploy command: npx wrangler deploy

package.jsondeploy スクリプトには wrangler deploy --minify --env production と書いていたのに、実際に実行されていたのは npx wrangler deploy というコマンドでした。
wrangler自身も、この時点で警告を出していました。

▲ [WARNING] Multiple environments are defined in the Wrangler configuration file, but no target environment was specified for the deploy command.

  To avoid unintentional changes to the wrong environment, it is recommended to explicitly specify the target environment using the `-e|--env` flag.
  If your intention is to use the top-level environment of your configuration simply pass an empty string to the flag to target such environment. For example `--env=""`.

実際にデプロイされたBindingsも、トップレベルの値になっていました。

env.CORS_ORIGIN_WEB ("http://localhost:5173")      Environment Variable

よくよく調べてみると、Cloudflare Workers Builds の公式ドキュメントにも明記されている仕様でした。

The deploy command lets you set the specific Wrangler command used to deploy your Worker. Your deploy command will default to npx wrangler deploy but you may customize this command.

If you have added a Wrangler deploy command as a script in your package.json, then you can run it by setting it as your deploy command. For example, npm run deploy.

つまり、Cloudflare Workers Buildsは package.jsondeploy スクリプトを自動では読みにいかず、Cloudflareダッシュボード側(Settings > Build)の「Deploy command」にデフォルトで設定されている npx wrangler deploy がそのまま実行される仕組みでした。

package.jsondeploy スクリプトを使わせたい場合は、ダッシュボード側のDeploy commandを明示的に(例えば pnpm deploy のように)変更しておく必要があります。

ローカルのターミナルで pnpm deploy を叩けば --env production は効きますが、GitHub連携での自動デプロイでは全く別のコマンド(npx wrangler deploy)が動いていたので、--env をいくらpackage.json側で調整しても無関係だった、というのが今回の真相でした。

ちなみに wrangler deploy コマンドの公式リファレンスを見ても、--env パラメータの説明は "Perform on a specific environment." の一文だけで、Workers Buildsがpackage.jsonのdeployスクリプトを使わずに独自のコマンドを実行する、という点には触れられていません。

おわりに

CORSエラーは「サーバー側の設定が間違っている」と決め打ちしがちですが、今回は wrangler.jsonc 上で環境ごとに vars を分けていたことに加えて、Cloudflare Workers Buildsが package.jsondeploy スクリプトを使わず、ダッシュボード側で別途設定された「Deploy command」を実行していたことが根本原因でした。
ローカルでいくら --env を検証しても、実際のデプロイに使われるコマンドとは無関係だったわけです。

設定ファイル上は正しく見えていても、実際にどのコマンドが・どの設定で動いているかはビルドログまで見ないとわからない、という点に気づきにくさがありました。

production用のWorkerを1つしか持たない個人開発規模なら、環境を分けずに1つの vars にまとめてしまえば、Deploy commandに --env を意識する必要もなくなるので、シンプルで安全かもしれません。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?