まーぽんって誰がつけたの?

iOS→Scala→インフラなおじさん技術メモ

Spot Fleet化するまでにはまったところと工夫した点

入札するインスタンスタイプとストラテジー

spot fleetの分散ストラテジーには、lowestPricediversifiedがある。lowestPriceはその時点で入札を入れたプールの中で一番最安値のやつで全台購入する。なので、そのプールが高騰したときに弱い。 じゃあ、diversifiedでってことなんだけど、これでも価格の乱高下が大きいプールのものを購入すると、シャットダウン -> 安定したプールのものが購入。ってことで結局どれか一つ安定したプールのものに近づいていく。

どういうことかというと、例えば、r4.xlargeは乱高下が大きく、c3.xlargeは安定してる。で、これをdiversifiedで買ったとしても、結局最終的に残るのはc3.xlargeだけになる。

なので、spot advisorというAWSさんがアドバイスしてくれたやつで買うのではなく、過去3ヶ月で安定しているもののみ入札するようにした。c3.xlarger3.xlargei3.xlargeが安定してたのでこれにオンデマンド価格で入札するようにした。これでそこそこ安定した状態になると判断した。

f:id:masato47744:20180124225835p:plain

spot instanceのterminate通知をハンドリングするようにした

linuxのupstartという仕組みでデーモンを登録。ただのshを書く。 このシェルの中で、curlで5秒ごとにwhileで確認する。 もし、通知がきたらコンテナインスタンスをDRAINING状態にupdateする。 そして、datadogのAPIを使ってevnetを通知するようにした。

#!/bin/bash
while sleep 5; do
if [ -z $$(curl -Isf http://169.254.169.254/latest/meta-data/spot/termination-time)];
then
/bin/false
else
logger "[spot-instance-termination-notice-handler.sh]: spot instance termination notice detected"
STATUS=DRAINING
ECS_CLUSTER=$$(curl -s http://localhost:51678/v1/metadata | jq .Cluster | tr -d \")
CONTAINER_INSTANCE=$$(curl -s http://localhost:51678/v1/metadata | jq .ContainerInstanceArn | tr -d \")
INSTANCE_ID=$$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
logger "[spot-instance-termination-notice-handler.sh]: putting instance in state $$STATUS"
aws ecs update-container-instances-state --cluster $$ECS_CLUSTER --container-instances $$CONTAINER_INSTANCE --status $$STATUS --region ap-northeast-1

request_body=$$(cat <<EOF_REQ
{
      "title": "[$${ECS_CLUSTER}] spot instance termination notice $${INSTANCE_ID}",
      "text": "start DRAINING and it will be terminated after 2 minutes",
      "priority": "normal",
      "tags": ["environment:${env}","cluster:$${ECS_CLUSTER}","spotinstance_termination"],
      "alert_type": "info"
}
EOF_REQ
)
curl -X POST -H "Content-type: application/json" \
-d $$request_body 'https://app.datadoghq.com/api/v1/events?api_key=${datadog_api_key}'

logger "[spot-instance-termination-notice-handler.sh]: putting myself to sleep..."
sleep 120
fi
done

この処理を書くために、iamなどを調整した。 terraformのtemplate_fileでは、$をエスケープするのに$$を使うこと、bashのヒアドキュメントで$が展開されないようにするために、EOFを'EOF'と囲むことで防げるなどを学んだ。

userdataをS3から取得して実行する形式にした

userdataが書き換わるとresourceが作り直しになってしまっていたので、実際の処理はS3からshをゲットしてきてそれを実行する形式にした。 いい感じにS3にshをuploadしてくれるのもterraformで書けた。

resource "aws_s3_bucket_object" "cluster_setup" {
  bucket  = "${var.cluster_setup_bucket}"
  key     = "cluster-setup-${var.name}-${var.env}"
  content = "${data.template_file.cluster_setup.rendered}"
}

data "template_file" "cluster_setup" {
  template = "${file("${path.module}/cluster_setup.sh")}"

  vars {
    cluster         = "${coalesce(var.cluster_name, "${var.name}-${var.env}")}"
    datadog_api_key = "${var.datadog_api_key}"
  }
}

ゲットしてくるだけのuserdataはこう

#!/bin/bash
yum update -y
yum install -y aws-cli

# execute cluster setup sh from s3
aws s3 cp s3://${cluster_setup_bucket}/${cluster_setup_object_key} cluster_setup.sh
chmod u+x cluster_setup.sh
./cluster_setup.sh

spot fleetの場合tagsをpropagate_at_launchできない(2017/9時点)

autoscalingであれば、こんな感じでできてたやつができない。

  tag {
    key                 = "Name"
    value               = "ecs-cluster-${var.name}-${var.env}"
    propagate_at_launch = true
  }

add support tags for aws_spot_fleet_request · Issue #1232 · terraform-providers/terraform-provider-aws

このissueで語られてるようにまだterraformが対応できてなかった。aws-sdk-goのupdateが必要なようで時間かかりそうと思ったので、aws cliでuserdataで自分でタグ付けすることにした。

INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
aws ec2 create-tags --region ap-northeast-1 --resources $${INSTANCE_ID} --tags Key=Name,Value=ecs-cluster-${name}-${env} Key=Environment,Value=${env}

と思ったけどできるようになってた🎉 Add 'tags' support to aws_spot_fleet_request by jekh · Pull Request #2042 · terraform-providers/terraform-provider-aws · GitHub 試してないから多分だけど。

target capacityとWeightedCapacityについて

spot fleetにはWeightedCapacityといって入札先に重み付けできる機能がある。例えば、vCPUが4のxlarge系のインスタンスとvCPUが2のlarge系のインスタンスを台数はどうでもいいから、最終的に28vCPUになるようにいい感じになってくれっていう指示ができる。

type WeightedCapacity
c3.large 2
m3.large 2
c3.xlarge 4

とWeightedCapacityを設定して、target capacity28にしたら、c3.largetが4, m3.largeが4, c3.xlargeが3 立ち上がった。

2*4+2*4+4*3=28vCPU

ただ、途中で入札をオンデマンド価格まで高くしても払うのはそのときの最低料金で済むということに気づいたので、この方式は採用しなかった。