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

メトリクス(のセクション)ごとに詳細な設定が出来ることもあります。例えば disk の場合、対象となるマウントポイント、除外するファイルシステム、デバイスをディメジョンに含めるか、などが指定できます。

{
  "metrics": {
    "metrics_collected": {
      "disk": {
        "resources": [
            "/"
        ],
        "measurement": [
          "disk_used_percent"
        ],
        "ignore_file_system_types": [
          "rootfs",
          "tmpfs",
          "devtmpfs"
        ],
        "drop_device": true
      }
    }
  }
}

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 とかがいいんでしょうかね。

Amazon EC2 Auto Scaling を Terraform で素振り

Amazon EC2 Auto Scaling を ユーザーガイド を読みながら Terraform で素振りしたメモ。残骸はこちら

Launch Configuration と Launch Template

Auto Scaling で起動する EC2 インスタンスを定義する方法は下記の複数があります。

  • Launch Configuration
  • Launch Template

Launch Configuration より Launch Template のほうが後発で、以下のような違いがあります。

  • Launch Configuration は Auto Scaling 専用なのに対して Launch Template は他の用途にも使用できる
    • Launch Template から直で EC2 インスタンスを作成したりできる
  • Launch Template はバージョン管理されるので更新すると新たにバージョンが作成される
    • Launch Configuration はバージョン管理されないので更新時に新旧維持するためには別名で作成するしか無い

また、Launch Template でしかできないことがいくつかあります。基本的に Launch Template を使っていれば良さそうです。

ルートデバイス

Launch Configuration なら Terraform で root_block_device でルートデバイスが指定できますが Launch Template だと block_device_mappings でデバイス名を明示的に指定する必要があります。

最初 CentOS 向けに /dev/sda1 で Launch Template を作成していましたが、AMI を Amazon Linux 2 ベースに変更したところインスタンスが開始できなくなりました。Amazon Linux 2 だとルートデバイスが /dev/xvda になるためです。

トラブルを避けるために Launch Template で block_device_mappings でルートデバイスを指定していろいろ設定するのはやめて、あらかじめカスタムも AMI にブロックデバイスの情報を程よく刻んでおくのが良いかもしれません。もっともそんな間違いは滅多に無いだろうし、間違えたとしても直ぐに気づくだろうので気にしなくても良いかもしれません。

下記のように AMI の block_device_mappings を参照すれば良いかも。

data "aws_ami" "app" {

  // snip

}

resource "aws_launch_template" "app" {

  // snip

  block_device_mappings {
    device_name = "${lookup(data.aws_ami.app.block_device_mappings[0], "device_name")}"

    ebs {
      volume_size           = 40
      volume_type           = "gp2"
      delete_on_termination = true
    }
  }
}

Blue/Green Deployment

無停止でインスタンスの AMI を更新する方法はいくつか考えられます。

  • ELB までをワンセットにした Blue/Green Deployment で DNS を切り替える
  • AutoScaling Group までをワンセットにした Blue/Green Deployment で ELB を切り替える
  • Launch Configuration/Template までをワンセットにして AutoScaling Group 内のインスタンスを Rolling Update

最後の方法は Terraform 単体ではたぶん無理です。DNS を切り替える方法は TTL の影響を受けるし Route53 も必要なので、ELB を切り替える方法を試しました。

AutoScaling Group が参照する Launch Configuration を変更したとしても起動済の EC2 インスタンスには影響しません。その AutoScaling Group で新しく起動するインスタンスでのみ新しい Launch Configuration で起動します。なので AMI 更新時に AutoScaling Group はリソースが再作成されるようにする必要があります。これは AutoScaling Group の name に Launch Configuration の name を含めればよいです、後述の通り Launch Configuration の変更時には別名で再作成させるため、Launch Configuration の name が変われば AutoScaling Group の name も変わって、AutoScaling Group も再作成になります。

AutoScaling Group が再作成されるとき、デフォだと「古いリソースの削除→新しいリソースの作成」の順番で処理されます。これだと再作成するときに一時的にインスタンスが全滅してしまうので lifecyclecreate_before_destroy = true を指定して「新しいリソースの作成→古いリソースの削除」の順番で処理されるようにします。

Launch Configuration は作成後に更新ができず、変更があるときはリソースの再作成が必要になります。再作成時に lifecyclecreate_before_destroy = true を指定していると一時的に新旧のリソースが同時に存在するため名前の重服を避けるための name_prefix を設定します。これで Launch Configuration の name は Terraform がタイムスタンプを元に自動生成します。

