GKEとEKSをいじってみる【Kubernetes】

# TL; DR
Kubernetesはコンテナオーケストレーションツールとして有名なミドルウェアですが、マネージド型のKubernetesってどこで使うのがいいんだろうか？とお悩みの方へ参考になれば幸いです。
ちなみに筆者はEKSでAPIをガッと集めたものを本番化したことが1度だけある程度のレベル感です。
運用自体は1ヶ月ほどだったので世の皆様ほどつらみなどは理解しておりません。
GKEに関しては完全に個人で遊ぶレベルなのでお察しです。それらを踏まえた上で生暖かく見守りつつ読んでいただければと思います。

# Kubernetesとは
この辺りはもう既に素晴らしい記事がQiita上にいっぱいあるので、特に詳しくは説明しません。
要はコンテナをよしなにしてくれる素敵なツールであるということを理解してもらえば十分です。余談としては今はCNCFが管理していますが、元々Googleが社内で利用していた**Borg**というクラスターマネージャーを元に開発されたものです。
興味ある方はBorgについての論文があるので見てみると面白いと思います。
https://research.google/pubs/pub43438/

この時点で既にGKEのがいいんじゃないか臭がしてくるのはお約束です。

# EKSとは
>Amazon Elastic Kubernetes Service (Amazon EKS) は、独自の Kubernetes コントロールプレーンを立ち上げたり維持したりすることなく、AWS で Kubernetes を簡単に実行できるようにするマネージド型サービスです。

公式リファレンス : https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/what-is-eks.html

要はAWSのマネージドなKubernetesサービスのことです。
マネージドサービスのお約束なのですが、制限が多かったりブラックボックスな部分が多かったりで中々ワガママなやつです。
Kubernetes自体学習コストが高いのですが、そこにAWS独自のものが入ってくるので中々大変にです。
またECSとの住み分けも判断に迷うことが多いです。

### メリット

 - 既にAWS上でサービスを展開している場合は導入がしやすい
 - Infrastructure as Codeが進んでいる場合は周辺リソース含めて管理が容易

### デメリット

 - 学習コストが高い
 - [ECS](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/Welcome.html)との住み分けが難しい

# GKEとは
>Kubernetes Engine（GKE）は、コンテナ化されたアプリケーションをデプロイするための、本番環境に対応したマネージド型環境です。

公式リファレンス : https://cloud.google.com/kubernetes-engine/?hl=ja

こちらもEKS同様GCPのマネージドなKubernetesサービスです。
前述の通り、コンテナ技術自体Googleが長年培ってきたものなので、入門はしやすいと思います。
反面ネットワーク周りなどブラックボックスが多く、インフラ寄りの方はお任せするのは物足りないと思います。
マネージドも割と充実している印象なのですが、やはり学習コストは高いと思います。

