Edited at

Elasticsearch Cluster on Linux Container


概要

Linuxコンテナ(systemd-nspawn)を使って、複数ホストにまたがるElasticsearchクラスタを構築してみました。

私はコンテナ歴12年くらいですが、ほとんどSolarisコンテナ(Zones)ばかり使っていて、Linuxコンテナはどんなものかよくわかっていないので、おかしなことを言っていましたらご指摘ください。


背景

以下のような感じの要件で、基盤のアーキテクチャを考えていました。


  1. 50~100ノード規模のElasticsearchクラスタを作りたい

  2. オンプレ環境で

  3. 一度起動したら年単位で動き続ける

  4. 商用ライセンスはあまり使いたくないけど、人柱になって自力でなんとかする気概はあり

なんとなく以下のような実現方式を考えました。本音はSolaris Zonesを使いたくてたまりませんが今回はLinux縛りです。

カテゴリ
方式
評価(主観)

物理系
物理サーバ50台
× ラックが足りない。運用嫌だ。

物理サーバ+マルチプロセス
△ 運用嫌だ。

ハイパーバイザ系
VMware
△ ライセンス必要。オーバーヘッド嫌だ。

KVM, Xen
△ オーバーヘッド嫌だ。

コンテナ系
Docker
△ どうも合わない。k8s, Swarmとかは大がかりすぎ。

OpenVZ
△ まだあるのだろうか?

LXD
○? 良さそう。使うならUbuntuが良さそうか。

systemd-nspawn
○? ほぼOS標準の機能!

Dockerは少し検証してみたのですが、動くには動くのでしょうが、ホストまたぎのネットワークを構成するのが面倒で、KVSを立ち上げて、VXLANでオーバーレイネットワークとかになるのでやる気がなくなりました。

そのためだけにKubernetesとかSwarmを使うのも大がかりすぎに思えました。

マルチホスト上での、dockerコンテナ間ネットワーク環境整備メモ(Overlay Networking w/ Etcd) - Qiita

DockerをDisる気はなく、用途がマッチすれば良いと思いますが、仮想マシンのようなことがやりたいならまったく不向きで、無理矢理使っても悲惨な運用になるのが落ちという感じがします。

長年Solarisコンテナを使ってきたのもあって、なるべくシンプルでほぼOS標準の機能で動作するということから systemd-nspawn にたどり着き、検証してみようと思いました。

なお、クラウド前提なら、Amazon Elasticsearch Service を検討するのもいいと思います。

料金 - Amazon Elasticsearch Service | AWS


シナリオ

今回構築する環境は以下のようなイメージです。物理マシンがあれば使ってもいいですが、私はVirtualBox上に構築することにしました。

container.png

構築は以下のような流れで進めました。


  • ホスト構築編


    • CentOS 7 をインストールする

    • systemd-networkd をインストールする

    • ZFS をインストールする(任意)



  • コンテナ構築編


    • コンテナイメージをインストールする

    • コンテナを起動する

    • Elasticsearchをインストールする

    • コンテナをコピーする




構築


ホスト構築編


CentOS 7 をインストールする

OSはユーザが多いであろうCentOSにしました。バージョンは現時点で最新の7.6にしました。

インストールはできましたが、グラフィカルインストーラだとマウスでボタンをクリックできないことがある謎の事象に遭遇したので、テキストインストーラを使う方がいいような気がします。

CentOS7 (1804) のテキストベースインストール手順 - Qiita

今回はホスト間で通信できる必要があるので、VirtualBoxのネットワークの設定でブリッジアダプターを使うとよいと思います。また、プロミスキャスモードを許可する設定も必要です。

virtualbox_network.png

また、OSの領域とコンテナの領域を分けたいので、ストレージの設定画面でディスクイメージを1つ追加しました。

SELinuxは無効にしました。

$ sudo vi /etc/sysconfig/selinux

...
SELINUX=disabled
...


systemd-networkd をインストールする

今回はホスト間で通信できるようにネットワークブリッジを使うので、ネットワークの管理をsystemd-networkdで行うのがよいようです。以下のページを参考にしました。

