EKSクラスタの効率的なリソース管理にSpotinst Oceanを使おう

はじめまして。2019年5月にスタディサプリENGLISH SREグループにJoinした横山です。

以前の記事にもありますが、現在Amazon Elastic Container Service for Kubernetes(以下EKS)の本番運用に向けて各要素の検討を行っています。今回は、EKSクラスタのリソース管理にSpotinst Oceanの活用を検討し、TerraformでEKSクラスタと同時に構築してみたことについて書いていきます。

Spotinstとは?

AWS、GCP等パブリッククラウドのIaaSを中心としたサービスのコスト最適化をしてくれるSaaSであり、現在はElastigroupとOceanの2つのプロダクトがあります。AWSでスポットインスタンスの利用を検討したことがあれば聞いたことがある方も多いのではないでしょうか。

Elastigroupは、Spotinst独自のアルゴリズムと過去の統計データを元に、中断されようとしているスポットインスタンス(AWSであれば)を自動的に他のインスタンスタイプや別ゾーンに移行、またはオンデマンドインタンスへ切り替えてくれます。可用性を保ちつつ、スポットインスタンスをフル活用してコスト削減を可能にしてくれます。

Oceanは、パブリッククラウド上のKubernetes(以下k8s)クラスタ(EKS等マネージドに限らない)の管理をターゲットにしています。k8sのNode管理にElastigroupを利用し、さらにk8sのPodのリソース要求に応じてクラスタのサイズ(Nodeの数)を調整してくれるというのが主な機能です。

今回は自前でEKSにCluster Autoscalerを設定するよりもOceanを使うメリットはあるか、OceanにHorizontal Pod Autoscaling(以下HPA)やVertical Pod Autoscaling(以下VPA)の機能はあるか、EKSクラスタを構築する際にSpotinstと繋ぐ方法はどうするか、といった観点で調査・検証をしたのでこれらの点についてまとめていきます。

結論から言うと、下記に挙げる特徴的な機能があり、Nodeのリソース管理を容易に効率的にでき、今後PodのAutoScale関連のより便利な機能のリリースも見込まれ、かつスポットインスタンス利用とリソース効率化によるコスト削減も可能なためOceanを導入する方向で考えています。

特徴的な機能

Tetris Scaling

Cluster AutoscalerでNodeが追加される際、PodのResource Requestsに合わせて適切なインスタンスタイプを選んでくれる機能です。実際に試してみましょう。

Nodeが1台しかない状態で、 resources.requests.cpu1を指定したPodを20台立ててみます。

クラスタのリソースが足りないため立てた直後はPendingとなりますが、k8sクラスタ内に立てておくspotinst controllerがこれを検知しNodeのオートスケールが発生します。

下記がスケール後の様子ですが、ちょうど良い感じのインスタンスのサイズが選択され、効率的にPodが配置されています。利用するインスタンスタイプは指定もできますが、今回は特に指定しておらず、そのときのスポット価格や需要予測に応じて幅広くインスタンスタイプが選ばれるようです。

自前でCluster Autoscalerを立てる場合1)参考:Cluster Autoscaler on AWS、AWSではASGの機能を使ってNodeのオートスケールを管理することになるため、ここまで柔軟にスケールするのは難しくなります。この機能だけでもSpotinstを使う価値はあると言えるでしょう。

ただし、Cluster Autoscaler on AWSの場合、複数のASGをmasterに紐づけることができ、その中のあるASGにnodeSelectorによるNode Affinityの指定が可能となります。このような要件があればこちらを使ったほうが良いかもしれません。

Headroom

Headroomは、その時点で実際にクラスタに必要なリソースよりも多くの余剰リソースを確保しておける機能です。

例えば、リクエストの増加等で急激に要求リソースが増加してPodを水平スケールさせたい場合、Nodeのリソースに空きがなければ新たにインスタンスを追加しなければなりませんが、起動するまでの数分のリードタイムが生じてしまいます。Headroomを使い、ある程度のPodの水平スケールに備えてNodeを多めにクラスタに持っておくことで対策できます。

また、Job/CronJobを利用してバッチ処理を行っていて、このPodに割り当てるためにある程度リソースを確保しておきたいと行ったユースケースも考えられるでしょう。

設定用のGUI。簡単に設定できますね。

Right sizing

OceanでHPAやVPAをやってくれる機能があるのか調べたところ、ちょうど先月(2019年5月)このようなリリースがされていました。

Streamline Pods & containers Right-sizing with Spotinst Ocean VPA

残念ながら現時点ではOceanには独自のHPAの機能はありませんが、上記の記事にこのようにありますので今後に期待です。

We will soon announce our own custom HPA (Horizontal Pod Auto Scaling), which will provide additional scaling elasticity to your Ocean cluster

