Alpine Linux

夢を見た。小用で目が覚め、時刻を確認した、0230Z。0300Z以降でお腹が空いたと言う センサーからの情報が有れば、そのまま今日のスタートになるんだけど、今回は偽で あった。追加寝。

デカになってた。ホシを尾行して、別府の杉乃井ホテルとか指宿の白水館とかへ行く。 決して、青森の酸ヶ湯温泉とか山形の白布温泉じゃない所が、後で考えると可笑しい。

刑事が正確に会社名や部署を宿帳に書くわけにもいかず、嘘を書いた。某ガードマン 会社のうん十年務めたご褒美の一人旅という事にした。偽名はありきたりの目立たない 山口と書いたかな。でも、これって、私文書偽造にならないのか? デカという職業柄 いらん事に気を遣う。

で、ホシはいつの間にか居なくなってたぞ。見張り失敗、デカ失格。そんな事を意に介さない で楽しむのが、亀有のコチカメ風。

のんびりしすぎて、チェックアウト時間ぎりぎり。帳場に行ったら、遠くから女将に 声をかけられた。山口さーん。

はーい、と言ったら、目が覚めた。0430Z。ちょいと寝坊したけど、今日も一日が始まるな。

Alpineを入れてみる

前回ドッカーでアルパインに出会った。

Alpine Linux で Docker イメージを劇的に小さくする を見ると、人気者のようですね。

ならば、単独で入れてみるかな。

AlpineLinuxにデスクトップ環境を導入する(VMwarePlayer)のように頑張っておられる方もいるようだし。

Small. Simple. Secure.の元、何種類かの コースが用意されてる。オイラーは、バニラを選んでみた。

ISOから起動したら、rootでログインして、setup-aplineすると、ネットはどうするとか、アカウントはどうするとか質問が出てくるんで、それに答えていく。diskに入れる時は、sysを選択する事。Alpine setup scriptsが、参考になるぞ。

終了する時は、poweroff とする事。これが、ちょっと 特殊で焦ったぞ。

alpine:~$ uname -a
Linux alp 4.9.29-0-hardened #1-Alpine SMP Mon May 22 19:30:59 GMT 2017 x86_64 Linux

dockerじゃないので、自前で用意したカーネルが動いている。なかなか新しめなやつです。 2cpuでちゃんと動いているぞ。

開発環境が、デフォで入らないのは、Windowsと一緒の事で、使う事だけのユーザーが99.9%を 占めているから、しょうがない。

自前でコンパイルをやりたい人は、 Creating an Alpine package あたりを参考にする。要は、

apk add alpine-sdk
apk add build-base gcc abuild binutils binutils-doc gcc-doc
apk add cmake cmake-doc extra-cmake-modules extra-cmake-modules-doc

ぐらいをやっておけって事だな。

シンプルに

Alpine Linux Init System

どこかの馬の骨みたいに、悪夢のシステムデーは採用しません。無理に採用するとdebianみたいに、 袂を分かつ事が起きるからです。 initの自由を目指すディストリビューション「Devuan 1.0」が公開

おかげで、/etcの中だけで、完結しています。unixはこうでなくちゃね。

alpine:/etc$ rc-status
Runlevel: default
 chronyd                                                           [  started  ]
 acpid                                                             [  started  ]
 crond                                                             [  started  ]
 sshd                                                              [  started  ]
Dynamic Runlevel: hotplugged
Dynamic Runlevel: needed/wanted
 sysfs                                                             [  started  ]
 fsck                                                              [  started  ]
 root                                                              [  started  ]
 localmount                                                        [  started  ]
 klogd                                                             [  started  ]
Dynamic Runlevel: manual

ああ、思い出した。sshdが使うホストキーは、どう作成してるんだろう? ちょいと 調べてみるかな。/etc/init.d/sshd

description="OpenBSD Secure Shell server"
description_checkconfig="Verify configuration file"
description_reload="Reload configuration"

