1.はじめに
会社で生成AIの啓発活動を担当し、社内への浸透を推進していますが、チャットボット以外にも生成AIを活用したサービスが次々と登場してきており、利用者の視点から入口を一本化する必要性を感じています。
さらに、Azure App ServiceでOkta認証を用いるWebサービスを構築する際にはApp Serviceが1リソースあたり1サービスとなるため、Okta認証をサービスごとに設定せざる負えない状況になっています。この方式は構築者の観点からですとシンプルで良いのですが、利用者からみると類似するサービスのアドレスが複数あるような状況になり分かりにくいです。
そこで、生成AIの入口を用途ごとに一本化し、さらにOkta認証もすべてを1つのサービスで実施できないかと検討していたところ、リバースプロキシで実現できる可能性が見えたため、試してみることにしました。
2.リバースプロキシとは
リバースプロキシは、クライアントからのリクエストを受け取り、適切なバックエンドサーバーに転送する中間サーバーの役割を果たします。これにより、クライアントは直接バックエンドサーバーにアクセスすることなく、必要なリソースやサービスを利用することができます。
リバースプロキシの主な機能は以下の5つに分類されます:
- セキュリティ面では、Oktaなどによる統合認証の実現、バックエンドサーバーの保護、TLS1.2による暗号化通信の管理、そしてDDoS攻撃からの防御を提供します。
- 負荷分散機能により、複数のバックエンドサーバーにトラフィックを分散させ、システム全体の可用性と性能を最適化します。
- キャッシング機能を通じて、よく利用されるコンテンツを一時保存し、応答時間の短縮とバックエンド負荷の軽減を実現します。
- 管理と可視性の面では、アクセスログ、トラフィック、性能データの一元管理を可能にします。
- バックエンド統合により、異なる技術やプロトコルを使用するサービスを一つのインターフェースにまとめ、クライアントへのアクセスを簡素化します。
この図はリバースプロキシのアーキテクチャを3つの層で表現しています:
- セキュリティ層:クライアントからのHTTPS(TLS1.2)リクエストを受け取り、Oktaによる認証を行います。
- パフォーマンス最適化層:キャッシュ機能と負荷分散機能を提供し、システムの効率を向上させます。
- バックエンド層:3つのバックエンドサーバーで構成され、負荷分散機能によってリクエストが適切に振り分けられます。
このアーキテクチャにより、セキュリティの確保とパフォーマンスの最適化を同時に実現しています。
3.リバースプロキシの実現方法
Azure App Serviceでリバースプロキシを実現する方法として、主に以下の2つのアプローチがあります。1つ目は、App Serviceの組み込み機能である「URL Rewrite」と「Reverse Proxy」を利用する方法です。2つ目は、Nginxなどのリバースプロキシサーバーをカスタムコンテナとしてデプロイする方法です。
それぞれのアプローチには利点と制約があり、要件に応じて適切な方法を選択する必要があります。本記事では、より柔軟な設定が可能なNginxを使用したアプローチを詳しく見ていきます。
4.Nginxとは
Nginx(エンジンエックス)は、ロシアのイーゴリ・シソエフ氏(Igor Sysoev)によって2004年にリリースされた、高性能なWebサーバー、リバースプロキシ、ロードバランサーとして機能するオープンソースソフトウェアです。Sysoev氏は、当時ロシアの大手ポータルサイトRambler.ruで働いていた際、C10K問題(1万以上の同時接続を処理する課題)を解決するために開発を始めました。
Nginxの特徴として、以下が挙げられます:
- 非同期イベント駆動アーキテクチャを採用し、少ないリソースで多数の同時接続を処理できる
- モジュール式の設計により、必要な機能を柔軟に追加・カスタマイズ可能
- 静的コンテンツの配信に優れ、キャッシング機能も充実している
- 設定が直感的で管理がしやすい
2019年には、F5 Networksが総額6億7000万ドルでNginx社を買収し、現在はエンタープライズ向けの商用版「NGINX Plus」も提供しています。2024年現在、世界中のWebサイトの約
30%以上がNginxを使用しており、特に大規模なWebサイトやクラウドサービスで広く採用されています。
4.1. Nginxの機能
項番 | 機能 | 機能(英語表記) | 詳細 |
---|---|---|---|
1 | Webサーバー機能 | HTTPS Servers | 静的コンテンツの高速配信、動的コンテンツの処理、バーチャルホスティング |
2 | リバースプロキシ | Reverse Proxy | バックエンドサーバーへのリクエスト転送、URLリライト、ヘッダー制御 |
3 | ロードバランシング | Load Balancing | 複数サーバーへの負荷分散、ヘルスチェック、セッション永続性 |
4 | キャッシング | Caching | 静的・動的コンテンツのキャッシュ、キャッシュ制御、パフォーマンス最適化 |
5 | セキュリティ | Security | SSL/TLS終端、アクセス制御、DDoS保護、WAF機能 |
6 | ストリーミング | Streaming | メディアストリーミング、HTTP Live Streaming (HLS)対応 |
7 | モニタリング | Monitoring | アクセスログ、エラーログ、メトリクス収集、状態監視 |
4.2. Nginxの定義ファイル
Nginxの設定は、主にnginx.confファイルとdefault.confファイルを通じて行われます。
nginx.conf
nginx.confは、Nginxの全体的な動作を制御するメインの設定ファイルです。このファイルは階層的な構造を持ち、http、server、locationなどの主要な設定ブロックで構成されています。通常は/etc/nginx/nginx.confに配置されます。
# メインの設定ブロック
http {
# グローバル設定
include mime.types;
default_type application/octet-stream;
# サーバーブロック
server {
listen 80;
server_name example.com;
# ロケーションブロック
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
}
}
}
default.conf
default.confは、特定のバーチャルホストの設定を定義するファイルです。このファイルはnginx.confからincludeされ、個別のサイトやアプリケーション固有の設定を記述します。通常は/etc/nginx/conf.d/default.confに配置されます。
# デフォルトのサーバー設定
server {
listen 80;
server_name localhost;
# ルートディレクトリの設定
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
# エラーページの設定
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
これらの設定ファイルを適切に組み合わせることで、複雑なルーティングやプロキシルール、セキュリティ設定などを柔軟に実現できます。
5.リバースプロキシで実現したいこと
リバースプロキシを使用して、以下の機能を実現したいと考えています:
- 単一のエントリーポイント: すべてのAIサービスに対して、一つのドメインからアクセス可能にする
- 集中認証管理: Okta認証を一箇所で実装し、すべてのサービスに適用する
- 柔軟なルーティング: URLパスに基づいて、適切なバックエンドサービスにリクエストを振り分ける
- セキュリティの強化: TLS 1.2以上の暗号化通信を強制し、セキュアな接続を確保する
- 統一されたユーザー体験: メインメニューから各サービスへのシームレスな遷移を実現する
以下の図は、実現したい構成を示しています。クライアントからの全てのリクエストは、まずOkta認証を通過し、その後メインメニューを経由して各サービスにアクセスする流れとなっています。
6.フィジビリティ
リバースプロキシ実装のフィジビリティを確認するため、以下の機能について検証が必要です
No | 検証項目 | 必要な機能 | 実現可能性 | 備考 |
---|---|---|---|---|
1 | 単一エントリーポイント | リバースプロキシ、URLルーティング | ◎ | Nginxの基本機能で実現可能 |
2 | Okta認証の集中管理 | 認証ヘッダー転送、セッション管理 | ○ | proxy_set_headerディレクティブで実現可能 |
3 | TLS 1.2強制 | SSL/TLS設定、暗号スイート制御 | ◎ | ssl_protocolsディレクティブで制御可能 |
4 | バックエンドサービス振り分け | location設定、プロキシパス | ◎ | location blockとproxy_passで実現可能 |
5 | HTTPSリダイレクト | リダイレクト設定 | ○ | return 301ディレクティブで実現可能 |
6 | ヘルスチェック | アップストリームヘルスモニタリング | △ | 追加モジュールが必要な可能性あり |
◎:標準機能で容易に実現可能
○:設定の調整で実現可能
△:追加の設定や機能が必要
総合的な判断として、要件の大部分はNginxの標準機能で実現可能であり、フィジビリティは高いと考えられます。特に重要な認証統合とルーティングについては、十分な機能が提供されています。
7.nginx.conf
リバースプロキシで実現したいことを入力にして生成AIでnginx.confを生成してみました。
このまま動作するかは分かりませんが、HTTPSのTLS1.2通信の設定やOkta認証などのセキュリティ設定、メインメニューを表示するための静的コンテンツ、Difyや複数のチャットボットへのバックエンドサービスへのプロキシルーティングなどを実現できていそうです。
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
# SSL設定
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# プロキシバッファ設定
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
# アップストリームサーバーの定義
upstream chatbot_service1 {
server chatbot-service1:8080;
}
upstream chatbot_service2 {
server chatbot-service2:8080;
}
upstream dify_service {
server dify-service:8080;
}
server {
listen 443 ssl;
server_name ai-services.example.com;
# SSL証明書
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# Okta認証設定
location = /auth {
internal;
proxy_pass https://your-okta-domain/oauth2/default/v1/authorize;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# メインメニュー(静的コンテンツ)
location / {
auth_request /auth;
root /usr/share/nginx/html;
index index.html;
}
# チャットボットサービス1
location /chatbot1 {
auth_request /auth;
proxy_pass http://chatbot_service1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# チャットボットサービス2
location /chatbot2 {
auth_request /auth;
proxy_pass http://chatbot_service2;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Difyサービス(要約、翻訳、文字起こし等)
location /dify {
auth_request /auth;
proxy_pass http://dify_service;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket対応
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# エラーページ
error_page 401 = @error401;
location @error401 {
return 302 /login?redirect_uri=$request_uri;
}
}
# HTTPをHTTPSにリダイレクト
server {
listen 80;
server_name ai-services.example.com;
return 301 https://$server_name$request_uri;
}
}
この設定では以下の機能を実現しています:
- TLS 1.2以上の強制使用とセキュアな暗号スイートの指定
- Okta認証の統合と認証要求の転送
- 各サービスへのプロキシルーティング設定
- WebSocket接続のサポート(Difyサービス用)
- HTTPからHTTPSへの自動リダイレクト
実際の計画ではNginxの上位にApp Serviceがあり、App ServiceにはNginxが導入されているので、TLS1.2とOkta認証/認可の設定を外してシンプルな設定にしてLinuxマシンで実験します。
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log
notice;
pid /var/run/nginx.pdf;
event {
worker_connection 1024;
}
# メインの設定ブロック
http {
# グローバル設定
include mime.types;
default_type application/octet-stream;
# サーバーブロック
server {
listen 80;
server_name example.com;
# 静的コンテンツ(メニュー)
root /webapp/public;
# ロケーションブロック
location /dify {
proxy_pass http://backend;
proxy_redirect off;
proxy_set_header Host $host;
}
location /chatbot {
proxy_pass http://backend;
proxy_redirect off;
proxy_set_header Host $host;
}
location /chatbot2 {
proxy_pass http://backend;
proxy_redirect off;
proxy_set_header Host $host;
}
}
}
8.検証構成
今回は単一コンテナなので、docker runでも実行できますが、パラメタなどを定義しておきたいので、あえてdocker-compose.yamlで構成を作りました。
イメージ的にはdocker compose upコマンドを実行するとdocker-compose.yamlを読み込んでコンテナを作成し、ホストコンピュータのconfigフォルダ内nginx.confとdefault.confを読み込みnginx:latestコンテナを起動するという流れです。
ホストコンピュータにはwwwrootフォルダを用意し、メインメニュー用のindex.htmlを管理しています。
configフォルダ内のファイルとwwwrootフォルダ、logフォルダはコンテナにvolume マウントします。
これによりコンテナ内のnginx.confをホストコンピュータを介して編集できます。
上記の構成を実現するためのフォルダ構造はこのような感じです。nginxフォルダ内でdocker compose upコマンドを実行すると動きます。
nginx/
|-- config
| |-- nginx.conf
|-- docker-compose.yaml
|-- log
| |-- access.log
| |-- error.log
|-- wwwroot
|-- index.html
nginx.conf
今回はYahoo newsをバックエンドに見立てて構成を組んでます。
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name localhost;
location / {
root /home/wwwroot;
index index.html;
}
location /yahoo {
proxy_pass https://news.yahoo.co.jp/;
proxy_redirect off;
}
}
}
default.conf
server {
listen 80;
listen [::]:80;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
index.html
※長くなるのでangular.jsのスタイルシートは省きます。
<!DOCTYPE html>
<html lang="ja" ng-app="menuApp">
<head>
<meta charset="UTF-8">
<title>メニュー画面</title>
</head>
<body>
<div class="menu" ng-controller="MenuController">
<div class="menu-header">社内生成AI業務毎のメニューを選択してください</div>
<div class="menu-content">
<div class="menu-list">
<div class="button" ng-click="selectMenu('要約')">要約</div>
<div class="button" ng-click="selectMenu('翻訳')">翻訳</div>
<div class="button" ng-click="selectMenu('文字起こし')">文字起こし</div>
<div class="button" ng-click="selectMenu('調査')">調査</div>
<div class="button" ng-click="selectMenu('議事録作成')">議事録作成</div>
</div>
<div class="submenu">
<div ng-if="selectedMenu === '議事録作成'">
<div class="button" ng-click="navigateToYahoo()">Yahoo!</div>
<div class="button" ng-click="navigateToYahoo()">会議記録</div>
<div class="button" ng-click="selectSubmenu('議題追加')">議題追加</div>
<div class="button" ng-click="selectSubmenu('議事録送信')">議事録送信</div>
</div>
<div ng-if="selectedMenu && selectedMenu !== '議事録作成'">
<p>{{ selectedMenu }}に関連するサブメニューはありません。</p>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
<script>
angular.module('menuApp', [])
.controller('MenuController', ['$scope', '$window', function($scope, $window) {
$scope.selectedMenu = '';
$scope.selectedSubmenu = '';
$scope.selectMenu = function(menu) {
$scope.selectedMenu = menu;
$scope.selectedSubmenu = '';
<!-- alert(menu + 'を選択しました!'); -->
};
$scope.selectSubmenu = function(submenu) {
$scope.selectedSubmenu = su
bmenu;
<!-- alert(submenu + 'を選択しました!'); -->
};
$scope.navigateToYahoo = function() {
$window.location.href = '/yahoo';
};
}]);
</script>
</body>
</html>
docker-compose.yaml
services:
rp:
image: nginx:latest
volumes:
- ./config/nginx.conf:/etc/nginx/nginx.conf
- ./config/nginx.conf.template:/etc/nginx/nginx.conf.template
- ./log:/var/log/nginx
port:
- "80:80"
9.課題
実際に動かして見ると課題が見えてきます。大きな課題はサブディレクトリに割り当てたバックエンドサーバのアドレス解決です。
No | 課題 | 具体的なケース | 対策 |
---|---|---|---|
1 | 相対アドレスリンクが解決できない | リバースプロキシしたバックエンドサーバのコンテンツに相対アドレスリンクがあると、クリック時に404 Not Foundエラーとなる | nginx.confのsub_filterディレクティブでリンクアドレスを書き換える |
2 | 絶対アドレスリンクが解決できない | リバースプロキシしたバックエンドサーバのコンテンツに絶対アドレスリンクで同じサーバのコンテンツへのリンクがあるとクリック時にリバースプロキシを外れてしまう。 | 同上 |
3 | text/html以外のコンテンツがsub_filterディレクティブで書き変わらない | cgiやPythonで記述されたページは書き変わりません。 | nginx.confのsub_filter_typesを*として全てのコンテンツを書き換え対象にする |
4 | gzipで圧縮されたコンテンツが書き変わらない | gzipで圧縮されて転送されるコンテンツページは書き変わりません。 | nginx.confのproxy_set_header Accept-Encoding でgzip圧縮しないように設定する |
5 | envsubstの書き換え問題 | docker-compose.yamlのcommandディレクティブでenvsubstを使い変数を書き換えると$hostなどが空に書き変わってしまう | envsubstは使わないt好かう場合は置き換える変数を指定する。 |
10.対策
課題を解決するためのnginx.confファイルです。
まだ、不足があるかもしれませんが、これでバックエンドサーバのコンテンツをおおむね解決できます。
nginx.conf(アドレス書き換え成功)
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
# Support websocket
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name localhost;
location / {
root /home/wwwroot;
index index.html;
}
######################################################################
# reffer to https://qiita.com/k1tajima/items/732ec6694ecb3e928533
location /yahoo/ {
# definitions
set $context "/yahoo/";
set $backend_url "https://news.yahoo.co.jp";
# Basic rewrite Rule
rewrite ^/yahoo/(.*) /$1 break;
# reffer to http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
# Could not set $backend_url with "proxy_redirect default" setting by potofo
proxy_pass https://news.yahoo.co.jp;
# reffer to http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_redirect
# proxy_redirect $scheme://$host/ $context; # Webアプリのレスポンスに応じて追加
proxy_redirect / $context;
proxy_redirect default;
# If you do not enable this setting, gzip compressed pages cannot be rewritten with sub_filter.
proxy_set_header Accept-Encoding "";
# If this setting is not enabled, pages other than text/html, such as cgi, will not be rewritten by the sub_filter.
# reffer to https://nginx.org/en/docs/http/ngx_http_sub_module.html#sub_filter_types
#sub_filter_types text/html; # This is defalut setting
sub_filter_types *;
# reffer to https://nginx.org/en/docs/http/ngx_http_sub_module.html#sub_filter_once
sub_filter_once off;
# Relative Address Rewrite Rules
# See http://nginx.org/en/docs/http/ngx_http_sub_module.html
sub_filter 'href="/' 'href="$context';
sub_filter 'src="/' 'src="$context';
sub_filter 'url("/' 'url("$context';
sub_filter 'action="/' 'action="$context';
sub_filter 'href=\'/' 'href=\'$context';
sub_filter 'src=\'/' 'src=\'$context';
sub_filter 'url(\'/' 'url(\'$context';
sub_filter 'action=\'/' 'action=\'$context';
# sub_filter 'http://$proxy_host/' '$scheme://$host$context';
# sub_filter 'http:\/\/$proxy_host\/' '$scheme:\/\/$host\/some-service\/';
# sub_filter '//$host/' '//$host$context'; # Webアプリのレスポンスに応じて選択
# sub_filter '\/\/$host\/' '\/\/$host\/some-service\/';
# Absolute Address Rewrite Rules
sub_filter 'href="$backend_url' 'href="/yahoo/';
# Set Proxy headers
# Support websocket
# reffer to https://nginx.org/en/docs/http/websocket.html
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# For reverse proxy, set Host to $proxy_host
#proxy_set_header Host $host;
#proxy_set_header Host $http_host;
#proxy_set_header Host $proxy_host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
}
}
}
キャプチャでは分かりにくいですが、きちんと解決できています。
11.App Serviceでの実装
Nginx Dockerイメージのサイズ
[root@localmotion ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 4f67c83422ec 6 months ago 188MB
[root@localmotion ~]#
nginxのDockerイメージのベースイメージの確認
root@723295a29390:/etc# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
nginxのDockerイメージの構成ファイルの配置は以下のようになっています。
/
|-- etc
| |-- nginx
| | |-- conf.d
| | | |-- default.d
| | |-- nginx.conf
ユーザの確認
ユーザはnginxにした方がよさそうですね。
root@723295a29390:/# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
nginx:x:101:101:nginx user:/nonexistent:/bin/false
root@723295a29390:/#
構成から/etc/nginxにファイル共有をボリュームマウントすれば/etc/nginx/nginx.confをAzure App Serviceのコンテナから参照できそうです。
11.2. ファイル共有の作成
Azureポータルからストレージアカウントを作成して、ファイル共有を作成します。
①ストレージアカウントの作成
ここではptfaccstorageをストレージアカウントとして作成します。
②ファイル共有の作成
ここではファイル共有としてnginxという共有を作成します。
/etc/nginx配下のフォルダを作成します。作成するのはnginx.confとwwwroot/index.htmlだけで良いです。
11.3. App Serviceの作成
B1(Basic B1)プランとしていますが、NginxのDockerイメージは188MB程度なので、App ServiceのスケールダウンでF1(Free F1)にしてみましたが、F1プランでも動きました。
踏み台にされるのはいやなので、サービスは停止しておきました。
12.参考URL
13.Appendix
nginx.conf
**user nginx;
worker_processes auto;**
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
# Support websocket
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name localhost;
location / {
**root /etc/nginx/wwwroot;**
index index.html;
}
######################################################################
# reffer to https://qiita.com/k1tajima/items/732ec6694ecb3e928533
location /yahoo/ {
# definitions
set $context "/yahoo/";
set $backend_url "https://news.yahoo.co.jp";
# Basic rewrite Rule
rewrite ^/yahoo/(.*) /$1 break;
# reffer to http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
# Could not set $backend_url with "proxy_redirect default" setting by potofo
proxy_pass https://news.yahoo.co.jp;
# reffer to http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_redirect
# proxy_redirect $scheme://$host/ $context; # Webアプリのレスポンスに応じて追加
proxy_redirect / $context;
proxy_redirect default;
# If you do not enable this setting, gzip compressed pages cannot be rewritten with sub_filter.
proxy_set_header Accept-Encoding "";
# If this setting is not enabled, pages other than text/html, such as cgi, will not be rewritten by the sub_filter.
# reffer to https://nginx.org/en/docs/http/ngx_http_sub_module.html#sub_filter_types
#sub_filter_types text/html; # This is defalut setting
sub_filter_types *;
# reffer to https://nginx.org/en/docs/http/ngx_http_sub_module.html#sub_filter_once
sub_filter_once off;
# Relative Address Rewrite Rules
# See http://nginx.org/en/docs/http/ngx_http_sub_module.html
sub_filter 'href="/' 'href="$context';
sub_filter 'src="/' 'src="$context';
sub_filter 'url("/' 'url("$context';
sub_filter 'action="/' 'action="$context';
sub_filter 'href=\'/' 'href=\'$context';
sub_filter 'src=\'/' 'src=\'$context';
sub_filter 'url(\'/' 'url(\'$context';
sub_filter 'action=\'/' 'action=\'$context';
# Absolute Address Rewrite Rules
sub_filter 'href="$backend_url' 'href="/yahoo/';
# Set Proxy headers
# Support websocket
# reffer to https://nginx.org/en/docs/http/websocket.html
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# For reverse proxy, set Host to $proxy_host
#proxy_set_header Host $host;
#proxy_set_header Host $http_host;
#proxy_set_header Host $proxy_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
}
}
}
wwwroor/index.html
<!DOCTYPE html>
<html lang="ja" ng-app="menuApp">
<head>
<meta charset="UTF-8">
<title>メニュー画面</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
background-color: #f4f4f4;
}
.menu {
width: 1920px;
height: 1080px;
display: flex;
flex-direction: column;
background: #fff;
border: 1px solid #ccc;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.menu-header {
flex: 0 0 auto;
text-align: center;
padding: 20px;
font-size: 24px;
background-color: #0078d7;
color: white;
}
.menu-content {
flex: 1 1 auto;
display: flex;
flex-direction: row;
padding: 20px;
}
.menu-list {
flex: 1 0 20%;
border-right: 1px solid #ddd;
padding: 10px;
display: flex;
flex-direction: column;
}
.menu-list .button {
margin: 10px 0;
padding: 15px;
background-color: #f0f0f0;
border: 1px solid #ccc;
border-radius: 5px;
text-align: center;
font-size: 18px;
cursor: pointer;
transition: background-color 0.3s, transform 0.2s;
}
.menu-list .button:hover {
background-color: #e0e0e0;
transform: scale(1.02);
}
.submenu {
flex: 1 0 80%;
padding: 20px;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-items: flex-start;
background-color: #fafafa;
}
.submenu .button {
margin: 10px;
padding: 20px;
background-color: #f9f9f9;
border: 1px solid #ccc;
border-radius: 5px;
text-align: center;
font-size: 16px;
cursor: pointer;
width: calc(100% - 40px);
box-sizing: border-box;
transition: background-color 0.3s, transform 0.2s;
}
.submenu .button:hover {
background-color: #eaeaea;
transform: scale(1.02);
}
</style>
</head>
<body>
<div class="menu" ng-controller="MenuController">
<div class="menu-header">社内生成AI業務毎のメニューを選択してください</div>
<div class="menu-content">
<div class="menu-list">
<div class="button" ng-click="selectMenu('要約')">要約</div>
<div class="button" ng-click="selectMenu('翻訳')">翻訳</div>
<div class="button" ng-click="selectMenu('文字起こし')">文字起こし</div>
<div class="button" ng-click="selectMenu('調査')">調査</div>
<div class="button" ng-click="selectMenu('議事録作成')">議事録作成</div>
</div>
<div class="submenu">
<div ng-if="selectedMenu === '議事録作成'">
<div class="button" ng-click="navigateToYahoo()">Yahoo!</div>
<div class="button" ng-click="selectSubmenu('会議記録')">会議記録</div>
<div class="button" ng-click="selectSubmenu('議題追加')">議題追加</div>
<div class="button" ng-click="selectSubmenu('議事録送信')">議事録送信</div>
</div>
<div ng-if="selectedMenu && selectedMenu !== '議事録作成'">
<p>{{ selectedMenu }}に関連するサブメニューはありません。</p>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
<script>
angular.module('menuApp', [])
.controller('MenuController', ['$scope', '$window', function($scope, $window) {
$scope.selectedMenu = '';
$scope.selectedSubmenu = '';
$scope.selectMenu = function(menu) {
$scope.selectedMenu = menu;
$scope.selectedSubmenu = '';
<!-- alert(menu + 'を選択しました!'); -->
};
$scope.selectSubmenu = function(submenu) {
$scope.selectedSubmenu = submenu;
<!-- alert(submenu + 'を選択しました!'); -->
};
$scope.navigateToYahoo = function() {
$window.location.href = '/yahoo';
};
}]);
</script>
</body>
</html>
Linuxでdocker-compose.yamlで環境変数を書き換える方法
rp:
image: nginx:latest
volumes:
- ./config/nginx.conf:/etc/nginx/nginx.conf
- ./config/nginx.conf.template:/etc/nginx/nginx.conf.template
- ./config/default.conf:/etc/nginx/cond.d/default.conf
- ./log:/var/log/nginx
- ./wwwroot:/home/wwwroot
ports:
- "80:80"
environment:
- NGINX_HOST=localmotion
- NGINX_YAHOO=https://news.yahoo.co.jp/
command: /bin/bash -c "envsubst '${NGINX_HOST}' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf && nginx -g 'daemon off;'"
#command: /bin/bash -c "envsubst < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf && nginx -g 'daemon off;'"