docker-composeで動く全文検索ありのIMAPサーバを作る

@aintek on Sun Mar 10 2019
27.2 min

目次

はじめに

IMAPサーバを導入するにあたり、Dovecotを採用することが多いと思います。
しかし、Dovecotの検索機能では物足りないため、Solr Pluginを使って全文検索をできるようにします。

Dovecotの導入

DovecotのDockerfileは公式では提供されていません。
今回は以下のURLのものをもとに作成します(debian イメージ)。
instrumentisto/dovecot

上記のイメージに加えて、

  1. Solr plugin の設定
  2. ダミーユーザの追加
  3. Localeの設定

を行います。

@@ -6,7 +6,6 @@
 MAINTAINER Instrumentisto Team <developer@instrumentisto.com>

+ARG SOLR_URL="http://solr:8983/"

 # Build and install Dovecot
 # https://anonscm.debian.org/git/collab-maint/dovecot.git/tree/debian/rules?id=fc8f5ddc49b39ee56eb57a082ee34dbd58a30c1d
@@ -24,7 +23,6 @@
             libpq5 libmariadbclient18 libsqlite3-0 \
             libldap-2.4 \
             libgssapi-krb5-2 libk5crypto3 libkrb5-3 \
+            expat libexpat1-dev \

  # Install tools for building
  && toolDeps=" \
@@ -62,7 +60,6 @@
         --with-libcap \
         --with-sql=plugin --with-pgsql --with-mysql --with-sqlite \
         --with-ldap=plugin \
+        --with-solr \
         --with-gssapi=plugin \
         --with-rundir=/run/dovecot \
         --localstatedir=/var \
@@ -124,36 +121,13 @@
  && mkdir -p /var/lib/dovecot \
 && /usr/libexec/dovecot/ssl-params \

