概要
nodejsでのアプリサーバーへの接続に苦労したのでメモ。
問題
以下のようなWEBサーバー,アプリサーバー,データベースの3層構造をkubernetesで構築しようとしました。
アプリ層には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サーバーにアクセスし,リクエストを投げようとするとエラーが出てしまいます。
問題の切り分け
デバッグのために,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
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つあります。
- アプリサーバーに対してブラウザからリクエストを投げられるように,アプリサーバーを公開する。
- nodejsのプロキシを用いて,ブラウザからのリクエストをwebサーバーを介してアプリサーバーに送信する。
1の方法は単純ですが,アプリサーバーを公開してしまうのでセキュリティの懸念が一つ増えます。
今回は2の方法で対応しました。
以下のように,vite.config.js
に,
"http://flask-app-service:5000"をターゲットにして,"/api"という名前のプロキシを作成します。
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
}
});
}
(...省略...)
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,
});