Kubernetes Tutorial - Basic Concepts¶
Walkthrough of basic Kubernetes concepts
Introduction¶
This tutorial will guide you through basic Kubernetes features and concepts.
Module Learning Objectives¶
This module will be fully interactive. Participants are strongly encouraged to follow along on the command line. After completing this module, participants should be able to:
- Create a 2-node Kubernetes cluster
- Learn basic commands for interacting with Kubernetes
- Deploy and inspect a running pod
- Create a templated deployment
- Scale a deployment
- Increase response throughput with a LoadBalancer
Why is this important?¶
Similar to Docker Swarm, Kubernetes is a container management system. It runs and manages containerized applications on a single system, or up to 5000 nodes in a distributed cluster.
Kubernetes enables many sophisticated features through simple templating:
- Basic autoscaling
- Long-running and batch services
- Overcommit our cluster while also removeing low-priority jobs
- Run services with stateful data (databases etc.)
- Fine-grained permissions on resources
- Automating complex tasks (operators)
Utilizing Kubernetes will make your deployments both more reproducible, resilient, and performant.
Requirements¶
- Accounts
Creating your Kubernetes cluster¶
In this section, we will launch and configure a 2-node Kubernetes cluster on labs.play-with-k8s.com.
Launching your k8 instance¶
Head over to https://labs.play-with-k8s.com and log in with your Docker Hub account.

You should now have the option to “start” your remote Kubernetes (k8) instance.

You’ll notice a timer in the upper-left, counting down the time left on your instance. This is a great free resource, but all changes will be lost between shutdowns. The pane on the left will list any VMs you launch in your instance, and the right pane will expose a terminal of the selected VM.
Creating your orchestrator¶
Now, create your first VM, which will serve as the orchestrator of your Kubernetes cluster, by clicking ADD NEW INSTANCE
.

Once this is running, you’ll be presented with a terminal. Run the first two suggested commands to provision your orchestrator and allow other VMs to join your cluster:
1. Initialize the orchestrator (this) node¶
kubeadm init --apiserver-advertise-address $(hostname -i) --pod-network-cidr 10.5.0.0/16
You may see some warnings after this command, no errors. This command also generates a command for registering other nodes to this Kubernetes cluster, which you should save.

