Chef 實戰 part3 — 安裝 IK Analyzer 及更新詞庫

Luyo
Luyo
Aug 27, 2017 · 19 min read

這次要來安裝 Elasticsearch 的 plugin — IK Analyzer,以及建立更新詞庫的機制。

NOTE: 其實一開始想學 Chef 就是為了做這件事,因為 IK Analyzer 雖說有遠端熱更新分詞功能,但我怎麼試就是都不能用,只好用本機端的詞庫。問題是我的 node 不只一台,必須建立一個優雅的機制去同時更新它們的本機端詞庫才行。

1. 安裝 IK plugin

上一篇中我們使用了官方的 Cookbook 來架設 Elasticsearch,並使用了它的 resource elasticsearch_install 以及 elasticsearch_configure ,這次要來嘗試使用它用來管理外掛的 resource elasticsearch_plugin

先看一下 IK 自己的安裝說明:

- optional 1 — download pre-build package from here: https://github.com/medcl/elasticsearch-analysis-ik/releases
unzip plugin to folder your-es-root/plugins/

- optional 2 — use elasticsearch-plugin to install ( version > v5.5.1 ):
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v5.5.1/elasticsearch-analysis-ik-5.5.1.zip

第一個方法是下載 build 好的檔案,unzip 到 es 的 plugins 資料夾下;第二個方法是使用 elasticsaerch-plugin 指令。

顯然我必須用第二個方法才能套用到 elasticsearch_plugin resource 的用法上。打開 recipes/default.rb 在結尾加入以下部分:

elasticsearch_plugin 'analysis-ik' do
url 'https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v5.5.1/elasticsearch-analysis-ik-5.5.1.zip'
action :install
end

上傳至 Chef server 並更新 node:

$ knife cookbook upload elasticsearch_ik; knife ssh 'name:es-1' 'sudo chef-client' --ssh-user centos --identity-file ~/.ssh/test.pem --attribute ipaddress
(...略)
172.31.21.70 * elasticsearch_plugin[analysis-ik] action install
172.31.21.70
172.31.21.70
172.31.21.70 Running handlers:
172.31.21.70 Running handlers complete
172.31.21.70 Chef Client finished, 1/46 resources updated in 42 seconds

看起來有安裝成功,連進 node 中確認一下:

$ sudo su
# cd /etc/elasticsearch/analysis-ik/
# cd /usr/share/elasticsearch/plugins/analysis-ik/

兩個資料夾都在,表示 analysis-ik 這個套件安裝成功囉。

2. 更新詞庫

接下來我要更新 IK 的詞庫設定,並將自訂的詞庫放到指定的路徑之中。

因為詞庫檔不是靜態檔案,而是從資料庫撈出資料並動態產生,是會變動的,所以我不能用之前學過的 filetemplate resource 來做。

那麼要怎麼去更新這個動態檔案呢?如果 Chef 有提供一種 resource 是可以把外部資料以 http request 的方式取得檔案,然後存放到指定的路徑,那麼我就可以在後端製作一支供內部資源使用的 web API 讓 node 去 request 回來存檔,問題就解決了。

翻一下文件中的 resource 列表,發現了一個 remote_file 的 resource,看起來就是為了我這樣的需要所設計的!

這時候我先偷偷回去把後端取得詞庫檔的 API 生了出來,然後再回到工作站開始做相關設定。

NOTE: 以下會以 https://api.xxx.ooo/get-my-dict 表示這支 API 的 endpoint

產生設定檔 template

首先要產生設定檔的 templaste,告訴 IK 要載入哪個詞庫檔:

$ chef generate template IKAnalyzer.cfg.xml
Recipe: code_generator::template
(...略)

然後打開 templates/IKAnalyzer.cfg.xml.erb 加入內容:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">verybuy.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

設定 template 及 remote_file resources

再回到 recipes/default.rb 接下去新增相關的 resources:

template '/etc/elasticsearch/analysis-ik/IKAnalyzer.cfg.xml' do
source 'IKAnalyzer.cfg.xml.erb'
end

remote_file '/etc/elasticsearch/analysis-ik/verybuy.dic' do
source 'https://api.xxx.ooo/get-my-dict'
owner 'elasticsearch'
group 'elasticsearch'
mode '0660'
action :create
end

template 的部分是 IK 詞庫設定檔的路徑及內容;而 remote_file 的部分就是剛剛講的要去哪裡抓檔案回來存到哪裡 。

再來就試試看上傳並更新 node:

$ knife cookbook upload elasticsearch_ik; knife ssh 'name:es-1' 'sudo chef-client' --ssh-user centos --identity-file ~/.ssh/test.pem --attribute ipaddress
(...略)
172.31.21.70 Chef Client finished, 2/48 resources updated in 18 seconds

resources 成功更新,進到 node 去確認檔案也有順利更新,再就來可以試試看 analyzer 有沒有順利運作。

測試之前必須先重啟 elasticsearch,新的 ik_smart analyzer 才會生效:

