CUBE SUGAR CONTAINER

技術系のこと書きます。

Linux の Network Namespace と radvd / dnsmasq で IPv6 SLAAC (+RDNSS) を試す

今回は、Linux の Network Namespace と radvd / dnsmasq を使って IPv6 の SLAAC を試してみる。 IPv6 では、アドレスの自動設定にいくつかのやり方がある。 SLAAC というのは、そのひとつで RFC 4862 で定義されている IPv6 Stateless Address Autoconfiguration のことを指す。 SLAAC では ICMPv6 NDP (Neighbor Discovery Protocol) の RA (Router Advertisement) というメッセージでアドレスとデフォルトルートを設定する。 その上で、RFC 8415 で定義されている Stateless DHCPv6 というプロトコルを使って DNS や NTP サーバを設定する。 なお、現在では RFC 8106 で定義されている RDNSS というオプションを使うことで、RA 単独でも DNS サーバを設定することができる。

使った環境は次のとおり。

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.4 LTS"
$ uname -r
4.15.0-76-generic
$ dpkg -l | egrep "(radvd|dnsmasq|isc-dhcp-client)"
ii  dnsmasq                         2.79-1                              all          Small caching DNS proxy and DHCP/TFTP server
ii  dnsmasq-base                    2.79-1                              amd64        Small caching DNS proxy and DHCP/TFTP server
ii  isc-dhcp-client                 4.3.5-3ubuntu7.1                    amd64        DHCP client for automatically obtaining an IP address
ii  radvd                           1:2.16-3                            amd64        Router Advertisement Daemon

もくじ

作るネットワーク

用意するネットワークの物理的な構成は次のとおり。 白抜きになっている箱が Network Namespace を表している。 また、端点をもった線は veth インターフェイスを表している。

f:id:momijiame:20200208172938p:plain
ネットワークの物理的な構成

ネットワークの論理的な構成は次のとおり。

f:id:momijiame:20200208172451p:plain
ネットワークの論理的な構成

実験では host のアドレスやデフォルトルートを設定することになる。

下準備

下準備として、必要なパッケージをインストールしておく。

$ sudo apt-get -y install radvd dnsmasq isc-dhcp-client tcpdump

ネットワークを作る

まずは Network Namespace を作る。

$ sudo ip netns add host
$ sudo ip netns add router

そして、Network Namespace 同士をつなぐ veth インターフェイスを作る。

$ sudo ip link add ht-veth0 type veth peer name gw-veth0

作ったインターフェイスを Network Namespace に所属させる。

$ sudo ip link set ht-veth0 netns host
$ sudo ip link set gw-veth0 netns router

デフォルトでは EUI-64 を使ってアドレスの下位 64 ビットが生成されるため、わかりやすいように MAC アドレスを変更しておく。

$ sudo ip netns exec host ip link set dev ht-veth0 address 00:00:5E:00:53:01
$ sudo ip netns exec router ip link set dev gw-veth0 address 00:00:5E:00:53:02

veth インターフェイスの状態を UP に設定する。

$ sudo ip netns exec host ip link set ht-veth0 up
$ sudo ip netns exec router ip link set gw-veth0 up

