2023年12月25日月曜日

emacs の lsp-mode で The following clients were selected based on priority エラー

emacs の lsp-mode で The following clients were selected based on priority エラー

概要

emacs + lsp-mode でうまく lsp サーバが起動できない場合の対処方法を紹介します

環境

  • macOS 11.7.10
  • emacs 29.1
  • lsp-mode 20231223.811

状況

emacs で ruby ファイルを起動した際に lsp サーバが見つからずに以下のようなエラーになる場合があります

Command "steep langserver --log-level warn" is not present on the path.                                                                      
Command "stree lsp" is not present on the path.
Command "ruby-lsp" is not present on the path.
Command "srb typecheck --lsp --disable-watchman" is not present on the path.
Command "bundle exec solargraph stdio" is present on the path.
Command "semgrep lsp" is not present on the path.
Command "rubocop --lsp" is not present on the path.
Command "steep langserver --log-level warn" is not present on the path.
Command "stree lsp" is not present on the path.
Command "ruby-lsp" is not present on the path.
Command "srb typecheck --lsp --disable-watchman" is not present on the path.
Command "bundle exec solargraph stdio" is present on the path.
Command "semgrep lsp" is not present on the path.
Command "rubocop --lsp" is not present on the path.
Found the following clients for /Users/hawk/data/repo/ruby-fndb/rank/app.rb: (server-id ruby-ls, priority -1)
The following clients were selected based on priority: (server-id ruby-ls, priority -1)

よくある対処方法としては emacs を起動しているシェルで PATH に solargraph や rubocop などがない場合に emacs から lsp が起動できないケースがありますが今回はしっかり PATH に通っている状態です

ログをよく見るとコマンドのチェックが二回行われています

原因

emacs に追加した lsp セッションパス内に複数の Gemfile があるとその配下全てでコマンドのチェックを行うため親ディレクトリなどに Gemfile があると 2 つの solargraph コマンドが見つかりどちらを起動したらいいのかわからないため該当のエラーになります

対応方法

一旦セッションファイルを作成して再度正しいプロジェクトのパスでセッションを登録しましょう

これで追加する際に親プロジェクトからではなく子プロジェクトを直接登録しましょう
以下の場合はドットを入力します

  • rm ~/.emacs.d/.lsp-session-v1
  • emacs app.rb
app.rb is not part of any project.                                                                                                           
                                                                                                                                             
i ==> Import project root ~/data/repo/ruby-fndb/                                                                                             
I ==> Import project by selecting root directory interactively                                                                               
. ==> Import project at current directory /Users/hawk/data/repo/ruby-fndb/rank/                                                      
d ==> Do not ask again for the current project by adding ~/data/repo/ruby-fndb/ to lsp-session-folders-blocklist                             
D ==> Do not ask again for the current project by selecting ignore path interactively                                                        
n ==> Do nothing: ask again when opening other files from the current project                                                                
                                                                                                                                             
Select action: .

これで再度 lsp が起動されエラーにならないことを確認します

最後に

emacs + lsp で起動しない場合は大抵の場合コマンドがインストールされていなかったり参照できなかったりが多いです
今回は参照はできるが複数の lsp サーバコマンドがある場合のエラーになります

ちなみに lsp-mode の ruby モードだと最優先で実行されるのは現状だと solargraph のようです

2023年12月22日金曜日

Python から jq を使う方法

Python から jq を使う方法

概要

コマンドラインからではなく Python プログラム内で jq を使う方法を紹介します

環境

  • macOS 14.2
  • Python 3.11.6
  • jq.py 1.6.0

サンプルコード

import jq

# キーを指定して値を参照
a = {"name": "hawk", "age": 10}
print(jq.compile(".name").input_value(a).first())

# 配列をループして順番に参照
a = {"langs": ["ruby", "python", "javascript", "swift"]}
for lang in jq.compile(".langs[]").input_value(a):
    print(lang)

# 配列の長さを取得
a = {"langs": ["ruby", "python", "javascript", "swift", "elisp"]}
print(len(jq.compile(".langs[]").input_value(a).all()))