Use systemd-networkd under CentOS 7 « Rene Diepstraten's Blog

以下のページも参考にしました。systemd関連のドキュメントはArch Linuxのものがわかりやすい感じがします。

systemd-networkd - ArchWiki

systemd-networkd と systemd-resolved をインストールします。

$ sudo yum install systemd-networkd systemd-resolved

設定ファイルを作成します。内容は環境に合わせて適当に変えてください。Redhat標準の /etc/sysconfig/network-scripts/ 以下のファイルは使われなくなります。

/etc/systemd/network/br0.netdev

----
[NetDev]
Name=br0
Kind=bridge

/etc/systemd/network/enp0s3.network

----
[Match]
Name=enp0s3

[Network]
Bridge=br0

/etc/systemd/network/br0.network

----
[Match]
Name=br0

[Network]
Address=192.168.0.10
Gateway=192.168.0.254
DNS=192.168.0.254

systemd-networkd と systemd-resolved を有効にします。

$ sudo systemctl disable network NetworkManager

$ sudo systemctl enable systemd-networkd systemd-resolved
$ sudo reboot

resolve.conf を入れ替えておきます。

$ sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf

networkctl コマンドで、IPアドレスなどを確認することが出来ます。

$ networkctl status

● State: routable
Address: 192.168.0.10 on br0
Gateway: 192.168.0.254 (XXX) on br0
DNS: 192.168.0.254


ZFS をインストールする(任意)

ZFSは必須ではありませんが、コンテナの領域を管理するのに都合が良さそうなので使ってみたいと思います。

systemd-nspawn はBtrfsと連携する機能があるようなのですが、ここは慣れ親しんだZFSにします。

ZFSをCentOSで使用するには、カーネルモジュールを追加する必要があります。ZFS on Linux の公式の手順に従ってインストールします。

RHEL and CentOS · zfsonlinux/zfs Wiki

ZFSのYumリポジトリを使えるようにします。

$ sudo yum install http://download.zfsonlinux.org/epel/zfs-release.el7_6.noarch.rpm

zfs-kmod のほうを有効にします。

$ sudo vi /etc/yum.repos.d/zfs.repo

----
[zfs]
name=ZFS on Linux for EL 7 - dkms
baseurl=http://download.zfsonlinux.org/epel/7/$basearch/
-enabled=1
+enabled=0
metadata_expire=7d
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-zfsonlinux
@@ -9,7 +9,7 @@
[zfs-kmod]
name=ZFS on Linux for EL 7 - kmod
baseurl=http://download.zfsonlinux.org/epel/7/kmod/$basearch/
-enabled=0
+enabled=1
metadata_expire=7d
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-zfsonlinux

ZFS をインストールします。

$ sudo yum install zfs

ARCのサイズを制限しておきます。

$ sudo vi /etc/modprobe.d/zfs.conf

----
options zfs zfs_arc_max=1073741824

ストレージプールを作成します。ZFS on Linux では、ls -l /dev/disk/by-id で表示されるデバイスIDを使用することが推奨されているようです。

$ sudo zpool create tank ata-VBOX_HARDDISK_XXXXXX-XXXXXX

tank という名前でストレージプールができました。

$ zpool list

NAME SIZE ALLOC FREE EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
tank 39.8G 291K 39.7G - 0% 0% 1.00x ONLINE -

コンテナ用の領域(データセット)を作成します。compressionの設定はお好みに合わせてという感じです。近年はCPUが高速なので、圧縮した方が Disk I/O が減って高速になるケースもあります。

$ sudo zfs create -o compression=lz4 -o mountpoint=/var/lib/machines tank/machines

$ sudo zfs create tank/machines/es11
$ sudo zfs create tank/machines/es12

こんな感じでデータセットができました。

$ zfs list

NAME USED AVAIL REFER MOUNTPOINT
tank 162K 38.5G 25.5K /tank
tank/machines 73K 38.5G 25K /var/lib/machines
tank/machines/es11 24K 38.5G 24K /var/lib/machines/es11
tank/machines/es12 24K 38.5G 24K /var/lib/machines/es12

