GeekFactory

int128.hatenablog.com

AWS Cluster AutoscalerをTerraformとHelmfileでデプロイする

Cluster Autoscalerを利用すると,CPUやメモリの要求量に応じてノード数を自動的に増減させることが可能です.

本稿では,Amazon EKSで以下を利用する方法を紹介します.

Cluster AutoscalerのPodにIAMロールを割り当てるため,あらかじめkube2iamもしくはkiamをデプロイしておく必要があります.詳しくは下記を参照してください.

int128.hatenablog.com

(2020/9/23 追記)Cluster AutoscalerのHelm chartはgithub.com/kubernetes/autoscalerに移動しました。また、権限の割り当てはIAM Roles for Service Accountsが推奨されています。

Worker Auto Scaling Groupの修正

Auto Scaling GroupにAuto Discovery用のタグを追加します.

terraform-aws-eks module を利用している場合は以下のように autoscaling_enabled = true を追加すればよいです.詳しくはモジュールの Autoscaling を参照してください.

  worker_groups_launch_template_mixed = [
    {
      # 中略
      autoscaling_enabled     = true
      asg_min_size            = 1
      asg_max_size            = 8
      asg_desired_capacity    = 4
    },
  ]

kopsを利用している場合は kops edit ig nodes で以下のようにノード用のタグを追加します.

  cloudLabels:
    k8s.io/cluster-autoscaler/enabled: 1
    k8s.io/cluster-autoscaler/CLUSTER_NAME: 1

IAM Roleの作成

Cluster AutoscalerがAuto Scaling Groupを取得したり変更したりするためのIAM Roleを作成します.必要なIAMポリシーは cluster-autoscaler/cloudprovider/aws に記載されています.

# IAM role for Cluster Autoscaler
resource "aws_iam_role" "cluster_autoscaler" {
  name               = "${var.cluster_name}-cluster-autoscaler"
  assume_role_policy = data.aws_iam_policy_document.kube2iam_assume_role.json
}

resource "aws_iam_role_policy" "cluster_autoscaler" {
  role   = aws_iam_role.cluster_autoscaler.name
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "autoscaling:DescribeAutoScalingGroups",
                "autoscaling:DescribeAutoScalingInstances",
                "autoscaling:DescribeLaunchConfigurations",
                "autoscaling:DescribeTags",
                "autoscaling:SetDesiredCapacity",
                "autoscaling:TerminateInstanceInAutoScalingGroup"
            ],
            "Resource": "*"
        }
    ]
}
EOF
}

data "aws_iam_policy_document" "kube2iam_assume_role" {
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
    principals {
      type        = "AWS"
      identifiers = [module.eks.worker_iam_role_arn]
    }
  }
}

Helm chartのデプロイ

stable/cluster-autoscaler chart をデプロイします.

releases:
  # https://github.com/helm/charts/tree/master/stable/cluster-autoscaler
  - name: cluster-autoscaler
    namespace: kube-system
    chart: stable/cluster-autoscaler
    values:
      - cloudProvider: aws
        awsRegion: ap-northeast-1
        autoDiscovery:
          clusterName: {{ .Environment.Values.clusterName }}
        rbac:
          create: true
        podAnnotations:
          # IAM role for Cluster Autoscaler
          iam.amazonaws.com/role: {{ .Environment.Values.clusterName }}-cluster-autoscaler

  # https://github.com/helm/charts/tree/master/stable/metrics-server
  - name: metrics-server
    namespace: kube-system
    chart: stable/metrics-server

environments:
  default:
    values:
      - clusterName: CLUSTER_NAME

Cluster Autoscalerのログにエラーが出ていないことを確認します.IAMロールやIAMポリシーが不適切な場合はエラーが出ます.

スケールアップの動作確認

ここでは以下のように echoserver をデプロイします.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: echoserver
  namespace: echoserver
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: echoserver
    spec:
      containers:
      - image: gcr.io/google_containers/echoserver:1.10
        name: echoserver
        ports:
        - containerPort: 8080

ここで,以下のようにCPU requestを設定します.1ノード2コアの場合は1500mにするとよいでしょう.要求値が大きすぎると,Cluster Autoscalerはノードを追加してもスケジュール不可能と判断してしまいます.

spec:
  template:
    spec:
      containers:
      - image: gcr.io/google_containers/echoserver:1.10
        name: echoserver
        ports:
        - containerPort: 8080
           resources:
              requests:
                cpu: 1500m

