[ 言語選択: English ]

1. [docker+k8] Apache Solrを利用した全文検索エンジンの作り方

ゼミ室10のThinkPad(Ubuntu 20.04)を想定した作業手順を掲載していますが、演習室のLinux(CentOS)の他、macOSやWindows10でも同様の作業が可能です。

macOS環境
  • Java開発環境(JDK-11 amd64版)がインストールされている (Adoptium OpenJDK)

  • Docker Desktop for macOSがインストールされている (公式ガイド)

Windows10環境
  • WSL2環境が設定されている (公式ガイド)

  • MS StoreからUbuntu 20.04 LTSをインストールする (公式ストア)

  • Docker Desktop for Windowsがインストールされている (公式ガイド)

  • WSL2環境(Ubuntu 20.04 LTS)にJava開発環境(javacコマンド)が導入されている (install default-jdk)

Windows10環境ではWSL2 + Ubuntu 20.04を利用しているので、ゼミ室10とほぼ同じです。

2. 用語について

Wikipediaの説明は、日本語版と英語版で一致するとは限りません。CrawlerとScrapingは明確に区別される概念ですが、日本語版のWikipediaでは、ほぼ同じ意味として説明されています。

Crawler(クローラー)

クローラ(Crawler)とは、ウェブ上の文書や画像などを周期的に取得し、自動的にデータベース化するプログラムである。「ボット(Bot)」、「スパイダー」、「ロボット」などとも呼ばれる。 Wikipedia - クローラー

Scraping(スクレイピング)

ウェブスクレイピング(英: Web scraping)とは、ウェブサイトから情報を抽出するコンピュータソフトウェア技術のこと。ウェブ・クローラー[1]あるいはウェブ・スパイダー[2]とも呼ばれる。 Wikipedia - ウェブスクレイピング

Scrapingは取得したWebページのデータから意味ある情報を作り出す行為を指します。Crawlerは単純にWebページのデータをデータベースとしてまとめるプログラムの動作・機能を指します。

3. 全文検索エンジンの概要

全文検索エンジンを構築するためには、CrawlerによってWebページを辿っていき、検索対象のデータを取得する必要があります。さらに取得したデータを解析し、必要なキーワードなどの抽出、素早く検索するための索引を付けるなどのデータベース機能が必要となります。

tutorial solr crawler.20210517 SCCP SolrCrawler

3.1. Crawlerに求められる機能

Crawlerには次のような機能が求められます。

  • 特定のURLからリンクを再帰的にたどる機能

  • 特定のリンクを辿らないための除外機能

  • 各Webページのデータを取得・蓄積するかを判断するための機能

  • 繰り返し同じWebページを辿らないためのスクリーニング機能

  • Webサーバーに過度な負荷を与えないための負荷調整機能 (アクセス間隔の設定、他)

  • 文字化けが発生しないように、Shift-JISやEUCなどの日本語文字コードへの対応

3.2. 全文検索エンジン用データベースに求められる機能

今回利用するApache Solrには次のような機能があります。

  • 転置インデックスによる索引機能

  • スコアリング機能 (検索対象による重み付け機能)

  • ハイライト機能 (検索結果の一部テキストと該当部分を取得する機能)

  • ファセット検索 (マッチした検索結果の属性情報を取得する機能)

3.3. その他の機能

今回は対象としませんが、例えば、Excelファイルなど、そのままではテキスト情報の抽出が難しいファイルについて、文字データの抽出などを行なう機能は、Apache Tikaによって可能になります。

4. 作業の概要と準備作業

全文検索エンジン・システムを構築するためには、前述のCrawler機能とデータベース機能をそれぞれ構築する必要があります。 順序は逆になりますが、データベース機能に該当するApache Solrの準備をしてから、Crawlerの構築をします。

4.1. 作業用ディレクトリの準備

作業を始める前に、任意の名前で作業用のディレクトリを準備して移動しておきます。

## ディレクトリ名は好みに応じて変更してください。
$ mkdir tutorial-solr-crawler
$ cd tutorial-solr-crawler

1度作成したディレクトリを2回作成する必要はありません。 次回作業をする際には、このディレクトリに移動してから作業を進めてください。

4.2. Apache Solrの利用 (Docker)

Apache Solrは単純に利用するだけであれば、非常に簡単に利用を開始することができます。

Dockerが利用できる環境であれば、次のようなコマンドで起動することができます。

$ mkdir var.solr
$ chmod 2777 var.solr
$ podman run -it --rm -d -p 8983:8983 -v `pwd`/var.solr:/var/solr --name solr docker.io/library/solr:8.11

