できる限りterraformでリソース管理しつつkopsでkubernetesクラスタを作る

最近KubernetesクラスタAWS上に作っているのだが,EKSは結構お高い.

これはどうしても削れない,EKSの利用料金として,$0.20/hour持っていかれるためである. EKSだけでだいたい$144/monthかかることになる.

これは流石に痛いので,kopsでKubernetesクラスタを作ることにした.

kopsコマンドで簡単にクラスタが作れるよ!という記事は結構あるのだが,実運用していく上で結構カスタマイズしたい部分があったので,書き残しておく.

VPC, IAM, SGあたりはterraform管理したい

とりあえず一度kopsで適当にクラスタを建ててみて,自動生成されるリソースのうち,こちら側で予め用意できるものがどの程度あるか確認した. これは,IAMやSecurityGroup等はできるだけterraform管理したいからだ.

kopsはVPCを始めとして,IAMやSecurityGroupを自動で作ってくれてしまう.たしかに初心者には優しいのだが,これをやられると,「別サーバからのアクセスを受け付けたいからSecurityGroupに穴あけなきゃ」というようなものをコードで管理する方法がなくなってしまう.

で,確認したところ

  • VPC
  • subnet
  • API用のELBにつけるSG
  • master/nodeのIAM Profile
  • master/nodeのSG

についてはこちらで用意したものを付与できる.

これならばkopsで問題なくいけそう. ただし,これらのリソースに加えて,KeyPair等までこちらで指定のものを使ってほしい場合には,kube-awsを検討したほうが良い.基本的に使用するAWSリソースのカスタマイズ性に関してはkube-awsのほうが多機能な気がしている. 俺が今回kopsを使っているのは,単純にkube-awsの裏にいるCloudFormationが嫌いというだけのことだ.

予め必要なリソースをterraformで作る

先にterraformで必要なものを作っておく. kopsは,あとから設定変更に応じてクラスタをアップデートできるのだが,インスタンス再生成が発生してしまい,結構時間を取られるので先に埋めてしまったほうが楽だ.

IAM Role

インスタンスに付与するIAM Role,Instance Profileを作っておく.

IAM Role:

resource "aws_iam_role" "k8s_master_role" {
  name               = "k8s-master-role"
  path               = "/"
  assume_role_policy = "${file("aws_iam_role_policies/ec2_assume_role_policy.json")}"
}

resource "aws_iam_role" "k8s_node_role" {
  name               = "k8s-node-role"
  path               = "/"
  assume_role_policy = "${file("aws_iam_role_policies/ec2_assume_role_policy.json")}"
}

こいつはEC2インスタンスにつけるRoleなので,EC2へのassumeが必要になる.

Assume Role Policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

次に,先のRoleを使ってInstance Profileを作っておく.

Instance Profile:

resource "aws_iam_instance_profile" "k8s_master_profile" {
  name = "k8s-master-profile"
  role = "${aws_iam_role.k8s_master_role.name}"
}

resource "aws_iam_instance_profile" "k8s_node_profile" {
  name = "k8s-node-profile"
  role = "${aws_iam_role.k8s_node_role.name}"
}

