LinuxマシンのサービスをDockerに移行するスクリプトを書いた

実行中のLinuxマシンからDockerのベースイメージを生成するスクリプトを書いてみました。

https://github.com/hidakanoko/vm2container

* このスクリプトは指定されたパッケージの依存性を解決して、パッケージに含まれるコマンドを実行する(プロセスを起動する)のに必要なファイルを集めてDockerのベースイメージを作ります。

* RPMを使ったシステムなら動くはずですが、今のところCentOS7でしかテストしていません...

* 実用性は低いような気がしますが、古いマシンをDockerに乗せ換えてとりあえず延命したいようなケースでは役に立つかも？

# 使い方

## スクリプトのダウンロード

```
# git clone https://github.com/hidakanoko/vm2container.git
Cloning into 'vm2container'...
remote: Enumerating objects: 131, done.
remote: Counting objects: 100% (131/131), done.
remote: Compressing objects: 100% (79/79), done.
remote: Total 131 (delta 68), reused 99 (delta 44), pack-reused 0
Receiving objects: 100% (131/131), 34.22 KiB | 0 bytes/s, done.
Resolving deltas: 100% (68/68), done.
```

## Helpの表示

```
# cd vm2container/
# bin/v2c -h
usage: vm2container [-h] [-p PACKAGE] [-s] [-l] [-a] [-d DIRECTORY]
                    [-v VERBOSE]

Create docker image from running full Linux environment.

optional arguments:
  -h, --help            show this help message and exit
  -p PACKAGE, --package PACKAGE
  -s, --showDeps
  -l, --listFiles
  -a, --createArchive
  -d DIRECTORY, --directory DIRECTORY
  -v VERBOSE, --verbose VERBOSE
                        ERROR, WARN, INFO, DEBUG
```

## パッケージの依存関係をツリー表示する

`-p <PACKAGE>`で起動したいコマンドを含むパッケージを指定します。下記サンプルでは`/usr/sbin/httpd`を起動するため`httpd`パッケージを指定しています。

`-s`で指定されたパッケージの依存関係を表示します。

```
# bin/v2c -p httpd -s
 * Resolving dependency for httpd...
done!
INFO: Total 115 package(s) found
httpd
    shadow-utils
        audit-libs
            libcap-ng
                glibc
                    glibc-common
                        libselinux
                            pcre
                                glibc [*]
                                libstdc++
                                    libgcc
                                    glibc [*]
                                libgcc [*]
                            glibc [*]
                            libsepol
                                glibc [*]
                                bash
                                    ncurses-libs
                                        libstdc++ [*]
                                        libgcc [*]
                                        ncurses-base
                                        glibc [*]
                                    glibc [*]
                        bash [*]
                        tzdata
                        glibc [*]
                    nss-softokn-freebl

(...omit)

INFO: [*]=Child dependencies are omitted as already described above.
```

依存性ツリーの中で同じパッケージが繰り返し現れるため、表示済のパッケージの依存関係は省略されます。(末尾に"[*]"がついているパッケージ)

## プロセスの起動に必要なファイルを一覧表示する

`-l`で指定されたパッケージと、その依存パッケージに含まれるすべてのファイルを一覧表示します。(`rpm -ql`で得られるもの)

```
# bin/v2c -p httpd -l
 * Resolving dependency for bash...
done!
INFO: Total 41 package(s) found
 * Creating file list of 1 package(s) and its dependent packages...
done!
/
/bin
/bin/egrep
/bin/fgrep
/bin/grep
/bin/sed
/etc
/etc/DIR_COLORS
/etc/DIR_COLORS.256color
/etc/DIR_COLORS.lightbgcolor
/etc/GREP_COLORS
/etc/X11

(...omit)

/usr/share/man/ay/man7 (not found)
/usr/share/man/ay/man7x (not found)
/usr/share/man/ay/man8 (not found)

(...omit)

/var/run
/var/run/setrans
/var/spool
/var/spool/lpd
/var/spool/mail
/var/tmp
/var/yp
INFO: Total 18684 file(s) found, however 13176 file(s) are not present in the file system
```

パッケージのファイルリストに含まれているものの、ファイルシステム内に存在しないファイルは末尾に"(not found)"がついています。
ユーザーが削除したファイルや、ghostファイルはすでにファイルシステムに存在しないためこのように表示しています。

## Dockerのイメージを作成する

`-a`でDockerイメージを作成します。

Webコンテンツとhttpdのユーザー設定ファイルを含めるため`-d <DIRECTORY>`オプションで/var/www/htmlと/etc/httpd/conf.dをアーカイブに含めるよう指定しています。

```
# ./bin/v2c -p httpd -d /var/www/html -d /etc/httpd/conf.d -a
 * Resolving dependency for httpd...
done!
INFO: Total 115 package(s) found
 * Creating file list of 1 package(s) and its dependent packages...
done!
INFO: Total 23237 file(s) found, however 13193 file(s) are not present in the file system
 * Creating archive...
done!
INFO: Archive created in /tmp/archive.tgz
```

## Dockerイメージとしてインポートする

生成したアーカイブはそのままDockerにインポートして、ベースイメージとして利用できます。

コンテナ起動時にパラメータを省略できるよう、エントリーポイントとコマンドパラメーターを含めています。

```
# docker image import /tmp/archive.tgz v2c-httpd:20190302 -c "ENTRYPOINT [\"/usr/sbin/httpd\"]" -c "CMD [\"-DFOREGROUND\"]"
sha256:7908fb6eb6f50618c688a51556b0348de5948fe0a855aa32b515a74182ca1bbe
```

## コンテナを起動する

```
# docker run -d --rm -p 8080:80 v2c-httpd:20190302
32409c65c9c5ed96103333461eda513fe1d7c9b842dfc4ad17e22258e155e9e6
```

ブラウザで http://DOCKERHOST:8080/ にアクセスするとApacheのTest Pageが表示されます。
/var/www/htmlに何かコンテンツを置いているのであればそれにアクセスすることもできます。

# ざっくりとした仕組み

Dockerはnamespaceやcgroupなどの技術を使って作成したサンドボックス内でプロセスを起動する技術です。そのためイメージの中に必要なファイルがそろっていればプロセスを起動できるはずです。

Dockerのドキュメントにベースイメージの作り方がありますが、これはdebootstrapでパッケージを展開してアーカイブで固めているだけです。

https://docs.docker.com/develop/develop-images/baseimages/

つまり逆に考えれば、実行中の環境でパッケージの依存性を解決して必要なファイルをすべて集めればDockerイメージを作ることが出来るはずです。(という思いつきからこのスクリプトを組んでみました)

`v2c`コマンドはパッケージの依存ツリーを解決して(`rpm -q --requires`, `rpm -q --whatprovides`など)、依存パッケージのファイルをすべて集めて(`rpm -ql`)、tgzに固めています。

前述のとおりDockerのベースイメージは基本的に必要なライブラリやコマンド、設定ファイルをtarで固めただけなので、このイメージはそのままベースイメージとしてインポート可能です。

# TODO

* パッケージに含まれるドキュメントファイルを除外できるようにする
* DEBパッケージ対応
* ベースイメージ + レイヤーイメージを作れるようにする。例えばベースイメージにはコアパッケージを含めておいて、レイヤーイメージにWebサーバーやMariaDBを起動するためのファイルを含めるような感じ。