-vオプションを指定することで、設定や保存したデータを保存することができます。

動作状況は次のようにdockerのpsモードで確認できます。

$ podman ps
CONTAINER ID  IMAGE                        COMMAND          CREATED             STATUS                 PORTS                   NAMES
dbf51c22bb84  docker.io/library/solr:8.11  solr-foreground  About a minute ago  Up About a minute ago  0.0.0.0:8983->8983/tcp  solr

Webブラウザからlocalhost(127.0.0.1)の8983番ポートに接続することで、 Web-UIが利用できます。 http://127.0.0.1:8983/

4.2.1. データ格納領域の準備

testcoreという名称で、データを保存する領域を確保します。

$ podman exec -it solr /opt/solr/bin/solr create_core -c testcore
WARNING: Using _default configset with data driven schema functionality. NOT RECOMMENDED for production use.
         To turn off: bin/solr config -c testcore -p 8983 -action set-user-property -property update.autoCreat
eFields -value false

Created new core 'testcore'

Web CLI (http://localhost:8983/solr/#/testcore/) にアクセスできれば、成功です。

4.2.2. データ項目の準備

今回は content という名称のフィールドを準備します。 下記のコマンドは非常に長いですが、改行を含めて curl〜 schema まで全てコピーし、ペーストして実行してください。

$ curl -X POST -H 'Content-type:application/json' --data-binary '{
  "add-field" : {
    "name" : "content",
    "type" : "text_ja",
    "multiValued" : "true",
    "indexed" : "true",
    "required" : "true",
    "stored" : "true"
  }
}' http://localhost:8983/solr/testcore/schema

この操作をしなくても、デフォルトの設定では自動的にフィールドは作成されるため、任意のフィールドに情報を格納できます。 ただし、日本語として認識させたい場合には、明示的に"text_ja"などのタイプを指定する必要があります。

コマンドを実行し、下記のような出力が得られればcontentフィールドの追加に成功しています。

{
  "responseHeader":{
    "status":0,
    "QTime":851}}

4.3. Crawlerの利用 (Java + crawler4j)

Crawlerは任意のプログラミング言語で利用できますが、今回はJava言語用のcrawler4jライブラリを利用します。 あらかじめ日本語などに対応したコードを準備しています。

$ git clone https://github.com/YasuhiroABE/crawler4j-japanese-contents.git
$ cd crawler4j-japanese-contents
$ mvn compile

mvnコマンドが最後に次のようなメッセージを出力すれば成功です。

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:12 min
[INFO] Finished at: 2021-11-29T06:58:28Z
[INFO] ------------------------------------------------------------------------

4.3.1. mvnコマンドが存在しない場合の対応

mvnコマンドがない場合には、次のように対応します。

4.3.2. WSL2(Windows)環境下でのmvnコマンドのインストール

WSL2環境下で次のようなコマンドを入力します。

## WindowsでWSL2を利用している場合
$ sudo apt update && sudo apt install maven

4.3.3. macOSなどの環境でmvnコマンドを利用する方法

演習室のCentOSの環境であれば、~yasu-abe/bin/mvn を利用してください。 これが利用できない場合には、macOS, WSL2を含めて次の要領でダウンロードしてください。

## macOSを利用している場合
$ wget https://dlcdn.apache.org/maven/maven-3/3.9.2/binaries/apache-maven-3.9.2-bin.tar.gz
$ tar xvzf apache-maven-3.2.2-bin.tar.gz
$ export PATH=$(pwd)/apache-maven-3.9.2/bin:$PATH
4.3.3.1. mvnコマンドをインストールしたら

改めて mvn compile を実行しましょう。

$ mvn compile

4.3.4. コンパイルに失敗する場合

下記のようなエラーメッセージを表示し、mvn compile コマンドを実行しても失敗する場合があります。

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  12.135 s
[INFO] Finished at: 2022-06-26T21:02:53+09:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal on project crawlerdemo: Could not resolve dependencies for project org.yasundial.crawler.app:crawlerdemo:jar:1.0-SNAPSHOT: Failed to collect dependencies at edu.uci.ics:crawler4j:jar:4.4.0 -> com.sleepycat:je:jar:5.0.84: Failed to read artifact descriptor for com.sleepycat:je:jar:5.0.84: Could not transfer artifact com.sleepycat:je:pom:5.0.84 from/to maven-default-http-blocker (http://0.0.0.0/): Blocked mirror for repositories: [oracleReleases (http://download.oracle.com/maven, default, releases+snapshots)] -> [Help 1]

自分でapache-mavenをダウンロードした場合には、apache-maven-3.9.2/conf/settings.xml を編集し、次のようにタグ(-->)の場所を変更し、mirrors全体をコメントにします。

<mirrors>
  <!--  mirror
  <mirror>
     ...
  </mirror>
  <mirror>
    <id>maven-default-http-blocker</id>
    <mirrorOf>external:http:*</mirrorOf>
    <name>Pseudo repository to mirror external repositories initially using HTTP.</name>
    <url>http://0.0.0.0/</url>
    <blocked>true</blocked>
  </mirror>
  -->

あるいは、<mirrors></mirrors>のように内容を削除することもできます。

macOSの場合は、brewでmvnコマンドを導入した場合、 /usr/local/Cellar/maven/<version>/libexec/conf にsettings.xmlファイルが存在します。

いまのところUbuntu 20.04 LTSのmvnコマンドでは、同様の現象は発生していません。WSl2を利用している場合は、Ubuntu 20.04/22.04 を利用している場合、同様に対応の必要はないはずですが、Ubuntuでは /etc/maven/settings.xml にあります。

settings.xmlファイルを編集したら改めて、compileしてください。

## 修正を実施して成功するまで繰り返し実行
$ mvn compile

4.3.5. config.propertiesの編集

動作確認のため、./config.properties ファイルを 次のように編集します。 自分でWebページを持っていれば、そのページを指定するなどしてください。

サーバー管理者に迷惑をかけないように、学外のWebサーバーは指定しないでください。

## crawl target URL
TARGET_URL=https://u-aizu.ac.jp/~yasu-abe/ja/

## visit patterns escape rules:
VISIT_URL_PATTERN=^https://u-aizu.ac.jp/~yasu-abe/.+$

## crawler4j Storage Directory (You don't need to keep it on a persistent volume in k8s.)
CRAWLER4J_STORAGE_DIR=data/crawl/strage

## Pass-through rules for the shouldVisit method.
OK_FILTER=.*(\\.(text|txt|html|htm|yaml|yml|csv|json))$|.*/$

4.3.6. Crawlerの実行

config.propertiesファイルを編集したら、次のようにcrawlerを実行します。

$ mvn exec:java

対象とするWebサーバーを外部のサイトにする場合には、利用規約などで利用用途に制限がないか確認してください。

また次のセクションのソースコードの変更を実施すると、このコマンドではデータベースにコンテンツを保存できないことからエラーが表示される場合があります。 ファイル変更後のCrawlerの起動は、次のセクションに記載されているように必要な環境変数を設定してから実行してください。

4.4. git cloneから実行まで

Asciinema によるCrawlerをgit cloneしてから実行するまでの流れを確認してください。

5. CrawlerからSolrへの接続

先ほど動作確認を行なったCrawlerのプログラム・コードを変更することで、Solrへの接続を行ないます。

5.1. Solr接続用ライブラリの追加

引き続き、 crawler4j-japanese-contents を利用し、Solr接続用のコードを追加します。

まず pom.xml ファイルを変更し、必要なライブラリの情報を追加します。 他のdependicyの情報と平行して、次の情報を追加します。

  <dependency>
      <groupId>org.apache.solr</groupId>
      <artifactId>solr-solrj</artifactId>
      <version>8.11.2</version>
  </dependency>

変更したら、mvn compileを実行し、変更に問題がないことを確認します。

$ mvn compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.192 s
[INFO] Finished at: 2021-05-16T22:33:55+09:00
[INFO] ------------------------------------------------------------------------

5.2. Javaコードの変更

src/main/java/org/yasundial/crawler/app/MySolr.java を新規に作成し、次のようなコードを配置します。

なお、このコードは、https://kazuhira-r.hatenablog.com/entry/20150912/1442056103 を元にしています。

package org.yasundial.crawler.app;

import java.io.IOException;
import java.util.Map;

import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.common.SolrInputDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MySolr {
    private static final MySolr INSTANCE = new MySolr();

    private SolrClient client;

    private MyConfig config = MyConfig.getInstance();
    private Logger logger = LoggerFactory.getLogger(getClass());

    private MySolr() {
        client = new HttpSolrClient.Builder(config.getString("MYC_SOLR_URL")).build();
    }

    public static MySolr getInstance() {
        return INSTANCE;
    }

    public void addDocument(Map<String, Object> document) {
        try {
            SolrInputDocument solrDocument = new SolrInputDocument();

            document.entrySet().forEach(entry -> solrDocument.addField(entry.getKey(), entry.getValue()));

            client.add(solrDocument);
        } catch (IOException | SolrServerException e) {
            logger.info("add Document error.", e);
        }
    }

    public void commit() {
        try {
            client.commit();
        } catch (IOException | SolrServerException e) {
            logger.info("commit error.", e);
        }
    }

    public void close() {
        try {
            client.close();
        } catch (IOException e) {
            logger.info("close error.", e);
        }
    }
}

続いて、src/main/java/org/yasundial/crawler/app/MyCrawler.java の最下行付近を次のように変更します。

        Document jsoupDoc = Jsoup.parse(html);
        System.out.println("title: " + jsoupDoc.title());
        System.out.println(" text: " + jsoupDoc.select("body").text());

        Map<String,Object> document = new LinkedHashMap<>();
        document.put("id", url);
        document.put("content", jsoupDoc.select("body").text());
        MySolr.getInstance().addDocument(document);
        MySolr.getInstance().commit();
    }
}

