りんけーじ - blog

2024/04/18 22:04 : NanoPi NEOでFreeBSDする

freebsd@generic:~ % uname -a
FreeBSD generic 14.0-STABLE FreeBSD 14.0-STABLE #0 stable/14-n267114-72c3d91294c4: Thu Apr  4 08:28:31 UTC 2024     root@releng1.nyi.freebsd.org:/usr/obj/usr/src/arm.armv7/sys/GENERIC arm

するためのあれこれ。

適当なマシンで、以下のとおり準備する。うっかりSTABLEを使ったが、まあ、なんでもいいだろう。

# pkg install u-boot-nanopi_neo-2024.01
# dd if=FreeBSD-14.0-STABLE-arm-armv7-GENERICSD-20240404-72c3d91294c4-267114.img.xz of=/dev/da0 bs=524288
# dd if=/usr/local/share/u-boot/u-boot-nanopi_neo/u-boot-sunxi-with-spl.bin of=/dev/da0 conv=notrunc,sync bs=1k seek=8

このイメージはデフォルトでSSHが有効で、ユーザ名freebsd/パスワードfreebsdでログインできる。さすがにrootユーザでのSSHログインはできないので、suとrootユーザの初期パスワードrootを使ってrootになる。

あとはパスワードを変更して(忘れたままインターネットに接続するようなアホはいないな?)、pkgでやりたい放題すればよい。

ちなみに、

WARNING: 32-bit kernels are deprecated and may be removed in FreeBSD 15.0.

とのことなので、15.0になったら64bitにしようね。

2024/04/14 20:04 : FreeBSDでOMRON UPSを監視する

OMRONのUPSを使っているが、長年監視を放置していた。いい加減監視したいと思って設定したので、記録を残しておく。まず、OpenBSD/NetBSDな人は以下を参照のこと。

オムロン製UPSをOpenBSD/NetBSDで使うためのメモ (GitHub Gist)

本記事は上記を参考に、FreeBSD 14.0向けに設定してみた記録になる。

やることは以下。

  • nutをインストールする
  • UPSのVendor IDとProduct IDを調べる
  • nutユーザがデバイスを使えるようにdevdを設定する
  • nutを設定する(※本稿では全部は扱わない)

諸事情あってproduct IDはすべて伏せている。参考にして設定する奇特な人は、XXXXのような部分は適宜読み替えてほしい。

nutをインストールする

pkgでいっぱつ。

# pkg install nut

UPS関係だから名前にUPSが入ってるだろうと思って pkg search ups しても出てこない。nutという名前は覚えて帰ろう。

UPSのVendor IDとProduct IDを調べる

なんでもいいので調べる。ここでは dmesgugenX.X を調べたあと、usbconfigdump_device_desc を使って調べた。

# usbconfig -d ugenX.X dump_device_desc
ugen0.3:  at usbus0, cfg=0 md=HOST spd=LOW (1.5Mbps) pwr=ON (100mA)
(...snip...)
  idVendor = 0x0590 
  idProduct = 0xXXXX 

nutユーザがデバイスを使えるようにdevdを設定する

nutはnutユーザで動作するので、通常 root:wheel/dev/ugenX.X (の指す /dev/usb/X.X) を触れない。devd を設定して、OMRON UPSだったらnutユーザにプレゼントしよう。設定後は service devd restart をお忘れなく。

/usr/local/etc/devd/nut-usb-omron.conf:

notify 100 {
  match "system" "USB";
  match "subsystem" "INTERFACE";
  match "type" "ATTACH";
  match "vendor" "0x0590";
  match "product" "0xXXXX";
        action "chgrp nut /dev/$cdev; chmod g+rw /dev/$cdev";
};

/usr/local/etc/devd/nut-usb.conf を参考に書いたが、subsystemはDEVICEではなくINTERFACEになるようだ。うまくいかないなら devd -d を実行して確認するといい。

nutを設定する

こんな感じ。driverは参考資料では blazer_usb だが、maintenance-onlyだよ、というので nutdrv_qx に変えた。

/usr/local/etc/nut/ups.conf:

[XXXXX]
        driver = nutdrv_qx
        port = auto
        desc = "OMRON XXXXX"
        vendorid = 0590
        productid = XXXX
        subdriver = ippon

