EKSでクラスタを作成した後にやるべきこと

# 概要
- AWSのマネージドKubernetesサービスであるEKSでクラスタを作成した後、アプリケーションを稼働させる前にセットアップしておくべきツールをまとめました

## セットアップするもの

### kube2iam
- Pod単位でIAM Roleを付与できるツール

### AWS ALB Ingress Controller
- Ingressリソースを作成するためのコントローラー

### External DNS
- Ingressリソースの作成時に構築されるLoad BalancerのDNS名を自動的にRoute53に登録してくれる

### Container Insights
- Podのログやリソースの情報をCloudWatchで見れるようにする

### Clustor Autoscaler
- そのまんまClustor Autoscaler
    - Worker Nodeのオートスケーラー

# 前提
- EKSのクラスタ、及びそれに紐付くWorker Nodeが稼働している

# kube2iam
- 稼働しているPod単位でIAM Roleを付与できるようになる
- このようなツールを使わない場合、Podに権限を付与する場合は以下の2通りの方法がある
    1. PodにIAM Userのクレデンシャルを埋め込む(Configファイルや環境変数など)
    2. 稼働しているEC2のIAM Roleに必要なPolicyを付与する

- １の場合はPodもしくは必要なロール単位でIAM Userを作成する必要があり、なおかつ安全のためSecretリソースを作成する必要がある
- 2の場合は対象のEC2上で稼働し得るPodで必要な全てのPolicyを付与する必要があり、セキュリティ的によろしくない(例えば、1つのPodがS3にアクセスする必要がある場合、そのEC2上で稼働している全てのPodにS3へアクセスできるPolicyが付与されてしまう)
- これらの問題解決や手間を省いてくれるツールが今回導入するkube2iam
- 同様のツールでkiamというものもあるが、kiamはServerというコンポーネントをMaster Nodeで動かすことを前提としているため、EKSでは使えない
    - EKSでもAffinityを使用してServer専用のノードを構築すれば使用可能との前例あり
        - 参考
            - https://speakerdeck.com/euno7/we-tried-to-build-eks-cluster-nicely