変更が終ったら、次のように実行します。

$ mvn compile

続いて、新しいプログムを実行しますが、Solrに接続するためのURLを指定します。

$ env MYC_SOLR_URL="http://localhost:8983/solr/testcore"  mvn exec:java

この MYC_SOLR_URL 環境変数は、先ほど追加したMySolr.javaの中で、次のように使われています。

    private MySolr() {
        client = new HttpSolrClient.Builder(config.getString("MYC_SOLR_URL")).build();
    }

5.3. env …​ mvn exec:java コマンドの実行に失敗する場合

答え合わせのためにソースコードを変更した後のファイルが下記からダウンロード可能です。

  • pom.xml (crawler4j-japanese-contents/pom.xml を置き換え)

  • MyCrawler.java (crawler4j-japanese-contents/src/main/java/org/yasundial/crawler/app/MyCrawler.java を置き換え)

  • MySolr.java (crawler4j-japanese-contents/src/main/java/org/yasundial/crawler/app/MySolr.java に作成)

ファイルを置き換えたら改めて環境変数を指定してmvn exec:javaを実行してください。

$ env MYC_SOLR_URL="http://localhost:8983/solr/testcore"  mvn exec:java

5.4. データベースの検索

ここまででCrawlerからSolrへコンテンツの登録が行なわれているはずなので、これを確認します。

$ curl 'http://localhost:8983/solr/testcore/select?q=content%3A*%E3%83%A2%E3%83%87%E3%83%AB*'

結果は次のようになります。

{
  "responseHeader":{
    "status":0,
    "QTime":233,
    "params":{
      "q":"content:*モデル*"}},
  "response":{"numFound":1,"start":0,"numFoundExact":true,"docs":[
      {
        "id":"https://www.u-aizu.ac.jp/~yasu-abe/ja/activity/",
        "content":["YasuhiroABE プロフィール 活動概要 学内のみ Home 活動概要  .... "],
        "_version_":1699964908869779456}]
}}

あるいはSolrのWebコンソールから、登録されている文書を確認することができます。

  • "testcore"の選択

  • "Query"ページに遷移

  • Exec Queryボタンを押下する

tutorial solr crawler.20211129 SolrAdmin testcore

5.4.1. 【実演】Solrに接続するライブラリを追加し、データをSolrに保存するまで

mvn exec:java に成功した後、次の要領でSolrにデータを保存するための変更を行ないます。

6. Ruby+Sinatraアプリからの接続

別のチュートリアル OpenAPI+Sinatraによる検索エンジンの作成 をまだ実施していない場合はこのセクションは飛ばして先に進んでください。

OpenAPI+Sinatraによる検索エンジンの作成では、次のように接続先の情報をハードコードしています。

url = URI::HTTPS.build({:host => "opm00h.u-aizu.ac.jp", :path => '/solr/api/v1/search', :query => "q=sccp&wt=json"})

この URL::HTTPS:host:path に対応する値を変更します。さらに、:port を加えています。 変更後のコードは次のようになります。

:hostに指定しているホスト名について、もしSinatraアプリをDockerやK8s環境で動作させる場合には、localhostや127.0.0.1といった指定ではうまく動作しません。コンテナ環境からlocalhostは自コンテナ自身を意味するので、同一マシンで動作させる場合でも、外部から接続可能なホスト名(192.168.100.25, etc.)を指定する必要があります。

url = URI::HTTP.build({:host => "localhost", :port=> 8983, :path => '/solr/testcore/select',
                         :query => "q=content:#{@param_q}&wt=json&hl=on&hl.fl=content&usePhraseHighLighter=true"})

変更点が大きいのは、実際のシステムでは直接Solrに接続していないからです。 いずれにしても、この程度の変更で、以前作成したアプリケーションと接続させることができました。