router の方にリンクローカルアドレス (fe80::1 と、グローバルアドレスを模したドキュメンテーションアドレス (2001:db8::1) を付与しておく。

$ sudo ip netns exec router ip address add fe80::1/64 dev gw-veth0
$ sudo ip netns exec router ip address add 2001:db8::1/64 dev gw-veth0

次のようにアドレスが付与された。

$ sudo ip netns exec router ip address show gw-veth0
16: gw-veth0@if17: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:00:5e:00:53:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 2001:db8::1/64 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::1/64 scope link 
       valid_lft forever preferred_lft forever
    inet6 fe80::200:5eff:fe00:5302/64 scope link 
       valid_lft forever preferred_lft forever

あとは router の方で IPv6 のルーティングを有効にしておく。 ちなみに、このカーネルパラメータのフラグを立てておかないと radvd が起動しない。

$ sudo ip netns exec router sysctl -w net.ipv6.conf.all.forwarding=1

radvd

はじめに、radvd から試してみる。 このプログラムは ICMPv6 NDP の RA を送ることができる。

はじめに、radvd の設定ファイルを用意する。 次の設定で、プレフィックスとして 2001:db8::/64 を広告しつつ、デフォルトルートを 2001:db8::1 に向けることができる。

cat << 'EOF' > radvd.conf
interface gw-veth0 {

    # 定期的にルータ広告を送る
    AdvSendAdvert on;

    # SLAAC でアドレスの自動生成に使うプレフィックスを広告する
    prefix 2001:db8::/64 { };

};
EOF

通信を観察するために tcpdump をしかけておく。

$ sudo ip netns exec host tcpdump -tnlvv -i ht-veth0 ip6
tcpdump: listening on ht-veth0, link-type EN10MB (Ethernet), capture size 262144 bytes

準備ができたら、用意した設定ファイルを使って radvd を起動する。

$ sudo ip netns exec router radvd -C radvd.con

すると、次のように prefix オプションを含む RA メッセージが出る。

$ sudo ip netns exec host tcpdump -tnlvv -i ht-veth0 ip6
tcpdump: listening on ht-veth0, link-type EN10MB (Ethernet), capture size 262144 bytes
IP6 (flowlabel 0xa72b0, hlim 255, next-header ICMPv6 (58) payload length: 56) fe80::1 > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 56
    hop limit 64, Flags [none], pref medium, router lifetime 1800s, reachable time 0ms, retrans timer 0ms
      prefix info option (3), length 32 (4): 2001:db8::/64, Flags [onlink, auto], valid time 86400s, pref. time 14400s
        0x0000:  40c0 0001 5180 0000 3840 0000 0000 2001
        0x0010:  0db8 0000 0000 0000 0000 0000 0000
      source link-address option (1), length 8 (1): 00:00:5e:00:53:02
        0x0000:  0000 5e00 5302

host のインターフェイスは、RA を受信して prefix オプションを使ってアドレスを自動設定する。

$ sudo ip netns exec host ip address show dynamic ht-veth0
11: ht-veth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:00:5e:00:53:01 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 2001:db8::200:5eff:fe00:5301/64 scope global dynamic mngtmpaddr 
       valid_lft 86379sec preferred_lft 14379sec

また、同時にルータ広告を送ってきたリンクローカルアドレスにデフォルトルートを設定する。

$ sudo ip netns exec host ip -6 route show
2001:db8::/64 dev ht-veth0 proto kernel metric 256 expires 86388sec pref medium
fe80::/64 dev ht-veth0 proto kernel metric 256 pref medium
default via fe80::1 dev ht-veth0 proto ra metric 1024 expires 1788sec hoplimit 64 pref medium

RDNSS の設定を追加してみる

RA の基本的な動作が確認できたので、つづいては RDNSS の設定を追加してパケットを観察してみる。

radvd の設定ファイルに RDNSS の設定を追加する。 配布する DNS サーバのアドレスは 2001:db8::dead:beef に指定した。

$ cat << 'EOF' > radvd.conf
interface gw-veth0 {

    # 定期的にルータ広告を送る
    AdvSendAdvert on;

    # SLAAC でアドレスの自動生成に使うプレフィックスを広告する
    prefix 2001:db8::/64 { };

    # RDNSS (RFC 8106) で DNS サーバを広告する
    RDNSS 2001:db8::dead:beef { };
};
EOF

radvd のプロセスに SIGHUP を送って設定ファイルを読み直させる。

$ sudo kill -HUP $(cat /var/run/radvd.pid)

すると、次のとおり RDNSS オプションを含む RA が観察できた。

$ sudo ip netns exec host tcpdump -tnlvv -i ht-veth0 ip6
tcpdump: listening on ht-veth0, link-type EN10MB (Ethernet), capture size 262144 bytes

...(snip)...

IP6 (flowlabel 0xa72b0, hlim 255, next-header ICMPv6 (58) payload length: 80) fe80::1 > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 80
    hop limit 64, Flags [none], pref medium, router lifetime 1800s, reachable time 0ms, retrans timer 0ms
      prefix info option (3), length 32 (4): 2001:db8::/64, Flags [onlink, auto], valid time 86400s, pref. time 14400s
        0x0000:  40c0 0001 5180 0000 3840 0000 0000 2001
        0x0010:  0db8 0000 0000 0000 0000 0000 0000
      rdnss option (25), length 24 (3):  lifetime 600s, addr: 2001:db8::dead:beef
        0x0000:  0000 0000 0258 2001 0db8 0000 0000 0000
        0x0010:  0000 dead beef
      source link-address option (1), length 8 (1): 00:00:5e:00:53:02
        0x0000:  0000 5e00 5302

dnsmasq (RA + Stateless DHCPv6)

つづいては dnsmasq を使って RA + Stateless DHCPv6 のパターンを検証してみる。

どうやら dnsmasq には RA を送る機能もあるようなので、いったん radvd のプロセスは kill しておく。

$ sudo kill $(cat /var/run/radvd.pid)

あらためて tcpdump をしかけておく。

$ sudo ip netns exec host tcpdump -tnlvv -i ht-veth0 ip6

そして、dnsmasq を起動する。 --enable-ra オプションが RA を送る指定になっている。

$ sudo ip netns exec router dnsmasq \
  --enable-ra \
  --dhcp-range=::,constructor:gw-veth0,ra-stateless \
  --dhcp-option=option6:dns-server,[2001:db8::dead:beef] \
  --dhcp-option=option6:ntp-server,[2001:db8::dead:beef] \
  --no-daemon

準備ができたら isc-dhcp の dhclient を起動する。 -6 オプションと -S オプションを組み合わせることで IPv6 SLAAC のモードになる。

$ sudo ip netns exec host dhclient -6 -S ht-veth0

tcpdump のターミナルを確認すると、次のとおり RA と Stateless DHCPv6 のパケットがやり取りされていることがわかる。 なお、RA にはデフォルトで RDNSS オプションが付与されるらしい。 まあ、たしかに Stateless DHCPv6 と同じ DNS サーバのアドレスを配布するなら、オプションがあっても副作用はとくにないのかな?

$ sudo ip netns exec host tcpdump -tnlvv -i ht-veth0 ip6
tcpdump: listening on ht-veth0, link-type EN10MB (Ethernet), capture size 262144 bytes

... (snip) ...

IP6 (class 0xc0, flowlabel 0xa72b0, hlim 255, next-header ICMPv6 (58) payload length: 88) fe80::1 > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 88
    hop limit 64, Flags [other stateful], pref medium, router lifetime 1800s, reachable time 0ms, retrans timer 0ms
      prefix info option (3), length 32 (4): 2001:db8::/64, Flags [onlink, auto], valid time 3600s, pref. time 3600s
        0x0000:  40c0 0000 0e10 0000 0e10 0000 0000 2001
        0x0010:  0db8 0000 0000 0000 0000 0000 0000
      mtu option (5), length 8 (1):  1500
        0x0000:  0000 0000 05dc
      source link-address option (1), length 8 (1): 00:00:5e:00:53:02
        0x0000:  0000 5e00 5302
      rdnss option (25), length 24 (3):  lifetime 3600s, addr: 2001:db8::dead:beaf
        0x0000:  0000 0000 0e10 2001 0db8 0000 0000 0000
        0x0010:  0000 dead beaf

IP6 (flowlabel 0x5f40a, hlim 1, next-header UDP (17) payload length: 48) fe80::200:5eff:fe00:5301.546 > ff02::1:2.547: [bad udp cksum 0xafc9 -> 0x2ffe!] dhcp6 inf-req (xid=29221d (client-ID hwaddr/time type 1 time 634459916 00005e005301) (option-request DNS-server DNS-search-list Client-FQDN SNTP-servers) (elapsed-time 0))

IP6 (class 0xc0, flowlabel 0x48785, hlim 64, next-header UDP (17) payload length: 76) fe80::1.547 > fe80::200:5eff:fe00:5301.546: [bad udp cksum 0xaf61 -> 0x705b!] dhcp6 reply (xid=29221d (client-ID hwaddr/time type 1 time 634459916 00005e005301) (server-ID hwaddr/time type 1 time 634459172 00005e005302) (DNS-server 2001:db8::dead:beaf) (lifetime 3600))

めでたしめでたし。

参考文献

tools.ietf.org

tools.ietf.org

tools.ietf.org

linux.die.net

linux.die.net

linux.die.net

Linux で VXLAN を扱ってみる

久しぶりに VXLAN について調べたところ、カーネルの機能で VXLAN インターフェイスが作れるようになってたので試してみた。 ここでいう VXLAN というのは、RFC7348 で定義されている Virtual eXtensible Local Area Network というプロトコルを指す。 このプロトコルを使うと Layer 2 のトンネリングが実現できる。

使った環境は次の通り。

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.4 LTS"
$ uname -r
4.15.0-76-generic
$ dpkg -l | grep iproute2
ii  iproute2                             4.15.0-2ubuntu1                     amd64        networking and traffic control tools
$ lsmod | grep -i vxlan
vxlan                  57344  0
ip6_udp_tunnel         16384  1 vxlan
udp_tunnel             16384  1 vxlan

もくじ

ネットワークの構成

検証には Network Namespace と veth インターフェイスを使って作ったネットワークを用いる。

ネットワークの物理的な構成は次のとおり。

f:id:momijiame:20200208004155p:plain
物理的なネットワーク構成

ネットワークは、全部で 3 つの Network Namespace からできている。 真ん中の router はルータとして動作し、残りの ns1ns2 はホストとして動作する。 端点をもった線はインターフェイスのつながりを表している。 その中でも、緑の破線はトンネリングによって仮想的なつながりがあることを示している。

ネットワークの論理的な構成は次のとおり。

f:id:momijiame:20200208004242p:plain
論理的なネットワーク構成

ネットワークは、全部で 3 つのセグメントからできている。 192.0.2.0/24198.51.100.0/24 は、通常のルーティングをする。 対して、203.0.113.0/24 は VXLAN によって延伸されたブロードキャストドメインで動作する。

下準備

あらかじめ、必要なパッケージをひととおりインストールしておく。

$ sudo apt-get -y install iproute2 iputils-ping tcpdump

ネットワークを作る

まずは Network Namespace を用意する。

$ sudo ip netns add ns1
$ sudo ip netns add router
$ sudo ip netns add ns2

つづいて veth インターフェイスを用意する。

$ sudo ip link add ns1-veth0 type veth peer name gw-veth0
$ sudo ip link add ns2-veth0 type veth peer name gw-veth1

作成した veth インターフェイスを Network Namespace に所属させていく。

$ sudo ip link set ns1-veth0 netns ns1
$ sudo ip link set gw-veth0 netns router
$ sudo ip link set gw-veth1 netns router
$ sudo ip link set ns2-veth0 netns ns2

インターフェイスの状態を UP に設定する。

$ sudo ip netns exec ns1 ip link set ns1-veth0 up
$ sudo ip netns exec router ip link set gw-veth0 up
$ sudo ip netns exec router ip link set gw-veth1 up
$ sudo ip netns exec ns2 ip link set ns2-veth0 up

インターフェイスに IP アドレスを付与する。

$ sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0
$ sudo ip netns exec router ip address add 192.0.2.254/24 dev gw-veth0
$ sudo ip netns exec router ip address add 198.51.100.254/24 dev gw-veth1
$ sudo ip netns exec ns2 ip address add 198.51.100.1/24 dev ns2-veth0

ns1ns2 のデフォルトルートを router の IP アドレスに向ける。

$ sudo ip netns exec ns1 ip route add default via 192.0.2.254
$ sudo ip netns exec ns2 ip route add default via 198.51.100.254

rotuer がルータとして動作するようにカーネルパラメータを設定する。

$ sudo ip netns exec router sysctl net.ipv4.ip_forward=1

ひとまず、通常のルーティングが動作することを確認しておく。

$ sudo ip netns exec ns1 ping -c 3 198.51.100.1 -I 192.0.2.1
PING 198.51.100.1 (198.51.100.1) from 192.0.2.1 : 56(84) bytes of data.
64 bytes from 198.51.100.1: icmp_seq=1 ttl=63 time=0.217 ms
64 bytes from 198.51.100.1: icmp_seq=2 ttl=63 time=0.043 ms
64 bytes from 198.51.100.1: icmp_seq=3 ttl=63 time=0.191 ms

--- 198.51.100.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2026ms
rtt min/avg/max/mdev = 0.043/0.150/0.217/0.077 ms

VXLAN を設定する

つづいて、今回の主眼である VXLAN のインターフェイスを用意する。 VNI には 100 を使った。

まずは ns1 に VXLAN インターフェイスを作る。

$ sudo ip netns exec ns1 \
     ip link add ns1-vxlan0 \
     type vxlan \
     id 100 \
     remote 198.51.100.1 \
     dstport 4789 \
     dev ns1-veth0

つづいて ns2 にも VXLAN インターフェイスを作る。

$ sudo ip netns exec ns2 \
     ip link add ns2-vxlan0 \
     type vxlan \
     id 100 \
     remote 192.0.2.1 \
     dstport 4789 \
     dev ns2-veth0

あとは、作った VXLAN インターフェイスに IP アドレスを付与したら状態を UP に設定するだけ。

$ sudo ip netns exec ns1 ip link set ns1-vxlan0 up
$ sudo ip netns exec ns1 ip address add 203.0.113.1/24 dev ns1-vxlan0
$ sudo ip netns exec ns2 ip link set ns2-vxlan0 up
$ sudo ip netns exec ns2 ip address add 203.0.113.2/24 dev ns2-vxlan0

パケットキャプチャするために ns2 のインターフェイスに tcpdump をしかけておく。

$ sudo ip netns exec ns2 tcpdump -tnl -i ns2-veth0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ns2-veth0, link-type EN10MB (Ethernet), capture size 262144 bytes

ns1 から ns2 に向けて Ping を打つ。 指定する IP アドレスは VXLAN インターフェイスのものを使う。

$ sudo ip netns exec ns1 ping -c 3 203.0.113.2 -I 203.0.113.1
PING 203.0.113.2 (203.0.113.2) from 203.0.113.1 : 56(84) bytes of data.
64 bytes from 203.0.113.2: icmp_seq=1 ttl=64 time=0.541 ms
64 bytes from 203.0.113.2: icmp_seq=2 ttl=64 time=0.094 ms
64 bytes from 203.0.113.2: icmp_seq=3 ttl=64 time=0.074 ms

--- 203.0.113.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2010ms
rtt min/avg/max/mdev = 0.074/0.236/0.541/0.215 ms

ちゃんとトンネリングが成功して、Ping の疎通がある。

先ほどしかけた tcpdump にも、VXLAN のパケットがキャプチャされている。 VXLAN のペイロードになっているパケット (フレーム) も一度に確認できるようだ。

$ sudo ip netns exec ns2 tcpdump -tnl -i ns2-veth0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ns2-veth0, link-type EN10MB (Ethernet), capture size 262144 bytes
IP 192.0.2.1.52760 > 198.51.100.1.4789: VXLAN, flags [I] (0x08), vni 100
IP 203.0.113.1 > 203.0.113.2: ICMP echo request, id 9404, seq 1, length 64
IP 198.51.100.1.52760 > 192.0.2.1.4789: VXLAN, flags [I] (0x08), vni 100
IP 203.0.113.2 > 203.0.113.1: ICMP echo reply, id 9404, seq 1, length 64
IP 192.0.2.1.52760 > 198.51.100.1.4789: VXLAN, flags [I] (0x08), vni 100
IP 203.0.113.1 > 203.0.113.2: ICMP echo request, id 9404, seq 2, length 64
IP 198.51.100.1.52760 > 192.0.2.1.4789: VXLAN, flags [I] (0x08), vni 100
IP 203.0.113.2 > 203.0.113.1: ICMP echo reply, id 9404, seq 2, length 64
IP 192.0.2.1.52760 > 198.51.100.1.4789: VXLAN, flags [I] (0x08), vni 100
IP 203.0.113.1 > 203.0.113.2: ICMP echo request, id 9404, seq 3, length 64
IP 198.51.100.1.52760 > 192.0.2.1.4789: VXLAN, flags [I] (0x08), vni 100
IP 203.0.113.2 > 203.0.113.1: ICMP echo reply, id 9404, seq 3, length 64

VXLAN インターフェイスの方で tcpdump をかけると、もちろん ICMP しか観測できない。

$ sudo ip netns exec ns2 tcpdump -tnl -i ns2-vxlan0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ns2-vxlan0, link-type EN10MB (Ethernet), capture size 262144 bytes
IP 203.0.113.1 > 203.0.113.2: ICMP echo request, id 9408, seq 1, length 64
IP 203.0.113.2 > 203.0.113.1: ICMP echo reply, id 9408, seq 1, length 64
IP 203.0.113.1 > 203.0.113.2: ICMP echo request, id 9408, seq 2, length 64
IP 203.0.113.2 > 203.0.113.1: ICMP echo reply, id 9408, seq 2, length 64
IP 203.0.113.1 > 203.0.113.2: ICMP echo request, id 9408, seq 3, length 64
IP 203.0.113.2 > 203.0.113.1: ICMP echo reply, id 9408, seq 3, length 64

いじょう。

参考文献

tools.ietf.org

https://www.kernel.org/doc/Documentation/networking/vxlan.txt

備考

ネットワークの物理的な構成を図示するのに使った blockdiag の定義は次のとおり。

blockdiag {
  ns1-vxlan0 [shape = endpoint];
  ns1-veth0 [shape = minidiamond];
  gw-veth0 [shape = minidiamond];
  gw-veth1 [shape = minidiamond];
  ns2-veth0 [shape = minidiamond];
  ns2-vxlan0 [shape = endpoint];

  group ns1 {
    orientation = portrait;
    label = 'ns1';
    color = '#CCCCFF';
    shape = line;
    ns1-veth0;
    ns1-vxlan0;
  }

  group router {
    label = 'router';
    color = '#CCCCFF';
    shape = line;
    gw-veth0;
    gw-veth1;
  }

  group ns2 {
    orientation = portrait;
    label = 'ns2';
    color = '#CCCCFF';
    shape = line;
    ns2-veth0;
    ns2-vxlan0;
  }

  ns1-vxlan0 -- ns1-veth0 [style = dotted];
  ns1-veth0 -- gw-veth0;
  gw-veth1 -- ns2-veth0;
  ns2-veth0 -- ns2-vxlan0 [style = dotted];
  ns1-vxlan0 -- ns2-vxlan0 [color = '#77FF77', style = dashed];
}

ネットワークの論理的な構成を図示するのに使った nwdiag の定義は次のとおり。

nwdiag {

  network {
    address = '192.0.2.0/24';
    ns1[address = 'ns1-veth0, 192.0.2.1'];
    router[address = 'gw-veth0, 192.0.2.254'];
  }

  network {
    address = '198.51.100.0/24';
    router[address = 'gw-veth1, 198.51.100.254'];
    ns2[address = 'ns2-veth0, 198.51.100.1'];
  }

  network {
    address = '203.0.113.0/24';
    ns1[address = 'ns1-vxlan0, 203.0.113.1'];
    ns2[address = 'ns2-vxlan0, 203.0.113.2'];
  }
}

Lenovo ThinkPad E595 を買ってメモリとストレージを交換してみた

我が家には、10 年前に購入した、OS が Windows 7 のノートパソコンが 1 台あった。 活躍する機会はさほど多くないものの、EOL を迎える製品を使い続けるリスクを考えて、以前からリプレースの機会をうかがっていた 1。 今回は、その買いかえに関する備忘録について書いてみる。

もくじ

次の機種に求められる要件について

まず、購入するからには長く使いたい。 そのため、ハードウェアの要件については次のように定めた。

  • CPU は 4 コア以上
  • メモリは 8GB 以上
  • ストレージは SSD で 256GB 以上
  • ディスプレイの解像度はフル HD 以上

また、前述したノートパソコンを使っているのは自分自身ではない。 そのため、使っている本人に、ノートパソコンに求める要件をまずは確認した。 ヒアリングによって得られた意見は次のとおり。 すべて満たすのは難しくとも、上位の要件についてはなんとかしたい。

  • ないと困る
    • Microsoft Office がインストールされている
  • あると困る
    • 値段が高い
  • あればうれしい
    • 軽い
    • 光学ドライブがある

ただ、Microsoft Office を入れて上記のスペックを満たしたもの、となると大抵の製品は 10 万円をこえる。 これだと、「値段が高いと困る」という要件を満たすことができない。 そうした中で、なかなか機種が選定できずにいたものの、あるとき次の機種が目にとまった。

kakaku.com

これは、コストパフォーマンスを重視した ThinkPad の E シリーズで、15 インチのディスプレイを持ったモデル。 これをカスタマイズして、ハードウェアを最小構成 2 にした上で Microsoft Office をバンドルすると、5.7 万円で買えることがわかった 3

で、ここからが本題なんだけど、このモデルはメモリやストレージがオンボードになっていない。 また、公式通販でもさまざまな組み合わせでカスタマイズできるとおり、パーツの相性が特に厳しいということもないようだ。 そのため、Web を調べると、メモリやストレージを交換して使っている人が割と見つかる。 なるほど、であればこれに自分でパーツを入れかえて使えば安くあがるのでは?と考えて試算してみた。

適合するパーツを選ぶ

まず、メモリは DDR4-2400 の SO-DIMM が使える。 なお、DDR4 は基本的に上位互換があるので、似たような値段の DDR4-2666 を選んでも問題ない。 要件を上回る 16GB を買っても、今なら約 ¥8,000 ほどで手に入る。 デュアルチャネルを活かすために 8GB のモジュールを 2 枚買う。

続いて、ストレージとしてはファームファクタが M.2 2280 の NVMe SSD を選ぶ。 こちらも、要件を上回る 500GB のモデルを買っても、今なら約 ¥8,000 ほど。

なお、M.2 タイプの SSD は発熱がはげしいので、ただ読み書きのパフォーマンスが良いモデルを選べば良いわけではない。 ノートパソコンに関しては、デスクトップよりも排熱が問題になりやすい。 今回も、パフォーマンスは抑えめのモデルを選んだ上で、気休めに次のヒートシンクを貼り付けることにした。 ノートパソコンの場合、筐体の余裕から使えるヒートシンクの厚みが限られる点に注意が必要になる。 このヒートシンクであれば、サイドパネルを外すことでかなり薄くなるようだ。

長尾製作所 M.2 SSD用ヒートシンクカバーSS-M2S-HS02

長尾製作所 M.2 SSD用ヒートシンクカバーSS-M2S-HS02

  • 発売日: 2017/03/15
  • メディア: Personal Computers

なお、筐体には SATA の 2.5 インチスロットもある。 そのため、読み書きの速度はインターフェイス的にやや落ちる 4 ものの、こちらを選んでも問題ない。

ということで、次の要件を満たしつつ 7.5 万円ほどで買えることがわかった。 ただし、「軽い」と「光学ドライブがある」は満たせていない。

  • CPU は 4 コア 8 スレッド (Ryzen 5 3500U)
  • メモリは 16GB
  • ストレージは 500GB の NVMe SSD
  • バンドル版 の Microsoft Office 2019 (Home and Business)
  • ディスプレイの解像度はフル HD

ところで、この時点ではまだ ThinkPad に E495 というモデルがあることを知らなかった。 こちらは、同じシリーズの 14 インチで、E595 よりも重量が 350g ほど軽い。 筐体のサイズ以外には値段もほとんど変わらないので、「軽いとうれしい」を満たすために、今ならこちらを選んでいたと思う。

kakaku.com

購入した後の手順について

ということで、本体とパーツをひととおり購入した。 Lenovo のブランドになってからは、はじめての ThinkPad になる。

f:id:momijiame:20200125174635j:plain

リカバリ用の USB メモリを作る

はじめに、Lenovo ID を登録して、筐体のシリアル番号を自身のアカウントに紐づけておく。

www.lenovo.com

この作業をすると、自身のアカウントに登録された製品情報からリカバリ用のイメージが取得できるようになる。 そうして取得したイメージを使って、あらかじめリカバリ用の USB メモリを作っておく。 Web には Windows のリカバリメディアを使う方法を試している人もいるけど、こちらの方が完全に製品出荷時の状態にできるので良いと思う。 使う USB メモリは 16GB 以上のサイズがあれば、何を選んでも問題ない。

パーツを交換する

パーツを交換する前には、あらかじめバッテリーの給電を含めて完全に電源を落としておく。 それには、起動画面で Lenovo のロゴが出ているときに F1 キーを押して BIOS 画面に入る。 Config > Power > Disable built-in buttery を Disabled にして電源を完全に切る。

このモデルは、筐体の開け方にややクセがあって、裏側のネジをすべてゆるめただけでは底面のパネルが取れない。 サイドにある隙間にヘラなどを差し込んで、固定されているツメをぐるっと一周すべて外す必要がある。 YouTube なんかに、開け方の動画が上がってるので参考にすると良い。

f:id:momijiame:20200126004553j:plain

底面パネルを外すと、こんな感じ。

f:id:momijiame:20200126005009j:plain

あらかじめインストールされているメモリのモジュールを外して、購入したモジュールを差し込む。 そして、M.2 のスロットには、ヒートシンクを貼り付けた SSD を固定する。 デフォルトの 2.5 インチ HDD は、外すと 100g ほど軽量化になるけど、せっかくなのでそのままにした。

用意した USB メモリを使ってリカバリする

パーツの交換がおわったら、リカバリ用の USB メモリを挿した状態で起動して、また BIOS 画面に入る。 そして、Startup > Boot から起動するデバイスの順序でリカバリ用の USB メモリをもっとも上にした状態で F10 を押下して起動する。 すると、リカバリがはじまるのであとはしばらく待つだけ。

なお、注意点としてストレージの中に OS がインストールされているものが残っているとリカバリがうまくいかない。 そのため、リカバリツールでは思い切って既存のすべてのストレージを消去するオプションを選択する必要がある。 あるいは、パーツを交換する作業で既存のストレージを取り外してしまうのも手だと思う。

つかう

リカバリがおわったら、あとは Windows 10 をセットアップして使う。 なお、気になる人は、UEFI に対応した Memtest86+ を使ってメモリに初期不良がないか確かめておくと良いと思う。

www.memtest.org

はじめに、負荷をかけたときの M.2 SSD の発熱が気になっていたのでベンチマークした結果は次のとおり。 負荷をかけた状態で 50℃ いくかいかないか、負荷をかけない状態なら 38℃ 前後で落ち着いた。

f:id:momijiame:20200126032618j:plain

なお、Microsoft Office に関してはリカバリツールではインストールされない。 そのため、Microsoft にアカウントを作ってプロダクトキーを入力してメディアをダウンロードする必要がある。 Microsoft Office のプロダクトキーは、本体の取扱説明書なんかと一緒に入っているので捨てないように注意しよう。

www.office.com

リカバリ直後にインストールされるアプリケーションに関しては、日本のメーカーのように謎のソフトが山のように入る、ということはなかった。 デフォルトでノートンアンチウイルスが入る点は余計と感じたものの、アンインストールしてしまえば問題はない。

Lenovo Vantage という管理ソフトに関しては、なかなかよくできていると感じた。 このソフトを使うと、たとえばバッテリーの充電が自動で中止される閾値を設定できる。 リチウムイオンバッテリーは 50 ~ 75% くらい充電した状態で使うと、もっとも長持ちすると言われている。 反対に 0% や 100% 前後で使っているとバッテリーが劣化する原因になるので、この機能は地味にうれしかった。 また、ハードウェアに固有の部分、BIOS やドライバのアップデートがひとつのソフトウェアで管理できるようになっている。

いじょう。


  1. OS を Windows 10 にして使い続ける、という選択肢もなくはなかったもののの、すでにストレージを SSD に交換するなど延命措置をとった上でバッテリーなどのヘタりもはげしく先が見えていた

  2. メモリを 4GB にして、ストレージから SSD をなくして代わりに 500GB の HDD にする

  3. ThinkPad の公式通販は定期的に値段を上げたり下げたりキャンペーンをよくやるので、現時点 (2020/02/02) ではもう少し高くなる (6.3 万円)

  4. とはいえ、転送速度で 500MB/s と 1500MB/s の違いを体感できるか?というと、おそらく難しい

VirtualBox で仮想マシンが入れ子 (Nested Virtualization) できるようになった

先日リリースされた VirtualBox 6.0 からは AMD の CPU で、6.1 からは Intel の CPU で Nested Virtualization がサポートされた。 Nested Virtualization というのは、仮想マシンの中に仮想マシンを入れ子に作ることを指す。 ようするに、仮想マシンをマトリョーシカのようにする。 この機能は、すでに VMware や KVM といったハイパーバイザではサポートされていたものの、今回それが VirtualBox でも使えるようになったというわけ。 この機能があると、サーバ周りのインフラ系をやっている人たちは、検証環境が作りやすくなってうれしい。 ただし、この機能を実現するには、仮想マシンの中で CPU の仮想化支援機能 (Intel-VT / AMD-V) が有効になっている必要がある 1

VirtualBox 6.1 のリリースノート 2 を見ると、次のような記載がある。

Virtualization core: Support for nested hardware-virtualization on Intel CPUs (starting with 5th generation Core i, codename Broadwell), so far tested only with guest running VirtualBox

どうやら、Intel であれば第 5 世代 Core i 以降の CPU で仮想化支援機能を使った Nested Virtualization ができるようになったらしい。 このニュースは、個人的に感慨深いものだった。 というのも、次のチケットを見てもらいたい。

www.virtualbox.org

このチケットは、VirtualBox に Nested Virtualization の機能を要望したものになっている。 問題は、チケットが作成された日付で、見ると "Opened 11 years ago" とある。 つまり、11 年という歳月をこえて、ユーザに要望されてきた機能がついに実現したというわけ。 ちなみに、これまで開発側の反応はどうだったかというと、チケットには「便利だろうけど実装するの大変だから...」みたいなコメントがあった。 なお、この機能について自分で調べていた頃のブログを調べると、ポストした日付が 8 年前になっていた。

Mac で仮想マシンの入れ子 (Nested Virtualization) をする | CUBE SUGAR STORAGEmomijiame.tumblr.com

今回は、せっかくなので VirtualBox を使った Nested Virtualization を試してみる。 使った環境は次のとおり。

$ sw_vers       
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G2022
$ sysctl -a | grep brand_string
machdep.cpu.brand_string: Intel(R) Core(TM) m3-7Y32 CPU @ 1.10GHz
$ vagrant version | head -n 1
Installed Version: 2.2.7
$ vboxmanage | head -n 1
Oracle VM VirtualBox Command Line Management Interface Version 6.1.2

もくじ

下準備

はじめに、Homebrew を使って Vagrant と VirtualBox をインストールしておく。 もちろん、Vagrant を使わずに VirtualBox の GUI フロントエンドを使ってもかまわない。

$ brew cask install vagrant virtualbox

Vagrant + VirtualBox で仮想マシンを用意する (L1)

物理的なハードウェア上で直接動作する仮想化のことを L1 と呼ぶことがあるようだ。 ようするに、一般的な状況としての仮想マシンがこれ。 まずは L1 の仮想マシンとして Vagrant + VirtualBox を使って Ubuntu 18.04 LTS をインストールする。

仮想マシンのイメージをダウンロードしたら、設定ファイルを生成する。

$ vagrant box add ubuntu/bionic64
$ vagrant init ubuntu/bionic64

次のように Vagrant の設定ファイルができる。

$ head Vagrantfile 
# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
  # The most common configuration options are documented and commented below.
  # For a complete reference, please see the online documentation at

ここで、設定ファイルを編集する必要がある。 ポイントは最後の vb.customize に渡している引数の --nested-hw-virt on で、これがないと L1 の仮想マシンで CPU の仮想化支援機能が有効にならない。 あと、Nested Virtulization をするには、かなり処理のオーバーヘッドがあるので仮想マシンのリソースは多めに確保しておいた方が良い。

  config.vm.provider "virtualbox" do |vb|
    vb.cpus = "2"
    vb.memory = "2048"
    vb.customize ["modifyvm", :id, "--nested-hw-virt", "on"]
  end

なお、Vagrant ではなく VirtualBox の GUI フロントエンドを使って操作しているときは、仮想マシンの設定画面を開いて次の項目にチェックをつければ良い。

f:id:momijiame:20200202080627p:plain
VirtualBox で Nested Virtualization するのに必要な GUI 設定画面のチェック項目

仮想マシンを起動したらログインする。

$ vagrant up
$ vagrant ssh

これで L1 の仮想マシンとして Ubuntu 18.04 LTS が利用できるようになった。

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.3 LTS"
$ uname -r
4.15.0-72-generic

CPU に仮想化支援機能のフラグが立っていることを確認する

それでは、CPU に仮想化支援機能のフラグが立っていることを確認してみよう。 Linux では proc ファイルシステムの /proc/cpuinfo で CPU のフラグが確認できる。 今回使っているのは Intel の CPU なので "vmx" というフラグを探す。

$ grep vmx /proc/cpuinfo
flags    : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq vmx ssse3 cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti tpr_shadow flexpriority fsgsbase avx2 invpcid rdseed clflushopt md_clear flush_l1d
flags    : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq vmx ssse3 cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti tpr_shadow flexpriority fsgsbase avx2 invpcid rdseed clflushopt md_clear flush_l1d

ちゃんと "vmx" フラグが立っていることがわかった。

仮想マシンの中に Vagrant + Libvirt (KVM) で仮想マシンを作る (L2)

続いては、L1 の仮想マシンの中に、さらに仮想マシンを作る。 先ほどのチケットには L1 / L2 共に VirtualBox を使った検証しかしていない、とあった。 そこで、せっかくなので L2 に KVM を使っても動くのかどうか調べてみることにした。 使う環境としては Libvirt 経由で KVM を Vagrant から扱う。

まずは必要なパッケージをインストールしておく。

$ sudo apt-get update
$ sudo apt-get -y install vagrant-libvirt qemu-kvm libvirt-bin gawk

KVM が使える状態になっていることを kvm-ok コマンドや、カーネルモジュールがロードされていることから確認する。

$ kvm-ok
INFO: /dev/kvm exists
KVM acceleration can be used
$ lsmod | grep kvm
kvm_intel             217088  0
kvm                   610304  1 kvm_intel
irqbypass              16384  1 kvm

現在のユーザを libvirt および kvm グループに参加させる。

$ sudo usermod -aG libvirt,kvm $(who am i | awk '{print $1}')

ここで、いったん L1 の仮想マシンを再起動しておく。

$ exit
$ vagrant reload

そして、もう一度 L1 の仮想マシンにログインする。

$ vagrant ssh

L2 の仮想マシンとしては、違いがわかりやすいように CentOS 7 を使うことにした。 次のようにして仮想マシンを起動する。

$ vagrant box add centos/7 --provider=libvirt
$ vagrant init centos/7
$ vagrant up

ちなみに、前述したとおり Nested Virtualization はオーバーヘッドが大きいので、この作業には大変に時間がかかる。 作業の進捗状況を確認したいときは、次のようにして仮想マシンのコンソールを取って見ると良い。

$ virsh list
$ virsh console <name>

仮想マシンが起動したら、ログインする。

$ vagrant ssh

確認すると、ちゃんと CentOS 7 が動作している。 これで、macOS / Ubuntu 18.04 LTS / CentOS 7 という仮想マシンのマトリョーシカが完成した。

$ cat /etc/redhat-release 
CentOS Linux release 7.6.1810 (Core) 
$ uname -r
3.10.0-957.12.2.el7.x86_64

なんとも感慨深い。

OpenStack 実践ガイド (impress top gear)

OpenStack 実践ガイド (impress top gear)

  • 作者:古賀 政純
  • 出版社/メーカー: インプレス
  • 発売日: 2016/08/25
  • メディア: 単行本(ソフトカバー)


  1. 完全仮想化をサポートしたハイパーバイザ (Xen など) であれば、その限りではないものの遅い

  2. https://www.virtualbox.org/wiki/Changelog-6.1

Ubuntu 18.04 LTS で Sphinx の PDF をビルドする

今回は Ubuntu 18.04 LTS を使って、Sphinx の PDF をビルドする方法について。

使った環境は次のとおり。

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.3 LTS"
$ uname -r
4.15.0-72-generic

下準備

Sphinx は TeX を使って PDF をビルドするんだけど、TeX Live のパッケージはかなり大きい。 そのため、APT でミラーリポジトリを使えるようにしておいた方が良い。

blog.amedama.jp

以下のコマンドを実行すればミラーリポジトリが有効になる。

$ sudo sed -i.bak -e 's%http://[^ ]\+%mirror://mirrors.ubuntu.com/mirrors.txt%g' /etc/apt/sources.list

Sphinx をインストールする

つづいて、Sphinx をインストールする。 最新のバージョンを使いたいので PIP からインストールすることにした。

まずは PIP をインストールする。

$ sudo apt update
$ sudo apt -y install \
      python3-pip \
      python3-setuptools \
      python3-wheel

PIP を使って、最新の Sphinx をインストールする。

$ sudo pip3 install sphinx

もし、バージョンが少し古くても構わないのであれば、次のように APT を使ってインストールすることもできる。

$ sudo apt -y install python3-sphinx

Sphinx のプロジェクトを作成する

sphinx-quickstart コマンドを使ってプロジェクトのテンプレートを作る。

$ sphinx-quickstart

ウィザード形式でプロジェクトの設定を聞かれるので答えていく。

Sphinx のプロジェクトに設定を追加する

日本語を含む PDF をビルドするときは、Sphinx の設定ファイル conf.py に最低限次の設定をした方が良いようだ。

language = 'ja'
latex_docclass = {'manual': 'jsbook'}

TeX Live をインストールする

つづいて、PDF をビルドするのに必要な TeX Live の関連パッケージをインストールする。

$ sudo apt -y install \
   texlive-latex-recommended \
   texlive-latex-extra \
   texlive-fonts-recommended \
   texlive-fonts-extra \
   texlive-lang-japanese \
   texlive-lang-cjk \
   latexmk

あとからパッケージが足りなくてつらい思いをしたくないときは、次のようにしてすべてのパッケージを入れてしまっても良い。 ただし、インストールするのにめちゃくちゃ時間がかかる。

$ sudo apt -y install texlive-full

PDF をビルドする

あとは make コマンドを使って latexpdf ターゲットを実行するだけ。

$ make latexpdf

実行がおわったら、成果物が入るディレクトリに PDF のファイルができているはず。

$ file _build/latex/*.pdf
_build/latex/example.pdf: PDF document, version 1.5

いじょう。

参考文献

sphinx-users.jp

sphinx-users.jp

Ubuntu 18.04 LTS で利用できるフォントの一覧を得る

今回は、Ubuntu 18.04 LTS で利用できるフォントの一覧を得る方法について。 結論から先に述べると fc-list コマンドを使えば良い。

使った環境は次のとおり。 ちなみに、相当古い Ubuntu でも同じ方法が使えるみたい。

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.3 LTS"
$ uname -r
4.15.0-74-generic

もくじ

下準備

下準備として fontconfig パッケージをインストールしておく。

$ sudo apt -y install fontconfig

利用できるフォントの一覧を得る

準備ができたら fc-list コマンドを実行する。 すると、利用できるフォントと、そのパスが一覧で得られる。

$ fc-list
/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf: DejaVu Serif:style=Bold
/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf: DejaVu Sans Mono:style=Book
/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf: DejaVu Sans:style=Book
/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf: DejaVu Sans:style=Bold
/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf: DejaVu Sans Mono:style=Bold
/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf: DejaVu Serif:style=Book

フォントを追加してみる

試しに IPAex フォントを追加でインストールして増えることを確認してみよう。

$ sudo apt -y install fonts-ipaexfont

もう一度 fc-list コマンドを実行すると、ちゃんと IPAex フォントが増えていることがわかる。

$ fc-list
/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf: DejaVu Serif:style=Bold
/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf: DejaVu Sans Mono:style=Book
/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf: DejaVu Sans:style=Book
/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf: DejaVu Sans:style=Bold
/usr/share/fonts/opentype/ipaexfont-gothic/ipaexg.ttf: IPAexGothic,IPAexゴシック:style=Regular
/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf: DejaVu Sans Mono:style=Bold
/usr/share/fonts/opentype/ipaexfont-mincho/ipaexm.ttf: IPAexMincho,IPAex明朝:style=Regular
/usr/share/fonts/truetype/fonts-japanese-mincho.ttf: IPAexMincho,IPAex明朝:style=Regular
/usr/share/fonts/truetype/fonts-japanese-gothic.ttf: IPAexGothic,IPAexゴシック:style=Regular
/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf: DejaVu Serif:style=Book

いじょう。

Python: Optuna で決められた時間内で最適化する

今回は Optuna の便利な使い方について。 現行の Optuna (v0.19.0) には決められた時間内で可能な限り最適化したい、というニーズを満たす API が実装されている。

使った環境は次の通り。

$ sw_vers 
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G1012
$ python -V          
Python 3.7.5
$ pip list | grep -i optuna
optuna            0.19.0

下準備

まずは Optuna と Scikit-learn をインストールしておく。

$ pip install optuna scikit-learn

決められた時間内で最適化するサンプルコード

以下が決められた時間内で可能な限り最適化するサンプルコード。 実現するには Study#optimize()n_trials の代わりに timeout オプションを指定する。 渡す値は最適化に使う秒数になっており、以下では 60 秒を指定している。 サンプルコードでは、RandomForest で乳がんデータセットを 5-Fold Stratified CV するときのハイパーパラメータを探索している。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import optuna
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import cross_validate
from sklearn import datasets


class Objective:
    """目的関数に相当するクラス"""

    def __init__(self, X, y):
        self.X = X
        self.y = y

    def __call__(self, trial):
        """オブジェクトが呼び出されたときに呼ばれる特殊メソッド"""
        # RandomForest のパラメータを最適化してみる
        params = {
            'n_estimators': 100,
            'max_depth': trial.suggest_int('max_depth', 2, 32),
            'min_samples_leaf': trial.suggest_int('min_samples_leaf', 1, 16),
        }
        model = RandomForestClassifier(**params)
        # 5-Fold Stratified CV
        kf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
        scores = cross_validate(model,
                                X=self.X, y=self.y,
                                cv=kf,
                                # メトリックは符号を反転したロジスティック損失
                                scoring='neg_log_loss',
                                n_jobs=-1)
        return scores['test_score'].mean()


def main():
    dataset = datasets.load_breast_cancer()
    X, y = dataset.data, dataset.target
    objective = Objective(X, y)
    # 関数を最大化するように最適化する
    study = optuna.create_study(direction='maximize')
    # 試行回数ではなく特定の時間内で最適化する
    study.optimize(objective, timeout=60)  # この例では 60 秒
    print('params:', study.best_params)


if __name__ == '__main__':
    main()

実行すると、次のようになる。

$ python optimeout.py
[I 2019-12-02 18:45:41,029] Finished trial#0 resulted in value: -0.1420495901047513. Current best value is -0.1420495901047513 with parameters: {'max_depth': 28, 'min_samples_leaf': 9}.
...
[I 2019-12-02 18:46:39,488] Finished trial#25 resulted in value: -0.11825965818535904. Current best value is -0.11397258384370261 with parameters: {'max_depth': 6, 'min_samples_leaf': 1}.
params: {'max_depth': 6, 'min_samples_leaf': 1}

上記を見ると、約 1 分で最適化が終了していることがわかる。

ぶっちゃけやってみるまで 1 回の試行にどれだけ時間がかかるかなんてわからないし、試行回数を指定するより便利だと思う。

Kaggleで勝つデータ分析の技術

Kaggleで勝つデータ分析の技術

  • 作者: 門脇大輔,阪田隆司,保坂桂佑,平松雄司
  • 出版社/メーカー: 技術評論社
  • 発売日: 2019/10/09
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る