🗂

Slackのoutgoing webhookをNginxのミラーリング構成で複製し複数サーバーで同一リクエストを受信する

2024/08/23に公開

はじめに

本記事では、Slackのoutgoing webhook、ngrok、Docker上のNginx、そして手動で起動する2つのFlaskアプリケーションを組み合わせた、HTTPリクエストのミラーリング構成について説明します。

この構成により、Slackから送信されたメッセージを2つの異なるFlaskサーバーで同時に処理することが可能になります。

システム構成

  1. Slackのoutgoing webhook
  2. ngrok(ローカル環境へのトンネリング)
  3. Docker上のNginx(リバースプロキシとリクエストミラーリング)
  4. 2つのFlaskアプリケーション(別々のターミナルで実行)

システム構成図

環境構築

1. Flaskアプリケーションの作成

2つの異なるポートで動作する2つのFlaskアプリケーションを作成します。

app1.py:

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

@app.route('/', methods=['POST'])
def receivedMessage():
    all_data = request.form.to_dict()
    print("All form data:", all_data)

    response = {
        "message": "Data received successfully",
        "data": all_data
    }
    return jsonify(response), 200

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')

2つの Flask アプリケーション(app1.pyapp2.py)は、ほぼ同一のコードを持っています。唯一の違いは、アプリケーションを起動する際のポート番号です:

app1.py:

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')  # デフォルトポート5000を使用

app2.py:

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=6000)  # ポート6000を明示的に指定

この違いにより、2つのアプリケーションが異なるポートで起動し、Nginx の設定で指定された upstream サーバーとして機能することができます。

例えば、一方のアプリケーションで新機能をテストしながら、もう一方で既存の安定版を運用するといったシナリオに適用できます。

2. Nginxの設定

nginx/conf.d/default.conf:

upstream server1 {
    server host.docker.internal:5000;
}

upstream server2 {
    server host.docker.internal:6000;
}

server {
    listen 80;
    server_name localhost;

    location / {
        proxy_pass http://server1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        mirror /mirror;
        mirror_request_body on;
    }

    location = /mirror {
        internal;
        proxy_pass http://server2$request_uri;
        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-Original-URI $request_uri;
    }
}

このNginx設定ファイルは、リクエストのミラーリングを実現するための核心部分です。以下に、各セクションの詳細な説明を記します:

  1. upstream ブロック:

    • 2つの異なるサーバー(Flask アプリケーション)を定義しています。
    • host.docker.internal は Docker 環境からホストマシンの IP アドレスを参照するための特殊なホスト名です。
    • ポート 5000 と 6000 は、それぞれの Flask アプリケーションが待ち受けるポートです。
  2. server ブロック:

    • ポート 80 でリクエストを受け付けるよう設定しています。
  3. メインの location / ブロック:

    • すべてのリクエストを server1(ポート 5000 の Flask アプリ)にプロキシします。
    • proxy_set_header ディレクティブにより、オリジナルのリクエスト情報を Flask アプリに転送します。
    • mirror /mirror; が重要なポイントで、すべてのリクエストを /mirror ロケーションにもコピーします。
    • mirror_request_body on; により、リクエストボディもミラーリングされます。
  4. location = /mirror ブロック:

    • internal; ディレクティブにより、この場所は外部からは直接アクセスできません。
    • ミラーリングされたリクエストを server2(ポート 6000 の Flask アプリ)に転送します。
    • オリジナルのリクエスト情報も同様に転送されます。

この設定により、1つのリクエストが2つの異なる Flask アプリケーションで同時に処理されることが可能になります。オリジナルのレスポンスは server1 から返され、ミラーリングされたリクエストの結果は無視されますが、両方のサーバーでリクエストが処理されます。

nginx/nginx.conf:

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    keepalive_timeout  65;

    include /etc/nginx/conf.d/*.conf;
}

3. Docker構成

docker-compose.yml:

services:
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/conf.d:/etc/nginx/conf.d

セットアップと実行

  1. Flaskアプリケーションの実行:
    別々のターミナルで以下のコマンドを実行します。

    Terminal 1:

    python app1.py
    

    Terminal 2:

    python app2.py
    
  2. Dockerの起動:

    docker-compose up -d
    
  3. ngrokの起動:

    ngrok http 80
    
  4. Slackのoutgoing webhookの設定:

    • Slackの管理画面で新しいoutgoing webhookを作成します。
    • URLにngrokが生成したURLを設定します。

動作確認

  1. Slackで設定したチャンネルにメッセージを投稿します。

  2. FlaskアプリケーションとNginxのログを確認します:

    Flask App 1:

    All form data: {'token': 'your_slack_token', 'team_id': 'your_team_id', ...}
    

    Flask App 2:

    All form data: {'token': 'your_slack_token', 'team_id': 'your_team_id', ...}
    

    Nginx logs:

    192.168.65.1 - - [23/Aug/2024:12:08:41 +0000] "POST / HTTP/1.1" 200 421 "-" "Slackbot 1.0 (+https://api.slack.com/robots)" "35.170.64.166"
    

まとめ

この構成により、以下のことが実現できました:

  1. Slackのoutgoing webhookからのリクエストをngrok経由で受信
  2. Nginxを使用したHTTPリクエストのミラーリング
  3. 2つの異なるFlaskアプリケーションでの同時リクエスト処理

主なポイント:

  • ngrokを使用したローカル環境へのトンネリング
  • Nginxのmirrorディレクティブを使用したリクエストのミラーリング
  • Dockerを使用したNginxの実行
  • 手動で起動する複数のFlaskアプリケーション

おわりに

この構成は、Slackボットの開発やテスト、段階的な機能更新、A/Bテストなど、様々な用途に活用できます。

Nginxのミラーリング機能を使用することで、既存のシステムに影響を与えることなく、新しい機能やパフォーマンス改善を安全にテストすることが可能になります。

また、この構成はローカル開発環境向けですが、本番環境では適切なセキュリティ対策や可用性を考慮した構成に変更する必要があります。

Discussion