checkconfig() {
    :
        if ! yesno "${SSHD_DISABLE_KEYGEN}"; then
                ssh-keygen -A || return 1
        fi
    :
}

start() {
        checkconfig || return 1

        ebegin "Starting ${SVCNAME}"
        start-stop-daemon --start --exec "${SSHD_BINARY}" \
            --pidfile "${SSHD_PIDFILE}" \
            -- ${SSHD_OPTS}
        eend $?
}

後は、懐かしい inittabと珍しい、rc.conf と conf.d/ それに runlevel/ ですかね。rc.confに組み込みlinuxの 一端が伺える。

# LINUX SPECIFIC OPTIONS

# This is the subsystem type. Valid options on Linux:
# ""               - nothing special
# "docker"         - Docker container manager
# "lxc"            - Linux Containers
# "openvz"         - Linux OpenVZ
# "prefix"         - Prefix
# "rkt"            - CoreOS container management system
# "uml"            - Usermode Linux
# "vserver"        - Linux vserver
# "systemd-nspawn" - Container created by the systemd-nspawn utility
# "xen0"           - Xen0 Domain
# "xenU"           - XenU Domain
# If this is commented out, automatic detection will be used.

ssh-keygen

上で出て来た、ssh-keygenを元祖 OpenBSDで見ておく。

まずは、マニュアルから、ssh-keygen(1)

     -A      For each of the key types (rsa1, rsa, dsa, ecdsa and ed25519) for
             which host keys do not exist, generate the host keys with the
             default key file path, an empty passphrase, default bits for the
             key type, and default comment.  This is used by /etc/rc to
             generate new host keys.

まとめて、ホストキーを作ってくれるとな。で、そのソースは、 /usr/src/usr.bin/ssh/ssh-keygen.c に有ったぞ。