そして,これが k8s-master-role につけるPolicy.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances",
        "ec2:DescribeRegions",
        "ec2:DescribeRouteTables",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeSubnets",
        "ec2:DescribeVolumes"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:CreateSecurityGroup",
        "ec2:CreateTags",
        "ec2:CreateVolume",
        "ec2:DescribeVolumesModifications",
        "ec2:ModifyInstanceAttribute",
        "ec2:ModifyVolume"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:AttachVolume",
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:CreateRoute",
        "ec2:DeleteRoute",
        "ec2:DeleteSecurityGroup",
        "ec2:DeleteVolume",
        "ec2:DetachVolume",
        "ec2:RevokeSecurityGroupIngress"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "autoscaling:DescribeAutoScalingGroups",
        "autoscaling:DescribeLaunchConfigurations",
        "autoscaling:DescribeTags"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "autoscaling:SetDesiredCapacity",
        "autoscaling:TerminateInstanceInAutoScalingGroup",
        "autoscaling:UpdateAutoScalingGroup"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "elasticloadbalancing:AddTags",
        "elasticloadbalancing:AttachLoadBalancerToSubnets",
        "elasticloadbalancing:ApplySecurityGroupsToLoadBalancer",
        "elasticloadbalancing:CreateLoadBalancer",
        "elasticloadbalancing:CreateLoadBalancerPolicy",
        "elasticloadbalancing:CreateLoadBalancerListeners",
        "elasticloadbalancing:ConfigureHealthCheck",
        "elasticloadbalancing:DeleteLoadBalancer",
        "elasticloadbalancing:DeleteLoadBalancerListeners",
        "elasticloadbalancing:DescribeLoadBalancers",
        "elasticloadbalancing:DescribeLoadBalancerAttributes",
        "elasticloadbalancing:DetachLoadBalancerFromSubnets",
        "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
        "elasticloadbalancing:ModifyLoadBalancerAttributes",
        "elasticloadbalancing:RegisterInstancesWithLoadBalancer",
        "elasticloadbalancing:SetLoadBalancerPoliciesForBackendServer"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeVpcs",
        "elasticloadbalancing:AddTags",
        "elasticloadbalancing:CreateListener",
        "elasticloadbalancing:CreateTargetGroup",
        "elasticloadbalancing:DeleteListener",
        "elasticloadbalancing:DeleteTargetGroup",
        "elasticloadbalancing:DeregisterTargets",
        "elasticloadbalancing:DescribeListeners",
        "elasticloadbalancing:DescribeLoadBalancerPolicies",
        "elasticloadbalancing:DescribeTargetGroups",
        "elasticloadbalancing:DescribeTargetHealth",
        "elasticloadbalancing:ModifyListener",
        "elasticloadbalancing:ModifyTargetGroup",
        "elasticloadbalancing:RegisterTargets",
        "elasticloadbalancing:SetLoadBalancerPoliciesOfListener"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "iam:ListServerCertificates",
        "iam:GetServerCertificate"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:Get*",
        "s3:ListBucket"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:GetRepositoryPolicy",
        "ecr:DescribeRepositories",
        "ecr:ListImages",
        "ecr:BatchGetImage"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:CreateNetworkInterface",
        "ec2:AttachNetworkInterface",
        "ec2:DeleteNetworkInterface",
        "ec2:DetachNetworkInterface",
        "ec2:DescribeNetworkInterfaces",
        "ec2:DescribeInstances",
        "ec2:ModifyNetworkInterfaceAttribute",
        "ec2:AssignPrivateIpAddresses",
        "tag:TagResources"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

次にnodeに付与するPolicy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:Get*",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::kops_storage_bucket",
        "arn:aws:s3:::kops_storage_bucket/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:GetRepositoryPolicy",
        "ecr:DescribeRepositories",
        "ecr:ListImages",
        "ecr:BatchGetImage"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances",
        "ec2:DescribeRegions",
        "ec2:CreateNetworkInterface",
        "ec2:AttachNetworkInterface",
        "ec2:DeleteNetworkInterface",
        "ec2:DetachNetworkInterface",
        "ec2:DescribeNetworkInterfaces",
        "ec2:DescribeInstances",
        "ec2:ModifyNetworkInterfaceAttribute",
        "ec2:AssignPrivateIpAddresses",
        "tag:TagResources"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

S3のBucket名は,kopsの構成ファイルを格納するS3のBucket名だ.これは初回に kops create するときに入力するものと同じである.

resource "aws_iam_policy" "k8s_cluster" {
  name        = "k8s-cluster-policy"
  path        = "/"
  description = ""
  policy      = "${file("aws_iam_policies/k8s_cluster_policy.json")}"
}

resource "aws_iam_policy" "k8s_node" {
  name        = "k8s-node-policy"
  path        = "/"
  description = ""
  policy      = "${file("aws_iam_policies/k8s_node_policy.json")}"
}

でこれらを紐付ける.

resource "aws_iam_policy_attachment" "k8s_cluster" {
  name = "k8s-cluster"

  roles = [
    "${aws_iam_role.k8s_master_role.name}",
  ]

  policy_arn = "${aws_iam_policy.k8s_cluster.arn}"
}

resource "aws_iam_policy_attachment" "k8s_node" {
  name = "k8s-node"

  roles = [
    "${aws_iam_role.k8s_node_role.name}",
  ]

  policy_arn = "${aws_iam_policy.k8s_node.arn}"
}

Security Group

masterに付与するSecurityGroup:

resource "aws_security_group" "master_instance" {
  name        = "${var.namespace}-master-instance-${var.env}"
  description = "Kubernetes master instance"

  vpc_id = "${data.terraform_remote_state.aws_vpc_tokyo.vpc_id}"

  # For SSH
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # Myself
  ingress {
    from_port = 0
    to_port   = 0
    protocol  = "-1"
    self      = true
  }

  # For node
  ingress {
    from_port = 0
    to_port   = 0
    protocol  = "-1"

    security_groups = [
      "${aws_security_group.node_instance.id}",
    ]
  }

  # For API ELB
  ingress {
    from_port = 443
    to_port   = 443
    protocol  = "tcp"

    security_groups = [
      "${aws_security_group.api_lb.id}",
    ]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags {
    Name    = "${var.namespace}-master-instance-${var.env}"
    tfstate = "${var.tfstate}"
  }
}

nodeに付与するSecurityGroup.ただし,これに関しては,SecurityGroupだけ定義して,Ruleを別定義にしている. これは,同時定義すると,master <-> nodeが相互依存になってしまって,terraform的に同時作成ができないためである.

resource "aws_security_group" "node_instance" {
  name        = "${var.namespace}-node-instance-${var.env}"
  description = "Kubernetes node instance"

  vpc_id = "${data.terraform_remote_state.aws_vpc_tokyo.vpc_id}"

  tags {
    Name    = "${var.namespace}-node-instance-${var.env}"
    tfstate = "${var.tfstate}"
  }
}

resource "aws_security_group_rule" "node_ssh" {
  type              = "ingress"
  from_port         = 22
  to_port           = 22
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = "${aws_security_group.node_instance.id}"
}

resource "aws_security_group_rule" "node_myself" {
  type              = "ingress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  self              = true
  security_group_id = "${aws_security_group.node_instance.id}"
}

resource "aws_security_group_rule" "node_from_master" {
  type                     = "ingress"
  from_port                = 0
  to_port                  = 0
  protocol                 = "-1"
  source_security_group_id = "${aws_security_group.master_instance.id}"
  security_group_id        = "${aws_security_group.node_instance.id}"
}

resource "aws_security_group_rule" "node_from_service" {
  type                     = "ingress"
  from_port                = 0
  to_port                  = 0
  protocol                 = "-1"
  source_security_group_id = "${aws_security_group.service_lb.id}"
  security_group_id        = "${aws_security_group.node_instance.id}"
}

resource "aws_security_group_rule" "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.node_instance.id}"
}

kopsはmasterインスタンスを作る際にELBも一緒に作る.これは kubectl コマンド等により外部からクラスタAPIを叩く際に,そのリクエストを受け付けるELBである. で,こいつにもSecurityGroupをつける必要がある.

resource "aws_security_group" "api_lb" {
  name        = "${var.namespace}-api-lb-${var.env}"
  description = "Kubernetes API LB"

  vpc_id = "${data.terraform_remote_state.aws_vpc_tokyo.vpc_id}"

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags {
    Name = "${var.namespace}-api-lb-${var.env}"
  }
}

これでterraformで作成しておくリソースはすべて作った.

Clusterを編集する

一番最初は kops create する.ただし --yes をつけてはいけない.これをつけると,いきなりクラスタを作り始めてしまう. クラスタを作る前に,構成情報だけ生成し,中身を編集しよう.

なお,このときに先程ポリシー内に記載した kops_storage_bucket を指定する必要がある.詳しくはkopsのガイドを見ると良い.

github.com

kops create したら早速 kops edit cluster をやろう.

ここで上書きできる項目は,

  • API用ELBのSecurityGroup
  • VPC
  • VPC Cidr
  • subnet

である.

github.com

github.com

apiVersion: kops/v1alpha2
kind: Cluster
metadata:
  creationTimestamp: 2019-02-19T12:59:21Z
  name: hogehoge
spec:
  api:
    loadBalancer:
      securityGroupOverride: <aws_security_group.api_lbのID>
      type: Public
  authorization:
    rbac: {}
  channel: stable
  cloudProvider: aws
  configBase: s3://hogehoge
  etcdClusters:
  - etcdMembers:
    - instanceGroup: master-ap-northeast-1a
      name: a
    name: main
  - etcdMembers:
    - instanceGroup: master-ap-northeast-1a
      name: a
    name: events
  iam:
    allowContainerRegistry: true
    legacy: false
  kubelet:
    anonymousAuth: false
  kubernetesApiAccess:
  - 0.0.0.0/0
  kubernetesVersion: 1.11.6
  masterInternalName: api.internal.hogehoge
  masterPublicName: api.hogehoge
  networkCIDR: 10.0.0.0/16
  networkID: <VPCのID>
  networking:
    amazonvpc: {}
  nonMasqueradeCIDR: <VPCのcidr block>
  sshAccess:
  - 0.0.0.0/0
  subnets:
  - cidr: <subnetのcidr block>
    id: <subnetのID>
    name: ap-northeast-1a
    type: Public
    zone: ap-northeast-1a
  - cidr: <subnetのcidr block>
    id: <subnetのID>
    name: ap-northeast-1c
    type: Public
    zone: ap-northeast-1c
  - cidr: <subnetのcidr block>
    id: <subnetのID>
    name: ap-northeast-1d
    type: Public
    zone: ap-northeast-1d
  topology:
    dns:
      type: Public
    masters: public
    nodes: public

