Azure Container Apps(ACA)環境でAPIやWebアプリを複数動かしていると、「dev1.example.com/api」「/reports」「/webapp」のように同一サブドメイン配下のパスでサービスを出し分けたくなります。本記事ではAzure Front Doorを使わず、ACA内にNGINXゲートウェイを1つ立ててパスベースルーティングを実現する設計と設定、運用のコツをまとめます。
やりたいことと、Azure Container Appsの公開単位を整理する
前提として、Azure Container AppsではアプリごとにIngress(HTTP/TCP)を有効化すると、外部アクセス用のFQDN(例:xxx.azurecontainerapps.io)が割り当てられ、HTTP IngressではTLS終端、HTTP/1.1・HTTP/2、WebSocket、gRPCなどが提供されます。またHTTP Ingressにはリクエストタイムアウト(240秒)などプラットフォーム側の制約もあります。
つまり、複数のコンテナーアプリをそのまま外部公開すると「アプリごとに別ドメイン(または別サブドメイン)」になりがちです。一方で「dev1.example.com配下のパス(/api, /reports, /webapp…)で出し分けたい」場合は、どこか1か所で受けたHTTPリクエストをパスに応じて振り分ける“入口”が必要になります。
なお近年のACAには、環境側にHTTPルート設定(rule-based routing)を作成し、同一FQDN配下のパスに応じて別コンテナーアプリへルーティングしつつ、転送前にパスを書き換える仕組みも用意されています。とはいえ、細かなヘッダー制御・キャッシュ・WebSocket/gRPCの扱い・独自の認証前処理など「ゲートウェイとしての自由度」を重視するなら、NGINXをACA内に置く構成は今も有力です。
結論:Front Doorなしでも、NGINXをゲートウェイにすればパスベースルーティングできる
構成はシンプルです。ACA環境内に「NGINX専用のコンテナーアプリ」を1つ作り、そこだけ外部Ingress+カスタムドメインを割り当てます。残りの18個(API、レポート、Webアプリなど)は内部Ingressにして外部公開をやめ、NGINXから内部ネットワークで呼び出します。
| コンポーネント | 役割 | 推奨設定の要点 |
|---|---|---|
| NGINX(ゲートウェイ) | dev1.example.comの入口。パスで振り分け | Ingress:外部(External)/カスタムドメイン+TLS/複数レプリカで冗長化 |
| API / Reports / WebApp(バックエンド群) | 実処理(ビジネスロジック) | Ingress:内部(Internal)/原則外部ドメインは割り当てない/必要ならIP制限も併用 |
| DNS | dev1.example.comをNGINXへ向ける | サブドメインならCNAMEでNGINXの生成ドメインへ直結(中継CNAMEは避ける) |
| TLS証明書 | HTTPS化 | ACAのマネージド証明書(無料)を使うと更新が自動(要件あり) |
全体像:リクエストは必ずNGINXを通す
通信の流れを文章で図解すると次のとおりです。
- ブラウザ/クライアント → https://dev1.example.com/(ACAのNGINXアプリのIngress)
- ACA Ingress(TLS終端)→ NGINXコンテナー(HTTP)
- NGINX → /api は api アプリへ、/reports は reports アプリへ…という具合に内部呼び出し
同一環境内のサービス呼び出しは、アプリ名で名前解決でき、http://<CONTAINER_APP_NAME>の形でアクセスできます(NGINXのproxy_pass先にそのまま使えるのが大きな利点です)。
手順:NGINXゲートウェイ用のContainer Appを追加する
NGINXアプリを外部Ingressで作成する
既にACA環境(dev1など)に18個のアプリがある前提なら、まずは同じ環境にNGINXアプリを1つ追加します。CLIで作るなら、外部Ingress+target port 80(NGINXがlistenするポート)を指定します。
az containerapp create \
--name nginx-gateway \
--resource-group <RG> \
--environment <ENV> \
--image nginx \
--ingress external \
--target-port 80
この時点でNGINXアプリには既定のFQDNが払い出されます(後でdev1.example.comを割り当てるのはこのアプリだけにします)。
バックエンド18サービスは内部Ingressへ寄せる
次に、APIやWebアプリなど本体のコンテナーアプリは、外部公開をやめて内部Ingressのみにします。これにより、インターネットからバックエンドへ直接到達できなくなり、入口をNGINXに一本化できます。
新規作成なら、たとえば次のようにinternalを指定します(既存アプリはIngress設定をinternalに変更します)。
az containerapp create \
--name api \
--resource-group <RG> \
--environment <ENV> \
--image <API_IMAGE> \
--ingress internal \
--target-port 8080
内部Ingressにしても、同一環境内からはアプリ名で呼べるため、NGINX側では proxy_pass http://api/; のように記述できます。
NGINX設定:/api・/reports・/webappを確実に振り分ける
まず押さえるべき「スラッシュとパス書き換え」の考え方
NGINXでパスベースルーティングを組むとき、最も事故が多いのが「バックエンドが期待するパス」と「入口で見えるパス」のズレです。/apiで受けたリクエストをバックエンドにそのまま/apiで渡すのか、/に書き換えて渡すのかを最初に決めてください。
| やりたいこと | NGINX側の考え方 | 典型例 |
|---|---|---|
| /api/xxx をバックエンドの /xxx として扱いたい(prefixを剥がす) | prefix rewriteが必要 | APIがルート起点で実装されている |
| /api/xxx をバックエンドでも /api/xxx のまま扱いたい | rewrite不要 | API側でパスを含めてルーティングしている |
| SPAを /webapp 配下で配信したい | アプリ側のbasePath設定が重要 | React/Vue/Angularの静的配信 |
nginx.confのサンプル(実運用向け)
以下は、dev1.example.com配下で /api・/reports・/webapp をルーティングする例です。NGINXはACAのIngressの背後で動くため、クライアントIPや元のhttps情報は X-Forwarded-* 系ヘッダーで渡ってきます。バックエンドでURL生成やリダイレクトを正しく行うため、これらを適切に引き継ぐのがポイントです。
events {
}
http {
# ログはstdoutに出しておくと、Container Appsのログ基盤で追いやすい
access_log /dev/stdout;
error_log /dev/stderr warn;
# 18サービス以上になるなら、upstreamでまとめると読みやすい
upstream api_upstream {
server api;
}
upstream reports_upstream {
server reports;
}
upstream webapp_upstream {
server webapp;
}
# WebSocket用にConnectionヘッダーを切り替える
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name dev1.example.com;
# 末尾スラッシュを揃える(相対パスやリダイレクト事故を減らす)
location = /api { return 301 /api/; }
location = /reports { return 301 /reports/; }
location = /webapp { return 301 /webapp/; }
# 共通のプロキシヘッダー(必要に応じて追加)
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $http_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
# WebSocketを使う可能性があるなら入れておく
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# /api 配下 → api へ(/api を剥がして / として転送する例)
location /api/ {
proxy_http_version 1.1;
proxy_pass http://api_upstream/;
}
# /reports 配下 → reports へ(こちらも prefix を剥がす例)
location /reports/ {
proxy_http_version 1.1;
proxy_pass http://reports_upstream/;
}
# /webapp 配下 → webapp へ
# 静的配信やSPAは「アプリ側を /webapp/ 前提でビルド/設定」するのが安全
location /webapp/ {
proxy_http_version 1.1;
proxy_pass http://webapp_upstream/;
}
}
}
注目ポイント
- proxy_passの末尾スラッシュ:
location /api/に対してproxy_pass http://api/;と書くと、/api/ の部分が / に置き換わる挙動になり、バックエンドは /xxx として受け取れます。逆にproxy_pass http://api;(末尾スラッシュなし)だと /api/xxx のまま渡りやすく、実装と噛み合わないと404になります。 - アプリ名で到達できる:同一ACA環境内なら
http://apiのようにアプリ名で呼べるため、サービスディスカバリを別途用意しなくても成立します。 - X-Forwarded-Proto:ACAのHTTP Ingressは
X-Forwarded-Protoなどのヘッダーを付与します。バックエンドがhttps前提のURLを生成する場合、NGINXでこのヘッダーを引き継いでおくと事故が減ります。 - タイムアウト:外側のACA HTTP Ingressには240秒のリクエストタイムアウトがあります。NGINX側で長めの
proxy_read_timeoutを設定しても、外側の制約で切れるケースがあるため、長時間処理は非同期化(ジョブ化)などの設計も検討してください。
proxy_passの書き方で挙動が変わる(早見表)
「/apiを剥がすのか、残すのか」をチーム内で迷わないよう、よく使うパターンを表にしておきます。最終的にはバックエンドの実装方針に合わせて統一してください。
| location | proxy_pass | バックエンドに届くパスのイメージ | 向いているケース |
|---|---|---|---|
| /api/ | http://api/ | /api/xxx → /xxx | APIがルート起点(/usersなど)で作られている |
| /api/ | http://api | /api/xxx → /api/xxx | API側が/ api prefix込みでルーティングしている |
| /webapp/ | http://webapp/ | /webapp/xxx → /xxx | 静的サイトをルート配信として作っているが、配下公開したい |
さらに、バックエンドがパス配下で動いていることを認識できるように、必要に応じて X-Forwarded-Prefix などのヘッダーを付けると、フレームワーク側のURL生成やルーティングの調整がしやすくなります(対応可否はアプリ実装に依存します)。
サービスが多いときのNGINX設定管理のコツ
- upstreamとlocationを分離:上流先(app名)とルーティング(path)の責務を分けると、増設時の差分が小さくなります。
- 設定の分割:ルートが増える場合は、
includeでファイル分割し、Azure Files側で「サービスごとにconfを追加」できる形にするとレビューしやすくなります。 - 複数ドメイン(dev1/dev2など)も同居可能:serverブロックを複数定義すればホスト名ベースの振り分けもできます。長いFQDNや大量のserver_nameを扱う場合は
server_names_hash_bucket_sizeの調整が必要になることがあります。
nginx.confの配置方法:イメージ同梱とAzure Filesマウント
NGINX設定ファイルの管理方法は大きく2つあります。運用チームの体制(CI/CDの有無、変更頻度)で選ぶと失敗しません。
| 方式 | メリット | 注意点 |
|---|---|---|
| コンテナイメージにnginx.confを同梱 | 変更はCI/CDで一元管理しやすい。ロールバックもイメージ単位 | 設定変更=イメージ再ビルドが必要。小変更でもパイプラインが回る |
| Azure Filesで外出ししてボリュームマウント | 設定ファイルだけ差し替え可能。18サービス以上でも追従が楽 | 差し替え後にNGINXへ反映する手順(リロード/再起動)を決める必要 |
Azure Filesに置いてマウントする(変更頻度が高い場合におすすめ)
ACAはAzure Filesの共有をボリュームとしてマウントできます。SMB/NFSでのマウントに対応しており、設定ファイルの外出し用途でもよく使われます。
やることは次の流れです(考え方を押さえれば、PortalでもCLIでも同じです)。
- ストレージアカウントとファイル共有を作成し、nginx.confをアップロード
- Container Apps環境に「環境ストレージ」としてAzure Filesを登録
- NGINXアプリのボリュームに紐付け、
/etc/nginx/nginx.confへsubPathでマウント
Microsoftの手順例では、NGINXコンテナーを外部公開し、内部Ingressのアプリへ proxy_pass http://app1/; のように転送する形でパスベースルーティングを構成しています。
設定変更を反映する方法(停止時間を最小化するコツ)
Azure Files上のnginx.confを更新しても、NGINXプロセスが自動でリロードするとは限りません。単純に再起動するなら、対象リビジョンの再起動コマンドが用意されています。
az containerapp revision restart \
--resource-group <RG> \
--revision <REVISION_NAME>
ただし、レプリカ数が1のまま再起動すると、その間入口が落ちる可能性があります。minReplicasを2以上にして冗長化した上で、更新作業を行うのが実務的です(後述)。
カスタムドメインとHTTPS:dev1.example.comはNGINXだけに割り当てる
同一サブドメイン配下でパス分岐したい場合、カスタムドメインはNGINXゲートウェイにだけ割り当てます。バックエンド18サービスに同じドメインを割り当てようとしても、入口が分散してしまい目的が達成できません。
DNSの基本(サブドメインの場合)
サブドメイン(例:dev1.example.com)は、通常CNAMEでNGINXアプリの「生成ドメイン(既定FQDN)」へ向けます。マネージド証明書(無料)を使う場合、CNAMEは中継せず、生成ドメインへ直接マップする必要があります(Cloudflareなど中継CNAMEを挟むと発行・更新が失敗する条件が明記されています)。
| 目的 | レコード | 例 |
|---|---|---|
| dev1.example.com → NGINXへ向ける | CNAME | dev1 → nginx-gateway.<random>.<region>.azurecontainerapps.io |
| ドメイン所有確認 | TXT | asuid.dev1 → (ポータル/CLIが表示する検証コード) |
ACAのマネージド証明書を使うときの要件
ACAのマネージド証明書は、要件を満たしている限り自動更新されます。主な要件として「HTTP Ingressを有効にし、外部から到達可能であること」「サブドメインはCNAMEで生成ドメインへ直接マップすること」「CAAレコードがある場合はDigiCertを許可すること」などが挙げられます。運用中にDNS設定を変えると更新が止まるため、最初に条件を満たしているか確認しておくのが安全です。
またHTTP Ingressは既定で80→443へリダイレクトされるため、基本的にはHTTPS前提で設計すると混乱が少なくなります。
セキュリティ設計:バックエンドを外に出さない+入口で制御する
この構成の最大のメリットは、バックエンドをインターネットに晒さずに済む点です。入口(NGINX)だけを公開し、認証・アクセス制御・監査ログのポイントを集約できます。
IngressをInternalにするだけで「直接アクセス」を減らせる
バックエンドはInternalにしておけば、少なくとも外部向けの公開面が小さくなります。さらに必要であれば、入口のNGINXアプリ側にIP制限を設定し、社内固定IPやVPN出口のみ許可するといった運用も可能です。
IP制限を使うときの考え方
- 本番の管理画面(/reports/adminなど)は、入口でIP制限+認証を組み合わせると堅牢
- ゼロトラスト前提なら、アプリ側の認証(OIDCなど)を必須にし、IP制限は「追加の防波堤」として使う
可用性とスケーリング:NGINXを単一障害点にしない
NGINXが唯一の入口になる以上、ここをボトルネック・単一障害点にしない設計が重要です。ACAはアプリ単位でレプリカを増やせるため、NGINXゲートウェイは最初から冗長化しておくのが基本です。
| 観点 | 推奨 | 理由 |
|---|---|---|
| レプリカ数 | minReplicas: 2以上 | 設定反映の再起動や片系障害でも入口が落ちにくい |
| スケールアウト | HTTP負荷に応じた自動スケール | ピーク時にゲートウェイが詰まるのを防ぐ |
| ヘルスチェック | startup / liveness / readiness を明示 | 起動直後の不安定時間やハングを早期検知できる |
| タイムアウト設計 | 240秒制限を前提にAPI設計 | 入口で待たせない。ジョブ化やページングで回避 |
ヘルスプローブは、起動確認(startup)、生存確認(liveness)、受付可能判定(readiness)の3種類があり、入口(NGINX)にもバックエンドにも設定できます。特にNGINXゲートウェイは入口なので、readinessが落ちたレプリカへ流れないようにしておくと障害時の体感が改善します。
運用でよくある詰まりどころ(チェックリスト)
404になる(パス書き換えがズレている)
- /api/xxx を /xxx として受けたいのに、バックエンドに /api/xxx のまま渡っている
- locationとproxy_passの末尾スラッシュの組み合わせを再確認する
- バックエンドのルーティング定義(/api prefixを含むか)を明文化する
リダイレクトが壊れる(/に飛ぶ、httpsにならない)
- バックエンドが生成する絶対URLが、/webapp前提になっていない
- X-Forwarded-Proto / X-Forwarded-Host を引き継いでいるか
- SPAの場合、ビルド時のbasePath(例:PUBLIC_URL=/webapp)を設定する
証明書が発行されない/更新されない
- CNAMEが「中継」になっていないか(生成ドメインへ直接か)
- CAAレコードがある場合、DigiCertの許可が入っているか
- 外部から到達可能なHTTP Ingressになっているか
マネージド証明書は条件を外すと発行・更新が失敗するため、DNSを触る運用がある場合は特に注意してください。
Front Doorなしでの別解:ACAのルールベースルーティングも検討する
「NGINXを自分で運用するのは避けたい」「パスで振り分けられれば十分」という場合は、ACAのルールベースルーティングが適します。環境側にHTTPルート設定を作り、パスごとに転送先のコンテナーアプリを指定し、必要ならprefixRewriteでパスを書き換えられます。
概念的には次のようなYAMLで、/apiはapiへ、/webappはwebappへ、といったルールが書けます(prefixRewriteで/に書き換える例)。
rules:
- description: api
routes:
- match:
prefix: /api
action:
prefixRewrite: /
targets:
- containerApp: api
- description: webapp
routes:
- match:
prefix: /webapp
action:
prefixRewrite: /
targets:
- containerApp: webapp
ただし、NGINXのような自由度(細かなヘッダー操作、認証前処理、キャッシュ、独自のルールエンジン等)が必要なら、引き続きゲートウェイ方式が向きます。要件が「パス分岐だけ」なのか「APIゲートウェイとしての機能」まで求めるのかで選定しましょう。
まとめ
Azure Front Doorを使わなくても、ACA環境内にNGINXを1つだけ外部公開のゲートウェイとして置けば、同一サブドメイン配下のパスで複数サービスを出し分けられます。バックエンドを内部Ingressに閉じることでセキュリティも上げやすく、サービスが増えてもNGINXのlocationを追加するだけで拡張できます。
一方で、入口が集約されるぶんスケール・冗長化・ヘルスチェック・設定反映手順は最初に固めておくことが重要です。運用しやすい形(イメージ同梱 or Azure Files外出し、更新手順、監視)を決めてから本番へ持ち込みましょう。

コメント