読者です 読者をやめる 読者になる 読者になる

debian ベースの Docker コンテナで busybox の cron を実行

DockerHub の言語系のイメージは(alpine のもあるけれども)debian ベースのものが多いですが cron でスクリプトを定期的に実行しようとして、

RUN apt-get update && apt-get install -y cron

とかすると、

  • cron から実行するスクリプトにコンテナの環境変数が渡されない
    • コンテナの開始時に環境変数をファイルに書いて cron から実行するスクリプトで読んだり
  • スクリプトの標準出力や標準エラーがどこに行くのかよくわからない
    • スクリプトはログをファイルに出力して tail -f したり
    • そのログだれがローテートするの?

とか、とてもつらそうです。

ので、cron の代わりに michaloo/go-cron を使ってみたりしてたのですが、下記の記事を見まして、

もしかして busybox の crond ならそういう諸々の問題は無いのでは? と思って試しました。

Dockerfile

FROM php:7.1-cli

RUN apt-get update && apt-get -y install busybox-static

COPY crontab /var/spool/cron/crontabs/root
COPY script.php /app/script.php

CMD busybox crond -l 2 -L /dev/stderr -f

crontab

* * * * * php /app/script.php

script.php

<?php
system('env | sort | grep OREORE');
fprintf(STDERR, "%s: this is stderr\n", date('H:i:s'));

ビルドして実行します。

docker build . -t oreore/example-php-cron
docker run --name=oreore -e OREORE=12345 --rm oreore/example-php-cron

環境変数 OREORE=12345 がスクリプトに渡って、かつ、スクリプトの標準出力や標準エラーがそのまま Docker に伝わっています。

crond: crond (busybox 1.22.1) started, log level 2
crond: USER root pid   6 cmd php /app/script.php
OREORE=12345
10:50:05: this is stderr
crond: USER root pid  12 cmd php /app/script.php
OREORE=12345
10:51:01: this is stderr

Cacti でアラートを thold プラグインを使わずにやるアイデア(中途半端)

Cacti の thold プラグインでアラートを仕込むのがめんどくさくて仕方なかったので、もういっそのこと直接 rra ファイルを見てアラートを出すスクリプトを自前で作れば良いんじゃないかと思ったときのメモ。

Cacti のバージョンは 0.8.8h です。

Prometheus に入門したので中途半端なままで放置。


rra ファイルのパスは cacti の DB から下記のような SQL で取ります。

SELECT
  host.id as host_id,
  host.description as host_description,
  data_template.name as data_template_name,
  data_template_rrd.data_source_name as data_source_name,
  poller_item.rrd_path as rrd_path
FROM data_local
INNER JOIN host
  ON data_local.host_id = host.id
INNER JOIN data_template
  ON data_local.data_template_id = data_template.id
INNER JOIN data_template_rrd
  ON  data_local.id = data_template_rrd.local_data_id
  AND data_local.data_template_id = data_template_rrd.data_template_id
INNER JOIN poller_item
    ON data_local.id = poller_item.local_data_id

メトリクスの値は rrdtool で下記のように取れます。

rrdtool fetch hoge_prod_ap01_load_1min_100.rrd MAX \
    -s $(( $(date +%s) - 3600 )) -e $(( $(date +%s) - 300 ))
                      load_1min

1490619600: 7.0000000000e-02
1490619900: 8.6600000000e-02
1490620200: 6.4900000000e-02
1490620500: 2.6266666667e-02
1490620800: 3.3333333333e-03
1490621100: 6.6933333333e-02
1490621400: 7.1633333333e-02
1490621700: 8.6400000000e-02
1490622000: 9.0000000000e-02
1490622300: 1.5533333333e-01
1490622600: 5.7533333333e-02
1490622900: 3.0000000000e-02

あるいは xport なら複数の rra から JSON で取得できるので、こっちのが良さそう。

