454 lines
18 KiB
Markdown
454 lines
18 KiB
Markdown
+++
|
||
title = "Intro to kubernetes"
|
||
weight = 0
|
||
date = "2025-07-22"
|
||
|
||
[extra]
|
||
image = "posts/kube-intro/preview.png"
|
||
image_width = 1200
|
||
image_height = 720
|
||
+++
|
||
|
||

|
||
|
||
## Problem in learning kubernetes
|
||
|
||
Lots of people are considering learning Kubernetes but are struggling to take the first
|
||
steps because the official docs are complete dogshit for beginners.
|
||
Don't get me wrong, the docs are great, but they serve a different purpose and are mostly used by experienced
|
||
developers as a reference for things they are about to build or for explanations of
|
||
some concepts that Kubernetes leverages.
|
||
|
||
Also official docs are mostly focus on how to deploy a big-ass production-ready system.
|
||
Which is not something that you will target as a beginner. So instead I'm gonna show you how to deploy a single app.
|
||
|
||
Another problem is that people who don't use Kubernetes or are not familiar with all the problems it solves say that
|
||
for medium and small projects, it is overkill.
|
||
Which is actually complete bullshit as well.
|
||
Because of the fact that Kubernetes automates a lot of things, it makes it easier to use than to not use it.
|
||
And it is not that hard to start.
|
||
|
||
## What is Kubernetes? And how is better than Docker?
|
||
|
||
People that are aquite good with docker might ask, like `why should I care? Because I can run docker in cluster mode with docker-swarm!`
|
||
Weeeeell, here's my response to you. The main problem with docker swarm is that it
|
||
might run applications on multiple nodes, but nothing more than that.
|
||
What about ingress management, automated certificate issuing, DNS updates, distributed volume provisioning? Docker swarm doesn't have it,
|
||
and won't have unless they change their approach for declaring workloads. Kubernetes on the other hand is easily extendable, is adopted by big-tech companies and
|
||
supported by a lot of cloud providers, which makes it a de-facto standard for container orchestration. Sad to admit,
|
||
but docker swarm is silently dying from the day it was born.
|
||
|
||
## What's inside of Kubernetes?
|
||
|
||
Since I wanted to give high-level overview of Kubernetes, I won't go into details about each component,
|
||
and networking, but I will give you a brief overview of the main components that make up Kubernetes.
|
||
|
||
* Container
|
||
* Pod
|
||
* Deployment
|
||
* Service
|
||
* Ingress
|
||
* Namespace
|
||
* Secret
|
||
* ConfigMap
|
||
|
||
Now let's take a look at each of these components in more detail.
|
||
|
||
#### Container
|
||
|
||
Containers are not really something unique to Kubernetes. You might be familiar with containers from other systems
|
||
like Docker. In context of kubernetes, containers are exactly same containers as they are in docker or containerd. Nothing brand new in here.
|
||
|
||
#### Pod
|
||
|
||

|
||
Pods are the smallest deployable units in Kubernetes.
|
||
It's a logically connected group of containers.
|
||
|
||
You can think of them as a virtual machines running some applications. And there are several reasons for that.
|
||
First of all, pods share same network and you can go from one container to another using localhost.
|
||
Also, you can attach volumes to pod and then share it to multiple containers.
|
||
Which seems a lot like a virtual machine, right?
|
||
|
||
#### Deployment
|
||
|
||
Pods are cool, but there are some limitations to them. First of all, the pod objects are immutable, which means that you can't change them after they are created.
|
||
|
||
It might be a problem if you want to update your application, because you will have to delete the pod and create
|
||
a new one. Also, scaling pods by yourself sounds weird. Of couese you can manually
|
||
create [ReplicaSet](https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/),
|
||
but on average it's not something you would want to do.
|
||
|
||
So that's why we have [Deployments](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/).
|
||
It allows you to describe a template for a pod and then Kubernetes will take care of creating, updating, and scaling pods for you. It will implicitly create a ReplicaSet for you, and will be maintaining it for you as well.
|
||
|
||
Deployment is a higher-level abstraction that allows you to manage pods more easily, scale them up and down, and update them without downtime.
|
||
|
||
There are some policies that you can set for deployments, like how many pods to keep running at the same
|
||
time, update strategies, and so on.
|
||
|
||
Generally speaking, use deployments to manage your pods, and don't create pods directly unless you have
|
||
a good reason to do so.
|
||
|
||
#### Service
|
||
|
||