static void
do_gen_all_hostkeys(struct passwd *pw)
{
        struct {
                char *key_type;
                char *key_type_display;
                char *path;
        } key_types[] = {
#ifdef WITH_OPENSSL
#ifdef WITH_SSH1
                { "rsa1", "RSA1", _PATH_HOST_KEY_FILE },
#endif /* WITH_SSH1 */
                { "rsa", "RSA" ,_PATH_HOST_RSA_KEY_FILE },
                { "dsa", "DSA", _PATH_HOST_DSA_KEY_FILE },
                { "ecdsa", "ECDSA",_PATH_HOST_ECDSA_KEY_FILE },
#endif /* WITH_OPENSSL */
                { "ed25519", "ED25519",_PATH_HOST_ED25519_KEY_FILE },
                { NULL, NULL, NULL }
        };
        :

ふーん、OPENSSLが定義されてると、ed25519ってのも定義されるんか。で、一応作成場所も 当たっておく。場所は、 pathnames.h に、定義されてた。

#define ETCDIR                          "/etc"
#define SSHDIR                          ETCDIR "/ssh"
#define _PATH_SSH_PIDDIR                "/var/run"

#define _PATH_SERVER_CONFIG_FILE        SSHDIR "/sshd_config"
#define _PATH_HOST_CONFIG_FILE          SSHDIR "/ssh_config"
#define _PATH_HOST_KEY_FILE             SSHDIR "/ssh_host_key"
#define _PATH_HOST_DSA_KEY_FILE         SSHDIR "/ssh_host_dsa_key"
#define _PATH_HOST_ECDSA_KEY_FILE       SSHDIR "/ssh_host_ecdsa_key"
#define _PATH_HOST_RSA_KEY_FILE         SSHDIR "/ssh_host_rsa_key"
#define _PATH_HOST_ED25519_KEY_FILE     SSHDIR "/ssh_host_ed25519_key"
#define _PATH_DH_MODULI                 ETCDIR "/moduli"

#define _PATH_SSH_PROGRAM               "/usr/bin/ssh"

丸裸にするのって、楽しいね。ああ、丸裸って書くと、ぐぐるの村八分にあうか。面倒な 世の中は嫌いだぞ。

apk index

/etc/apk/world ってファイルに、自分が故意に入れたパッケージ名が記憶されてる。

パッケージにどんなのが有るかWebで眺めるのもいいけど、手元でgrepとかやって、絞り込み たいな。何処かに、その為のインデックスが無いかな? ガサ入れしてみた。 それらしいのが有ったぞ。

alpine:/var/cache/apk$ ls
APKINDEX.6e97666e.tar.gz  APKINDEX.ed3a7416.tar.gz

2つ有るのは、メインと有志のと別々に管理してるからだな。展開してみると、 APKINDEXの方が求めるものだった。

C:Q1T4CHVvlDM7lBpgvS3XVJD9w6nEw=
P:ocaml
V:4.04.0-r1
A:x86_64
S:75797210
I:217706496
T:Main implementation of the Caml programming language
U:http://caml.inria.fr
L:LGPLv2
o:ocaml
m:Borys Zhukov <mp5@mp5.im>
t:1480778225
c:b6b2655272894633f0ef5a97a140d83d42841401
D:so:libc.musl-x86_64.so.1 so:libncursesw.so.6

C:Q1VRgYwhd85no/XFXclxTJouHIRoM=
P:libreoffice-lang-sq
   :

こんな形式のデータになっていた。P:と o:と、同じようなデータが記載されてるんで、 どちらを取り出すか迷ったけど、Pの方を抜き出して、ソートして眺めている。

alpine:~$ wc APK-index
     7961      7961    110239 APK-index
alpine:~$ fgrep -v -- -doc APK-index | fgrep -v -- -dev | wc
     5292      5292     71198

何も考えずにカウントすると、8000近い数値が出てくるけど、ドキュメントとかヘッダー類も 数えちゃってるので、水増しされてる。それらを取り除くと、約5300個のパッケージに なった。(パッケージによっては、何とかプラグインなんてのも独立させてる場合が有るので、 有効数は更に少なくなる)

じゃ、パッケージになってないのはどうする? その為に、初っ端に開発環境を放り込んだんだろ。有効利用しろよ。

まずはgaucheから

コンパイル環境を整えた後、真っ先にコンパイルしてみるのはgaucheだ。昔はコンパイル環境の テストにつかってたのはrubyだったけど、いつの間にか疎遠になってる。で、やってみると、

make[1]: Leaving directory '/home/sakae/Gauche-0.9.5/lib'
make[1]: Entering directory '/home/sakae/Gauche-0.9.5/ext'
(cd util; make default)
make[2]: Entering directory '/home/sakae/Gauche-0.9.5/ext/util'
../../src/gosh -ftest ../../src/precomp -e -P -o util--match ../../libsrc/util/match.scm
make[2]: *** [Makefile:25: util--match.c] Segmentation fault

見事にセグフォだわい。原因は大体予想がつくぞ。クリチカルな事をやってるGCで落ちてるはず。 組み込み用のlibc(libc.musl)じゃケア出来ていないのだろう。

alpine:~/Gauche-0.9.5/src$ ./gosh
Segmentation fault

gdbを入れて追ってみる。

(gdb) r main
Starting program: /home/sakae/Gauche-0.9.5/src/gosh main

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff78b62c2 in GC_find_limit_with_bound (p=0x555555759090 "\001",
    up=up@entry=0, bound=bound@entry=0x0) at os_dep.c:966
966                     GC_noop1((word)(*result));
(gdb) bt
#0  0x00007ffff78b62c2 in GC_find_limit_with_bound (p=0x555555759090 "\001",
    up=up@entry=0, bound=bound@entry=0x0) at os_dep.c:966
#1  0x00007ffff78b635e in GC_find_limit (p=<optimized out>, up=up@entry=0)
    at os_dep.c:978
#2  0x00007ffff78b63ce in GC_init_linux_data_start () at os_dep.c:463
#3  0x00007ffff78b4df0 in GC_init () at misc.c:1159
#4  0x0000555555555c86 in main (ac=2, av=0x7fffffffebe8) at main.c:592

