めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

RHEL6 で悩ましい諸々のまとめ

Network Manager と仲良くなれないやら modprobe.conf がなくなったやら諸々の悩みを解決するお手伝いをいたします。(この記事は、不定期にアップデートする予定です。)

Red Hat 公式ガイドはこちらを参照

※ RHEL5もまだまだ現役という貴兄には、プロのためのLinuxシステム構築・運用技術もよろしくお願いします。

システムコンソールの切り替え

Ctrl + Alt + F1 が X Window になってます。コンソールでテキストログインしたい時は、Ctrl + Alt + F2 〜 F6 を使います。

NetworkManager

デフォルトで NetworkManager サービスが起動するため /etc/sysconfig/network-scripts/ifcfg-XXX の書式が従来と変わっています。

従来の書式で設定ファイルを書く場合は、設定ファイルに下記の1行を追加します。

NM_CONTROLLED=no

もしくは NetworkManager サービスを停止します。

# chkconfig NetworkManager off
# service NetworkManager stop

特に KVM 環境で Bridge デバイスを作る場合は、NetworkManager を使用することができませんので、サーバ環境では NetworkManager サービスを止めておくのがよいと思います。

ethX と MAC Address の紐付け

/etc/udev/rules.d/70-persistent-net.rules で指定します。

# cat /etc/udev/rules.d/70-persistent-net.rules
# This file was automatically generated by the /lib/udev/write_net_rules
# program, run by the persistent-net-generator.rules rules file.
#
# You can modify it, as long as you keep each rule on a single
# line, and change only the value of the NAME= key.

# PCI device 0x14e4:0x164c (bnx2) (custom name provided by external tool)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:14:5e:fa:98:a2", ATTR{type}=="1", KERNEL=="eth*", NAME="eth1"

# PCI device 0x14e4:0x164c (bnx2) (custom name provided by external tool)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:14:5e:fa:98:a0", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"

が、相変わらず、/etc/sysconfig/network-scripts/ifcfg-ethX にも HWADDR 指定があるので、両方を合わせないといけない。ifcfg-ethX の HWADDR は削除した方が幸せになれるかも知れません。

/etc/modprobe.conf

/etc/modprobe.conf が無くなりました。目的別に対応を説明します。

(1) initrdに投入する内蔵ディスク用のデバイス・ドライバの指定(alias scsi_hostadapterXX)

RHEL5 までは、mkinitrd コマンドは /etc/modporbe.conf をみて、そのサーバを起動するために必要なドライバを含む initrd を作成しました。initrd に含まれる初期化スクリプト init では、下記の例のように必要なドライバを直接 insmod しています。

echo "Loading ehci-hcd.ko module"
insmod /lib/ehci-hcd.ko
echo "Loading ohci-hcd.ko module"
insmod /lib/ohci-hcd.ko
echo "Loading uhci-hcd.ko module"
insmod /lib/uhci-hcd.ko
mount -t usbfs /proc/bus/usb /proc/bus/usb
echo "Loading jbd.ko module"
insmod /lib/jbd.ko
echo "Loading ext3.ko module"
insmod /lib/ext3.ko
echo "Loading scsi_mod.ko module"
insmod /lib/scsi_mod.ko
echo "Loading sd_mod.ko module"
insmod /lib/sd_mod.ko
echo "Loading scsi_transport_sas.ko module"
insmod /lib/scsi_transport_sas.ko
echo "Loading mptbase.ko module"
insmod /lib/mptbase.ko
echo "Loading mptscsih.ko module"
insmod /lib/mptscsih.ko
echo "Loading mptsas.ko module"
insmod /lib/mptsas.ko
echo "Loading shpchp.ko module"
insmod /lib/shpchp.ko
echo "Loading libata.ko module"
insmod /lib/libata.ko
echo "Loading ata_piix.ko module"
insmod /lib/ata_piix.ko
...

RHEL6 では、initrd の作成が dracut に変わって、サーバ個別の initrd ではなく、汎用的な initrd が作成されるようになったので、/etc/modprobe.conf による指定は不要になりました。dracut が作成する initrd では、udev によって、サーバに接続されたデバイスに応じたドライバを自動判別してロードします。

具体的には、initrd に含まれる etc/udev/rules.d/80-drivers.rules のルールから modprobe が呼び出されます。