# all や first は参照した値の型を適切に返すが text は文字列として返す
print(type(jq.compile(".langs[]").input_value(a).all()))
print(type(jq.compile(".langs").input_value(a).text()))

上記をコマンドでやった場合

echo '{"name": "hawk", "age": 10}' | jq .name

echo '{"langs": ["ruby", "python", "javascript", "swift"]}' | jq '.langs[]'

echo '{"langs": ["ruby", "python", "javascript", "swift", "elisp"]}' | jq '.langs | length'

最後に

JSON のパースだけであれば Python デフォルトの json モジュールを使ってもできますが dict を直接扱うのが嫌な場合や複雑な場合には jq.py を使うことできれいに書けることがあるかもしれません

参考サイト

2023年12月21日木曜日

Ubuntu に最新の git をインストールする方法

Ubuntu に最新の git をインストールする方法

概要

いつも忘れるのでメモ

環境

  • Ubuntu 22.04
  • git 2.43.0

インストール方法

  • sudo add-apt-repository ppa:git-core/ppa
  • sudo apt update
  • sudo apt install git

動作確認

  • git --version
git version 2.43.0

更にバージョンアップする場合は

  • sudo apt update
  • sudo apt upgrade

メモ

すでに git がインストールされている場合でも apt remove git はしないで OK です

参考サイト

2023年12月18日月曜日

Prometheus の query_range で最大プロット可能なステップ数を計算する方法

Prometheus の query_range で最大プロット可能なステップ数を計算する方法

概要

前回 query_range をコールする方法を紹介しました
その際にプロット数の最大値が 11,000 個ということを紹介しました
指定した範囲に応じて最大ステップ数が変わるので今回はそれを考慮したコードを紹介します

環境

  • Python 3.11.3
  • requests 2.31.0

サンプルコード

ポイントは step = int(range / 11000) + 1 の部分です
レンジ内における最大プロット数に収まる step 数を計算します

from datetime import datetime, timedelta, timezone

import requests

proxies = {"http": "192.168.100.2:3128"}
end = datetime.now(timezone.utc)
start = end - timedelta(hours=60)
end_s = end.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
start_s = start.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
# 60時間の範囲で指定可能な最大ステップ数を計算する
# query_range のプロット数が11,000を超えないようにする
range = (end - start).total_seconds()
# 1秒分加算することで指定可能なstep数の最大値になる
step = int(range / 11000) + 1
params = {
    "query": 'instance:node_filesystem_avail:ratio{mountpoint="/var/opt/gitlab"}',
    "start": start_s,
    "end": end_s,
    "step": f"{step}s",
}
schema = "http"
hostname = "192.168.100.1"
port = "9090"
path = "/api/v1/query_range"
url = f"{schema}://{hostname}:{port}{path}"

res = requests.get(url, params=params, proxies=proxies)

# ステップ数とレンジによりプロット数が11,000を超えていないことを確認
# print(res.json())
print(f"step -> {step}s")
print(f"range -> {range}")
print(len(res.json()["data"]["result"][0]["values"]))

注意事項

上記場合一つ考慮が足りない点があります
例えば上記の場合 60 時間分のレンジ内で 11,000 プロット取れるステップ数 (20s) を計算していますがもし 60 時間分のデータがまだない場合にはもっと細かいステップ数を指定することができます

例えば 30 時間分のデータしかない場合には単純に倍のステップ数を指定することができます (10s)
これを考慮する場合には指定のレンジ内で取得したデータの実際のレンジ情報を取得しその実際のレンジ情報からステップ数を計算する必要があるので 2 回 API をコールする必要が出てきます
正確に出したい場合にはその方法が必要になるかなと思います

また scrape_interval が 30s などになっている場合はそもそもデータが 30 秒おきに格納されていることになるので step=30s 以下を指定しても結果的には step=30s と同じデータになるはずです

最後に

Promethues の UI でネットワークを見ると実際に query_range をコールしているクエリが見えるのでそれを参考にしてもいいかもしれません

2023年12月14日木曜日

Promethues の API を requests でコールする

Promethues の API を requests でコールする

概要

過去にライブラリを使った方法を紹介しました
今回は requests だけでコールしてみました