AutoScaling Group と ELB のターゲットグループは aws_autoscaling_grouptarget_group_arns で行う必要があります。aws_autoscaling_attachment リソースでも紐付けできますが、aws_autoscaling_attachment だとデプロイ時に一時的に 503 になる期間が生じます。aws_autoscaling_attachment だと新しい AutoScaling Group が作成された後に ELB にアタッチされるため、ヘルスチェックのタイプが作成直後は EC2 になります。そのため EC2 インスタンスのステータスが OK になった時点で 古い AutoScaling Group が ELB からデタッチされるため、その後、新しい AutoScaling Group を ELB にアタッチしして新しい EC2 インスタンスで Web サーバの準備が終わって ELB のヘルスチェックが完了するまでの期間がダウンタイムになります。

まとめると、次のようなリソース定義になります。

resource "aws_launch_configuration" "app" {
  name_prefix     = "hello-asg-app-"

  // snip

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_autoscaling_group" "app" {

  name                 = "${aws_launch_configuration.app.name}"
  launch_configuration = "${aws_launch_configuration.app.name}"

  // snip

  health_check_type   = "ELB"
  target_group_arns = ["${aws_lb_target_group.app.arn}"]

  lifecycle {
    create_before_destroy = true
  }
}

なお、この方法だとひとつの ELB に新旧両方の AutoScaling Group がアタッチされている期間があるため、ELB からの転送先に一時的に新旧が混在します。

Launch Template を使う場合は Launch Template がバージョニングをサポートしているので、aws_launch_configurationname_prefixcreate_before_destroy のような仕込みは不要です。ただし Launch Template の更新時に AutoScaling Group の再作成を行わせる必要があるのは変わらないので、AutoScaling Group の name には Launch Template のバージョンを含めます。

resource "aws_autoscaling_group" "app" {

  name = "hello-asg-app-${aws_launch_template.app.latest_version}"

  launch_template = {
    id      = "${aws_launch_template.app.id}"
    version = "${aws_launch_template.app.latest_version}"
  }

  health_check_type   = "ELB"
  target_group_arns = ["${aws_lb_target_group.app.arn}"]

  lifecycle {
    create_before_destroy = true
  }
}

スケーリングポリシー

メトリクスに基づいた、いわゆるオートスケーリングの設定には下記の3つのポリシーが使用できます。

  • ターゲット追跡スケーリング
  • ステップスケーリング
  • 簡易スケーリング

ターゲット追跡スケーリングが一番後発です。基本的にターゲット追跡スケーリングを使っておけば OK です。

ターゲット追跡スケーリング

指定したメトリクスが指定した値に近づくように自動的にスケーリングします。メトリクスには以下の事前定義されたメトリクスが指定できます。

  • ターゲット別の Application Load Balancer リクエストの数
  • CPU の平均使用率
  • 平均ネットワーク入力 (バイト)
  • 平均ネットワーク出力 (バイト)

スケーリングポリシーを設定すると、マネジメントコンソールからは変更や削除ができない CloudWatch アラームが自動で作成されます。例えば「CPU の平均使用率」を 40 に設定すると次のような CloudWatch アラームが自動的に作成されます。

  • CPUUtilization > 40 for 3 datapoints within 3 minutes
  • CPUUtilization < 36 for 15 datapoints within 15 minutes

この2つの閾値に収まるようにインスタンス数が AutoScaling Group の Min と Max の間で自動的に調整されます。さらに CPUUtilization < 36 のアラームはCPU使用率がほぼゼロのまま放置しているとさらに低い値に自動的に変化したので、スケーリング閾値のアラーム自体も自動で調整されるようです。

マネジメントコンソールからは事前定義済の前述の4つのメトリクスしか選択できませんが、任意のメトリクスが指定できます。ただし、AutoScaling Group のインスタンス数に対して逆相関(インスタンスが増えれば値が減る)なメトリクスでなければ意味がありません。

Terraform で事前定義済のメトリクスを指定するには次のようにします。

resource "aws_autoscaling_policy" "cpu" {
  autoscaling_group_name = "${aws_autoscaling_group.app.name}"
  name                   = "cpu"
  policy_type            = "TargetTrackingScaling"

  target_tracking_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ASGAverageCPUUtilization"
    }

    target_value = 40.0
  }
}

同じメトリクスをカスタムメトリクスとして指定するなら以下のようになります。