rrdtool xport --json \
    -s $(( $(date +%s) - 3600 )) -e $(( $(date +%s) - 300 )) \
    DEF:mem_buffers=hoge_prod_ap01_mem_buffers_101.rrd:mem_buffers:MAX \
    DEF:mem_cache=hoge_prod_ap01_mem_cache_102.rrd:mem_cache:MAX \
    DEF:mem_free=hoge_prod_ap01_mem_free_103.rrd:mem_free:MAX \
    DEF:mem_total=hoge_prod_ap01_mem_total_104.rrd:mem_total:MAX \
    XPORT:mem_buffers:mem_buffers \
    XPORT:mem_cache:mem_cache \
    XPORT:mem_free:mem_free \
    XPORT:mem_total:mem_total
{ about: 'RRDtool xport JSON output',
  meta: {
    "start": 1490619900,
    "step": 300,
    "end": 1490619900,
    "legend": [
      'mem_buffers',
      'mem_cache',
      'mem_free',
      'mem_total'
          ]
     },
  "data": [
    [ 8.5200000000e+02, 1.9999189200e+06, 7.8921592000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0017213467e+06, 7.8882005333e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0034699067e+06, 7.8495132000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0018100000e+06, 7.8899000000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0028664133e+06, 7.8575062667e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0050689867e+06, 7.8603305333e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0068675200e+06, 7.8260720000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0052690000e+06, 7.8560060000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0062675333e+06, 7.8268226667e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0079922267e+06, 7.8281285333e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0063401600e+06, 7.8266224000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0078709467e+06, 7.8317032000e+05, 3.8821600000e+06  ]
  ]
}

がしかし、これよく見たら JSON としては invalid ですね。。。--json を指定しなければ XML なのでそっちの方が良いかも。

あとは・・・

  • ホスト名は host.description に対して正規表現でパターンで指定する
    • それっぽく description が設定されている前提
    • <service>-<env>-<role><num> みたいに
  • data_source_name だけではメトリクスを一意に特定できないことがあるのでデータテンプレートも指定する
    • データテンプレートは名前でしか識別できなさそうなのでエイリアスを正規表現で指定する
    • 実際の閾値設定ではエイリアス名で指定する
    • data_source_name だけで一意になるなら監視設定ではデータテンプレートのエイリアスは省略しても良い
  • メトリクスをコールバックで加工した値に閾値を設定する

例えば下記のように設定する。

return [
    // データテンプレートのエイリアス
    // 複数のデータテンプレートがマッチするがデータソース名と足して一意になる前提
    'alias_data_template' => [
        'memory' => '^ucd/net - Memory -',
    ],
    'alert' => [
        [
            // ホスト名のパターン
            'host' => '^hoge-prod-ap\d+$',

            // データテンプレートのエイリアス+データソースのリスト
            'ds' => [
                'memory:mem_total',
                'memory:mem_buffers',
                'memory:mem_cache',
                'memory:mem_free',
            ],
            // チェックする値、引数の並びは ds に対応
            'expr' => function ($total, $buffer, $cache, $free) {
                return ($total - $buffer - $cache - $free) / $total * 100;
            },
            // 閾値
            'threshold' => [
                'WARNING' => 80,
                'CRITICAL' => 90,
            ],
            // 通知先
            'notify' => [
                'WARNING' => ['ore@example.com', 'are@example.com'],
                'CRITICAL' => 'sore@example.com',
            ],
            // 通知のメッセージ
            'message' => '{severity} [{hostname}] memory usage too high ... now:{value} > {threshold}',
        ],
    ],
];

なにかしらスクリプトを実行することで、DB を舐めてホスト名やデータテンプレート名のパターンとデータソースに基づく rra のパスをキャッシュしておくことで、定期的な閾値のチェック処理を軽減する。

例えば下記のようなキャッシュになるだろうか。

return [
    'host_ids' => [
        // パターンに対するホストIDの一覧
        '^hoge-prod-ap\d+$' => [1, 2, 3, 4],
    ],
    'rra_paths' => [
        // ホストID => データテンプレート+データソース => RRA
        1 => [
            'memory:mem_total'   => '/path/to/rra/are_prod_ap01_mem_total_1.rra',
            'memory:mem_buffers' => '/path/to/rra/are_prod_ap01_mem_buffers_2.rra',
            'memory:mem_cache'   => '/path/to/rra/are_prod_ap01_mem_cache_3.rra',
            'memory:mem_free'    => '/path/to/rra/are_prod_ap01_mem_free_4.rra',
        ],
    ],

];

Prometheus を使ってみた感じ、さくさく設定できてもうこれでいいかなと思ったので、この案はお蔵入り。

PhpStorm の Run/Debug で docker-compose run でテストを実行

docker-compose で複数のコンテナで構成されている環境に対して、PhpStorm の Run/Debug で docker-compose run でテストなどを実行できるようにしたときのメモ。


