bobuhiro11's diary

ER-X を Prometheus で監視する

10 Aug 2020

Ubiquiti Networks社のルータ EdgeRouter X (ER-X)を数年前に購入したものの放置したままだったので、 今回 L2 スイッチとして使ってみた。 ER-X上ではDebianベースのOSで動いているため、Prometheus用に Node exporter のような監視プログラムを使うことができる。 Prometheus自体はRaspberry Pi上で動かした。 ElastiFlowも動かしたかったが、Raspberry Piのスペックでは厳しそうだった。

Prometheus と Grafana の導入

Raspberry PiにPrometheusとGrafnaをインストールする。 Prometheusはメトリクスを収集するために、GrafanaはPrometheusで収集したメトリクスをウェブUIから確認するために使う。

wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
apt install prometheus grafana -y
sudo systemctl start prometheus
sudo systemctl start grafana-server

Node exporter の導入

ER-XにNode exporterをインストールする。 CPU、メモリ、ディスク、ネットワークに関連するメトリクスを自動で収集してくれる。

curl -L https://github.com/prometheus/node_exporter/releases/download/v1.0.1/node_exporter-1.0.1.linux-mipsle.tar.gz -o node_exporter.tar.gz
tar xf ./node_exporter.tar.gz
sudo mv ./node_exporter-1.0.1.linux-mipsle/node_exporter /usr/bin/node_exporter

cat << EOF | sudo tee /lib/systemd/system/node_exporter.service
[Unit]
Description=NodeExporter
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/bin/node_exporter
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl start node_exporter

Blackbox exporter の導入

自宅内外の適当なホストに対して常時Pingを打ち続けることで外部監視をしたい。 Raspberry Pi上にBlackbox exporterをインストールする。

curl -L https://github.com/prometheus/blackbox_exporter/releases/download/v0.17.0/blackbox_exporter-0.17.0.linux-armv7.tar.gz -o blackbox_exporter.tar.gz
tar -xzf blackbox_exporter.tar.gz
sudo cp blackbox_exporter /usr/bin
sudo cp blackbox.yml /etc/

cat << EOF | sudo tee  /lib/systemd/system/blackbox_exporter.service
[Unit]
Description=BlackboxExporter
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/bin/blackbox_exporter --config.file=/etc/blackbox.yml
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

cat << EOF | sudo tee -a /etc/blackbox.yml
icmp_ipv4:
  prober: icmp
  icmp:
    preferred_ip_protocol: ip4
EOF


cat << EOF | sudo tee -a /etc/prometheus/prometheus.yml
  - job_name: 'blackbox'
    metrics_path: /probe
    params:
      module: [icmp_ipv4]
    static_configs:
      - targets:
        - localhost
        - 192.168.3.1
        - 192.168.3.200
        - 192.168.3.201
        - 192.168.3.202
        - google.co.jp
        - yahoo.co.jp
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: 127.0.0.1:9115
EOF

sudo systemctl daemon-reload
sudo systemctl start blackbox_exporter
sudo systemctl restart prometheus

Educational Codeforces Round 92 C. Good String

04 Aug 2020

提出したコード

文字列 $t$ が与えられるので、$t$ の循環左シフト後と循環右シフト後の文字列を等しくしたい。 $t$からいくつか文字を取り除いてこの条件を満たすときに、取り除く最小の文字数は?

$n$ を $t$ の文字列長として、その偶奇で分けて考える。

$n$ が奇数の場合

$t = t_1 t_2 t_3 t_4 t_5$ を例とする。 巡回左シフト $ t_2 t_3 t_4 t_5 t_1$ と巡回右シフト $t_5 t_1 t_2 t_3 t_4$ を等しくしたい。 つまり、すべての文字を等しくする必要がある。

$n$ が偶数の場合

$t = t_1 t_2 t_3 t_4$ を例とする。 巡回左シフト $t_2 t_3 t_4 t_1$ と巡回右シフト $t_4 t_1 t_2 t_3$ を等しくしたい。 つまり、偶数番目はすべて同じ文字であって、かつ奇数番目もすべて同じ文字でないといけない。

解法

いくつかの文字を取り除いた後の文字列の先頭2文字について全探索をする。 先頭2文字が同じ場合、先頭2文字$aa$の後に何文字か$a \dots a$が続けばよい。 違う場合は、先頭2文字$ab$の後に$ababab \dots ab$が続けばよい。


QEMU/KVM上 で Kubernetes The Hard Way (事前準備からTLS証明書の作成まで)

