Spot Fleetを使ってEC2を1/4の料金で運用する

## はじめに

普段EC2でサーバーを運用していて、スポットインスタンスを使うことで約1/4の料金で利用できてる。

![スクリーンショット 2018-01-08 13.40.58.png](https://qiita-image-store.s3.amazonaws.com/0/21172/8f647d55-19b6-0c93-8f9e-9fbbfa612cfd.png)

## スポットインスタンスについて

![スクリーンショット 2018-01-08 13.11.12.png](https://qiita-image-store.s3.amazonaws.com/0/21172/449154a2-2da1-39db-4eb7-de232cf5cd1b.png)

* 基本的にオンデマンドインスタンスに比べてかなり安いがオンデマンドインスタンスより値段が高くなる場合もある
* 設定した最高価格の値段より高くなった時にデフォルトの動作だとインスタンスが削除される

停止にする設定もある。

https://aws.amazon.com/jp/about-aws/whats-new/2017/09/amazon-ec2-spot-can-now-stop-and-start-your-spot-instances/
   
> 容量が指定料金内で利用不可能になりイベントが中断された場合に、Amazon EC2 スポットで Amazon EBS-backed インスタンスを終了する代わりに、それを停止することが可能になりました。

* 同じインスタンスタイプでもavailability zoneで値段が違う
* 上位のインスタンスタイプのほうが安くなる場合もある

## 最高価格

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/using-spot-instances-history.html

> スポットインスタンスをリクエストするときは、デフォルトの上限料金 (オンデマンド料金) を使用することをお勧めします。

スポットインスタンスのインスタンスの価格がオンデマンドインスタンスの価格より高くなる場合があるので、オンデマンドインスタンスの上限に設定にしておいたほうがいい。

## Spot Fleet

Spot Fleetを使うことで、予め最高価格と復数のインスタンスタイプとAvailability Zoneを設定しておくことでその中で一番安いスポットインスタンスをn個用意するということを自動で出来るようになる

また動かしてるスポットインスタンスのインスタンスタイプの価格が高騰して停止した場合に、設定した他の安い条件のものがあった場合にそちらが新規で立ち上がるようになる。

## Terraformを使ったECSで使う場合の設定例

```sh:user_data/ecs.sh.tpl
#!/bin/bash

echo ECS_CLUSTER=${ecs_cluster} >> /etc/ecs/ecs.config
```

```terraform:aws_default_subnet.tf
resource "aws_default_subnet" "1a" {
  availability_zone = "${data.aws_region.current.name}a"
}

resource "aws_default_subnet" "1c" {
  availability_zone = "${data.aws_region.current.name}c"
}
```

```terraform:aws_spot_fleet_request.tf
data "template_file" "aws_instance_app_user_data" {
  template = "${file("user_data/ecs.sh.tpl")}"

  vars {
    ecs_cluster = "app"
  }
}

data "aws_ami" "ecs_optimized" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "architecture"
    values = ["x86_64"]
  }

  filter {
    name   = "root-device-type"
    values = ["ebs"]
  }

  filter {
    name   = "name"
    values = ["amzn-ami-*-amazon-ecs-optimized"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  filter {
    name   = "block-device-mapping.volume-type"
    values = ["gp2"]
  }
}

resource "aws_spot_fleet_request" "app" {
  iam_fleet_role  = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/aws-ec2-spot-fleet-tagging-role"
  spot_price      = "0.1290"
  target_capacity = "2"
  valid_until     = "2019-11-04T20:44:20Z"
  terminate_instances_with_expiration = true

  launch_specification {
    ami = "${data.aws_ami.ecs_optimized.id}"
    instance_type = "t2.large"
    iam_instance_profile = "${aws_iam_instance_profile.app.name}"
    vpc_security_group_ids = ["${aws_security_group.app.id}"]
    user_data       = "${data.template_file.aws_instance_app_user_data.rendered}"
    subnet_id       = "${aws_default_subnet.1a.id}"
    associate_public_ip_address = true

    tags {
      Name = "app"
    }
  }

  launch_specification {
    ami = "${data.aws_ami.ecs_optimized.id}"
    instance_type = "m4.large"
    iam_instance_profile = "${aws_iam_instance_profile.app.name}"
    vpc_security_group_ids = ["${aws_security_group.app.id}"]
    user_data       = "${data.template_file.aws_instance_app_user_data.rendered}"
    subnet_id       = "${aws_default_subnet.1a.id}"
    associate_public_ip_address = true

    tags {
      Name = "app"
    }
  }

  launch_specification {
    ami = "${data.aws_ami.ecs_optimized.id}"
    instance_type = "m4.large"
    iam_instance_profile = "${aws_iam_instance_profile.app.name}"
    vpc_security_group_ids = ["${aws_security_group.app.id}"]
    user_data       = "${data.template_file.aws_instance_app_user_data.rendered}"
    subnet_id       = "${aws_default_subnet.1c.id}"
    associate_public_ip_address = true

    tags {
      Name = "app"
    }
  } 
}
```

この設定の場合t2.largeのap-northeast-1a、m4.largeのap-northeast-1a, m4.largeのap-northeast-1cの中で一番安いもので、かつその料金が$0.1290以下の場合の時にEC2インスタンスを2つ動いてる状態にするという動作になる。
$0.1290はm4.largeのオンデマンドインスタンスの価格。
またSpot Fleetの有効期限は2019-11-04T20:44:20Zで有効期限が切れた場合にインスタンスを停止するという設定になる。

## 最後に

一時的にサーバーが止まっても問題ない用途に使う場合は問題ないが、ダウンタイムをないように運用するのはオンデマンドインスタンスと併用するなど工夫が必要。