systemd-nspawnでは、 /var/lib/machines/ 配下のディレクトリ名がコンテナのhostnameになるように想定されているようです。長さの制限は一見ないように見えるのですが、systemd-networkd がインタフェース名を自動生成するときに先頭11文字で切り落としてしまうようで、先頭11文字がユニークでないと名前が衝突するようです。そのうち改善するのかもしれませんが、ディレクトリ名(コンテナ名)は11文字以下にするのが無難なようです。

nspawn containers with too long names conflict regarding veth name if `--network-veth` is used · Issue #10721 · systemd/systemd


コンテナ構築編


コンテナイメージをインストールする

systemd-nspawn では、コンテナイメージをインストールする方法は主に3種類くらいあるようです。


  1. yum install --installroot でインストールする。chroot環境を作るのと似た感じ。

  2. ビルド済みのイメージを使う。Dockerのイメージも使えるようだ。

  3. すでに作成済みのコンテナからコピーする。

ひとまずオーソドックスな1.でやってみることにします。3.はあとからやります。

ZFSで作成した領域に最低限のパッケージをインストールします。

$ sudo yum -y --nogpg --releasever=7 --installroot=/var/lib/machines/es11 install systemd systemd-networkd passwd yum vim-minimal

lsで見ると、OSのイメージぽく見えます。

$ ls -l /var/lib/machines/es11/

total 17
lrwxrwxrwx. 1 root root 7 Apr 6 23:24 bin -> usr/bin
dr-xr-xr-x. 2 root root 2 Apr 11 2018 boot
drwxr-xr-x. 2 root root 3 Apr 6 23:24 dev
drwxr-xr-x. 47 root root 113 Apr 6 23:25 etc
drwxr-xr-x. 2 root root 2 Apr 11 2018 home
lrwxrwxrwx. 1 root root 7 Apr 6 23:24 lib -> usr/lib
lrwxrwxrwx. 1 root root 9 Apr 6 23:24 lib64 -> usr/lib64
drwxr-xr-x. 2 root root 2 Apr 11 2018 media
drwxr-xr-x. 2 root root 2 Apr 11 2018 mnt
drwxr-xr-x. 2 root root 2 Apr 11 2018 opt
dr-xr-xr-x. 2 root root 2 Apr 11 2018 proc
dr-xr-x---. 2 root root 2 Apr 11 2018 root
drwxr-xr-x. 12 root root 12 Apr 6 23:25 run
lrwxrwxrwx. 1 root root 8 Apr 6 23:24 sbin -> usr/sbin
drwxr-xr-x. 2 root root 2 Apr 11 2018 srv
dr-xr-xr-x. 2 root root 2 Apr 11 2018 sys
drwxrwxrwt. 7 root root 7 Apr 6 23:25 tmp
drwxr-xr-x. 13 root root 14 Apr 6 23:24 usr
drwxr-xr-x. 18 root root 21 Apr 6 23:24 var

ちなみに、圧縮率を見てみると2倍以上になっていてなかなかいい感じです。lz4は圧縮率と速度のバランスが良く、ZFSを使うならおすすめです。

$ zfs get compressratio tank/machines/es11

NAME PROPERTY VALUE SOURCE
tank/machines/es11 compressratio 2.03x -


コンテナを起動する

systemd-nspawn -Dオプションで、イメージをインストールしたディレクトリを指定してコンテナを起動し、rootのパスワードを設定します。

$ sudo systemd-nspawn -D /var/lib/machines/es11

-bash-4.2# passwd
...
-bash-4.2# exit

SELinuxを適切に設定するか無効にしないとパスワード変更で↓のようなエラーになるようなのでご注意ください。私は恥ずかしながらSELinuxをまともに使ったことがありません。

passwd: Authentication token manipulation error

コンテナをデーモンモードで起動します。

$ sudo systemd-nspawn -b -D /var/lib/machines/es11

起動シーケンスが走って、ログインプロンプトが出てくるのでrootでログインします。

Spawning container es11 on /var/lib/machines/es11.

Press ^] three times within 1s to kill container.
systemd 219 running in system mode. (+PAM +AUDIT +SELINUX +IMA -APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 -SECCOMP +BLKID +ELFUTILS +KMOD +IDN)
Detected virtualization systemd-nspawn.
Detected architecture x86-64.