6.1. Ruby+Sinatraへの変更作業

以前に作業した docker-sccp-sinatra-sample/ ディレクトリに移動した上で次のように、api/default_api.rb を編集し、最後にcurlコマンドで動作を確認しています。

7. 本格的なサービスを構築するために

今回は開発用にDockerを利用する例として、Apache Solrを取り上げました。

実際に、学内サーチエンジンのようなサービスにするためには、次のような点がまだ不足しています。

  • SolrがSPoF (Single-Point-of-Failure、単一障害点) とならないように、冗長化(SolrCloud化)する

  • Internet-Facing Serverから直接データベース(Solr)サーバーに接続させないためのQuery Parser(クエリー解析器)機能を追加する

7.1. SolrCloudについて

最初のSolrCloudはKurbernetesなどを利用して、複数のSolrサーバーに負荷分散を行ないます。 現在の学内サーチエンジンの構成は次のようになっています。

tutorial solr crawler.20210517 UoASearch SolrCloud

このSolrCloudは4台を2台ペアで構成していて、最大2台までは停止してもサービスを継続できます。 また1つのリクエストは、ペアになっているshard1とshard2に負荷分散されるため、検索スピードは1台より向上します。

これはSSD/HDDで行なわれるRAID-10のような構成ともいえます。

7.2. Query Parserについて

次に入力された検索文字列を解析し、適切に処理する仕組みが必要です。

単純な解析であれば正規表現(Regular Expression, regexp)を利用する事も考えられますが、通常は解析器を利用します。

古典的な解析器は字句解析器(Lexer)と構文解析器(Parser)を組み合せてプログラミングしていましたが、最近では Parsing Expression Grammar (PEG) を応用するなどして構文解析器に字句解析器を組み込む手法が一般的です。

具体的には、flex(lex)+bison(yacc)というアプリケーション(Generator)を利用していた状況から、Ragel, AntLR, PEG* のようなアプリケーションを利用する傾向が強くなっています。

例として、Ruby言語用にPEG形式のParserを生成するParsletライブラリを利用して解析を行なっています。

require 'parslet'

class QueryParser < Parslet::Parser
  rule(:space) { match('\s').repeat(1) }
  rule(:type1) { str('-').maybe >> match('[^"\s]').repeat(1) }
  rule(:type2) { str('-').maybe >> str('"') >> match('[^"]').repeat(1) >> str('"') }
  rule(:query) { (type1.as(:t1) | type2.as(:t1) | space).repeat(1) }
  root(:query)
end

このコードは改善の余地がいろいろありますが、入力された文字列を順番に処理していく流れはつかめるのではないでしょうか。

7.3. 学内サーチエンジンの実装について

https://opm00h.u-aizu.ac.jp/solr/api/v1/searchでアクセスできる学内検索エンジンは、Apache Solrに接続しているPodで、Query Parserを実装しています。しかし、返却するデータはApache Solrの戻り値を加工せずにそのまま返却しています。

意図しない情報漏洩を防ぐためには、この結果も返却して良いものだけを通過させるような機能が必要です。 ここでは学内限定なのと、教育目的もあるため、そのような処理はしていません。

広く公開するサービスを構築する際には、入力と出力の両方を必ずチェックし、意図している情報だけを通過させるようにする必要があります。

8. K8sによる検索サービスの運用

Solrは既にDockerコンテナとして提供されているため、これをK8sで運用することはそれほど難しくありません。

ここからは公式サイトが提供しているSolrコンテナと、先ほど改良したJavaコードを含んだCrawlerコンテナを利用して独自の検索サービスを構築していきます。

8.1. SolrのK8sでの稼動

次のようなYAMLファイルをkubectlコマンドから反映させます。

Solrを複数コンテナで稼動させるには、Solr Cloud用の特別な設定が必要になるので、ここではreplicas:には1を設定してください。

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: solr
spec:
  replicas: 1
  selector:
    matchLabels:
      app: solr
  template:
    metadata:
      labels:
        app: solr
    spec:
      containers:
      - name: solr
        image: solr:8.11
        imagePullPolicy: Always

Serviceオブジェクトは次のようなファイルで定義できます。

---
apiVersion: v1
kind: Service
metadata:
  name: solr
  labels:
    app: solr
spec:
  type: ClusterIP
  selector:
    app: solr
  ports:
  - port: 8983
    targetPort: 8983

2つのYAMLファイルの内容をkubectl applyコマンドで反映してから、SolrのPodがRunningになっていることを確認します。