|
||
|
||
[Service](https://kubernetes.io/docs/concepts/services-networking/service/) is a resource that allows you to set a single IP address and DNS name for a set of pods.
|
||
|
||
By default you can reach to service by it's name from the same namespace. But if you're in a different namespace, you can reach it by using domain name such as this:
|
||
|
||
`<service-name>.<namespace-name>.svc.cluster.local`.
|
||
|
||
You can think of services as a load balancer that distributes traffic to a set of pods.
|
||
|
||
#### Ingress
|
||
|
||

|
||
|
||
Ingress is a resource that allows you to expose your services to the outside world with a single IP address and DNS name.
|
||
|
||
You can think of it as a reverse proxy that routes traffic to different services based on the request path or host.
|
||
In order to use ingress, you need to have an ingress controller running in your cluster.
|
||
K3S by default has [Traefik](https://traefik.io/) ingress controller installed, which is a
|
||
great choice for beginners. But for more control and features, I personally prefer to use [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/).
|
||
|
||
Ingress works this way: you create an ingress resource that defines the rules for
|
||
routing traffic to different services, based on the request path or host.
|
||
|
||
Then the ingress controller will take care of routing the traffic to the appropriate service.
|
||
|
||
#### Namespace
|
||
|
||
I mentioned namespaces in the beginning, but I didn't explain what they are. Actually it's a very simple concept.
|
||
It's a way to group resources in Kubernetes and manage access. Most of the time you can think of
|
||
namespaces as just a way to organise resources in your cluster.
|
||
|
||
I prefer using different namespaces for different apps. For example my mailserver is deployed in namespace `mailserver`. But my blog is deployed in namespace `s3rius` for personal projects.
|
||
|
||
Telegram bots, CI/CD pipelines, and other applications that I deploy in my cluster are also deployed in their own namespaces. Which makes it easier to manage resources and access control.
|
||
|
||
About access control, you can use [Role-Based Access Control (RBAC)](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) to manage access to resources in a namespace. It will only be useful if you have multiple users or teams working in the same cluster.
|
||
|
||
You can create roles and role bindings to grant permissions to users or groups in a namespace. But I won't cover RBAC in this blog post. Just because it's too complex and we're only discussing basics.
|
||
|
||
#### ConfigMap and Secret
|
||
|
||
These two resources are used to store configuration data and sensitive information, respectively.
|
||
For example, you can use ConfigMap to store environment variables, configuration files,
|
||
or any other non-sensitive data that your application needs to run. And then mount them as
|
||
files to your pod, or populate environment variables from it, etc.
|
||
|
||
In secrets you can store sensitive information like passwords, API keys, or any other data that
|
||
you don't want to expose in plain text.
|
||
But please keep in mind that by default k8s encodes all secrets using base64, which is completely insecure.
|
||
To make secrets actually secret, you better use [Vault](https://developer.hashicorp.com/vault/docs/platform/k8s)
|
||
or [External secrets operator](https://external-secrets.io/latest/) or something similar. But for now let's just use default base64 encoded secrets.
|
||
|
||
## How to deploy kubernetes
|
||
|
||
For local development you have several options:
|
||
* [K3D](https://k3d.io/) - k3s in Docker.
|
||
* [Minikube](https://minikube.sigs.k8s.io/docs/) - k8s in docker.
|
||
* [Kind](https://kind.sigs.k8s.io/) - Kubernetes in Docker, but with a focus on testing Kubernetes itself.
|
||
|
||
For production:
|
||
* [Kubeadm](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/) - raw and barebones way to deploy Kubernetes.
|
||
* [Kubespray](https://kubespray.io/) - Ansible-based tool to deploy Kubernetes.
|
||
* [K3S](https://k3s.io/) - Lightweight Kubernetes distribution that is easy to deploy and maintain.
|
||
* [Rancher](https://rancher.com/) - A complete Kubernetes management platform
|
||
* [DeckHouse](https://deckhouse.io/products/kubernetes-platform/gs/) - A complete Kubernetes management platform that is easy to deploy and maintain.
|
||
|
||
My personal recommendation is to use K3D for local development and K3S for production. For deploying K3S
|
||
you can use [k3sup](https://github.com/alexellis/k3sup), which is a simple script that allows you to deploy K3S on any Linux server with a single command.
|
||
|
||
#### Learning environment
|
||
|
||
Let's use Docker-packed Kubernetes to start off. We're going to use [k3d](https://k3d.io) to spin up a cluster.
|
||
It's an amazing tool that allows you to create a Kubernetes cluster and everything you need with a single command.
|
||
Additionally, it spins up [k3s](https://k3s.io/) instead of Google's Kubernetes,
|
||
which is a lightweight version of Kubernetes that is perfect for beginners.
|
||
|
||
It has all the features you need to get started and is much easier to set up than other distributions.
|
||
I'm using this k3d configuration to create a cluster:
|
||
|
||
```yaml
|
||
# yaml-language-server $schema=https://raw.githubusercontent.com/k3d-io/k3d/main/pkg/config/v1alpha5/schema.json
|
||
apiVersion: k3d.io/v1alpha5
|
||
kind: Simple
|
||
metadata:
|
||
name: default
|
||
servers: 1
|
||
volumes:
|
||
- volume: /tmp/k3d-udev:/run/udev # For openebs
|
||
ports:
|
||
- port: 80:80 # HTTP ports mapping for cluster
|
||
nodeFilters:
|
||
- loadbalancer
|
||
- port: 443:443 # HTTPS ports mapping for cluster.
|
||
nodeFilters:
|
||
- loadbalancer
|
||
registries:
|
||
create:
|
||
name: registry.localhost # Registry for containers
|
||
host: "0.0.0.0" # Host for registry
|
||
hostPort: "5000" # Local port for registry
|
||
```
|
||
|
||
With this configuration, you can create a cluster with a single command:
|
||
```bash
|
||
k3d cluster create --config "k3d.yaml"
|
||
```
|
||
|
||
#### Connecting to the cluster
|
||
|
||
After you install kubectl you should be able to locate file `~/.kube/config`. This file contains all required infomration to connect to clusters. Tools like minikube, k3d or kind will automatically update this file when you create a cluster.
|
||
|
||
Let's take a look at the contents of this file:
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Config
|
||
preferences: {}
|
||
# Here's list of clusters that you can connect to.
|
||
# In this case we have only one cluster, which is k3d-default.
|
||
clusters:
|
||
- cluster:
|
||
certificate-authority-data: DATA+OMITTED
|
||
server: https://0.0.0.0:44755
|
||
name: k3d-default
|
||
# Users is a list of users that you can use to connect to clusters.
|
||
# Most of the time you will have each user for each cluster.
|
||
# But in some cases you might have multiple users for the same cluster.
|
||
users:
|
||
- name: admin@k3d-default
|
||
user:
|
||
client-certificate-data: DATA+OMITTED
|
||
client-key-data: DATA+OMITTED
|
||
# Context is a combination of cluster and user that you can use to connect to a cluster.
|
||
# You can create any combination of cluster and user,
|
||
# but most of the time you will have only one context for each cluster.
|
||
contexts:
|
||
- context:
|
||
cluster: k3d-default
|
||
user: admin@k3d-default
|
||
name: k3d-default
|
||
# Currently selected context. It will be used as a default one
|
||
# for all kubectl commands.
|
||
current-context: k3d-default
|
||
```
|
||
|
||
I will update my context to use k3d-default as my default context with this command:
|
||
|
||
```bash
|
||
kubectl config use-context k3d-default
|
||
```
|
||
|
||
Also, we can check if we are connected to the cluster by running:
|
||
```bash
|
||
kubectl cluster-info
|
||
```
|
||
|
||
Once we are connected to the cluster, we can start deploying applications and managing resources. But before that
|
||
I want to mention [Lens](https://k8slens.dev/) and [K9S](https://k9scli.io/).
|
||
These two things will help you a lot to get into kubernetes. Becuase kubectl is great, no shit,
|
||
I'll be using it for showing everything what is going on.
|
||
|
||
But I highly recommend you to install `Lens` or `K9S` to have better overview of your cluster and resources.
|
||
So you will be able to see what is going on in your cluster, what pods are running, what services are available,
|
||
and so on. I personally use `k9s` and I think it's much better, because it has everything you need and it's fast as hell.
|
||
Lens is a bit more heavy, but it's still a great tool for getting started with Kubernetes.
|
||
|
||
|
||
## Deploying your first application
|
||
|
||
I'm going to use python to write a small server to deploy in the cluster. Here's the application:
|
||
|
||
```python,name=server.py
|
||
import os
|
||
from aiohttp import web
|
||
|
||
routes = web.RouteTableDef()
|
||
|
||
@routes.get("/")
|
||
async def index(req: web.Request) -> web.Response:
|
||
# We increment the request count and return the state
|
||
req.app["state"]["requests"] += 1
|
||
return web.json_response(req.app["state"])
|
||
|
||
|
||
app = web.Application()
|
||
app["state"] = {
|
||
"requests": 0,
|
||
"hostname": os.environ.get("HOSTNAME", "unknown"),
|
||
}
|
||
app.router.add_routes(routes)
|
||
|
||
if __name__ == "__main__":
|
||
web.run_app(app, port=8000, host="0.0.0.0")
|
||
```
|
||
|
||
As you can see, this is a simple aiohttp application that returns the number of requests and the hostname of the pod.
|
||
This is a good example of an application that can be deployed in Kubernetes, because it is stateless and can be scaled easily.
|
||
|
||
Let's create a Dockerfile for this application:
|
||
```dockerfile,name=Dockerfile
|
||
FROM python:3.11-alpine3.19
|
||
|
||
RUN pip install "aiohttp>=3.12,<4.0"
|
||
WORKDIR /app
|
||
COPY server.py .
|
||
CMD ["python", "server.py"]
|
||
```
|
||
|
||
Here's a simple Dockerfile that installs aiohttp and runs the application. Let's build an image and upload it to our running cluster.
|
||
|
||
```bash
|
||
docker build -t "registry.localhost:5000/small-app:latest" .
|
||
docker push "registry.localhost:5000/small-app:latest"
|
||
```
|
||
|
||
Now let's deploy this application so it will become available from our host machine.
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
# Here's the name of the deployment, which is small-app.
|
||
metadata:
|
||
creationTimestamp: null
|
||
name: small-app
|
||
spec:
|
||
replicas: 1
|
||
# Here we define selector that will help this deployment
|
||
# to find pods that it manages. Usually it is the
|
||
# same as labels in pod template.
|
||
selector:
|
||
matchLabels:
|
||
app: small-app
|
||
strategy: {}
|
||
# Here we define the pod template that will be used to create
|
||
# pods for this deployment.
|
||
template:
|
||
metadata:
|
||
# Each pod create by this deployment will have these labels.
|
||
labels:
|
||
app: small-app
|
||
spec:
|
||
containers:
|
||
- image: registry.localhost:5000/small-app:latest
|
||
name: small-app
|
||
ports:
|
||
- containerPort: 8000
|
||
protocol: TCP
|
||
resources: {}
|
||
---
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: small-app
|
||
namespace: small-app
|
||
spec:
|
||
# This type of service will make it accessible only
|
||
# from inside the cluster.
|
||
# If you want to expose it to the outside world,
|
||
# you can use LoadBalancer or NodePort.
|
||
# But we don't need it for now.
|
||
type: ClusterIP
|
||
# Here we define ports available for this service.
|
||
# Port is the port that will be exposed by the service,
|
||
# targetPort is the port that the service
|
||
# will forward traffic to in target pods.
|
||
ports:
|
||
- name: http
|
||
port: 80
|
||
protocol: TCP
|
||
targetPort: 8000
|
||
# Here we define where to route traffic for this service.
|
||
# In this case we route traffic to pods that have
|
||
# label app=small-app.
|
||
selector:
|
||
app: small-app
|
||
---
|
||
# Write ingress
|
||
apiVersion: networking.k8s.io/v1
|
||
kind: Ingress
|
||
metadata:
|
||
name: small-app
|
||
namespace: small-app
|
||
spec:
|
||
rules:
|
||
# Here's the host configuration for the Ingress
|
||
# It will route traffic for this host to the small-app service
|
||
- host: small-app.localhost
|
||
http:
|
||
paths:
|
||
# Here we define the path and the backend service
|
||
# The path is set to '/' which means all
|
||
# traffic to this host will be routed
|
||
# to the defined backend service
|
||
- path: /
|
||
pathType: Prefix
|
||
backend:
|
||
service:
|
||
name: small-app
|
||
port:
|
||
number: 80
|
||
resources: {}
|
||
```
|
||
|
||
Now let's save this file as `small-app.yaml` and apply it to our cluster:
|
||
|
||
```bash
|
||
kubectl apply -f small-app.yaml
|
||
```
|
||
|
||
Once the command is executed, you should see that the deployment and service are created successfully.
|
||
|
||
```bash
|
||
❯ kubectl get pods -n small-app
|
||
NAME READY STATUS RESTARTS AGE
|
||
small-app-54f455696b-rp8qw 1/1 Running 0 13m
|
||
```
|
||
|
||
If you were using my k3d configuration, you should be able to access the application at [small-app.localhost](http://small-app.localhost/).
|
||
|
||
Let's check if it works. I'm gonna use curl and jq for that:
|
||
|
||
```bash
|
||
❯ curl -s http://small-app.localhost | jq
|
||
{
|
||
"requests": 1,
|
||
"hostname": "small-app-54f455696b-rp8qw"
|
||
}
|
||
```
|
||
|
||
Now you can scale up the application by changing the number of replicas in the deployment.
|
||
You can do it by editing the deployment and updating the `replicas` field to 3, for example and then running
|
||
|
||
```bash
|
||
kubectl apply -f "small-app.yaml"
|
||
```
|
||
|
||
Or alternatively you can use `kubectl scale` command:
|
||
```bash
|
||
kubectl scale deployment -n small-app small-app --replicas 3
|
||
```
|
||
|
||
Let's verify that the application is scaled up:
|
||
```bash
|
||
❯ kubectl get pods -n small-app
|
||
NAME READY STATUS RESTARTS AGE
|
||
small-app-54f455696b-6tkxx 1/1 Running 0 21s
|
||
small-app-54f455696b-9sd7r 1/1 Running 0 21s
|
||
small-app-54f455696b-rp8qw 1/1 Running 0 25m
|
||
```
|
||
|
||
Now let's fire some requests to the application and see how it works.
|
||
|
||

|
||
|
||
Works as expected. The application is scaled up and we can see that the requests are distributed between the pods.
|
||
|
||
I guess that is more than enough to get you started with Kubernetes. I might create some more posts on how to tune up your cluster, how to use volumes, how to use secrets and configmaps, and so on.
|
||
|
||
But now I'm tired and just want to publish it already. So please go easy on me. Stay tuned.
|