こんな感じにしておく.

Instancegroupを編集する

次に kops edit ig する.

ここで上書きできるのが,

  • master/nodeのInstance Profile
  • master/nodeに付与するSecurityGroup

である.

github.com

github.com

apiVersion: kops/v1alpha2
kind: InstanceGroup
metadata:
  creationTimestamp: 2019-02-19T12:59:21Z
  labels:
    kops.k8s.io/cluster: hogehoge
  name: master-ap-northeast-1a
spec:
  iam:
    profile: <aws_iam_profile.k8s_master_profileのARN>
  image: kope.io/k8s-1.11-debian-stretch-amd64-hvm-ebs-2018-08-17
  machineType: m5.large
  maxPrice: "0.1"
  maxSize: 1
  minSize: 1
  nodeLabels:
    kops.k8s.io/instancegroup: master-ap-northeast-1a
  role: Master
  securityGroupOverride: <aws_security_group.master_instanceのID>
  subnets:
  - ap-northeast-1a

---

apiVersion: kops/v1alpha2
kind: InstanceGroup
metadata:
  creationTimestamp: 2019-02-19T12:59:21Z
  labels:
    kops.k8s.io/cluster: hogehoge
  name: nodes
spec:
  iam:
    profile: <aws_iam_profile.k8s_node_profileのARN
  image: kope.io/k8s-1.11-debian-stretch-amd64-hvm-ebs-2018-08-17
  machineType: t3.medium
  maxPrice: "0.05"
  maxSize: 2
  minSize: 2
  nodeLabels:
    kops.k8s.io/instancegroup: nodes
  role: Node
  securityGroupOverride: <aws_security_group.node_instanceのID>
  subnets:
  - ap-northeast-1a
  - ap-northeast-1c
  - ap-northeast-1d

これで良い. あとは, kops update --yes とかして,実際のクラスタを作成しよう.

terraformで作成したリソースが紐付いたクラスタが作成されるはずである.

aws-iam-authenticatorによる認証を行いたい

kopsでクラスタを作成すると .kube/configkubectl で使う認証情報が吐き出される.ここにはCertificate等が書かれている. しかし,kopsを使ったマシン以外のマシンで kubectl を使いたい場合や,別のメンバーに kubectl による認証をさせるためには,結構めんどくさい.

EKSを使っていた際は, aws-iam-authenticatorを使っていたので,これで解決できると非常に楽になる.

というわけでやってみる. もちろんサポートはしている.

github.com

ただし,このガイドの通りにやると

error building loader: certificate "aws-iam-authenticator" not found

となってしまう.

そこで,

github.com

この通りにやるとうまく行く.

kubernetes admin roleを作る

まず,AdminのRoleを作る必要がある.再びterraformで,

resource "aws_iam_role" "kubernetes_admin_role" {
  name               = "kubernetes-admin-role"
  path               = "/"
  assume_role_policy = "${data.template_file.account_assume_role_policy.rendered}"
}
data "template_file" "account_assume_role_policy" {
  template = "${file("${path.module}/aws_iam_role_policies/sts_assume_role_policy.json.tpl")}"

  vars {
    account_id = "${var.account_id}"
  }
}
{
  "Version":"2012-10-17",
  "Statement": [
    {
      "Effect":"Allow",
      "Principal": {
        "AWS": "${account_id}"
      },
      "Action": "sts:AssumeRole",
      "Condition": {}
    }
  ]
}

こんなのを作ってやる. こいつはassumeさせるだけで,特にPolicyを付与させる必要はない.

ConfigMapを作る

次にkubernetes内にConfigMapを作る必要がある.

kubectlが使える状況で,

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: kube-system
  name: aws-iam-authenticator
  labels:
    k8s-app: aws-iam-authenticator