残念な事に、予想が見事的中してしまった。(予想が的中して嬉しいのは、競馬ファンだけ、かな?) 潔く、撤退しましょ。gaucheの代わりに、chibi-schemeをコンパイルして入れた。こちらは、 自前のGCをやってるんで、素直に動いた。

難物のgdbはどうよ?

クリチカルなやつとしてgdbが有る。パッケージに有るので素直にそれを使えばいいんだけど、 自前でコンパイルする練習のためにやってみる。

make[3]: Leaving directory '/home/sakae/gdb-7.12.1/gdb'
g++ -g -O2   -I. -I. -I./common -I./config -DLOCALEDIR="\"/usr/local/share/locale\"" -DHAVE_CONFIG_H -I./../include/opcode -I./../opcodes/.. -I./../readline/.. -I./../zlib -I../bfd -I./../bfd -I./../include -I../libdecnumber -I./../libdecnumber -I./../intl -I./gnulib/import -Ibuild-gnulib/import    -Wall -Wpointer-arith -Wno-unused -Wunused-value -Wunused-function -Wno-switch -Wno-char-subscripts -Wempty-body -Wunused-but-set-parameter -Wunused-but-set-variable -Wno-sign-compare -Wno-write-strings -Wno-narrowing -Wformat-nonliteral  -c -o amd64-linux-nat.o -MT amd64-linux-nat.o -MMD -MP -MF .deps/amd64-linux-nat.Tpo amd64-linux-nat.c
amd64-linux-nat.c:27:23: fatal error: asm/prctl.h: No such file or directory
 #include <asm/prctl.h>
                       ^
compilation terminated.

やっぱりコンパイルが中止された。ヘッダーが見つからんって、毎度お馴染みのリナ意地悪 攻撃ですよ。何とか-devを見繕って入れなきゃいかんか脳。ヘッダーは別パッケージっている リナの流儀ってどうして生まれたのだろう?

一番に思いつくのは、DISK容量が貴重だったんで、万人が使う事は無いヘッダーはいらないって 事で、別にしたんだな。次に思いつくのは、悪い人が来て、勝手に悪さするプログラムを コンパイル、インストールするのを防ぐため。libcのヘッダーが無いと、ハロワすら表示 出来ませんからな。(この説、ちと根拠に乏しいか。まずは、コンパイル環境が有るって前提が 必要だからね)

でもまあ、コンパイル環境がデフォで入っていないって事からすると、まあ、リナは普通に 用意されたアプリを使うものですっていう、メーカーOSに倣っているんだな。

で、どうしよう? こういう時は、パッケージを入れた時に、どんなものが付属してきたか 調べるのがいいかな。上で挙げた、INDEXに依存情報が記載されてたな。

D:so:libc.musl-x86_64.so.1 so:libexpat.so.1 so:libncursesw.so.6 so:libpython2.7.
so.1.0 so:libreadline.so.6 so:libz.so.1

何種類かのライブラリィーが必要との事。対応をもう少し精密に調べてみたいな。Webに載ってるのかしらん? 当たってみよう。

package details (gdb)の、 Git repositoryを辿ると、FreeBSDで言う、Makefile相当が現れた。通称、APKBUILDって言う んだな。なんか、パッチを当ててるよ。それはそうと、目当ては、

makedepends="ncurses-dev expat-dev texinfo readline-dev python2-dev
	zlib-dev autoconf automake libtool linux-headers"

だな。一番怪しそう(必要と思われる)なのは、linux-headerか。大事になりそうなんで 止めておこう。それより、汎用性のありそうな、ncurses-devとかreadline-dev、zlib-dev ぐらいを入れておこう。(大事な事なので、何度でも言います。リナの意地悪に対抗して生きる術です)

busybox and kernel

アルパインの特徴、小さいを実現してる決定打、busyboxにちょっと対面しておくか。 ソースを取ってきて、コンパイルしてみる。

