Preparing chroot i686 environment in x86_64 host in CentOS 7

▼x86_64ホストの中に、chrootな i686 環境を用意する (CentOS 7)▼

x86_64ホストで、ネイティブモードでi686のプログラムをコンパイルしたい等の 場合、バーチャルマシンやコンテナを使わなくても i686 な chroot 環境があれば十分です。 ので、その準備方法。

$Keywords: i686 chroot, yum --installroot, rpm --root --initdb, --rebuilddb $


▼chroot ディレクトリを展開する

▼パッケージの引き込み

以下の例では、yum を使って chroot 内に必要なパッケージを引き込みます。 のでネットワーク接続環境が必要です。 ローカルで閉じたい場合は、適当な場所に .i686 用のDVDをマウントし、 yum --disablerepo=\* --enablerepo=c7-mediaに差し替えてください。
mkdir -p /chroot/i686

## RPMデータベース (/var/lib/rpm/)を初期化
rpm --root /chroot/i686 --initdb
## yumのリポジトリ設定 (/etc/yum.repos.d/) を追加
rpm --root /chroot/i686 -ivh centos-release-7-3.1611.el7.centos.i686.rpm	

## これでは.x86_64 のパッケージが選ばれてしまうので、
# yum --installroot=/chroot/i686 install rpm-build yum

## setarchで uname -m の値を変更して yum を起動する。
setarch i686 --32bit yum --installroot=/chroot/i686 install rpm-build yum sudo	

## /etc/passwd を移植する。セキュリティ目的でchrootする場合は
## 丸コピーせず、必要最小限のアカウントに絞ったほうが良い
cp -p /etc/{group,gshadow,passwd,shadow} /chroot/i686/etc/
cp -p /etc/sudoers /chroot/i686/etc/

## (nsswitch.conf で/etc/passwd以外を使うのなら、NSS関連ファイルも移植する)

## chroot内から外部ネットワークのDNS解決をできるようにする
cp -p /etc/resolv.conf /chroot/i686/etc/

## ここまで、1回やればよい設定

▼特殊mount

## ここから、再起動したらやり直す設定
## mount は、ホストの /etc/fstab に書いておけば自動マウントさせることもできる
mount -t proc -o nosuid,nodev,noexec proc /chroot/i686/proc
mount -t devtmpfs -o nosuid,strictatime devmpfs /chroot/i686/dev
mount -t devpts -o nosuid,noexec,gid=5,mode=620 devpts /chroot/i686/dev/pts	;# for sshd

mount --bind /home /chroot/i686/home	;# セキュリテイ目的の場合は行わない
#-------
#/etc/fstab に固定するなら、以下のように書く

/proc		/chroot/i686/proc	auto	bind 0 0
/dev		/chroot/i686/dev	auto	bind 0 0
/dev/pts	/chroot/i686/dev/pts	auto	bind 0 0
/home		/chroot/i686/home	auto	bind 0 0

#-------

▼chroot下でシェルを起動する

ここまで行えば、chrootコマンドで .i686環境のシェルが使えます。
host$ sudo setarch i686 --32bit chroot /chroot/i686
bash-4.2# uname -a
Linux TSPC330-VM1 3.10.0-514.el7.x86_64 #1 SMP Tue Nov 22 16:42:41 UTC 2016 i686 i686 i386 GNU/Linux
bash-4.2# _
「カーネルは x86_64 だが、ユーザーランドは全部 i686」な環境です。 glibc なんかはクロスコンパイル状態ではうまくコンパイルできませんが、 この chroot な環境ならコンパイルできます。

▼/etc/rpm/platformの細工は不要

CentOS 5, CentOS 6 時代は、chroot側の /etc/rpm/platform を細工しないと うまく .i686 のファイルが導入できなかったりしましたが、 CentOS 7 では setarch i686 で uname -m の値を変えるだけで うまくいくようです。

▼chroot内で sshd を動かす

chroot環境を使うたびに毎回 sudo setarch i686 --32bit chroot /chroot/i686 sudo -u $LOGNAME -s するのはだるいので、chroot内で sshd を動かします。

## ホスト側から sshd をインストールする
setarch i686 --32bit yum --installroot=/chroot/i686 install openssh-server
## ホストの ssh key を移植する
cp -p --preserve=all /etc/ssh/ssh_host* /chroot/i686/etc/ssh/
## ポート番号を変える。LXCやVMではないので、ネットワークはホストと共用のため
vi /chroot/i686/etc/ssh/sshd_config	;# Port 6022 などに変える

## chroot内のsshdを動かす
setarch i686 --32bit chroot /chroot/i686 /usr/sbin/sshd
## pidは /chroot/i686/var/run/sshd.pid に残る

## なお、chroot内では systemd関連コマンドは使えない。
## (/ と /proc/1/root の st_ino が違うと動作を拒否される)

▼systemd-nspawnでいいんじゃ?

systemd-nspawn を使えば、コンテナを作ってくれて /proc, /sys の隔離と自動マウント、hostname変更もやってくれます。らくちん ですが、 ので、本稿ではいちいちmountして陽にchrootする方法をとっています。

▼chroot内でプロンプトを変える

chroot環境で作業しているかどうかを判断する手段は意外に無いので、 プロンプトを変えてみます。 hostnameなどはホストと共用なのでアテにならない。 chroot側の ~/.bashrc に書き加えます:

# ~/.bashrc

#...

ROOTINO=`stat -c %i /`
if [ "$ROOTINO" != 2 ]; then
        PS1="[\u@\h-$ROOTINO \W]\$ "
