WSL のコマンドラインから Windows 10 の通知を送るメモ

トースト通知というの?

いくつかツールを試してみたメモ。

Growl For Windows

Windows 10 の通知ではなく独自の通知が実装されています。

Chocolatey でサクッとインストールできるかと思いきや、インストールに失敗します。

こっちからインストーラーをダウンロードすれば OK です。

Growl 自体はわかりませんが Growl For Windows はもメンテされていないようです。

notifu

コマンドラインで使うことが想定されたものだと思いますが /v/? でダイアログが表示される。。。

toaster

リポジトリにまんまバイナリが入っているのでダウンロードすれば使えます(exe だけじゃなく dll も必要)

Welcome to toast.
Provide toast with a message and display it-
via the graphical notification system.
-Nels

---- Usage ----
toast <string>|[-t <string>][-m <string>][-p <string>]

---- Args ----
<string>                | Toast <string>, no add. args will be read.
[-t] <title string>     | Displayed on the first line of the toast.
[-m] <message string>   | Displayed on the remaining lines, wrapped.
[-p] <image URI>        | Display toast with an image
[-w]                    | Wait for toast to expire or activate.
?                       | Print these intructions. Same as no args.
Exit Status     :  Exit Code
Failed          : -1
Success         :  0
Hidden          :  1
Dismissed       :  2
Timeout         :  3

---- Image Notes ----
Images must be .png with:
        maximum dimensions of 1024x1024
        size <= 200kb
These limitations are due to the Toast notification system.
This should go without saying, but windows style paths are required.

SnoreToast

下記からバイナリがダウンロードできます。

Welcome to SnoreToast 0.5.2.
A command line application which is capable of creating Windows Toast notifications.

---- Usage ----
SnoreToast [Options]

---- Options ----
[-t] <title string>     | Displayed on the first line of the toast.
[-m] <message string>   | Displayed on the remaining lines, wrapped.
[-p] <image URI>        | Display toast with an image, local files only.
[-w]                    | Wait for toast to expire or activate.
[-id] <id>              | sets the id for a notification to be able to close it later.
[-s] <sound URI>        | Sets the sound of the notifications, for possible values see http://msdn.microsoft.com/en-us/library/windows/apps/hh761492.aspx.
[-silent]               | Don't play a sound file when showing the notifications.
[-appID] <App.ID>       | Don't create a shortcut but use the provided app id.
-close <id>             | Closes a currently displayed notification, in order to be able to close a notification the parameter -w must be used to create the notification.

-install <path> <application> <appID>| Creates a shortcut <path> in the start menu which point to the executable <application>, appID used for the notifications.

-v                      | Print the version and copying information.
-h                      | Print these instructions. Same as no args.
Exit Status     :  Exit Code
Failed          : -1
Success         :  0
Hidden          :  1
Dismissed       :  2
Timeout         :  3

---- Image Notes ----
Images must be .png with:
        maximum dimensions of 1024x1024
        size <= 200kb
These limitations are due to the Toast notification system.
This should go without saying, but windows style paths are required.

Powershell

Powershell だけでもできるようです。

さいごに

SnoreToast がバイナリポンで動くのと使い勝手が良さそうだったので、とりあえず下記のようなスクリプトをかまして使ってみます。

#!/bin/bash