環境

  • Python 3.11.3
  • requests 2.31.0

サンプルコード

API は query_range を使います

from datetime import datetime, timedelta, timezone

import requests

proxies = {"http": "192.168.100.2:3128"}
# 30分前から現在時刻まで取得
now = datetime.now(timezone.utc)
end = now.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
start = (now - timedelta(minutes=30)).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
params = {
    "query": 'instance:node_filesystem_avail:ratio{mountpoint="/var/opt/gitlab"}',
    "start": start,
    "end": end,
    "step": "60s",
}
schema = "http"
hostname = "192.168.100.1"
port = "9090"
path = "/api/v1/query_range"
url = f"{schema}://{hostname}:{port}{path}"

res = requests.get(url, params=params, proxies=proxies)
print(res.json())

注意事項

step=60s にしていますが query_range API の場合、取得できる最大プロットの数が 11,000 になるのでそれを超えなければ更に短い時間でも OK です
query API であれば制限はないようです
一週間取得する場合には 60s からでないと取得できません
また Prometheus はデフォルトだと 15 日間しかデータを保存していません

最後に

メトリックを取得するだけならかなり簡単にコールできるので専用のライブラリはいらないかもです

参考サイト

2023年12月13日水曜日

Alertmanager でノードが再起動したときにアラートを上げる方法とその効果

Alertmanager でノードが再起動したときにアラートを上げる方法とその効果

概要

ノードが再起動した際にアラートを上げるルールを紹介します
またその効果を紹介します

環境

  • Gitlab 16.3.6
  • Alertmanager 2.30.4

ルール

groups:
- name: Custom
  rules:
  - alert: NodeHasRebooted
    annotations:
      description: Node has rebooted
      summary: Node {{ (or $labels.node $labels.instance) }} has rebooted {{ $value }} seconds ago.
    expr: (time() - node_boot_time_seconds < 600) and (time() - 600 - (node_boot_time_seconds offset 10m) > 600)
    labels:
      severity: critical

効果

  • ノードが再起動した際に必ずアラートします
  • それにより再起動時に RESOLVED メールが届かないという問題が解消します
  • 再起動アラートは10分間なので10分間ノードが起動すると解消します
  • もし他のアラートが上がっている状態でノードが再起動しそのアラートがノード再起動時に解消していると RESOLVED メールが届かないことがあります
  • それをノード再起動アラートを必ず起こすことで解消メールが送信させることができます

最後に

600sec 以下だと time との差が短すぎてアラートが上がらないことがあるので少し長めに設定しましょう

参考サイト

2023年12月12日火曜日

omnibus-gitlab の AlertManager でログレベルをデバッグにする方法

omnibus-gitlab の AlertManager でログレベルをデバッグにする方法

概要

機能として提供されていないので直接ファイルを編集する必要があります

環境

  • Gitlab 16.3.6

runit ファイルの編集

--log.level=debug を追記します

  • docker cp root_gitlab_1:/opt/gitlab/service/alertmanager/run .
  • vim run
#!/bin/sh
exec 2>&1

umask 077
exec chpst -P -e /opt/gitlab/etc/alertmanager/env \
  -U gitlab-prometheus:gitlab-prometheus \
  -u gitlab-prometheus:gitlab-prometheus \
  /opt/gitlab/embedded/bin/alertmanager --web.listen-address=0.0.0.0:9093 --storage.path=/var/opt/gitlab/alertmanager/data --config.file=/var/opt/gitlab/alertmanager/alertmanager.yml --log.level=debug

あとは再度コンテナに配置し再起動します

  • docker cp run root_gitlab_1:/opt/gitlab/service/alertmanager/run
  • docker-compose exec gitlab gitlab-ctl restart alertmanager

再起動後ログが debug になっていることを確認します

  • tail -f /path/to/gitlab_mnt/log/alertmanager/current

prometheus の場合は

root_gitlab_1:/opt/gitlab/service/prometheus/run ファイルを同様に編集して配置してあげましょう

コンテナが再起動すると元に戻ってしまう

omnibus-gitlab はコンテナが再起動すると reconfigure が走ってしまいます
reconfigure が走ると内部で chef が実行されるため手動で変更した runit のファイルが上書きされログレベルの設定がなくなります