resource "aws_autoscaling_policy" "cpu" {
  autoscaling_group_name = "${aws_autoscaling_group.app.name}"
  name                   = "cpu"
  policy_type            = "TargetTrackingScaling"

  target_tracking_configuration {
    customized_metric_specification {
      namespace   = "AWS/EC2"
      metric_name = "CPUUtilization"
      statistic   = "Average"

      metric_dimension {
        name  = "AutoScalingGroupName"
        value = "${aws_autoscaling_group.app.name}"
      }
    }

    target_value = 40.0
  }
}

簡易スケーリングとステップスケーリング

簡易スケーリングとステップスケーリングは、スケーリングポリシーと、スケーリングポリシーをアクションとして呼び出す CloudWatch アラームを別々に作成する必要があります。

簡易スケーリングとステップスケーリングはよく似ていて、簡易スケーリングがスケーリングの調整値(インスタンスをどれだけ追加するか、とか)が1段階しか設定できないのに対して、ステップスケーリングならアラームを超過した量に応じて、段階的にスケーリング調整値を指定できます(閾値を超えたら 4 台追加するけれども 20% 超えてるなら一気に 10 台追加する、とか)。

なお、ステップスケーリングのみ metric_aggregation_type が指定できます、が、何に使われるものなのかわかりません・・・。対応するアラームの statistic と同じものを指定しておけばよいのかな? なお、API リファレンスでは metric_aggregation_type には Minimum/Maximum/Average のいずれかしか指定できないことになっているのですが実際には Sum も指定できるようです。SampleCount は指定できないようです。

スケジュールに基づくスケーリング

指定された間隔や cron 式による時刻に AutoScaling Group の Desired Capacity や Min Size や Max Size を変更します。特定の曜日や特定の時間帯だけスケールインさせたりできます。

詳細モニタリング

スケーリングポリシーを設定するなら、詳細モニタリングを有効にして1分ごとにメトリクスが記録されるようにし、迅速にスケーリングできるようにしておくと良いです。

resource "aws_launch_template" "app" {

  // snip

  monitoring {
    enabled = true
  }
}

クールダウンとウォームアップ

クールダウンは手動スケーリングや簡易スケーリングポリシーで適用される。クールダウン期間中は簡易スケーリングポリシーによるスケーリングが発生しなくなる。

手動スケーリングではクールダウンをなしにするか Group に設定されたデフォルトのクールダウン期間を用いるかを選択できる。簡易スケーリングポリシーではスケーリング固有のクールダウン期間を指定するか、Group に設定されたデフォルトのクールダウン期間を用いるかを選択できる。

ウォームアップはターゲット追跡スケーリングとステップスケーリングで適用される。ウォームアップ期間中のインスタンスはスケールアウトの調整値を適用するときの元数として使用されない。例えば、現在5台のインスタンスが起動中で、うち2台がウォームアップ期間だとすると、このときにインスタンスを3台追加する調整値のスケーリングポリシーが実行されたとしても、2台がウォームアップ中なので1台しか追加されない。

ライフサイクルフック

ELB にアタッチされる AutoScaling Group の場合は前述の通り ELB の転送先の切り替えによる Blue/Green Deployment で無停止でアップデートできます。

Terraform が AutoScaling Group を再作成するとき、Terraform は新しい AutoScaling Group が利用可能になるまで待機してから古い AutoScaling Group を削除します。AutoScaling Group のヘルスチェックタイプを ELB にすれば AutoScaling Group が利用可能かの判断は ELB のヘルスチェックが Healthy となったときになるため、Terraform が AutoScaling Group を再作成するとき、新しいインスタンスが ELB で Healthy になるまで Terraform は待機します。

一方、ELB が絡まないワーカーのようなインスタンスだと前述のような ELB のヘルスチェックが使えないため簡単ではありません。EC2 インスタンスのステータスが OK となった時点で Terraform が新しい AutoScaling Group を利用可能として待機が終わってしまうためです。

ライフサイクルフックを使えばインスタンスのステータスが OK となっただけでは利用可能とはならず、準備が整った任意のタイミングまでインスタンスが利用可能と判断されるのを待機できるので、ワーカーのようなインスタンスでも Blue/Green Deployment できます。

例えば、下記のようなスクリプトをユーザーデータに設定し、インスタンスの開始後に準備が整うまで利用可能になるのを待機させることができます。

#/bin/bash

set -eux

# インスタンスメタデータからインスタンスIDとリージョンを取得
instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
region=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed 's/.$//')

# インスタンスのタグから AutoScaling Group を取得
group_name=$(aws ec2 describe-instances \
  --region "$region" \
  --instance-ids "$instance_id" \
  --query 'Reservations[].Instances[].Tags[?Key==`aws:autoscaling:groupName`][].Value' \
  --output text)

# ここでなにか処理して準備ができるまで待つ

