いまさら vagrant-aws を使ってみる

ずいぶん前に Docker Desktop (Docker for Windows) のために Hyper-V を有効にしたため VirtualBox が使えなくなりました。それに伴って Vagrant も使わなくなりました。

最初は Vagrant が使えなくなるのは辛いかな・・と思っていたのですが、開発系の作業は Docker で事足りているし、サーバ系の作業でどうしても Docker では足りないときは KVM (virt-install) とか EC2 にインスタンスを作るなどすればいいので、Vagrant が使えなくなってもそれほど困りませんでした。

ただ、Vagrant でローカルの VirtualBox に環境を作るのと比べると KVM に virt-install でセットアップしたり EC2 インスタンスをマネジメントコンソールや CLI で作るのは作業のステップが多くて煩雑が気もしています。

そこで、EC2 インスタンスをサクサク作ったり消したりできるよう vagrant-aws を試してみました。vagrant-aws なら Hyper-V 有効なままでも問題なく使用できます。

環境は次のとおりです。

  • Fedora 31 (WSL)
  • Vagrant 2.2.9
  • vagrant-aws 0.7.2

クイックスタート

下記の README のクイックスタートの手順の通りに試してみます。

https://github.com/mitchellh/vagrant-aws

# vagrant をインストール
sudo dnf install https://releases.hashicorp.com/vagrant/2.2.9/vagrant_2.2.9_x86_64.rpm

# vagrant-aws プラグインをインストール
vagrant plugin install vagrant-aws

# ダミーのボックスを追加
vagrant box add dummy https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box

# Vagrantfile を作成
vim Vagrantfile

# プロバイダに aws を指定して開始
vagrant up --provider=aws

# ssh で接続
vagrant ssh sv01

がしかし、いくつかハマりどころがありました。

Vagrantfile

Vagrantfile の内容は下記のようになりました。クイックスタートそのままだと面白くないので複数台の環境を作るようにしています。

class Hash
  def slice(*keep_keys)
    h = {}
    keep_keys.each { |key| h[key] = fetch(key) if has_key?(key) }
    h
  end unless Hash.method_defined?(:slice)
  def except(*less_keys)
    slice(*keys - less_keys)
  end unless Hash.method_defined?(:except)
end

Vagrant.configure("2") do |config|
  config.vm.box = "dummy"

  ["sv01", "sv02", "sv03"].each {|name|
    config.vm.define name do |config|
      config.vm.hostname = name
    end
  }

  config.vm.provider :aws do |aws, override|
    aws.aws_profile = ENV["AWS_PROFILE"]
    aws.region = "ap-northeast-1"
    aws.keypair_name = ENV["AWS_KEYNAME"]
    aws.instance_type = "t2.micro"
    aws.associate_public_ip = true

    aws.region_config "ap-northeast-1" do |region|
      region.ami = "ami-06ad9296e6cf1e3cf"
    end

    override.ssh.username = "ec2-user"
    override.ssh.private_key_path = "~/.ssh/id_rsa"
  end

  config.vm.provision "shell", inline: <<-SHELL
    set -eux
    amazon-linux-extras install -y ansible2
  SHELL

end

VPC やセキュリティグループが指定されていませんが、未指定なら AWS アカウントを作ったときに最初から作成されているデフォルト VPC やセキュリティグループが使用されます。

Undefined HashMap method except

Vagrantfile の先頭に見慣れないものがありますが、これは下記の vagrant-aws のバグに対するワークアラウンドです。

https://github.com/mitchellh/vagrant-aws/issues/566

素のままでは、最新の Vagrant で vagrant-aws は動きません。

aws_profile と region

aws.aws_profile を指定した場合、~/.aws/credentials に書いているクレデンシャルを使いたいだけだとしても ~/.aws/config に該当するエントリが必要です。エントリがないと vagrant-aws でヌルポのようなエラーになりました。

また、aws.aws_profile を指定した場合は Vagrantfile でのリージョンの指定は無視され、~/.aws/config で指定されたリージョンが優先されます。~/.aws/config でリージョンが指定されていない場合は us-east-1 になります。

~/.aws/credentials にクレデンシャルを書きつつ、リージョンは Vagrantfile で指定、みたいにしたかったのですが、出来無さそうです。

さいごに

下記を見るにもう何年もリリースされていないしプルリクも放置されっぱなしのようなので、もうメンテナンスされていないと考えたほうが良いのでしょうか。

https://github.com/mitchellh/vagrant-aws

よくよく考えてみれば Terraform で十分な気もします。インスタンスを立ち上げてからのプロビジョニングが vagrant の方がやりやすいかと思いましたが、Terraform でも remote-exec でできなくもないし、userdata でもできます。

同じようなことを Terraform でやると次のような感じでしょうか。

variable profile {}
variable keyname {}
variable private_key {}

locals {
  servers = {
    sv01 = {}
    sv02 = {}
    sv03 = {}
  }
}

provider aws {
  region  = "ap-northeast-1"
  profile = var.profile
}

data aws_ssm_parameter amazon_linux {
  name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
}

resource aws_instance servers {
  for_each = local.servers

  ami                         = data.aws_ssm_parameter.amazon_linux.value
  key_name                    = var.keyname
  instance_type               = "t2.micro"
  associate_public_ip_address = true

  # user_data = <<-EOS
  #   #!/bin/bash
  #   set -eux
  #   amazon-linux-extras install -y ansible2
  # EOS

  provisioner "remote-exec" {
    inline = [
      <<-EOS
        set -eux
        sudo amazon-linux-extras install -y ansible2
      EOS
    ]

    connection {
      type        = "ssh"
      host        = self.public_ip
      user        = "ec2-user"
      private_key = file(var.private_key)
    }
  }
}

output public_ips {
  value = { for k, v in aws_instance.servers : k => v.public_ip }
}

改めて比べてみると・・うーん、やっぱり Terraform でいいかなと思います。