Introduction

In this tutorial Kubernetes' core concepts Pods, Services, Routes and ReplicationControllers and their YAML representations are discussed.

In these examples, an Apache HTTP server is started from a container image included in OpenShift by default and the server is exposed at <myservice>.rahtiapp.fi.

Minimally we need the following objects defined in the cluster to have the server running:

The following is the minimal set of objects required in the cluster to have the server running and accessible.

  1. A Pod that runs the container
  2. A Service that exposes the pod internally and gives it a predictable name
  3. A Route that will expose the Service to the Internet by redirecting traffic to <myservice>.rahtiapp.fi to the Service object.

Note

In practice, one should not deploy applications the way described in this tutorial. It is only meant for learning the core concepts of Kuberenetes. If you already know about pods, services and routes, you might be interested in the "Advanced concepts" chapter.

Preparations

If you are logged in to the Rahti web console and have the OpenShift command line tool oc installed, you can skip this section and move on the next one ("Projects").

Install the oc command line tool and authenticate a command line session after logging in to Rahti at rahti.csc.fi as follows:

  1. Install oc command line tools by clicking the question mark and then "Command Line Tools" at the top right corner of OpenShift web console:

    install cli menu

  2. Click the "Latest release" link:

    install cli page

  3. Download and unpack the correct version for your platform and make sure that the binaries are found in a directory that is in the PATH environment variable.

  4. Copy the login command by clicking here:

    copy login

  5. Paste the result to terminal. It should be similar to this:

oc login https://rahti.csc.fi:8443 --token=<secret access token>

Note

The secret access token is only valid for a limited time. After it expires, you will need to repeat the steps to login. If you open a new terminal and have already logged in previously, the session will be valid in the new terminal as well.

Projects

The command oc projects shows the projects one has access to:

$ oc projects
You have access to the following projects and can switch between them with 'oc
project <projectname>':

    someone-elses-public-project
  * my-project-with-unique-name

Using project "my-project-with-unique-name" on server "https://rahti.csc.fi:8443".

Note

The listing may include projects that other users have created to host public Docker images. While it is possible to switch to these projects, you will only have read-only access to the Docker images hosted in them.

If there weren't a single suitable project, a new one could've be created with oc new-project:

oc new-project my-project-with-unique-name

The name of the project needs to be unique across the Rahti container cloud, and moreover, the name may only contain letters, digits and hyphen symbols and it is not case sensitive. In essence, the name needs to be usable as part of a DNS name.

If you have multiple CSC projects with access to Rahti, the description of the project must contain csc_project: #######, where ####### is the project that should be billed (see "Projects and quota"). The description can be included in the new-project command as follows

oc new-project my-project-with-unique-name --description='csc_project: #######'

Switching between other projects is done with oc project command:

oc project another-project

Pods and Command Line Interface

Pods are objects that run one or more containers. The containers in the pod share the same IP address and are guaranteed to run on one physical node so that they can find each other at localhost and can communicate through shared memory.

In our case, the pod will run a container image with a web server installed in it:

pod.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
  labels:
    app: serveapp
    pool: servepod
spec:
  containers:
  - name: serve-cont
    image: "docker-registry.default.svc:5000/openshift/httpd"

This pod will run one container image given in the field spec.containers.image.

The name of the pod is given in metadata.name. The pod can be referred now using oc:

oc get pods mypod

The field metadata.labels.pool is an arbitrary key-value pair so that the pod can be referred by, e.g., services.

The Kubernetes objects are represented in YAML format. Here is a minimal introduction to it.

Pods and other Kubernetes/OpenShift API objects are created with the oc command line utility as follows:

oc create -f pod.yaml

The pod should now appear in the "Overview" page in OpenShift's web console if the correct project is viewed.

Pods can be deleted using the oc delete command:

oc delete pod mypod

Consequently, the pod should disapper from the OpenShift web console, but let's keep this one running for now.


Resource requests and limits

Typically one allocates resources to containers using requests and limits, but in these examples we refrain from doing that for the sake of brevity. If no values are provided, default values will be used instead. The same pod as above with memory and CPU resources of 200 MB to 1 GB and 0.2 CPU to 1 CPU would read as:

pod.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
  labels:
    app: serveapp
    pool: servepod
spec:
  containers:
  - name: serve-cont
    image: "docker-registry.default.svc:5000/openshift/httpd"
    resources:
      requests:
        memory: "200M"
        cpu: "200m"
      limits:
        memory: "1G"
        cpu: "1"

You can read more about requests and limits in the Kubernetes documentation.


Service

The IP addresses of Pods are not consistent and may change if, for example, a pod killed and recreated. Thus, in order to reliably access a pod, its IP address must be tracked and stored. Service objects do just that and as a result they provide a consistent network identity to pods:

service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: serve
  labels:
    app: serveapp
spec:
  ports:
  - name: 8080-tcp
    port: 8080
    protocol: TCP
  selector:
    pool: servepod

This service will redirect TCP traffic internally in the project to the pods having labels listed in spec.selector. In this case, the traffic is redirected to those pods that have the label pool: servepod. If there are multiple pods matching spec.selector, then the traffic is split between pods. By default, the splitting is done in a round-robin manner.

Let's ensure that the Service actually works by launching a remote shell in the container running in the pod mypod and pinging the Service:

$ oc rsh mypod
sh-4.2$ ping serve
PING serve.my-project-with-unique-name.svc.cluster.local (172.30.39.82) 56(84) bytes of data.

Route

Route object is an OpenShift extension to Kubernetes that will route HTTP traffic from the Internet (or from whichever network the OpenShift cluster is connected to) to Services in the OpenShift cluster.

route.yaml:

apiVersion: v1
kind: Route
metadata:
  labels:
    app: serveapp
  name: myservice
  annotations:
    haproxy.router.openshift.io/ip_whitelist: 192.168.1.0/24 10.0.0.1
spec:
  host: <myservice>.rahtiapp.fi
  to:
    kind: Service
    name: serve
    weight: 100

This route will redirect traffic from internet to that service in the cluster whose metadata.name equals spec.to.name.

This particular route also allows traffic only from subnet 192.168.1.0/24 and the IP 10.0.0.1. Security-wise it is highly encouraged to utilize IP whitelisting for services that are not meant to be visible to the entire Internet.

Caution

If the whitelist entry is malformed, OpenShift will discard the whitelist and traffic is allowed from everywhere.

  • If the service spec.to.name has multiple ports defined then one should define spec.port.targetport
  • By default the hostname is metadata.name + - + project name + .rahtiapp.fi unless otherwise specified in spec.host.

So far we have set up a pod, a service and a route. If the physical server where to pod lives gets shut down one has to manually start the pod again with oc create -f pod.yaml. The ReplicationController and ReplicaSet objects are mechanism that will, roughly speaking, do that for the user.

ReplicationController

A ReplicationController ensures that there are spec.replicas number of pods whose labels match spec.selector.matchLabels (or spec.selector.matchExpression) running in the cluster. If there are too many, ReplicationController will shut down the extra ones and if there are too few, it will start up pods according to spec.template field. Actually, the template field is exactly the pod described in pod.yaml except the fields apiVersion and kind are missing.

ReplicationController.yaml:

apiVersion: v1
kind: ReplicationController
metadata:
  labels:
    app: serveapp
  name: blogtest-replicator
spec:
  replicas: 1
  selector:
    matchLabels:
      app: serveapp
      pool: servepod
  template:
    metadata:
      name: mypod
      labels:
        app: serveapp
        pool: servepod
    spec:
      containers:
      - name: serve-cont
        image: "docker-registry.default.svc:5000/openshift/httpd"

The ReplicationControllers are functionally near ReplicaSets, treated in Chapter "Kubernetes and OpenShift Concepts". In essence, a ReplicationController can be transformed in to a ReplicaSet by changing metadata.labels to metadata.matchLabels and setting kind: ReplicaSet. The motivation to understand the ReplicationController object is that DeploymentConfig objects generate ReplicationControllers.

Note

A central Kubernetes' concept coined reconciliation loop manifests in ReplicationControllers. Reconciliation loop is a mechanism that measures the actual state of the system, constructs current state based to the measurement of the system and performs such actions that the state of the system would equal to the desired state.

In such a terminology, ReplicationControllers are objects that describe desired state of the cluster. Another such an object is the service object encountered earlier. There is an another reconciliation loop that compares the endpoints of the service the actual pods that are ready and adjusts accordingly. As a result, the endpoints of the service always point to pods that are ready and only those pods whose labels contain all the fields in the selector of the service object. In fact, every incidence of spec in a YAML representations of Kubernetes objects, describes a specification for a reconciliation loop. The loops for pods just happen to be tied to the worker nodes of Kubernetes and are thus susceptible to deletion if/when the worker nodes are deprovisioned.

Cleaning up

Once we are satisfied with the application, let us not keep it running in the cluster but let's remove it with the oc delete command:

oc delete all --selector app=serveapp

This will delete all objects having label app: serveapp.

Conclusion

In this tutorial a static web page server was set up using YAML files representing the Kubernetes objects. The created objects can be further modified in the OpenShift web console where, e.g.,

  • Routes can be modified to be secure ones encrypted by TLS,
  • autoscalers, storage, resource limits and health checks can be added to ReplicationControllers, and
  • new routes can be added to Services.

Short introduction to YAML

YAML is used to describe key-value maps and arrays. YAML file are recognized from .yml or .yaml file suffix.

A YAML dataset can be

  • Value
value
  • Array
- value 1
- value 2
- value 3

or

[value 1, value 2, value 3]
  • Dictionary
key: value
another_key: another value

or

key:
  value
another_key:
  another value
  • YAML dataset
key:
  - value 1
  - another key:
      yet another key: value of yak
    another keys lost sibling:
      - more values
    this keys value is also an array:
    - but indentation is not necessary here

Values can be inputted across multiple lines with >:

key: >
  Here's a value that is written over multiple lines
  but is actually still considered a single line until now.

  Placing double newline here will result in newline in the actual data.

Verbatim style is supported with | character:

key: |
  Now each
  newline is
  treated as such

YAML is also a superset of JSON (JavaScript Object Notation). Thus,

{
  "key":
  [
    "value 1",
    {
      "another key": {"yet another key": "value of yak"},
      "another keys lost sibling": ["more values"],
      "this keys value is also an array": ["but indentation is not necessary here"]
    }
  ]
}

is also valid YAML.

For more information, see yaml.org or json.org.