if [[ $# -eq 0 ]]; then
  message="$(cat -)"
else
  message="$*"
fi

/mnt/c/path/to/SnoreToast/SnoreToast.exe -close SnoreToast >/dev/null
/mnt/c/path/to/SnoreToast/SnoreToast.exe -t SnoreToast -m "$message" -silent -w -id SnoreToast >/dev/null &

連続で表示したとき、普通にやると最初の通知を非表示にするまで次の通知が表示されないのですが、↑のように -id で ID を付ける& -w で通知が消えるまで待機しつつ、次の通知を表示する前に -close で ID を指定すれば、前の通知を非表示&次の通知をすぐ表示、とできます。

次のように使います。

# 引数でメッセージを指定
toast this is toast message
# 標準入力からメッセージを読む
ls | toast

標準入力から読めるようにしてみたものの、サイズ的に先頭の4行しか表示できないもよう。

docker-compose で MySQL レプリケーション環境をサクッと用意する

本番環境は MySQL のマスターとリードレプリカのスレーブで構成されているものの、ローカル環境はシングル構成なこと、よくあります。

そういうとき、レプリケーション遅延が原因による不自然な表示・・不具合が生じることがあったりするので、docker-compose なローカル環境でもサクッとレプリケーション環境を用意する方法。

Docker Hub のオフィシャルの MySQL イメージならコンテナの /docker-entrypoint-initdb.d/ にシェルスクリプトを置いておけば初回開始時に自動的に実行されるので、これを利用します。

docker-compose.yml

環境変数 MYSQL_REPLICATION_USER MYSQL_REPLICATION_PASSWORD でレプリケーションのユーザー名・パスワードを指定します。MYSQL_REPLICATION_HOST でマスターのホストを指定しますがこれはスレーブでのみ設定します。

version: '3.7'

services:

  mysql-master:
    image: mysql:8
    command:
      - --default_authentication_plugin=mysql_native_password
      - --gtid_mode=ON
      - --enforce_gtid_consistency=ON
      - --relay-log=relay-bin
      - --server_id=1
    environment: &environment
      TZ: Asia/Tokyo
      MYSQL_ALLOW_EMPTY_PASSWORD: 1
      MYSQL_DATABASE: test
      MYSQL_USER: test
      MYSQL_PASSWORD: pass
      MYSQL_REPLICATION_USER: repl
      MYSQL_REPLICATION_PASSWORD: pass
    volumes:
      - ./init.sh:/docker-entrypoint-initdb.d/init.sh:ro

  mysql-slave:
    image: mysql:8
    depends_on:
      - mysql-master
    command:
      - --default_authentication_plugin=mysql_native_password
      - --gtid_mode=ON
      - --enforce_gtid_consistency=ON
      - --relay-log=relay-bin
      - --server_id=2
    environment:
      <<: *environment
      MYSQL_REPLICATION_HOST: mysql-master
    volumes:
      - ./init.sh:/docker-entrypoint-initdb.d/init.sh:ro

init.sh

コンテナの /docker-entrypoint-initdb.d/init.sh にマウントされるスクリプトです。レプレーションユーザーを作成したり、スレーブを開始したりします。

オフィシャルの MySQL イメージだと entrypoint でユーザーを作成したりデータベースを作成したりいろいろ行われているので、そのままだとレプリケーションの開始後に更新が競合します。

なので、一通り共通の準備が終わったところで RESET MASTER でバイナリログをふっとばします。

#!/bin/bash

if [ -v MYSQL_REPLICATION_USER -a -v MYSQL_REPLICATION_PASSWORD ]; then
  mysql -u root -v mysql <<SQL
    CREATE USER '$MYSQL_REPLICATION_USER'@'%' IDENTIFIED BY '$MYSQL_REPLICATION_PASSWORD';
    GRANT REPLICATION SLAVE ON *.* TO '$MYSQL_REPLICATION_USER'@'%';
SQL
fi

mysql -u root -v mysql <<SQL
  RESET MASTER;
SQL

if [ -v MYSQL_REPLICATION_USER -a -v MYSQL_REPLICATION_PASSWORD -a -v MYSQL_REPLICATION_HOST ]; then
  mysqladmin ping --wait=5 -h "$MYSQL_REPLICATION_HOST"
  mysql -u root -v <<SQL
    CHANGE MASTER TO
      MASTER_HOST = '$MYSQL_REPLICATION_HOST',
      MASTER_USER = '$MYSQL_REPLICATION_USER',
      MASTER_PASSWORD = '$MYSQL_REPLICATION_PASSWORD',
      MASTER_AUTO_POSITION = 1,
      MASTER_DELAY = 10;
    START SLAVE;
SQL
fi

動作確認

docker-compose up -d

# レプリケーション状態 -> Slave_IO_Running と Slave_SQL_Running ともに Yes
docker-compose exec mysql-slave mysql -e 'show slave status \G'

# マスターにテーブルを作ってデータを入れる
docker-compose exec mysql-master mysql test -e 'create table t (id int not null primary key)'
docker-compose exec mysql-master mysql test -e 'insert into t values (1)'

# レプリケーション状態 -> Retrieved_Gtid_Set や Executed_Gtid_Set がちょっと進む
docker-compose exec mysql-slave mysql -e 'show slave status \G'

# レプリケーションされたデータが表示される
docker-compose exec mysql-slave mysql test -e 'select * from t'

PHP 製のデータベースのスキーマ定義を差分で適用するツール「dbdatool」

データベースのスキーマ定義の変更、いわゆるデータベースのマイグレーションついて、スキーマ定義の差分を SQL とかそれ用の DSL とかで作成し、リポジトリ管理してマイグレーションツールで実環境に適応するのが多いと思います。

この方法はわかりやすくて良いですが、スキーマの変更が頻繁だと細々とした変更が差分ファイルとして積み上がっていくのでかなり煩雑です。

一方で、最新のスキーマ定義だけをリポジトリ管理し、実際のデータベースと比較してその差分だけを適用するようにすれば、差分を管理する必要は無くなって、例えばテーブルに列を増やしたければ最新のスキーマ定義ファイルに修正を加えるだけで済みます。

そのようなマイグレーションツールは Perl や Ruby では GitDDLRidgepole などがあるようです。PHP なら Doctrine でそれっぽいことができるようなのですが Doctrine 特有の制約が多そうだったので、dbdatool というツールを作成しました(いまのところ MySQL 専用)。

概要

スキーマ定義は生の SQL で CREATE TABLE などの DDL が管理されている想定です。ただし、ツール自体に生の SQL を読み込み&解析するような機能は無く、できることは基本的には下記のみです。

  • 稼働中のデータベースからスキーマ定義ファイルを独自の JSON 形式で出力する
  • スキーマ定義ファイル(↑で出力したファイル)とデータベースを比較して差分を ALTER などで出力する
  • ↑の差分をデータベースに適用する

あるいは、次のように PDO の DSN の形式で、2つのデータベースのスキーマ定義の差分を表示したり、差分を適用したりもできます。

php dbdatool.phar diff \
    "mysql:host=192.0.2.100;port=3306;dbname=test;charset=utf8:user:password" \
    "mysql:host=192.0.2.200;port=3306;dbname=test;charset=utf8:user:password"

使い方の例

dbdatool のDB接続情報の設定ファイルを作成します。接続情報は環境変数から得るのがポイントです。

<?php
$host = getenv('DB_HOST');
$port = getenv('DB_PORT');
$dbname = getenv('DB_DATABASE');
$username = getenv('DB_USERNAME');
$password = getenv('DB_PASSWORD');
return [
    'dsn' => "mysql:host=$host;port=$port;dbname=$dbname;charset=utf8",
    'username' => $username,
    'password' => $password,
];

dotenv 使ってるなら先頭に↓みたいなのが必要です。

<?php
require __DIR__ . '/../vendor/autoload.php';
(new \Dotenv\Dotenv(dirname(__DIR__)))->load();

この設定ファイルのパスを composer.json に追記します。composer.json にこのように書いておけば dbdatool の CLI によって自動的に読まれます。

{
    "extra": {
        "dbdatool-config": ["database/config.php"]
    }
}

ここまでが事前の準備です。

次に、スキーマ定義に変更があるときは生の SQL で管理している DDL を修正した後、手元の適当なデータベースに mysql コマンドでインポートします。たいてい開発者の手元には開発用とテスト用で2つのデータベースがあるので、テスト用のデータベースにインポートします。

cat *.sql | mysql hoge_test -v

テスト用のデータベースからスキーマ定義ファイルをダンプします。dbdatool の設定ファイルでDB接続情報を環境変数から得るようにしていたので、実行時に環境変数を指定すればテスト用のデータベースからダンプできます。

MYSQL_DATABASE=hoge_test php dbdatool.phar dump > schema.json

スキーマ定義ファイルと開発用のデータベースとの差分を表示・確認して、問題なければ差分を適用します。

php dbdatool.phar diff schema.json
php dbdatool.phar apply schema.json

Git リポジトリに追加・コミット・プッシュします。

git add .
git commit -m 'Fix database schema'
git push

他の開発者は git pull の後にスキーマ定義の変更を適用できます。

git pull -r
php dbdatool.phar diff schema.json
php dbdatool.phar apply schema.json

実環境へも schema.json をアプリのコードと一緒にデプロイすれば差分で適用できます。

特徴

スキーマ定義は生の SQL の DDL で管理したかったのですが、生の SQL と実際のデータベースとを直接比較して差分を導出しようとすると SQL のパーサーが必要になってしまいます。そこまで作り込みたくなかったので、生の SQL は一旦適当なデータベースにインポートした上で INFORMATION_SCHEMA から必要な情報を取得してスキーマ定義ファイルとして保存&リポジトリ管理することにしました。

スキーマ定義ファイル(schema.json)は JSON なのでやろうと思えば手で編集できますが、かなり雑な比較で差分を導出しているので、手書きだといろいろ不都合が生じることがあります。例えば tinyintboolean を同一視していないので schema.jsonboolean と書いてしまうと apply しても差分がなくなりません(DB上は tinyint になっているため)。

なお、パーティショニング、ビュー、トリガ、ストアド、などはサポートしていません。サポートしているのは下記のみです。

  • テーブル(生成列も含む、パーティショニングは未サポート)
  • インデックス
  • 外部キー制約

さいごに

後に schemalex とか sqldef とかを知って、バイナリポンで動くならこういうので良いかとも思ったのですが、既に dbdatool をプロジェクトで使い始めていたので、自分でメンテして自由が利かせられる dbdatool を暫くは使っていこうと思ってます。

CloudWatch Agent を試す

CloudWatch の EC2 のメトリクスだとメモリ使用率やディスク使用率が取れないので別途 Amazon CloudWatch Monitoring Scripts でカスタムメトリクスとして取得したりしていましたが、今日日は CloudWatch Agent を使えばいいだろうので素振り。

残骸はこちら

IAM ロール

EC2 インスタンスで CloudWatch Agent を実行するためには IAM ロールをインスタンスプロファイルで付与する必要があります。ポリシーは AWS 管理の arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy で OK です。

注意点として、CloudWatch Agent は設定ファイルをを SSM Parameter store から取得することができるのですが、このポリシーを使う場合は AmazonCloudWatch- という名前で始まるパラメータ名で設定ファイルを保存する必要があります(ポリシーでそのように制限されている)。

CloudWatch Agent のセットアップ

手作業での手順は次のとおり。これに相当する手順を Terraform で EC2 のユーザーデータに入れてます。

# amazon-cloudwatch-agent のインストール
sudo yum install https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm

# 設定ファイルを作成
sudo vim /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json

# 設定ファイルをインポートしてエージェントを開始
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 \
    -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json -s

ユーザーガイドでは SSM (AWS Systems Manager) で CloudWatch Agent をセットアップしていますが、そうはせずに Terraform で EC2 インスタンスのユーザーデータに必要な情報を書き込んで cloud-init でセットアップしました。

設定ファイル

前述の手作業の手順の最後のコマンドは、amazon-cloudwatch-agent.json を TOML 形式の設定ファイルに変換して amazon-cloudwatch-agent.toml に保存した後にエージェントを開始します。

はじめから amazon-cloudwatch-agent.toml を作成して配置すればいいような気もしますが amazon-cloudwatch-agent.jsonamazon-cloudwatch-agent.toml とを比べてみると単にフォーマットが変換されただけではなくいろいろ違いがあるので、設定を容易にするために変換をかますようになっているのかもしれません。

また、amazon-cloudwatch-agent.toml には InstanceId がベタ書きされているので(CloudWatch Logs へのログ転送を設定しているなら)、あらかじめ CloudWatch Agent がセットアップされた AMI を作成して使う場合でも amazon-cloudwatch-agent-ctl コマンドは必要です。

なお、amazon-cloudwatch-agent-config-wizard という TUI のウィザード形式で設定ファイルを作成するツールがあるので、このツールが作成したファイルを編集して使用すると良いかもしれません。

収集できるメトリクス

収集できるメトリクスの一覧は次のとおりです。

また、procstat で特定のプロセスのメトリクスを取得したりもできます。

これら以外に StatsDcollectd が収集したメトリクスを CloudWatch に記録することもできるようです。

メトリクスに固有のディメンジョン

メトリクスによっては固有のディメンジョンが設けられていることがあります。

例えば cpu であればコアごとまたはトータルを示すディメンジョンが、disk なら、デバイス・ファイルシステム・マウントポイント、などがディメンジョンになります。

要するに、CPU はコアごとのメトリクスまたはトータルのメトリクスが、ディスクならデバイス&ファイルシステム&マウントポイントごとのメトリクスが記録される、ということです。

append_dimensions

前述のディメンジョンとは別に amazon-cloudwatch-agent.jsonmetrics.append_dimensions で追加のディメンジョンが指定できます。ただし、下記の4つのいずれかしか指定できないようです。

{
  "metrics": {
    "append_dimensions": {
      "AutoScalingGroupName": "${aws:AutoScalingGroupName}",
      "ImageId": "${aws:ImageId}",
      "InstanceId": "${aws:InstanceId}",
      "InstanceType": "${aws:InstanceType}"
    }
  }
}

キーと値のセットはこれに一致する必要があります(値が存在する意味がないような・・謎い)。

指定した項目がメトリクスのディメンジョンに追加されます。全部指定すればひとつのメトリクスにこれら4つのディメンジョンがすべて追加されます。それぞれのディメンジョンを持つ別のメトリクスになるというわけではありません。

aggregation_dimensions

aggregation_dimensions でメトリクスを集約するディメンジョンを指定できます。例えば AutoScalingGroupName で集約すれば AutoScalingGroupName のみをディメンジョンに持つメトリクスを記録できます。要するに AutoScalingGroupName ごとに集約したメトリクスになります。

AutoScaling の条件として使うなら AutoScalingGroupName で集約する必要がありますね。

ログ

メトリクスだけではなく、ログファイルを監視して CloudWatch Logs に送ることもできます。

さいごに

セットアップも簡単なのでリソース監視に CloudWatch をメインに据えるならとりあえず使っとけば良いと思います。


CloudWatch Agent とは直接関係無いですが・・・複数のディメンジョンを持つメトリクスに対して一部のディメンジョンでのみ集計した結果に対して CloudWatch Alarm を設定したりはできないんですね。

例えば AutoScalingGroupName と InstanceId の2つのディメンジョンを持つメトリクスに対して、AutoScalingGroupName を条件に複数の InstanceId を合計したメトリクスに対して CloudWatch Alarm を設定することができません。

Metric Math で近いことならできそうですが・・あらかじめ集計する複数のメトリクスをリストしておく必要があるので AutoScaling でインスタンスが増えたり減ったりする状況ではダメそうです。

ので、やりたければ AutoScalingGroupName だけをディメンジョンに持つメトリクスを別に記録する必要があります。

AWS/EC2 名前空間のメトリクスでは、同じ CPUUtilization でも InstanceId ごとや AutoScalingGroupName ごとのメトリクスに CloudWatch Alarm が設定できたので、複数のメトリクスの集計に対して CloudWatch Alarm を設定することができるような気がしていたのですが、単に同じ CPUUtilization で InstanceId のみをディメンジョンに持つメトリクスと AutoScalingGroupName をディメンジョンに持つメトリクスが別々に記録されているだけでした。

Prometheus なら rate(node_cpu{mode="system",group="mygroup"}[5m]) とかでサッとできるので、CloudWatch Alarm のちょっと使いにくいなーと感じるところです。

RancherOS で KVM+libvirt に固定IPで Docker ホストを作るメモ

KVM+libvirt な仮想化ホストに、ブリッジ I/F で固定IPな Docker ホストを RancherOS で作るメモ。

# RancherOS の ISO をダウンロード
wget https://releases.rancher.com/os/latest/rancheros.iso

# SSH鍵をダウンロード
curl -s https://github.com/ngyuki.keys -o authorized_keys

# ConfigDrive のディレクトリを準備
mkdir -p configdrive/openstack/latest

# ConfigDrive の user_data を作成
vim configdrive/openstack/latest/user_data
#cloud-config

ssh_authorized_keys:
$(sed 's/^/  - /' authorized_keys)

rancher:
  network:
    dns:
      nameservers:
        - 192.168.0.1
    interfaces:
      eth0:
        address: 192.168.0.100/24
        gateway: 192.168.0.1
        dhcp: false

  state:
    formatzero: true
    autoformat:
      - /dev/vda
# ConfigDrive の ISO を作成
mkisofs -R -V config-2 -o configdrive.iso configdrive

# 永続データのディスクイメージを作成
echo -n "boot2docker, please format-me" > data.img
truncate -s 1G data.img

# ゲストを作成
virt-install \
  --import \
  --name rancheros \
  --hvm \
  --virt-type kvm \
  --ram 2048 \
  --vcpus 1 \
  --arch x86_64 \
  --os-type linux \
  --boot cdrom \
  --disk "$PWD/data.img,device=disk,bus=virtio,cache=writeback" \
  --disk "$PWD/rancheros.iso,device=cdrom" \
  --disk "$PWD/configdrive.iso,device=cdrom" \
  --network network=front,model=virtio \
  --graphics none \
  --serial pty \
  --console pty

動作確認します。

ssh docker@192.168.0.100 docker run --rm hello-world
ssh docker@192.168.0.100 docker run -d -p 80:80 nginx
curl http://192.168.0.100/

補足とか

cloud-init

RancherOS は cloud-init で初期設定が可能なのですが、cloud-init がそのまま使用されているわけではなく、独自のこれ https://github.com/rancher/os/tree/master/config/cloudinit が使用されているようです(CoreOS からのフォーク?)。

なので DataSources も一部しかサポートしていません。NoCloud がダメっぽかったので ConfigDrive で初期設定しています。

autoformat

user_data で下記の通りにしておくと /dev/vda が永続データ用の領域として自動でフォーマットされるのですが、そのディスクの先頭に boot2docker, please format-me が記述されている必要があります。

rancher:
  state:
    formatzero: true
    autoformat:
      - /dev/vda

なお、この方法でフォーマットすると /dev/vda にはパーティションテーブル無しで直接ファイルシステムが作成されます。

パーティションが切られてたほうが良いなら、次のようにあらかじめディスクイメージにパーティションを作成しておけば OK です。

truncate -s 1G data.img
virt-format --format=raw --partition=gpt --filesystem=ext4 --label=B2D_STATE -a data.img

boot2docker で KVM+libvirt に固定IPで docker ホストを作るメモ

KVM+libvirt な仮想化ホストに、ブリッジ I/F で固定IPな docker ホストを boot2docker で作る試行錯誤のメモ。

なお boot2docker は新しい Docker のリリースや kernel の更新以外では更新されないメンテナンスモードになっているようです。

userdata.tar で SSH 鍵を配置

boot2docker は最初のブート時に先頭が boot2docker, please format-me という文字列で始まっているブロックデバイスを tar ファイルとして取り出して、ブートの都度 /home/docker に展開します。

tar を取り出した後のそのデバイスは永続データの領域として自動的にフォーマットされます。

そのようなブロックデバイスのためのディスクイメージはは次のように作成できます。

mkdir -p .ssh
chmod 700 .ssh/
curl https://github.com/ngyuki.keys -o .ssh/authorized_keys
chmod 600 .ssh/authorized_keys
echo "boot2docker, please format-me" > "boot2docker, please format-me"
tar cvf userdata.img "boot2docker, please format-me" .ssh
truncate -s 1G userdata.img
qemu-img convert -f raw -O qcow2 userdata.img userdata.qcow2

docker-machine はこの仕組を用いて authorized_keys を配置しているようですが、tar ファイルは /home/docker に展開されるだけなので固定IPを付与するような処理は行なえません。

bootsync.sh でブート時に固定IPを付与

boot2docker はブート時に /var/lib/boot2docker/bootsync.sh/var/lib/boot2docker/bootlocal.sh があればそれを sh で実行します。

/var/lib/boot2docker/ は永続データの領域なので、/var/lib/boot2docker/bootsync.sh に次のように書いておけばブートの都度、固定IPが付与されます。

killall udhcpc
sleep 1
ip addr flush dev eth0
ip addr add 192.168.0.100/24 dev eth0
ip route add default via 192.168.0.1 dev eth0
echo nameserver 192.168.0.1 > /etc/resolv.conf

しかし、初回ブート時にこのファイルを作成する術がありません。コンソールで作業すればいいだけですが・・

boot2docker-data

boot2docker はブート時に boot2docker-data というラベルのついたパーティションを探して、見つかればそれを永続データの領域としてマウントします。見つからなければ boot2docker, please format-me という文字列で始まっているブロックデバイス、またはパーティション未作成なブロックデバイスを自動的にフォーマットしてマウントします。

普通は初回ブート時に自動フォーマットされるときに、このラベルがついたファイルシステムとしてフォーマットされるのですが、あらかじめフォーマット済で boot2docker-data というラベルのついたパーディションを含むディスクファイルを用意すれば、任意のカスタマイズされた永続データ領域を初回ブート時から使用できます。

boot2dockerswap

boot2docker はブート時に boot2dockerswap というラベルのついたパーティションを探して、見つかればそれをスワップとして使います。見つからなくても警告が出力されるだけで起動はするようです。

普通は初回ブート時に自動フォーマットされるときに、永続データ用のパーティションをフォーマットするついでにこのラベルの付いたスワップパーティションも作成されますが、あらかじめ boot2dockerswap というラベルの付いたスワップパーティションを含むディスクファイルを用意すれば、自動フォーマットが行われなくてもスワップが有効になります。

やってみる

# 固定IPのためのスクリプトを作成
mkdir -p ./rootfs/var/lib/boot2docker/
cat <<'EOS'> ./rootfs/var/lib/boot2docker/bootsync.sh
killall udhcpc
sleep 1
ip addr flush dev eth0
ip addr add 192.168.0.100/24 dev eth0
ip route add default via 192.168.0.1 dev eth0
echo nameserver 192.168.0.1 > /etc/resolv.conf
EOS
chmod -x ./rootfs/var/lib/boot2docker/bootsync.sh

# SSH公開鍵を含む userdata.tar を作成
mkdir -p ./userdata/.ssh/
curl -s https://github.com/ngyuki.keys -o ./userdata/.ssh/authorized_keys
chmod 700 ./userdata/.ssh/
chmod 600 ./userdata/.ssh/authorized_keys
tar cvf ./rootfs/var/lib/boot2docker/userdata.tar -C ./userdata/ .ssh/

# ↑が保持ぞんされたファイルシステムを含むディスクファイルを作成
virt-make-fs --format=raw --partition=gpt --size=1G --type=ext4 --label=boot2docker-data ./rootfs/ data.img

# スワップのディスクファイルを作成
truncate -s 1G swap.img
parted -s -a optimal swap.img -- mklabel gpt mkpart primary 1 -1
guestfish -a swap.img run : mkswap /dev/sda1 label:boot2dockerswap

# 確認
virt-df -h -a data.img -a swap.img
#=> Filesystem                                Size       Used  Available  Use%
#=> data.img+:/dev/sda1                       992M       2.5M       922M    1%

# 確認
virt-filesystems --all --long -h -a data.img -a swap.img
#=> Name       Type        VFS   Label             MBR  Size   Parent
#=> /dev/sda1  filesystem  ext4  boot2docker-disk  -    1.0G   -
#=> /dev/sdb1  filesystem  swap  boot2dockerswap   -    1022M  -
#=> /dev/sda1  partition   -     -                 -    1.0G   /dev/sda
#=> /dev/sdb1  partition   -     -                 -    1022M  /dev/sdb
#=> /dev/sda   device      -     -                 -    1.0G   -
#=> /dev/sdb   device      -     -                 -    1.0G   -

# boot2docker.iso をダウンロード
wget https://github.com/boot2docker/boot2docker/releases/download/v18.09.1/boot2docker.iso

# ゲストを作成
virt-install \
  --import \
  --name boot2docker \
  --hvm \
  --virt-type kvm \
  --ram 1024 \
  --vcpus 1 \
  --arch x86_64 \
  --os-type linux \
  --boot cdrom \
  --disk "$PWD/data.img,device=disk,bus=virtio,cache=writeback" \
  --disk "$PWD/swap.img,device=disk,bus=virtio,cache=none" \
  --disk "$PWD/boot2docker.iso,device=cdrom" \
  --network network=default,model=virtio \
  --graphics none \
  --serial pty \
  --console pty

動作確認します。

env DOCKER_HOST=ssh://docker@192.168.0.100 docker run --rm hello-world
env DOCKER_HOST=ssh://docker@192.168.0.100 docker run -d -p 80:80 nginx
curl http://192.168.0.100/

さいごに

クラスタ化しないシングルの Docker 環境をサッと作れるように試行錯誤したのですが、boot2docker.iso

On the other hand, the boot2docker distribution (as in, boot2docker.iso) is in "maintenance mode".

とのことらしいので、別の Docker ホスト用の軽量 Linux を使うのが良いですかね。。

ただ、Container Linux (CoreOS) や Atomic Host はクラスタにする前提な気がして敷居が高く感じたので、RancherOS とかがいいんでしょうかね。