# cat 80-drivers.rules
# do not edit this file, it will be overwritten on update

ACTION!="add", GOTO="drivers_end"

DRIVER!="?*", ENV{MODALIAS}=="?*", RUN+="/sbin/modprobe -b $env{MODALIAS}"
SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="SD", RUN+="/sbin/modprobe -b tifm_sd"
SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="MS", RUN+="/sbin/modprobe -b tifm_ms"
SUBSYSTEM=="memstick", RUN+="/sbin/modprobe -b --all ms_block mspro_block"
SUBSYSTEM=="i2o", RUN+="/sbin/modprobe -b i2o_block"
SUBSYSTEM=="scsi", ENV{DEVTYPE}=="scsi_device", TEST!="[module/sg]", RUN+="/sbin/modprobe -b sg"
SUBSYSTEM=="module", KERNEL=="parport_pc", RUN+="/sbin/modprobe -b ppdev"

LABEL="drivers_end"

そうは言っても特定のドライバを特定の順序で読み込ませたい場合は、カーネルオプション rdloaddriver にドライバを読み込む順に記載します。具体的には、init スクリプトから下記のスクリプトが実行されて、指定のドライバがロードされます。

# cat cmdline/01parse-kernel.sh
#!/bin/sh

for p in $(getargs rdloaddriver=); do
        modprobe $p
done

dracut が標準で initrd に含めないドライバを使用する際は、dracut の -d オプションで強制的に initrd に入れておきます。dracut については、レッドハットニュースレター:vol.53 - dracut もどうぞ。

(2) NIC用のデバイス・ドライバの指定(alias ethXX)

サーバ起動後の追加のドライバのロードは、すべて udev で処理されるようになっています。具体的には、rc.sysinit から start_udev したタイミングで、/lib/udev/rules.d/80-drivers.rules に従って自動的に modprobe されます。(dracut 内部の動作と基本的には同じです。)

# cat /lib/udev/rules.d/80-drivers.rules
# do not edit this file, it will be overwritten on update

ACTION!="add", GOTO="drivers_end"

DRIVER!="?*", ENV{MODALIAS}=="?*", RUN+="/sbin/modprobe -b $env{MODALIAS}"
SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="SD", RUN+="/sbin/modprobe -b tifm_sd"
SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="MS", RUN+="/sbin/modprobe -b tifm_ms"
SUBSYSTEM=="memstick", RUN+="/sbin/modprobe -b --all ms_block mspro_block"
SUBSYSTEM=="i2o", RUN+="/sbin/modprobe -b i2o_block"
SUBSYSTEM=="scsi", ENV{DEVTYPE}=="scsi_device", TEST!="[module/sg]", RUN+="/sbin/modprobe -b sg"
SUBSYSTEM=="module", KERNEL=="parport_pc", RUN+="/sbin/modprobe -b ppdev"

LABEL="drivers_end"

ちなみに、udev が必要なドライバを判別する方法は次のとおりです。まず、PCI デバイスなどは、/sys に登録されるとカードに固有の modalias 情報が登録されます。

# cat /sys/devices/pci0000\:00/0000\:00\:06.0/0000\:03\:00.0/0000\:04\:00.0/modalias
pci:v000014E4d0000164Csv00001014sd00000342bc02sc00i00

一方、各ドライバは、サポートするデバイスの modalias 情報を持ちます。下記の alias: 部分です。

