※当サイトの記事には、広告・プロモーションが含まれます。

Oxidizedのエラーのログは散逸するし、ログローテーションどうすれば良いのか

gigazine.net

2024年10月20日にリリースされたLinux 6.12-rc4カーネルで、Linuxカーネルメンテナーからロシア人あるいはロシアと関連するアカウントを削除する提案がマージされ、Linuxコミュニティで激しい議論が巻き起こったため、Linuxカーネル優しい終身の独裁者を務めるリーナス・トーバルズ氏がこの件について説明しました。

「ロシア人がLinuxのカーネルメンテナーを解任されている件」についてリーナス・トーバルズが説明 - GIGAZINE

事の発端は、2024年10月18日にグレッグ・クロー=ハートマン氏が「様々なコンプライアンス要件」を理由として、複数のLinuxカーネルメンテナーをMAINTAINERSファイルから削除する変更を提案したことでした。削除されたメンテナーの多くはロシアのメールアドレスを使用していたか、ロシアとの関連が指摘される人々でした

「ロシア人がLinuxのカーネルメンテナーを解任されている件」についてリーナス・トーバルズが説明 - GIGAZINE

この突然の変更に対して、コミュニティ内で大きな議論が巻き起こりました。影響を受けたメンテナーたちは驚きを表明し、一部のメンバーは変更の取り消しを求めるパッチを提案しました。特に問題視されたのは、「コンプライアンス要件」の具体的な内容が公開されていない点でした。

「ロシア人がLinuxのカーネルメンテナーを解任されている件」についてリーナス・トーバルズが説明 - GIGAZINE

この状況に対して、2024年10月23日にトーバルズ氏は「ロシアの荒らしがたくさん現れた。変更が行われた理由は明確で、取り消されることはない」と宣言。

「ロシア人がLinuxのカーネルメンテナーを解任されている件」についてリーナス・トーバルズが説明 - GIGAZINE

Linuxのコアな部分を扱うからして、信用が大事ということかね。

現実のニュースというのは、「2022年ロシアのウクライナ侵攻」で始まり現在も進行中の戦争を指していると思われますが、

冬戦争(ふゆせんそう、フィンランド語talvisota)は、第二次世界大戦の勃発から3か月目にあたる1939年11月30日に、ソビエト連邦フィンランド侵攻した戦争である。フィンランドはこの侵略に抵抗し、多くの犠牲を出しながらも、独立を守ったが、モスクワ講和条約により領土の一部が割譲された。

冬戦争 - Wikipedia

⇧「冬戦争」なんてものもあったんですね。

戦争は、あらゆるものを台無しにしてくれますな。

Oxidizedのエラーのログが散逸する話

「Oxidized」のエラーハンドリングが謎過ぎるのだが、Docker Composeで稼働させた感じでは、

