Azure Container Apps 上で Odoo を公開しようとすると、HTTP(8069) と WebSocket/longpolling(8072) を同居させたい場面がよくあります。しかし単純に Ingress と NGINX を組み合わせただけでは、/ は表示できるのに /websocket だけタイムアウトする、という状態にハマりがちです。この記事では、その原因と Azure Container Apps らしいスマートな解決パターンを詳しく解説します。
Azure Container Apps 上での Odoo 公開シナリオ
まずは、この記事で想定している構成を整理します。
| コンポーネント | 役割 | ポイント |
|---|---|---|
| NGINX ゲートウェイ (ACA) | フロントのリバースプロキシ / TLS 終端 / 複数ドメイン対応 | Azure Container Apps 上で稼働。 各バックエンド ACA へ内部トラフィックを振り分け |
| Odoo アプリ (ACA) | 業務アプリケーション本体 | HTTP: 8069 / WebSocket(Longpolling): 8072 を使用 |
| Azure Container Apps 環境 | 同一 VNet 相当の論理境界 | 同じ環境内の ACA 同士はアプリ名で内部 DNS 解決される |
Odoo 側のポート利用は、おおよそ次のようになっています。
| ポート | 用途 | プロトコル | クライアントからの主なアクセス |
|---|---|---|---|
| 8069 | 通常の HTTP アプリ (Web UI, API) | HTTP/1.1 | ブラウザから https://example.com/ などでアクセス |
| 8072 | gevent / longpolling / Live Chat 用 WebSocket | WebSocket (HTTP Upgrade) | ブラウザから /websocket や /longpolling/xxx として内部的に利用 |
ユーザー体験としては「全部 https://example.com/ 以下」で完結させたいのに、実際の Odoo コンテナでは 2 つのポートを使う、というギャップがハマりポイントになります。
なぜ / は表示できるのに /websocket はタイムアウトするのか
症状として多いのは次のパターンです。
/(トップページ)は問題なく表示できる- しかし
/websocketや Live Chat がタイムアウト / 接続失敗する - NGINX 側で
proxy_pass http://odoo-app:8072などに変えても 404 / タイムアウトになることがある
ここでカギになるのが「Azure Container Apps の Ingress 仕様」です。
Azure Container Apps Ingress の仕様をおさらい
Azure Container Apps の HTTP Ingress は、ざっくり次のような動きをします。
| 設定項目 | 役割 | 重要なポイント |
|---|---|---|
transport: http | HTTP/HTTPS トラフィックを扱うモード | 80/443 で受けたリクエストをコンテナの targetPort に転送 |
external: true/false | 外部公開 or 内部のみ | true のときは FQDN が払い出される |
targetPort | コンテナが LISTEN しているポート | HTTP Ingress から流れるのは 1 つの targetPort のみ |
ここが Kubernetes Ingress と感覚的に違うところで、「パスごとに違うコンテナポートへ振り分ける」といったことは、Azure Container Apps の HTTP Ingress 単体ではできません。
つまり、Odoo コンテナの Ingress 設定で targetPort: 8069 にしている場合、FQDN や環境の外部エンドポイントから来た HTTP トラフィックは必ず 8069 にのみ流れます。/websocket だからといって 8072 には届きません。
そのため、
- FQDN 経由で
/にアクセス → 8069 に届くので OK - 同じ FQDN 経由で
/websocketにアクセス → それでも 8069 に届いてしまい、期待する 8072 に届かない
という現象が発生します。
解決策の全体像:8072 を「追加 TCP ポート」として内部公開
そこで使うのが、Ingress の additionalPortMappings(追加 TCP ポート)機能です。これを利用すると、一つの Container App から複数のポートを露出させることができます。
今回のゴールは次の形です。
- Odoo コンテナの HTTP Ingress は従来どおり
targetPort: 8069 - 8072 は追加 TCP ポートとして同一 ACA 環境内にだけ公開(
external: false) - フロントの NGINX からは、内部 DNS で
http://odoo-app:8072へ直接プロキシ
構成イメージをテキストで描くと次のようになります。
- クライアントブラウザ → NGINX ゲートウェイ (HTTPS)
- NGINX ゲートウェイ
/→http://odoo-app:8069/websocket→http://odoo-app:8072
- Odoo コンテナ (ACA)
- HTTP: 8069(Ingress の
targetPort) - WebSocket: 8072(
additionalPortMappingsで内部公開)
- HTTP: 8069(Ingress の
ポイントは「WebSocket 用の 8072 を外部には出さず、同じ ACA 環境の中だけで使う」というところです。これにより、セキュアかつシンプルに HTTP と WebSocket を分離できます。
Odoo コンテナアプリ側の設定:追加 TCP ポート 8072 を公開する
まず Odoo コンテナアプリの Ingress で 8072 を追加 TCP ポートとして公開します。
YAML (ARM テンプレート) の例
典型的な Ingress 定義は次のようになります。
properties:
configuration:
ingress:
external: false # Odoo 自体は内部専用(FQDN は不用)
transport: http # メインは HTTP Ingress
targetPort: 8069 # Odoo の HTTP ポート
additionalPortMappings:
- targetPort: 8072 # Odoo の WebSocket / gevent
exposedPort: 8072 # 環境内で露出するポート
external: false # 外部には公開しない
この設定を行うと、同じ Container Apps 環境内の他のアプリからは、
http://odoo-app:8069→ Odoo HTTPhttp://odoo-app:8072→ Odoo WebSocket / longpolling
という形でアクセスできるようになります(odoo-app は Container App 名)。
Azure Portal から設定する場合
Azure Portal の GUI で作業する場合は、概ね次の手順になります。
- Odoo の Container App を開く
- [Ingress] ブレードを開く
- Ingress は 「内部」(Internal) を選択し、ターゲットポートに 8069 を指定
- 画面下部の「Additional TCP ports(追加 TCP ポート)」セクションを展開
- Target port に 8072、Exposed port に 8072、Ingress traffic を「Internal only」に設定
- [Save] して、新しいリビジョンが作成・アクティブになることを確認
リビジョンが古いままだと、追加 TCP ポートの設定が反映されていないことがあるため、「アクティブなリビジョン」がどれかを常に確認しておくと安全です。
Odoo 側の確認ポイント
Odoo コンテナの中では、次のような状態になっていることを確認しておくと安心です。
- プロセスが
8069と8072の両方で LISTEN しているnetstat -tlnpやss -tlnpなどで確認
- Odoo の設定で longpolling / gevent が有効になっている
--longpolling-port=8072を指定して起動している など
ここが間違っていると、Ingress や NGINX の設定が正しくても 8072 側が応答せず、結果的にタイムアウトします。
NGINX ゲートウェイ側の設定:内部 DNS で 8069 / 8072 に振り分ける
次に、フロントの NGINX ゲートウェイ (Container App) の設定です。ポイントは次の 3 つです。
- 同一 Container Apps 環境内では、アプリ名で内部 DNS 解決できる
- Docker 固有の
resolver 127.0.0.11;は不要(ACA では使わない) /と/websocketでバックエンドのポートを分ける
NGINX 設定例
upstream odoo_http {
server odoo-app:8069; # Azure Container Apps 内部 DNS
}
upstream odoo_ws {
server odoo-app:8072; # 追加 TCP ポート
}
map $http_upgrade $connection_upgrade {
default close;
websocket upgrade;
}
server {
listen 80; # 実環境では 443 で TLS 終端する想定
server_name example.com; # Odoo 用のドメイン名
# 通常の Odoo HTTP
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://odoo_http;
}
# Odoo の WebSocket / longpolling
location /websocket {
proxy_http_version 1.1;
# WebSocket 必須ヘッダー
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# Odoo 側で必要になることがあるヘッダー
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Origin $http_origin;
proxy_connect_timeout 5s;
proxy_read_timeout 3600s;
proxy_send_timeout 120s;
proxy_pass http://odoo_ws;
}
}
ここでは説明のために upstream を使いましたが、
proxy_pass http://odoo-app:8069;proxy_pass http://odoo-app:8072;
のように直接書いても問題ありません。
WebSocket 用ヘッダーの意味
| ヘッダー | 値 | 役割 |
|---|---|---|
Upgrade | $http_upgrade | HTTP から WebSocket へのプロトコルアップグレードを伝える |
Connection | $connection_upgrade | WebSocket 用の接続が継続されることを示す |
Origin | $http_origin | ブラウザからの Origin を Odoo に伝える。CSRF/セキュリティチェックで利用される場合あり |
X-Forwarded-Proto | https | バックエンドに対して「クライアントは HTTPS でアクセスしている」ことを伝える |
特に NGINX を TLS 終端として使う場合、X-Forwarded-Proto や Host を正しく渡さないと、Odoo 側でリダイレクトがループしたり、外部 URL が http:// になってしまうことがあるので注意してください。
内部 DNS と名前解決のポイント
Azure Container Apps では「同一 Container Apps 環境内」でのみ、アプリ名による内部 DNS 解決が機能します。
| 名前解決のパターン | 例 | 動作 |
|---|---|---|
| アプリ名 | odoo-app | 同じ Container Apps 環境内から参照すると内部 IP に解決される |
| FQDN (外部 Ingress) | odoo-app.blue123...japaneast.azurecontainerapps.io | HTTP Ingress を経由して targetPort のみ に流れる |
<aca-name>.internal 形式 | odoo-app.internal など | 環境や設定により解決しないことがある。基本的には非推奨 |
この構成では、ゲートウェイ NGINX と Odoo コンテナは同じ Container Apps 環境内に配置するのが前提です。別環境に配置してしまうと、アプリ名での内部 DNS 解決が効かず、getent hosts odoo-app が失敗します。
動作確認:ゲートウェイコンテナからの疎通チェック
設定を行ったら、NGINX ゲートウェイのコンテナ内から次のようにテストすると安心です。
# 1. 内部 DNS でアプリ名が引けるか
getent hosts odoo-app
# 2. 追加 TCP ポート 8072 に接続できるか
nc -vz odoo-app 8072
# 3. HTTP レベルでの応答を確認
curl -i http://odoo-app:8072/
ここで 8072 への nc / curl が成功していれば、追加 TCP ポートとしての公開はできています。あとは NGINX の location 定義と WebSocket ヘッダーの問題に絞って原因を切り分けていけます。
よくあるトラブルと原因切り分け
実際に運用していると、次のようなエラーに出会うことが多いです。
| 症状 | 考えられる原因 | 確認ポイント |
|---|---|---|
| / は表示できるが、/websocket がタイムアウト | Odoo の 8072 が ACA で公開されていない / NGINX が 8069 に向いている | additionalPortMappings の設定有無、proxy_pass の宛先ポートを確認 |
| /websocket にアクセスすると 404 | Odoo 側で gevent / longpolling が有効になっていない | Odoo の起動オプション・ログを確認し、8072 で LISTEN しているか確認 |
| NGINX エラーログに「host not found in upstream」 | アプリ名が誤っている / 別環境に配置している | getent hosts odoo-app で名前解決できるか確認 |
| 接続直後に 502 Bad Gateway | WebSocket ヘッダー不足 / タイムアウト値が短すぎる | Upgrade / Connection ヘッダー、proxy_read_timeout を確認 |
特に「FQDN をそのまま NGINX の proxy_pass に使っている」ケースは要注意です。Azure Container Apps の FQDN はあくまで HTTP Ingress に対するもので、targetPort にしか流れないため、8072 には絶対届きません。
セキュリティと運用面での注意点
Odoo と WebSocket を Azure Container Apps 上で公開する際は、セキュリティと運用面でもいくつかポイントがあります。
8072 は必ず内部のみ (external: false) にする
- WebSocket 用ポートはインターネットに直接出さず、Container Apps 環境内からのみアクセス可能にする
- 外部からは必ず NGINX ゲートウェイを経由させる(WAF やログ、TLS 終端を集約できる)
ヘッダーと TLS の整合性
- クライアントは HTTPS でアクセス → NGINX で TLS 終端 → Odoo へは HTTP でプロキシ
- この場合、Odoo 側に
X-Forwarded-Proto=httpsを渡しておかないと、URL 生成やリダイレクトが http:// になりがち - HSTS を使う場合は、必ず NGINX 側でヘッダーを付与する
スケールアウトと WebSocket
Odoo コンテナをスケールアウトすると、WebSocket 接続が複数のレプリカに分散します。Azure Container Apps は内部的にロードバランサーを持っているため、基本的にはそのままでも問題ありませんが、以下の点に注意してください。
- Odoo の設定で workers / gevent ワーカー数を適切に調整する
- 長時間接続する WebSocket が多い場合、コンテナのメモリ・CPU を余裕を持って割り当てる
- スケーリングルール(HTTP/TCP ベース)を使う場合は、閾値とクールダウン時間を慎重に設定する
応用パターン:マルチドメイン / マルチ Odoo 構成
同じパターンは、マルチドメイン・マルチテナント構成にも応用できます。
- Odoo ごとに Container App を分ける
odoo-tenant-a(8069 / 8072)odoo-tenant-b(8069 / 8072)
- NGINX ゲートウェイでは
server_nameごとに upstream を切り替えるa.example.com→odoo-tenant-a:8069 / :8072b.example.com→odoo-tenant-b:8069 / :8072
この場合も、「各 Odoo アプリで 8072 を追加 TCP ポートとして内部公開」「NGINX からはアプリ名で解決してポートを分ける」という基本パターンはまったく同じです。
構成のチェックリスト
最後に、Azure Container Apps で Odoo の HTTP(8069) と WebSocket(8072) を同居させる際のチェックリストをまとめておきます。
| 項目 | 確認内容 |
|---|---|
| Odoo Ingress (HTTP) | external: false(内部のみ) transport: http targetPort: 8069 |
| 追加 TCP ポート | additionalPortMappings に 8072 を追加 targetPort: 8072, exposedPort: 8072 external: false にしてインターネットには公開しない |
| NGINX からの接続 | 同じ Container Apps 環境内に配置している proxy_pass http://odoo-app:8069(/ 用) proxy_pass http://odoo-app:8072(/websocket 用) |
| WebSocket ヘッダー | Upgrade / Connection ヘッダーを適切に設定 必要に応じて Origin を Odoo に渡す |
| 内部疎通テスト | getent hosts odoo-app が成功する nc -vz odoo-app 8072 で接続できる curl -i http://odoo-app:8072/ で応答がある |
| Odoo プロセス | 8069 / 8072 の両方で LISTEN gevent / longpolling が有効 |
まとめ:Azure Container Apps で HTTP + WebSocket を安定稼働させる「型」
Azure Container Apps の HTTP Ingress は 1 つの targetPort にしかトラフィックを流せないため、Odoo のように 8069 と 8072 の 2 ポートを使うアプリでは一工夫が必要です。
その答えが、「WebSocket 用ポートを追加 TCP ポート(additionalPortMappings)として内部公開し、フロントの NGINX からアプリ名+ポートで直接プロキシする」というパターンです。
- Odoo(バックエンド)
- Ingress:
external: false,targetPort: 8069 additionalPortMappingsに 8072(external: false)を追加
- Ingress:
- NGINX ゲートウェイ(フロント)
/→http://odoo-app:8069/websocket→http://odoo-app:8072- WebSocket ヘッダーとタイムアウトを適切に設定
- Docker 用
resolver 127.0.0.11;は削除
この構成をベースにすれば、Azure Container Apps 上でも Odoo の HTTP(8069) と WebSocket(8072) を安定してプロキシでき、Live Chat やリアルタイム機能も安心して活用できます。既存の Kubernetes Ingress の感覚をそのまま持ち込むとうまく動かないことが多いので、「HTTP Ingress は targetPort 一つ+追加 TCP ポート」という ACA 特有の考え方を押さえておくと、今後のトラブルシューティングにも役立つはずです。

コメント