Welcome to CentOS Linux 7 (Core)!

...
CentOS Linux 7 (Core)
Kernel 3.10.0-957.el7.x86_64 on an x86_64

es11 login: root
Password:
Last login: Sat Apr 6 23:43:37 on console

ps -ef で見ると、最低限のプロセスが起動しているのがわかります。

container# ps -ef

UID PID PPID C STIME TTY TIME CMD
root 1 0 1 23:45 ? 00:00:00 /usr/lib/systemd/systemd
root 14 1 0 23:45 ? 00:00:00 /usr/lib/systemd/systemd-journald
dbus 19 1 0 23:45 ? 00:00:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfil
root 21 1 0 23:45 ? 00:00:00 /usr/lib/systemd/systemd-logind
root 23 1 0 23:45 ? 00:00:00 login -- root
root 25 23 0 23:45 console 00:00:00 -bash
root 37 25 0 23:45 console 00:00:00 ps -ef

デフォルトでは、ホストとネットワークを共有しています。

container# networkctl status

● State: n/a
Address: 192.168.0.10 on br0
Gateway: 192.168.0.254 (JStream Technologies Inc.) on br0

Ctrl+] を3回入力するとコンテナが終了します。

Container es11 terminated by signal KILL.

ネットワークの設定を追加します。コンテナの中身はただのディレクトリなので、ホスト側からも見えます。

$ cd /var/lib/machines/es11

$ cd etc/systemd
$ sudo mkdir network
$ cd network

systemd-nspawnではhost0という仮想NICが自動的に作られるので、以下のように設定ファイルを作成します。

$ sudo vi host0.network

----
[Match]
Name=host0

[Network]
Address=192.168.0.11/24
Gateway=192.168.0.254
DNS=192.168.0.254

今度は、--network-bridge オプションを追加して起動します。

$ sudo systemd-nspawn -b -D /var/lib/machines/es11 --network-bridge=br0

host0にIPアドレスが設定されていることがわかります。これで、コンテナに独自のIPアドレスを割り当てて外部と通信できるようになりました。

container# networkctl status

● State: routable
Address: 192.168.0.11 on host0
Gateway: 192.168.0.254 on host0
DNS: 192.168.0.254

machinectl login でrootでログインできるように、/etc/securettyにpts/0を追記しておきます。

container# echo pts/0 >> /etc/securetty

いったん Ctrl+] を3回入力してコンテナを終了します。

Container es11 terminated by signal KILL.

ここからは、machinectlコマンドでコンテナを操作します。

ネットワークブリッジを使うように起動オプションを書き換えます。また、network.targetより後に起動するようにします。

$ sudo vi /usr/lib/systemd/system/systemd-nspawn\@.service

> After=network.target
...
< ExecStart=/usr/bin/systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --network-veth --machine=%I
---
> ExecStart=/usr/bin/systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --network-bridge=br0 --machine=%I

machines.targetを有効にします。

$ systemctl enable machines.target

machinectl enable でコンテナが自動起動するようにします。

$ sudo machinectl enable es11

Created symlink from /etc/systemd/system/machines.target.wants/systemd-nspawn@es11.service to /usr/lib/systemd/system/systemd-nspawn@.service.

machinectl start コマンドで、コンテナを起動できます。

$ sudo machinectl start es11

machinectl login コマンドで、コンテナにログインできます。

$ sudo machinectl login es11

ここまでで、コンテナを仮想サーバと同じような感覚で扱えるようになりました。

systemd-nspawn の概要は、例によって Arch Linux のページがわかりやすいと思います。

systemd-nspawn - ArchWiki


Elasticsearchをインストールする

ホスト側で、vm.max_map_countの値を増やしておきます。

$ sudo vi /etc/sysctl.d/10-vm.conf

---
vm.max_map_count=262144

$ sudo sysctl -p

Elasticsearchのyumリポジトリを追加します。

container# cd /etc/yum.repos.d

