CLOVER🍀

That was when it all began.

Apache Solr 5.xのレプリケーションを、Docker Composeを使って試す

Docker Composeを試してみたので、これを使ってApache Solrのレプリケーションを構成してみたいと思います。

Index Replication | Apache Solr Reference Guide 6.6

Solrのレプリケーションは、マスターとスレーブがあって、それぞれの設定をsolrconfig.xmlにすればよいみたいです。

参考)
https://cwiki.apache.org/confluence/display/solr/Making+and+Restoring+Backups+of+SolrCores

tree-tips: solrのReplicationHandlerで簡単レプリケーション! | Apache Solr

moco(beta)'s backup: Solrのレプリケーション機能を試す (1)

第5回 Solr4でレプリケーションを構築する

LINE Engineering Blog

Solrをイジった備忘録その3(レプリケーション編)

このあたりを参考にSolrを設定します。

Dockerfile

で、Docker Composeを使って構成すると言いましたが、とりあえず普通にDockerfileを書いてDockerイメージをビルドします。
Dockerfile

FROM ubuntu:latest

ENV JAVA_HOME /usr/lib/jvm/java-8-oracle

ENV SOLR_VERSION 5.3.0
ENV SOLR_ARCHIVE solr-${SOLR_VERSION}.tgz

ENV SOLR_INSTALL_DIR /opt
ENV SOLR_DATA_DIR /var/solr
ENV SOLR_PORT 8983

ENV SOLR_CORE_NAME mycore

## Oracle JDK 8インストール
RUN echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | debconf-set-selections && \
    apt-get install -y software-properties-common && \
    add-apt-repository -y ppa:webupd8team/java && \
    apt-get update && \
    apt-get install -y oracle-java8-installer

## ツールインストール
RUN apt-get install -y lsof \
                       curl \
                       vim

## 日本語環境サポート
RUN apt-get install -y language-pack-ja-base \
                       language-pack-ja \
                       ibus-mozc \
                       man \
                       manpages-ja && \
    update-locale LANG=ja_JP.UTF-8 LANGUAGE=ja_JP:ja

ENV LANG ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8
ENV LC_CTYPE ja_JP.UTF-8

## Solrインストール
RUN wget -q http://ftp.jaist.ac.jp/pub/apache//lucene/solr/${SOLR_VERSION}/${SOLR_ARCHIVE} && \
    tar -zxvf ${SOLR_ARCHIVE} solr-${SOLR_VERSION}/bin/install_solr_service.sh && \
    solr-${SOLR_VERSION}/bin/install_solr_service.sh ${SOLR_ARCHIVE} -i ${SOLR_INSTALL_DIR} -d ${SOLR_DATA_DIR} -p ${SOLR_PORT}

EXPOSE ${SOLR_PORT}

## コア作成
RUN service solr restart && \
    sudo -u solr ${SOLR_INSTALL_DIR}/solr/bin/solr create -c ${SOLR_CORE_NAME} && \
    service solr stop && \
    rm /var/solr/data/${SOLR_CORE_NAME}/conf/managed-schema

ADD solrconfig.xml /var/solr/data/${SOLR_CORE_NAME}/conf/solrconfig.xml
ADD schema.xml /var/solr/data/${SOLR_CORE_NAME}/conf/schema.xml

ADD entrypoint.sh entrypoint.sh
RUN chmod a+x entrypoint.sh

## 実行コマンド
ENTRYPOINT ["./entrypoint.sh"]

いくつか設定ファイルのコピーやシェルスクリプトが登場しますが、それは後でまた載せます。

今回は、こんな名前でDockerイメージとして作成しました。

$ docker build -t kazuhira/apache-solr-replication:5.3.0 .

レプリケーション向けの設定

Dockerfileが置かれているカレントディレクトリの内容は、このようになっています。

$ ls -l
合計 84
-rw-rw-r-- 1 xxxxx xxxxx  1843  9月 22 17:16 Dockerfile
-rw-rw-r-- 1 xxxxx xxxxx   520  9月 22 17:32 entrypoint.sh
-rw-rw-r-- 1 xxxxx xxxxx 10784  9月 22 20:44 schema.xml
-rw-rw-r-- 1 xxxxx xxxxx 62463  9月 22 20:43 solrconfig.xml