28 Jul 2020

QEMU/KVM上で https://github.com/kelseyhightower/kubernetes-the-hard-wayを試してみたので、ログを残しておく。 元々の文章では、GCPを前提としているが、今回は手元のQEMU/KVM上で構築した。

バージョン一覧

  • kubernetes-the-hard-way bf2850974e19c118d04fdc0809ce2ae8a0026a27
  • Kubernetes 1.12.0
  • containerd Container Runtime 1.2.0-rc.0
  • gVisor 50c283b9f56bb7200938d9e207355f05f79f0d17
  • CNI Container Networking 0.6.0
  • etcd v3.3.9
  • CoreDNS v1.2.2

1. 事前準備

VMイメージを取得して、起動するまで。

# VMイメージの準備
$ http_dir=http://ftp.jaist.ac.jp/pub/Linux/Fedora/releases/30/Cloud/x86_64/images
$ wget $http_dir/Fedora-Cloud-Base-30-1.2.x86_64.qcow2
$ virt-customize -a /var/lib/libvirt/images/Fedora-Cloud-Base-30-1.2.x86_64.qcow2 \
  --run-command 'yum remove cloud-init* -y' \
  --root-password password:root
$ cd /var/lib/libvirt/images/
$ for i in 0 1 2 ; do \
  sudo cp ./Fedora-Cloud-Base-30-1.2.x86_64.qcow2 controller-$i.qcow2; \
  sudo cp ./Fedora-Cloud-Base-30-1.2.x86_64.qcow2 worker-$i.qcow2; \
  done

# VM起動
$ for name in controller-0 controller-1 controller-2 worker-0 worker-1 worker-2
  do
    sudo virt-install --name=$name --virt-type kvm --graphics none \
                      --disk /var/lib/libvirt/images/$name.qcow2 --vcpus=1 \
                      --ram=512 --network network=default,model=virtio \
                      --import --noautoconsole
  done
$ sudo virsh list --all
Id   Name           State
-----------------------------
4    controller-0   実行中
5    controller-2   実行中
6    controller-1   実行中
7    worker-0       実行中
8    worker-1       実行中
9    worker-2       実行中

# VM初期設定(VMごとに)
$ hostnamectl set-hostname controller-0
$ nmcli c m "System eth0" ipv4.addresses "192.168.122.10/24" \
                          ipv4.method manual \
                          connection.autoconnect yes \
                          ipv4.gateway 192.168.122.1 \
                          ipv4.dns 8.8.8.8
$ systemctl restart NetworkManager

# host側のNW設定
$ sudo sysctl -p /etc/sysctl.conf
net.ipv4.ip_forward = 1
$ cat /etc/hosts | grep 192.168.122
192.168.122.10 controller-0
192.168.122.11 controller-1
192.168.122.12 controller-2
192.168.122.13 worker-0
192.168.122.14 worker-1
192.168.122.15 worker-2

2. クライアントツールのインストール

# Public Key Infrastructure (PKI)のため
$ wget -q --show-progress --https-only --timestamping \
   https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 \
   https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
$ chmod +x cfssl_linux-amd64 cfssljson_linux-amd64
$ mv cfssl_linux-amd64 /usr/local/bin/cfssl
$ mv cfssljson_linux-amd64 /usr/local/bin/cfssljson
$ cfssl version
Version: 1.2.0
Revision: dev
Runtime: go1.6

# kubectlのインストール
$ wget https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kubectl
$ chmod +x kubectl
$ mv kubectl /usr/local/bin
$ kubectl version --client
Client Version: version.Info{Major:"1", Minor:"12", GitVersion:"v1.12.0",
                             GitCommit:"0ed33881dc4355495f623c6f22e7dd0b7632b7c0",
                             GitTreeState:"clean",
                             BuildDate:"2018-09-27T17:05:32Z",
                             GoVersion:"go1.10.4", Compiler:"gc",
                             Platform:"linux/amd64"}

3. コンピュートリソースの準備

今回はGCPを使わないので省略。

4. 認証局のセットアップとTLS証明書の生成

CloudFlareのPKIツールキットを使って、PKI基盤を作る。 まず、CA(Certificate Authority)のための設定ファイル、証明書、秘密鍵を作る。

$ cat > ca-config.json <<EOF
{
  "signing": {
    "default": {
      "expiry": "8760h"
    },
    "profiles": {
      "kubernetes": {
        "usages": ["signing", "key encipherment", "server auth", "client auth"],
        "expiry": "8760h"
      }
    }
  }
}
EOF

