🏢

Oracle Cloud InstructureのAlways Free Tierで仮想マシンをNomadで使い倒す

2023/05/03に公開約17,900字

Oracle Cloud InstructureのAlways Free Tierで仮想マシンをNomadで使い倒す

  • 無料でVPSを使って遊ぶ

    • ディスクの消費(I/O)が無料なので、Dockerコンテナで検証環境を作っては潰し、作っては潰しできる。
      • 積極的にOracleのディスクを摩耗していこう。
  • 制約

    • 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
  • 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"]
    
  • /opt/consul/* 周りのバグ

    • 潔く過去の記憶を吹き飛ばそう
    sudo rm -rf /opt/consul/*
    

動作確認

  • WebUIの動作確認

    • Nomadの初期設定ではあろうことか全てのIFでリッスンする設定になっているが、デフォルトでOCIのVPSはSSHのポートしかfirewalldで許可していないので、SSHポートフォワーディングで接続を試す。
    • ssh <user>@<hostname> -L 4646:localhost:4646
  • サンプル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"
      }
      
    • 改めて動作確認する

      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
  • ポイント

    • 二台とも同じ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からの通信を全許可する
    • oci-ingress-acl.png
  • 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の倍数となってらず、残されたノードはリーダーを正しく決定することが出来ない為

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",
                    ]
                }
            }
        }
    }
    
  • 実行
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を使用します

  • /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番ポート宛の通信を全許可する
    • oci-ingress-acl-domain.png
  • 動作確認

    • 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権威サーバの出来上がり!なかなかきもい!

HTTPサーバの構築

第4章 Nomadで常時稼働JobとしてVPNサーバを動かそうf

第5章 NomadでバッチjobとしてLet'sEncrypt証明書発行ジョブを動かそう

Discussion

ログインするとコメントできます