Dockerfileの中でSolrのコアを作成するようにしているのですが、今回のコアの名前は「mycore」としています。

ENV SOLR_CORE_NAME mycore

で、schema.xmlとsolrconfig.xmlの2つのファイルは、ここで作ったコアに対して反映するようにDockerfileを書いています。

schema.xmlの内容はお好みで…ですが、今回はこのくらいの少ない内容で。
※いろいろ端折っています

    <field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />
    <field name="_version_" type="long" indexed="true" stored="true"/>

    <field name="name" type="text_ja" indexed="true" stored="true"/>

  <!-- Field to use to determine and enforce document uniqueness. 
      Unless this field is marked with required="false", it will be a required field
   -->
    <uniqueKey>id</uniqueKey>

Kuromojiは、ユーザー定義辞書を含めるようにしています。

    <fieldType name="text_ja" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="false">
      <analyzer>
        <!-- <tokenizer class="solr.JapaneseTokenizerFactory" mode="search"/>  -->
        <tokenizer class="solr.JapaneseTokenizerFactory" mode="search" userDictionary="lang/userdict_ja.txt"/>

solrconfig.xmlですが、schema.xmlを使うようにClassicIndexSchemaFactoryを使うようにしているのと、レプリケーション向けに以下のような設定を入れています。

  <requestHandler name="/replication" class="solr.ReplicationHandler">
    <lst name="master">
      <str name="enable">${solr.repl.master:false}</str>
      <str name="replicateAfter">startup</str>
      <str name="replicateAfter">commit</str>
      <str name="replicateAfter">optimize</str>
      <str name="confFiles">schema.xml,stopwords.txt,synonyms.txt,lang/stopwords_ja.txt,lang/userdict_ja.txt</str>
      <str name="backupAfter">optimize</str>
    </lst>
    <int name="maxNumberOfBackups">3</int>
    <lst name="slave">
      <str name="enable">${solr.repl.slave:false}</str>
      <str name="masterUrl">http://solr-master:8983/solr/mycore/replication</str>
      <str name="pollInterval">00:00:05</str>
      <str name="httpConnTimeout">5000</str>
      <str name="httpReadTimeout">10000</str>
    </lst>
  </requestHandler>

こちらを参考に、システムプロパティでマスターとスレーブを切り替える設定しています。

tree-tips: solrのReplicationHandlerで簡単レプリケーション! | Apache Solr

マスター側の設定は、レプリケーションのトリガーは起動時、コミット時、オプティマイズ時

      <str name="replicateAfter">startup</str>
      <str name="replicateAfter">commit</str>
      <str name="replicateAfter">optimize</str>

の3つとし、設定ファイルは以下の内容をレプリケーションします。

      <str name="confFiles">schema.xml,stopwords.txt,synonyms.txt,lang/stopwords_ja.txt,lang/userdict_ja.txt</str>

最後に、オプティマイズ後にバックアップを取るように設定。

      <str name="backupAfter">optimize</str>

バックアップの数は3つまで。

    <int name="maxNumberOfBackups">3</int>

で、マスターとして機能させるかどうかはシステムプロパティで設定します、と。

      <str name="enable">${solr.repl.master:false}</str>

スレーブ側は、有効/無効をシステムプロパティで切り替えるとともに、タイムアウトなどの設定を入れています。

    <lst name="slave">
      <str name="enable">${solr.repl.slave:false}</str>
      <str name="masterUrl">http://solr-master:8983/solr/mycore/replication</str>
      <str name="pollInterval">00:00:05</str>
      <str name="httpConnTimeout">5000</str>
      <str name="httpReadTimeout">10000</str>
    </lst>

Solrのレプリケーションは、スレーブからマスターに問い合わせにいく形なので、そのエンドポイントをまずは指定。

      <str name="masterUrl">http://solr-master:8983/solr/mycore/replication</str>

ホスト名は、「solr-master」としています。あと、ポーリング間隔は5秒で。

      <str name="pollInterval">00:00:05</str>

で、この両者の切り替えなんですけど、DockerのENTRYPOINTに設定するシェルスクリプト内で、どうこうしてしまおうという作戦。シェルスクリプトの中身は、こんな感じです。
entrypoint.sh

#!/bin/bash