# complete-lifecycle-action を呼び出してライフサイクルを続行する
# この処理が呼ばれるまでは新しいインスタンスは利用可能とならないので
# Terraform も待機される
aws autoscaling complete-lifecycle-action \
  --region "$region" \
  --auto-scaling-group-name "$group_name" \
  --lifecycle-hook-name "hello-asg-app-launching" \
  --lifecycle-action-result "CONTINUE" \
  --instance-id "$instance_id"

ただ、インスタンスが生きたままインスタンスの中のサービスが死んでも AutoScaling による故障が検出されないので、ヘルスチェックも独自に実装して適宜 aws autoscaling set-instance-health でインスタンスを Unhealthy にするか、software watchdog timer などでインスタンスを停止するなどの対策が必要になるかもしれません。

Application AutoScaling

今回試したものは EC2 インスタンスをスケーリングするための EC2 Auto Scaling ですが、その他の AWS リソースのスケーリングのための Application AutoScaling というものもあります。

Packer

カスタム AMI を作成するために Packer を使いました。手作業や AWS CLI で頑張るよりはだいぶ楽だと思います。それでも Docker イメージを作成するのと比べればそうとう手間ですが。

さいごに

頻繁に更新されるシステムで都度 AMI を作り直してデプロイするのは時間もかかるし検証も面倒そうなので、AMI を更新するのはミドルウェア構成が変わったときだけにして、アプリのソースは tar.gz で固めて S3 に置くなどしてインスタンスの開始時に S3 から展開してサービスを開始、アプリの更新時は AMI はそのままでアプリのソースだけ S3 経由でデプロイするのが運用しやすそうです(ミドルウェアレイヤーだけイミュータブルなイメージ)。

また、スケーリングポリシーはそれが必要となるようなシステムに成長するかどうかはやってみなければわからないだろうので最初は設定せず、最初は必要に応じて手動スケーリングで良いだろうと思います。EC2 インスタンスは AWS 側の都合でわりと突然死することがあるので AutoScaling でインスタンス数の維持(故障したら自動で再作成)だけでも十分有用です。

ただ、AutoScaling のために AMI をうまく作るために変に消耗するぐらいなら、ECS とかで Docker イメージをアプリのコード込でデプロイするのでも良いように思います。

Amazon Elastic Container Service for Kubernetes (EKS) を Terraform で素振り

少し前に EKS が Tokyo リージョンで使えるようになったので素振りしたメモ。残骸はこちら

なお、てっきり EKS も Fargate が使えるものだと思っていたのですが、そうでもなかったので(将来的にはできるようになる?)、EC2 でワーカーノード作ってます。

EKS の費用

EKS クラスタ を作成すると 1 時間あたり 0.20 USD、さらにクラスタのワーカーノードのために EC2/EBS の料金、あと Service で LoadBalancer を指定すると ELB の費用も必要です。

kubectl と aws-iam-authenticator

EKS を使用するためには Kubernetes の kubectl コマンドと、kubectl で IAM 認証を使うための aws-iam-authenticator をインストールする必要があります。

kubectl は Kubernetesの公式ドキュメント に則ってディストリビューションのパッケージマネージャーでインストールしてもよいし、AWSでバイナリがホストされている のでそれを直でインストールしても良いです。

aws-iam-authenticator も AWS にバイナリがホストされているのでそこからインストールします。

クラスタを作成

EKS クラスタの作成には結構な時間がかかります(8分ぐらいかかった)。

ECS クラスタは一瞬で作成されていましたが、ECS の場合はなにかインスタンス的なものが作られているわけではないのに対して、EKS クラスタの場合は Kubernetes のコントロールプレーン(Kubernetes Master)のインスタンスが実際にプロビジョニングされるためです(たぶん)。ELB や RDS と同類のものだと思えば良いですね。

なお、始めにお試しでマネジメントコンソールから作成してみたところ下記の記事のような問題で kubectl が失敗したので AWS CLI で作成するか Terraform などで作成するのが良さそうです。

タグ

EKS で使用する VPC・サブネット・EC2インスタンス には kubernetes.io/cluster/${cluster_name} のような名前のタグが必要です。このタグを目印に EKS がこれらを制御するようです。

shared はその VPC やサブネットが複数のクラスタから使用されることを許可します。値 owned はそのクラスタ専用になるようですが、どういう違いがあるのかはよくわかりませんでした。

なお、EKS のユーザーガイドにはそのような記述が無いようなのですが、ワーカーノードにアタッチするセキュリティグループにも同様にタグが必要です。