data:
  config.yaml: |
    clusterID: hogehoge
    server:
      # each mapRoles entry maps an IAM role to a username and set of groups
      # Each username and group can optionally contain template parameters:
      #  1) "{{AccountID}}" is the 12 digit AWS ID.
      #  2) "{{SessionName}}" is the role session name.
      mapRoles:
      - roleARN: arn:aws:iam::12345678:role/kubernetes-admin-role
        username: kubernetes-admin
        groups:
        - system:masters
      # map EC2 instances in my "KubernetesNode" role to users like
      # "aws:000000000000:instance:i-0123456789abcdef0". Only use this if you
      # trust that the role can only be assumed by EC2 instances. If an IAM user
      # can assume this role directly (with sts:AssumeRole) they can control
      # SessionName.
      - roleARN: arn:aws:iam::12345678:role/k8s-node-role
        username: aws:{{AccountID}}:instance:{{SessionName}}
        groups:
        - system:bootstrappers
        - aws:instances
      # map federated users in my "KubernetesAdmin" role to users like
      # "admin:alice-example.com". The SessionName is an arbitrary role name
      # like an e-mail address passed by the identity provider. Note that if this
      # role is assumed directly by an IAM User (not via federation), the user
      # can control the SessionName.
      - roleARN: arn:aws:iam::12345678:role/kubernetes-admin-role
        username: admin:{{SessionName}}
        groups:
        - system:masters
      # each mapUsers entry maps an IAM role to a static username and set of groups
      mapUsers:
      - userARN: arn:aws:iam::12345678:user/h3poteto
        username: h3poteto
        groups:
        - system:masters

これを kubectl apply してしまう.

clusterを更新する

次に kops edit cluster して,

apiVersion: kops/v1alpha2
kind: Cluster
metadata:
  creationTimestamp: 2019-02-19T12:59:21Z
  name: hogehoge
spec:
  authentication:
    aws: {}

というように authenticationの行を追加し,awsを指定する

これで,

$ kops update cluster $NAME --yes
$ kops rolling-update cluster ${NAME} --instance-group-roles=Master  --cloudonly --force --yes
$ kops validate cluster

という順でやっていくと,見事aws-iam-authenticatorが起動する.

認証する

最後に, .kube/config-hogehoge を作り,

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: <ここは ~/.kube/configからコピーする>
    server: <API ELBのendpoint>
  name: hogehoge
contexts:
- context:
    cluster: hogehoge
    user: aws
  name: aws
current-context: aws
kind: Config
preferences: {}
users:
- name: aws
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1alpha1
      command: aws-iam-authenticator
      args:
        - "token"
        - "-i"
        - "hogehoge

こんなのを用意する.

で,

$ export KUBECONFIG=$HOME/.kube/config-hogehoge
$ kubectl version

とすると無事認証が通る.

これでaws-iam-authenticatorによる認証ができた.

費用をケチる

kopsでクラスタを構成するとEC2インスタンスはオンデマンドのものを使うことになる. これをASGから起動しているのだが,最近のASGはSpotPriceを設定することができる. これを設定すると,ASGでインスタンスを管理しつつ,単発のSpotInstanceを指定の価格で購入することになる.

こうすることで,見た目はASGだけど,実質SpotInstanceを利用することができる.

これはSpotFleetとは少し違う.残念ながら現状kopsはSpotFleetには対応していない.

というわけで kops edit ig する.

apiVersion: kops/v1alpha2
kind: InstanceGroup
metadata:
  creationTimestamp: 2019-02-19T12:59:21Z
  labels:
    kops.k8s.io/cluster: hogehoge
  name: master-ap-northeast-1a
spec:
  image: kope.io/k8s-1.11-debian-stretch-amd64-hvm-ebs-2018-08-17
  machineType: m5.large
  maxPrice: "0.1"
  maxSize: 1
  minSize: 1

ここの maxPrice を設定すると,これがSpotのMaxPriceとして設定され,SpotInstanceを使うようになる.

こうすることでnodeやmasterのインスタンス料金をケチれる.

まとめ

というわけで無事,terraformでリソースを管理しつつKubernetesクラスタを作ることができた. 欲を言うなら,インスタンスが使うKeyPairあたりも別で管理させてほしいのだが…….この辺はkube-awsだとできるらしい.

あと,SGやIAMで,どんなものが必要になるかがどこにも書いてなくて,ソースを見るか,実際に一度クラスタを作って眺めるしかなかった. このあたりはもう少し丁寧にドキュメントになっていると嬉しい.

今回はSpotInstanceも使っているし,masterは1台だけだし,そこまで強いクラスタを作ったわけではない.どちらかというと安く済ませたいというのが大きい. しかし最終的に,高可用性や耐障害性を考えると,masterを複数台構成にしたり(これ自体はkopsでもそんなに難しいわけではない),etcdを冗長化したりする必要がある. ましてやKubernetesのmasterは,それなりにでかいインスタンスを使う必要があり,それを複数台+APIエンドポイント用のELBとかを考え始めると,おそらくEKSと値段的にはいい勝負なのではないだろうか. なので,そういうしっかりしたクラスタを作りたいのであれば,kopsでmulti master構成を考えるよりはEKSを考えたほうが良いとは思う.