if [ "$1" == "master" ]; then
    grep 'SOLR_OPTS="$SOLR_OPTS -Dsolr.repl.master=true"' /var/solr/solr.in.sh || \
        echo 'SOLR_OPTS="$SOLR_OPTS -Dsolr.repl.master=true"' >> /var/solr/solr.in.sh
elif [ "$1" == "slave" ]; then
    grep 'SOLR_OPTS="$SOLR_OPTS -Dsolr.repl.slave=true"' /var/solr/solr.in.sh || \
        echo 'SOLR_OPTS="$SOLR_OPTS -Dsolr.repl.slave=true"' >> /var/solr/solr.in.sh
else
    echo "require argument 'master' or 'slave'"
    exit 1
fi

service solr start && tail -f /dev/null

「master」と入力されると/var/solr/solr.in.shに「-Dsolr.repl.master=true」を追加し、「slave」と入力されると「-Dsolr.repl.slave=true」を追加するようにしました。

ここまでで、Dockerの準備はおしまい。

Docker Composeの設定

あとは、作成したDockerイメージを使ってレプリケーションを構成するための、Docker Composeの設定を書きます。
docker-compose.yml

master:
  image: kazuhira/apache-solr-replication:5.3.0
  ports:
    - "8983:8983"
  container_name: solr-master
  hostname: solr-master
  command: "master"
slave:
  image: kazuhira/apache-solr-replication:5.3.0
  ports:
    - "8983"
  links:
    - master
  command: "slave"

同じDockerイメージを使って、「command」で追加引数を入れます。あと、マスターのみホスト側から参照するポートは固定にしました。

動作確認

それでは、起動してみます。「docker-compose.yml」が配置されているディレクトリで、以下のコマンドを実行。

$ docker-compose up -d
Creating solr-master...
Creating solrreplication_slave_1...

コンテナが2つ起動します。

利用しているポートの割り振りは、こんな感じで。

$ docker-compose ps
         Name                    Command           State            Ports          
----------------------------------------------------------------------------------
solr-master               ./entrypoint.sh master   Up      0.0.0.0:8983->8983/tcp  
solrreplication_slave_1   ./entrypoint.sh slave    Up      0.0.0.0:32820->8983/tcp

この場合、それぞれ以下のURLでSolrの管理画面にアクセスできます。

マスター http://localhost:8983/solr/#/mycore
スレーブ http://localhost:32820/solr/#/mycore

マスター側にデータ登録。対象データは、とりあえずこんな感じ。
data1.json

[
  { "id": 1, "name": "[改訂新版] Apache Solr入門 ~オープンソース全文検索エンジン" },
  { "id": 2, "name": "Apache Lucene 入門 〜Java・オープンソース・全文検索システムの構築" }
]

実行。

$ curl -H 'Content-Type: application/json' 'http://localhost:8983/solr/mycore/update?commit=true' --data-binary @data1.json

マスターで検索。

$ curl 'http://localhost:8983/solr/mycore/select?wt=json&indent=true' -d '{ "query": "*:*" }'{
  "responseHeader":{
    "status":0,
    "QTime":21,
    "params":{
      "indent":"true",
      "json":"{ \"query\": \"*:*\" }",
      "wt":"json"}},
  "response":{"numFound":2,"start":0,"docs":[
      {
        "id":"1",
        "name":"[改訂新版] Apache Solr入門 ~オープンソース全文検索エンジン",
        "_version_":1513016793776521216},
      {
        "id":"2",
        "name":"Apache Lucene 入門 〜Java・オープンソース・全文検索システムの構築",
        "_version_":1513016793951633408}]
  }}

スレーブで検索。

$ curl 'http://localhost:32820/solr/mycore/select?wt=json&indent=true' -d '{ "query": "*:*" }'
{
  "responseHeader":{
    "status":0,
    "QTime":32,
    "params":{
      "indent":"true",
      "json":"{ \"query\": \"*:*\" }",
      "wt":"json"}},
  "response":{"numFound":2,"start":0,"docs":[
      {
        "id":"1",
        "name":"[改訂新版] Apache Solr入門 ~オープンソース全文検索エンジン",
        "_version_":1513016793776521216},
      {
        "id":"2",
        "name":"Apache Lucene 入門 〜Java・オープンソース・全文検索システムの構築",
        "_version_":1513016793951633408}]
  }}