なのでそれの対処として直接 cookbooks を編集する方法も紹介します

sv-alertmanager-run.erb の編集

  • docker cp root_gitlab_1:/opt/gitlab/embedded/cookbooks/monitoring/templates/sv-alertmanager-run.erb .
  • vim sv-alertmanager-run.erb
#!/bin/sh
exec 2>&1
<%= render("mount_point_check.erb") %>
umask 077
exec chpst -P -e <%= @options[:env_dir] %> \
  -U <%= node['monitoring']['prometheus']['username'] %>:<%= node['monitoring']['prometheus']['group'] %> \
  -u <%= node['monitoring']['prometheus']['username'] %>:<%= node['monitoring']['prometheus']['group'] %> \
  /opt/gitlab/embedded/bin/alertmanager <%= @options[:flags] %> --log.level=debug
  • docker cp sv-alertmanager-run.erb root_gitlab_1:/opt/gitlab/embedded/cookbooks/monitoring/templates/sv-alertmanager-run.erb

これでコンテナが再作成 (down -> up) されない限り runit の実行ファイルに debug オプションが付与され続けます

Prometheus の場合は /opt/gitlab/embedded/cookbooks/monitoring/templates/sv-prometheus-run.erb を編集してください

最後に

omnibus-gitlab に MR して docker-compose から変更できるようにしてもいいのかもしれません

参考サイト

2023年12月8日金曜日

SSH のローカルポートフォーワードで channel 2: open failed: connect failed: Temporary failure in name resolution エラー

SSH のローカルポートフォーワードで channel 2: open failed: connect failed: Temporary failure in name resolution エラー

概要

対処方法を紹介します

環境

  • Windows10

対処方法 .ssh/config の Host と Hostname を IP 指定にする

接続できる例

Host 192.168.100.1
     Hostname 192.168.0.1
     Port 22
     User root
     IdentityFile /path/to/secret.pem
     ProxyCommand ssh proxy /bin/nc %h %p
  • ssh -N -L 5000:192.168.100.1:9090 192.168.100.1

接続できない例

Host ins01
     Hostname 192.168.0.1
     Port 22
     User root
     IdentityFile /path/to/secret.pem
     ProxyCommand ssh proxy /bin/nc %h %p
  • ssh -N -L 5000:ins01:9090 ins01

最後に

普通に ssh する場合は Host と Hostname が違っていても問題ないのですがポートフォーワードする場合は IP で指定したほうが無難のようです

2023年12月7日木曜日

Gitlab の Promethues を使って死活監視をするときに使えそうなメトリックス

Gitlab の Promethues を使って死活監視をするときに使えそうなメトリックス

概要

Gitlab 内蔵の Promethues を使って Gitlab 自体の死活監視をする方法を紹介します
使えそうなメトリックスとダメそうなメトリックスがあったのでそれも紹介します

環境

  • Gitlab 16.3.6
  • Prometheus 2.46.0

使えそうなメトリックス

  • gitlab_sli:gitlab_component_errors:rate{job="gitlab-workhorse"} > 0

が一番いいかなと思います
gitlab に定期的にアクセスして 500 が帰ってきたときの割合を返してくれるメトリックスです

これは gitlab が提供してくれる recording alert で実際の中身は sum(rate(http_request_duration_seconds_count{job="gitlab-rails",status=~"5.."}[1m])) になっています

使えなさそうなメトリック

  • avg_over_time(up[5m]) * 100 < 50

これも gitlab が提供してくれるアラートルールなのですが up を使っています
up だと各プロセスではなく Promethues が使用している targets つまり exporter のプロセス監視をしているだけなので実際の redis や postgres, rails (puma) がダウンしても exporter に影響がないためアラートがあがりません

なのでこの ServiceDown というアラートを死活監視として使うことはおすすめしません

実際にプロセス監視ができそうなのが redis_up というクエリだけで他のプロセスに関しては死活監視できそうなクエリはデフォルトの gitlab のメトリックはありませんでした

最後に

自分で blackbox_exporter を追加しても OK です
が面倒なのでデフォルトの gitlab + Promethues で死活監視できる方法を紹介しました

