Running Kubernetes on AWS using kops

2018-01-24

Aws · Devops · Kops · Kubernetes

5 minutes

Overview

This post will walk through the steps on how we provisioned our production Kubernetes cluster @mobingi. Some of the bits here are already automated in our case but I will try to include as much details as I can.

Our goals would be the following:

  • Provision a Kubernetes cluster on AWS using kops.
  • The cluster will have two autoscaling groups: one for on-demand, one for spot instances.
  • It’s going to be a gossip-based cluster.
  • RBAC is enabled in the cluster.

There is no need to rewrite the otherwise excellent installation instructions written in the kops wiki. Basically, we can just follow the setup instructions there. The instructions in this post will be specific to our goals above.

Gossip-based cluster

We will skip the DNS configuration section as we are provisioning a gossip-based cluster, meaning, the cluster name will be something like "<some-name>.k8s.local". We will be using "mycluster.k8s.local" as our cluster name.

SSH keypair

We will create the keypair in AWS under EC2 -> Key Pairs -> Create Key Pair. We need this keypair when we need to ssh to our cluster nodes. After saving the keypair somewhere, we will generate the public key using the following command:

$ ssh-keygen -y -f mycluster.pem > mycluster.pem.pub

Create the cluster

At this point, we should already have our environment variables set, mainly NAME and KOPS_STATE_STORE. To create the cluster:

# I'm from Japan so I'm using ap-northeast-1 (Tokyo)
$ kops create cluster \
      --ssh-public-key mycluster.pem.pub
      --zones ap-northeast-1a,ap-northeast-1c \
      --authorization RBAC \
      --cloud aws ${NAME} \
      --yes

It will take some time before the cluster will be ready. To validate the cluster:

$ kops validate cluster
Using cluster from kubectl context: mycluster.k8s.local

Validating cluster mycluster.k8s.local

INSTANCE GROUPS
NAME                    ROLE    MACHINETYPE     MIN     MAX     SUBNETS
master-ap-northeast-1a  Master  m3.medium       1       1       ...
nodes                   Node    t2.medium       2       2       ...

NODE STATUS
NAME                                                    ROLE    READY
ip-x-x-x-x.ap-northeast-1.compute.internal              master  True
ip-x-x-x-x.ap-northeast-1.compute.internal              node    True
ip-x-x-x-x.ap-northeast-1.compute.internal              node    True
...

Your cluster mycluster.k8s.local is ready

Notice that we only have one instance for our master. We can also opt to have a highly available master using these options, which is generally recommended for production clusters. Based on our experience though, this single master instance setup is good enough for development and/or staging clusters. There’s going to be downtime if master goes down in which case the duration will depend on how long AWS autoscaling group kicks in. During that window, k8s API won’t be accessible but the nodes will continue to work, including our deployed applications.

Spot instance autoscaling group

Once the cluster is ready, we will add another instance group for spot instances. The default instance group created in the previous command, named “nodes”, will be our on-demand group. To add:

$ kops create ig nodes-spots -subnet ap-northeast-1a,ap-northeast-1c

You can then edit using the following contents (modify values as needed):

apiVersion: kops/v1alpha2
kind: InstanceGroup
metadata:
  creationTimestamp: <some-datetime-value-here>
  labels:
    kops.k8s.io/cluster: mycluster.k8s.local
  name: nodes-spot
spec:
  image: kope.io/k8s-1.8-debian-jessie-amd64-hvm-ebs-2017-12-02
  machineType: t2.medium
  maxPrice: "0.02"
  maxSize: 10
  minSize: 2
  nodeLabels:
    kops.k8s.io/instancegroup: nodes
    spot: "true"
  role: Node
  subnets:
  - ap-northeast-1a
  - ap-northeast-1c

We can now update our cluster with the following commands:

# [optional] update our on-demand group's max size to some number
$ kops edit ig nodes

# [optional] preview the changes to be applied
$ kops update cluster ${NAME}