## 構築
- こちらのサイト([Amazon EKSでkube2iamを使う手順](https://qiita.com/hideaki_aoyagi/items/c84d8bceed0682059d53))を参考にマニフェストファイルを作成

### kube2iam-sa.yaml
- ServiceAccountとClusterRoleを作成し、それらを基にClusterRoleBindingを作成

### kube2iam-ds.yaml
- kube2iamとしての処理を行うPodがDaemonsetとしてデプロイされる
- kube2iam-sa.yamlで作成したServiceAccountがアタッチされる

- 以下のようにデプロイ

```
$ kubectl apply -f kube2iam-sa.yaml
$ kubectl apply -f kube2iam-ds.yaml
```

## 使用方法
- PodにアタッチするIAM Roleを作成
    - ここでは以下のようなRoleを作成

```json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "sts:AssumeRole"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}
```
- 作成したIAM RoleのTrust RelationshipにStatementを追加
    - 追加しているのは2つ目のStatement
    - Principal.AWSにはWorker Nodeに付与されているIAM Roleを指定する
- これによりWorker NodeがAssume Roleによってセッション情報を取得できるようになる

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    },
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/kubernetes-worker-role"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```

- マニフェストファイルの編集
    - Deploymentの場合はspec.template.metadata.annotationsにKeyをiam.amazonaws.com/role、ValueをIAM Roleのarnとして記載する
    - 以下の例では、nginx-deploymentから作成されるPodにはsample-roleというIAM Roleが付与される

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  template:
    metadata:
      annotations:
        iam.amazonaws.com/role: sample-role
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.9.1
        ports:
        - containerPort: 80
```

## 参考
- https://github.com/jtblin/kube2iam
- [Amazon EKSでkube2iamを使う手順](https://qiita.com/hideaki_aoyagi/items/c84d8bceed0682059d53)

# AWS ALB Ingress Controller
- EKSにIngressリソースを作成するためのコントーローラーを作成
- Ingressコントローラーにもいくつか種類があるが、ここでは[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/alb-ingress.html)で紹介されているALB Ingress Controllerを採用

## 構築
**ここでの手順は、kube2iamのセットアップが完了していることが前提となっています**

- IAM Policy作成
    - Ingress Controllerに付与するIAM Roleを作成する前に、そのRoleにアタッチするIAM Policyを作成
    - 以下のようなPolicyを作成

```json
{
  "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:DescribeInstances",
        "ec2:DescribeInstanceStatus",
        "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: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": "*"
    }
  ]
}
```

- IAM Role作成
    - 先ほど作成したIAM PolicyをアタッチしたIAM Roleを作成
    - Trust relationshipにStatementを追加する(kube2iamの章を参照)
- ServiceAccount、ClusterRole、ClusterRoleBindingを作成

```
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.2/docs/examples/rbac-role.yaml
```

- Ingress Controllerのデプロイ
    - https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.2/docs/examples/alb-ingress-controller.yaml からマニフェストファイルを取得
    - 変更点は以下の2箇所
        - --cluster-nameを対象のクラスタ名に書き換える
        - spec.templete.metadata.annotation.iam.amazonaws.com/roleに先ほど作成したalb-ingress-controllerのIAM Roleを指定する
        - デプロイする

```
$ kubectl apply -f alb-ingress-controller.yaml
```

- サブネットにタグを付与
    - AWS ALB Ingress Controllerから検知されるよう、Public Subnetに以下2つのTagを付与
        - kubernetes.io/role/alb-ingress:(ブランク)
        - kubernetes.io/role/elb:1
## Ingressリソースのマニフェストファイル修正
- Ingressリソースを作成する際は、マニフェストファイルのannotationsに以下を追記
- 以下一例

```
annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-1:XXXXXXXXXXXXX:certificate/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX
    alb.ingress.kubernetes.io/actions.[ingressで指定するserviceName]: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'
    alb.ingress.kubernetes.io/waf-acl-id: XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX
    alb.ingress.kubernetes.io/security-groups: sg-XXXXXXXXXXXXXXXX