PhpStorm 2016.1 ぐらいから Remote interpreter に Docker が追加されており、PhpStorm の Run/Debug で「コンテナ作成&スクリプトやテスト実行」ができるようになっています。

Xdebug を実行する PHP のイメージにインストールしておけばデバッガでアタッチすることもでき、非常に便利そうですが・・・

単一のイメージから単一のコンテナを作成して実行するだけなので、docker-compose などで複数のコンテナ(DBとか)を実行する構成だと Run/Debug でテストを実行したときに DB が無くてテストがコケます。

docker run ではなく docker-compose run できれば解決なのだけれども。。。

いずれ出来るようになりそうな気がするけど、当座の方法として Vagrant の Remote interpreter で php executable を docker-compose run で php のコンテナを実行するシェルスクリプトに差し替えることで対応しました。

方法

下記のように設定します。

  • Vagrant 環境に docker-engine と docker-compose をインストール
    • docker は vagrant ユーザーから実行できるようにする
  • docker-compose.yml で下記をマウントするように設定
    • $PWD:$PWD:rw
      • /vagrant:/vagrant:rw とかでも ./:/vagrant:rw とかでも良い
    • $HOME:$HOME:ro
      • /home/vagrant:/home/vagrant:ro とかでも良い
  • Remote interpreter の php executable で後述のスクリプトを指定する

Vagrantfile で synced_folderssh.username を変更したときのことを考慮して $PWD とか $HOME とか使っているけどベタに書いても構いません。

php executable に指定するスクリプトは下記の内容で、プロジェクトルートに配置します。

#!/bin/bash

cd -- "$(dirname -- "$(readlink -f -- "$0")")" # `cd /vagrant` とかでも良い

exec docker-compose run --rm -T -w "$OLDPWD" \
  -e SSH_CLIENT="$SSH_CLIENT" -e XDEBUG_CONFIG="$XDEBUG_CONFIG" php php "$@"

2つある php の1つめは docker-compose はサービスの名前なので、適宜変更のこと。

後は Vagrant 環境の中で docker-compose up でコンテナを開始し、PhpStorm の Run/Debug でテストなどを実行すると docker-compose run で実行されます。

php のイメージに xdegug をインストールしておけばブレークポイントを仕込んで止めたり出来ます。

試行錯誤のメモ

プロジェクトルートのディレクトリ

コンテナ内でのプロジェクトルートのディレクトリは Vagrant 環境でのディレクトリと同じディレクトリになる必要があります。 そうしないと、リモートデバッグ時にコンテナ内の xdebug から通知されるパスと、ローカルの実際のパスの対応がとれません。

普通は /vagrant:/vagrant:rw とかだけど、Vagrantfile で、

config.vm.synced_folder ".", "/app"

のように変更しているなら /app:/app:rw とかに変更します。

このディレクトリは docker-compose 実行時のカレントディレクトリになるはずなので $PWD:$PWD:rw とかでも良いです。↑ではそうしてます。

Vagrant ユーザーのホームディレクトリ

PhpStorm の Vagrant の Remote inspector は Vagrant のログインユーザーのホームディレクトリに .phpstorm_helpers/phpinfo.php というファイルを配置し、これを実行することで環境の情報を得ます。

ので、このファイルがホスト(Vagrant 環境)とコンテナで共有されている必要があります。

↑ではホームディレクトリまるごとを共有しています。

SSH_CLIENT 環境変数

xdebug.remote_host の値の算出のために SSH_CLIENT 環境変数 が使用されるので、ホストの SSH_CLIENT 環境変数をコンテナにそのまま渡す必要があります。

XDEBUG_CONFIG 環境変数

リモートデバッグの開始のために XDEBUG_CONFIG 環境変数が必要なので、ホストの XDEBUG_CONFIG 環境変数をコンテナにそのまま渡す必要があります。

Local interpreter ではダメ?

Vagrant を噛まさずに Local の interpreter で php executable を↑のようなスクリプトにすれば、Docker for Mac で同じことが実現できるかと思ったけれども、xdebug.remote_host が 127.0.0.1 固定になるようで、コンテナから 127.0.0.1 ではホストにアクセスできないのでダメでした。

また、そもそも Docker for Windows だとパスの対応が取れなくてダメだと思います(C:\Users\ore -> /C/Users/ore とか)。

今回は Mac で試しているのだけど Windows でも出来るようにしたいので Vagrant のが無難だろうと思います。

スクリーンショット

f:id:ngyuki:20170326102634p:plain