Each join command contains a unique key, so you should save yours and not rely on mine.
2. Initialize networking¶
kubectl apply -f https://raw.githubusercontent.com/cloudnativelabs/kube-router/master/daemonset/kubeadm-kuberouter.yaml
Creating your worker¶
Create a second VM to serve as the worker in your Kubernetes cluster by clicking ADD NEW INSTANCE
again.
Once that VM done initializing and you are presented with a terminal, run your unique kubadm join
command.
This should complete fairly quickly, and you can confirm it worked by switching back to your orchestrator (node1) and running
kubectl get nodes
if everything was set up correctly, you should see output similar to the following.
NAME STATUS ROLES AGE VERSION
node1 Ready master 19m v1.18.4
node2 Ready <none> 21s v1.18.4
At this point, your 2-node Kubernetes cluster with one orchestrator and one worker is set up and ready to accept tasks. If you wanted to add another worker to your cluster, just repeat the steps in this sub-section. Kubernetes can scale up to 5,000 workers, and each worker can be a VM or a physical machine that “joins” the cluster.
Kubernetes Terminology¶
Now that you’ve seen a basic cluster, lets cover some terminology before jumping into commands.
Pod: | Smallest deployable unit. Consists of 1 or more containers. Kind of like “localhost”. |
---|---|
Deployment: | Multiple pods. |
Service: | Expose a pod or deployment to network. |
Volume: | Attach storage. |
Namespace: | Permissions-based grouping of objects. |
Job: | Run a container to completion. |
And more not covered today:
ConfigMap: | Store strings or files for pods to use. |
---|---|
Secret: | Encrypted configmap. |
Ingress: | Expose HTTP+S routes to the network. Like a HTTP-specific Service. |
Basic Kubernetes commands¶
On your orchestrator node (node1), lets run through the following commands to learn about what they do.
Kubernetes commands¶
Listing¶
- Listing nodes
kubectl get nodes
- Listing everything
kubectl get all --all-namespaces
The main program for interacting with Kubernetes is kubectl
, which is a CLI tool that talks to the Kubernetes API.
Note
You can also use --kubeconfig
to pass a whole config to kubectl
Getting information¶
Information can be queried with the kubectl get
command.
Which can query resources like nodes
kubectl get nodes
and auto-complete to the best matching target
kubectl get no
kubectl get node
You can also increase the amount of information with the -o wide
argument
kubectl get nodes -o wide
or output in standardized formats like:
- JSON
-o json
- YAML
-o yaml
Note
Outputting information to JSON is useful since it allows you to query information with jq
kubectl get nodes -o json | jq ".items[] | {name:.metadata.name} + .status.capacity"
Viewing details¶
The kubectl get
command is great for listing resources, but details about each specific item can also be returned with kubectl describe
which is used with the format
kubectl describe [type]/[name]
kubectl describe [type] [name]
We can get information about node1 with
kubectl describe node node1
You can also get an explanation about different types of resources with
kubectl explain [type]
# What is a node?
kubectl explain node
# What is a service?
kubectl explain service
Exploring deployments¶
Services¶
A service is a pod or deployment exposed to a network. We can see that our cluster already has the API service running with
kubectl get services
A ClusterIP service is internal, meaning that it’s only available from inside the cluster.
Note
The API requires authentication, so it returns a 403 error if you try to connect.
# Assuming this is the IP of your Kubernetes service
curl -k https://10.96.0.1
Running containers¶
Containers are manipulated through pods, and a pod is a group of containers:
- running together (on the same node)
- sharing resources (RAM, CPU; but also network, volumes)
Running pods can be listed with
kubectl get pods
You’ll quickly find that there are no pods running in the default namespace.
Namespaces¶
Namespaces are a way to separate resources by named tag. Namespaces can be listed with
kubectl get namespaces
When we looked for pods, we queried the “default” namespace.
We list pods running in specific name spaces with the -n
argument.
kubectl -n kube-system get pods
Note
Information about these services can be found here.
The READY
column indicates the number of containers in each pod, and pods with a name ending with -node
are the main components (they have been specifically “pinned” to the orchestrator node)
Running our first pod¶
In this example we are going to:
- Create a pod that happens to run a single alpine linux container
- The container will run the
ping
- We’ll ping Google’s public DNS (8.8.8.8)
Note
Just to be clear, Kubernetes runs “pods”, not containers.
Starting the pod¶
Start the pod with kubectl run
which will look similar to docker run
.
kubectl run pingpong --image alpine ping 8.8.8.8
Kubernetes should only report that pod/pingpong
was created.
You can confirm this by listing all running pods.
kubectl get pods -o wide
It should have been started on our only worker node, node2
.
Viewing output¶
If you remember, the ping
command, by default, pings an address and prints the response time until it is terminated.
That means our pod is still printing to standard output somewhere on the cluster.
That output can be viewed with the kubectl logs [type]/[name]
command.
kubectl logs pod/pingpong
You can also select specific parts of the output with:
--tail N
- View the last N lines of outputkubectl logs pod/pingpong --tail 3
--since N[unit]
- View logs since the last N hours (h), minutes (m), seconds (s)kubectl logs pod/pingpong --since 10s
--follow
- Upate the output in real time (similar to watch)kubectl logs pod/pingpong --tail 1 --follow
First deployment¶
While running pods with kubectl run
is both convenient and familiar, you have to use templated deployments to take advantage of advanced Kubernetes features.
Pingpong deployment¶
We originally deployed the pingpong pod using
kubectl run pingpong --image alpine ping 8.8.8.8
We can create a deployment with the same behavior by creating the pingpong.yaml config
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | apiVersion: apps/v1
kind: Deployment
metadata:
name: pingpong
spec:
selector:
matchLabels:
app: ping-app
replicas: 1
template:
metadata:
labels:
app: ping-app
spec:
containers:
- name: pingpong
image: alpine
command: ["ping"]
args: ["8.8.8.8"]
|
and submitting it for deployment.
kubectl apply -f pingpong.yaml
You’ll notice that it created a deployment, replicaset, and a pod.
kubectl get all
Scaling the deployment¶
Now that our pingpong application is a proper deployment, we can scale it up with kubectl scale
kubectl scale --replicas=2 deployment.apps/pingpong
If you look at everything running, you’ll see that there are now 2 replicas and 2 pods.
kubectl get all
However, the longer running pod will have a longer output. Each pod has a randomized name, so you’ll need to fill in your own.
kubectl logs [pod1] | wc -l
kubectl logs [pod2] | wc -l
You can also view the last few lines of each pod’s log by selecting by app
kubectl logs -l app=ping-app --tail 1
Removing your deployment¶
Once done, deployments are removed in the same way as pods, but all pods and replicasets will be removed as well.
kubectl delete deployment.apps/pingpong
Exposing services¶
The kubectl expose
command creates a service for existing pods.
A service is a stable address for a pod or deployment.
A service is always required to make an external connection, and once created, kube-dns
will allow us to resolve it by name (i.e. after creating service hello, the name hello will resolve to something).
Basic service types¶
- ClusterIP (default type)
- A virtual IP address is allocated for the service (in an internal, private range), which is reachable only from within the cluster (nodes and pods). Code can connect to the service using the original port number.
- NodePort
- A port is allocated for the service (by default, in the 30000-32768 range) that port is made available on all our nodes and anybody can connect to it. Code must be configured to connect to that new port number
- LoadBalancer
- An external load balancer is allocated for the service, and sends traffic to a
NodePort
. - ExternalName
- The DNS entry managed by
kube-dns
will just be a CNAME to a provided record. No port, no IP address, no nothing else is allocated.
Exposing a port¶
The docker container gzynda/sleepy-server serves a webpage on a specified port after sleeping a specified number of seconds.
The configuration file sleepy.yaml creates a deployment and exposes it outside the cluster on port 80.
It then creates the sleepy-svc
service to expose port 80 of the deployment to the outside world.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | apiVersion: apps/v1
kind: Deployment
metadata:
name: sleepy
spec:
selector:
matchLabels:
app: sleepy-server
replicas: 1
template:
metadata:
labels:
app: sleepy-server
spec:
containers:
- name: sleepy-server
image: gzynda/sleepy-server:latest
command: ["sleepy-server.py"]
args: ["-p","80"]
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: sleepy-svc
labels:
app: sleepy
spec:
type: LoadBalancer
ports:
- protocol: TCP
port: 80
selector:
app: sleepy-server
|
Get the external port of your deployment by listing your available services and looking for the (second) port after 80.