コントロールプレーンのセキュリティグループ(クラスタ作成時に指定するセキュリティグループ)ではなく、EC2インスタンスにアタッチするセキュリティグループです。Kubernetes が ELB を自動的に作成した際に ELB のセキュリティグループからワーカーノードのセキュリティグループへアクセス可能にするために、ワーカーノードのセキュリティグループが Kubernetes によって自動的に更新されるため、そのためにタグ付けが必要です。

タグがないと、ELB の作成時の更新(ELB のセキュリティグループからのアクセス許可を追加)は行われるものの、ELB の削除時の更新(追加されたアクセス許可を削除)が行われなくなります。

kubeconfig

EKS クラスタの作成後、kubectl で制御できるように ~/.kube/config を作成する必要があります。手作業で作成しなくても AWS CLI で下記のようにさっと作成できます。

aws eks update-kubeconfig --name hello-eks

Launch Configuration

EKS と直接関係はありませんが、ワーカーノードを Auto Scaling で作成できるようにするために Launch Configuration を作成する場合、使用している IAM User や IAM Role が下記のようにソースアドレスで制限されているとダメでした。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "NotIpAddress": {
          "aws:SourceIp": [
            "192.0.2.123/32"
          ]
        }
      }
    }
  ]
}

Launch Configuration 以外にもいくつかソースアドレスで制限しているとダメな AWS リソースがあったと思います。

aws-auth の ConfigMap

ワーカーノードをクラスタと結合(普通はジョインと言うと思う)するためにはクラスタの作成後に Kubernetes に EC2 インスタンスプロファイルを認証するための ConfigMap を反映する必要があります。

aws-auth-cm.yaml をダウンロードしてインスタンスプロファイルの Role の ARN(Terraform の aws_iam_role.node.arn)を書き換えて反映します。

curl -O https://amazon-eks.s3-us-west-2.amazonaws.com/cloudformation/2018-08-30/aws-auth-cm.yaml
vim aws-auth-cm.yaml # rolearn  の部分を書き換え
kubectl apply -f aws-auth-cm.yaml

Kubernetes の Deployment や Service を作成

Service は ELB を使うので spec.typeLoadBalancer にします。

kubectl apply -f deploy.yaml

Service を作成すると ELB および ELB にアタッチされるセキュリティグループが自動的に作成され、さらに ELB からワーカーノードにアクセスできるようにするために、ワーカーノードのセキュリティグループに ELB のセキュリティグループからのアクセス許可が自動的に追加されます。

ELB の DNS 名は kubectl で確認できます。

kubectl get service httpd -o json | jq '.status.loadBalancer.ingress[].hostname' -r

サブネットの構成

クラスタの作成時に指定するサブネットで Public と Private を両方含めておくと、Kubernetes は自動的にサブネットの Public と Private を判断して Internet facing な ELB は Public に配置されます。

Private サブネットにはワーカーノードが配置されるわけですが、それは Kubernetes によって行われるわけではなく普通に EC2 インスタンスを実行したり AutoScaling を使うなりで配置するものなので、クラスタ作成時のサブネット指定は ELB が配置される Public サブネットだけで良いような気もしたのですが、ユーザーガイドを読んだ感じ Private サブネットも指定する必要があるようです。

クラスタ作成時に指定したサブネットには Kubernetes のコントロールプレーンと通信するための ENI が自動的に作成されるようなので、ワーカーノードが配置される Private サブネットも指定する必要がある、ということだと思います。

さいごに

EKS のユーザーガイドは下記にあります。

がしかし、これだけだと EKS の Kubernetes とは無関係な AWS の特有の事情しかわからないので、ちゃんと使うなら Kubernetes のドキュメントを読み進める必要があります。

クラスタを作成するだけでワーカーノードがなくても費用がかかるし、EC2 でワーカーノードを準備する必要があるので ECS と比べてお手軽感はありませんね。

また、ECS ならマネジメントコンソールで Task や Service の状態がいろいろ見れますが、EKS だとマネジメントコンソールではほとんどなにも見えなくて、基本的には kubectl を使う必要があります。ただ、どうせ CLI メインになるだろうので AWS CLI とか ECS CLI とかよりも kubectl で操作できる EKS の方が良いかもしれません。GUI が必要なら Kubernetes Dashboard というものも使えるようです。

EKS と直接は関係ない問題ですが、ワーカーノードを作成するために AutoScaling を使ってみましたが Terraform だと AutoScaling で AMI のアップデードに課題がありそうです。Rolling Update や Blue/Green Deployment は Terraform 単体できれいにやるのは難しそうです。

Amazon Elastic Container Service (ECS) を Terraform で素振り