INSTALLの説明によれば、リナのカーネルを作る過程と似せてあるとな。

The BusyBox build process is similar to the Linux kernel build:

  make menuconfig     # This creates a file called ".config"
  make                # This creates the "busybox" executable
  make install        # or make CONFIG_PREFIX=/path/from/root install

menuconfigの代わりに、make defconfigすると、全部入りが出来るようだ。で、全部入りを やってみる。

alpine:~/busybox-1.21.0$ make
  CC      networking/ifconfig.o
In file included from include/libbb.h:40:0,
                 from networking/ifconfig.c:49:
/usr/include/sys/poll.h:1:2: warning: #warning redirecting incorrect #include <sys/poll.h> to <poll.h> [-Wcpp]
 #warning redirecting incorrect #include <sys/poll.h> to <poll.h>
  ^~~~~~~
networking/ifconfig.c:59:26: fatal error: net/if_slip.h: No such file or directory
 # include <net/if_slip.h>
                          ^
compilation terminated.

ははは、またヘッダー無いぞエラーだよ。リナでコンパイルする苦労の99%はヘッダー探しに 費やされるという事です。取り合えずなんで、net関係は無しよでコンパイルしたら

/usr/lib/gcc/x86_64-alpine-linux-musl/6.2.1/../../../../x86_64-alpine-linux-musl/bin/ld: cannot find -ldmalloc
collect2: error: ld returned 1 exit status
make: *** [Makefile:716: busybox_unstripped] Error 1

今の環境に、dmallocは無いと抜かす。jemallocならapkで入るんだけどね。取り合えず、 dmallocは無しの方向で、コンパイルを完了させた。簡単な試食。

alpine:~/busybox-1.21.0$ ./busybox du -sh
34.4M   .
alpine:~/busybox-1.21.0$ ./busybox ls -l busybox*
-rwxr-xr-x    1 sakae    nogroup     702624 May 29 15:50 busybox
-rwxr-xr-x    1 sakae    nogroup     819448 May 29 15:50 busybox_unstripped
-rw-r--r--    1 sakae    nogroup     728261 May 29 15:50 busybox_unstripped.map
-rw-r--r--    1 sakae    nogroup      27295 May 29 15:50 busybox_unstripped.out

busyboxは、リナに使われるコマンドの集大成をコンパクトにまとめたもの。手元に置いて、 参照するのがよかろう。余計な機能が省かれているので、見通しが良いぞ。

上でカーネルの作成なんて言葉を聞いたものだから、オイラーもやってみたくなった。 ネットを調べると、専用の道具を使うようだけど、昔ながらの方法も出てたので、やってみた。

カーネル/コンパイル/伝統的な方法

今動いてるカーネルの設定をそのまま使う方法が有るとな。/boot/configを持ってきて、 やってみたらエラー。ソースが新しいからかな。で、やっぱりmenuconfigしたよ。GUI(もどき)で、設定してくって苦痛だな。お勧めに従ってやってみたら、perlが必要とか。そんな事、 どの案内にも書いてなかったぞ。

で、20分ほどして、カーネルが出来上がった。今動いてるのよりも随分と太ったものになったぞ。その代わり、/lib/modulesの下に出来たのは小粒だった。

alpine:/lib/modules$ du -sh *
138.1M  4.4.59
320.0K  4.4.68-mine

異常に小さい。このままインストールすると二度と起動しなくなる予感がしたので、ここで止める。もっと練習が必要そうだけど、得る所が無さそうだな。もっと違う事を野郎。

docker in alpine linux

で、悪戯心を発揮して、wikiでdockerなんてのを検索したら、下記を発見。

Docker for Alpine

alpineを知ったのはdockerを経由してだ。前にも書いたけど、alpineはその小ささから注目を 浴び、ドッカー社に目を付けられた。そのお返しに、alpineがドッカーを飲み込んじゃえって 作戦。

ドッカーがパッケージになってるので、一発導入。そして、サービスの登録と起動。