Paste both the URL and PORT into your web browser in the URL:PORT format to view the running webpage from this deployment.
You’ll notice that the webpage displays the name of the host it is being served from, sleeps for half a second, and then finishes printing “Hello World!”. If the page takes around half a second to complete loading every time it is requested, how many pages can be served per second?
We can confirm this by stress-testing the web server with seige using 1 worker (-c1
), 0 delay (-d0
), and 10 tries (-r10
).
I recommend running the docker container directly for this use case.
$ docker run --rm -t yokogawa/siege -d0 -r10 -c1 [node2 IP]:[external PORT]
** SIEGE 3.0.5
** Preparing 1 concurrent users for battle.
The server is now under siege.. done.
Transactions: 10 hits
Availability: 100.00 %
Elapsed time: 5.02 secs
Data transferred: 0.00 MB
Response time: 0.50 secs
Transaction rate: 1.99 trans/sec
Throughput: 0.00 MB/sec
Concurrency: 1.00
Successful transactions: 10
Failed transactions: 0
Longest transaction: 0.51
Shortest transaction: 0.50
Which should confirm that the deployment can serve ~2 responses per second, since it sleeps for half a second when rendering the page.
Scaling the web server¶
Assuming we want more people to enjoy our sleepy server, you can serve more concurrent requests by scaling up the deployment.
kubectl scale --replicas=2 deployment.apps/sleepy
The LoadBalancer
will automatically balance the traffic between pods.
If you refresh the webpage, you should notice that the host name periodically changes.
You should also see an increased transaction rate if you re-run siege. Just be sure to increase the number of concurrent users to at least 3.
$ docker run --rm -t yokogawa/siege -d0 -r10 -c3 [node2 IP]:[external PORT]
** SIEGE 3.0.5
** Preparing 3 concurrent users for battle.
The server is now under siege.. done.
Transactions: 30 hits
Availability: 100.00 %
Elapsed time: 9.04 secs
Data transferred: 0.00 MB
Response time: 0.87 secs
Transaction rate: 3.32 trans/sec
Throughput: 0.00 MB/sec
Concurrency: 2.89
Successful transactions: 30
Failed transactions: 0
Longest transaction: 1.51
Shortest transaction: 0.50
Try scaling up the server to more replicas and benchmarking your transaction rate again.
Conclusions¶
At this point you have
Created a 2-node Kubernetes cluster
- 1 Orchestrator
- 1 Worker
Learned basic commands for interacting with Kubernetes
kubectl get
kubectl log
kubectl run
kubectl apply
kubectl delete
Deployed and inspected the log of a running pod
kubectl run [pod name] --image=[image] [args]
kubectl logs pod/[pod name]
Created a deployment from a template
kubectl apply -f [template]
Scaled up the number of replica pods in your deployment to increase response throughput
kubectl scale --replicas=2 [deployment]
Continuing your education¶
Kubernetes¶
Documentation: | https://kubernetes.io/docs/home/ |
---|---|
Tutorials: | https://kubernetes.io/docs/tutorials/ |
Cheat Sheet: | https://kubernetes.io/docs/reference/kubectl/cheatsheet/ |
TACC Institute: | https://ancantu.github.io/SCICLD2019/docs/kubernetes/kubernetes.html |