f:id:ngyuki:20170326102646p:plain

f:id:ngyuki:20170326102652p:plain

CentOS を Kickstart の liveimg で rootfs の tgz からインストールする

最近は CentOS のインストールは Kickstart でなるべく自動化して GUI でぽちぽちしなくて良いようにしていますが、Kickstart で liveimg というものを使えばインストール自動化だけでなく、あらかじめ作成しておいた rootfs のディレクトリツリーをそのまま展開してインストールすることができることを知ったので、やってみました。

ざっくり説明すると下記のような流れでインストールします。

  • あらかじめ rootfs のディレクトリを作成して tar.gz(tgz) にまとめる
  • Kickstart の ks.cfg で liveimg で http 経由の tgz を指定する
  • 通常ならパッケージからインストールされるところが tgz をダンロードして展開するだけになる
  • ディスクの構成とかネットワークの構成とかは通常のインストールと同じように Kickstart で構成される

rootfs の tgz を作成

適当な CentOS 7 のサーバで tgz を下記のように作成します。

# chroot するディレクトリを作成
mkdir -p rootfs/

# yum リポジトリの設定ファイルをコピー
rsync /etc/yum.repos.d/CentOS* rootfs/ -Rav

# resolv.conf をコピー
cp /etc/resolv.conf rootfs/etc/resolv.conf

# 主要なパッケージをインストール
yum -y --installroot="$PWD/rootfs/" --releasever=7 install @core kernel grub2 authconfig mdadm lvm2

# chroot で中に入っていろいろ弄る
chroot rootfs/

# sudoers wheel
tee /etc/sudoers.d/wheel <<'EOS'
%wheel ALL=(ALL) NOPASSWD: ALL
Defaults:%wheel env_keep += SSH_AUTH_SOCK
Defaults:%wheel !requiretty
Defaults:%root  !requiretty
EOS
chmod 0440 /etc/sudoers.d/wheel

# ipv6 disable
tee /etc/sysctl.d/ipv6-disable.conf <<'EOS'
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
EOS

# sshd_config
sed -i '/UseDNS /c UseDNS no' /etc/ssh/sshd_config
sed -i '/PermitRootLogin /c PermitRootLogin without-password' /etc/ssh/sshd_config
sed -i '/AddressFamily /c AddressFamily inet' /etc/ssh/sshd_config

# postfix
postconf -e inet_protocols=ipv4

