Gentoo Linux のインストールは、GUI インストーラが用意された他の多くの Linux ディストリビュージョンと違い、次のようなプリミティブな方法であることが知られています。
- ブロックデバイス上にパーティションを切る
/
ファイルシステムの内容を Tar アーカイブから展開する- 必要なパッケージのソースコードをダウンロード、展開、ビルドする
- ebuild と呼ばれる Bash スクリプトライクなテキストファイルによって行われます
- Linux Kernel の config を作成しビルドする
- ブロックデバイス上にブートローダを書き込む
これは柔軟なシステムを構成する上でとても有用ですが、一方で複数回繰り返す場合には煩雑なことも事実です。
そこでこのようなインストール手順を Ansible を用いて自動化することを考えます。
なぜ Ansible か?
Ansible は人気のある構成管理ツールであり、2015 年の Red Hat, Inc.による買収を経て様々な領域で使われています。
OSS として長く安定的にメンテナンスされていること、サーバーが不要なこと、YAML によるテキスト形式で処理を記述できることなどは個人的に特に扱いやすい点です。
ただし YAML 形式のトレードオフとして、複雑な処理を書く場合には Jinja2 テンプレートを駆使する必要があるかもしれません。
How to Run
私が作成した Ansible の Role は次のリポジトリで公開しています。
https://github.com/mazgi/ansible-galaxy.gentoo-systemd-remote
また _driver/provisioning/site.yml
には Role を呼び出す Playbook があり、 Docker Compose を通して実行できるようにしています。
この記事では次のような構成で実行する方法を紹介します。
- Hyper-V VM
- Generation 2, See Generation 2 Virtual Machine Overview
Secure Boot
disabled
- Docker Compose
Hyper-V 上は Windows 標準の仮想化ソフトウェアです。
VM を作るときに Generation 1 または 2 を選べますが、Generation 2 を指定することで UEFI からの起動がサポートされます。
本記事では Hyper-V VM を例にしますが、他の仮想化ソフトウェアやベアメタルでも実行方法はあまり変わらないでしょう。
ただしベアメタルの場合、NIC や RAID コントローラのドライバを Linux Kernel の config で有効にしたり、事前に RAID を構築しておくなどの手順が必要なことがあります。
本記事でご紹介する手順で Gentoo Linux をインストールするためにはインストール対象のホストが何らかの Linux ディストリビュージョンで起動している必要があります。
具体的には SSH サービスが起動し、GUID パーティションを操作し、Tar 等のアーカイブを展開し、 /dev 等ファイルシステムが提供されている必要があります。
私はこのような用途で SystemRescue (also known as SystemRescueCd)をよく使います。
これは数百 MB の ISO イメージに収められた Linux ディストリビュージョンで、その名前の通り HDD や SSD 等のブロックデバイスから起動できなくなった OS を救済する便利なツールが組み込まれています。
また SystemRescue は起動後に何もしません。
つまり他の多くの Linux ディストリビュージョンのインストールメディアと異なり、インストールウィザードが起動することもなければ、クリック 1 つでオススメのパーティション構成がブロックデバイスに書き込まれることもありません。
単に ISO イメージと RAM 上に一時的に作られたファイルシステムから OS が起動しシェルのプロンプトが現れます。
この点も私がこのような用途で SystemRescue を好んで使う理由です。
本記事の構成では Ansible を Docker 上で実行しています。
この方法により Ansible 自体や他の依存するツール群のインストールを docker-compose up
コマンド 1 つに集約できます。
もちろん Docker を介さず Ansible をインストールすることもできます。
その場合は Dockerfile
や docker-compose.yml
等の内容を参考に手順を組み立ててください。
1. .env
ファイルを作る
リポジトリを Clone したディレクトリに移動し Docker Compose で読み込まれる .env ファイルを作ります。
Bash や Z shell では次のように実行することで作成できます。
rm -f .env
test $(uname -s) = 'Linux' && echo "UID=$(id -u)\nGID=$(id -g)" >> .env
cat<<EOE >> .env
CURRENT_ENV_NAME=production
DOCKER_GID=$(getent group docker | cut -d : -f 3)
EOE
2. VM を SystemRescue から起動し SSHD を有効にする
Gentoo Linux をインストールする対象の VM を、SystemRescueの ISO イメージから起動します。
またこの時 SSH デーモン(SSHD)も起動します。
これは SSH 経由で Ansible を適用するためです。
SSHD を起動するため Booting SystemRescueCd に従って起動時に次の 2 つのパラメタを渡してください。
rootpass=(任意のパスワード)
- Sets the root password of the system running on the livecd to ****. That way you can connect from the network and ssh on the livecd and authenticate using this password.
nofirewall
- You need to use this option if you need to establish connections to the system running SystemRescueCd from outside (for example connections to sshd).
手順の中でここで渡すパスワードを使ってログインすることはありません。
しかし root のパスワードが設定されていないと SSH 接続できないため何か設定する必要があります。
あるいは起動後に passwd
コマンドで設定し、Firewall を Off にしても同等です。
3. 公開鍵認証で SSH ログインできるよう設定する
SystemRescue で起動した VM に Ansible 実行ホストから SSH 経由で provisioning するため、公開鍵認証でログインできるように設定します。
今回は ssh-keygen
コマンドで一時的に鍵ペアを生成して ~/.ssh
ディレクトリを作ります。
鍵ペア自体は使わないので mkdir && chown && chmod
で ~/.ssh
ディレクトリを作っても問題ありません。
ホスト側の秘密鍵に合う公開鍵を ~/.ssh/authorized_keys
に保存します。
私のように GitHub に鍵を預けている方であれば次のように cURL で取得できるでしょう。
curl -L github.com/mazgi.keys > ~/.ssh/authorized_keys
ip コマンドなどで VM に割り当てられた IP アドレスを表示し、SSH ログインできることを確認します。
ip a show
4. Provisioning 実行
リポジトリを Clone したディレクトリで docker-compose up
します。
返り値 0
で終了すれば正常です。
Ansible のコレクションや Role を取得しているため、ネットワーク状況により時間がかかるかもしれません。
Ansible 上での疎通を確認します。
次のように Ansible の ping が通るか確認してください。
docker-compose run provisioning ansible --inventory 192.0.2.1, --user root --module-name ping all
以降、 192.0.2.1
は VM に割り当てられた IP アドレスを示します。
あなたの VM の IP アドレスに読み替えてください。
また複数の VM を指定する場合、 192.0.2.1,192.0.2.2
のように指定できます。
Ansible の仕様で今回のように Inventory をコマンドラインで指定する場合には 192.0.2.1,
のように ,
が必要です。
疎通が確認できたら Playbook を適用します。
Note: ここからの手順には長い時間がかかります、また途中で失敗することがあります。
次のように ansible-playbook
コマンドを実行してください。
docker-compose run provisioning ansible-playbook --inventory 192.0.2.1, /project/provisioning/site.yml
次の 3 つの質問に答えます。
Please type the main device name [sda]:
Please type the network interface name pattern [eth*]:
Please type the new root password:
途中、各種パッケージのソースコードなどをダウンロードしてビルドする際にタイムアウトやビルドエラーに遭遇することがあります。
その場合、多くは ansible-playbook
コマンドを再実行することで問題が解消します。
Tarball の事前ダウンロードやバイナリパッケージの共有など、時間短縮とエラー発生の削減を工夫していきたいところです。
次のように結果が表示され、”PLAY RECAP”の unreachable
と failed
が共に 0
であれば成功です。
VM を再起動すると VM のブロックデバイスから Gentoo Linux が起動するはずです。
Implementations
Gentoo Linux は無数の構成を取れますが、この Role では私が現在最もよく使う次の構成にしています。
- UEFI+GPT
- ファイルシステムは Btrfs
- スーパーデーモンは systemd
- DHCP で IP アドレスを取得する
- SSH サービスが起動し、手元の鍵ペアでログインできる
- Docker サービスが起動する
追加で必要なサービスやアプリケーション、通常ユーザーとその環境は別途構築する想定です。
かつてはもっと多様な構成をサポートしようと考えたこともあったのですが、時代や私自身が扱う領域が変わったため単純化しました。
Gentoo Linux の構成内容と合わせて Ansible Task としての実装方法をいくつか解説します。
パーティション作成
Linux でパーティションを構成する際、対話的なツールである GParted や fdisk がよく使われます。
しかしパーティション作成を自動化する場合、非対話的に実行できるGNU Partedが便利です。
Parted を使うと例えば次のようにパーティションを作ることができます。
parted --script --align optimal /dev/sda -- mkpart uefi_boot fat32 1MiB 128MiB
Tarball のダウンロードと検証
Tarball などのアーカイブをダウンロードし、独自の方法で検証したい場合があります。
例えば提供元からチェックサムが提供されている場合などです。
その場合、次のように register
を使うことで実現できます。
- name: Verify exists stage3 archive
changed_when: no
ignore_errors: yes
shell: >
sha512sum --check /mnt/gentoo/{{ latest_stage3_filename.stdout | basename }}.DIGESTS 2> /dev/null | grep -E '^{{ latest_stage3_filename.stdout | basename }}:\s+OK$'
args:
chdir: /mnt/gentoo/
register: latest_stage3_archive_verified
- name: Download latest stage3 archive
get_url:
dest: /mnt/gentoo/
url: "{{ portage.mirror_uri }}/releases/amd64/autobuilds/{{ latest_stage3_filename.stdout }}"
when: latest_stage3_archive_verified is failed
あらゆる設定ファイルのテンプレート化
Linux を含め UNIX like な OS はほとんどの設定をテキストファイルで管理しています。
またファイルシステムを /
を起点とした 1 つの Tree として扱います。
また Ansible では jinja2 テンプレートをテンプレートの Tree に対して適用できます。
これらの特徴を活かして設定ファイルをあらかじめ適用先に合わせた Tree として配置しておくことで簡単に適用できます。
テンプレートは例えば次のように書けます。{{}}
が Ansible により置き換えられて配置されます。
/dev/{{ main_block_device }}3 / btrfs defaults,subvol=gentoo 0 1
/dev/{{ main_block_device }}3 /var/log btrfs defaults,subvol=var-log 0 1
/dev/{{ main_block_device }}1 /boot vfat defaults 0 1
/dev/{{ main_block_device }}2 none swap sw 0 0
もちろん置き換えるものがなければテンプレートの内容がそのまま反映されますので、構成を管理したい全てのファイルをテンプレート化しておくことができます。
例えば Linux Kernel の config は現時点では既存環境の /proc/config.gz
を zcat
したものをそのまま配置しています。
このように設定ファイルを実際のファイルシステムに合わせてテンプレートとして配置しておくことで、将来内容が変わっても同じように適用できます。
このテンプレートを適用するために必要な Ansible Task は次の 2 つだけです。
- name: Create directories by the templates
file:
path: "{{ item | regex_replace('^('+role_path+'/templates)(.*)', \"/mnt/gentoo\\2\") }}"
state: directory
with_lines: find {{ role_path }}/templates -type d
- name: Apply templates
template:
dest: "{{ item | regex_replace('^('+role_path+'/templates)(.*)(.j2$)', \"/mnt/gentoo\\2\") }}"
src: "{{ item }}"
with_lines: find {{ role_path }}/templates -type f -name '*.j2'
標準出力の内容による判別
Provisioning を行う上で「適用が変更を及ぼしたか」判別したいことがあります。
特に「変更を及ぼしてもそうではなくても返り値が同じ場合」、標準出力や標準エラー出力の内容を読み取って判断する必要が生じます。
これは Ansbile Task では次のように register
と changed_when
を組み合わせることで実現できます。
- name: Generate locales
changed_when:
- '" Adding locales to archive ..." in _result.stdout'
shell: >
chroot /mnt/gentoo /bin/bash -lc 'locale-gen --update'
register: _result
ブートローダの簡略化
Linux を起動できるブートローダとして GRUB や ELILO が有名です。
しかし Linux Kernel には UEFI から直接起動できる機能があります( CONFIG_EFI_STUB
)。
これを有効にすることで特定のブートローダをインストールせずに OS を起動できるようになります。
具体的には efibootmgr
を用いて次のように実行することで実現できます。
efibootmgr --create --part 1 --disk /dev/sda --label Gentoo --loader "\\EFI\\Gentoo\\vmlinuz-gentoo.efi" --unicode "root=/dev/sda3 rootflags=subvol=gentoo rootfstype=btrfs initrd=\\EFI\\Gentoo\\initramfs-gentoo.img"
以上、このようなハードウェアとアプリケーションの中間にあたるレイヤーを扱う際に参考になれば幸いです。