# modinfo bnx2
filename:       /lib/modules/2.6.32-71.el6.x86_64/kernel/drivers/net/bnx2.ko
firmware:       bnx2/bnx2-rv2p-09ax-5.0.0.j10.fw
firmware:       bnx2/bnx2-rv2p-09-5.0.0.j10.fw
firmware:       bnx2/bnx2-mips-09-5.0.0.j15.fw
firmware:       bnx2/bnx2-rv2p-06-5.0.0.j3.fw
firmware:       bnx2/bnx2-mips-06-5.0.0.j6.fw
version:        2.0.8-j15
license:        GPL
description:    Broadcom NetXtreme II BCM5706/5708/5709/5716 Driver
author:         Michael Chan <mchan@broadcom.com>
srcversion:     8CDA41B3E0DF70A112FBA80
alias:          pci:v000014E4d0000163Csv*sd*bc*sc*i*
alias:          pci:v000014E4d0000163Bsv*sd*bc*sc*i*
alias:          pci:v000014E4d0000163Asv*sd*bc*sc*i*
alias:          pci:v000014E4d00001639sv*sd*bc*sc*i*
alias:          pci:v000014E4d000016ACsv*sd*bc*sc*i*
alias:          pci:v000014E4d000016AAsv*sd*bc*sc*i*
alias:          pci:v000014E4d000016AAsv0000103Csd00003102bc*sc*i*
alias:          pci:v000014E4d0000164Csv*sd*bc*sc*i*
alias:          pci:v000014E4d0000164Asv*sd*bc*sc*i*
alias:          pci:v000014E4d0000164Asv0000103Csd00003106bc*sc*i*
alias:          pci:v000014E4d0000164Asv0000103Csd00003101bc*sc*i*
depends:
vermagic:       2.6.32-71.el6.x86_64 SMP mod_unload modversions
parm:           disable_msi:Disable Message Signaled Interrupt (MSI) (int)

これらの情報は、depmod がまとめて /lib/modules/$(uname -r)/modules.alias に記録しており、「modporbe 」を実行すると modules.alias から必要なドライバを判別して自動的にロードします。/lib/udev/rules.d/80-drivers.rules では、Kernel が udev に通知した modalias を使って modprobe しています。udev に勝手にロードしてほしくないドライバは、/etc/modprobe.d/blacklist.conf 内に blacklist オプションで指定しておきます。

KVM に関連する iptables

/etc/sysconfig/iptables に下記のエントリが勝手に追加される場合があります。

-A FORWARD -m physdev --physdev-is-bridged -j ACCEPT

これは、ブリッジデバイスを出入りするパケットに対する iptables の設定で、KVM の仮想ネットワークを意識した設定です。(詳細は、iptablesのmanページをどうぞ。)

なのですが、同時に、/etc/sysctl.conf にデフォルトで下記が設定されます。

# Disable netfilter on bridges.
net.bridge.bridge-nf-call-ip6tables = 0
net.bridge.bridge-nf-call-iptables = 0
net.bridge.bridge-nf-call-arptables = 0

これは、ブリッジデバイスを出入りするパケットに対する iptables の適用を行わないという設定で、前述の iptables の設定が無意味になります。結論としては、iptables の設定が勝手に追加されるという動きがおかしい、ということで、RHEL6.1 では前述の iptables の設定追加はなくなる予定です。こちらの Bugzilla を参照。

Upstart

カーネルが最初に起動するプロセス /sbin/init の動きがそっくり新しくなっています。init に関連するコマンドは upstart パッケージが提供します。

# rpm -ql upstart
/etc/dbus-1/system.d/Upstart.conf
/etc/init
/etc/init/init-system-dbus.conf
/sbin/halt
/sbin/init
/sbin/initctl
/sbin/poweroff
/sbin/reboot
/sbin/reload
/sbin/restart
/sbin/runlevel
/sbin/shutdown
/sbin/start
/sbin/status
/sbin/stop
/sbin/telinit
(以下省略)

もともと init は、ジョブコントローラ的な性質がありました。たとえば、従来の init は、/etc/inittab で定義された「ジョブ」を「ランレベルの変更」という「イベント」に応じて実行します。実行中のジョブが異常終了すると再実行する(respawn)というジョブトラッキングも行います。
Upstart はより高度なイベントベースのジョブコントローラとして init を再実装したものとみることができます。「任意の文字列で表されるイベント」に応じて、事前に定義したさまざまなジョブを実行・停止するという機能を持ちます。

事前定義された主なイベントは次のとおりです。

イベント 説明
startup init の起動時に発生
starting の起動を開始した際に発生
started の起動が完了した際に発生
stopping の停止を開始した際に発生
stopped の停止が完了した際に発生
runlevel X telinit コマンドでランレベルを変更した際に発生

これ以外にも任意の文字列をユーザ定義イベントとして使用可能です。次のコマンドでユーザレベルでイベントを発生することもできます。

# initctl emit <イベント>