ECS を Terraform で素振りしたメモ。Fargate 前提です。残骸はこちら

Fargate の費用

ECS は EC2 でクラスタを作るか Fargate かを選択できます。Fargate の方がマネージドなので楽ですが割高です。

ざっくりと EC2 の t3 インスタンスを比べてみます。Fargate の費用は以下のとおりですが。

AWS Fargate の料金

  • CPU → per vCPU per hour 0.0632USD
  • MEM → per GB per hour 0.0158USD

EC2 の t3 の費用と比較すると次のとおりです。

タイプ vCPU MEM USD/時間 Fargate
t3.nano 2 0.5 GiB 0.0068 0.1343
t3.micro 2 1 GiB 0.0136 0.1422
t3.small 2 2 GiB 0.0272 0.1580
t3.medium 2 4 GiB 0.0544 0.1896
t3.large 2 8 GiB 0.1088 0.2528
t3.xlarge 4 16 GiB 0.2176 0.5056
t3.2xlarge 8 32 GiB 0.4352 1.0112

Fargate がメモリに比べて CPU が高い?ため、小さめのインスタンスだと Fargate がかなり割高ですが、大きめのインスタンスだとだいたい2倍程度です。

ただし、EC2 はこれとは別に EBS の費用も必要です。Fargate のストレージはタスクあたり 10GB(コンテナあたり?)+ボリュームマウント用に 4GB と固定なものの、追加の費用はかからなさそうです。また、EC2 だと Rolling アップデートや Blue/Green デプロイしようとするとある程度の余剰(Blue/Green なら倍)リソースが必要になります(EC2 でもそのときだけスケールさせればいいのかもしれないけどデプロイがかなり辛くなりそう)。

なお、Fargate の最小構成は 0.25 vCPU / MEM 0.5 GB なので、最小でも .0237 (USD/時間) なので t3.nano と比べてだいぶ高いです。

クラスタとサービスとタスク

タスクは複数のコンテナによって構成されていて、事前に作成されたタスク定義をひな型として実行されます。ひとつのタスクの中の複数のコンテナは同じノードで実行されます。Fargate で実行するときのネットワーキングタイプの awsvpc だとタスクごとに ENI がアタッチされます。

サービスは↑のタスクが指定数が実行され続けるように維持したり、タスクのポートをELB のターゲットグループへ登録したりします。

クラスタはタスクを実行するためのコンピューティングリソースです。

ぐぐるともっとわかりやすい説明がたくさんあります。

タスクロールとタスク実行ロール

ECS でサービスやタスクを実行するとき、「タスクロール」と「タスク実行ロール」の2つのロールを設定します。

「タスク実行ロール」はタスクを実行するためのロールで(そのまんま)、 ECR からイメージを Pull したり、ログを CloudWatchLogs に記録するために使用されます。マネジメントコンソールで操作するのであれば基本的に自動で作成されるもので問題ありません。Terraform なら

「タスクロール」は実行されるコンテナに付与されるロールです。EC2 のインスタンスプロファイルみたいなものです。

新しいイメージのデプロイ

イメージを Dockerhub にプッシュしたあと、サービスの更新で「新しいデプロイの強制」を ON にして更新すると、新しいイメージがプルされてコンテナが開始され、古いコンテナが停止されます。AWS CLI でも相当のことはできます。

デフォルトだと Rolling update なので新旧のコンテナが混在します。試していないですが Blue/Green にもできます。

Terraform 単体だと・・・イメージをビルドして Dockerhub にプッシュするたびに、イメージのタグを変更し、tf ファイルのタスク定義を書き換えるしか無いですかね。あるいはタグは latest にして、デプロイには AWS CLI を使うとか。

タスクのスケジューリング

ECS のマネジメントコンソールに「タスクのスケジューリング」というのがあって、特定のタスク定義から Cron 風に定期的にタスクを実行させたりできます。ただし、これは実は CloudWatch Event のターゲットとして ECS タスクを指定しているだけなので、AWS CLI や Terraform で設定するときは CloudWatch Event の方を設定します。

プライベートサブネット

ECS サービスをプライベートサブネットに入れる場合、Docker イメージをプルしたりログを CloudWatch Logs に送信したりするために NAT ゲートウェイなり PrivateLink なりが必要です。 以下によると ECS からのイメージのプルには ECR の 2 つのエンドポイント以外に S3 も必要なようです。

ECS CLI

ECS のクラスタやタスクの作成や更新を行うための CLI ツールです。