一方VPAはRight Sizingという機能が上記の記事にあるよう新しくリリースされたので簡単に試してみました。

Metrics Serverを入れる必要があるため入れてみたところ、SpotinstのUIのRight Sizingの箇所に下記が表示されます。

We are collecting your cluster's metrics, and will suggest how to optimize resources within up to 4 days.

しばらく放置しておくと、今度は下記の情報が表示されます。

どうやら、動的にPodのRequestsを書き換えてくれるわけではなく、Resizing Recommendationsがされるだけのようです。まだリリースされたばかりですし、こちらも今後に期待でしょうか。

TerraformでEKSとOceanをまとめて構築してみる

最後に、EKSクラスタとSpotinstをまとめて構築する方法についてです。

SpotinstのUI経由で新規でEKSの構築も可能(Spotinstが用意したCFnテンプレートを利用)ですが、今回はTerraformで管理している既存のVPC等のリソースにEKSとOceanを追加していきたいという要件があるので、Terraformを選択しました。

SpotinstのTerraform Providerがあったためこれを使います2)このProviderは現在Terraform v0.12に対応していませんでした。Terraform v0.11.14で試しています。

SpotinstのUIから設定していくと下記のようにHCLのコード(よく見ると・・・?)がexportできますが、自分で書いてみたほうが早いでしょう。

参考までに今回書いたものを載せておきます。

provider "spotinst" {
  token = "xxx"
  account = "xxx"
}
resource "spotinst_ocean_aws" "eks-cluster" {
  region               = "ap-northeast-1"
  name                 = "${local.eks-cluster-names[0]}"
  controller_id        = "${local.eks-cluster-names[0]}"
  max_size             = 50
  subnet_ids           = ["${aws_subnet.eks_private.*.id[0]}", "${aws_subnet.eks_private.*.id[1]}"]
  image_id             = "${module.eks--cluster.workers_default_ami_id}"
  user_data            = "${base64encode(element(module.eks-cluster.workers_user_data, 0))}"
  security_groups      = ["${module.eks-cluster.worker_security_group_id}", "${aws_security_group.eks_vpc_private.id}"]
  iam_instance_profile = "${element(module.eks-cluster.worker_iam_instance_profile_arns, 0)}"
  root_volume_size     = 50
  fallback_to_ondemand = true
  spot_percentage      = 100
  autoscaler = {
    autoscale_is_enabled = true
    resource_limits = {
      max_vcpu       = 200
      max_memory_gib = 400
    }
    autoscale_headroom = {
      cpu_per_unit    = 1024
      memory_per_unit = 1024
      num_of_units    = 1
    }
  }
  tags = {
    "key" = "k8s.io/cluster-autoscaler/disabled"
    "value" = "true"
  }
  tags = {
    "key" = "kubernetes.io/cluster/${local.eks-cluster-names[0]}"
    "value" = "owned"
  }
}

一度のterraform applyでEKSとSpotinstのリソースを全て構築したいため、既存のVPCやterraform-aws-eks moduleで作ったEKSクラスタ関連のリソースの値を与えています。こうしておくことで設定を更新したい場合、例えばIAM PolicyをNodeのInstance Profileに追加したいような際にも、Instance Profileの定義を追加してterraform applyするだけでSpotinstのほうにも設定が追加されるので、普通にTerraformでAWSリソースを管理するのと同様にSpotinstの設定ができます。

terraform applyした後も少し作業が必要で、EKSクラスタ上にspotinst-kubernetes-cluster-controllerを立てる必要があります。これについては下記のようにSpotinstのUIから方法を確認できます。

最後に

今回はEKSクラスタのリソース管理にSpotinst Oceanを使うことについて書きました。まだざっくり触ってみたという段階ですので指摘やコメント等頂けると嬉しいです。

Spotinstの紹介が大半になってしまいましたが、今後は実際にProductionに導入していった際の課題や知見、新しくSpotinstにリリースされた機能の検証について書いていきたいと思います。

また、Spotinstには本記事の内容だけでなく豊富な機能、Integrationがあります。興味のある方は無料プランもあるので実際に試してみましょう。

気になる料金ですが、"オンデマンドの価格からスポットインスタンスによって割引された価格"の20%です。スポットインスタンスを普通に利用するより多少上乗せされることになりますが、可用性に関する考慮等スポットインスタンスをProductionで利用するための運用コストや、本記事で挙げた機能等が使えることを考えると、払う価値のある額ではないかと思います。

脚注

脚注
1 参考:Cluster Autoscaler on AWS
2 このProviderは現在Terraform v0.12に対応していませんでした。Terraform v0.11.14で試しています。