+ # add plugins
+ && sed -i -e 's/#mail_plugins = /mail_plugins = $mail_plugins fts fts_solr/g' /etc/dovecot/conf.d/10-mail.conf \
+ && sed -i -e "/^plugin {$/a \  fts_solr = url=${SOLR_URL}solr/dovecot/" \
+           -e "/^plugin {$/a \  fts = solr" \
+                /etc/dovecot/conf.d/90-plugin.conf \
+# Create Dummy User
+ && addgroup -gid 1000 dmygroup \
+ && adduser -uid 1000 --ingroup dmygroup dmyuser \
+ && echo "dmyuser:{plain}password:1000:1000::/home/dmyuser" >> /etc/dovecot/users \
+ && sed -i -e 's,^#mail_location =,mail_location = maildir:~/Maildir,' \
+           -e 's,^#mail_access_groups$,mail_access_groups = dmygroup,' \
+            /etc/dovecot/conf.d/10-mail.conf \
+ && sed -i -e 's,^#disable_plaintext_auth = yes$,disable_plaintext_auth = no,' \
+            /etc/dovecot/conf.d/10-auth.conf \
+
+# Locale
+ && apt-get install -y locales \
+ && echo "ja_JP.UTF-8 UTF-8" >> /etc/locale.gen \
+ && locale-gen ja_JP.UTF-8 \
+# && dpkg-reconfigure locales \
+# && /usr/sbin/update-locale LANG=ja_JP.UTF-8
+
+# Debug 
+# # Cleanup unnecessary stuff
+# && apt-get purge -y --auto-remove \
+#                  -o APT::AutoRemove::RecommendsImportant=false \
+#            $toolDeps $buildDeps \
+# && rm -rf /var/lib/apt/lists/* \
+#           /tmp/*
- # Cleanup unnecessary stuff
- && apt-get purge -y --auto-remove \
-                  -o APT::AutoRemove::RecommendsImportant=false \
-            $toolDeps $buildDeps \
- && rm -rf /var/lib/apt/lists/* \
-           /tmp/*

+ENV LC_ALL=ja_JP.UTF-8

上記の設定でDovecotは動くと思います。
ダミーユーザは dmyuser/password でログインできます。

Solrの導入

SolrはDocker Hubに公式イメージがあるため、それを利用します。
solr

Dovecot用のcoreを作る

Solr で Dovecot用のcoreを作成します。
Solrのイメージを使えるようにして、

docker run -d -P solr create -c dovecot

これでdovecot のcoreが作成されるので、それをホスト側に持ってきます。

Dovecot用にcoreを編集する

作成したcoreだけでは期待通りに動かないので修正します。

dovecot/conf/solrconfig.xml

@@ -850,50 +850,6 @@

        http://wiki.apache.org/solr/SpellCheckComponent
     -->
-  <searchComponent name="spellcheck" class="solr.SpellCheckComponent">
-
-    <str name="queryAnalyzerFieldType">text_general</str>
-
-    <!-- Multiple "Spell Checkers" can be declared and used by this
-         component
-      -->
-
-    <!-- a spellchecker built from a field of the main index -->
-    <lst name="spellchecker">
-      <str name="name">default</str>
-      <str name="field">_text_</str>
-      <str name="classname">solr.DirectSolrSpellChecker</str>
-      <!-- the spellcheck distance measure used, the default is the internal levenshtein -->
-      <str name="distanceMeasure">internal</str>
-      <!-- minimum accuracy needed to be considered a valid spellcheck suggestion -->                                                                    -      <float name="accuracy">0.5</float>
-      <!-- the maximum #edits we consider when enumerating terms: can be 1 or 2 -->
-      <int name="maxEdits">2</int>
-      <!-- the minimum shared prefix when enumerating terms -->
-      <int name="minPrefix">1</int>
-      <!-- maximum number of inspections per result. -->
-      <int name="maxInspections">5</int>
-      <!-- minimum length of a query term to be considered for correction -->
-      <int name="minQueryLength">4</int>
-      <!-- maximum threshold of documents a query term can appear to be considered for correction -->
-      <float name="maxQueryFrequency">0.01</float>
-      <!-- uncomment this to require suggestions to occur in 1% of the documents
-        <float name="thresholdTokenFrequency">.01</float>
-      -->
-    </lst>
-
-    <!-- a spellchecker that can break or combine words.  See "/spell" handler below for usage -->
-    <!--
-    <lst name="spellchecker">
-      <str name="name">wordbreak</str>
-      <str name="classname">solr.WordBreakSolrSpellChecker</str>
-      <str name="field">name</str>
-      <str name="combineWords">true</str>
-      <str name="breakWords">true</str>
-      <int name="maxChanges">10</int>
-    </lst>
-    -->
-  </searchComponent>

   <!-- A request handler for demonstrating the spellcheck component.

@@ -1158,43 +1114,16 @@
       <str>yyyy-MM-dd</str>
     </arr>
   </updateProcessor>
-  <updateProcessor class="solr.AddSchemaFieldsUpdateProcessorFactory" name="add-schema-fields">
-    <lst name="typeMapping">
-      <str name="valueClass">java.lang.String</str>
-      <str name="fieldType">text_general</str>
-      <lst name="copyField">
-        <str name="dest">*_str</str>
-        <int name="maxChars">256</int>
-      </lst>
-      <!-- Use as default mapping instead of defaultFieldType -->
-      <bool name="default">true</bool>
-    </lst>
-    <lst name="typeMapping">
-      <str name="valueClass">java.lang.Boolean</str>
-      <str name="fieldType">booleans</str>
-    </lst>
-    <lst name="typeMapping">
-      <str name="valueClass">java.util.Date</str>
-      <str name="fieldType">pdates</str>
-    </lst>
-    <lst name="typeMapping">
-      <str name="valueClass">java.lang.Long</str>
-      <str name="valueClass">java.lang.Integer</str>
-      <str name="fieldType">plongs</str>
-    </lst>
-    <lst name="typeMapping">
-      <str name="valueClass">java.lang.Number</str>
-      <str name="fieldType">pdoubles</str>
-    </lst>
-  </updateProcessor>

   <!-- The update.autoCreateFields property can be turned to false to disable schemaless mode -->
+  <!--
   <updateRequestProcessorChain name="add-unknown-fields-to-the-schema" default="${update.autoCreateFields:true}"
            processor="uuid,remove-blank,field-name-mutating,parse-boolean,parse-long,parse-double,parse-date,add-schema-fields">
     <processor class="solr.LogUpdateProcessorFactory"/>
     <processor class="solr.DistributedUpdateProcessorFactory"/>
     <processor class="solr.RunUpdateProcessorFactory"/>
   </updateRequestProcessorChain>
+  -->

   <!-- Deduplication

@@ -1285,6 +1214,9 @@
      <queryResponseWriter name="csv" class="solr.CSVResponseWriter"/>
      <queryResponseWriter name="schema.xml" class="solr.SchemaXmlResponseWriter"/>
     -->
+  <queryResponseWriter name="xml"
+                     default="true"
+                     class="solr.XMLResponseWriter" />

   <queryResponseWriter name="json" class="solr.JSONResponseWriter">
     <!-- For the purposes of the tutorial, JSON responses are written as

dovecot/conf/schema.xml

@@ -40,15 +41,29 @@
       <filter class="solr.PorterStemFilterFactory"/>
     </analyzer>
   </fieldType>
+  <fieldType name="text_ja" class="solr.TextField" autoGeneratePhraseQueries="false" positionIncrementGap="100">
+    <analyzer>
+      <tokenizer class="solr.JapaneseTokenizerFactory" mode="search"/>
+      <filter class="solr.JapaneseBaseFormFilterFactory"/>
+      <filter class="solr.JapanesePartOfSpeechStopFilterFactory" tags="lang/stoptags_ja.txt"/>
+      <filter class="solr.CJKWidthFilterFactory"/>
+      <filter class="solr.StopFilterFactory" words="lang/stopwords_ja.txt" ignoreCase="true"/>
+      <filter class="solr.JapaneseKatakanaStemFilterFactory" minimumLength="4"/>
+      <filter class="solr.LowerCaseFilterFactory"/>
+    </analyzer>
+  </fieldType>
   <field name="_version_" type="long" indexed="true" stored="true"/>
   <field name="bcc" type="text_en_splitting" indexed="true" stored="false"/>
-  <field name="body" type="text_en_splitting" indexed="true" stored="false"/>
+<!--  <field name="body" type="text_en_splitting" indexed="true" stored="false"/>-->
+  <field name="body" type="text_ja" indexed="true" stored="true"/>
   <field name="box" type="string" indexed="true" required="true" stored="true"/>
   <field name="cc" type="text_en_splitting" indexed="true" stored="false"/>
   <field name="from" type="text_en_splitting" indexed="true" stored="false"/>
-  <field name="hdr" type="text_en_splitting" indexed="true" stored="false"/>
+<!--  <field name="hdr" type="text_en_splitting" indexed="true" stored="false"/>-->
+  <field name="hdr" type="text_ja" indexed="true" stored="false"/>
   <field name="id" type="string" indexed="true" required="true" stored="true"/>
-  <field name="subject" type="text_en_splitting" indexed="true" stored="false"/>
+<!--  <field name="subject" type="text_en_splitting" indexed="true" stored="false"/>-->
+  <field name="subject" type="text_ja" indexed="true" stored="true"/>
   <field name="text" type="text_en_splitting" multiValued="true" indexed="true" stored="false"/>
   <field name="to" type="text_en_splitting" indexed="true" stored="false"/>
   <field name="uid" type="long" indexed="true" required="true" stored="true"/>

上記の編集により、ヘッダ、件名、本文のフィールドを日本語で検索可能にしています。
また、以下のファイルをdovecot/config/ 以下に配置します。

  • mapping-FoldToASCII.txt
  • mapping-ISOLatin1Accent.txt

これでSolrの設定ファイルはできました。

Solr のDockerfileを作る

上記で作成したcoreを配置するようなDockerfileを作ります。
また、Localeも変更しています。

FROM solr:latest

USER root
RUN apt-get -y update && apt-get -y upgrade

# locale
RUN apt-get -y install locales \
 && localedef -f UTF-8 -i ja_JP ja_JP.UTF-8
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8

USER solr

COPY --chown=solr:solr ./dovecot /opt/solr/server/solr/dovecot

docker-compose

Dovecot、Solrのタグ付けをして、docker-composeの設定ファイルを作成します。

services:
  mail:
    image: dovecot-debian:v1.0
    ports:
    - "143:143"
    depends_on:
    - solr

  solr:
    image: solr-dovecot:v1.0
    ports:
    - "8983:8983"

まとめ

上記の手順により、全文検索ができるIMAPサーバが作成できたと思います。
確認は、

doveadm fts rescan -u dmyuser

をして、IMAPのコマンドの

SEARCH text "テスト"

や、shellから

doveadm search -u dmyuser MAILBOX INBOX subject "テスト"

などで全文検索出来ていることが確認できると思います。

日別に記事を見る
興味のあるタイトルをクリック