あとは自作の exporter を作ってもいいですがそれも面倒かなと思います

参考サイト

2023年12月6日水曜日

bundler の with と without オプションは非推奨になっていた

bundler の with と without オプションは非推奨になっていた

概要

推奨方法を紹介します
結論としては環境変数を使いましょう

環境

  • macOS 11.7.10
  • Ruby 3.2.2
  • bundler 2.3.7

今まで

  • bundle install --with development
  • bundle install --without development

これから

  • bundle config set with '' && bundle config set without 'development' && bundle install
  • bundle config set without '' && bundle config set with 'development' && bundle install

環境変数を使うことはできなさそう

BUNDLE_WITHOUT or BUNDLER_WITHOUT を設定すればいけるという記事を何個か見たのですがうまく動作しませんでした

もし BUNDLE_WITHOUT を使いたい場合は結局 bundle config set しないといけないので上記と同じ手順を踏むことになりそうです

最後に

なんか劣化したような気がします

参考サイト

2023年12月5日火曜日

bundler-audit で Gemfile 内で使っている gem の脆弱性チェックをする

bundler-audit で Gemfile 内で使っている gem の脆弱性チェックをする

概要

過去にtrivyを使って脆弱性スキャンを紹介しました
今回は gem に特化したツールを紹介します

環境

  • macOS 11.7.10
  • Ruby 3.2.2
    • bundler-audit 0.9.1

インストール

  • vim Gemfile
gem 'bundler-audit'
  • bundle config path vendor
  • bundle install

使ってみる

まずは最新の脆弱性情報を取得します

  • bundle exec bundler-audit update
Download ruby-advisory-db ...
Cloning into '/Users/user01/.local/share/ruby-advisory-db'...
remote: Enumerating objects: 11402, done.
remote: Counting objects: 100% (1519/1519), done.
remote: Compressing objects: 100% (349/349), done.
remote: Total 11402 (delta 1237), reused 1228 (delta 1160), pack-reused 9883
Receiving objects: 100% (11402/11402), 1.82 MiB | 8.14 MiB/s, done.
Resolving deltas: 100% (6446/6446), done.
ruby-advisory-db:
  advisories:   827 advisories
  last updated: 2023-11-30 12:36:04 -0800
  commit:       d821bf162550302abd1fa1fe15007f3012b76f32

ローカルにキャッシュされます

あとは実行すれば OK です

  • bundle exec bundler-audit
No vulnerabilities found

と表示された場合は特に脆弱な gem をプロジェクト内で使用していないことになります

更新とスキャンを同時に行う

  • bundle exec bundler-audit check --update

こっちを使ったほうがいいかもです

最後に

あとはこれを CI に追加すれば gem のセキュリティチェックができるようになります
当然ですが CVE ベースなのでゼロデイ攻撃などには対応していません

trivy でもできそうですがすでに bundler-audit を使っている場合はこれで十分かもしれないです

参考サイト

2023年12月4日月曜日

global な環境にインストールした gem をすべて削除する方法

global な環境にインストールした gem をすべて削除する方法

概要

Homebrew などでインストールした gem コマンドでインストールするとすべての環境に共通の場所に gem がインストールされます
まちがってグローバルな環境にいろいろな gem をインストールしてしまった場合に一旦すべてを削除する方法を紹介します

環境

  • macOS 14.1.2
  • Ruby 3.2.2

コマンド

  • gem uninstall -I -a -x --user-install --force

rbenv など使っている場合は必ず system になるパスで上記を実行しましょう

トラブルシューティング

アンインストール中に以下のようなエラーになり削除できないケースがあります

ERROR:  While executing gem ... (Gem::InstallError)
    test-unit is not installed in GEM_HOME, try:
        gem uninstall -i /opt/homebrew/Cellar/ruby/3.2.2_1/lib/ruby/gems/3.2.0 test-unit

そんな場合は素直に記載のコマンドを実行してアンインストールしましょう

  • gem uninstall -i /opt/homebrew/Cellar/ruby/3.2.2_1/lib/ruby/gems/3.2.0 test-unit