container# vi elasticsearch.repo
---
[elasticsearch-6.x]
name=Elasticsearch repository for 6.x packages
baseurl=https://artifacts.elastic.co/packages/6.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md

Javaをインストールします。

container# yum install java-11-openjdk

Elasticsearchをインストールします。

container# yum install elasticsearch

設定を変更します。

container# cd /etc/elasticsearch

container# vi elasticsearch.yml
---
...
node.name: ${HOSTNAME}

network.host: 0.0.0.0

discovery.zen.ping.unicast.hosts: ["192.168.0.11", "192.168.0.12","192.168.0.21", "192.168.0.22"]

本番で使うなら Shard allocation awareness も考慮した方がよいと思います。

Shard allocation awareness | Elasticsearch Reference [master] | Elastic

サービスを有効にします。

container# systemctl daemon-reload

container# systemctl enable elasticsearch.service
container# systemctl start elasticsearch.service

動作確認してみます。ひとまず単一ノードで無事に起動しているようです。

container# curl http://localhost:9200/

{
"name" : "es11",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "Rnvu_GRyQoapvhnodWHb4A",
"version" : {
"number" : "6.7.1",
"build_flavor" : "default",
"build_type" : "rpm",
"build_hash" : "2f32220",
"build_date" : "2019-04-02T15:59:27.961366Z",
"build_snapshot" : false,
"lucene_version" : "7.7.0",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}


コンテナをコピーする

いったんコンテナを停止します。

$ sudo machinectl poweroff es11

machinectlコマンドには、export-tarというオプションがあるようなのですが、CentOS 7 に付属のものはバージョンが古いのかそのオプションがありません。

machinectl(1) — Arch manual pages

仕方がないので、rsyncでコピーすることにします。

$ cd /var/lib/machines/

$ sudo rsync -av es11/ es12
...

Node IDが重複しないように消しておきます。

container# rm -rf /var/lib/elasticsearch/nodes/0/

ネットワークの設定を書き換えます。

$ cd es12

$ cd etc/systemd/network
$ sudo vi host0.network
----
[Match]
Name=host0

[Network]
Address=192.168.0.12/24
Gateway=192.168.0.254
DNS=192.168.0.254

machinectl enable でコンテナが自動起動するようにします。

$ sudo machinectl enable es12

Created symlink from /etc/systemd/system/machines.target.wants/systemd-nspawn@es12.service to /usr/lib/systemd/system/systemd-nspawn@.service.

machinectl start コマンドで、コンテナを起動します。

$ sudo machinectl start es11

$ sudo machinectl start es12

2台でクラスタ構成を組めていることを確認できました。

$ curl http://192.168.0.11:9200/_cat/health?v

epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1554574589 18:16:29 elasticsearch green 2 2 0 0 0 0 0 0 - 100.0%


ホストの追加

もう一式ホストを追加します。私はVirtualBoxのクローン機能を使って作りました。以下のあたりに気をつければ良いと思います。


  • ホスト


    • ホスト名変更: hostnamectl set-hostname ...

    • IPアドレス変更: /etc/systemd/network/ 以下

    • ブリッジのMACアドレス変更

    • zpoolのimport: zpool import -f tank



  • コンテナ


    • コンテナ名変更: zfs rename

    • IPアドレス変更

    • ElasticseachのID情報削除



クラスタの状態を確認すると、無事に4台でクラスタ構成を組めていることを確認できました。

$ curl http://192.168.0.11:9200/_cat/health?v

epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1554575745 18:35:45 elasticsearch green 4 4 0 0 0 0 0 0 - 100.0%


まとめ

Linuxコンテナ(systemd-nspawn)でホストまたぎのElasticsearchクラスタを構築してみました。 Docker+Kubernetes よりはるかにシンプルにできたと思います。

今回は私の趣味で ZFS on Linux を使っていますが、ほぼOS標準の機能だけでもできるので、運用の観点でも安心感があるのではないでしょうか。

今回の検証で、Linuxコンテナを完全に理解しました(※チュートリアルを終えたレベルという意味)。なお、Solarisコンテナについては何もわかりません(※10年以上キャリアグレードの商用環境で使いまくったという意味)。