#1 kpatchとは?
ライブパッチ機能です。
カーネルを再起動することなく、パッチを適用することができます。
#2 検証環境
VMware Workstation 14 Player上の仮想マシンを使いました。
仮想マシンは、「最小限のインストール」->「開発ツール」を選択して作成しました。
##2.1 カーネル版数
[root@server ~]# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)
[root@server ~]# uname -r
3.10.0-957.el7.x86_64
##2.2 ディスク容量
15Gの空き領域が必要です。
空き領域は、~/.kpatchに作成するキャッシュで使用するようです。
#3 コマンドのインストール
kpatch-buildとkpatchをインストールします。
・kpatch-build:パッチからモジュールを作成するコマンド。
・kpatch:モジュールのロード/アンロードをするコマンド。
モジュールをロードすることで、パッチがカーネルに適用されます。
##3.1 パッケージのインストール
kpatch: dynamic kernel patchingのCentOS 7に記載されている手順にしたがって実行しました。
[root@server ~]# UNAME=$(uname -r)
[root@server ~]# yum -y install gcc kernel-devel-${UNAME%.*} elfutils elfutils-devel
[root@server ~]# yum -y install pesign yum-utils zlib-devel binutils-devel newt-devel python-devel perl-ExtUtils-Embed audit-libs audit-libs-devel numactl-devel pciutils-devel bison
[root@server ~]# yum-config-manager --enable debug
[root@server ~]# yum-builddep kernel-${UNAME%.*}
[root@server ~]# debuginfo-install kernel-${UNAME%.*}
[root@server ~]# yum -y install epel-release
[root@server ~]# yum -y install ccache
[root@server ~]# ccache --max-size=5G
Set cache size limit to 5.0 GB
[root@server ~]# yum -y install patchutils
##3.2 コマンドのインストール
[root@server ~]# git clone https://github.com/dynup/kpatch.git
Cloning into 'kpatch'...
remote: Enumerating objects: 41, done.
remote: Counting objects: 100% (41/41), done.
remote: Compressing objects: 100% (31/31), done.
remote: Total 6609 (delta 13), reused 28 (delta 10), pack-reused 6568
Receiving objects: 100% (6609/6609), 1.84 MiB | 267.00 KiB/s, done.
Resolving deltas: 100% (3932/3932), done.
[root@server ~]# cd kpatch/
[root@server kpatch]# make
make installを実行すると、kpatchコマンドとkpatch-buildコマンドがインストールされます。
[root@server kpatch]# make install
[root@server kpatch]# kpatch version
0.6.3
#4 パッチの作成
kernel-debuginfoパッケージをインストールすると、
下記ディレクトリにカーネルソースが展開されます。
[root@server kernel-3.10.0-957.el7]# pwd
/usr/src/debug/kernel-3.10.0-957.el7
[root@server kernel-3.10.0-957.el7]# ls -F
linux-3.10.0-957.el7.x86_64/
ディレクトリをコピーします。
[root@server kernel-3.10.0-957.el7]# cp -pr linux-3.10.0-957.el7.x86_64 linux-3.10.0-957.el7.x86_64.new
コピー先ディレクトリ(newが付いたディレクトリ)のソースに対して修正を実施します。
[root@server kernel-3.10.0-957.el7]# ls -F
linux-3.10.0-957.el7.x86_64/ linux-3.10.0-957.el7.x86_64.new/
net/ipv4/ping.cのping_init_sock関数にprintkを1行(下記★)追加します。
static int ping_init_sock(struct sock *sk)
{
struct net *net = sock_net(sk);
kgid_t group = current_egid();
struct group_info *group_info;
int i, j, count;
kgid_t low, high;
int ret = 0;
★ printk(KERN_ERR "TEST:ping_init_sock(1)");
inet_get_ping_group_range_net(net, &low, &high);
if (gid_lte(low, group) && gid_lte(group, high))
return 0;
-以下、略-
修正前後のping.cの差分を抽出します。抽出した差分がパッチとなります。
パッチは、test.patchというファイル名で保存します。
[root@server kernel-3.10.0-957.el7]# diff -Nur linux-3.10.0-957.el7.x86_64 linux-3.10.0-957.el7.x86_64.new > /root/test.patch
作成したパッチの中身を確認します。
[root@server kernel-3.10.0-957.el7]# cat /root/test.patch
diff -Nur linux-3.10.0-957.el7.x86_64/net/ipv4/ping.c linux-3.10.0-957.el7.x86_64.new/net/ipv4/ping.c
--- linux-3.10.0-957.el7.x86_64/net/ipv4/ping.c 2018-10-05 05:18:19.000000000 +0900
+++ linux-3.10.0-957.el7.x86_64.new/net/ipv4/ping.c 2019-05-05 10:32:32.825126680 +0900
@@ -211,6 +211,8 @@
kgid_t low, high;
int ret = 0;
+ printk(KERN_ERR "TEST:ping_init_sock(1)");
+
inet_get_ping_group_range_net(net, &low, &high);
if (gid_lte(low, group) && gid_lte(group, high))
return 0;
#5 モジュールの作成
kpatch-buildコマンドを使って、パッチからモジュールを作成します。
kpatch-buildコマンドの初回実行時は、結構時間(40分くらい)がかかりました。
[root@server ~]# ls
anaconda-ks.cfg kpatch test.patch
[root@server ~]# kpatch-build -t vmlinux test.patch
Fedora/Red Hat distribution detected
Downloading kernel source for 3.10.0-957.el7.x86_64
Unpacking kernel source
Testing patch file(s)
Reading special section data
Building original source
Building patched source
Extracting new and modified ELF sections
ping.o: changed function: ping_init_sock
Patched objects: vmlinux
Building patch module: livepatch-test.ko
SUCCESS
モジュール(livepatch-test.ko)が作成されたことがわかります。
[root@server ~]# ls -F
anaconda-ks.cfg kpatch/ livepatch-test.ko test.patch
#6 パッチの適用
モジュールをロードして、動作中のカーネルに対してパッチを適用してみます。
まだ何もモジュールがロードされていないことがわかります。
[root@server ~]# kpatch list
Loaded patch modules:
Installed patch modules:
モジュール(livepatch_test)をロードします。
[root@server ~]# kpatch load livepatch-test.ko
loading patch module: livepatch-test.ko
waiting (up to 15 seconds) for patch transition to complete...
transition complete (2 seconds)
モジュール(livepatch_test)がロードできたことがわかります。
[root@server ~]# kpatch list
Loaded patch modules:
livepatch_test [enabled]
Installed patch modules:
#7 実験結果
適用したパッチが動作するかどうか確認してみます。
printkの出力結果をジャーナルで確認するため、journalctlコマンドを実行します。
journalctlコマンドの使い方は、ここ(journalctl コマンドの使い方)を参照してください。
[root@server ~]# journalctl -f
もう1つターミナルを開きます。
デフォルトGWに対してpingを1回実行してみます。
pingコマンドの使い方は、ここ(pingコマンドの使い方)を参照してください。
[root@server ~]# ping -c 1 192.168.3.1
私の環境では、pingを1回実行しても、ジャーナルが出力されませんでした。
[root@server ~]# journalctl -f
5月 05 11:25:37 server kernel: TEST:ping_init_sock(1)
5月 05 11:25:37 server kernel: TEST:ping_init_sock(1)
1回実行した後、さらに、もう1回実行すると、2回分のジャーナルが出力されました。
何故そのような挙動になるのか理解できませんが、とりあえず、
動作中のカーネルに対して、再起動なしでパッチが適用できることは確認できました。
systemtapで確認すると、pingを1回実行すると、ping_init_sock関数が1回
呼ばれていました。
#8 その他
##8.1 モジュールのアンロード方法
モジュールのアンロードは、以下のようにコマンドを実行します。
[root@server ~]# kpatch list
Loaded patch modules:
livepatch_test [enabled]
Installed patch modules:
[root@server ~]# kpatch unload livepatch_test
disabling patch module: livepatch_test
waiting (up to 15 seconds) for patch transition to complete...
transition complete (1 seconds)
unloading patch module: livepatch_test
[root@server ~]# kpatch list
Loaded patch modules:
Installed patch modules:
##8.2 古いCentOSで実行した場合の挙動
今現在(2019/5/6)、CentOS7の最新はCentOS7.6です。
CentOS7.4でkpatchを試したのですが、下記エラーがでて、モジュールの作成ができませんでした。
[root@server ~]# kpatch-build --skip-gcc-check -t vmlinux test.patch
WARNING: Skipping gcc version matching check (not recommended)
Fedora/Red Hat distribution detected
Downloading kernel source for 3.10.0-693.el7.x86_64
ERROR: kpatch build failed. Check /root/.kpatch/build.log for more details.
上記エラーは、下記★行のdie関数で出力しています。
シェルスクリプトの一部を以下に引用しました。
[root@server ~]# less /usr/local/bin/kpatch-build
if [[ -z "$SRCRPM" ]]; then
if [[ "$DISTRO" = fedora ]]; then
wget -P "$TEMPDIR" "http://kojipkgs.fedoraproject.org/packages/kernel/$KVER/$KREL/src/kernel-$KVER-$KREL.src.rpm" 2>&1 | logger || die
else
command -v yumdownloader &>/dev/null || die "yumdownloader (yum-utils or dnf-utils) not installed"
★ yumdownloader --source --destdir "$TEMPDIR" "kernel$ALT-$KVER-$KREL" 2>&1 | logger || die
fi
SRCRPM="$TEMPDIR/kernel$ALT-$KVER-$KREL.src.rpm"
fi
以下は、★印の行を抜き出したものです。
yumdownloader --source --destdir "$TEMPDIR" "kernel$ALT-$KVER-$KREL" 2>&1 | logger || die
sourceは、バイナリRPMではなく、ソースRPMをダウンロードするオプションです。
destdirは、ダウンロードするRPMの保存場所(/root/.kpatch/tmp)を指定するオプションです。
上記1行は、以下のように展開されます。
yumdownloader --source --destdir /root/.kpatch/tmp kernel-3.10.0-693.el7 2>&1 | logger || die
yumdownloaderを単独で実行すると、以下のように「ソースRPMがない」という結果になりました。
[root@server ~]# yumdownloader --source --destdir /root/.kpatch/tmp kernel-3.10.0-693.el7
読み込んだプラグイン:fastestmirror, langpacks
Enabling epel-source repository
Loading mirror speeds from cached hostfile
* base: ftp.riken.jp
* epel: ftp.riken.jp
* epel-source: ftp.riken.jp
* extras: ftp.riken.jp
* updates: ftp.riken.jp
No source RPM found for kernel-3.10.0-693.el7.x86_64
Nothing to download
一方、最新(CentOS7.6)のソースRPMをダウンロードしてみると、成功しました。
つまり、yumdownloaderが参照するリポジトリには、最新のRPMしか存在しない、
ということのようです。
[root@server ~]# yumdownloader --source --destdir /tmp kernel-3.10.0-957.el7
読み込んだプラグイン:fastestmirror, langpacks
Enabling epel-source repository
Loading mirror speeds from cached hostfile
* base: ftp.riken.jp
* epel: ftp.riken.jp
* epel-source: ftp.riken.jp
* extras: ftp.riken.jp
* updates: ftp.riken.jp
No Presto metadata available for base-source
kernel-3.10.0-957.el7.src.rpm | 96 MB 00:06:05
[root@server ~]# ls -l /tmp/kernel-3.10.0-957.el7.src.rpm
-rw-r--r--. 1 root root 101032927 11月 13 00:31 /tmp/kernel-3.10.0-957.el7.src.rpm
そこで、リポジトリの定義を参照してみました。
ソースRPMのリポジトリは、/etc/yum.repos.d/CentOS-Sources.repo
に定義されています。
baseurlの部分だけを抜き出して、$releasever
を7に置き換えると、以下のようになります。
ブラウザでbaseurlを参照してみたのですが、CentOS7.4のソースRPMはありませんでした。
[base-source]
baseurl=http://vault.centos.org/centos/7/os/Source/
[updates-source]
baseurl=http://vault.centos.org/centos/7/updates/Source/
[extras-source]
baseurl=http://vault.centos.org/centos/7/extras/Source/
[centosplus-source]
baseurl=http://vault.centos.org/centos/7/centosplus/Source/
結論として、下記エラーがでて、モジュールの作成ができないのは、
kpatch-buildのバグではないかと思います。
ERROR: kpatch build failed. Check /root/.kpatch/build.log for more details.
kpatch-buildを実行しているCentOSの版数と同じ版数のソースRPMを
ダウンロードできるようにしなくてはいけないと思います。
$releasever
の部分を7
ではなく、7.4.1708
に置き換えることができれば
上手くいくと思うのだけど。う~ん、どうしよう。。。
#Z 参考情報
Red Hat Enterprise Linux 7.2 以降における kpatch のガイド
Linux live kernel patching with kpatch on CentOS 7
kpatch - live kernel patching