イベントに反応するジョブの定義は、/etc/init/ 以下の .conf に記載します。1 つのジョブは、基本的には 1 つのプロセスに対応します。ジョブを停止する際は、該当のプロセスに TERM シグナルを送って、それでも停止しなければ KILL シグナルを送ります。また、イベントに反応する以外に、次のコマンドで強制的にジョブを開始・停止することも可能です。状態確認なども同様に。

# initctl start <ジョブ>
# initctl stop <ジョブ>
# initctl restart <ジョブ>
# initctl reload <ジョブ>
# initctl status <ジョブ>
# initctl list

ジョブ定義の詳細は、init(5)の man ページ(英語版)を参照してください。ここでは、デフォルトで用意されたジョブをみながら起動時の処理の流れを確認します。

まず、init 起動時に発生する startup イベントに反応して、rcS ジョブが走ります。(下記の日本語コメントは筆者が追記)

# cat /etc/init/rcS.conf
# rcS - runlevel compatibility
#
# This task runs the old sysv-rc startup scripts.

start on startup      # startup イベントに反応して起動
stop on runlevel      # 実行中に runlevel イベントが発生するとジョブの実行を中止
task                  # 1回実行したら終わりの task 型のジョブ(exit 0 で終了したら respawn はしない)

console output
exec /etc/rc.d/rc.sysinit     # /etc/rc.d/rc.sysinit をジョブとして実行する

post-stop script              # ジョブの実行が終わって "stopped rcS" イベントを発生した後にこのスクリプトを実行
        if [ "$UPSTART_EVENTS" = "startup" ]; then
                [ -f /etc/inittab ] && runlevel=$(/bin/awk -F ':' '$3 == "initdefault" && $1 !~ "^#" { print $2 }' /etc/inittab)
                [ -z "$runlevel" ] && runlevel="3"
                for t in $(cat /proc/cmdline); do
                        case $t in
                                -s|single|S|s) runlevel="S" ;;
                                [1-9])       runlevel="$t" ;;
                        esac
                done
                exec telinit $runlevel
         # telinit コマンドで "runlevel X" イベントを発生(X は /etc/inittab もしくはカーネルオプションで指定)
        fi
end script

ここで rc.sysinit が実行されます。post-stop script で発生する "runlevel X" イベントに反応するのは、rc ジョブです。

# cat /etc/init/rc.conf
# rc - System V runlevel compatibility
#
# This task runs the old sysv-rc runlevel scripts.  It
# is usually started by the telinit compatibility wrapper.

start on runlevel [0123456]   # runlevel X イベントに反応してジョブを開始
stop on runlevel [!$RUNLEVEL]    # 異なる runlevel のイベントが発生したらジョブを中止
                                 # (RUNLEVEL 環境変数は telinit コマンドが設定している。)
task                             # 1回実行したら終わりの task 型のジョブ(exit 0 で終了したら respawn はしない)

export RUNLEVEL
console output
exec /etc/rc.d/rc $RUNLEVEL      
   # /etc/rc.d/rc X を実行。ここで、昔ながらの runlevel に応じた init.d スクリプトが実行される

rc ジョブが完了して、"stopped rc" イベントが発生すると、これに反応して、start-ttys ジョブが実行されます。

# cat /etc/init/start-ttys.conf
#
# This service starts the configured number of gettys.

start on stopped rc RUNLEVEL=[2345]     # "stopped rc" イベントに反応してジョブを開始
env ACTIVE_CONSOLES=/dev/tty[1-6]
env X_TTY=/dev/tty1
task                                    # 1回実行したら終わりの task 型のジョブ(exit 0 で終了したら respawn はしない
script
        . /etc/sysconfig/init
        for tty in $(echo $ACTIVE_CONSOLES) ; do
                [ "$RUNLEVEL" = "5" -a "$tty" = "$X_TTY" ] && continue
                initctl start tty TTY=$tty    # "tty" ジョブを実行
        done
end script

これは tty ジョブを実行するためのラッパーでした。tty ジョブの実体は次のとおり。

# cat /etc/init/tty.conf
# tty - getty
#
# This service maintains a getty on the sepcified device.

stop on runlevel [016]

respawn           # 停止したら即座に respawn
instance $TTY        # 同名のジョブを複数実行する際は、個別に inistance 名を設定します。
exec /sbin/mingetty $TTY     # migetty を起動