```

- alb.ingress.kubernetes.io/listen-ports
    - LoadBalancerで受け付けるプロトコルを指定
    - HTTPのトラッフィクをHTTPSにリダイレクトさせたい場合も、上記のようにHTTPとHTTPSどちらも指定する
- alb.ingress.kubernetes.io/certificate-arn
    - HTTPSのトラフィックを受け付ける場合は、Certificate Managerで作成した証明書のarnを記載する
- alb.ingress.kubernetes.io/actions.[ingressで指定するserviceName]
    - HTTPのトラフィックをHTTPSにリダイレクトさせる場合に必要
    - この際、Ingressリソースのマニフェストファイルにも変更が必要になる
    - spec.rules.http.pathsの一番上にpathを追記
        - 上から優先順位をつけてLoadBalancerのruleに適用されるため
    - これによりLoadBalancerのruleにリダイレクトが追加される
    
```
- path: /*
  backend:
    serviceName: [annotationで指定したserviceName]
    servicePort: use-annotation
```

- alb.ingress.kubernetes.io/waf-acl-id
    - LoadBalancerにWAFをアタッチする場合、ACLのIDを記載


- alb.ingress.kubernetes.io/security-groups
    - LoadBalancerにアタッチするSecurity Groupを指定
    - 何も指定しないと自動でSecurity Groupが作成されるが、**基本的に指定することを推奨**
        - 自動で作成されたSecurity GroupはNetwork Interfaceにアタッチされるが、アタッチできるSecurity Groupの上限数は5、申請しても最大で16個まで。作成するIngressリソース数が限られていることが事前に分かっている場合には不要かもしれないが、稼働させたアプリが増えた際などのエラーを防ぐためにも指定しておいた方がベター。
        - また、**事前に作成したSecurity Groupを指定した場合には、Worker NodeのSecurity GroupのInboundで、このSecurity Groupからの通信を許可しておく必要がある。**
            - これをしないとHealth Checkが通らなくなる、、、

- 他のAnnotationは[ここ](https://kubernetes-sigs.github.io/aws-alb-ingress-controller/guide/ingress/annotation/)で確認できる

## 参考
- [AWSのKubernetesでサービスを公開する最高の方法~ALB,ACM,Route53の自動作成~](https://daichan.club/container/78878)

# External DNS
- Ingressのデプロイが検知されると自動的にRoute53へドメインの登録を行ってくれる

## IAM Policy作成
- External DNSに付与するRoleにアタッチするためのIAM Policyを作成
- 以下のようなPolicyを作成

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "route53:ChangeResourceRecordSets"
            ],
            "Resource": [
                "arn:aws:route53:::hostedzone/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "route53:ListHostedZones",
                "route53:ListResourceRecordSets"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
```

## IAM Role作成
- 先ほど作成したIAM PolicyをアタッチしたIAM Roleを作成
- Trust relationshipにStatementを追加(kube2iamの章を参照)

## マニフェストファイル作成
- 以下のファイルを作成
- 修正点は2点
    - Deploymentのspec.templete.metadata.annotations.iam.amazonaws.com/roleに先ほど作成したExternal DNS用のIAM RoleのArnを指定する
    - Deploymentのspec.templete.spec.containers.argsのdomain-filterに使用するドメイン(XXXX.com)を指定

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: external-dns
rules:
  - apiGroups:
      - ""
    resources:
      - "services"
    verbs:
      - "get"
      - "watch"
      - "list"
  - apiGroups:
      - ""
    resources:
      - "pods"
    verbs:
      - "get"
      - "watch"
      - "list"
  - apiGroups:
      - "extensions"
    resources:
      - "ingresses"
    verbs:
      - "get"
      - "watch"
      - "list"
  - apiGroups:
      - ""
    resources:
      - "nodes"
    verbs:
      - "list"
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: external-dns-viewer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: external-dns
subjects:
- kind: ServiceAccount
  name: external-dns
  namespace: kube-system
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: external-dns
  namespace: kube-system
spec:
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: external-dns
      annotations:
        iam.amazonaws.com/role: route53-externaldns
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
        image: registry.opensource.zalan.do/teapot/external-dns:v0.5.9
        args:
        - --source=service
        - --source=ingress
        - --domain-filter=XXXX.com
        - --provider=aws
        - --policy=sync
        - --registry=txt
        - --txt-owner-id=prod
```
- **同じホストを複数のクラスタで共有する場合、--txt-owner-idはクラスタ毎に異なる値を設定する**
    - 同じidが使われていると、どのクラスタで作成されたものか判断がつかず、誤削除されてしまう

## 参考
- [AWSのKubernetesでサービスを公開する最高の方法~ALB,ACM,Route53の自動作成~](https://daichan.club/container/78878)
- https://github.com/kubernetes-incubator/external-dns

# Container Insights 

## IAM Role作成
- CloudWatchAgentServerPolicy(AWSのデフォルトで作成されているPolicy)をアタッチしたIAM Roleを作成
- Trust relationshipにStatementを追加(kube2iamの章を参照)

## CloudWatchエージェントのセットアップ
- Namespaceの作成

```
$ curl -O https://s3.amazonaws.com/cloudwatch-agent-k8s-yamls/kubernetes-monitoring/cloudwatch-namespace.yaml
$ kubectl apply -f cloudwatch-namespace.yaml
```

- ServiceAccountの作成

```
$ curl -O https://s3.amazonaws.com/cloudwatch-agent-k8s-yamls/kubernetes-monitoring/cwagent-serviceaccount.yaml
$ kubectl apply -f cwagent-serviceaccount.yaml
```

- ConfigMap作成
    - ファイル内の{{cluster-name}}を対象のクラスタ名に変更

```
$ curl -O https://s3.amazonaws.com/cloudwatch-agent-k8s-yamls/kubernetes-monitoring/cwagent-configmap.yaml
$ kubectl apply -f cwagent-configmap.yaml
```

- CloudWatch エージェントをデプロイ
    - ファイル内のspec.template.metadata.annotationsにiam.amazonaws.com/roleを追記して、事前に作成したIAM Role名を記載する

```
$ curl -O https://s3.amazonaws.com/cloudwatch-agent-k8s-yamls/kubernetes-monitoring/cwagent-daemonset.yaml
$ kubectl apply -f cwagent-daemonset.yaml
```
- 確認
    - CloudWatch MetricsでContinerInsightsというメトリクスが作成されている

## FluentDのセットアップ

- FluendDのインストール
    - CloudWatch エージェントの際と同じようにfluentd.ymlにIAM Roleのannotationを追記しておく

```
$ curl -O https://s3.amazonaws.com/cloudwatch-agent-k8s-yamls/fluentd/fluentd.yml
$ kubectl create configmap cluster-info \
--from-literal=cluster.name=[cluster_name] \
--from-literal=logs.region=[region_name] -n amazon-cloudwatch
$ kubectl apply -f fluentd.yml
```
- 確認
    - CloudWatch Logsで以下のロググループが3つ作成されている
        - /aws/containerinsights/Cluster_Name/application
        - /aws/containerinsights/Cluster_Name/host
        - /aws/containerinsights/Cluster_Name/dataplane

## 参考
- https://aws.amazon.com/jp/premiumsupport/knowledge-center/eks-cluster-autoscaler-setup/

# Clustor Autoscaler
- EC2のAutoScalingGroupに紐づいてオートスケールが行われる
- StatusがPendingのPodが存在することがトリガーになる

## IAM Polocy作成
- Clustor AutoscalerのPodに付与するIAM Roleにアタッチするもの
- 2019年12月24日 修正
    - ec2:DescribeLaunchTemplateVersionsを追加
    - https://github.com/alphagov/gsp/pull/611

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "autoscaling:DescribeAutoScalingGroups",
                "autoscaling:DescribeAutoScalingInstances",
                "autoscaling:DescribeLaunchConfigurations",
                "autoscaling:DescribeTags",
                "autoscaling:SetDesiredCapacity",
                "autoscaling:TerminateInstanceInAutoScalingGroup",
                "ec2:DescribeLaunchTemplateVersions"
            ],
            "Resource": "*"
        }
    ]
}

```

## IAM Role作成
- 先ほど作成したPolicyがアタッチされたRoleを作成
- Trust relationshipにStatementを追加(kube2iamの章を参照)

## デプロイ
- マニフェストファイルを取得
    - マニフェストファイルに明示的にAutoScalingGroupの名前を記載する方法もあるが、ここでは自動的にAutoScalingGroupを検知する方法(autodiscover)を採用する
    - AutoScalingGroupのタグで判別される

```
$ wget https://raw.githubusercontent.com/kubernetes/autoscaler/master/cluster-autoscaler/cloudprovider/aws/examples/cluster-autoscaler-autodiscover.yaml
```
- Deploymentのspec.template.spec.containers.command内の--node-group-auto-discovery末尾のクラスタ名を対象のものに書き換える

```yaml
command:
    - ./cluster-autoscaler
    - --v=4
    - --stderrthreshold=info
    - --cloud-provider=aws
    - --skip-nodes-with-local-storage=false
    - --expander=least-waste
    - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/<YOUR CLUSTER NAME>
```

- Deploymentのspec.template.metadata.annotationsに先ほど作成したRoleのarnを記載する
- オートスケールさせるAutoScalingGroupのタグに以下のキーを追加(バリューは空でOK)
    - k8s.io/cluster-autoscaler/enabled
    - k8s.io/cluster-autoscaler/[クラスタ名]

- デプロイ

```
$ kubectl apply -f cluster-autoscaler-autodiscover.yaml
```

## 確認
- レプリカ数を適当な数に設定する

```
$ kubectl create deployment autoscaler-demo --image=nginx
$ kubectl scale deployment autoscaler-demo --replicas=100
```
- Nodeが増えていればセットアップ完了！

## 参考
- https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md
    - その他、タグやIAM Policyを編集することで、0個へのスケールや0個からのスケールも実現できる