概要
Linuxコンテナ(systemd-nspawn)を使って、複数ホストにまたがるElasticsearchクラスタを構築してみました。
私はコンテナ歴12年くらいですが、ほとんどSolarisコンテナ(Zones)ばかり使っていて、Linuxコンテナはどんなものかよくわかっていないので、おかしなことを言っていましたらご指摘ください。
背景
以下のような感じの要件で、基盤のアーキテクチャを考えていました。
- 50~100ノード規模のElasticsearchクラスタを作りたい
- オンプレ環境で
- 一度起動したら年単位で動き続ける
- 商用ライセンスはあまり使いたくないけど、人柱になって自力でなんとかする気概はあり
なんとなく以下のような実現方式を考えました。本音はSolaris Zonesを使いたくてたまりませんが今回はLinux縛りです。
カテゴリ |
方式 |
評価(主観) |
物理系 |
物理サーバ50台 |
× ラックが足りない。運用嫌だ。 |
|
物理サーバ+マルチプロセス |
△ 運用嫌だ。 |
ハイパーバイザ系 |
VMware |
△ ライセンス必要。オーバーヘッド嫌だ。 |
|
KVM, Xen |
△ オーバーヘッド嫌だ。 |
コンテナ系 |
Docker |
△ どうも合わない。k8s, Swarmとかは大がかりすぎ。 |
|
OpenVZ |
△ まだあるのだろうか? |
|
LXD |
○? 良さそう。使うならUbuntuが良さそうか。 |
|
systemd-nspawn |
○? ほぼOS標準の機能! |
Dockerは少し検証してみたのですが、動くには動くのでしょうが、ホストまたぎのネットワークを構成するのが面倒で、KVSを立ち上げて、VXLANでオーバーレイネットワークとかになるのでやる気がなくなりました。
そのためだけにKubernetesとかSwarmを使うのも大がかりすぎに思えました。
qiita.com
DockerをDisる気はなく、用途がマッチすれば良いと思いますが、仮想マシンのようなことがやりたいならまったく不向きで、無理矢理使っても悲惨な運用になるのが落ちという感じがします。
長年Solarisコンテナを使ってきたのもあって、なるべくシンプルでほぼOS標準の機能で動作するということから systemd-nspawn にたどり着き、検証してみようと思いました。
なお、クラウド前提なら、Amazon Elasticsearch Service を検討するのもいいと思います。
aws.amazon.com
シナリオ
今回構築する環境は以下のようなイメージです。物理マシンがあれば使ってもいいですが、私はVirtualBox上に構築することにしました。
構築は以下のような流れで進めました。
- ホスト構築編
- CentOS 7 をインストールする
- systemd-networkd をインストールする
- ZFS をインストールする(任意)
- コンテナ構築編
- コンテナイメージをインストールする
- コンテナを起動する
- Elasticsearchをインストールする
- コンテナをコピーする
構築
ホスト構築編
OSはユーザが多いであろうCentOSにしました。バージョンは現時点で最新の7.6にしました。
インストールはできましたが、グラフィカルインストーラだとマウスでボタンをクリックできないことがある謎の事象に遭遇したので、テキストインストーラを使う方がいいような気がします。
qiita.com
今回はホスト間で通信できる必要があるので、VirtualBoxのネットワークの設定でブリッジアダプターを使うとよいと思います。また、プロミスキャスモードを許可する設定も必要です。
また、OSの領域とコンテナの領域を分けたいので、ストレージの設定画面でディスクイメージを1つ追加しました。
SELinuxは無効にしました。
$ sudo vi /etc/sysconfig/selinux
----
...
SELINUX=disabled
...
systemd-networkd をインストールする
今回はホスト間で通信できるようにネットワークブリッジを使うので、ネットワークの管理をsystemd-networkdで行うのがよいようです。以下のページを参考にしました。
renediepstraten.nl
以下のページも参考にしました。systemd関連のドキュメントはArch Linuxのものがわかりやすい感じがします。
wiki.archlinux.jp
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 の公式の手順に従ってインストールします。
github.com
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文字以下にするのが無難なようです。
github.com
コンテナ構築編
コンテナイメージをインストールする
systemd-nspawn では、コンテナイメージをインストールする方法は主に3種類くらいあるようです。
- yum install --installroot でインストールする。chroot環境を作るのと似た感じ。
- ビルド済みのイメージを使う。Dockerのイメージも使えるようだ。
- すでに作成済みのコンテナからコピーする。
ひとまずオーソドックスな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を有効にします。
$ sudo 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 のページがわかりやすいと思います。
wiki.archlinux.jp
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 も考慮した方がよいと思います。
www.elastic.co
サービスを有効にします。
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 ...
- machine-id変更: rm /etc/machine-id; systemd-machine-id-setup
- 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年以上キャリアグレードの商用環境で使いまくったという意味)。
Qiitaにも上げてみました。
qiita.com