$ kubectl -n $(id -un) get pod,svc -l app=solr
NAME                        READY   STATUS    RESTARTS   AGE
pod/solr-75574f8fb8-gzx2m   1/1     Running   0          3m15s

NAME           TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/solr   ClusterIP   10.233.4.161   <none>        8983/TCP   3m3s

8.1.1. YAMLファイルの反映方法

$ kubectl -n $(id -un) get pod,svc -l app=solr コマンドを実行する前にDeploymentオブジェクト、Serviceオブジェクトの作成に失敗している場合には次のようなメッセージが表示されます。

No resources found in s13xxxxx namespace.

このようなメッセージが表示された場合にはYAMLファイルをダウンロードするか、URLを指定してkubectl applyコマンドを実行してください。

$ kubectl -n $(id -un) apply -f https://web-int.u-aizu.ac.jp/~yasu-abe/ja/sccp/manual/tutorial-solr-crawler.deploy-solr.yaml
$ kubectl -n $(id -un) apply -f https://web-int.u-aizu.ac.jp/~yasu-abe/ja/sccp/manual/tutorial-solr-crawler.svc-solr.yaml

8.1.2. YAMLファイルのダウンロードからSolrの起動までの実行例

ファイルのURLは変更されている場合があるため、本文のURLを利用してください。

8.2. Solr上でのtestcoreの作成

K8s上で稼動しているSolrに外部から接続することはできないため、kubectlのexecコマンドを利用してPodの内部に入って作業を行ないます。

$ kubectl -n $(id -un) exec -it "$(kubectl -n $(id -un) get pod -l app=solr -o jsonpath='{.items[0].metadata.name}')" -- bash

ここで、プロンプトが変化して solr@solr-75574f8fb8-gzx2m:/opt/solr-8.8.2$ のようなコマンド待機状態になれば成功です。

今回はcurlは使わずに /opt/solr/bin/solr コマンドを利用します。

プロンプトが変化していることを確認してから次のコマンドを実行して testcore を作成してください。

... $ /opt/solr/bin/solr create_core -c testcore
... $ exit

8.2.1. testcore作成の実行例

8.3. Crawlerコンテナのデプロイメント

自分でHarborにCrawlerコンテナを登録して利用しても良いですが、ここでは inovtst9.u-aizu.ac.jp/library/solr-crawler:1.1.0 コンテナを利用します。

Crawlerは次のように kind: Cronjob として登録します。

---
apiVersion: batch/v1
kind: CronJob
metadata:
  name: solr-crawler
spec:
  schedule: "10 9 * * *"
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 30
  successfulJobsHistoryLimit: 5
  failedJobsHistoryLimit: 5
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          containers:
          - name: solr-crawler
            image: inovtst9.u-aizu.ac.jp/library/solr-crawler:1.1.0
            env:
            - name: LC_CTYPE
              value: ja_JP.UTF-8
            - name: TARGET_URL
              value: "https://u-aizu.ac.jp/~yasu-abe/ja/"
            - name: VISIT_URL_PATTERN
              value: "^https://u-aizu.ac.jp/~yasu-abe/.+$"
            - name: OK_FILTER
              value: ".*(\\.(text|txt|html|htm|yaml|yml|csv|json))$|.*/$"
            - name: MYC_SOLR_URL
              value: "http://solr:8983/solr/testcore"

schedule: 行は、"10 9 * * *" だと、18:10 に1回だけ実行する指定となります。 世界標準時との時差は+9時間ありますので、現在の時刻から5分程度後の時刻を指定してください。

【参考情報】
schedule:行に指定できる書式は、"<minute> <hour> <day of month> <month> <day of week>" の5つの数字です。

$ man 5 crontab
...
        field          allowed values
        -----          --------------
        minute         0–59
        hour           0–23
        day of month   1–31
        month          1–12 (or names, see below)
        day of week    0–7 (0 or 7 is Sun, or use names)
...

すぐにクローリングが開始されるよう、現時刻から+3分程度後の時刻を指定してください。

$ wget https://web-int.u-aizu.ac.jp/~yasu-abe/ja/sccp/manual/tutorial-solr-crawler.cronjob-solr.yaml
## $ emacs tutorial-solr-crawler.cronjob-solr.yaml などで適宜変更
$ kubectl -n $(id -un) apply -f tutorial-solr-crawler.cronjob-solr.yaml

8.3.1. 【実演】cronjobのYAMLファイルのダウンロードからkubectlで反映するまで

"10 9 * * *" (18:10) が設定されている実行開始時刻を、"55 8 * * *" (17:55) に変更しています。

wgetコマンドに指定しているURLは変更されているので、本文を確認してください。