No 項目 内容 出力先例
1 log configファイルで設定 /opt/app/oxidized/log/*.log
2 crashes configファイルで設定 /opt/app/oxidized/crashes/*
3 crash 不明 /opt/app/oxidized/crash

⇧ の3カ所に出力されたのだけど、エラーの内容によって出力先が変わるようなのだが、どういったエラーの場合にどの出力先に書き出されるのか、ルールが全く分からない...

公式のドキュメントによると、

github.com

⇧ configファイルのサンプルがあるのだが、

https://github.com/ytti/oxidized/blob/master/docs/Configuration.md#advanced-configuration

---
username: oxidized
password: S3cr3tx
model: junos
interval: 3600 #interval in seconds
log: ~/.config/oxidized/log
debug: false
threads: 30 # maximum number of threads
# use_max_threads:
# false - the number of threads is selected automatically based on the interval option, but not more than the maximum
# true - always use the maximum number of threads
use_max_threads: false
timeout: 20
retries: 3
prompt: !ruby/regexp /^([\w.@-]+[#>]\s?)$/
crash:
  directory: ~/.config/oxidized/crashes
  hostnames: false
vars:
  enable: S3cr3tx
groups: {}
rest: 127.0.0.1:8888
pid: ~/.config/oxidized/oxidized.pid
input:
  default: ssh, telnet
  debug: false
  ssh:
    secure: false
output:
  default: git
  git:
      user: Oxidized
      email: oxidized@example.com
      repo: "~/.config/oxidized/oxidized.git"
source:
  default: csv
  csv:
    file: ~/.config/oxidized/router.db
    delimiter: !ruby/regexp /:/
    map:
      name: 0
      model: 1
      username: 2
      password: 3
    vars_map:
      enable: 4
model_map:
  cisco: ios
  juniper: junos   

⇧ と言った感じで、「log」と「crashes」については、出力先を設定できるのだが、「crash」については出力先が設定できる様子がない。

3か所を監視対象とするしか無い感じとは言え、どんなエラーの時に、

  1. log
  2. crashes
  3. crash

⇧ のどこに、どのような内容でエラーが出力されるのかの全貌が把握できず、誠に遺憾でありますと。

正常性を監視したくても、エラー出力のルールが定まっていないというのが、かなり辛い気がする...

あまりにも、ブラックボックス過ぎたので、

github.com

⇧「issue」で質問してみたけど、「けんもほろろな」回答で、且つ、勝手にクローズされるという...

しかも、「oxidized-web」じゃなくて、「oxidized」についてのエラー処理について聞きたかったのであって、質問と回答が噛み合っていないんよね...

エラーハンドリングとか、エラー周りの設計がどうなっているのか知りたかっただけなんだけどなぁ...

Oxidizedのログローテーションどうすれば良いのか

そして、もう一つの問題が「ログローテーション」。

公式のドキュメントによると、

github.com

https://github.com/ytti/oxidized/blob/master/extra/oxidized.logrotate

/var/log/oxidized/*.log {
    weekly
    rotate 3
    size 10M
    compress
    delaycompress
    missingok
}    

⇧ 説明が無さ過ぎて困惑するのだけど、

github.com

⇧ 上記のissueによれば、「logrotate」の設定ファイル配置ディレクトリである「/etc/logrotate.d/」配下に「Oxidized」用の「ログローテーション」の設定ファイルを追加すれば良いとあるのですが、「ログローテーション」で新しいログファイルに切り替わらないって仰っておられるのですが、解決策を提示せずにissueがクローズされているという...

というか、機能しないのであるならば、注釈を入れたりしておくべきではとおもってしまうのですが...

「Oxidized」のプロセスが、古いログファイルの「inode」を掴みっぱなしになってしまうらしく、「Oxidized」のプロセスを再起動する必要があるとのこと。

で、一般的なサービス起動で動作しているようなプロセスにおける「ログローテーション」の場合、

masudak.hatenablog.jp

まぁ、HUPをそのまま送ってもいいんだけど、再現したかったので、ローテート実行させた。要は、rotateが走ると、ローテート先ファイルのinodeが変わるんだと。

logrotateがプロセスにHUP送る理由を調べてみた - カイワレの大冒険 Third

qiita.com

ローテートしても古いファイルに書き込み続ける問題への対応

この問題を解決するには、postrotateを利用してプロセスを再起動するなどの対応が必要だ。

logrotateでログが欠損するケースとその対策 #logrotate - Qiita

再起動すれば、通常はファイルが無いことを検知して、新しいログファイルを生成する。
例として、以下のようなかたちでhttpdをgracefulにrestartする。

logrotateでログが欠損するケースとその対策 #logrotate - Qiita

⇧ 上記サイト様によりますと、サービスとして起動しているプロセスを再起動する感じになるようですと。

なんだけど、

lebanon.exblog.jp

「restart」では httpd プロセスを完全に停止してから再起動します。
対して「graceful」では httpd プロセスの通信が終わるのを待って、順次新しい設定を反映した httpd を起動させる方法です。サービスの停止が出来ない場合(既にウェブサーバが運用中など)には「graceful」の使用をお勧めします。

logrotateでログが欠損するケースとその対策 #logrotate - Qiita

⇧ とあるように、サービスとして起動してるようなプロセスにおいての再起動の方法には種類がありますと。

ただ、「Oxidized」をDockerコンテナとして稼働させている場合は、

github.com

https://github.com/ytti/oxidized/blob/master/extra/oxidized.runit

#!/bin/bash
[ ! -d /home/oxidized/.config/oxidized ] && mkdir -p /home/oxidized/.config/oxidized
[ -f /home/oxidized/.config/oxidized/pid ] && rm /home/oxidized/.config/oxidized/pid
chown -R oxidized:oxidized /home/oxidized/.config/oxidized
exec setuser oxidized oxidized    

github.com

https://github.com/ytti/oxidized/blob/master/extra/auto-reload-config.runit

#!/bin/bash

if [ -z "$CONFIG_RELOAD_INTERVAL" ]; then
    # Just stop and do nothing
    sleep infinity
fi

while true; do
    sleep $CONFIG_RELOAD_INTERVAL
    echo "Reloading config..."
    curl -s http://localhost:8888/reload?format=json -O /dev/null
done    

github.com

https://github.com/ytti/oxidized/blob/master/extra/oxidized.service

# Put this file in /etc/systemd/system.
#
# To set OXIDIZED_HOME instead of the default,
# ~oxidized/.config/oxidized, uncomment (and modify as required) the
# "Environment" variable below so systemd sets the correct
# environment.

[Unit]
Description=Oxidized - Network Device Configuration Backup Tool
After=network-online.target multi-user.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/oxidized
User=oxidized
KillSignal=SIGKILL
#Environment="OXIDIZED_HOME=/etc/oxidized"
Restart=on-failure
RestartSec=300s

[Install]
WantedBy=multi-user.target    

github.com

https://github.com/ytti/oxidized/blob/master/Dockerfile

# Stage 1: Build x25519 and any necessary dependencies
FROM docker.io/phusion/baseimage:noble-1.0.0 AS x25519-builder

# install necessary packages for building gems
RUN apt-get update && apt-get install -y \
    build-essential \
    git \
    ruby-dev \
    && rm -rf /var/lib/apt/lists/*

# create bundle directory
RUN mkdir -p /usr/local/bundle
ENV GEM_HOME=/usr/local/bundle

# Install the x25519 gem
RUN gem install x25519 --no-document

# Stage2: build an oxidized container from phusion/baseimage-docker and install x25519 from stage1
FROM docker.io/phusion/baseimage:noble-1.0.0

ENV DEBIAN_FRONTEND=noninteractive

##### Place "static" commands at the beginning to optimize image size and build speed
# add non-privileged user
ARG UID=30000
ARG GID=$UID
RUN groupadd -g "${GID}" -r oxidized && useradd -u "${UID}" -r -m -d /home/oxidized -g oxidized oxidized

# link config for msmtp for easier use.
RUN ln -s /home/oxidized/.config/oxidized/.msmtprc /home/oxidized/

# create parent directory & touch required file
RUN mkdir -p /home/oxidized/.config/oxidized/
RUN touch /home/oxidized/.config/oxidized/.msmtprc

# setup the access to the file
RUN chmod 600 /home/oxidized/.msmtprc
RUN chown oxidized:oxidized /home/oxidized/.msmtprc

# add runit services
COPY extra/oxidized.runit /etc/service/oxidized/run
COPY extra/auto-reload-config.runit /etc/service/auto-reload-config/run
COPY extra/update-ca-certificates.runit /etc/service/update-ca-certificates/run

# set up dependencies for the build process
RUN apt-get -yq update \
    && apt-get -yq upgrade \
    && apt-get -yq --no-install-recommends install ruby \
    # Build process of oxidized from git (beloww)
    git \
    # Allow git send-email from docker image
    git-email libmailtools-perl \
    # Allow sending emails in the docker container
    msmtp \
    # Debuging tools inside the container
    inetutils-telnet \
    # Use ubuntu gems where possible
    # Gems needed by oxidized
    ruby-rugged ruby-slop ruby-psych \
    ruby-net-telnet ruby-net-ssh ruby-net-ftp ruby-net-scp ruby-ed25519 \
    # Gem dependencies for inputs
    ruby-net-http-persistent ruby-mechanize \
    # Gem dependencies for sources
    ruby-sqlite3 ruby-mysql2 ruby-pg ruby-sequel ruby-gpgme\
    # Gem dependencies for hooks
    ruby-aws-sdk ruby-xmpp4r \
    # Gems needed by oxidized-web
    ruby-charlock-holmes ruby-haml ruby-htmlentities ruby-json \
    puma ruby-sinatra ruby-sinatra-contrib \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# copy the compiled gem from the builder stage
COPY --from=x25519-builder /usr/local/bundle /usr/local/bundle

# Set environment variables for bundler
ENV GEM_HOME="/usr/local/bundle"
ENV PATH="$GEM_HOME/bin:$PATH"

# gems not available in ubuntu noble
RUN gem install --no-document \
    # dependencies for hooks
    slack-ruby-client cisco_spark \
    # dependencies for specific inputs
    net-tftp

# build and install oxidized
COPY . /tmp/oxidized/
WORKDIR /tmp/oxidized

# docker automated build gets shallow copy, but non-shallow copy cannot be unshallowed
RUN git fetch --unshallow || true

# Ensure rugged is built with ssh support
RUN CMAKE_FLAGS='-DUSE_SSH=ON' rake install

# web interface
RUN gem install oxidized-web --no-document

# clean up
WORKDIR /
RUN rm -rf /tmp/oxidized

EXPOSE 8888/tcp    

⇧「Runit」というものでプロセスを起動させているようなのですが、どう再起動させるべきなのかが分からない...

そもそも「Runit」がよく分からんですと...

ネットの情報によると、

wiki.archlinux.jp

Runit はプロセススーパーバイザーです。pid1 として sysv の init を置き換えることができる runit-init が含まれており、inittab から起動することができます。他の init システムを使うこともできます。

https://wiki.archlinux.jp/index.php/Runit

⇧ とあるのだけど、今いち、通常の「サービス」と「プロセススーパーバイザー」の違いというのが分からない...

ChatGPTに質問してみたところ、

No 特徴 サービス プロセススーパーバイザー
1 定義 特定の機能やアプリケーションを提供するプロセス 他のプロセスを管理・監視するプログラム
2 目的 特定のタスクを実行する プロセスの起動、停止、再起動を管理
3 実行形態 常駐することが多い 他のプロセスを管理する
4 再起動機能 通常は再起動機能を持たない 異常終了時の自動再起動が可能
5 監視機能 基本的には監視機能を持たない プロセスの状態やリソース使用を監視
6 httpdApache)、mysqldMySQL systemdrunitsupervisord
7 リソース使用 CPUやメモリを使用 管理するプロセスのリソース使用を監視

⇧ というような回答が返ってきた。

回答に至った一次情報が分からないので、平気で嘘を付いてるとは思うんだが、ネット上でも、整理されてる情報が無いから致し方ないですな...

そもそも、「サービス」として動かせば良いのでは?と思ってしまい、「プロセススーパーバイザー」の必要性が分からない。

ネットの情報を漁っていたら、

goodbyegangster.hatenablog.com

Dockerコンテナではプロセスを1つだけ起動させる、とは有名なContainerベストプラクティスですが、これはどうしてでしょう。いろいろ理由があると思いますが、明確な技術的理由として、DockerではCMDオプションやENTRYPOINTオプションで起動したプロセスがコンテナ上のPID1になるからだ、と思っています。

コンテナで複数プロセスを起動させる - goodbyegangsterのブログ

つまりinitプログラムがおらず、Supervisorの機能もないので、複数プロセスを起動させる場合にはUNIXの世界でのルール違反になってしまう、と。

コンテナで複数プロセスを起動させる - goodbyegangsterのブログ

じゃあ複数プロセスを起動させたい場合にはどうすればいいのさ、という話ですが、コンテナ上でinit的なものをPID1で動かしてあげれば良い訳です。それにあたるものとして runit というものがあります。

コンテナで複数プロセスを起動させる - goodbyegangsterのブログ

で、この runit を同梱してくれたコンテナイメージがあり、それが phusion/baseimage です。

コンテナで複数プロセスを起動させる - goodbyegangsterのブログ

⇧ 上記サイト様が説明してくれておりました。

なるほど、「Oxidized」の「Dockerfile」で確かに、「phusion/baseimage」を利用してましたな。

ただ、「Oxidized」で「Runit」を利用している理由は全く分からんのだけど...

話が脱線しましたが、「runit」で「graceful」相当に該当するコマンドはというと、

stackoverflow.com

⇧ stackoverflowの回答によりますと、

■Graceful reload that keeps things up and running.

sv reload [service script]    

⇧ とありますと。

「ログローテーション」自体のハマりどころも多そうで、

qiita.com

qiita.com

⇧ 上記サイト様がまとめてくださっております。

ちなみに、

dev.classmethod.jp

原因

原因は Red Hat Enterprise Linux 9 の logrotate の起動方法のデフォルトがcronからsystemd-timerに変わっており、logrotateのデフォルトの動作間隔がdailyであるためでした。
1つずつ解説していきます。

Red Hat Enterprise Linux 9でlogrotateを使ってログファイルを1時間ごとにローテーションさせてみた | DevelopersIO

いきなり結論

  • Red Hat Enterprise Linux 9 の logrotate はcronではなく、systemd-timerで起動している。
  • デフォルトでは、 logrotate は日次で起動するようになっている。
  • 1時間ごとにローテションさせるには、logrotate.timer内のOnCalendardailyからhourlyに変更し、デーモンを再起動する必要がある。

Red Hat Enterprise Linux 9でlogrotateを使ってログファイルを1時間ごとにローテーションさせてみた | DevelopersIO

Red Hat系のバージョン9系から、logrotateの起動についての仕様が変わったらしい。

Rockey Linux 9系はというと、

⇧「logrotate.timer」が 表示されないんだが...

そもそも、

⇧ インストールされていないようなのだけど、設定ファイルの配置場所っぽい「/etc/logrotate.d/」だけは用意されているという罠過ぎる...

流石に、「OS(Operation System)」の初期インストール時に同梱されてるものだと思っていたんだが、まさかのインストールが必要とは想定の範囲外ですわ...

今日も今日とて、貴重な時間が不毛な作業に失われていくのであった...

毎度モヤモヤ感が半端ない…

今回はこのへんで。