Spot Fleet化するまでにはまったところと工夫した点
入札するインスタンスタイプとストラテジー
spot fleetの分散ストラテジーには、lowestPrice
とdiversified
がある。lowestPrice
はその時点で入札を入れたプールの中で一番最安値のやつで全台購入する。なので、そのプールが高騰したときに弱い。
じゃあ、diversified
でってことなんだけど、これでも価格の乱高下が大きいプールのものを購入すると、シャットダウン -> 安定したプールのものが購入。ってことで結局どれか一つ安定したプールのものに近づいていく。
どういうことかというと、例えば、r4.xlarge
は乱高下が大きく、c3.xlarge
は安定してる。で、これをdiversified
で買ったとしても、結局最終的に残るのはc3.xlarge
だけになる。
なので、spot advisorというAWSさんがアドバイスしてくれたやつで買うのではなく、過去3ヶ月で安定しているもののみ入札するようにした。c3.xlarge
、r3.xlarge
、i3.xlarge
が安定してたのでこれにオンデマンド価格で入札するようにした。これでそこそこ安定した状態になると判断した。
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 }
この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
ただ、途中で入札をオンデマンド価格まで高くしても払うのはそのときの最低料金で済むということに気づいたので、この方式は採用しなかった。