Oracle Cloud InstructureのAlways Free Tierで仮想マシンをNomadで使い倒す
Oracle Cloud InstructureのAlways Free Tierで仮想マシンをNomadで使い倒す
-
無料でVPSを使って遊ぶ
- ディスクの消費(I/O)が無料なので、Dockerコンテナで検証環境を作っては潰し、作っては潰しできる。
- 積極的にOracleのディスクを摩耗していこう。
- ディスクの消費(I/O)が無料なので、Dockerコンテナで検証環境を作っては潰し、作っては潰しできる。
-
制約
- OCIのA1シェイプが使えない
- RHEL,CentOSの代わりにAlmaLinuxを使いたい
- Podman-Dockerは使いたくない
- Kubernetesは使いたくない(スペック的に)
第一章 Nomadのシングルノード環境を構築して遊牧民になろう
- microk8sでよくない?という意見はある。でも、Nomadという名前が好き。
最小構成インストール
-
環境
- OCI Compute: VM.Standard.E2.1.Micro (Always Free-eligible)
- 1 core OCPU, 1 GB memory, 0.48 Gbps network bandwidth
- OS: AlmaLinux9
- OCI Compute: VM.Standard.E2.1.Micro (Always Free-eligible)
-
Docker-CEのインストール
- Apache License 2.0
# https://www.techrepublic.com/article/install-latest-docker-almalinux/
sudo dnf update
sudo dnf groupinstall "Development Tools"
sudo reboot
sudo dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install docker-ce --allowerasing
sudo systemctl enable --now docker
- Nomad/Consulのインストール
- Mozilla Public License 2.0
# releaseをRHELでインストールしている所がポイント
sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo dnf -y install nomad
sudo dnf -y install consul
- Nomad/Consulエージェントのテスト実行
# それぞれ新しいコンソールから実行する
sudo nomad agent -dev
# それぞれ新しいコンソールから実行する
sudo consul agent -dev
systemd(自動実行スクリプト)の設定
- /etc/systemd/system/nomad.service
# https://developer.hashicorp.com/nomad/tutorials/enterprise/production-deployment-guide-vm-with-consul#configure-systemd
sudo touch /etc/systemd/system/nomad.service
# /etc/systemd/system/nomad.service
# AlmaLinuxのバイナリ配置が若干違うので、nomadバイナリのPATHはちょっと修正
# 今回はスタンドアローン(serverであり、clientでもある)ので、user/groupともにrootに修正
[Unit]
Description=Nomad
Documentation=https://www.nomadproject.io/docs/
Wants=network-online.target
After=network-online.target
# When using Nomad with Consul it is not necessary to start Consul first. These
# lines start Consul before Nomad as an optimization to avoid Nomad logging
# that Consul is unavailable at startup.
Wants=consul.service
After=consul.service
[Service]
# Nomad server should be run as the nomad user. Nomad clients
# should be run as root
#User=nomad
#Group=nomad
User=root
Group=root
ExecReload=/bin/kill -HUP $MAINPID
ExecStart=/usr/bin/nomad agent -config /etc/nomad.d
# ExecStart=/usr/local/bin/nomad agent -config /etc/nomad.d
KillMode=process
KillSignal=SIGINT
LimitNOFILE=65536
LimitNPROC=infinity
Restart=on-failure
RestartSec=2
## Configure unit start rate limiting. Units which are started more than
## *burst* times within an *interval* time span are not permitted to start any
## more. Use `StartLimitIntervalSec` or `StartLimitInterval` (depending on
## systemd version) to configure the checking interval and `StartLimitBurst`
## to configure how many starts per interval are allowed. The values in the
## commented lines are defaults.
# StartLimitBurst = 5
## StartLimitIntervalSec is used for systemd versions >= 230
# StartLimitIntervalSec = 10s
## StartLimitInterval is used for systemd versions < 230
# StartLimitInterval = 10s
TasksMax=infinity
OOMScoreAdjust=-1000
[Install]
WantedBy=multi-user.target
- /etc/systemd/system/consul.service
# https://developer.hashicorp.com/consul/tutorials/production-deploy/deployment-guide
sudo touch /etc/systemd/system/consul.service
[Unit]
Description="HashiCorp Consul - A service mesh solution"
Documentation=https://www.consul.io/
Requires=network-online.target
After=network-online.target
ConditionFileNotEmpty=/etc/consul.d/consul.hcl
[Service]
EnvironmentFile=-/etc/consul.d/consul.env
User=consul
Group=consul
ExecStart=/usr/bin/consul agent -config-dir=/etc/consul.d/
ExecReload=/bin/kill --signal HUP $MAINPID
KillMode=process
KillSignal=SIGTERM
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
動作確認・エラー切り分け
エラー切り分け
# ほぼ間違いなくエラーが出るので、それぞれ以下のコマンドで切り分け
# # Nomad
# https://developer.hashicorp.com/nomad/docs/configuration
sudo -u nomad /usr/bin/nomad config validate /etc/nomad.d
sudo -u nomad /usr/bin/nomad agent -config /etc/nomad.d
sudo systemctl start nomad
sudo journalctl -xe
# # Consul
# https://developer.hashicorp.com/consul/docs/agent/config/config-files
sudo -u consul consul validate /etc/consul.d/
sudo -u consul /usr/bin/consul agent -config-dir=/etc/consul.d/
sudo systemctl start consul
sudo journalctl -xe
-
nomad: WARNING: Bootstrap mode enabled! Potentially unsafe operation.
- Nomadクラスタを構築するときに初回だけ使うが、今回はシングルノード構成なので、無視
-
consul: ==> Multiple private IPv4 addresses found. Please configure one with 'bind' and/or 'advertise'.
- OCIというか、VPCは基本的にプライベートIPでサーバを運用するので、docker-ce daemonが稼働している場合は
docker0
のネットワークである172.17.0.1/16
を検知してこのエラーが出る。ip a
コマンドで確認する事ができる。 - 今回はシングルノード構成なので以下の設定を加える
- /etc/consul.d/consul.hcl
# /etc/consul.d/consul.hcl server = true bind_addr = "127.0.0.1" advertise_addr = "127.0.0.1" bootstrap_expect = 1 retry_join = ["127.0.0.1"]
- OCIというか、VPCは基本的にプライベートIPでサーバを運用するので、docker-ce daemonが稼働している場合は
-
/opt/consul/* 周りのバグ
- 潔く過去の記憶を吹き飛ばそう
sudo rm -rf /opt/consul/*
動作確認
-
WebUIの動作確認
- Nomadの初期設定ではあろうことか全てのIFでリッスンする設定になっているが、デフォルトでOCIのVPSはSSHのポートしかfirewalldで許可していないので、SSHポートフォワーディングで接続を試す。
-
ssh <user>@<hostname> -L 4646:localhost:4646
-
サンプルjobの稼働
- http://localhost:4646/ui/jobs からRun Jobボタンでjobを登録できる
job "docs" { datacenters = ["dc1"] group "example" { network { port "http" { static = 5678 } } task "server" { driver = "docker" config { image = "hashicorp/http-echo" ports = ["http"] args = [ "-listen", ":5678", "-text", "hello world", ] } } } }
-
これをそのまま実行すると、0.0.0.0へのルーティングを持ったI/Fにバインドされてしまう(別にいいけど・・・)ので以下の設定を加える。
- /etc/nomad.d/nomad.hcl
client { network_interface = "lo" }
- こうすることで、127.0.0.1でリッスンしてくれる。多分。
- テンプレートを使用することもできる。
- https://pkg.go.dev/github.com/hashicorp/go-sockaddr/template#section-readme
-
改めて動作確認する
sudo nomad run docs.job curl localhost:5678 # hello world
まとめ
-
Nomadサーバを建てることで、jobの実行管理ができるようになった。
- Hashicorp製品はかなり整った設定なので、完全にシングルノード構成で構築するのは逆に手こずった。
-
以下の設定ファイルが全てなのでこれらをNode毎にAnsibleで管理するのが良い。Ansible使えないやつはNomad使うべきじゃない。
- /etc/consul.d/
- /etc/nomad.d/
-
本質的にはclientとserverの両方をたてている
- 結構ボロボロと要らんポート使うので、クラウド業者側のfirewallはしっかり制限しよう。
第二章 Nomadでクラスタを組もう
- Oracle Cloud InstructureのAlways Free Tierで1 OCPU / 1G MEMな仮想マシンが二台まで作れるらしいので、クラスタを組んで遊ぼう。
- 冗長構成ではないので、注意
まずは第一章で作成したサーバと同じ設定のものを二台用意する。
-
用意した環境
-
VM1 OCI Compute: VM.Standard.E2.1.Micro (Always Free-eligible)
- 1 core OCPU, 1 GB memory, 0.48 Gbps network bandwidth
- OS: AlmaLinux9
-
VM2 OCI Compute: VM.Standard.E2.1.Micro (Always Free-eligible)
- 1 core OCPU, 1 GB memory, 0.48 Gbps network bandwidth
- OS: AlmaLinux9
-
VM1 OCI Compute: VM.Standard.E2.1.Micro (Always Free-eligible)
-
ポイント
- 二台とも同じVirtual Cloud Networkに設定すること
- NomadとConsulがスタンドアローンで動いている状態までもっていくこと。
VM1とVM2の間の通信を全許可する
-
VM1,VM2の所属するVirtual Cloud Network(VCN)のサブネットを特定
- デフォルトは10.0.0.0/24のはず。
-
VM1,VM2の所属するVCNのSecurityListのingress-ACLを設定
- 10.0.0.0/24からの通信を全許可する
-
VM1,2のfirewalldをALL Permitに設定する
# 全許可!
sudo firewall-cmd --permanent --zone=public --set-target=ACCEPT
sudo firewall-cmd --reload
-
VM1,VM2のInternal FQDN (OCIのInstance情報参照)
- VCN内でクラスタ接続を行う為。
- 通常はVM名.subnet名.vcn名.oraclevcn.comである。
-
以上を実施後、お互いにInternal FQDNに対してpingを打ち応答があること、ARP情報に乗っかってきている事を確認しよう。
ping VM名.subnet名.vcn名.oraclevcn.com
cat /proc/net/arp
VM1,2の設定を変更しプロセスを再起動、クラスタ化する
-
VM1がマスターであるので、全く同じ設定になる。
-
{{ GetDefaultInterfaces | attr \"address\" }}
という記法は https://pkg.go.dev/github.com/hashicorp/go-sockaddr/template を参考にすること。OCIの場合はプライベートIPが0.0.0.0/0へのルートを持つので、この記法を使う必要があった。 -
Nomadの設定
- /etc/nomad.d/nomad.hcl
# /etc/nomad.d/nomad.hcl data_dir = "/opt/nomad/data" bind_addr = "0.0.0.0" server { enabled = true bootstrap_expect = 2 } client { enabled = true servers = ["<VM1のInternal FQDN>"] }
- 設定のチェックと再起動
sudo -u nomad /usr/bin/nomad config validate /etc/nomad.d sudo systemctl restart nomad sudo journalctl -xe sudo systemctl status nomad
-
Consulの設定
- /etc/consul.d/consul.hcl
# /etc/consul.d/consul.hcl server = true bind_addr = "{{ GetDefaultInterfaces | attr \"address\" }}" bootstrap_expect = 2 retry_join = ["<VM1のInternal FQDN>"]
- 設定のチェックと再起動
sudo -u consul consul validate /etc/consul.d/ sudo rm -rf /opt/consul/* #前回の思い出を削除 sudo systemctl restart consul sudo journalctl -xe sudo systemctl status consul
Nomadクラスタが稼働している事を確認する
- VM1,VM2両方でクラスタの稼働を確認する。
- Nomad
sudo nomad node status sudo nomad server members # どちらかがLeaderになっているはず
- Consul
sudo consul members
- ポイント
- 通常、優先度の概念が無いシステムにおいて、ノードが3の倍数とならない冗長構成は取らないので、うまくいかないケースが多い。(ビザンチン将軍問題)
- うまく行かない場合、片方を手動でjoinさせるか、再起動してみよう。
- 各ノードのNomadのNomadプロセスを意図的に落としてみよう。
- Leaderを落とすと、他のメンバーにLeaderが移譲される
- Leaderが復活しても、Leaderが移譲されたままである。
- 片方のノードを落とすと、Nomadクラスタが全断する
- ノードが3の倍数となってらず、残されたノードはリーダーを正しく決定することが出来ない為
- Leaderを落とすと、他のメンバーにLeaderが移譲される
- 通常、優先度の概念が無いシステムにおいて、ノードが3の倍数とならない冗長構成は取らないので、うまくいかないケースが多い。(ビザンチン将軍問題)
Nomadクラスタにジョブを登録し2つのノードで実行してみる
- 全部のノードで1個ずつjobを動かしたいなら、spreadを使う。
- 実はSpreadを指定しなくても、countが2以上であればデフォルトで複数のノードで均等にバランシングしてくれる。
- この場合、ノード数以上のcount(例えば、4)を設定すると、TCPのリッスンポートがコリジョンを起こすので3台目,4台目のデプロイが延々と終わらない。(ノードがクラスタの"dc1"に増えるのを健気に待ってくれる・・・)
- spread記法については https://developer.hashicorp.com/nomad/docs/job-specification/spread#value
- docs.job
job "docs" { datacenters = ["dc1"] group "example" { count=2 spread { attribute = "${node.unique.id}" } network { port "http" { static = 5678 } } task "server" { driver = "docker" config { image = "hashicorp/http-echo" ports = ["http"] args = [ "-listen", ":5678", "-text", "hello world", ] } } } }
- 実はSpreadを指定しなくても、countが2以上であればデフォルトで複数のノードで均等にバランシングしてくれる。
- 実行
sudo nomad run docs.job #どっちのノードから実行しても良い
curl <VM1かVM2のInternal FQDN>:5678
# hello world
まとめ
-
Nomadクラスタを2台のノードで構築し、冗長構成が機能しない事を確認した。
- 通常は3の倍数のノードで構築すること。
-
Nomadクラスタを使って、複数ノードに対して同一のJobを実行する事を確認した。
第3章 NomadでHTTPサービスを冗長構成にしよう
- ドメインレジストラ: 適当なやつ
- DNS権威サーバ: KnotDNS
- ロードバランサ: HAProxy
- HTTPサーバ: nginx
構築する構成
- 環境設定:
- 所有するドメイン: example.com
- 切り出すサブドメイン: oracle.example.com
- Host1のグローバルIPv4アドレス: IP1
- ドメイン1: a.oracle.example.com
- ドメイン2: ns1.oracle.example.com
- Host2のグローバルIPv4アドレス: IP2
- ドメイン1: b.oracle.example.com
- ドメイン2: ns2.oracle.example.com
サブドメインの委任
ドメインレジストラのexample.comのDNSレコード管理画面からDNSゾーンカット・ドメイン委任を行う
- DNSレコード設定例
oracle.example.com. IN NS ns1.oracle.example.com.
oracle.example.com. IN NS ns2.oracle.example.com.
ns1.oracle.example.com. IN A IP1
ns1.oracle.example.com. IN A IP2
ns2.oracle.example.com. IN A IP1
ns2.oracle.example.com. IN A IP2
DNS権威サーバの構築
-
DNS権威サーバとしてknotdnsを使用します
- https://hub.docker.com/r/cznic/knot
- 権威サーバとして軽量であり、RRLが使える
-
/config/knot.confを作成
- knotdnsのコンテナの中の /config/ の中にサンプルあります。
- DNS権威サーバのエチケットとして、DNS Cookie+RRLを適用しています
server:
rundir: "/rundir"
user: knot:knot
automatic-acl: off # NO NOTIFY
answer-rotation: on # DNS-ROUND-ROBIN
listen: [ 0.0.0.0@53 ]
log:
- target: stdout
any: debug
database:
storage: "/storage"
mod-cookies:
- id: default
secret-lifetime: 30h # The Server Secret is regenerated every 30 hours
badcookie-slip: 3 # The server replies only to every third query with a wrong cookie
mod-rrl:
- id: default
rate-limit: 200 # Allow 200 resp/s for each flow
slip: 2 # Approximately every other response slips
template:
- id: default
storage: "/config"
file: "%s.zone"
global-module: mod-cookies/default
global-module: mod-rrl/default
zone:
- domain: oracle.example.com
- /config/oracle.example.com.zoneを作成
$ORIGIN oracle.example.com.
$TTL 3600
@ SOA ns1.oracle.example.com. hostmaster.oracle.example.com. (
2010111213 ; serial
6h ; refresh
1h ; retry
1w ; expire
1d ) ; minimum
NS ns1
NS ns2
A IP1
A IP2
ns1 A IP1
ns1 A IP2
ns2 A IP1
ns2 A IP2
a A IP1
b A IP2
- knotdの実行
knotd -v -c /config/knot.conf
- 以上をもとに、Nomadのjobファイルを作成 (knotdns.jobという名前で保存)
# Change variables
variables {
domain = "oracle.example.com"
ip1 = "0.0.0.1"
ip2 = "0.0.0.2"
}
job "knotdns.job" {
datacenters = ["dc1"]
group "knotdns.group" {
count=2
spread {
attribute = "${node.unique.id}"
}
network {
port "domain" {
static = 53
}
}
task "knotdns.server" {
driver = "docker"
template {
destination = "local/knot.conf"
data = <<EOH
server:
rundir: "/rundir"
user: knot:knot
automatic-acl: off # NO NOTIFY
answer-rotation: on # DNS-ROUND-ROBIN
listen: [ 0.0.0.0@53 ]
log:
- target: stdout
any: debug
database:
storage: "/storage"
mod-cookies:
- id: default
secret-lifetime: 30h # The Server Secret is regenerated every 30 hours
badcookie-slip: 3 # The server replies only to every third query with a wrong cookie
mod-rrl:
- id: default
rate-limit: 200 # Allow 200 resp/s for each flow
slip: 2 # Approximately every other response slips
template:
- id: default
storage: "/config"
file: "%s.zone"
global-module: mod-cookies/default
global-module: mod-rrl/default
zone:
- domain: ${ var.domain }
EOH
}
template {
destination = "local/${ var.domain }.zone"
data = <<EOH
$ORIGIN ${ var.domain }.
$TTL 3600
@ SOA ns1.${ var.domain }. hostmaster.${ var.domain }. (
2010111213 ; serial
6h ; refresh
1h ; retry
1w ; expire
1d ) ; minimum
NS ns1
NS ns2
A ${ var.ip1 }
A ${ var.ip2 }
ns1 A ${ var.ip1 }
ns1 A ${ var.ip2 }
ns2 A ${ var.ip1 }
ns2 A ${ var.ip2 }
a A ${ var.ip1 }
b A ${ var.ip2 }
EOH
}
config {
image = "cznic/knot"
ports = ["domain"]
args = [
"knotd",
"-v",
"-c",
"/config/knot.conf",
]
volumes = ["local/knot.conf:/config/knot.conf","local/${ var.domain }.zone:/config/${ var.domain }.zone"]
}
}
}
}
- Nomadで実行
nomad run knotdns.job
-
VM1,VM2の所属するVCNのSecurityListのingress-ACLを設定
- 0.0.0.0/0からVMへのTCP/UDP53番ポート宛の通信を全許可する
-
動作確認
- oracle.example.comは例。ご自身の指定ドメイン
- 何度か叩いて、DNSラウンドロビンを確認すること
host oracle.example.com 8.8.8.8
# 連打するとDNS-Cookiesが発動し、RRLも引き起こすので
# 常識的な回数実行
- もし実行中コンテナ上のknotdにアクセスしたい場合は以下
nomad job status knotdns.job
#(Allocation IDをメモ)
nomad exec $AID bash
knotc stats
分散型権威DNSサーバ完成!
- ゾーン転送をしないくせに両方全く同じゾーン情報を返すDNS権威サーバの出来上がり!なかなかきもい!
Discussion