Skip to content

Running individual containers

In this tutorial, we learn how to run individual containers in Kubernetes.

Before you can continue with the exercises, make sure you have access to your training cluster.

About resources (objects)

Everything in Kubernetes is described as resources aka objects. You describe what you want to have in Kubernetes, and Kubernetes will make sure it and the related resources exists in the cluster. For example, you can tell Kubernetes that you want a container to be running in the cluster, and Kubernetes will ensure that the container is running according to the specification and restart the container, if it stops.

Resources are managed through the Kubernetes API, which is often controlled via the kubectl command-line tool we installed earlier, and the resources are often described as files for manageability.

These files are called manifests, and are most commonly written in YAML, although JSON is also a possibility.

Namespace

In Kubernetes, most resources are grouped into namespaces. Before we can run containers on Kubernetes, we need a namespace.

For all the Kubernetes experiments you create, it's a good idea to have a dedicated namespace. This way you're less likely to touch any existing resources, and you can easily delete the experiment when you're done with it. It's considered a best practice to never use the default namespace for anything.

Kubernetes allows you to use any naming and grouping convention you like for namespaces but there are a couple of limitations when it comes to naming them.

  1. The name can be at most 63 characters in length.
  2. The name can only contain lowercase alphanumeric characters, '-' or '.'.
  3. The name must start with an alphanumeric character.
  4. The name must end with an alphanumeric character.

The above rules apply to all resource names and not just namespaces with the exception of the first rule. Many resources also allow resource names to be up to 253 characters long.

Creating a Namespace

Note

If you're using a cluster provided by the instructors, a namespace is already provided for you.

You don't need to repeat the steps below, but you should still read them.

As mentioned above, resources can be managed using YAML.

First, let's create the first manifest named namespace.yaml with the following contents:

apiVersion: v1
kind: Namespace
metadata:
  name: tutorial

This file describes a Kubernetes namespace named tutorial. The apiVersion and kind are used for identifying the Kubernetes resource type, while metadata.name is used for the name. This pattern is repeated in all the Kubernetes resources.

We can create the namespace using kubectl apply command:

$ kubectl apply -f namespace.yaml
namespace/tutorial created

Or

$ kubectl create namespace tutorial

If you list the namespaces using kubectl get, you should see your namespace listed among others.

$ kubectl get ns
NAME              STATUS   AGE
default           Active   25m
kube-node-lease   Active   25m
kube-public       Active   25m
kube-system       Active   25m
tutorial          Active   20s

The names of the namespaces are unique cluster-wide, which means that you can't create another namespace named tutorial.

Accessing Namespace resources

In kubectl, we access resources in the namespace by providing the --namespace or -n flag.

If you are connected to the training cluster set up by Polar Squad's instructors, and thus didn't create your own namespace. You can check the name of your personal namespace like this:

$ kubectl config view --minify --output 'jsonpath={..namespace}'

Note

Replace the examples' tutorial namespace with this in all of the exercises that follow.

$ kubectl get all --namespace tutorial

A lot of the times, you may end up working on a single namespace for an extended period of time. It can get tiring to write the same namespace selector over and over again.

Fortunately, we can customize the default namespace for kubectl. Let's change our default namespace to the namespace we created earlier:

$ kubectl config set-context --current --namespace tutorial
Context "yourname-tutorial.k8s.local" modified.

You might see a different context name depending on your cluster name.

If you've installed kubens, you can also use it to switch the default namespace.

$ kubens tutorial

If you have sufficient privileges in the cluster, it's also possible to view resources across all namespaces using the flag --all-namespaces or -A.

$ kubectl get all --all-namespaces

A single Pod to run a container

In Kubernetes, a container is always run in a Pod. A Pod contains one or more containers. It's the smallest deployable unit of computing in Kubernetes. All of the containers in one Pod are always run on the same node.

Containers in the Pod share networking and storage.

  • Each container in a Pod shares the localhost address (i.e. 127.0.0.1).
  • Each Pod has a single IP address that targets all of the containers in the Pod.
  • All of the containers may mount and share a disk volume.

We can create our own Pods directly on Kubernetes similar to how we created the namespace. Let's create a YAML manifest named demo-pod.yaml with the following contents:

apiVersion: v1
kind: Pod
metadata:
  name: demo-pod
  labels:
    app: demo
    style: web-server
spec:
  containers:
  - image: nginxinc/nginx-unprivileged
    name: nginx
    ports:
    - containerPort: 8080

In this YAML document:

  • We use Pod kind instead of Namespace to tell Kubernetes that we're creating a Pod resource.
  • In the metadata section, we could use namespace to tell, which namespace to create the Pod in. If not set, it will be created in the one determined by the current selected context.
  • In the metadata section, we use labels to attach arbitrary key-value pairs to the Pod resource. We can use any labels we want. We can later use these labels to query Pods based on it.
  • We have a section called spec, which describes Pod details i.e. what containers we want to run and with what configuration. In this case, we run a single NGINX web server container with the default configuration.