通信テストは、以下のようにしてドライバを直接実行すればいい。read: (... のような出力が定期的に出ればOK。

# /usr/local/libexec/nut/nutdrv_qx -DDDD -a XXXXX

あとはUPSがどうなったらどうしたい、の話になるので、man nut.conf でも実行してnutと仲良くなるといい。

私はドライバ出力を食ってprometheusに出力したかっただけなので、nut自体は使っていない。

2024/03/29 08:03 : FreeBSDのVMでHDD容量を拡張

ESXiなどでディスクサイズを拡張したときどうするか。再起動すれば認識するのは分かっているが、再起動したくない。

というときは、以下でよい模様。

$ sudo camcontrol reprobe da0

あとはgpart resizeしたりgrowfsしたり、zpool online -eしたりすればよい。

2024/02/02 00:02 : gitlab-runnerにrefusing to work with credential missing host fieldと言われたら

GitLabでCIしていたら特定のホストで動いたときだけ fatal: refusing to work with credential missing host field というエラーが発生した。あまり情報も整理していないのでメモ程度だが、残さないよりマシと思って書き残すことにした。

結論:

  • GitLabにHTTPSでアクセスしている、かつ、HTTPSでクライアント証明書の提示を要求していて、gitlab-runner側に使う証明書と鍵を設定している。(この時点でたぶん、大多数の人は当て嵌まらないハズ。)
  • クライアント側証明書に使われている署名のハッシュアルゴリズムがSHA-1になっていて、opensslに蹴られていた。


とはいえ、opensslがなんらかの理由でイヤと言えば全部このエラーになってしまう気がするので、調べ方も重要であろう。

gitlab-runnerはいろんな環境変数やらなんやらを設定してgitを叩くので、手動で実行しても同じ問題は再現しづらい。そこで、gitlab-runnerに細工をして実行することにする。具体的には、以下を実施した。

  • 環境変数を設定した状態でgitlab-runnerを起動する。GIT_TRACE=1, GIT_CURL_VERBOSE=1あたりが特に有効。詳しくは Git-Internals-Environment-Variables を読まれたし。
  • runnerが作る一時ファイルはすぐに消されてしまうものがあるので、必要ならshell scriptなどで書いた偽のgitを作り、PATHを通しておく。

今回だと、GIT_TRACE=1GIT_CURL_VERBOSE=1 を設定することで、GitLab側のJob出力で以下を得た。

16:23:14.638471 http.c:845              == Info: could not load PEM client certificate from /var/tmp/gitlab_runner/builds/(中略)/ほげほげ.tmp/CI_SERVER_TLS_CERT_FILE, OpenSSL error error:0A00018E:S

このファイルを調べようとしたが、普段は存在しない。実行中だけ存在するものと考えて偽gitにファイルコピーを仕込んだ結果、この証明書はgitlab-runnerに設定しているものと同じと分かった。また、opensslのエラーコードから、これはどうやらmd too weakというエラーらしいことが分かり、ハッシュアルゴリズムを疑った。(実際、このエラーはFreeBSD 14なシステム上のgitlab-runnerだけで起きていて、13では起きなかった。opensslのバージョン違いによる差であったのだろう。)

謝辞: manを読んでおきながら読み飛ばして GIT_CURL_VERBOSE に気づいてなかったとき、教えていただいたzunda氏に感謝します。

余談: 今回の問題発生時は最初にGitLabサーバのHTTPSポートに接続してTCP handshakeを行い、直後にgitlab-runner側が切断するという不可解な動作をしていた。どうやら証明書のチェックはTCP connect後にやっているらしい。いずれにせよ、切断(先にFIN送出)しているのがgitlab-runner側なので、gitlab-runner側の問題であることは明白だったのだが、少々混乱もした。

2023/09/18 12:09 : FreeBSDでOpenObserveクラスタ構築

elasticsearchとlogstash(とJava)に嫌気が差したので、MinIO+OpenObserveに引っ越した。

  • minioは go install github.com/minio/minio@latestでさくっと入る。
  • OpenObserveのクラスタ構成のためにetcdが必要。こいつはpkgがあるので楽ちん。
  • OpenObserveはソースを拾ってきてビルドする。rust-nightlyが必要。WebUIのコードはFreeBSDでサポートされないCypressが含まれているので、package.jsonをいじって外す。
  • 各サーバに全コンポーネントをつっこむなら簡単。そうでない場合はetcd/minioをhaproxyとかで生きているサービスに飛ばすこと。

MinIO

特に書くことはない。やることはこれだけ。

go install github.com/minio/minio@latest
go install github.com/minio/mc@latest

jail上で起動すると、root disk判定が誤判定するので、 MINIO_ROOTDISK_THRESHOLD_SIZE=1 など環境変数を設定しておく。もちろん保存先を間違えると大変な目に遭うので注意。

etcd

pkg install coreos-etcd34 で入る。ちなみにrc scriptがないので、自分で daemon を呼ぶ。

起こし方は素直にhttps://etcd.io/docs/v3.5/op-guide/clustering/のガイドを読むのがいい。

一応残しておくと、試した設定はこんな感じ。各ホストで調整が必要なURLが多いので注意する。quorumを満たしていないと起動しないことと、一度起動できちゃうと無視される設定があるので注意する。変だなと思ったら rm -rf /var/db/etcd して起動しなおすほうが早い。

name: openobserve1
data-dir: "/var/db/etcd/data"
wal-dir: "/var/db/etcd/wal"
strict-reconfig-check: true

initial-cluster-state: new
#initial-cluster-state: existing
initial-cluster-token: openobserve-etcd
initial-cluster: "openobserve1=http://10.0.0.1:2380,openobserve2=http://10.0.0.2:2380"
initial-advertise-peer-urls: "http://10.0.0.1:2380"
listen-peer-urls: "http://10.0.0.1:2380"
advertise-client-urls: "http://10.0.0.1:2379"
listen-client-urls: "http://10.0.0.1:2379,http://127.0.0.1:2379"

OpenObserve

ビルドはサーバプロセス(rust)とWebUI(node.js)の2本立て。それぞれビルドする。

サーバ側はrustup.rsを使ってrustのnightlyを入れておく。pkg install rust-nightlyでも良さそうだが、どうもいつもcargoがコケるのでrustupを使っている。

rust(とcargo)を入れたらサーバをビルドする。そのままだとsimdjsonのビルドが失敗するので、.cargo/config.toml に以下を書き加える。

[target.x86_64-unknown-freebsd]
rustflags = ["-C", "target-feature=+sse2,+ssse3,+sse4.1,+sse4.2"]

書き加えたら、cargo build --releaseでビルドする。

WebUIはnodeとnpmでビルドする。ちなみにpkg install node npm したものだが、node18-18.16.0とnpm-node18-9.7.2になっている。まずはビルドに必要がなく、FreeBSDがunsupportedなCypressをpackage.jsonからもぎ取っておく。diffだとこんな感じ:

*** web/package.json.orig       Mon Sep 11 08:34:13 2023
--- web/package.json    Mon Sep 11 08:37:21 2023
***************
*** 14,21 ****
      "copy": "cd dist && mkdir src && cd src && mkdir assets && cd .. && cd .. && cp -r src/assets/* dist/src/assets/",
      "test:unit": "vitest --environment jsdom --root src/",
      "test:unit:coverage": "vitest --coverage",
-     "test:e2e": "start-server-and-test preview :4173 'cypress run --e2e'",
-     "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress open --e2e'",
      "build-only": "vite build",
      "build-only-alpha1": "vite build --mode=alpha1",
      "build-only-cloud-prod": "vite build --mode=production",
--- 14,19 ----
***************
*** 52,59 ****
      "vuex": "^4.0.2"
    },
    "devDependencies": {
-     "@cypress/vue": "^4.0.0",
-     "@cypress/webpack-dev-server": "^2.0.0",
      "@quasar/vite-plugin": "^1.0.10",
      "@rushstack/eslint-patch": "^1.1.0",
      "@types/d3-hierarchy": "^3.1.2",
--- 50,55 ----
***************
*** 71,82 ****
      "@vue/test-utils": "^2.0.2",
      "@vue/tsconfig": "^0.1.3",
      "c8": "^7.11.3",
-     "cypress": "^10.3.0",
-     "cypress-localstorage-commands": "^2.2.1",
      "dotenv": "^16.0.3",
      "eslint": "^8.5.0",
      "eslint-config-prettier": "^8.5.0",
-     "eslint-plugin-cypress": "^2.12.1",
      "eslint-plugin-vue": "^9.5.1",
      "fs-extra": "^11.1.1",
      "happy-dom": "^6.0.4",
--- 67,75 ----

変更したら、npm inpm run buildでビルドする。distに成果物が入るので、ここをnginxなどで公開する。(後述)

それぞれビルドしたら起動する。WebUI側はstatic contentsなので、nginxなどでserveする。nginx.confはこんな感じだろうか:

worker_processes 1;
events {
    worker_connections 1024;
}
http {
    include mime.types;

    sendfile on;
    keepalive_timeout 65;

    server {
        listen 80;
        server_name openobserve.coco.local;
        location / {
            proxy_pass http://127.0.0.1:5080;
        }
        location /assets {
            alias /openobserve/web/dist/assets;
            index index.html;
        }
        location /web {
            alias /openobserve/web/dist;
            index index.html;
        }
    }
}

サーバプロセスは環境変数を設定して起こす。一覧はこちら: https://openobserve.ai/docs/environment-variables/たとえばこんな感じ。ZO_LOCAL_MODE_STORAGEはたぶん要らない。ZO_TELEMETRYとZO_PROMETHEUS_ENABLEDはお好みで。今回はminio/etcd/openobserveを全サーバにそれぞれ入れたので127.0.0.1になっているが、そこは環境に合わせて適宜調整。

ZO_LOCAL_MODE="false"
ZO_LOCAL_MODE_STORAGE="s3"
ZO_GRPC_ORG_HEADER_KEY="cluster"
ZO_TELEMETRY="false"
ZO_PROMETHEUS_ENABLED="true"
ZO_ETCD_ADDR="http://127.0.0.1:2379"
ZO_ROOT_USER_EMAIL="user@localhost.local"
ZO_ROOT_USER_PASSWORD="super-complex-password"
ZO_S3_ACCESS_KEY=""
ZO_S3_SECRET_KEY=""
ZO_S3_REGION_NAME="any"
ZO_S3_BUCKET_NAME="openobserve"
ZO_S3_SERVER_URL="http://127.0.0.1:9000"
ZO_S3_PROVIDER="minio"

これらをexportしたら引数なしでopenobserveを起動すればよい。

ところでOpenObserveのクラスタ構成はガイドがなくてやや不親切。ただ、k8s用helm chartがあるので、こいつを見ながら設定していた。

https://github.com/openobserve/openobserve-helm-chart/blob/main/values.yaml

OpenObserveではまったところ

ちなみにZO_GRPC_ORG_HEADER_KEYの値を設定し忘れて検索するとエラーになる。これが非常に分かりづらい。openobserveには以下のログが出ていた。

[2023-09-18T01:38:46Z INFO  openobserve::service::search] service:search:enter; org_id="default" stream_type=Logs                                                                                
[2023-09-18T01:38:46Z INFO  openobserve::service::search] service:search:cluster; org_id="default"                                                                                               
[2023-09-18T01:38:46Z INFO  openobserve::service::search::sql] service:search:sql:new; org_id="default"                                                                                          
[2023-09-18T01:38:46Z INFO  openobserve::service::search] get_file_list; stream_type=Logs time_level=Unset org_id="default" stream_name="default"                                                
[2023-09-18T01:38:46Z INFO  openobserve::service::search] search->file_list: time_range: Some((1694914712941000, 1695001112941000)), num: 114, offset: 39
[2023-09-18T01:38:46Z INFO  openobserve::service::search] service:search:cluster:grpc_search; org_id="default"
[2023-09-18T01:38:46Z INFO  openobserve::service::search] service:search:cluster:grpc_search; org_id="default"
[2023-09-18T01:38:46Z INFO  openobserve::service::search] service:search:cluster:grpc_search; org_id="default"
[2023-09-18T01:38:46Z INFO  openobserve::handler::grpc::request::search] grpc:search:enter; org_id="default"
[2023-09-18T01:38:46Z INFO  openobserve::service::search::grpc] service:search:grpc:search; org_id="default"
[2023-09-18T01:38:46Z INFO  openobserve::service::search::sql] service:search:sql:new; org_id="default"
[2023-09-18T01:38:46Z ERROR openobserve::service::search] search->grpc: node: 2, search err: Status { code: Unauthenticated, message: "No valid auth token", metadata: MetadataMap { headers: {"c
ontent-type": "application/grpc", "date": "Mon, 18 Sep 2023 01:38:46 GMT"} }, source: None }                             
[2023-09-18T01:38:46Z INFO  openobserve::service::search::grpc] service:search:grpc:in_wal; org_id="default" stream_name="default" stream_type=Logs
[2023-09-18T01:38:46Z INFO  openobserve::service::search::grpc] service:search:grpc:in_storage; org_id="default" stream_name="default" stream_type=Logs
[2023-09-18T01:38:46Z INFO  openobserve::service::search::grpc::storage] service:search:grpc:storage:enter; org_id="default" stream_name="default"
[2023-09-18T01:38:46Z INFO  openobserve::service::search::grpc::storage] search->storage: org default, stream default, load file_list num 39
[2023-09-18T01:38:46Z INFO  openobserve::service::search::grpc::storage] search->storage: org default, stream default, load files 39, scan_size 17390808, compressed_size 1598007
[2023-09-18T01:38:46Z INFO  tracing::span] service:search:grpc:storage:cache_parquet_files;
[2023-09-18T01:38:46Z INFO  openobserve::service::search::grpc::wal] service:search:wal:enter; org_id="default" stream_name="default"
[2023-09-18T01:38:46Z INFO  openobserve::service::search::grpc::wal] service:search:grpc:wal:get_file_list; org_id="default" stream_name="default"
[2023-09-18T01:38:46Z INFO  openobserve::service::search::grpc::wal] wal->search: load files 2, scan_size 34366
[2023-09-18T01:38:46Z INFO  openobserve::service::search::grpc::wal] service:search:grpc:wal:datafusion; org_id="default" stream_name="default" stream_type=Logs
[2023-09-18T01:38:46Z INFO  openobserve::service::search::grpc::storage] search->storage: org default, stream default, load files 39, into memory cache done
[2023-09-18T01:38:46Z INFO  openobserve::service::search::grpc::storage] service:search:grpc:storage:datafusion; org_id="default" stream_name="default" stream_type=Logs
[2023-09-18T01:38:46Z INFO  tracing::span] datafusion::storage::memory::list;
[2023-09-18T01:38:46Z INFO  openobserve::service::search::datafusion::exec] Query took 0.008 seconds.
[2023-09-18T01:38:46Z INFO  openobserve::service::search::datafusion::exec] Query all took 0.009 seconds.
[2023-09-18T01:38:46Z INFO  openobserve::service::search::datafusion::exec] Query took 0.011 seconds.
[2023-09-18T01:38:46Z INFO  openobserve::service::search::datafusion::exec] Query all took 0.011 seconds.
[2023-09-18T01:38:46Z INFO  openobserve::service::search] search->grpc: result node: 3, is_querier: true, total: 0, took: 31, files: 36, scan_size: 22
[2023-09-18T01:38:46Z INFO  openobserve::service::search] search->grpc: result node: 1, is_querier: true, total: 0, took: 41, files: 41, scan_size: 16
[2023-09-18T01:38:46Z ERROR openobserve::handler::http::request::search] search error: ErrorCode(ServerInternalError("search node error"))
[2023-09-18T01:38:46Z INFO  actix_web::middleware::logger] 10.33.3.28 "POST /api/default/_search?type=logs HTTP/1.0" 500 94 "141" "http://openobserve1.local/web/logstreams/stream-explore?s
tream_name=default&stream_type=logs&org_identifier=default" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0" 0.148536

ポイントは以下のログであろう。

[2023-09-18T01:38:46Z ERROR openobserve::service::search] search->grpc: node: 2, search err: Status { code: Unauthenticated, message: "No valid auth token", metadata: MetadataMap { headers: {"c
ontent-type": "application/grpc", "date": "Mon, 18 Sep 2023 01:38:46 GMT"} }, source: None }                             
[2023-09-18T01:38:46Z ERROR openobserve::handler::http::request::search] search error: ErrorCode(ServerInternalError("search node error"))

openobserver-2で検索を要求した場合、openobserve-1側に以下のようなログが残った。

[2023-09-18T01:39:50Z INFO  openobserve::handler::grpc::auth] Err authenticating Invalid value provided for the HTTP Authorization header

古い記事