再度必要なものだけインストールする

  • gem install bundler
  • gem install solargraph
  • gem install rubocop

最後に

アンインストール後に gem list でインストール済みの gem の一覧を見ると結構あるので実は削除されていないものがあるような気もします

参考サイト

2023年12月1日金曜日

Promethues + Alertmanager + node_exporter の最小構成 docker compose

Promethues + Alertmanager + node_exporter の最小構成 docker compose

概要

メモがてら残しておきます

環境

  • Ubuntu 22.04
  • docker 24.0.4

compose.yml

  • vim compose.yml
services:
  prometheus:
    image: prom/prometheus
    container_name: prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
    ports:
      - 9090:9090
    restart: unless-stopped
    volumes:
      - ./prometheus:/etc/prometheus
      - prom_data:/prometheus
  node_exporter:
    image: quay.io/prometheus/node-exporter:latest
    container_name: node_exporter
    command:
      - '--path.rootfs=/host'
    network_mode: host
    pid: host
    restart: unless-stopped
    volumes:
      - '/:/host:ro,rslave'
  alertmanager:
    image: prom/alertmanager
    container_name: alertmanager
    volumes:
      - ./alertmanager:/etc/alertmanager
    command: "--config.file=/etc/alertmanager/alertmanager.yml"
    ports:
      - 9093:9093
    restart: unless-stopped
volumes:
  prom_data:

Promethues

  • vim promethues/prometheus.yml
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      - alertmanager:9093

rule_files:
- '/etc/prometheus/*.rules'

scrape_configs:
- job_name: 'node_exporter'
  static_configs:
  - targets:
    - '192.168.100.1:9100'
  • vim prometheus/node.rules
groups:
- name: Node
  rules:
  - record: instance:node_cpus:count
    expr: count without(cpu, mode) (node_cpu{mode="idle"})

Alertmanager

  • vim alertmanager/alertmanager.yml
oute:
  receiver: default-receiver
  routes:
  - receiver: email
    matchers:
    - alertname=""
    group_wait: 30s
    group_interval: 5m
    repeat_interval: 4h
receivers:
- name: default-receiver
- name: email
  email_configs:
  - send_resolved: true
    to: your_mail_address@here
    from: your_from_address@here
    hello: localhost
    smarthost: smtp.ess.nifcloud.com:465
    auth_username: xxx
    auth_password: xxx
    headers:
      Subject: '{{ template "my.email.text.subject" . }}'
    text: '{{ template "my.email.text.body" . }}'
    require_tls: false
templates:
- /etc/alertmanager/*.tmpl
  • vim alertmanager/my_email_text.tmpl
{{ define "my.email.text.body" }}
{{ range .Alerts.Firing }}
{{ range .Annotations.SortedPairs }}
- {{ .Name }} = {{ .Value }}
{{ end }}
{{ end }}
{{ end }}

{{ define "my.email.text.subject" }}
{{ $alerts := "" }}
{{ $instance_id := (index .Alerts 0).Labels.instance_id }}
{{ range .Alerts }}
{{ $alerts = (printf "%v %v" $alerts .Labels.alertname) }}
{{ end }}
[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] ({{ $instance_id }} ->{{ $alerts }})
{{ end }}

動作確認

  • docker compose up -d
  • docker compose ps
NAME                IMAGE                                     COMMAND                  SERVICE             CREATED             STATUS                    PORTS
alertmanager        prom/alertmanager                         "/bin/alertmanager -…"   alertmanager        13 minutes ago      Up 13 minutes             0.0.0.0:9093->9093/tcp, :::9093->9093/tcp
node_exporter       quay.io/prometheus/node-exporter:latest   "/bin/node_exporter …"   node_exporter       13 minutes ago      Up 13 minutes
prometheus          prom/prometheus                           "/bin/prometheus --c…"   prometheus          13 minutes ago      Up 13 minutes             0.0.0.0:9090->9090/tcp, :::9090->9090/tcp

最後に

あとは必要に応じて node.rules ファイルや prometheus.yml でメトリックスの取得先を変更すれば OK です

my_email_text.tmpl を変更すればテキストメールのテンプレートも変更できます