雰囲気データサイエンティストの備忘録

Atmosphere Data Scientist's Memorandum


webアプリからプロキシ経由でアプリケーションサーバーにアクセスする
NodeJS

概要

nodejsでのアプリサーバーへの接続に苦労したのでメモ。

問題

以下のようなWEBサーバー,アプリサーバー,データベースの3層構造をkubernetesで構築しようとしました。
architecture

アプリ層にはClusterIPサービスを設定し,webサーバーからのリクエストを受け付けるようになっています。
以下のようにaxiosでpostを投げるようにコードを書きました。

const res: AxiosResponse<ResponseType>
                = await axios.post('http://flask-app-service:5000/predict', {
                    input: [parseFloat(inputValue)]
                }, {
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    timeout: 3000,
                });
            console.log(res.data);

ブラウザでwebサーバーにアクセスし,リクエストを投げようとするとエラーが出てしまいます。
unhealty

問題の切り分け

デバッグのために,kubectlでwebサーバーのPodにアクセスします。

kubectl exec -it frontend-******* -- sh

webサーバーの中から手動でリクエストを投げてみる。
期待通りのレスポンスが返ってくるので,接続経路は確保できているようです。

curl http://flask-app-service:5000/predict -H "Content-Type: application/json" -d '{"input": [10.0]}'
# レスポンス
# {
#  "input": [
#    [
#      10.0
#    ]
#  ],
#  "prediction": [
#    21.0
#  ],
#  "timestamp": "2024-07-12T07:51:42.221945",
#  "uuid": "e9fb64c6-d05d-406b-8cc1-6443563c6c74"
#}

であれば,typescriptのコードが間違っているのかと思い,nodejsをインタラクティブシェルで実行してみます。
こちらも正常なレスポンスが返ってきます。

nodejs
以下はnodejsのインタラクティブシェル上で実行
const { default: axios, AxiosResponse } = await import("axios");

await axios.post('http://flask-app-service:5000/predict', {
                    input: [10.0]
                }, {
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    timeout: 3000,
                });

/*
{
  (...省略...)
  data: {
    input: [ [Array] ],
    prediction: [ 21 ],
    timestamp: '2024-07-12T07:59:12.169473',
    uuid: '7c22127e-7db1-4d3f-b949-343cd3cf97b9'
  }
}
*/

webサーバーに入って実行したコマンドと全く同じコードなのにブラウザから実行できないのはなぜ?

原因

原因はいたって初歩的で,「フロントエンドの処理はユーザー側(ブラウザ側)で実行されるため」でした。
つまり,WEBサーバー→アプリサーバーの経路はできていても,ブラウザ→アプリサーバーの経路が作られていないために,失敗してします。

対策

対策は2つあります。

  1. アプリサーバーに対してブラウザからリクエストを投げられるように,アプリサーバーを公開する。
  2. nodejsのプロキシを用いて,ブラウザからのリクエストをwebサーバーを介してアプリサーバーに送信する。

1の方法は単純ですが,アプリサーバーを公開してしまうのでセキュリティの懸念が一つ増えます。
今回は2の方法で対応しました。

以下のように,vite.config.jsに,
"http://flask-app-service:5000"をターゲットにして,"/api"という名前のプロキシを作成します。

vite.config.js
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'

export default ({ mode }) => {
  const env = loadEnv(mode, process.cwd(), '')

  return defineConfig({
    plugins: [react()],
    server: {
      watch: {
        usePolling: true
      },
      port: 3000,
      proxy: {
        '/api': {
          target: env.VITE_FLASK_APP_URI,
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, ''),
        },
      },
    },
    preview: {
      port: 4173
    }
  });
}
manifest.yaml
(...省略...)
        env:
        - name: VITE_FLASK_APP_URI
          value: "http://flask-app-service:5000"
(...省略...)

typescriptから実行すると,今度は正常にレスポンスが返ってきます。

const res: AxiosResponse<ResponseType>
                = await axios.post('api/predict', {
                    input: [parseFloat(inputValue)]
                }, {
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    timeout: 3000,
                });

healty