8.4. cronjobを実行した後の稼動確認

指定した時刻が経過してから、次のようにsolrのコンテナに入り、testcoreに保存されているドキュメントを確認します。

$ kubectl -n $(id -un) exec -it "$(kubectl -n $(id -un) get pod -l app=solr -o jsonpath='{.items[0].metadata.name}')" -- bash

次に先ほどのように Solr Pod の中に入ったことをプロンプトの変化で確認し、curlコマンドを利用してSolrに登録されているデータを確認します。

... $  curl http://127.0.0.1:8983/solr/testcore/select?q="id:*"

8.4.1. cronjobの動作状況を確認の事例

実行中のSolrコンテナに入り、`testcore`に保存されているデータを表示するまでの実行例です。

8.5. Webページ上で検索結果を表示する

OpenAPI+Sinatraによる検索エンジンの作成 を実施した場合は、次のセクションから説明している要領で自前のSolrへ接続します。

8.5.1. sinatra-webappを改造する

OpenAPI+Sinatraによる検索エンジンの作成 を実施していない場合には、この飛ばして次に進んでください。

次のように code/ ディレクトリの、api/default_api.rb を変更してください。

url = URI::HTTP.build({:host => "solr", :port => 8983, :path => '/solr/testcore/select',
    :query => "q=content:#{@param_q}&wt=json&hl=on&hl.fl=content&usePhraseHighLighter=true"})

変更後に、コンテナを再度ビルドし、Harborにpushします。

Makefile 先頭にある DOCKER_IMAGE_VERSION を 1.0.0 などに変更します。

DOCKER_IMAGE_VERSION = 1.0.0

コマンドの実行での作業を繰り返してください。

$ make REGISTRY_SERVER=inovtst9.u-aizu.ac.jp docker-build-prod
$ make REGISTRY_SERVER=inovtst9.u-aizu.ac.jp docker-tag

$ podman login inovtst9.u-aizu.ac.jp
$ make REGISTRY_SERVER=inovtst9.u-aizu.ac.jp docker-push
$ podman logout inovtst9.u-aizu.ac.jp

8.5.2. sinatra-webappを変更してからHarborに登録するまでの作業例

OpenAPI+Sinatraによる検索エンジンの作成 を実施していない場合には、この飛ばして次に進んでください。

8.5.3. pod/sinatra-webappをアップロードしたバージョン(1.0.0)に変更する

OpenAPI+Sinatraによる検索エンジンの作成 を実施していない場合には、この飛ばして次に進んでください。

次は、Kubernentesからの利用で利用した、01.deploy-sinatra.yaml ファイルを書き換えて、今回Harborに登録したイメージを指定します。

もし、01.deploy-sinatra.yamlがどこにあるか分からなくなったら、上記リンク先からファイルをダウンロードすることもできます。

順番に進めてくるとHarborに自分のコンテナを登録しているはずなので、下記の <your project name> は自分のAINS-IDに置き換えてください。コンテナを登録せずに進めてきたり、うまく動作しない場合には、library/sinatra-webapp:1.0.0 を利用し、問題の切り分けを行なってください。

      image: inovtst9.u-aizu.ac.jp/<your project name>/sinatra-webapp:1.0.0

誰でも利用できる https://inovtst9.u-aizu.ac.jp/harbor/projects/1/repositories/sinatra-webapp コンテナを登録・公開していますので、自分のアプリがうまく動作しない場合には、これをデプロイして問題の切り分けを行なってください。

書き換えたYAMLファイルをkubectlコマンドで反映したら、各自のURLでアクセスできることを確認してください。

$ kubectl -n $(id -un) apply -f 01.deploy-sinatra.yaml

8.5.4. テスト用のsinatra-webappイメージ

今回は自分で準備したsinatra-webappイメージを利用していますが、コンテナに問題があるのか、その他の設定に問題があるのか区別するためにSolrに接続するアプリケーションを準備しています。

$ kubectl -n $(id -un) apply -f https://web-int.u-aizu.ac.jp/~yasu-abe/ja/sccp/manual/tutorial-solr-crawler.deploy-webapp.yaml

8.5.5. 動作中のpodのコンテナ・イメージを変更する別の方法

OpenAPI+Sinatraによる検索エンジンの作成 を実施していない場合には、この飛ばして次に進んでください。

YAMLファイルを変更する以外に、既に動作しているpodのimage:行を変更する場合には、次のように直接editコマンドを実行することもできます。

$ env EDITOR=emacs kubectl -n $(id -un) edit deployment/sinatra-webapp