OKそうですね。

スレーブを追加してみる

ここで、Docker Composeでスレーブを3台にしてみます。

$ docker-compose scale slave=3
Creating and starting 2... done
Creating and starting 3... done

ポートの状態。

$ docker-compose ps
         Name                    Command           State            Ports          
----------------------------------------------------------------------------------
solr-master               ./entrypoint.sh master   Up      0.0.0.0:8983->8983/tcp  
solrreplication_slave_1   ./entrypoint.sh slave    Up      0.0.0.0:32820->8983/tcp 
solrreplication_slave_2   ./entrypoint.sh slave    Up      0.0.0.0:32821->8983/tcp 
solrreplication_slave_3   ./entrypoint.sh slave    Up      0.0.0.0:32822->8983/tcp

追加したスレーブからも、同じようにデータが検索できます。

$ curl 'http://localhost:32821/solr/mycore/select?wt=json&indent=true' -d '{ "query": "*:*" }'
{
  "responseHeader":{
    "status":0,
    "QTime":34,
    "params":{
      "indent":"true",
      "json":"{ \"query\": \"*:*\" }",
      "wt":"json"}},
  "response":{"numFound":2,"start":0,"docs":[
      {
        "id":"1",
        "name":"[改訂新版] Apache Solr入門 ~オープンソース全文検索エンジン",
        "_version_":1513016793776521216},
      {
        "id":"2",
        "name":"Apache Lucene 入門 〜Java・オープンソース・全文検索システムの構築",
        "_version_":1513016793951633408}]
  }}

ここでさらに、マスターにデータ登録。
data2.json

[
  { "id": 3, "name": "高速スケーラブル検索エンジン ElasticSearch Server" },
  { "id": 4, "name": "Beginning Java EE 6 GlassFish 3で始めるエンタープライズJava" },
  { "id": 5, "name": "Javaエンジニア養成読本" }
]

登録。

$ curl -H 'Content-Type: application/json' 'http://localhost:8983/solr/mycore/update?commit=true' --data
-binary @data2.json

追加した3台目になるスレーブに対して、検索を実行。

$ curl 'http://localhost:32822/solr/mycore/select?wt=json&indent=true' -d '{ "query": "*:*" }'
{
  "responseHeader":{
    "status":0,
    "QTime":25,
    "params":{
      "indent":"true",
      "json":"{ \"query\": \"*:*\" }",
      "wt":"json"}},
  "response":{"numFound":5,"start":0,"docs":[
      {
        "id":"1",
        "name":"[改訂新版] Apache Solr入門 ~オープンソース全文検索エンジン",
        "_version_":1513016793776521216},
      {
        "id":"2",
        "name":"Apache Lucene 入門 〜Java・オープンソース・全文検索システムの構築",
        "_version_":1513016793951633408},
      {
        "id":"3",
        "name":"高速スケーラブル検索エンジン ElasticSearch Server",
        "_version_":1513017253929418752},
      {
        "id":"4",
        "name":"Beginning Java EE 6 GlassFish 3で始めるエンタープライズJava",
        "_version_":1513017253932564480},
      {
        "id":"5",
        "name":"Javaエンジニア養成読本",
        "_version_":1513017253937807360}]
  }}

OKそうですね。

最後に、スレーブは減らしておきます…。

$ docker-compose scale slave=1
Stopping solrreplication_slave_2... done
Stopping solrreplication_slave_3... done
Removing solrreplication_slave_3... done
Removing solrreplication_slave_2... done

$ docker-compose ps
         Name                    Command           State            Ports          
----------------------------------------------------------------------------------
solr-master               ./entrypoint.sh master   Up      0.0.0.0:8983->8983/tcp  
solrreplication_slave_1   ./entrypoint.sh slave    Up      0.0.0.0:32820->8983/tcp

まとめ

というわけで、Apache Solrでレプリケーションの設定をして、Docker Composeでレプリケーションを構成するのと、スレーブの増減をやってみました。

いろいろ確認とかしやすくなるので、使い捨てられる割り切った構成が取れるの、便利ですね。

※設定ファイルの反映は、確認しましたがこのエントリ上は端折りました…