Djangoアプリ上でFessを使って検索窓を設置する


はじめに

Djangoでサイトを作っていた際に、検索機能を手軽に設置したいなぁと思い、
OSSの検索エンジン Fess を使って実現したので方法を共有します。
Fessについて知りたい方は こちら をご参照ください。

想定読者

  • Djangoでのアプリケーション開発手法を理解している
  • Docker / docker-compose の最低限の使用法について理解している
  • Nginxの最低限の設定方法について理解している

構成

docker-composeにて以下コンテナを起動します。

  • nginxコンテナ
  • djangoコンテナ
  • postgresqlコンテナ
  • fessコンテナ
  • elasticsearchコンテナ

以下は docker-compose.yml の例です。
なお、fessとelasticsearchの設定についてはこちらのGithubを参考にさせていただきました。
また、ディレクトリ構成などは各開発状況に依存します。

docker-compose.yml
version: '3.7'

services:
    nginx:
        image: nginx:1.19.3-alpine
        ports:
            - "80:8000"
        volumes:
            - ./nginx/conf:/etc/nginx/conf.d
            - ./nginx/uwsgi_params:/etc/nginx/uwsgi_params
            - ./nginx/log:/var/log/nginx
            - ./app/static:/static
        depends_on:
            - django

    django:
        build: ./app
        command: uwsgi --socket :8001 --module app.wsgi --py-autoreload 1
        volumes:
            - ./app/:/usr/src/app/
        ports:
            - 8001:8001
        depends_on:
            - postgres
            - fess

    postgres:
        image: postgres:12.4-alpine
        volumes:
            - ./postgres/data:/var/lib/postgresql/data

    fess:
        image: ghcr.io/codelibs/fess:13.12.0
        environment:
            - "ES_HTTP_URL=http://es:9200"
            - "FESS_DICTIONARY_PATH=/usr/share/elasticsearch/config/dictionary/"
        ports:
            - "8080:8080"
        depends_on:
            - es

    es:
        image: ghcr.io/codelibs/fess-elasticsearch:7.12.0
        environment:
            - node.name=es01
            - discovery.seed_hosts=es01
            - cluster.initial_master_nodes=es01
            - cluster.name=fess-es
            - bootstrap.memory_lock=true
            - "ES_JAVA_OPTS=-Xms1g -Xmx1g"
            - "FESS_DICTIONARY_PATH=/usr/share/elasticsearch/config/dictionary"
        ulimits:
            memlock:
              soft: -1
              hard: -1
            nofile:
              soft: 65535
              hard: 65535
        volumes:
            - ./elasticsearch/data:/usr/share/elasticsearch/data
            - ./elasticsearch/dictionary:/usr/share/elasticsearch/config/dictionary
        ports:
            - 9200:9200

ただし、これで docker-compose up すると
elasticsearch が立ち上がってないから fess が落ちたよ!」
と言われることがあります。

depends_onで指定はしていますが、内部の起動タイミングが合わないことがあるようです。

先にelasticsearchだけ立ち上げておけばよいのですが、よい解決法がありましたら教えてください。

Djangoのテンプレートへの検索窓追加

こちらのサンプルに従って検索窓をtemplateに配置します。
以下はサイトのURLが https://yoursite.co.jp の場合の例です。

<form id="searchForm" method="get" action="https://yoursite.co.jp/search/">
    <input required pattern=".*\S+.*" id="query" type="text" name="q" maxlength="1000" autocomplete="off">
    <input type="submit" name="search" value="検索">
</form>

サンプルとの差分としては、required pattern=".*\S+.*" の部分です。
これがないと空、またはスペースのみの状態で検索ボタンが押せるのですが、
空で検索するとなぜか私の環境の場合はエラー画面となってしまったので追加しています。

Nginxの設定ファイル修正

フロントからのリクエストパスに応じて振り分けていきます。
以下の例では、/static, /admin/ などのDjangoのパスをまず設定し、
Fess関連のパスをfessコンテナに転送しています。
(とりあえず見つけた範囲を設定していますが他にもあるかもしれません)

最後にそれ以外のパスをdjangoコンテナに転送しています。
(Fess関連のパスがDjangoのパスと重複しないようにする必要があります)

nginx.conf
upstream django {
    ip_hash;
    server django:8001;
}

server {
    listen      8000;
    server_name 127.0.0.1;
    charset     utf-8;

    location /static {
        alias /static;
    }

    location /admin/ {
         deny all;
    }

    location /search/ {
        proxy_pass http://fess:8080/search/;
    }

    location /css/ {
        proxy_pass http://fess:8080/css/;
    }

    location /js/ {
        proxy_pass http://fess:8080/js/;
    }

    location /images/ {
        proxy_pass http://fess:8080/images/;
    }

    location /go/ {
        proxy_pass http://fess:8080/go/;
    }

    location /cache/ {
        proxy_pass http://fess:8080/cache/;
    }

    location / {
        uwsgi_pass  django;
        include     /etc/nginx/uwsgi_params;
    }
}

server_tokens off;

Fessの設定

ブラウザで http://[IP address]:8080 にアクセスするとFessの画面が表示されます。
ログインすると管理画面が表示されますので、クロールの設定やスケジューラの設定をしてください。
詳細はFess公式サイトに記載されていますので、自分がハマったところだけ記載しておきます。

(以下はすごく素人的なハマりだと思いますので、熟練者の方は生暖かく見守りください)

Fessからのクロール先のURLはlocalhostでは機能しません。
なぜなら、fessコンテナから見たlocalhostは、通常fessコンテナ自身を表すからです。

また、クロール先を http://django:8001http://nginx:8000 にすると一見インデックスが作成されているように見えます。
しかし、検索結果のリンクURLも上記アドレスとなってしまうため、検索結果からサイトにアクセスできません。
こちらコメントにて「パスマッピング」の設定をすればよいとのご指摘をいただきました!

なので、クロール先は https://yoursite.co.jp にしましょう。
この場合、クロールのリクエストは一旦外に出ることになります。
マシンのFW等でIP制限をしている場合は、マシン自体のグローバルIPを指定しないとはじかれてしまうので注意が必要です。

その他はまりどころ

以下の設定(Ubuntu 20.04の例)をしないとelasticsearchコンテナが落ちます。

$ sudo sysctl -w vm.max_map_count=262144