Right now, we only include a small amount of information, but there are tons of more things we could customize. We can use kubectl explain to show what properties are available for us. Try it out!

$ kubectl explain pod
$ kubectl explain pod.metadata
$ kubectl explain pod.spec
$ kubectl explain pod.spec.containers

Let's create the Pod in Kubernetes using kubectl apply like we did before.

$ kubectl apply -f demo-pod.yaml
pod/demo-pod created

Note that the names of the resources created within the namespace are unique for that namespace and resource type. For example, I can't create another Pod named demo-pod in the namespace tutorial while the Pod exists but I can create one with that name in another namespace. I can also create another resource that's not a Pod and give it the name demo-pod. Moreover, the names of the Pods must follow the resource naming rules as mentioned earlier.

Warning

It's not recommended to run bare Pods for production workloads yourself. A single Pod lacks the proper lifecycle management we want to run highly-available, stateless software. For that purpose, we should use Deployments which we'll learn about in the next section.

Getting information on our Pod

The Pod should now be running. Let's find it by listing all the pods.

$ kubectl get pod
NAME       READY   STATUS    RESTARTS   AGE
demo-pod   1/1     Running   0          46s

We can also use kubectl describe to get additional information about the Pod.

$ kubectl describe pod demo-pod
Name:               demo-pod
Namespace:          tutorial
Priority:           0
PriorityClassName:  <none>
Node:               nodes-z1-1-yourname-training-k8s-local/10.1.32.15
Start Time:         Thu, 17 Oct 2019 08:38:45 +0300
Labels:             app=demo
...

We can also query this information in YAML format using the -o flag in get command:

$ kubectl get pod demo-pod -o yaml
apiVersion: v1
kind: Pod
metadata:
...

We can also query individual fields using the -o flag. Let's get the Pod IP address, so that we can communicate with the Pod.

$ kubectl get pod demo-pod -o 'jsonpath={ .status.podIP }'
100.101.38.203

Keep in mind that the IP for the Pod in your cluster might be different and that it's only available within the cluster.

You cannot connect directly to the Pod IP from your machine.

Labels

All resources in Kubernetes (Pods, Namespaces etc.) can include labels. Labels are arbitrary key-value pairs: We can use whatever labels we like, and as many as we like.

The labels are primarily used for querying resources on Kubernetes. For example, here's how you can query all the Pods that include the label app=demo:

$ kubectl get pod -l app=demo
NAME       READY   STATUS    RESTARTS   AGE
demo-pod   1/1     Running   0          55s

If we try to list pods for a non-existent label, we get no results:

$ kubectl get pod -l foo=bar
No resources found in default namespace.

Some resources use label queries as part of their functionality, which are covered in the next session of this training. For example:

  • Deployments manage the lifecycle of all the Pods that match their label query.
  • Services send traffic to the Pods that match their label query.

Additionally, resources can include annotations. They're similar to labels in that they're also arbitrary key-value pairs that can be attached to any resource in Kubernetes. However, unlike labels, annotation can't be used for querying resources. They're better suited for attaching arbitrary metadata.

One-off pods

Other containers running in the cluster can use the IP address to contact the Pod. Since the Pod is only available in Kubernetes, we can't access it from the laptop directly. Instead, we can create a one-off Pod that uses curl to query the IP address for us.

Note

Remember to change the IP for curl command for the one you've got on the previous command, if necessary.

$ kubectl run --restart=Never --rm -it --image=curlimages/curl call-demo -- http://100.101.38.203:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Awesome! We got a response. Let's break down the command above:

  • kubectl run: We use the run sub-command to create a Pod directly from the command-line parameters.
  • --restart=Never: Once the containers in the Pod stop, the Pod is not restarted.
  • --rm: Once the Pod is stopped, it gets removed automatically.
  • -it: We enable STDIN and TTY for the Pod, so that we can receive the command output directly to our terminal window.
  • --image=curlimages/curl: We use the official public curl Docker image
  • call-demo: Name of the Pod we create
  • --: Separator between the Pod spec arguments and the curl arguments
  • http://100.101.38.203:8080: The address we pass to curl. This essentially gets translated to curl http://100.101.38.203:8080 call.

Pod introspection

The NGINX container in the demo Pod writes access logs. We can view these using kubectl logs command:

$ kubectl logs demo-pod
172.17.0.9 - - [17/Oct/2019:06:42:05 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.66.0-DEV" "-" ...

We can also execute commands directly in the Pod using kubectl exec to do more introspection.

$ kubectl exec -t demo-pod -- cat /usr/share/nginx/html/index.html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Next

For now, let's delete the demo Pod.

$ kubectl delete pod demo-pod
pod "demo-pod" deleted

In the next section, we'll learn how to run apps properly in Kubernetes using Deployments.