ログ日記

作業ログと日記とメモ

Debian Buster に Cacti をインストールする

ログをElasticSearch+Kibanaに集約したり、Prometheus を使ってみたりしたけれども、普通にサーバーのログファイル+Cactiに戻ってきた。

最近の流行は大量のサーバーとか大量のコンテナとかがメインターゲットなんだね。
普通に数台〜十数台のコンテナではないサーバーなら、ログファイルとCactiが一番使いやすそうということになった。

実際に長期間KibanaとPrometheusを試したわけではないから分からないけれども。

そういうわけでDebian BusterにCactiをインストールしたログ。

apt install cacti cacti-spine
chown www-data:www-data /var/log/cacti/*

インストール時にログの所有者がwww-dataになっていないので手動変更する。
自動でapache2とmod-phpがインストールされる。
普段はnginxとphp-fpmを使っているけれど、自動インストールに任せたかったのでapacheのままにした。

/etc/apache2/conf-available/cacti.conf に設定があり、これだけで /cacti で既に利用できるようになっているけれども、sslとか独自サブドメインとかの設定をするなら普通にsites-availableを自分で用意する。


それから、SNMPは使わないので
Installing Percona Monitoring Plugins for Cacti
PerconaのCactiプラグインを入れる。このテンプレートで、SNMPの代わりにSSHで繋げる。
(NginxでもSSHしてからwgetするので、直接繋ぎたい場合は後でコマンドに --use-ssh 0 を付ける)

busterが無くstretchしか無いけれども、特に依存があるわけでもないのでstretch用パッケージで問題ない。

PostgreSQL
PostgreSQL Host Template - Cacti

PHP-FPM
GitHub - glensc/cacti-template-php-fpm: Cacti PHP-FPM template


どれもあまりメンテされてなさそうだけれども、使える。
cactiの過去グラフが平均化されないようにする方法 - Qiita
この辺も設定する。


tholdプラグインは、gitで取ってきて 2ea956d6 をインストール。
バージョンが合わない問題、バージョンタグが細かく打たれていない問題があるので、互換性がある最新のハッシュを探す必要があった。
v1.2のようにタグを決め打ちで取得すると、バグがかなりある。
2ea956d6 でも若干怪しい部分があるが、これ以上新しくすると互換性がなくなる。


それから
MAILER WARNING: Mail failed from... - Cacti
このようなエラーに対応する。

Debian公式パッケージになっているのに、バグがあるとはね…。
人気の無いパッケージのつらさよ。


まあでも一度動き始めれば使い続けられるという安心感があるし、PHPerなので多少のバグは自分で直せる。
色々試して、RRDTool だとディスク容量が増えない安心感があるということも実感した。
ログを3年保存とかにしても、設定直後にディスクが確保されるから、その時点で足りていれば大丈夫。
サーバー数が倍になる予定なら、あとから2倍の容量になるだろうという予測も簡単。


実際の設定反映はAnsibleにしている。
各ノードにはsshのログが大量に出るので、/etc/rsyslog.d/cacti.conf に

if \
  ($msg contains 'Received disconnect from 192.168.0.10') or \
  ($msg contains 'Disconnected from user cacti 192.168.0.10') or \
  ($msg contains 'Accepted publickey for cacti from 192.168.0.10' ) then \
  /var/log/cacti.log
& ~

を追加して、/etc/logrotate.d/rsyslog に /var/log/cacti.log を追加したりといったことも自動化した。

プログラムの分け方とディレクトリ構造

最近、プログラムのディレクトリレイアウトというかファイルを置く場所を変えようと試みている。



旧来のMVC的な構造だと、ControllerやModelのディレクトリを分ける、レイヤーごとにまとめる、という感じになると思う。
商品情報表示画面、その管理画面、CSVダウンロード機能があるとすると、

old1
├── src
│   ├── Controller
│   │   ├── Admin
│   │   │   ├── ProductController.php
│   │   │   └── ProductCsvController.php
│   │   └── ProductController.php
│   ├── Dto
│   │   └── ProductDto.php
│   ├── Model
│   │   ├── CsvModel.php
│   │   ├── ProductCsvModel.php
│   │   └── ProductModel.php
│   └── ViewModel
│       └── ProductBehavior.php
└── templates
    ├── admin
    │   └── product
    │       └── product.blade.php
    └── product.blade.php

こんな感じになると思う。
ModelはDomainとかServiceとかInfraとか、そういうのでも良い。

ここに、期間限定キャンペーン用商品に関する機能を追加するとしよう。
特殊な画面と特殊な処理なので、DBに数項目追加するといった対応はできないとする。
そうすると

old2
├── src
│   ├── Controller
│   │   ├── Admin
│   │   │   ├── CampaignProductCsvController.php
│   │   │   ├── ProductController.php
│   │   │   └── ProductCsvController.php
│   │   ├── CampaignProductController.php
│   │   └── ProductController.php
│   ├── Dto
│   │   ├── CampaignProductDto.php
│   │   └── ProductDto.php
│   ├── Model
│   │   ├── CampaignProductCsvModel.php
│   │   ├── CsvModel.php
│   │   ├── ProductCsvModel.php
│   │   └── ProductModel.php
│   └── ViewModel
│       ├── CampaignProductBehavior.php
│       └── ProductBehavior.php
└── templates
    ├── admin
    │   └── product
    │       ├── campaign_product.blade.php
    │       └── product.blade.php
    ├── campaign_product.blade.php
    └── product.blade.php

こんな感じのディレクトリレイアウト、クラス名、テンプレート名で機能追加するだろうか。

フレームワークなんかでは専用コマンドが用意されたりしていて、scaffoldと呼ばれたりもする。
これで一気にController、Model、Viewのファイルを生成したりするわけだけれども、そもそもの構造が複雑だからこそ専用コマンドが複雑になるとも言える。

自分はこの自動生成は好きではない。AbstractControllerやAbstractModelも好きではない。できるだけシンプルにしたい。
最近その辺りの構造を大きく変えようと思っている。


基本的には層でディレクトリを分けるのではなくて、画面単位で分ける。

new1
└── src
    ├── Model
    │   ├── CsvModel.php
    │   ├── ProductModel
    │   │   └── Dto.php
    │   └── ProductModel.php
    ├── Page
    │   ├── Admin
    │   │   └── Product
    │   │       ├── CsvModel.php
    │   │       ├── CsvPage.php
    │   │       ├── Dto.php
    │   │       ├── Html.php
    │   │       └── Page.php
    │   └── Product
    │       ├── Dto.php
    │       ├── Html.php
    │       └── Page.php
    └── ViewModel
        └── ProductBehavior.php

こんな感じにする。
ControllerはMVC的なコントロールはしないので単にPageという名前する。
テンプレートエンジンはやめて、Html.php に昔ながらのやり方で書く。とは言ってもネームスペースとクラスは使う。
Page.php から ModelやViewModelを使って何らかの処理をした後に、Dtoに詰め込んでHtml.php に渡す。これを同じディレクトリに置く。
2箇所以上で使う処理は旧来のModelやService等に分ける。1箇所からしか使われない処理はPageクラスの近くに置く。先のことは考えない。他のPageでの処理を使いたくなったその時に共通化して移動する。

キャンペーン機能を追加した後はこんな感じ。

new2
└── src
    ├── Model
    │   ├── CsvModel.php
    │   ├── ProductModel
    │   │   ├── CampaignInterface.php
    │   │   └── Dto.php
    │   └── ProductModel.php
    ├── Page
    │   ├── Admin
    │   │   └── Product
    │   │       ├── Campaign
    │   │       │   ├── CsvModel.php
    │   │       │   ├── CsvPage.php
    │   │       │   └── Html.php
    │   │       ├── CsvModel.php
    │   │       ├── CsvPage.php
    │   │       ├── Dto.php
    │   │       ├── Html.php
    │   │       └── Page.php
    │   └── Product
    │       ├── Campaign
    │       │   ├── Dto.php
    │       │   ├── Html.php
    │       │   └── Page.php
    │       ├── Dto.php
    │       ├── Html.php
    │       └── Page.php
    └── ViewModel
        ├── CampaignProductBehavior.php
        └── ProductBehavior.php

ここでも各レイヤーをまとめて同じディレクトリに追加する。この方が見やすく、依存関係も分かりやすい。
リファクタリングも手軽にできる。


前提として、DBで

select * from product

のようなことはしない、という要因もあるかもしれない。

select product_id, name, description from product

のように、必要な項目だけ取得する。
なおかつ、型指定ありのDtoに詰め替える。
逆に言うと、型指定ありのDtoで必要な項目だけ詰めて渡すようなことをやり始めると、あまり共通化ができない、とも言える。

Html.php もPHPStanで静的解析に含むので、かなり安心感がある。

もし共通化してDBの返却値も共通化したりすると、富豪的なDBアクセスとオブジェクト生成で遅くなる。
各画面でDBの同じテーブルにアクセスしたとしても、必要な項目まで同じということはあまり起こらない。それでも共通化するとしたら、各画面で必要な項目をどんどん追加していくことになってDB処理が膨れていく。
機能を削除したとしても、迂闊に項目を消したりできない。

そういった理由で、今は徐々にテンプレートエンジンを使うのをやめて各ページの処理とHTMLを同じ場所に移動したりして、徐々に変更して試している。

Debian stretch で MySQL 5.6 のアップグレード

本家のリポジトリを使ったMySQLの5.6.47 から 5.6.48 のアップグレードが失敗する。


/usr/bin/mysqld_safe のスクリプトが間違っているっぽい。
データディレクトリが /usr/data になってしまう。
OSもMySQLも古いバージョンだから、メンテされていないのかもしれない。

ファイルを書き換えたり色々試したけど、インストールの前後の処理で上書きされたり、post install が止まったりして不安定だった。

ln -s /var/lib/mysql /usr/data

取り急ぎリンクを張ってやり過ごした。


apt でエラーが出たときのコマンドメモ。

dpkg --audit
dpkg --configure --pending
apt --fix-broken install

色々案内されたけど、インストールスクリプトが間違ってたらどうしようもないよね。


apt install --reinstall mysql-server mysql-community-server ではうまくいかず、一旦removeしてinstallした。
パッケージを消してもデータはそのまま残ってるので後でinstallすれば使える。

PHPStan バージョン0.12.24から PDOStatementが Traversable でなくなってしまった件

ジェネリクスの実装が

@implements Traversable<array<int|string, mixed>>

で固定されるようになった。

FETCH_CLASSとジェネリクスを組み合わせていい感じにマッピングする方法が使えなくなって、ちょっとめんどくなってしまった。
都度 @var が必要になる。

一応報告しておいたけど。
https://github.com/phpstan/phpstan/issues/3509
closeされてしまった。

素のPDOStatementを使いつつがっつりクラス定義する人が居なくて、あまり認識されてないのかもしれない。

Let's Encryptで後からサブドメインを変更する

既に取得済みのSSLに、後からサブドメインを追加したくなった場合。

削除して再取得するしかないのかなと思ったけど、ドメイン名 common name の更新もできるようだ。
github.com

certbot certonly --force-renew --cert-name example.com -d example.com -d '*.example.com'

www.example.com を消してワイルドカードに変更した。
ドメインリストの最初のドメインがcommon name。

コマンドは素っ気ないけれど、最初に所有者チェックの方法の選択肢を聞かれて、その後に追加されるドメインと削除されるドメインの確認が表示される。

certbot certificates

このコマンドで確認ができる。

DDDのさわりをやろうとした

ちょっと一部だけ複雑なプログラムがあったので、PHPでDDDっぽいオブジェクト指向をやろうとした。
なかなか大変だった。


まず静的解析による型チェックは必須。
型が自動でチェックされないDDDは、適当に書き捨てたプログラムより分かりにくくなると思った。

パッケージプライベートかfriendが欲しい。インナークラスも欲しい。
油断すると層を飛び越えてnewしちゃう。
例外も層を飛び越えたくないから、一度catchで掴んで新しい例外で包み直してからthrowしたい。でもめんどくさいしもうJavaじゃないかっていうコードになる。

あとテンプレートエンジンは使わず素のPHPで書くことが必要。
元々phpstanを使い始めてからテンプレートエンジンのデメリットが目立ってきたところで、複雑なオブジェクトを扱い出すとテンプレートエンジンのデメリットがますます強調されてきた。
折角の型チェックがテンプレートエンジンを使うことによって全て無に帰す。これはよくない。


それからDIやORMで文字列を指定して何かを取得する処理は全てやめた方が良い。
json_decodeも危険だしデータをarrayに詰め込むなんてもってのほか。


これは結構な思考の転換が求められる。



ひとまずパッケージプライベートのような仕組みは必須レベルで必要な気がする。
余裕ができたらphpstanでパッケージのnamespace階層をチェックするextensionを作ろう。

PHPStan が早い

PHPStanのデフォルトのルールを変更したくて、どうすれば良いか分からなかった。
デフォルトの config.neon を変更する方法を教えてってissueに書いたら10分後に返信来た。
scopeClassを変更すればgetType()で何でもできるよ、でも全体で一つの拡張しか使えないから実験的な機能だよ、だそうだ。

それから次の週に、ArrayAccessのジェネリクスが動かないのでバグレポートした。
これも数時間で返信が来た。
それでバグじゃなくてfeature-requestに分類されて、もしかして結構後回しになっちゃうのかな?と思って次の週に自分でプルリクした。

正直実装方法が分からなくて不完全なコミットだった。プルリクにドラフトの機能があったんだね。そっちにすれば良かったと後で気付いたけど変更出来ず…。
コメントもらったり修正したりしてたら作者が関連コード見つけて解決してくれたっぽい。テストだけ取り込まれた。
その間も1日以内。

それで次の日にはマイナーバージョン上がった版がリリースされてた。(リリースページに名前があるよ!)

進むの早すぎない?というか作者の返信が早いし活動もずっとやってる感じがある。
次回があればもう少しまともなプルリクにしようと思うけども、ソース読んで理解するより早く開発が進んで追いつけない気もする。