$ knife ssh 'name:es-1' 'sudo service elasticsearch restart' --ssh-user centos --identity-file ~/.ssh/test.pem --attribute ipaddress
172.31.21.70 Restarting elasticsearch (via systemctl): [ OK ]

3. 測試斷詞

我這次要拿「非常勸敗」這個詞來測試。原本的詞庫裡沒這組詞彙, curl 的結果如下:

$ curl -XGET '172.31.21.70:9200/_analyze?pretty' -H 'Content-Type: application/json' -d'
> {
> "analyzer" : "ik_smart",
> "text" : ["非常勸敗"]
> }
> '

{
"tokens" : [
{
"token" : "非常",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "勸",
"start_offset" : 2,
"end_offset" : 3,
"type" : "CN_CHAR",
"position" : 1
},
{
"token" : "敗",
"start_offset" : 3,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 2
}
]
}

因為原本的詞庫裡有「非常」,所以「非常勸敗」會被斷詞為「非常」、「勸」、「敗」。

現在我到資料庫去新增了「非常勸敗」這組詞彙,然後也確認了 API 回傳的結果裡面有這組詞彙。

接下來,我只要跑更新 node 的動作,應該就會自動更新 node 上的詞庫了:

$ knife ssh 'name:es-1' 'sudo chef-client' --ssh-user centos --identity-file ~/.ssh/test.pem --attribute ipaddress
(...略)
172.31.21.70 * remote_file[/etc/elasticsearch/analysis-ik/verybuy.dic] action create
172.31.21.70 - update content in file /etc/elasticsearch/analysis-ik/verybuy.dic from 6e5d3a to b46e4c
172.31.21.70 (new content is binary, diff output suppressed)
172.31.21.70 - restore selinux security context
172.31.21.70
172.31.21.70 Running handlers:
172.31.21.70 Running handlers complete
172.31.21.70 Chef Client finished, 1/48 resources updated in 13 seconds

連到 node 裡去確認一下,我的詞庫檔 verbuy.dic 已經順利被更新了。

因為詞庫檔若有更新,必須重新啟動 elasticsearch 才會生效,所以先用 knife ssh 來重啟:

$ knife ssh 'name:es-1' 'sudo service elasticsearch restart' --ssh-user centos --identity-file ~/.ssh/test.pem --attribute ipaddress
172.31.21.70 Restarting elasticsearch (via systemctl): [ OK ]

最後再次執行一樣的 curl 指令:

$ curl -XGET '172.31.21.70:9200/_analyze?pretty' -H 'Content-Type: application/json' -d'
{
"analyzer" : "ik_smart",
"text" : ["非常勸敗"]
}
'

{
"tokens" : [
{
"token" : "非常勸敗",
"start_offset" : 0,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 0
}
]
}

斷詞成功!

4. 使用變數

最後,我覺得 recipe 還有一些小地方可以寫得更好。

首先是 remote_file 裡的 usergroup 的部分,應該用變數的方法來寫比較妥當。參考官方 elasticsearch_user 的說明,編輯剛剛在 recipes/default.rb 中新增的 remote_file resource:

remote_file '/etc/elasticsearch/analysis-ik/verybuy.dic' do
source 'https://api.xxx.ooo/get-my-dict'
owner node['elasticsearch']['user']['username']
group node['elasticsearch']['user']['groupname']

mode '0660'
action :create
end

上傳並更新 node:

$ knife cookbook upload elasticsearch_ik; knife ssh 'name:es-1' 'sudo chef-client' --ssh-user centos --identity-file ~/.ssh/test.pem --attribute ipaddress
(...略)
172.31.21.70 Running handlers:
172.31.21.70 Running handlers complete
172.31.21.70 Chef Client finished, 0/48 resources updated in 13 seconds

到 node 去看,檔案權限沒有發生變化,表示變數設定正確。

5. 設定重啟 elasticsearch service 條件

其次,每次更新完詞庫就要重新啟動 elasticsearch 才會生效,所以 recipe 中必須要有重新啟動 service 的動作。但是如果每次跑這個 recipe 都重新啟動 elasticsearch 也蠻蠢的,因為詞庫又不一定有更新。

google 了一下找到這篇文章,介紹了 only_if 的用法,搭配上dict.updated_by_last_action? 就可以指定當某個 resource 有更新的時候才執行這個動作。

馬上來改造一下我的 resources:

dict = remote_file '/etc/elasticsearch/analysis-ik/verybuy.dic' do
source 'https://api.xxx.ooo/get-my-dict'
owner node['elasticsearch']['user']['username']
group node['elasticsearch']['user']['groupname']
mode '0660'
action :create
end
service 'elasticsearch' do
action [:restart]
only_if { dict.updated_by_last_action? }
end

這邊的作法其實有問題,詳見底下的 NOTE2 及 NOTE3

上傳並更新 node:

$ knife cookbook upload elasticsearch_ik; knife ssh 'name:es-1' 'sudo chef-client' --ssh-user centos --identity-file ~/.ssh/test.pem --attribute ipaddress
(...略)
172.31.21.70 Running handlers:
172.31.21.70 Running handlers complete
172.31.21.70 Chef Client finished, 0/48 resources updated in 13 seconds

更新成功,也連進 node 確認了 elasticsearch 沒有被重啟,因為目前詞庫並沒有被更新,所以 only_if 這個條件看起來是有運作了,但我們還要做進一步的確認。

確認方式是在資料庫中隨便新增一筆詞彙,因為資料庫更新了,詞庫也應該要更新;而詞庫更新了,elasticsearch service 就應該要重新啟動才對。

所以我就去資料庫新增了一組詞彙「測試123」,然後更新 node:

$ knife ssh 'name:es-1' 'sudo chef-client' --ssh-user centos --identity-file ~/.ssh/test.pem --attribute ipaddress
(...略)
172.31.21.70 * remote_file[/etc/elasticsearch/analysis-ik/verybuy.dic] action create
172.31.21.70 - update content in file /etc/elasticsearch/analysis-ik/verybuy.dic from 0262aa to 10630d
172.31.21.70 (current file is binary, diff output suppressed)
172.31.21.70 - restore selinux security context
172.31.21.70 * service[elasticsearch] action restart
172.31.21.70 - restart service service[elasticsearch]
172.31.21.70
172.31.21.70 Running handlers:
172.31.21.70 Running handlers complete
172.31.21.70 Chef Client finished, 2/48 resources updated in 12 seconds

確認預期該發生的事情都有發生 — 更新檔案及重啟服務,最後 curl 一下確認斷詞狀況:

$ curl -XGET '172.31.21.70:9200/_analyze?pretty' -H 'Content-Type: application/json' -d'
{
"analyzer" : "ik_smart",
"text" : ["非常勸敗chef實戰測試123"]
}
'

{
"tokens" : [
{
"token" : "非常勸敗",
"start_offset" : 0,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "chef",
"start_offset" : 4,
"end_offset" : 8,
"type" : "ENGLISH",
"position" : 1
},
{
"token" : "實",
"start_offset" : 8,
"end_offset" : 9,
"type" : "CN_CHAR",
"position" : 2
},
{
"token" : "戰",
"start_offset" : 9,
"end_offset" : 10,
"type" : "CN_CHAR",
"position" : 3
},
{
"token" : "測試123",
"start_offset" : 10,
"end_offset" : 15,
"type" : "CN_WORD",
"position" : 4
}
]
}

斷詞也順利變成預期中的樣子囉!

到此,整個 recipes/default.rb 長這樣:

include_recipe 'elasticsearch'elasticsearch_configure 'elasticsearch' do
allocated_memory '256m'
configuration ({
'cluster.name' => 'development',
'network.host' => '172.31.21.70',
})
end
elasticsearch_plugin 'analysis-ik' do
url 'https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v5.5.1/elasticsearch-analysis-ik-5.5.1.zip'
action :install
end
template '/etc/elasticsearch/analysis-ik/IKAnalyzer.cfg.xml' do
source 'IKAnalyzer.cfg.xml.erb'
end
dict = remote_file '/etc/elasticsearch/analysis-ik/verybuy.dic' do
source 'https://api.xxx.ooo/get-my-dict'
owner node['elasticsearch']['user']['username']
group node['elasticsearch']['user']['groupname']
mode '0660'
action :create
end
service 'elasticsearch' do
action [:restart]
only_if { dict.updated_by_last_action? }
end

NOTE 1: 其實開頭少了一行 package 'java-1.8.0-openjdk' ,因為實戰 part1 已經安裝過這個套件,所以這邊就漏掉了。

NOTE 2: 在 Chef 實戰 part4 我才發現最後的這個 service resource 其實會覆蓋掉 elasticsearch_service resource 的預設行為,所以這個作法是有問題的。

NOTE 3: only_if 在這個情境下有更好的解法,就是在 remote_file 中設定 notification 讓指定的 service 去做指定的 action。修改方法也記錄在 Chef 實戰 part4


小結

在這篇文章中,我順利地在 node 上安裝好 IK plugin,也完成了更新詞庫的機制,學到了下列幾項用法:

  1. remote_file resource 的用法
  2. only_if 的用法

但目前 cookbook 雖算是完成了,但整個系統恐怕還不太實用,因為還差了以下兩件事:

  1. 我的 node 不只一台,必須設定讓一個指令可以同時更新多台 nodes
  2. 需要有定時更新的機制

還好這些事情之前有學過:第 1 點可以用 role 解決,第 2 點可以用 chef-client cookbook 解決。

設定過程會不會遇到什麼問題我也不知道,就留待下回分曉囉。


verybuy-dev

VeryBuy 研發手札

)

Luyo

Written by

Luyo

Founder, Developer of VeryBuy — https://www.verybuy.cc

verybuy-dev

VeryBuy 研發手札

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade