概要
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を使うのも大がかりすぎに思えました。
DockerをDisる気はなく、用途がマッチすれば良いと思いますが、仮想マシンのようなことがやりたいならまったく不向きで、無理矢理使っても悲惨な運用になるのが落ちという感じがします。 長年Solarisコンテナを使ってきたのもあって、なるべくシンプルでほぼOS標準の機能で動作するということから systemd-nspawn にたどり着き、検証してみようと思いました。
なお、クラウド前提なら、Amazon Elasticsearch Service を検討するのもいいと思います。
シナリオ
今回構築する環境は以下のようなイメージです。物理マシンがあれば使ってもいいですが、私はVirtualBox上に構築することにしました。
構築は以下のような流れで進めました。
- ホスト構築編
- コンテナ構築編
- コンテナイメージをインストールする
- コンテナを起動する
- Elasticsearchをインストールする
- コンテナをコピーする
構築
ホスト構築編
CentOS 7 をインストールする
OSはユーザが多いであろうCentOSにしました。バージョンは現時点で最新の7.6にしました。
インストールはできましたが、グラフィカルインストーラだとマウスでボタンをクリックできないことがある謎の事象に遭遇したので、テキストインストーラを使う方がいいような気がします。
今回はホスト間で通信できる必要があるので、VirtualBoxのネットワークの設定でブリッジアダプターを使うとよいと思います。また、プロミスキャスモードを許可する設定も必要です。
また、OSの領域とコンテナの領域を分けたいので、ストレージの設定画面でディスクイメージを1つ追加しました。
SELinuxは無効にしました。
$ sudo vi /etc/sysconfig/selinux ---- ... SELINUX=disabled ...
systemd-networkd をインストールする
今回はホスト間で通信できるようにネットワークブリッジを使うので、ネットワークの管理をsystemd-networkdで行うのがよいようです。以下のページを参考にしました。
以下のページも参考にしました。systemd関連のドキュメントはArch Linuxのものがわかりやすい感じがします。
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 の公式の手順に従ってインストールします。
$ 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文字以下にするのが無難なようです。
コンテナ構築編
コンテナイメージをインストールする
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 のページがわかりやすいと思います。
Elasticsearchをインストールする
ホスト側で、vm.max_map_countの値を増やしておきます。
$ sudo vi /etc/sysctl.d/10-vm.conf --- vm.max_map_count=262144 $ sudo sysctl -p
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 も考慮した方がよいと思います。
サービスを有効にします。
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のクローン機能を使って作りました。以下のあたりに気をつければ良いと思います。
- ホスト
- コンテナ
クラスタの状態を確認すると、無事に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