fi
/ の inode が 2 以外であれば chroot環境なので、その値を PS1 に追加してみます。 あまり美しくありません。 具体的に chroot しているパス名を chroot環境下で取得する方法は無いようです というかできたらたぶんセキュリティーホール。

chroot環境を複数使う場合は、echo /chroot/i686 > /chroot/i686/etc/chrooted のような目印になるファイルを作って読み込んだほうが良いでしょう。

▼chroot内のsshdを自動起動にする

ホスト側での設定です。 まずは /etc/fstab で /proc 等の自動マウントを設定しておきます。 そのうえで、 systemd unit ファイルを新しくこしらえます。 以下のようなファイルを /etc/systemd/system/chroot-sshd@.service として 作成してから、

# # systemctl enable chroot-sshd@chrootdir-i686:6022.service # systemctl start chroot-sshd@chrootdir-i686:6022.service # -> invokes chroot /chrootdir/i686 sshd -p 6022 # [Unit] Description=chrooted OpenSSH server daemon Documentation=man:sshd(8) man:sshd_config(5) After=network.target sshd-keygen.service Wants=sshd-keygen.service [Service] Type=simple EnvironmentFile=/etc/sysconfig/sshd ExecStart=/bin/sh -c 'UNIT_I="%I"; d=/$${UNIT_I%:*}; if [ ! -d $$d ]; then echo $$d nonexistent; exit 1; fi; case "$$UNIT_I" in *:[0-9]*) p="-p $${UNIT_I#*:}"; esac; exec setarch i686 --32bit chroot $$d /usr/sbin/sshd -D $$p $OPTIONS' ExecReload=/bin/kill -HUP $MAINPID KillMode=process #Restart=on-failure #RestartSec=42s [Install] WantedBy=multi-user.target
sudo systemctl enable chroot-sshd@chroot-i686.service	;#自動起動の設定
sudo systemctl start  chroot-sshd@chroot-i686.service	;#起動する
sudo systemctl status chroot-sshd@chroot-i686.service	;#状況確認

chroot内では systemd は動かせないので、 プロセス管理はホスト側でやることになります。

sshd -D でフォアグラウンド起動しているので、PidFileの設定はありません。

ここまでやるなら、chroot先の特殊mountは /etc/fstab ではなく 専用の systemd unit (chroot-mount@.serviceとか) を作りたくなりますが、 本稿はそこまではやりません。


▼トラブルシューティング

▼yumでデータベースが壊れているエラーが出る

host$ sudo yum --installroot=/chroot/i686 install sudo
error: db5 error(5) from dbenv->open: Input/output error
error: cannot open Packages index using db5 - Input/output error (5)
error: cannot open Packages database in /chroot/i686/var/lib/rpm
CRITICAL:yum.main:

Error: rpmdb open failed

不思議なことに chroot 内から yum を起動するとまだ使えたりする。

対処法:

host$ sudo rpm --root=/chroot/i686 -v --rebuilddb
原因はよくわかりませんが、たまにホスト側から見ると /var/lib/rpm/ の Berkeley DBが壊れているように見えることがあるようです。

▼python-2.7.5-48.el7.src.rpmのビルドに失敗する

test_ioctl
test_ioctl (test.test_ioctl.IoctlTests) ... ok
test_ioctl_mutate (test.test_ioctl.IoctlTests) ... ok
test_ioctl_mutate_1024 (test.test_ioctl.IoctlTests) ... ok
test_ioctl_mutate_2048 (test.test_ioctl.IoctlTests) ... ok
test_ioctl_signed_unsigned_code_param (test.test_ioctl.IoctlTests) ... test test_ioctl failed -- Traceback (most recent call last):
File "/home/kabe/python-c7/BUILD/Python-2.7.5/Lib/test/test_ioctl.py", line 71, in test_ioctl_signed_unsigned_code_param
mfd, sfd = pty.openpty()
File "/home/kabe/python-c7/BUILD/Python-2.7.5/Lib/pty.py", line 29, in openpty
master_fd, slave_name = _open_terminal()
File "/home/kabe/python-c7/BUILD/Python-2.7.5/Lib/pty.py", line 70, in _open_terminal
raise os.error, 'out of pty devices'
OSError: out of pty devices

ERROR

======================================================================
ERROR: test_ioctl_signed_unsigned_code_param (test.test_ioctl.IoctlTests)
----------------------------------------------------------------------

対処: /proc, /dev, /dev/pts のマウントオプションを 確認してください。 nosuidやらstrictatimeやらが必要なものがあります。 「正しい」オプションでマウントされていれば、test_ioctlは スキップされます(解決になってない)。

▼ホスト側での script(1) が使えなくなった、xterm が使えなくなった、openpty(3)が失敗する

$ script typescript
script: openpty failed: Operation not permitted
Terminated

対処: /dev/pts のマウントオプションに gid=5 がついているか確認します。 /etc/fstab に bind マウントでなく、ベタ書きで

devpts /chroot/i686/dev/pts devpts nosuid,noexec 0 0
と書いて gid=5 を忘れると、 ホスト側の /dev/pts のマウントオプションも道連れで変更され、 root以外では openpty(3) (grantpt(3)) が正常に動かなくなります。

解説: glibc は、openpty(3) で仮想端末を割り当てる際、 マスター側は /dev/ptmx をオープンし、 スレーブ側はchown $LOGNAME tty /dev/pts/0 相当のことを行います。 が、/dev/pts が gid=5 (tty) 無しでマウントされていると、スレーブ側は st_uid, st_gid がユーザーのものになっていて ttyグループではなくなるため、 chown が失敗します (root以外では)。


kabe.sra-tohoku.co.jp