これで migetty が respawn 指定で起動します。

従来 /etc/inittab に記載されていた内容がきれいにジョブに置き換えられていることが分かります。現在は、旧来の init との互換性を考えて、このようなジョブ定義がなされていますが、将来的には、起動処理を高速化するために、より並列度の高いジョブ実行ができるように定義が変わっていくと予想されます。

KVM ゲスト OS のシリアルコンソール

RHEL60 を KVM ゲストとする際に、virsh console で仮想シリアル接続するための設定です。

まず、GRUB の画面出力と Kernel メッセージの出力先を仮想シリアル端末に設定します。

/etc/grub.conf

# grub.conf generated by anaconda
#
# Note that you do not have to rerun grub after making changes to this file
# NOTICE:  You have a /boot partition.  This means that
#          all kernel and initrd paths are relative to /boot/, eg.
#          root (hd0,0)
#          kernel /vmlinuz-version ro root=/dev/vda2
#          initrd /initrd-[generic-]version.img
#boot=/dev/vda
default=0
timeout=5
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1    # GRUB の出力先を
terminal --timeout=5 serial console                                 # 仮想シリアルに設定
title Red Hat Enterprise Linux (2.6.32-71.el6.x86_64)
        root (hd0,0)
#        kernel /vmlinuz-2.6.32-71.el6.x86_64 ro root=UUID=807c23e0-693b-46bb-9485-7db5d665e303 rd_NO_LUKS rd_NO_LVM rd_NO_MD rd_NO_DM LANG=ja_JP.UTF-8 KEYBOARDTYPE=pc KEYTABLE=jp106 crashkernel=auto rhgb quiet
        kernel /vmlinuz-2.6.32-71.el6.x86_64 ro root=UUID=807c23e0-693b-46bb-9485-7db5d665e303 rd_NO_LUKS rd_NO_LVM rd_NO_MD rd_NO_DM LANG=ja_JP.UTF-8 KEYBOARDTYPE=pc KEYTABLE=jp106 crashkernel=auto console=ttyS0,115200n8 console=tty0
        initrd /initramfs-2.6.32-71.el6.x86_64.img
# ↑ Kernel メッセージの出力先を仮想シリアルに設定

続いて、仮想シリアルコンソールからのログインを受け付けます。先に説明した upstart による mingetty 起動スクリプトに agetty の起動を追加します。

/etc/init/start-ttys.conf

#
# This service starts the configured number of gettys.

start on stopped rc RUNLEVEL=[2345]

env ACTIVE_CONSOLES=/dev/tty[1-6]
env X_TTY=/dev/tty1
task
script
        . /etc/sysconfig/init
        for tty in $(echo $ACTIVE_CONSOLES) ; do
                [ "$RUNLEVEL" = "5" -a "$tty" = "$X_TTY" ] && continue
                initctl start tty TTY=$tty
        done
        initctl start serial DEV=ttyS0 SPEED=115200   # 仮想シリアルコンソールからのログインを受け付け

end script

loop デバイスの追加方法

/etc/udev/makedev.d/50-udev.nodes が無くなっているので道に迷った気分になりますが・・・。まず、デフォルトの loop0 〜 loop7 は、50-udev.nodes ではなく、start_udev の中で直接作成されるようになっています。

/sbin/start_udev から抜粋

        for i in 0 1 2 3 4 5 6 7; do
                [ -b /dev/loop$i ] || /bin/mknod -m 0640 /dev/loop$i b 7 $i
                /bin/chown root:disk /dev/loop$i
        done

start_udev の中に 50-udev.nodes を参照して追加のデバイスを作成する仕組みは残っていますので、loop8 以降については、これまで同様に 50-udev.nodes で指定することができます。下記は、loop8 〜 loop511 を追加しています。

# for i in $(seq 8 511); do echo "loop$i" >> /etc/udev/makedev.d/50-udev.nodes

ただし、256 個以上の loop デバイスを作成する場合は、/etc/makedev.d/01linux-2.6.x で指定される最大値も修正しておきます。

/etc/makedev.d/01linux-2.6.x

#b $STORAGE               7   0  1 256 loop%d   # デフォルトは最大 256 個
b $STORAGE               7   0  1 2046 loop%d   # 最大 2046 個に変更