ecs-cli up で VPC とサブネットを作成できます。が、サブネットをフロントとバックで分けたりセキュリティグループを細かく設定したりしようとすると ECS CLI だけでは完結できないので VPC などは Terraform で作っとけば良いように思います。

docker-compose.yml ファイルでタスクを定義をして、VPCやサブネットなどの ECS 固有のパラメータを ecs-params.yml で指定し、ecs-cli compose service up でサービスを開始できます。

ecs-cli compose service up \
    --cluster hello-ecs-cluster \
    --launch-type FARGATE \
    --create-log-groups \
    --target-group-arn arn:aws:elasticloadbalancing:ap-northeast-1:999999999999:targetgroup/hello-ecs-http/9999999999999999 \
    --container-name app \
    --container-port 80

普段 docker-compose を使っていれば docker-compose.yml に慣れ親しんでいるので良いのですけど・・・ELB(ALB)との紐付けは ECS CLI のコマンドラインオプションで指定するしかない?

ecs-cli configure で IAM アクセスキーなどの認証情報やデフォルトのクラスタ・起動タイプ(Fargate/EC2)・リージョンなどを設定します。ただ、認証情報は AWS CLI のために設定した認証情報もそのまま使えるので ECS CLI 用に新たに設定する必要は無いと思います。デフォルトのクラスタや起動タイプも都度コマンドラインオプションで指定しても良いように思うので、ecs-cli configure しなくても良いような気もします。

うーん・・・ Terraform で十分な気がする? VPC やサブネットの ID を ecs-params.yml にベタ書きする必要があるし。

強いて言えば ECS CLI ならタスクの強制リスタート(新しいイメージをデプロイしたあとにそのイメージで起動し直す)とか、ecs-cli compose service ps コマンドでタスクの一覧をさっと見たり、ecs-cli compose run でタスクを one-shop で実行したり、ECR から pull/push も ECS CLI からできるので、Terraform と併用すると良いかも?

さいごに

マネジメントコンソールを触っていると、チュートリアル代わりなのだと思いますが「今すぐ始める」で CloudFormation でばこーんと一通りの環境を立ち上げてお試しすることができます。

また、「今すぐ始める」を使わなくても、クラスタを作成するときに一緒に VPC も作成できたり(これも CloudFormation だったと思う)、サービスを作成するときに一緒に ELB も作成できたり(これは CloudFormation ではなかったと思う)、至れり尽くせりなのですが、逆にどこでなにが作成されているかわかりにくいので、お試しで使う以外ではこれらの便利作成機能は使わなくて良いと思います。

Terraform で作成してみましたが、Fargate なら VPC(とそれに紐付くいろいろ)の作成が一番めんどくさくて、ECS 固有のものはタスク定義が Terraform の中にさらに JSON を書く必要があって微妙なの以外は難しいところはなさそうです。ただ EC2 でクラスタを組むのと比べると制限もあります。

Fargate だと Docker Volumes が使用できません。ので EFS をマウントして永続化ボリュームにしたりできません。Fargate では永続化ストレージには RDS とか S3 とサービスを使うしか無いようです。そもそも EFS のようなファイルシステムが必要という時点でなにかおかしいという意見もあると思いますが。

Fargate だとネットワーキングは awsvpc で固定です、host や bridge は使用できません。ただ awsvpc で十分な気もします。host や bridge でなければ困るようなユースケースあるかな? 強いて言えば awsvpc だとタスクごとに ENI が作成されるのでタスクをたくさん作ると ENI の上限にかかりやすいようです。

でもたいていのユースケースで Fargate で問題ないと思うし、スケールのために EC2 もオートスケールさせるのは Fargate と比べて面倒くさすぎるし、基本的に Fargate で良いと思う。

.

.

.

と思ったけど、オートスケールせずに固定的にリソース確保するのなら EC2 でクラスタ作るのでも良いかも。その場合 Blue/Green はまあ無理だけど、素のままでも Rolling Update できて Cron の冗長化も考えなくて良くなるなら、それだけでも素の EC2 と比べれば十分メリットはあるような気がする。

Fargate だとホストに SSH できないので、なにか問題があるときの調査がめちゃくちゃ困難だし。ただし、その場合でも EC2 インスタンスの可用性や手動スケーリングの容易さのために Auto Scaling Group は使っておいて良いと思う。

.

.

.

いやまあでも Fargate の制限は MySQL on EC2 に対する RDS for MySQL の制限みたいなものだと思えば Fargate 一択という気もする

virt-builder でサクッと作ったゲストが好みじゃなかった件

だいぶ前に Qiita で virt-builder でゲストを作って virt-resize でリサイズして virt-customize でカスタマイズ という記事を書いていて。

