A few weeks ago, my task at work was an interesting one: To deploy a Kubernetes cluster and write the associated tooling so that developers can deploy the code in the branches they’re working on to it, so they can test their changes.
Until that point, I’ve been wanting to learn Kubernetes because it sounded interesting (even though the name is rather problematic when you’re Greek), but I never had an opportunity because I don’t have anything that needs to be on a cluster. So, I jumped at the chance, and started reading up on it, but all the materials (including the official tutorial) seemed too verbose and poorly-structured, so I was a bit dejected.
Anyway, after a few days of research, things finally just clicked and I was deploying machines left and right with wild abandon, quickly racking up thousands in AWS bills, like any self-respecting backend developer in 2018. Since my resume now said “Kubernetes expert”, a thought immediately occurred: “Why not take my vast, unending knowledge of this system that I have collected over hours of research and make it more accessible for people?” Since I couldn’t convince myself I shouldn’t write another rambling article, I quickly got to it.
This is that article.
The main problem I had with existing articles is that none that I could find gave me a high-level summary of what parts are and how they fit together first, before delving into the specifics. That way of presentation is how I learn best, and I’m writing this in that style, in the hope that it will suit you too. If you know of any articles/tutorials that describe how Kubernetes works eloquently, understandably and expertly, shut the hell up because where were you when I needed you and now I’ve written my article and I’m not taking it down.
Also keep in mind that I’ve literally only been working with Kubernetes for a week, so things won’t be very in-depth and some might be inaccurate, although hopefully nothing will be flat-out wrong and the information here will be enough to get you to the point of running a simple cluster.
That having been said, in the end I actually found the concepts in Kubernetes surprisingly simple, although I’m sure there are plenty of things I don’t know yet. The things I do know, though, were enough to set up a cluster and get our stack running on it, and I’m fairly sure they’re enough to get most people started.
The basics
The first thing we need to do is detail the parts of a Kubernetes deployment:
- The cluster: The entire thing, consisting of a control plane and some nodes. Basically, the set of machines you run your containers on, along with the machines that manage the running for you.
- The control plane: This is the part that controls everything else, as the name suggests, and this is what I know nothing about, since we just paid Amazon to worry about this. My understanding is that that is the best decision, and that you should pay some company to manage it for you unless you’re Google.
- Nodes: A node is basically a server, as in the physical worker machine that you’re paying for. It’s where all your code will get deployed, and the way to turn a bare server into a node is to install Docker, kubelet, kube-proxy and some other stuff on it. This article assumes you already have a few workers in your cluster.
- Pods: A pod is a bunch of containers.
This is where your code goes, and usually you’ll have one pod per container, although you might want to put some closely-related services in the same pod.
A pod runs on a single node (but a node can run many pods), which means that all the containers in the pod will have the same IP address and that they can talk to each other by connecting to each other’s ports on
localhost
. Pods cannot be updated once they are deployed, they can only be deleted or replaced. - Deployments: A deployment is how you actually deploy pods to the cluster. You can get a pod running without a deployment, but without one you can’t easily specify the number of replicas you want, automatically redeploy pods when they fail, roll back to earlier states, etc. The deployment is what makes code lifecycle management easier, and is what you’ll be using to get your Docker images running on Kubernetes.
- Services: Services allow you to open ports from one pod to the others, and specify the DNS name for one pod to be able to find and connect to others in the cluster.
- Ingress: Ingresses are how you tell your ingress controller (usually a web server, like Traefik) what to expose to the outside world, and on which path or hostname.
An ingress maps
https://some-hostname.your-cluster.your-company.com
to the pod that will actually answer that request. This tutorial also assumes you have an ingress configured already, although setting up Traefik to do that shouldn’t be terribly hard (use the Deployment method when following their tutorial).
All these things can either be created on the command line, via kubectl
, or, more sanely, via a YAML file that will contain the definitions and details for what you want deployed (which is then applied with kubectl apply -f <yaml file>
.
To summarize, you will put containers in pods, which will be created and deployed by deployments, whose networking will be handled by services, and add an ingress so the outside world can access your server.
Let’s go over the parts one by one and see what their YAML configurations look like.
The pod
Let’s look at the YAML configuration for a pod that will run the redis
Docker image in a container.
Remember that pods aren’t meant to be persistent, so you’re pretty much never going to use one in anything directly.
Instead, you’ll deploy pods indirectly, using a deployment, which we’ll look at next.
The configuration example below is for your edification only. Just look at it and go on reading, don’t stop to marvel at its beauty.
apiVersion: v1
kind: Pod
metadata:
name: my-pod-name
spec:
containers:
- name: my-redis
image: redis
ports:
- containerPort: 6379
As you can see, it’s pretty straightforward, you add a bunch of Kubernetes-specific stuff that everybody just copy-pastes, then you declare that this configuration is for a Pod, give it a name, specify the container(s) running in it and the ports they listen on, delete the entire file and you’re ready!
More information is available in the official Kubernetes documentation on pods.
The deployment
Here’s how you’ll actually run the pod above, ie using a deployment. Remember, you won’t need to include the pod config above at all, instead we’ll repeat it in this one.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment-name
spec:
replicas: 1
template:
metadata:
labels:
app: my-redis-pod
spec:
containers:
- name: my-redis
image: redis
ports:
- containerPort: 6379
You’ll notice that this is mostly the Pod configuration from above, but with some additional config like replicas
, etc.
These define the name of the deployment and how many replicas we want to deploy. Changing that number will deploy more of the pods specified in the template
section.
More information is available in the official Kubernetes documentation on deployments.
The service
Now that we have a deployment of a pod, we need to expose its ports to the rest of the cluster.
The containerPort
directive in the deployment exposes the Docker port, but doesn’t actually forward the port on the host, so multiple pods (not containers in the same pod) can use the same port without conflicts.
To actually expose the port above to other pods running on the cluster, we’ll need to create a Service for it. This creates the necessary rules to forward the port and gives us a DNS entry we can use to resolve the IP for that pod.
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: ClusterIP
ports:
- port: 6379
name: redis-port
targetPort: 6379
protocol: TCP
selector:
app: my-redis-pod
This will expose the redis port to other pods in the cluster, which will be able to connect to it by connecting to my-service:6379
.
To deploy more parts of your stack, just add another deployment and the associate service to the cluster. You can deploy your main application service in the exact same way as redis, above.
The ingress
Finally, we can expose our service to the internet using an Ingress. Here’s an example using Traefik, and while you may not actually want to expose Redis to the world like this, the same principle holds for your own application.
apiVersion: extensions/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- host: redis.yourdomain.com
http:
paths:
- backend:
serviceName: my-service
servicePort: 6379
This stanza basically tells Traefik that you want all traffic on a host called redis.yourdomain.com
to be forwarded to my-service
on port 6379
.
It’s just configuration for Traefik, as far as I can tell.
After applying it, the pod will be exposed via Traefik on redis.yourdomain.com
.
Epilogue
I hope this has been useful. The article is short because the Kubernetes basics are short, but we managed to cover how to get a service running with minimal hassle.
Please let me know if there were any inaccuracies or mistakes in the post (or if you have anything to add) by tweeting or tooting at me.
Now you know Kubernetes!