インターネットにつながらないLinuxクラスターを運用する
はしがき
ここ最近は小規模なLinuxクラスターの運用管理もやっています。
事情よりクラスター自体がインターネットに接続できないため、通常に比べ管理が面倒だったり、外部サービスを利用できないため不便なことが多々あります。 そんな環境で色々やってるので書いておきます。
環境はCentOS7です。
構成管理
ansibleを使用しています。クライアント側にインストールの必要が無いため、管理用端末のみインターネットに接続してインストールすればいいので楽です。
ansible用ユーザーを作って公開鍵認証にしていますが、初回のユーザー作成と鍵配布もansibleで行うため、
そのときだけはremote_user: root
とbecome_method: su
を使ってrootパスワードを使用しています。
---
- hosts: all
become: yes
become_method: su
remote_user: root
tasks:
- name: provisioner
user:
name: provisioner
append: yes
- name: provisioner ssh key
authorized_key:
user: provisioner
key: "{{ lookup('file', '../../ssh/id_rsa.pub') }}"
- name: add sudoers
lineinfile: >
dest=/etc/sudoers
line='provisioner ALL=(ALL) NOPASSWD: ALL'
上記のplaybookを--ask-become-pass
, --ask-pass
付きで流します。
これ以後はprovisionerユーザーがsudo可能になるので、
他のplaybookbecome:yes
だけでシステム設定できるようになりました。
ちなみにこのときにansible.cfgでssh_args
でssh鍵を指定していると接続エラーになるので、
手動でコメントアウトしています。いい方法ないかな。
[ssh_connection]
ssh_args = -F ssh/ssh_config #ここ
package
yum
インターネットにつながらないため、yumなどがそのままでは使用できません。 rpmをコピーする方法では依存関係処理が面倒なため、フルミラーを作ります。 ftp.riken.jpあたりからrsyncしてくるのが早いです。
yumミラーはhttpで見えるようにしてもいいのですが、今回は全ノードがnfsをマウントしているのでそこにおいています。
/etc/yum.repos.dの中身を消して、次のようにローカルのものに書き換えれば完了です。
[local-repo-os]
name=local-repo-os
baseurl=file:///nfs/mirror/centos/7/os/x86_64
enabled=1
[local-repo-updates]
name=local-repo-updates
baseurl=file:///nfs/mirror/centos/7/updates/x86_64
enabled=1
R
yum以外にもRのリポジトリミラーも作っています。
Rの場合、rsyncでコピー後、Rからwrite_PACKAGE関数を読んでパッケージリストを作る必要があります。
tools:::write_PACKAGES("/nfs/mirror/R/src/contrib")
その後、$HOME/.Rprofileにmirrorの場所を指定すると通常どおりinstall.packages
が使用できるようになります。
options(repos="file:///nfs/mirror/R")
bioconductorもミラーしているのですが、ディレクトリ構成が異なりcontribディレクトリの場所をinstall.packages
が見つけてくれないため、上記のoptionsではうまく行きません。
今のところcontriburl
を直接指定していますが、もっといい方法がありそうです。
install.packages(“package”, contriburl=”file:///nfs/mirror/biocondutor/packages/3.5/contrib”)
stringi
ネットワークにつながらない環境で、tidyverse
などをインストールしようとすると大抵stringiのインストールで失敗します。
stringiがicudtNN.zip(NNは数字)をインストール中にダウンロードしようとするためです。
http://static.rexamine.com/packages/から対応バージョンのzipファイルをダウンロードして、ノードにコピーします。
configure.vers
にICUDT_DIR
をzipのあるパスを指定してインストール可能です。
install.packages("stringi", configure.vars="ICUDT_DIR=<dir_to_copy_icudt_from>")
python
直接whlファイルをpipで入れるときにプラットフォームが合わないとエラーになることがあります。
whlフィアルの名前が-cp27m-manylinux1_x86_64.whl
のようにmanylinuxとなっていたら
import pip; print(pip.pep425tags.get_supported()
とやって出て来るプラットフォーム名に合わせるとインストール可能になります。
pythonのミラーは今後の課題です。
ntp
結構忘れがちなのがノードの時計です。ほっとくと結構ずれますが、publicなntpサーバーには接続できないため補正がかかりません。 内部ntpサーバーを立ればクラスター内の時刻同期は出来ますが、外部とはずれ続けます。その時刻の補正も必要です。
対策として、シチズンGPSタイムサーバーを使っています。 アンテナを窓際において動かしていますが、 幸い受信感度は良く、問題なく時刻合わせが出来ています。
監視
外部監視サービスは使用できないので、自前で運用することになります。
今回はprometheusを使い、 グラフはgrafanaで表示しています。
prometheusを選んだ理由は、触ってみたかったというのが一番ですが
- 単一バイナリで楽
- アラートもできる
という点からも使ってみようという気になりました。
service discoveryは使用してないためあまり恩恵がないかもしれませんが、 規模が大きくなったらconsulあたりを入れようかとも考えています。
使ってみた感想としては、
- node_exporter楽
- Textfile Collectorで追加も楽
- queryやconfigのドキュメントが最小限なのでなれるまで時間かかる
- わかればgrafanaからグラフ作るのも色々できる
- alert.rulesの書式がいつの間にかymlになってて動かなくなってた
zabbixのようになんでも入りではありませんが、手をかけることで育っていく感じがしています。
アラート
アラートルールは
- ノードダウン
- RAID状態(textfile collectorで自前で出してる数値)
- DISK空き容量
- CPU温度
などをとりあえず設定しています。雑です。
groups:
- name: alert.rules
rules:
- alert: InstanceDown
expr: up == 0
for: 2m
labels:
severity: critical
annotations:
description: '{{ $labels.instance }} has been down for more than 2 minutes.'
summary: Instance {{ $labels.instance }} down
- alert: RAIDFail
expr: raid_failed + raid_degraded > 0
labels:
severity: critical
annotations:
description: '{{ $labels.instance }} raid failed'
summary: Instance {{ $labels.instance }} raid failed
- alert: DiskSpace
expr: min(node_filesystem_free / node_filesystem_size) < 0.05
labels:
severity: critical
annotations:
description: '{{ $labels.instance }} disk full'
summary: Instance {{ $labels.instance }} disk full
- alert: CPUTemp
expr: node_hwmon_temp_celsius{chip=~"platform_coretemp_.*"} > 80
labels:
severity: critical
annotations:
description: '{{ $labels.instance }} cpu over temp'
summary: Instance {{ $labels.instance }} cpu over temp
通知
アラートできるようになりましたが、インターネットにつながらない場合アラートを送る先が難しいです。 slack通知などができれば楽なのですがそういうわけにもいきません。 grafanaのダッシュボードをずっと見ててもいいのですがあまり楽しくはないです。
このクラスターは自分たちが使用しているため、24時間365日の監視は必要なく、 アラートは業務中に自分が気がつけばいいだろうということにしました。
alertmanagerから任意のwebhookを呼べるので、 今回は使用しているStumpWM内にwebサーバーを動かし受信、メッセージを表示させてみました。
.stumpwm/init.lispの初期化コード内でwookieを立てて、 alertmanagerから送られてくるjsonをそのまま表示しています。
(ql:quickload 'wookie)
(ql:quickload 'cl-json)
(wookie:load-plugins)
(wookie:defroute (:post "/") (req res)
(let ((body (wookie-util:body-to-string
(wookie:request-body req)
(wookie-util:get-header (wookie:request-headers req) "context-type")))
(*print-right-margin* 30))
(stumpwm:message "ALERT~%~W" (cl-json:decode-json-from-string body)))
(wookie:send-response res :body "OK"))
(bt:make-thread
(lambda ()
(as:with-event-loop ()
(wookie:start-server
(make-instance 'wookie:listener :port 9999))))))
お手軽ですが、まあ気がつくだろうという感じです。
余談ですがアラートをテストしていて、 アラートが動いてないことに気がつくためのアラートはどうしたらいいんだろうと考えています。
まとめ
インターネットにつながらないと、色々なエコシステムに乗ることが出来ず面倒です。 自前で運用するリスクやコストもかかるので可能なら避けたほうが良いでしょう。
10年ぐらい前の運用はこんな感じだった気もするので、世の中便利になったのではないでしょうか。 AWSとかdockerとかすごいなあと思いました。
他にも思いついたら書いていきたいと思います。