2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Azure App Serviceでリバースプロキシ

Last updated at Posted at 2024-12-08

image.png

1.はじめに

会社で生成AIの啓発活動を担当し、社内への浸透を推進していますが、チャットボット以外にも生成AIを活用したサービスが次々と登場してきており、利用者の視点から入口を一本化する必要性を感じています。
さらに、Azure App ServiceでOkta認証を用いるWebサービスを構築する際にはApp Serviceが1リソースあたり1サービスとなるため、Okta認証をサービスごとに設定せざる負えない状況になっています。この方式は構築者の観点からですとシンプルで良いのですが、利用者からみると類似するサービスのアドレスが複数あるような状況になり分かりにくいです。
そこで、生成AIの入口を用途ごとに一本化し、さらにOkta認証もすべてを1つのサービスで実施できないかと検討していたところ、リバースプロキシで実現できる可能性が見えたため、試してみることにしました。

2.リバースプロキシとは

リバースプロキシは、クライアントからのリクエストを受け取り、適切なバックエンドサーバーに転送する中間サーバーの役割を果たします。これにより、クライアントは直接バックエンドサーバーにアクセスすることなく、必要なリソースやサービスを利用することができます。

リバースプロキシの主な機能は以下の5つに分類されます:

  1. セキュリティ面では、Oktaなどによる統合認証の実現、バックエンドサーバーの保護、TLS1.2による暗号化通信の管理、そしてDDoS攻撃からの防御を提供します。
  2. 負荷分散機能により、複数のバックエンドサーバーにトラフィックを分散させ、システム全体の可用性と性能を最適化します。
  3. キャッシング機能を通じて、よく利用されるコンテンツを一時保存し、応答時間の短縮とバックエンド負荷の軽減を実現します。
  4. 管理と可視性の面では、アクセスログ、トラフィック、性能データの一元管理を可能にします。
  5. バックエンド統合により、異なる技術やプロトコルを使用するサービスを一つのインターフェースにまとめ、クライアントへのアクセスを簡素化します。

この図はリバースプロキシのアーキテクチャを3つの層で表現しています:

  1. セキュリティ層:クライアントからのHTTPS(TLS1.2)リクエストを受け取り、Oktaによる認証を行います。
  2. パフォーマンス最適化層:キャッシュ機能と負荷分散機能を提供し、システムの効率を向上させます。
  3. バックエンド層: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![メインメニュー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3764962/f03198e7-b8b4-db15-3205-2a482dfa72c6.png)
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"

メインメニュー.png

yahoo_news_with_issue.png

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;
          }
  }
}

yahoo_news.png

キャプチャでは分かりにくいですが、きちんと解決できています。

yahoo_news_article.png

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をストレージアカウントとして作成します。

Azure_create_storageaccount.png

②ファイル共有の作成

ここではファイル共有としてnginxという共有を作成します。
Azure_create_sharedfile_1.png

Azure_create_sharedfile_2.png

③nginx.confファイルのアップロード
Azure_upload_config_1.png

Azure_upload_config_2.png

/etc/nginx配下のフォルダを作成します。作成するのはnginx.confとwwwroot/index.htmlだけで良いです。

ファイルはAppendixにあります。
Azure_upload_config_3.png

11.3. App Serviceの作成

①Wevアプリの作成
Azure_create_webapp.png

②Nginxコンテナのデプロイ
Azure_create_nginx_container_1.png
Azure_create_nginx_container_2.png
Azure_create_nginx_container_3.png

③ストレージアカウントのファイル共有のパスのマッピング
Azure_mapping_fileshare_path.png

④パスのマッピング後のNginxの画面
Azure_メインメニュー.png
Azure_yahoo_news.png
Azure_yahoo_news_detail.png

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;'"
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?