Terraformで構築したEKSにHelmでHPA、CA、ALB Ingressまで導入

# 概要

k8s上でオートスケールするWebアプリケーションを動かす場合、DeploymentとServiceに加え、Ingress、Horizontal Pod Autoscaler (HPA) まで作成することになります。
また、ホストのオートスケーリングのためにはCluster Autoscaler (CA) の導入も必要です。

そこで、 [Amazon EKS Workshop](https://eksworkshop.com/) などを参考にEKSでHPA、CA、Ingressまで試してみて、すんなり進まずハマった点を含めてまとめました。

この記事のソースコードは [こちら](https://github.com/os1ma/eks-sample/tree/master/terraform-eks-helming-hpa-ca-alb-ingress) です。

# 前提
以下のツールをインストール済みのMacを使って実施しました。

* kubectl v1.13.1
* aws cli 1.16.70
    * ~/.aws/credentials も設定済みとします
* aws-iam-authenticator
    * heptio-authenticator-aws から名前が変わっているので注意してください
* helm v2.12.1
* terraform v0.11.10
* jq 1.6

# 検証ステップ

以下のステップで検証しました。

1. TerraformでEKSを構築
2. Helmをインストール
3. Metrics Serverをインストール
4. Cluster Autoscalerをインストール
5. AWS ALB Ingress Controllerをインストール
6. Deployment、Service、Ingress、HPAを作成
7. スケーリングの検証

# 検証内容

## TerraformでEKSを構築

最近はeksctlで構築するサンプルが多いですが、個人的にTerraformで構築してみたかったので、 [terraform-aws-modules/eks](https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/2.0.0) を使ってみました。
特に [eks_test_fixture example](https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/examples/eks_test_fixture) を参考にして、以下の3ファイルを作成しました。

```providers.tf
provider "aws" {
  version = ">= 1.24.0"
  region  = "ap-northeast-1"
}

provider "random" {
  version = "= 1.3.1"
}
```

```vpc.tf
data "aws_availability_zones" "available" {}

resource "aws_security_group" "all_worker_mgmt" {
  name_prefix = "all_worker_management"
  vpc_id      = "${module.vpc.vpc_id}"

  ingress {
    from_port = 22
    to_port   = 22
    protocol  = "tcp"

    cidr_blocks = [
      "10.0.0.0/8",
      "172.16.0.0/12",
      "192.168.0.0/16",
    ]
  }
}

module "vpc" {
  source             = "terraform-aws-modules/vpc/aws"
  version            = "1.14.0"
  name               = "test-vpc"
  cidr               = "10.0.0.0/16"
  azs                = ["${data.aws_availability_zones.available.names[0]}", "${data.aws_availability_zones.available.names[1]}", "${data.aws_availability_zones.available.names[2]}"]
  private_subnets    = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets     = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"]
  enable_nat_gateway = true
  single_nat_gateway = true
  tags               = "${merge(local.tags, map("kubernetes.io/cluster/${local.cluster_name}", "shared"))}"
}
```

```eks.tf
locals {
  cluster_name = "test-eks-${random_string.suffix.result}"
  worker_groups = [
    {
      instance_type       = "t2.small"
      subnets             = "${join(",", module.vpc.private_subnets)}"
    },
  ]
  tags = {
    Environment = "test"
  }
}

resource "random_string" "suffix" {
  length  = 8
  special = false
}

module "eks" {
  source                               = "terraform-aws-modules/eks/aws"
  cluster_name                         = "${local.cluster_name}"
  subnets                              = ["${module.vpc.private_subnets}"]
  tags                                 = "${local.tags}"
  vpc_id                               = "${module.vpc.vpc_id}"
  worker_groups                        = "${local.worker_groups}"
  worker_group_count                   = "1"
  worker_additional_security_group_ids = ["${aws_security_group.all_worker_mgmt.id}"]
}
```

ファイルを作成したら、Terraformを実行します。

```bash
$ ls
eks.tf       providers.tf vpc.tf
$ terraform init
$ terraform apply
```

これだけで、EKSとワーカーノードが作成されます。

Terraformの実行が完了すると、ワーカーノードを登録するためのConfigMapとkubeconfigができています。

```bash
$ ls
config-map-aws-auth_test-eks-pSfTUN5w.yaml providers.tf
eks.tf                                     terraform.tfstate
kubeconfig_test-eks-pSfTUN5w               vpc.tf
```

Configmapは適用済みのため、kubectl applyする必要はありません。
kubeconfigは環境変数に設定します。

```bash
$ export KUBECONFIG=`pwd`/`ls kubeconfig_*`
$ kubectl get configmaps --all-namespaces
NAMESPACE     NAME                                 DATA   AGE
kube-system   aws-auth                             3      3m
kube-system   coredns                              1      4m
kube-system   extension-apiserver-authentication   5      4m
kube-system   kube-proxy                           1      4m
$ kubectl get nodes
NAME                                            STATUS   ROLES    AGE   VERSION
ip-10-0-1-211.ap-northeast-1.compute.internal   Ready    <none>   2m    v1.11.5
```

## Helmをインストール

[Install Helm CLI](https://eksworkshop.com/helm_root/helm_intro/install/) の通りHelmをインストールします。

```helm/rbac.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tiller
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: tiller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: tiller
    namespace: kube-system
```

```bash
$ kubectl apply -f helm/rbac.yaml
$ helm init --service-account tiller
```

tillerのPodが起動したらHelmのインストールは完了です。

```bash
$ kubectl get po --all-namespaces
NAMESPACE     NAME                             READY   STATUS    RESTARTS   AGE
kube-system   aws-node-dkzxr                   1/1     Running   1          6m
kube-system   coredns-7774b7957b-fndtt         1/1     Running   0          8m
kube-system   coredns-7774b7957b-nmc6q         1/1     Running   0          8m
kube-system   kube-proxy-bvzwf                 1/1     Running   0          6m
kube-system   tiller-deploy-85744d9bfb-ghm5j   1/1     Running   0          22s
```

## Metrics Server

### インストール

HPAはMetrics Serverに依存しているため、[Configure Horizontal Pod Autoscaler (HPA)](https://eksworkshop.com/scaling/deploy_hpa/) を参考に、HelmでMetrics Serverをインストールします。

CIを意識して、helm installの代わりにhelm upgrade --installを使っています。

```bash
$ helm upgrade --install \
    metrics-server \
    stable/metrics-server \
    --version 2.0.4 \
    --namespace kube-system
```

### VPCのDNS設定

Metrics Serverが無事インストールできれば `kubectl top nodes` などでメトリクスを確認できます。
しかし、この時点の設定ではしばらく待ってもメトリクスが収集できません。

```bash
$ kubectl top nodes
error: metrics not available yet
```

Metrics Serverのログを確認すると...

```
$ kubectl -n kube-system logs metrics-server-5f64dbfb9d-rc67t
    :
E1231 02:40:13.761695       1 manager.go:102] unable to fully collect metrics: unable to fully scrape metrics from source kubelet_summary:ip-10-0-1-211.ap-northeast-1.compute.internal: unable to fetch metrics from Kubelet ip-10-0-1-211.ap-northeast-1.compute.internal (ip-10-0-1-211.ap-northeast-1.compute.internal): Get https://ip-10-0-1-211.ap-northeast-1.compute.internal:10250/stats/summary/: dial tcp: lookup ip-10-0-1-211.ap-northeast-1.compute.internal on 172.20.0.10:53: no such host
    :
```

ワーカーノードが名前解決できていないことが原因のようです。
これは、AWS VPC Terraform Moduleでは、デフォルトでenable_dns_hostnamesがfaluseに設定されているためです。

vpc.tfに `enable_dns_hostnames = true` という記述を追加して、再度Terraformを実行します。

```bash
$ cat vpc.tf
    :
module "vpc" {
    :
  enable_dns_hostnames = true
    :
}
$ terraform apply
```

しばらく待つと、メトリクスの収集ができるようになっています。

```bash
$ kubectl top nodes
NAME                                            CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
ip-10-0-1-211.ap-northeast-1.compute.internal   30m          3%     330Mi           17%
```

HPAが動作するかどうかは、後でCAとまとめて確認します。

### 参考
- [VPC での DNS の使用](https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-dns.html)
- [terraform-aws-modules/terraform-aws-vpc - variables.tf](https://github.com/terraform-aws-modules/terraform-aws-vpc/blob/master/variables.tf)

## Cluster Autoscaler

### インストール

[terraform-aws-eks - autoscaling.md](https://github.com/terraform-aws-modules/terraform-aws-eks/blob/master/docs/autoscaling.md) を参考に、Cluster Autoscalerをインストールします。

values.yamlを作成しますが、リージョンとクラスタ名はここに記述せず、Helm実行時の引数で渡すようにします。

```cluster-autoscaler/values.yaml
rbac:
  create: true

sslCertPath: /etc/ssl/certs/ca-bundle.crt

cloudProvider: aws

autoDiscovery:
  enabled: true
```

`helm upgrade --install` を実行します。

```bash
$ helm upgrade --install \
    cluster-autoscaler \
    stable/cluster-autoscaler \
    --version 0.11.0 \
    --namespace kube-system \
    --values cluster-autoscaler/values.yaml \
    --set autoDiscovery.clusterName=test-eks-pSfTUN5w \
    --set awsRegion=ap-northeast-1
```

CAの状態はConfigMapのcluster-autoscaler-statusで確認できます。
この時点では、CAはインストールされたものの、まだスケーリング対象のワーカーグループが登録されていません。

```bash
$ kubectl -n kube-system describe configmaps cluster-autoscaler-status
Name:         cluster-autoscaler-status
Namespace:    kube-system
Labels:       <none>
Annotations:  cluster-autoscaler.kubernetes.io/last-updated: 2018-12-31 03:20:54.995043016 +0000 UTC

Data
====
status:
----
Cluster-autoscaler status at 2018-12-31 03:20:54.995043016 +0000 UTC:
Cluster-wide:
  Health:      Healthy (ready=1 unready=0 notStarted=0 longNotStarted=0 registered=1 longUnregistered=0)
               LastProbeTime:      2018-12-31 03:20:54.932736374 +0000 UTC m=+222.490186161
               LastTransitionTime: 2018-12-31 03:17:22.931577808 +0000 UTC m=+10.489027591
  ScaleUp:     NoActivity (ready=1 registered=1)
               LastProbeTime:      2018-12-31 03:20:54.932736374 +0000 UTC m=+222.490186161
               LastTransitionTime: 2018-12-31 03:17:22.931577808 +0000 UTC m=+10.489027591
  ScaleDown:   NoCandidates (candidates=0)
               LastProbeTime:      2018-12-31 03:20:54.932736374 +0000 UTC m=+222.490186161
               LastTransitionTime: 2018-12-31 03:17:22.931577808 +0000 UTC m=+10.489027591

Events:  <none>
```

### ワーカーグループの登録

スケーリング対象のワーカーグループを登録するため、eks.tfを修正してワーカーグループの設定を変更します。
`autoscaling_enabled = true` と `protect_from_scale_in = true` を追記し、`asg_min_size` 、 `asg_max_size` 、 `asg_desired_capacity ` も設定しておきます。

```eks.tf
locals {
  cluster_name = "test-eks-${random_string.suffix.result}"
  worker_groups = [
    {
      instance_type       = "t2.small"
      subnets             = "${join(",", module.vpc.private_subnets)}"
      asg_min_size = 2
      asg_max_size = 10
      asg_desired_capacity = 2
      autoscaling_enabled = true
      protect_from_scale_in = true
    },
  ]
    :
}
    :
```

```bash
$ terraform apply
```

すると、cluster-autoscaler-statusにNodeGroupsが追加されます。

```bash
$ kubectl -n kube-system describe configmaps cluster-autoscaler-status
Name:         cluster-autoscaler-status
Namespace:    kube-system
Labels:       <none>
Annotations:  cluster-autoscaler.kubernetes.io/last-updated: 2018-12-31 03:34:54.585531666 +0000 UTC

Data
====
status:
----
Cluster-autoscaler status at 2018-12-31 03:34:54.585531666 +0000 UTC:
Cluster-wide:
  Health:      Healthy (ready=2 unready=0 notStarted=0 longNotStarted=0 registered=2 longUnregistered=0)
               LastProbeTime:      2018-12-31 03:34:54.458541261 +0000 UTC m=+1062.015991044
               LastTransitionTime: 2018-12-31 03:17:22.931577808 +0000 UTC m=+10.489027591
  ScaleUp:     NoActivity (ready=2 registered=2)
               LastProbeTime:      2018-12-31 03:34:54.458541261 +0000 UTC m=+1062.015991044
               LastTransitionTime: 2018-12-31 03:17:22.931577808 +0000 UTC m=+10.489027591
  ScaleDown:   NoCandidates (candidates=0)
               LastProbeTime:      2018-12-31 03:34:54.458541261 +0000 UTC m=+1062.015991044
               LastTransitionTime: 2018-12-31 03:17:22.931577808 +0000 UTC m=+10.489027591

NodeGroups:
  Name:        test-eks-pSfTUN5w-02018123102273646430000000f
  Health:      Healthy (ready=2 unready=0 notStarted=0 longNotStarted=0 registered=2 longUnregistered=0 cloudProviderTarget=2 (minSize=2, maxSize=10))
               LastProbeTime:      2018-12-31 03:34:54.458541261 +0000 UTC m=+1062.015991044
               LastTransitionTime: 2018-12-31 03:32:31.956211675 +0000 UTC m=+919.513661423
  ScaleUp:     NoActivity (ready=2 cloudProviderTarget=2)
               LastProbeTime:      2018-12-31 03:34:54.458541261 +0000 UTC m=+1062.015991044
               LastTransitionTime: 2018-12-31 03:32:31.956211675 +0000 UTC m=+919.513661423
  ScaleDown:   NoCandidates (candidates=0)
               LastProbeTime:      2018-12-31 03:34:54.458541261 +0000 UTC m=+1062.015991044
               LastTransitionTime: 2018-12-31 03:32:31.956211675 +0000 UTC m=+919.513661423


Events:  <none>
```

CAの動作確認は最後に実施します。

### 参考
- [cluster-autoscaler](https://github.com/helm/charts/tree/master/stable/cluster-autoscaler)

### 補足 - k8sクラスタ名の取得

CAのインストールを自動化したい場合、k8sクラスタ名をTerraformから取得することになります。
その場合、outputs.tfを作成して再度 `terraform apply` を実行した後、 `terraform output` を実行することになります。

```outputs.tf
output "cluster_name" {
  value = "${local.cluster_name}"
}
```

```bash
$ terraform apply
$ terraform output -json
{
    "cluster_name": {
        "sensitive": false,
        "type": "string",
        "value": "test-eks-pSfTUN5w"
    }
}
$ terraform output -json | jq -r '.cluster_name.value'
test-eks-pSfTUN5w
```

## AWS ALB Ingress Controller

### インストール

k8s on AWSでALBを使うには、 [AWS ALB Ingress Controller](https://kubernetes-sigs.github.io/aws-alb-ingress-controller/) をインストールする必要があります。
[Setup ALB Ingress Controller](https://kubernetes-sigs.github.io/aws-alb-ingress-controller/guide/controller/setup/) を参考に、Helmでインストールします。

```aws-alb-ingress-controller/values.yaml
autoDiscoverAwsRegion: true
autoDiscoverAwsVpcID: true
```

```bash
$ helm repo add \
    incubator http://storage.googleapis.com/kubernetes-charts-incubator
$ helm upgrade --install \
    aws-alb-ingress-controller \
    incubator/aws-alb-ingress-controller \
    --version 0.1.4 \
    --namespace kube-system \
    --values aws-alb-ingress-controller/values.yaml \
    --set clusterName=test-eks-pSfTUN5w
```

### IAM Roleの設定

これでインストール自体はできましたが、まだALB Ingressを作ることはできません。
この状態ではEKSがELBを扱う権限を持たないため、IAM Roleを追加する必要があります。

iam.tfというファイルを作り、Terraformを実行します。

```iam.tf
resource "aws_iam_role_policy_attachment" "alb_ingress_policy_attachment" {
  role = "${module.eks.worker_iam_role_name}"
  policy_arn = "${aws_iam_policy.alb_ingress_policy.arn}"
}

resource "aws_iam_policy" "alb_ingress_policy" {
  name = "alb-ingress-policy"
  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "acm:DescribeCertificate",
        "acm:ListCertificates",
        "acm:GetCertificate"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:CreateSecurityGroup",
        "ec2:CreateTags",
        "ec2:DeleteTags",
        "ec2:DeleteSecurityGroup",
        "ec2:DescribeAccountAttributes",
        "ec2:DescribeAddresses",
        "ec2:DescribeInstances",
        "ec2:DescribeInstanceStatus",
        "ec2:DescribeInternetGateways",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeSubnets",
        "ec2:DescribeTags",
        "ec2:DescribeVpcs",
        "ec2:ModifyInstanceAttribute",
        "ec2:ModifyNetworkInterfaceAttribute",
        "ec2:RevokeSecurityGroupIngress"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "elasticloadbalancing:AddTags",
        "elasticloadbalancing:CreateListener",
        "elasticloadbalancing:CreateLoadBalancer",
        "elasticloadbalancing:CreateRule",
        "elasticloadbalancing:CreateTargetGroup",
        "elasticloadbalancing:DeleteListener",
        "elasticloadbalancing:DeleteLoadBalancer",
        "elasticloadbalancing:DeleteRule",
        "elasticloadbalancing:DeleteTargetGroup",
        "elasticloadbalancing:DeregisterTargets",
        "elasticloadbalancing:DescribeListeners",
        "elasticloadbalancing:DescribeLoadBalancers",
        "elasticloadbalancing:DescribeLoadBalancerAttributes",
        "elasticloadbalancing:DescribeRules",
        "elasticloadbalancing:DescribeSSLPolicies",
        "elasticloadbalancing:DescribeTags",
        "elasticloadbalancing:DescribeTargetGroups",
        "elasticloadbalancing:DescribeTargetGroupAttributes",
        "elasticloadbalancing:DescribeTargetHealth",
        "elasticloadbalancing:ModifyListener",
        "elasticloadbalancing:ModifyLoadBalancerAttributes",
        "elasticloadbalancing:ModifyRule",
        "elasticloadbalancing:ModifyTargetGroup",
        "elasticloadbalancing:ModifyTargetGroupAttributes",
        "elasticloadbalancing:RegisterTargets",
        "elasticloadbalancing:RemoveTags",
        "elasticloadbalancing:SetIpAddressType",
        "elasticloadbalancing:SetSecurityGroups",
        "elasticloadbalancing:SetSubnets",
        "elasticloadbalancing:SetWebACL"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "iam:CreateServiceLinkedRole",
        "iam:GetServerCertificate",
        "iam:ListServerCertificates"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "waf-regional:GetWebACLForResource",
        "waf-regional:GetWebACL",
        "waf-regional:AssociateWebACL",
        "waf-regional:DisassociateWebACL"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "tag:GetResources",
        "tag:TagResources"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "waf:GetWebACL"
      ],
      "Resource": "*"
    }
  ]
}
EOF
}
```

```bash
$ terraform apply
```

これで、ALB Ingressを作るための設定が完了しました。

### サブネットのオートディスカバリ設定

今の状態でIngressを作成する場合、マニフェストは以下のようになります。

```ingress.yaml
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/subnets: subnet-aaa,subnet-bbb,subnet-ccc
  name: sample-app
spec:
  rules:
    - http:
        paths:
          - path: /
            backend:
              serviceName: sample-app
              servicePort: 80
```

このままではマニフェストにサブネットIDを書くことになり、面倒です。
そこで、 [Subnet Auto Discovery](https://kubernetes-sigs.github.io/aws-alb-ingress-controller/guide/controller/config/#subnet-auto-discovery) を設定します。
vpc.tfを修正し、Private Subnetに `kubernetes.io/role/internal-elb` 、Public Subnetに `kubernetes.io/role/elb` というキーのタグを作成します。

```vpc.tf
    :
module "vpc" {
    :
  tags               = "${merge(local.tags, map("kubernetes.io/cluster/${local.cluster_name}", "shared"))}"
  private_subnet_tags  = {
    "kubernetes.io/role/internal-elb" = ""
  }
  public_subnet_tags   = {
    "kubernetes.io/role/elb" = ""
  }
}
```

```bash
$ terraform apply
```

これでAWS ALB Ingress Controllerの設定は完了です。

### 参考
- [ALB Ingress Controller を使う](https://qiita.com/koudaiii/items/2031d67c715b5bb50357)

### 注意
この記事には記載しませんが、Route53との連携やACMを用いたHTTPSを実現するためには追加の設定が必要です。

## Deployment、Service、Ingress、HPAを作成

これだけセットアップしたので、ついにオートスケールするWebアプリケーションをデプロイできます。
Deployment、Service、Ingress、HPAの4つのマニフェストを作成します。

```deployment.yaml
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sample-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample-app
  minReadySeconds: 5
  progressDeadlineSeconds: 60
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 50%
      maxUnavailable: 50%
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: sample-app
        image: nginx:1.15.8-alpine
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          timeoutSeconds: 10
          periodSeconds: 10
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          timeoutSeconds: 10
          periodSeconds: 10
          failureThreshold: 3
        resources:
          limits:
            cpu: 500m
            memory: 512Mi
          requests:
            cpu: 500m
            memory: 512Mi
```

```service.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: sample-app
spec:
  selector:
    app: sample-app
  ports:
    - name: sample-app
      port: 80
      targetPort: 80
  type: NodePort
```

```ingress.yaml
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
  name: sample-app
spec:
  rules:
    - http:
        paths:
          - path: /
            backend:
              serviceName: sample-app
              servicePort: 80
```

```hpa.yaml
---
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: sample-app
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: sample-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      targetAverageUtilization: 10
```

kubectlでデプロイします。

```bash
$ ls
deployment.yaml hpa.yaml        ingress.yaml    service.yaml
$ kubectl apply -R -f .
deployment.extensions/sample-app created
horizontalpodautoscaler.autoscaling/sample-app created
ingress.extensions/sample-app created
service/sample-app created
$ kubectl get all
NAME                              READY   STATUS    RESTARTS   AGE
pod/sample-app-665765dbcd-kqd9g   1/1     Running   0          24s
pod/sample-app-665765dbcd-vl6nm   1/1     Running   0          24s

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/kubernetes   ClusterIP   172.20.0.1      <none>        443/TCP        2h
service/sample-app   NodePort    172.20.100.78   <none>        80:30913/TCP   23s

NAME                         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/sample-app   2         2         2            2           24s

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/sample-app-665765dbcd   2         2         2       24s

NAME                                             REFERENCE               TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/sample-app   Deployment/sample-app   <unknown>/10%   2         10        0          24s
```

Ingressの起動には時間がかかります。
しばらく待つと、無事Ingress経由でNginxにアクセスできます。

```
$ curl `kubectl get ingresses -o jsonpath --template {.items[0].status.loadBalancer.ingress[0].hostname}`
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```

## スケーリングの検証

HPA、CAの検証のため、 `kubectl exec` を利用して、コンテナ内でCPU使用率を高めてみます。

```bash
$ kubectl exec -it sample-app-665765dbcd-kqd9g sh
/ # yes > /dev/null
```

このまま別ターミナルを見てみると、Podが2個から3個にオートスケールしています。

```bash
$ kubectl get all
NAME                              READY   STATUS    RESTARTS   AGE
pod/sample-app-665765dbcd-kqd9g   1/1     Running   0          12m
pod/sample-app-665765dbcd-m79hs   0/1     Pending   0          50s
pod/sample-app-665765dbcd-vl6nm   1/1     Running   0          12m

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/kubernetes   ClusterIP   172.20.0.1      <none>        443/TCP        2h
service/sample-app   NodePort    172.20.100.78   <none>        80:30913/TCP   12m

NAME                         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/sample-app   3         3         3            2           12m

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/sample-app-665765dbcd   3         3         2       12m

NAME                                             REFERENCE               TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/sample-app   Deployment/sample-app   50%/10%   2         10        3          12m
```

HPAで作成されたPodのSTATUSがPendingのため、CAによりノードの追加が行われるはずです。
cluster-autoscaler-statusを見てみると...

```
$ kubectl -n kube-system describe configmaps cluster-autoscaler-status
Name:         cluster-autoscaler-status
Namespace:    kube-system
Labels:       <none>
Annotations:  cluster-autoscaler.kubernetes.io/last-updated: 2018-12-31 04:47:06.27366144 +0000 UTC

Data
====
status:
----
Cluster-autoscaler status at 2018-12-31 04:47:06.27366144 +0000 UTC:
Cluster-wide:
  Health:      Healthy (ready=3 unready=0 notStarted=0 longNotStarted=0 registered=3 longUnregistered=0)
               LastProbeTime:      2018-12-31 04:47:06.088555494 +0000 UTC m=+5393.646005236
               LastTransitionTime: 2018-12-31 03:17:22.931577808 +0000 UTC m=+10.489027591
  ScaleUp:     NoActivity (ready=3 registered=3)
               LastProbeTime:      2018-12-31 04:47:06.088555494 +0000 UTC m=+5393.646005236
               LastTransitionTime: 2018-12-31 04:47:06.088555494 +0000 UTC m=+5393.646005236
  ScaleDown:   NoCandidates (candidates=0)
               LastProbeTime:      2018-12-31 04:47:06.088555494 +0000 UTC m=+5393.646005236
               LastTransitionTime: 2018-12-31 04:47:06.088555494 +0000 UTC m=+5393.646005236

NodeGroups:
  Name:        test-eks-pSfTUN5w-02018123102273646430000000f
  Health:      Healthy (ready=3 unready=0 notStarted=0 longNotStarted=0 registered=3 longUnregistered=0 cloudProviderTarget=3 (minSize=2, maxSize=10))
               LastProbeTime:      2018-12-31 04:47:06.088555494 +0000 UTC m=+5393.646005236
               LastTransitionTime: 2018-12-31 03:32:31.956211675 +0000 UTC m=+919.513661423
  ScaleUp:     NoActivity (ready=3 cloudProviderTarget=3)
               LastProbeTime:      2018-12-31 04:47:06.088555494 +0000 UTC m=+5393.646005236
               LastTransitionTime: 2018-12-31 04:47:06.088555494 +0000 UTC m=+5393.646005236
  ScaleDown:   NoCandidates (candidates=0)
               LastProbeTime:      2018-12-31 04:47:06.088555494 +0000 UTC m=+5393.646005236
               LastTransitionTime: 2018-12-31 04:47:06.088555494 +0000 UTC m=+5393.646005236


Events:
  Type    Reason         Age    From                Message
  ----    ------         ----   ----                -------
  Normal  ScaledUpGroup  2m22s  cluster-autoscaler  Scale-up: setting group test-eks-pSfTUN5w-02018123102273646430000000f size to 3
  Normal  ScaledUpGroup  2m22s  cluster-autoscaler  Scale-up: group test-eks-pSfTUN5w-02018123102273646430000000f size set to 3
```

Scale-upのイベントが発生しています。

しばらく待つと、Pod、ノードともにオートスケールしたことを確認できます。
HPA、CA、ALB Ingressまで無事導入できました。

## 片付け

```bash
$ kubectl delete deployment sample-app
$ kubectl delete service sample-app
$ kubectl delete ingress sample-app
$ kubectl delete hpa sample-app
$ helm delete --purge `helm ls -aq`
$ terraform destroy
```

※ AutoScalingGroupは手動で削除しないとterraform destoryに失敗するかもしれません。