asciinemaでの動作例は次のようになります。

直接、設定を変更する方法は、自分がいまどのイメージを動作させるのか分からなくなってしまう可能性が高くなるため、YAMLファイルを編集しapplyする方法がお勧めです。

8.6. Ccronjobの削除について

Crawlerが動いたままだとWebサイトの管理者に迷惑をかける可能性があるので、動作を確認したらcronjobは停止しておきます。

ダウンロードしたcronjob-solr.yamlを利用した場合には次のようにファイルを利用して削除することができます。

$ kubectl -n $(id -un) delete -f cronjob-solr.yaml

あるいは、次のように手動でcronjobオブジェクトを指定して、削除することもできます。

$ kubectl -n $(id -un) get cronjob
$ kubectl -n $(id -un) delete cronjob solr-crawler

9. inovtst9.u-aizu.ac.jp/$(id -un)/solr-crawler の作り方

最初に作業をした、crawler4j-japanese-contents/ ディレクトリには、Dockerfileなどは含まれていません。 自分用のsolr-crawlerを作成する場合には、次のように必要なファイルを追加し、コンテナを作成・登録してください。

9.1. JARファイル

Dockerコンテナでは、mvn compile ; mvn exec:java をそのまま利用することは難しさがあります。 コンテナに格納する際には、mvnは利用せずに、JREとJARを利用します。

mvn compile が終った状態で、次のようにJARファイルを生成します。

$ mvn install

この作業によって、target/crawlerdemo-1.0-SNAPSHOT.jar が生成されます。 このJARファイルの META-INF/MANIFEST.MF には、Main-Class: org.yasundial.crawler.app.App が含まれ、java -jarコマンドにより直接実行することができるようになります。

9.2. Dockerfile

メインとなるDockerfileの内容は以下のとおりです。

## deploying for production

FROM docker.io/library/eclipse-temurin:17-jre-alpine

RUN apk update && \
    apk add --no-cache tzdata bash ca-certificates

RUN mkdir /jobs
WORKDIR /jobs
COPY target/crawlerdemo-1.0-SNAPSHOT.jar /jobs/
COPY run.sh /jobs/
RUN chmod +x /jobs/run.sh

ENV TARGET_URL="https://example.com/~user01/"
ENV VISIT_URL_PATTERN="^https://example.com/~user01/.+$"
ENV CRAWLER4J_STORAGE_DIR="data/crawl/strage"
ENV OK_FILTER=".*(\\.(text|txt|html|htm|yaml|yml|csv|json))$|.*/$"
ENV MYC_SOLR_URL="http://example.com:8983/solr/test"

RUN mkdir /jobs/data
RUN addgroup crawler
RUN adduser -S -G crawler crawler
RUN chown crawler /jobs/data
RUN chgrp -R crawler /jobs
RUN chmod -R g+rwX /jobs
USER crawler

ENTRYPOINT ["./run.sh"]

9.3. run.sh

run.shの内容はシンプルです。

#!/bin/bash

exec java -jar crawlerdemo-1.0-SNAPSHOT.jar

9.4. Makefile

必要なタスクは最新版のMakefileには含まれています。

Makefileの先頭にある変数を変更し、自分に書き込み権限のある値にREGISTRY_LIBRARYを変更します。

9.5. solr-crawlerコンテナの作成とHarborへの格納

Dockerコンテナの作成方法は他のチュートリアルと同様です。

$ mvn package
$ make REGISTRY_SERVER=inovtst9.u-aizu.ac.jp docker-build-prod
$ make REGISTRY_SERVER=inovtst9.u-aizu.ac.jp docker-tag

$ podman login inovtst9.u-aizu.ac.jp
$ make REGISTRY_SERVER=inovtst9.u-aizu.ac.jp docker-push
$ podman logout inovtst9.u-aizu.ac.jp

Harbor の管理画面から 自分のプロジェクトの中に、作成した solr-crawler:1.0.0 が含まれていることを確認してください。

10. まとめ

Solrはinovtst9.u-aizu.ac.jp経由でWebブラウザからアクセスできないため、リモートでの作業には難しさがあったと思います。

現実のシステムでも、Firewallの向こう側にある機器をメンテナンスする場合には、同様にWebブラウザでのアクセスが難しい場合があります。

無理にWebブラウザでアクセスしようとしてセキュリティレベルを下げてしまう場合もあります。

メンテナンスのために一時的にServiceオブジェクトの設定を変更し、LoadBalancer経由でアクセス可能なEXTERNAL_IPを割り当てるという方法もあります。この方法であればWebブラウザからアクセスすることも可能となります。

以上