Cluster Autoscalerのログを確認します.以下のように Upcoming 1 nodes と表示されると成功です.同時に,Auto Scaling GroupのDesired Capacityが書き換わっていることを確認します.しばらく経つと,ノードが増えてPodがスケジュールされるはずです.

I0927 11:50:35.158353       1 scale_up.go:263] Pod echoserver/echoserver-74fd7d865f-vkzqb is unschedulable
I0927 11:50:35.158391       1 scale_up.go:300] Upcoming 0 nodes
I0927 11:50:35.158521       1 scale_up.go:423] Best option to resize: ASG_NAME
I0927 11:50:35.158540       1 scale_up.go:427] Estimated 1 nodes needed in ASG_NAME
I0927 11:50:35.158556       1 scale_up.go:529] Final scale-up plan: [{ASG_NAME 4->5 (max: 8)}]
I0927 11:50:35.158572       1 scale_up.go:694] Scale-up: setting group ASG_NAME size to 5

I0927 11:50:45.523906       1 scale_up.go:263] Pod echoserver/echoserver-74fd7d865f-vkzqb is unschedulable
I0927 11:50:45.524041       1 scale_up.go:300] Upcoming 1 nodes

I0927 11:52:36.144782       1 clusterstate.go:194] Scale up in group ASG_NAME finished successfully in 2m0.794268739s

スケールダウンの動作確認

前のセクションで設定したCPU requestを削除します.

デフォルトでは,Cluster Autoscalerがノードが必要ないと判断してから10分後に実際にノードを削除します(詳しくは How does scale-down work? を参照してください).Cluster Autoscalerのログを確認し,以下のように Terminating EC2 instance が表示されると成功です.

I0927 11:57:07.790306       1 scale_down.go:407] Node ip-172-19-67-52.ap-northeast-1.compute.internal - utilization 0.055000
I0927 11:57:07.790634       1 static_autoscaler.go:359] ip-172-19-67-52.ap-northeast-1.compute.internal is unneeded since 2019-09-27 11:57:07.773690521 +0000 UTC m=+2997.491422805 duration 0s

I0927 12:07:12.161679       1 static_autoscaler.go:359] ip-172-19-67-52.ap-northeast-1.compute.internal is unneeded since 2019-09-27 11:57:07.773690521 +0000 UTC m=+2997.491422805 duration 10m4.367847963s
I0927 12:07:12.161767       1 scale_down.go:600] ip-172-19-67-52.ap-northeast-1.compute.internal was unneeded for 10m4.367847963s
I0927 12:07:12.161868       1 scale_down.go:819] Scale-down: removing empty node ip-172-19-67-52.ap-northeast-1.compute.internal
I0927 12:07:12.176857       1 delete.go:64] Successfully added toBeDeletedTaint on node ip-172-19-67-52.ap-northeast-1.compute.internal
I0927 12:07:12.391908       1 auto_scaling_groups.go:269] Terminating EC2 instance: i-066bc60549f083e38

最後に,Auto Scaling Groupのdesired capacityが元に戻っていることを確認します.

GitLab RunnerとkanikoでDockerイメージをビルドする

DockerやKubernetesでGitLab Runnerを実行する場合,GitLab RunnerでDockerイメージをビルドするにはDocker in Dockerの特権モードを構成する必要があります.kanikoを利用すると,特権モードを使わずにDockerイメージをビルドできます.

本稿では,GitLab Runner上でkanikoを利用してDockerイメージをビルドし,Amazon ECRにDockerイメージをpushするまでの流れを紹介します. https://docs.gitlab.com/ee/ci/docker/using_kaniko.html で説明されている内容を元にしています.

ここでは,以下の前提とします.

IAM Roleの割り当て

GitLab Runner workerがECRにアクセスできるように,GitLab Runner workerのPodにIAM Roleを割り当てます.具体的な方法は下記を参照してください.

int128.hatenablog.com

.gitlab-ci.ymlの作成

kanikoを利用するには gcr.io/kaniko-project/executor イメージを指定します.イメージには amazon-ecr-credential-helper が組み込まれており,/kaniko/.docker/config.jsoncredHelpers を設定するとIAM Roleを利用してECRにアクセスできます.

.gitlab-ci.yml を以下のように設定します.

stages:
  - push

