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.
- The name can be at most 63 characters in length.
- The name can only contain lowercase alphanumeric characters, '-' or '.'.
- The name must start with an alphanumeric character.
- 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 ofNamespace
to tell Kubernetes that we're creating a Pod resource. - In the
metadata
section, we could usenamespace
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 uselabels
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 publiccurl
Docker imagecall-demo
: Name of the Pod we create--
: Separator between the Pod spec arguments and thecurl
argumentshttp://100.101.38.203:8080
: The address we pass tocurl
. This essentially gets translated tocurl 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.