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との住み分けが難しい
GKEとは
Kubernetes Engine(GKE)は、コンテナ化されたアプリケーションをデプロイするための、本番環境に対応したマネージド型環境です。
公式リファレンス : https://cloud.google.com/kubernetes-engine/?hl=ja
こちらもEKS同様GCPのマネージドなKubernetesサービスです。
前述の通り、コンテナ技術自体Googleが長年培ってきたものなので、入門はしやすいと思います。
反面ネットワーク周りなどブラックボックスが多く、インフラ寄りの方はお任せするのは物足りないと思います。
マネージドも割と充実している印象なのですが、やはり学習コストは高いと思います。
メリット
- サポートが手厚い
- Stackdriverがネイティブで使える
デメリット
- 学習コストが高い
- Cloud Runとの住み分けに迷う
EKSへのデプロイ
先日のre:Invent 2019にてEKS on FargateがGAとなりましたが、今回は通常のEC2にデプロイしていきます。
公式にもあるゲストブックアプリケーションのデプロイをしていきます。
リソース作成
今回はGKEにもデプロイをするのでTerraformでコードを書いていきます。
// 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
}
// 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
を作成します。
$ 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
手動で作ることももちろん可能ですが、パスを指定したりのちょっとめんどくさいので今回は普通にやりました。
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の作成済みリソースと連携します。
$ kubectl apply -f config_map_aws_auth.yaml
configmap/aws-auth created
ここまで実施するとkubectlにてリソース状態を確認できます。
$ 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
にアクセスしてあげればゲストブックが見えます。
GKEへのデプロイ
こちらも同様Terraformでリソースを作成して、kubectlにてゲストブックマニュフェストを反映していきます。
リソース作成
Terraformのコードはマネージドが優秀なのでめちゃくちゃ短いです。
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",
]
}
}
サクッとできました。
Cloud Shell
GCPはコンソール上で簡単にCLIを叩くことができます。せっかくなので利用してみます。
画面上部の赤枠を押すと下部に表示されます。
ではこのままコマンドを打ち込んでいきます。
aws cliを使って実施したようにgcloudコマンドを利用してkubeconfigを作成します。
$ 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からマニュフェストを引っ張ってくるだけです。
$ 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
まとめ
どちらもkubectlでのマニュフェスト反映は同じようにできますが、それをデプロイするための環境を整えるまでの道のりが違います。
AWSはやはりSecurityGroup周りなどから用意してあげる必要があり、使い出すまでに手間がかかるかなと思います。
反面GCPは簡単にできるけど、内部の通信がどうなっているかがわからなくて微妙な気持ちも起きると思います。
初めて触る分にはGCPの方が入門しやすいと思いますが、自分の環境や習熟度によって使い分ける必要があると思います。
とりあえずどちらでも面白いのでこれを機にぜひ触れてみていただければと思います!