alpine:~# apk add docker
(1/6) Installing libmnl (1.0.4-r0)
(2/6) Installing libnftnl-libs (1.0.7-r0)
(3/6) Installing iptables (1.6.0-r0)
(4/6) Installing xz (5.2.2-r1)
(5/6) Installing libseccomp (2.3.1-r0)
(6/6) Installing docker (1.12.6-r0)
Executing docker-1.12.6-r0.pre-install
Executing busybox-1.25.1-r0.trigger
OK: 972 MiB in 130 packages
alpine:~# rc-update add docker boot
 * service docker added to runlevel boot
alpine:~# service docker start
 * Caching service dependencies ...                                       [ ok ]
 * /var/log/docker.log: creating file
 * /var/log/docker.log: correcting mode
 * /var/log/docker.log: correcting owner
 * Starting docker ...                                                    [ ok ]

自分自身の分身をドッカーへ

alpine:~# docker pull alpine
Using default tag: latest
latest: Pulling from library/alpine
cfc728c1c558: Pull complete
Digest: sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96
Status: Downloaded newer image for alpine:latest
alpine:~# docker run -it alpine /bin/ash
/ # df
Filesystem           1K-blocks      Used Available Use% Mounted on
overlay                6593672   2361716   3877296  38% /
tmpfs                  2021904         0   2021904   0% /dev
tmpfs                  2021904         0   2021904   0% /sys/fs/cgroup
/dev/sda3              6593672   2361716   3877296  38% /etc/resolv.conf
/dev/sda3              6593672   2361716   3877296  38% /etc/hostname
/dev/sda3              6593672   2361716   3877296  38% /etc/hosts
shm                      65536         0     65536   0% /dev/shm
tmpfs                  2021904         0   2021904   0% /proc/latency_stats
tmpfs                  2021904         0   2021904   0% /proc/timer_list
tmpfs                  2021904         0   2021904   0% /proc/timer_stats
tmpfs                  2021904         0   2021904   0% /proc/sched_debug

次はウブをドッカーへ

alpine:~# docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
b6f892c0043b: Pull complete
55010f332b04: Pull complete
2955fb827c94: Pull complete
3deef3fcbd30: Pull complete
cf9722e506aa: Pull complete
Digest: sha256:382452f82a8bbd34443b2c727650af46aced0f94a44463c62a9848133ecb1aa8
Status: Downloaded newer image for ubuntu:latest
alpine:~# docker run -it ubuntu /bin/bash
root@9de8c49d509c:/# cat /etc/os-release
NAME="Ubuntu"
VERSION="16.04.2 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.2 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial
root@9de8c49d509c:/#

ウブの長期保守版も、2回の改定が行われているのね。

root@9de8c49d509c:/# df
Filesystem     1K-blocks    Used Available Use% Mounted on
overlay          6593672 2501836   3737176  41% /
tmpfs            2021904       0   2021904   0% /dev
tmpfs            2021904       0   2021904   0% /sys/fs/cgroup
/dev/sda3        6593672 2501836   3737176  41% /etc/hosts
shm                65536       0     65536   0% /dev/shm

オーバーレイって所に重ね合わせの法則が適用されるんか。

alpine:~# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              ebcd9d4fca80        3 days ago          117.9 MB
alpine              latest              02674b9cb179        8 days ago          3.984 MB

そして、それぞれのイメージの大小比較。圧倒的にalpneが小さいな。ドッカー社が目を 付けるのも頷けるよ。

dot.profile for alpine

alpine:~$ cat .profile
alias lv=less
alias updatedb='su -c "find / > /var/db/LOCATE"'
alias locate='cat /var/db/LOCATE | grep -v docker/overlay | egrep '
tmux

こんな初期ファイルを用意した。他のOSで常用してるlocateが見当たらなかったので、 簡易版を書いた。ドッカー関係の一部を隠蔽してる。他にも/devの下とか/procの下を 除外すべきだろうけど、面倒なので、お茶を濁している。