$ cat > ca-csr.json <<EOF
{
  "CN": "Kubernetes",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "CA",
      "ST": "Oregon"
    }
  ]
}
EOF

$ cfssl gencert -initca ca-csr.json | cfssljson -bare ca
[INFO] generating a new CA key and certificate from CSR
[INFO] generate received request
[INFO] received CSR
[INFO] generating key: rsa-2048
[INFO] encoded CSR
[INFO] signed certificate with serial number XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

$ ls ca*pem
ca-key.pem  ca.pem

続いて、各コンポーネントのクライアント・サーバ証明書を作る。 また、adminユーザのためのクライアント証明書を作る。

$ cat > admin-csr.json <<EOF
{
  "CN": "admin",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:masters",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

$ cfssl gencert \
   -ca=ca.pem \
   -ca-key=ca-key.pem \
   -config=ca-config.json \
   -profile=kubernetes \
   admin-csr.json | cfssljson -bare admin
[INFO] generate received request
[INFO] received CSR
[INFO] generating key: rsa-2048
[INFO] encoded CSR
[INFO] signed certificate with serial number XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

$ ls admin*pem
admin-key.pem  admin.pem

Node AuthorizerがKubeletを認証する。証明書と秘密鍵をノードごとに作成する。

$ for instance in worker-0 worker-1 worker-2; do
cat > ${instance}-csr.json <<EOF
{
  "CN": "system:node:${instance}",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:nodes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

ip=`cat /etc/hosts | grep $instance | awk '{print $1;}'`
echo $instance $ip
cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -hostname=${instance},${ip} \
  -profile=kubernetes \
  ${instance}-csr.json | cfssljson -bare ${instance}
done

$ ls worker*pem
worker-0-key.pem  worker-0.pem  worker-1-key.pem  worker-1.pem  worker-2-key.pem  worker-2.pem

kube-controller-managerのためのクライアント証明書と秘密鍵を作る。

$ cat > kube-controller-manager-csr.json <<EOF
{
  "CN": "system:kube-controller-manager",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:kube-controller-manager",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

$ cfssl gencert \
   -ca=ca.pem \
   -ca-key=ca-key.pem \
   -config=ca-config.json \
   -profile=kubernetes \
   kube-controller-manager-csr.json | cfssljson -bare kube-controller-manager
[INFO] generate received request
[INFO] received CSR
[INFO] generating key: rsa-2048
[INFO] encoded CSR
[INFO] signed certificate with serial number XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

$ ls kube-controller-manager*pem
kube-controller-manager-key.pem kube-controller-manager.pem

kube-proxyのための証明書も作る。

$ cat > kube-proxy-csr.json <<EOF
{
  "CN": "system:kube-proxy",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:node-proxier",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

$ cfssl gencert \
   -ca=ca.pem \
   -ca-key=ca-key.pem \
   -config=ca-config.json \
   -profile=kubernetes \
   kube-proxy-csr.json | cfssljson -bare kube-proxy
[INFO] generate received request
[INFO] received CSR
[INFO] generating key: rsa-2048
[INFO] encoded CSR
[INFO] signed certificate with serial number XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

$ ls kube-proxy*pem
kube-proxy-key.pem  kube-proxy.pem

kube-scheduler用のクライアント証明書と秘密鍵を作る。

$ cat > kube-scheduler-csr.json <<EOF
{
  "CN": "system:kube-scheduler",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:kube-scheduler",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

$ cfssl gencert \
   -ca=ca.pem \
   -ca-key=ca-key.pem \
   -config=ca-config.json \
   -profile=kubernetes \
   kube-scheduler-csr.json | cfssljson -bare kube-scheduler
[INFO] generate received request
[INFO] received CSR
[INFO] generating key: rsa-2048
[INFO] encoded CSR
[INFO] signed certificate with serial number XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
$ ls kube-scheduler*pem
kube-scheduler-key.pem  kube-scheduler.pem

kubernetes APIサーバのクライアント証明書と秘密鍵を作成する。 LBをたてるのはめんどうなので、controller-0をAPI public addressとした。

$ KUBERNETES_PUBLIC_ADDRESS=192.168.122.10

$ cat > kubernetes-csr.json <<EOF
{
  "CN": "kubernetes",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

$ cfssl gencert \
   -ca=ca.pem \
   -ca-key=ca-key.pem \
   -config=ca-config.json \
   -hostname=10.32.0.1,10.240.0.10,10.240.0.11,10.240.0.12,${KUBERNETES_PUBLIC_ADDRESS},127.0.0.1,kubernetes.default \
   -profile=kubernetes \
   kubernetes-csr.json | cfssljson -bare kubernetes
[INFO] generate received request
[INFO] received CSR
[INFO] generating key: rsa-2048
[INFO] encoded CSR
[INFO] signed certificate with serial number XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
$ ls kubernetes*pem
kubernetes-key.pem  kubernetes.pem

service-accountの証明書と秘密鍵を作る。

$ cat > service-account-csr.json <<EOF
{
  "CN": "service-accounts",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

$ cfssl gencert \
   -ca=ca.pem \
   -ca-key=ca-key.pem \
   -config=ca-config.json \
   -profile=kubernetes \
   service-account-csr.json | cfssljson -bare service-account
[INFO] generate received request
[INFO] received CSR
[INFO] generating key: rsa-2048
[INFO] encoded CSR
[INFO] signed certificate with serial number XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
$ ls service-account*pem
service-account-key.pem  service-account.pem

サーバ・クライアント証明書を配布する。

$ for instance in worker-0 worker-1 worker-2; do
    scp ca.pem ${instance}-key.pem ${instance}.pem root@${instance}:~/
  done

$ for instance in controller-0 controller-1 controller-2; do
    scp ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem \
      service-account-key.pem service-account.pem root@${instance}:~/
  done

続きは余裕があるときにやっていく。


Educational Codeforces Round 89 D. Two Divisors

21 Jul 2020

提出したコード

整数$a \ge 2$が与えられるので、$gcd(d_1+d_2,a)=1$を満たすような組$(d_1,d_2)$を求めたい。 ただし、$d_1 \ge 2$かつ$d_2 \ge 2$。 これを$n$個回繰り返す。

まず $a = {p_1}^{k_1} \cdot {p_2}^{k_2} \cdots {p_m}^{k_m}$ のように素因数分解を行う。 もし $m = 1$あるいは$a$が素数の場合には、答えは$(-1,-1)$。 $m \ge 2$の場合には、$(p_1, p_2 \cdot p_3 \cdots p_m)$が答えとなる。

なぜこれが答えでよいのか見ていく。 「$d_1 + d_2$がいかなる素因数$p_i$に対しても割り切れないこと」が言えればよい。 まず、「$d1 + d2$ が $p1$ で割り切れない」ことが正しいのか考えてみる。 $d1 \equiv 0, d2 \not \equiv 0 (mod \ p_1) $ より、$d1 + d2 \not \equiv 0 (mod \ p_1)$ となる。 続いて、「$d1 + d2$ は $p_i (2 \le i \le m)$ で割り切れない」ことが正しいのか考えてみる。 $d1 \not \equiv 0, d2 \equiv 0 (mod \ p_i) $ より、$d1 + d2 \not \equiv 0 (mod \ p_i)$ となる。

$n$回のループで$a$が与えられるたびにナイーブに素因数分解を行うと間に合わないので、 あらかじめ値$x$が持つ最小の素因数 minPrime[x] を、エラトステネスの篩の要領で求めておくとよい。


ABC 173 F - Intervals on Tree

07 Jul 2020

提出したコード

木において「連結成分の数 = 頂点数 - 辺の数」で求められることに注目する。 直感的に、頂点が増えると連結成分の数が1増え、辺の数が増えると連結成分の数が1減るという感じで理解できる。 $f(L, R)$もこれで求められる。

\[\begin{aligned} &f(L, R) \\ &= 区間[L \ R]に含まれる点の数 \\ &- 両端が区間[L \ R]に含まれる辺の数 \\ \end{aligned}\]

これをLとRの全通りで求めて総和をとればよい。 頂点数と辺の数はそれぞれ独立に求めていけばよい。

\[\begin{aligned} &\sum_{L,R} f(L,R) \\ &= \sum_{L,R} 区間[L \ R]に含まれる頂点の数 \\ &- \sum_{L,R} 両端が区間[L \ R]に含まれる辺の数 \\ &= V - E \end{aligned}\]

以下のように図示してみると、ある頂点$i$は全体で$(i+1)(N-i)$回現れるので、すべての$i$でこれの総和を取って$V$とすればよい。 同様にある辺$(u, v)$は全体で$(u+1)(N-v)$回現れるので、すべての辺について総和を取って$E$とする。 $V-E$が答えとなる。