virt-builder ふむふむ便利そう、だがしかしなんか気に入らないので普通にゲスト作るときはやっぱ Kickstart だわ

と思ったはずなのだけど、なにが気に入らなかったのか忘れてしまったのでそのメモ。

virt-builder でサクッとゲストを作る

virt-builder という libvirt 管理下の KVM などの仮想環境にサクッとゲストを作るコマンドがあります。libguestfs-tools-c パッケージに含まれているのでインストールします。

yum -y install libguestfs-tools-c

まずはゲスト用のボリュームを作成します。

lvcreate vg1 -n vm.ore-no-virt -L 6G

ゲストのイメージの中身を弄るためのスクリプトを作ります。

cat <<'__RUN__'> run.sh
set -eux

# selinux
sed -i '/^SELINUX=/c SELINUX=disabled' /etc/selinux/config

# ngyuki
useradd ngyuki -m -g wheel

# authorized_keys
mkdir -p /home/ngyuki/.ssh
curl -fsSL https://github.com/ngyuki.keys | awk 1 > /home/ngyuki/.ssh/authorized_keys
chown -R ngyuki: /home/ngyuki/.ssh
chmod 700 /home/ngyuki/.ssh
chmod 600 /home/ngyuki/.ssh/authorized_keys

# sudoers wheel
tee /etc/sudoers.d/wheel <<EOS
%wheel ALL=(ALL) NOPASSWD: ALL
Defaults:%wheel env_keep += SSH_AUTH_SOCK
Defaults:%wheel !requiretty
Defaults:%root  !requiretty
EOS

# sudoers chmod
chmod 0440 /etc/sudoers.d/wheel

# ipv6
tee /etc/sysctl.d/ipv6-disable.conf <<EOS
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
EOS

# sshd
sed -i '/UseDNS /c UseDNS no' /etc/ssh/sshd_config
sed -i '/PermitRootLogin /c PermitRootLogin yes' /etc/ssh/sshd_config
sed -i '/AddressFamily /c AddressFamily inet' /etc/ssh/sshd_config

# postfix
postconf -e inet_protocols=ipv4
__RUN__

virt-builder でゲストのイメージを作ります。1回目はイメージのテンプレートがダウンロードされるので結構時間がかかります。2回目以降はキャッシュされているので早いです。

virt-builder centos-7.4 \
  --output /dev/vg1/vm.ore-no-virt \
  --arch x86_64 \
  --hostname ore-no-virt \
  --root-password disable \
  --timezone Asia/Tokyo \
  --run run.sh \
  --firstboot-command '
      nmcli con modify eth0 \
        connection.autoconnect yes \
        ipv4.method manual \
        ipv4.addresses 10.12.16.99/23 \
        ipv4.gateway 10.12.16.1 \
        ipv4.dns 10.12.16.2 \
        ipv6.method ignore
      nmcli con up eth0
    '

イメージを元に libvirt にインポートします。

virt-install \
  --name ore-no-virt \
  --hvm \
  --virt-type kvm \
  --ram 1024 \
  --vcpus 1 \
  --arch x86_64 \
  --os-type linux \
  --os-variant rhel7 \
  --boot hd \
  --disk path=/dev/vg1/vm.ore-no-virt \
  --network network=back \
  --graphics none \
  --serial pty \
  --console pty \
  --import \
  --noreboot

ゲストを開始して SSH でログインできます。

virsh start ore-no-virt
ssh ngyuki@10.12.16.99

気に入らなかった理由

イメージのテンプレートのパーティション構成がなんか好きではなかったので使ってなかったんでした。

sudo parted -l /dev/vda
#=> Model: Virtio Block Device (virtblk)
#=> Disk /dev/vda: 6442MB
#=> Sector size (logical/physical): 512B/512B
#=> Partition Table: msdos
#=> Disk Flags:
#=>
#=> Number  Start   End     Size    Type     File system     Flags
#=>  1      1049kB  1075MB  1074MB  primary  xfs             boot
#=>  2      1075MB  1720MB  645MB   primary  linux-swap(v1)
#=>  3      1720MB  6442MB  4723MB  primary  xfs

今日日仮想サーバなら /boot とか無くていいしスワップも必要なら別ディスクとしてアタッチするし、1つのディスクで複数パーティション切るぐらいなら複数のディスクをアタッチすればいいんじゃね?と思うので、仮想サーバでは基本的に1ディスク=1パーティションとしてます。

ので、この方法での環境構築はたぶんやりません。


この記事、元は 2018/03 ごろに書いてたものです。