push:
  stage: push
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  variables:
    ECR_HOST: xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com
  script:
    - |
      cat > /kaniko/.docker/config.json <<EOF
      {
        "credHelpers": {
          "${ECR_HOST}": "ecr-login"
        }
      }
      EOF
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $ECR_HOST/repository-name:latest
  dependencies:
    - build
  tags:
    - app

ビルドを実行した後,ECRのリポジトリにイメージが表示されると成功です.

トラブルシューティング

ECRにアクセスする権限がない場合や /kaniko/.docker/config.json が適切に設定されていない場合は以下のエラーが表示されます.

error checking push permissions -- make sure you entered the correct tag name, and that you are authenticated correctly, and try again: checking push permission for "xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/repository-name:latest": unsupported status code 401; body: Not Authorized

参考までに, gcr.io/kaniko-project/executor イメージの環境変数は以下に設定されています.

ENV DOCKER_CREDENTIAL_GCR_CONFIG=/kaniko/.config/gcloud/docker_credential_gcr_config.json
ENV DOCKER_CONFIG=/kaniko/.docker/
ENV SSL_CERT_DIR=/kaniko/ssl/certs
ENV PATH=/usr/local/bin:/kaniko
ENV USER=/root
ENV HOME=/root

(2019/9/27追記) kanikoのイメージは debug タグを利用する必要があります.latest タグのイメージでは以下のエラーが出て動作しないようです.

Error: failed to start container "build": Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused "exec: \"sh\": executable file not found in $PATH": unknown

terraform-aws-modules/acm/aws でTLS証明書を取得する

terraform-aws-modules/acm/aws を使うとACMの証明書を簡単に取得できます.コンソール作業が一切必要ないのがうれしい.

例えば,ドメイン foo.example.com のRoute53 Hosted Zoneが存在する前提で *.foo.example.comTLS証明書を取得するには,以下のコードを追加します.

module "acm" {
  source = "terraform-aws-modules/acm/aws"

  domain_name = "*.foo.example.com"
  zone_id     = data.aws_route53_zone.service.zone_id
}

data "aws_route53_zone" "service" {
  name = "foo.example.com"
}

Terraformを実行すると以下のリソースが作成されます.

  # module.acm.aws_acm_certificate.this[0] will be created
  + resource "aws_acm_certificate" "this" {
      + arn                       = (known after apply)
      + domain_name               = "*.foo.example.com"
      + domain_validation_options = (known after apply)
      + id                        = (known after apply)
      + subject_alternative_names = []
      + validation_emails         = (known after apply)
      + validation_method         = "DNS"
    }

  # module.acm.aws_acm_certificate_validation.this[0] will be created
  + resource "aws_acm_certificate_validation" "this" {
      + certificate_arn         = (known after apply)
      + id                      = (known after apply)
      + validation_record_fqdns = (known after apply)
    }

  # module.acm.aws_route53_record.validation[0] will be created
  + resource "aws_route53_record" "validation" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = (known after apply)
      + records         = (known after apply)
      + ttl             = 60
      + type            = (known after apply)
      + zone_id         = "YOUR_ZONE_ID"
    }

...

module.acm.aws_acm_certificate.this[0]: Creation complete after 7s [id=arn:aws:acm:us-east-1:ID:certificate/ID]
module.acm.aws_route53_record.validation[0]: Creating...
module.acm.aws_route53_record.validation[0]: Still creating... [10s elapsed]
module.acm.aws_route53_record.validation[0]: Still creating... [20s elapsed]
module.acm.aws_route53_record.validation[0]: Still creating... [30s elapsed]
module.acm.aws_route53_record.validation[0]: Creation complete after 38s [id=ZONE_ID.foo.example.com._CNAME]
module.acm.aws_acm_certificate_validation.this[0]: Creating...
module.acm.aws_acm_certificate_validation.this[0]: Still creating... [10s elapsed]
module.acm.aws_acm_certificate_validation.this[0]: Still creating... [20s elapsed]
module.acm.aws_acm_certificate_validation.this[0]: Still creating... [30s elapsed]
module.acm.aws_acm_certificate_validation.this[0]: Still creating... [40s elapsed]
module.acm.aws_acm_certificate_validation.this[0]: Still creating... [50s elapsed]
module.acm.aws_acm_certificate_validation.this[0]: Still creating... [1m0s elapsed]
module.acm.aws_acm_certificate_validation.this[0]: Creation complete after 1m10s [id=2019-09-19 02:12:32 +0000 UTC]

モジュールの詳細は terraform-aws-modules/acm/aws を参照してください.