# actual cluster update
$ kops update cluster ${NAME} --yes

# [optional] check if we need rolling update
$ kops rolling-update cluster

# if so, add --yes option
$ kops rolling-update cluster --yes

We can now validate the cluster to see our changes:

$ kops validate cluster
Validating cluster mycluster.k8s.local

INSTANCE GROUPS
NAME                    ROLE    MACHINETYPE     MIN     MAX     SUBNETS
master-ap-northeast-1a  Master  m3.medium       1       1       ...
nodes                   Node    t2.medium       2       4       ...
nodes-spot              Node    t2.medium       2       10      ...

NODE STATUS
NAME                                                    ROLE    READY
ip-x-x-x-x.ap-northeast-1.compute.internal              master  True
ip-x-x-x-x.ap-northeast-1.compute.internal              node    True
ip-x-x-x-x.ap-northeast-1.compute.internal              node    True
...

Your cluster mycluster.k8s.local is ready

When our cluster was created, kops also has automatically generated our config file for kubectl. To verify:

$ kubectl cluster-info
Kubernetes master is running at https...
KubeDNS is running at https...

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

Setup cluster autoscaler

Clusters created using kops use autoscaling groups, but without scaling policies (at the time of writing). To enable dynamic scaling of our cluster, we will use cluster autoscaler. Before cluster autoscaler deployment, we need to setup some prerequisites.

First, we need to attach the following permissions to master and nodes IAM role. Go to IAM roles console and add an inline policy to the roles created by kops. Role names would be something like:

  • master.mycluster.k8s.local
  • nodes.mycluster.k8s.local
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "autoscaling:DescribeAutoScalingGroups",
                "autoscaling:DescribeAutoScalingInstances",
                "autoscaling:DescribeTags",
                "autoscaling:SetDesiredCapacity",
                "autoscaling:TerminateInstanceInAutoScalingGroup"
            ],
            "Resource": "*"
        }
    ]
}

The latest installation instructions can be found here. The general idea is to choose the latest yaml file, update it with your own values, and apply the file using kubectl. The readme file also provides a script that does the download and edit for us. We will be using the following command line arguments for our autoscaler:

command:
  - ./cluster-autoscaler
  - --cloud-provider=aws
  - --v=4
  - --stderrthreshold=info
  - --scale-down-delay=5m
  - --skip-nodes-with-local-storage=false
  - --expander=least-waste
  - --nodes=2:4:nodes.mycluster.k8s.local
  - --nodes=2:10:nodes-spot.mycluster.k8s.local 

You should update the last two line with your own autoscaling group min/max values. Finally, we deploy our autoscaler with:

$ kubectl create -f cluster-autoscaler.yml
deployment "cluster-autoscaler" created

That’s it. You may also want to install these addons if you like.

SSH to a node

# sample ip's only
$ kubectl get node -o wide
NAME                           STATUS    ROLES     AGE   VERSION   EXTERNAL-IP  OS-IMAGE                      ...
ip-x.x.x.x.ap-northeast-1...   Ready     master    1d    v1.8.6    1.2.3.4      Debian GNU/Linux 8 (jessie)   ...
ip-x.x.x.x.ap-northeast-1...   Ready     node      1d    v1.8.6    1.2.3.5      Debian GNU/Linux 8 (jessie)   ...
ip-x.x.x.x.ap-northeast-1...   Ready     node      1d    v1.8.6    1.2.3.6      Debian GNU/Linux 8 (jessie)   ...
ip-x.x.x.x.ap-northeast-1...   Ready     node      1d    v1.8.6    1.2.3.7      Debian GNU/Linux 8 (jessie)   ...
ip-x.x.x.x.ap-northeast-1...   Ready     node      1d    v1.8.6    1.2.3.8      Debian GNU/Linux 8 (jessie)   ...

# default username is `admin`
$ ssh -i mycluster.pem admin@1.2.3.4