# authorized_keys
mkdir -p /root/.ssh/pub
curl -fsSL https://github.com/ngyuki.keys | awk 1 > /root/.ssh/pub/ngyuki
cat /root/.ssh/pub/* > /root/.ssh/authorized_keys
chown -R root: /root/.ssh
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys

# chroot から抜ける
exit

# tgz にまとめて HTTP で見れる場所に置く
tar czvf /var/www/html/rootfs.tgz --directory rootfs/ .

Kickstart

次のように Kickstart ファイルを作ります。

ks.cfg

#version=RHEL7

install
liveimg --url=http://example.com/rootfs.tgz
text
cmdline
skipx

lang en_US.UTF-8
keyboard --vckeymap=jp106 --xlayouts=jp
timezone Asia/Tokyo --isUtc --nontp

network --activate --device=link --onboot=yes --bootproto=dhcp --noipv6

zerombr
clearpart --all
bootloader --location=mbr

part raid.11 --ondrive=sda --asprimary --size=500
part raid.12 --ondrive=sdb --asprimary --size=500

part raid.21 --ondrive=sda --asprimary --grow
part raid.22 --ondrive=sdb --asprimary --grow

part swap --ondrive=sda --asprimary --size=1024
part swap --ondrive=sdb --asprimary --size=1024

raid /boot --fstype=xfs --device=md0 --level=RAID1 raid.11 raid.12
raid pv.01 --fstype=xfs --device=md1 --level=RAID1 raid.21 raid.22

volgroup vg0 pv.01 --pesize=32768

logvol / --vgname=vg0 --size=8192 --name=lv_root

rootpw --plaintext password
auth --enableshadow --passalgo=sha512
selinux --disabled
firewall --disabled
firstboot --disabled

reboot

%post --log=/root/ks-post.log
set -eux
sed -i -r '/^GRUB_CMDLINE_LINUX=/s/\s+rhgb//' /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub.cfg
%end

Install

CentOS 7 の minimal の CD-ROM からブートして、インストールのカーネルパラメータで下記のように Kickstart ファイルを指定します。

inst.ks=http://example.com/ks.cfg

さいごに

あらかじめいろいろ構成した一式でインストールできるため、普通に「CentOS をインストール → 追加のパッケージをインストールしたり設定したり」と比べるとかなり早いです。

物理サーバをたくさんセットアップしなければならないときの時間短縮に使えそうです。

また、いざというときにインターネットに接続できない環境で再セットアップする必要に迫られたときも、この方法ならどこか適当な Web サーバにアーカイブを配置するだけなので、普通にやるのと比べれば楽です(yum リポジトリのミラーとか作らなくても良い)。

補足

パッケージのインストールで @core kernel grub2 authconfig mdadm lvm2 とかを指定していますが、@core kernel grub2 authconfig あたりがないと Kickstart でコケます。

mdadmlvm2 はルートパーティションや /boot が MD とか LVM とかの場合に必要です。

参考

CentOS 7 で Pacemaker/Corosync 使うなら LinuxHA Japan と CentOS のどっちが良い?

  • 元は 1 年くらい前に書いたものなので古いです
  • 今は CentOS の公式レポから入れた Pacemaker/Corosync が元気に動いています

Pacemaker/Corosync を使うにあたり、LinuxHA Japan のパッケージと CentOS のパッケージのどちらが良いかのメモ。

LinuxHA Japan のパッケージは下記から入手できる。

ざっくりバージョンの比較

  • LinuxHA Japan
    • corosync-2.3.4-1
    • pacemaker-1.1.13-1
    • resource-agents-3.9.6-1
  • CentOS
    • corosync-2.3.4-7
    • pacemaker-1.1.13-10
    • resource-agents-3.9.5-54

メンテナンスのポリシー

CentOS は updates にパッケージがあったので、バージョンを維持したまま CentOS 7 のライフサイクルの終了まで保守される。

LinuxHA Japan の方は新しいバージョンに更新されている。ポリシーも良くわからない(たぶん無い?)

LinuxHA Japan 特有のパッケージ

下記は LinuxHA Japan がメンテしているものなので LinuxHA Japan のパッケージにしか含まれていない。

  • pm_crmgen
    • Excel から crm.xml を作成するツール
  • pm_diskd
    • ディスクの正常性の監視(pingd の ディスク版)
  • pm_extras
    • いくつかの追加のリソースエージェントとか
    • インタフェースの状態をアトリビュートに記録するデーモン(crm_mon で表示できる)
  • pm_ctl
    • SSH 経由でクラスタのすべての Pacemaker を開始・停止などの制御を行なうツール
    • リソースのマイグレーションもコマンド一発で出来たりするっぽい

下記は他所でメンテされているものだが LinuxHA Japan のパッケージに含まれている。

pssh は次のように使えるものでこれ単体で非常に便利だけど Pacemaker と直接の関係はない。

$ pssh -H 192.168.33.10 -H 192.168.33.11 -H 192.168.33.12 mkdir -p /tmp/hoge
[1] 08:07:14 [SUCCESS] 192.168.33.12
[2] 08:07:14 [SUCCESS] 192.168.33.10
[3] 08:07:14 [SUCCESS] 192.168.33.11

crm と pcs

crm (crmsh パッケージのコマンド) での下記は・・・

crm configure

property stonith-enabled="false"
property no-quorum-policy="ignore"

rsc_defaults migration-threshold="5"
rsc_defaults resource-stickiness="INFINITY"
rsc_defaults failure-timeout="3600s"

primitive vip1 ocf:heartbeat:IPaddr2 \
  params ip="192.168.33.21" cidr_netmask="24" nic="enp0s8" \
  op monitor interval="10" timeout="20" on-fail="restart" \
  op start interval="0" timeout="20" \
  op stop interval="0" timeout="20"

primitive vip2 ocf:heartbeat:IPaddr2 \
  params ip="192.168.33.22" cidr_netmask="24" nic="enp0s8" \
  op monitor interval="10" timeout="20" on-fail="restart" \
  op start interval="0" timeout="20" \
  op stop interval="0" timeout="20"

primitive vip3 ocf:heartbeat:IPaddr2 \
  params ip="192.168.33.23" cidr_netmask="24" nic="enp0s8" \
  op monitor interval="10" timeout="20" on-fail="restart" \
  op start interval="0" timeout="20" \
  op stop interval="0" timeout="20"

group vips vip1 vip2 vip3

verify
commit
show

quit

pcs だと下記の通り。

pcs property set stonith-enabled="false"
pcs property set no-quorum-policy="ignore"

pcs resource defaults migration-threshold="5"
pcs resource defaults resource-stickiness="INFINITY"
pcs resource defaults failure-timeout="3600s"

pcs resource create vip1 ocf:heartbeat:IPaddr2 \
  ip="192.168.33.21" cidr_netmask="24" nic="enp0s8" \
    op monitor interval="10" timeout="20" on-fail="restart" \
    op start interval="0" timeout="20" \
    op stop interval="0" timeout="20"

pcs resource create vip2 ocf:heartbeat:IPaddr2 \
  ip="192.168.33.22" cidr_netmask="24" nic="enp0s8" \
    op monitor interval="10" timeout="20" on-fail="restart" \
    op start interval="0" timeout="20" \
    op stop interval="0" timeout="20"

pcs resource create vip3 ocf:heartbeat:IPaddr2 \
  ip="192.168.33.23" cidr_netmask="24" nic="enp0s8" \
    op monitor interval="10" timeout="20" on-fail="restart" \
    op start interval="0" timeout="20" \
    op stop interval="0" timeout="20"

pcs resource group add vips vip1 vip2 vip3

pcs config
pcs status

まとめ

LinuxHA Japan の特有のパッケージに特別必要なものがないなら CentOS で良いように思う。

Pacemaker 1.0 時代に慣れていると crmsh が無いのが辛いかと思ってたけど、代替の pcs が対話型シェル風に使えないだけでほとんど同じように使えてとくに困らなさそう。

最近の snmptrapd は DISPLAY-HINT 255t でもマルチバイト文字が置換されてしまう件

諸事情でアプリやサーバからいろいろな通知を SNMP トラップで送ることがよくあるのですが、データバインディングの中身に日本語(UTF-8)を含めなければならないことがあります。

送るのは snmptrap コマンドで普通に送れますが・・snmptrapd でそれを受信するためには少し工夫が必要でした。

snmptrapd で日本語を受ける

普通に受けるとマルチバイト部分が "E3 81 82 E3 81 84 E3 81 86 E3 81 88 E3 81 8A" のように16進の文字列に置き換えられて traphandle には渡されます。

これをどうにかするために、まずトラップの MIB で次のように型を定義します。

HogeString ::= TEXTUAL-CONVENTION
    DISPLAY-HINT    "255t"
    STATUS          current
    SYNTAX          OCTET STRING

トラップのデータバインディングのオブジェクトの定義で↑の型を使います。

hogeMessage OBJECT-TYPE
    SYNTAX      HogeString
    MAX-ACCESS  read-only
    STATUS      current
    ::= { hogeAlarmObjs 1 }

この MIB を snmptrapd に食わせて snmptrap で日本語(UTF-8)を含むトラップを送ると UTF-8 のバイナリそのままで traphandle に渡されます。

CentOS 7 で問題発生

CentOS 5 の snmptrapd なら↑の方法で大丈夫でした。

がしかし、CentOS 7 の snmptrapd だとマルチバイト部分が . で置換されてしまいました。

原因

これ↓です。

そのままバッファにコピーされていたものが sprint_realloc_asciistring を通るようになっています。

sprint_realloc_asciistring では isprint でも isspace でもない文字はすべて . に置換されます。

簡単な解決方法

たぶんない。

強いて言えばトラップで日本語などのマルチバイト文字を使わないようにすることですけど。

面倒な解決方法

16進の文字列を traphandle に指定するスクリプトでデコードします。

実はもっと昔は、日本語(UTF-8)ではなく日本語(Shift_JIS)を使っていました。その頃は16進になった文字列を自前でデコードしていました。

ので、そんなに面倒なことでは無いはずですが・・・

MIB を食わさずに -Oa を付けて snmptrapd を起動すると同じ動き(. に置換)になり、-Ox なら16進表記になります。

-Oa は文字列をそのまま表示するオプションで、-Ox は文字列を16進で表示するオプションです。どちらのオプションも指定しないときは文字列がそのままで表示可能かを自動で判断されます。

がしかし、MIB で DISPLAY-HINT が指定されているときは -Oa-Ox は無視して DISPLAY-HINT に従います。

なので、↑の MIB を食わすと -Ox を指定したとしてもマルチバイト文字は . に置換されてしまい、16進文字列にはなりません。

つまり、↑の MIB を食わせる限りマルチバイト文字が . に置換されてしまい、デコードも不可能です。

MIB を食わせなければ良いのですが・・・下記のような列挙型のオブジェクトは値そのものではなく名前に置き換わって欲しいので、MIB は食わせたいです。

HogePriority ::= INTEGER {
    EMERG   (0),
    ALERT   (1),
    CRIT    (2),
    ERR     (3),
    WARN    (4),
    NOTICE  (5),
    INFO    (6),
    DEBUG   (7)
}

もちろん MIB を書き換えて DISPLAY-HINT を削除してしまえば良いんですけど・・・

MIB をそのままで解決する

/etc/snmp/snmp.conf に下記を追記します。

noDisplayHint yes

すると DISPLAY-HINT が無視されて16進文字列になるので、自前でデコードできます。

ただ、この方法だと↑の列挙型のオブジェクトの名前への解決が行われないっぽい気が・・・やっぱり MIB 書き換えるしか無いか。

なお、この設定(noDisplayHint)はオプションの -Ih と同じ意味ですが、snmptrapd は -I が別の意味に割り当てられているので設定ファイルで指定する必要があります。

Jenkins Pipeline を使ってみたメモ その2

notifyCommit を呼び出したときに実行されるビルドについて、フリースタイルと Pipeline で下記の違いが合った。

  • フリースタイル
    • 1回の notifyCommit で複数のブランチがビルドされる(こともある)
    • Git ポーリングで見つかった、更新されたすべてのブランチのビルドがキューに入る
  • Pipeline
    • 1回の notifyCommit で1つのブランチしかビルドされない
    • Git ポーリングで見つかった、更新されたブランチのうち1つだけがキューに入る
    • さらに、Pending のビルドがあると notifyCommit を呼んでもキューに入らない

なので、下記のようにブランチ [A] が「Pending 〜 開始」の間に別のブランチ [B] がプッシュされるとビルドされない。

  1. [A] ブランチをプッシュ
  2. [A] notifyCommit 呼び出し
  3. [A] ビルドが Pending
  4. [B] ブランチをプッシュ
  5. [B] notifyCommit 呼び出し
  6. [A] ビルドが開始

もっとも、[A] のビルドが開始された後に何らかの方法で notifyCommit を呼べば [B] のブランチはビルドされるし、3. から 6. までの時間はそんなに掛からないはずなので(待機時間を 0 すれば Git のポーリングの時間だけのはず)、あまり困らないような気もしたけど・・

下記のように3つのブランチがプッシュされた状況だと普通に起こりえる。

  1. [A] ブランチをプッシュ
  2. [A] notifyCommit 呼び出し
  3. [A] ビルドが開始
  4. [B] ブランチをプッシュ
  5. [B] notifyCommit 呼び出し
  6. [B] ビルドが Pending (並列実行は無効にしている)
  7. [C] ブランチをプッシュ
  8. [C] notifyCommit 呼び出し
    • [B] が Pending なのでビルドのキューに入らない
  9. [A] ビルドがが終了
  10. [B] ビルドが開始

このケース、[C][A] だった場合も同じだと思う。

うーん、Pipeline は Git の notifyCommit との連携が微妙な感じ・・・

解決?

と思ってたんだけど、Pipeline の前段にフリースタイルジョブを置いて Parameterized Trigger の Pass-through Git Commit that was built でコミット ID を渡せばすべて解決した気がする。

↑の問題はそもそもフリースタイルなら問題ないので、フリースタイルのジョブから Pipeline をトリガすれば良い。

また、下記の記事に書いた checkout scm でコミットが指定されない問題も解決できているように思う。

checkout scm でコミットが指定されない問題は、要するに次のような Jenkinsfile だったときに

node ('ore') {
    stage 'first'
    checkout scm
    sleep 60
}

node ('are') {
    stage 'second'
    checkout scm
}

sleep 60 している間にブランチがプッシュされると、次のステージの checkout scm で異なるコミットがチェックアウトされてしまう、ということ。

がしかし、Parameterized Trigger でコミット ID を渡してやれば大丈夫。

また、BRANCH_NAME が設定されない問題も Parameterized Trigger で $GIT_BRANCH を元に設定してやれば良い。

ただ、Pipeline のジョブを手動で実行した場合はダメなので注意が必要。手動でビルドしたければ前段のフリースタイルジョブをビルドしなければならない・・・