### メリット

 - サポートが手厚い
 - [Stackdriver](https://cloud.google.com/stackdriver/?hl=ja)がネイティブで使える

### デメリット

 - 学習コストが高い
 - [Cloud Run](https://cloud.google.com/run/?hl=ja)との住み分けに迷う

# EKSへのデプロイ
先日のre:Invent 2019にて[EKS on Fargate](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/fargate.html)がGAとなりましたが、今回は通常のEC2にデプロイしていきます。
公式にもある[ゲストブック](https://github.com/kubernetes/examples/tree/master/guestbook-go)アプリケーションのデプロイをしていきます。

### リソース作成
今回はGKEにもデプロイをするのでTerraformでコードを書いていきます。

```go:eks.tf
// EKS Cluster cofigure
resource "aws_eks_cluster" "advent" {
  name     = "${var.service_name}-${var.env}-k8s"
  role_arn = aws_iam_role.eks_assume_role.arn
  vpc_config {
    subnet_ids         = [for subnets in aws_subnet.advent_public : subnets.id]
    security_group_ids = [aws_security_group.advent_eks.id]
  }

  tags = {
    Name    = "${var.service_name}-${var.env}-igw"
    Service = var.service_name
    ENV     = var.env
  }
}

// Security Group configure for EKS Cluster
resource "aws_security_group" "advent_eks" {
  name        = "${var.service_name}-${var.env}-eks"
  description = "${var.service_name} ${var.env} eks security group"
  vpc_id      = aws_vpc.advent_eks.id

  tags = {
    Name    = "${var.service_name}-${var.env}-eks-sg"
    Service = var.service_name
    ENV     = var.env
  }
}

resource "aws_security_group_rule" "advent_eks_local" {
  type              = "ingress"
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
  security_group_id = aws_security_group.advent_eks.id
  cidr_blocks       = var.own_ip
}

resource "aws_security_group_rule" "advent_eks_ingress" {
  type                     = "ingress"
  from_port                = 1025
  to_port                  = 65535
  protocol                 = "tcp"
  security_group_id        = aws_security_group.advent_eks.id
  source_security_group_id = aws_security_group.k8s_node.id
}

resource "aws_security_group_rule" "eks_2node_ingress_443" {
  type                     = "ingress"
  from_port                = 443
  to_port                  = 443
  protocol                 = "tcp"
  security_group_id        = aws_security_group.advent_eks.id
  source_security_group_id = aws_security_group.k8s_node.id
}

resource "aws_security_group_rule" "eks_default_egress" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  security_group_id = aws_security_group.advent_eks.id
  cidr_blocks       = ["0.0.0.0/0"]
}

resource "aws_security_group_rule" "eks_2node_egress" {
  type                     = "egress"
  from_port                = 1025
  to_port                  = 65535
  protocol                 = "tcp"
  security_group_id        = aws_security_group.advent_eks.id
  source_security_group_id = aws_security_group.k8s_node.id
}

resource "aws_security_group_rule" "eks_2node_egress_443" {
  type                     = "egress"
  from_port                = 443
  to_port                  = 443
  protocol                 = "tcp"
  security_group_id        = aws_security_group.advent_eks.id
  source_security_group_id = aws_security_group.k8s_node.id
}
```

```go:worker.tf
// EKS Node cofigure
resource "aws_eks_node_group" "k8s_node" {
  cluster_name    = aws_eks_cluster.advent.name
  node_group_name = "advent-k8s"
  node_role_arn   = aws_iam_role.k8s_node.arn
  subnet_ids      = [for subnets in aws_subnet.advent_public : subnets.id]

  scaling_config {
    desired_size = 1
    max_size     = 3
    min_size     = 1
  }

  disk_size = 20
  instance_types = ["t2.small"]


  depends_on = [
    aws_iam_role_policy_attachment.managed_AmazonEKSWorkerNodePolicy,
    aws_iam_role_policy_attachment.managed_AmazonEKS_CNI_Policy,
    aws_iam_role_policy_attachment.managed_AmazonEC2ContainerRegistryReadOnly,
  ]

  tags = {
    Name    = "${var.service_name}-${var.env}-k8s-node"
    Service = var.service_name
    ENV     = var.env
  }
}

// Security Group configure for EKS Node
resource "aws_security_group" "k8s_node" {
  name        = "${var.service_name}-${var.env}-k8s-node"
  description = "${var.service_name} ${var.env} k8s node security group"
  vpc_id      = aws_vpc.advent_eks.id

  tags = {
    Name    = "${var.service_name}-${var.env}-k8s-node-sg"
    Service = var.service_name
    ENV     = var.env
  }
}

resource "aws_security_group_rule" "k8s_node_ingress" {
  type              = "ingress"
  from_port         = 0
  to_port           = 65535
  protocol          = "-1"
  self              = true
  security_group_id = aws_security_group.k8s_node.id
}

resource "aws_security_group_rule" "control_plane_ingress" {
  type                     = "ingress"
  from_port                = 1025
  to_port                  = 65535
  protocol                 = "tcp"
  security_group_id        = aws_security_group.k8s_node.id
  source_security_group_id = aws_security_group.advent_eks.id
}

resource "aws_security_group_rule" "control_plane_ingress_443" {
  type                     = "ingress"
  from_port                = 443
  to_port                  = 443
  protocol                 = "tcp"
  security_group_id        = aws_security_group.k8s_node.id
  source_security_group_id = aws_security_group.advent_eks.id
}

resource "aws_security_group_rule" "k8s_node_egress" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.k8s_node.id
}
```

VPCなどはよしなに用意してください。
ポイントはcontrol planeとワーカーノードから相互に疎通できるよう必要なポートを開けてあげる必要があるところです。

### ConfigMap
ざっとリソースができたら下記コマンドにてEKS用に`kubeconfig`を作成します。

```bash
$ aws eks --region us-east-1 update-kubeconfig --name advent-k8s-sand-k8s
Added new context arn:aws:eks:us-east-1:123456789012:cluster/advent-k8s-sand-k8s to /Users/user/.kube/config
```

手動で作ることももちろん可能ですが、パスを指定したりのちょっとめんどくさいので今回は普通にやりました。

```yaml:config_map_aws_auth.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: arn:aws:iam::123456789012:role/advent-k8s-sand-k8s-nodes
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes
```

kubectlコマンドにてAWSの作成済みリソースと連携します。

```bash
$ kubectl apply -f config_map_aws_auth.yaml
configmap/aws-auth created
```

ここまで実施するとkubectlにてリソース状態を確認できます。

```bash
$ kubectl get nodes --watch
NAME                              STATUS     ROLES    AGE   VERSION
ip-192-168-179-153.ec2.internal   NotReady   <none>   8s    v1.14.8-eks-b8860f
ip-192-168-73-176.ec2.internal    NotReady   <none>   11s   v1.14.8-eks-b8860f
NAME                              AGE
ip-192-168-73-176.ec2.internal    20s
ip-192-168-73-176.ec2.internal    20s
ip-192-168-179-153.ec2.internal   20s
ip-192-168-179-153.ec2.internal   20s
ip-192-168-179-153.ec2.internal   30s
ip-192-168-73-176.ec2.internal    50s
ip-192-168-179-153.ec2.internal   90s
ip-192-168-73-176.ec2.internal    110s
ip-192-168-179-153.ec2.internal   2m30s
$ 
$ kubectl get nodes
NAME                              STATUS   ROLES    AGE     VERSION
ip-192-168-179-153.ec2.internal   Ready    <none>   2m39s   v1.14.8-eks-b8860f
ip-192-168-73-176.ec2.internal    Ready    <none>   2m42s   v1.14.8-eks-b8860f
```

### kubectl apply
ここから先はGCPでも同じですがゲストブックのマニュフェストを反映していきます。

```
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-master-controller.json
replicationcontroller/redis-master created
$ 
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-master-service.json
service/redis-master created
$ 
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-slave-controller.json
replicationcontroller/redis-slave created
$ 
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-slave-service.json
service/redis-slave created
$ 
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/guestbook-controller.json
replicationcontroller/guestbook created
$ 
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/guestbook-service.json
service/guestbook created
$ 
$ kubectl get service
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP                                                              PORT(S)          AGE
guestbook      LoadBalancer   10.100.87.103   a55fb3b2225bb11ea8ecd0aa3c594265-989118657.us-east-1.elb.amazonaws.com   3000:31524/TCP   6s
kubernetes     ClusterIP      10.100.0.1      <none>                                                                   443/TCP          48m
redis-master   ClusterIP      10.100.87.66    <none>                                                                   6379/TCP         57s
redis-slave    ClusterIP      10.100.107.22   <none>                                                                   6379/TCP         22s
```

あとはLBの`EXTERNAL-IP`にアクセスしてあげればゲストブックが見えます。
![1_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423088/245f2b62-b37b-344c-7573-0321b9c7f559.png)

# GKEへのデプロイ
こちらも同様Terraformでリソースを作成して、kubectlにてゲストブックマニュフェストを反映していきます。

### リソース作成
Terraformのコードはマネージドが優秀なのでめちゃくちゃ短いです。

```go:gke.tf
resource "google_container_cluster" "advent_gke" {
  name     = "${var.service_name}-${var.env}-gke"
  location = var.location

  remove_default_node_pool = true
  initial_node_count       = 2

  master_auth {
    username = var.user_name
    password = var.user_passwd

    client_certificate_config {
      issue_client_certificate = false
    }
  }
}

resource "google_container_node_pool" "advent_k8s_node" {
  name       = "${var.service_name}-${var.env}-k8s-node"
  location   = var.location
  cluster    = google_container_cluster.advent_gke.name
  node_count = 2

  node_config {
    preemptible  = true
    machine_type = var.machine

    metadata = {
      disable-legacy-endpoints = "true"
    }

    oauth_scopes = [
      "https://www.googleapis.com/auth/logging.write",
      "https://www.googleapis.com/auth/monitoring",
    ]
  }
}
```

<img width="910" alt="1_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423088/a95d6e58-5609-055a-1a0e-fee5654905c0.png">

サクッとできました。

### Cloud Shell
GCPはコンソール上で簡単にCLIを叩くことができます。せっかくなので利用してみます。
画面上部の赤枠を押すと下部に表示されます。
<img width="1321" alt="1_3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423088/d03f920f-e860-b3b5-4e79-431fc2e3e2f5.png">

ではこのままコマンドを打ち込んでいきます。

aws cliを使って実施したようにgcloudコマンドを利用してkubeconfigを作成します。

```bash
$ gcloud container clusters get-credentials advent-k8s-sand-gke --zone us-east1-b
Fetching cluster endpoint and auth data.
kubeconfig entry generated for advent-k8s-sand-gke.
$ 
$ kubectl get node
NAME                                                  STATUS   ROLES    AGE   VERSION
gke-advent-k8s-sand--advent-k8s-sand--7921968e-j0s6   Ready    <none>   41m   v1.13.11-gke.14
gke-advent-k8s-sand--advent-k8s-sand--7921968e-s5cm   Ready    <none>   41m   v1.13.11-gke.14
$ kubectl get node --watch
NAME                                                  STATUS   ROLES    AGE   VERSION
gke-advent-k8s-sand--advent-k8s-sand--7921968e-j0s6   Ready    <none>   41m   v1.13.11-gke.14
gke-advent-k8s-sand--advent-k8s-sand--7921968e-s5cm   Ready    <none>   41m   v1.13.11-gke.14
gke-advent-k8s-sand--advent-k8s-sand--7921968e-s5cm   Ready   <none>   41m   v1.13.11-gke.14
```
無事に確認できました。

### kubectl
あとは同様にgithubからマニュフェストを引っ張ってくるだけです。

```bash
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-master-controlle
r.json
replicationcontroller/redis-master created
$ 
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-master-service.j
son
service/redis-master created
$ 
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-slave-controller
.json
replicationcontroller/redis-slave created
$ 
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-slave-service.js
on
service/redis-slave created
$ 
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/guestbook-controller.j
son
replicationcontroller/guestbook created
$ 
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/guestbook-service.json
service/guestbook created
$ 
$ kubectl get services
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)          AGE
guestbook      LoadBalancer   10.23.241.207   35.231.230.228   3000:32602/TCP   112s
kubernetes     ClusterIP      10.23.240.1     <none>           443/TCP          47m
redis-master   ClusterIP      10.23.247.54    <none>           6379/TCP         2m20s
redis-slave    ClusterIP      10.23.241.61    <none>           6379/TCP         2m4s
```

![1_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423088/a8504d8a-bc8c-9a2f-7f70-4a8168bc3b71.png)

### まとめ
どちらもkubectlでのマニュフェスト反映は同じようにできますが、それをデプロイするための環境を整えるまでの道のりが違います。
AWSはやはりSecurityGroup周りなどから用意してあげる必要があり、使い出すまでに手間がかかるかなと思います。
反面GCPは簡単にできるけど、内部の通信がどうなっているかがわからなくて微妙な気持ちも起きると思います。
初めて触る分にはGCPの方が入門しやすいと思いますが、自分の環境や習熟度によって使い分ける必要があると思います。
とりあえずどちらでも面白いのでこれを機にぜひ触れてみていただければと思います！