Spectrums

Node JS- Getting Started

Node.js is a highly popular, scalable, open-source server. It uses Google Chrome V8 JavaScript engine at its core. It was developed by Ryan Dahl based on the concept of non-blocking IO in 2009.

In a layman’s view, the NodeJS is the Chrome’s V8 engine converted into a backend server. In this process, we have removed the DOM related libraries for the browser display. As part of the back-end needs, we have added many crucial libraries for the IO operations with filesystem, database and the network systems etc. These libraries enables us to keep these major time consuming IO operations asynchronous and non-blocking.

 

 

As a result of these IO libraries, we are able to keep the major overhead of a back end server away from the single processing thread. And, in turn, it allows the processing thread to be free and run the programs as fast as we observer in the browsers.

The removal of the delays due to IO operations makes NodeJS highly scalable for the data intensive applications compared to its multi-threaded server counterpart. The article on ‘Single Vs Multi-Thread Model‘ explains this difference using a simulation.

Apart from being scalable, the other key advantage is its ease of learning. With the same language in use for both the front-end and the back-end, it becomes easy to shuffle and manage the teams. A good end to end knowledge, greatly improves the interest and the contribution from the team members as well. No wonder why, we have such a large community base and a huge set of libraries in the NPM registry!

Before we move on to the next topic, here is the summary of the key benefits including what we have discussed so far.

Key Benefits of NodeJS

  1. Highly Scalable: The non-blocking IO implementation makes the NodeJS server highly scalable for the data intensive applications. It enables us to handle significantly high amount of traffic with minimal front-end servers. 

    This is the one of the core reason behind its fast paced growth and wide level of acceptance. Most of the leading enterprises including the likes of LinkedIn, Uber, Twitter, Microsoft, Netflix use NodeJS for many of their high volume applications.
  2. Easy Learning Curve : The usage of the same language for both client and server side code makes the learning easy in NodeJS.
  3. Lesser Compatibility Issue : The server-side compilation allows us to use the latest ECMA scripts syntax, worrying much less about clients browser versions. The ECMA transpilers can take care of many of the compatibility issues. The frameworks like Angular, React and Vue are some the example providing easy to use frontend frameworks using ECMA scripts. 
  4. Huge Open-source Libraries : It’s a highly popular framework enjoying a huge community base. And, it has million plus open-source libraries available in it’s npm registry.  

NodeJS IDEs

It is well supported by a good set of tools and IDEs. Some of the prominent IDEs for NodeJS development are :

Desktop IDECloud Based
Atom by GitHubEclipse Cloud IDEs by Eclipse
Visual Studio Code by MicroSoftAWS Cloud9 by AWS
IntelliJ IDEA or WebStorm by JetBrains
Komodo IDE by ActiveState
Sublime Text by Sublime HQ
Brackets by Adobe

Kubernetes – POD

A Pod is a basic unit of deployment that encloses one or more containers in it. The Pod API object is used to create a single instance of a Pod.

An application may need multiple things for its operation as shown in the diagram. A container that run the app may not support all of them; a Docker container does not provide an in-built support for external storage for instance.

The Pod that encloses the containers, builds the features needed for the underlying application instances. It fulfills the needs of the app and the needs of framework to manage the app instance as shown.

As shown in the diagram, each Pod gets a separate virtual IP as its address and acts like a separate virtual machine. With the virtual networking model K8s keeps the applications secured and independent from the from outside world. It also helps K8s automate internal traffic routing and service discovery.

 

Lifecycle , Restart Policy , Hooks & Probes

As the system plans to create an instance of a Pod, the system asks the scheduler to find a suitable Node. As we find the node, the kubelet on the Node is handed over the Pod spec to create and manage it. The kubelet ensures the Pod and the containers within it run as per the specifications. And, keeps updating their status to the Master node.

This diagram briefly consolidates Pod and Container life-cycle events to show the order of their occurrence in a normal course of action. Apart from the order, it shows their relationship and significance.

 

Creating , Exploring & Debugging a POD

A simple POD

test-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: demo-app
  label:
    app: demo-app
spec:
  containers:
  - name: hello-echo
    image: k8s.gcr.io/echoserver:1.10
    ports:
    - containerPort: 8080

kind – Says its a POD API object.
name – Gives a unique name to these objects
labels – Allows us to give a group name to multiple instances of the Pod
image – specifies the app build IMAGE file to be used
port – Provides the port at which the app will run

After creating this file, we can use the following command to deploy it:

kubectl apply -f  test-pod.yaml

Since its simple POD , we can also conveniently deploy it using a command line as follows :

kubectl run hello-echo \
--image=k8s.gcr.io/echoserver:1.10 \
--port=8080 \
--generator=run-pod/v1 

 

Deploy the Pod

Lets deploy it using the command line on a minikube environment and explore:

$ kubectl run hello-echo --image=k8s.gcr.io/echoserver:1.10 --port=8080 --generator=run-pod/v1 
pod/hello-echo created

$ kubectl get pod -o wide
NAME         READY   STATUS    RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
hello-echo   1/1     Running   0          48s   160.74.10.26   minikube   <none>           <none>

As we see, the Pod is running on the minikube Node with its virtual IP 160.74.10.26 .

Inside the Pod,we can access the application using localhost or the Pod’s IP. It is to be noted that, there can be multiple Pods on a Node, the localhost inside a Pod refers to the Pod IP , not to the Node’s ip.

 

exec : Login to container workspace & access the application

As we can not access this IP from outside,let us login to the container and try accessing the application.

#Command to login to the container workspace
$ kubectl exec -it hello-echo -- /bin/bash
root@hello-echo:/#

#Access the application inside the cluster using localhost or  pod ip
root@hello-echo:/# curl http://localhost:8080 | head -4
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   404    0   404    0     0  28230      0 --:--:-- --:--:-- --:--:-- 28857

Hostname: hello-echo

 

Logs : Explore the container logs

Here we are accessing the logs using only the Pod name. If there multiple containers, we can specify the container name after the pod name.

$ kubectl logs hello-echo
Generating self-signed cert
Generating a 2048 bit RSA private key
writing new private key to '/certs/privateKey.key'
-----
Starting nginx
127.0.0.1 - - [07/Jul/2020:07:13:51 +0000] "GET / HTTP/1.1" 200 416 "-" "curl/7.47.0"
127.0.0.1 - - [07/Jul/2020:07:15:17 +0000] "GET / HTTP/1.1" 200 416 "-" "curl/7.47.0"

 

Get : Explore a detailed runtime Pod state and status history

The ‘get’ command using ‘-o yaml’ provides the important information about the selected object as stored in the system.

We can explore the detailed runtime Pod status as below:

$ kubectl get pod hello-echo -o yaml 
-----
  kind: Pod
  metadata:
    creationTimestamp: "2020-07-07T07:10:29Z"
    labels:
      run: hello-echo
    name: hello-echo
    namespace: default
   ---------
  spec:
    containers:
    - image: k8s.gcr.io/echoserver:1.10
      imagePullPolicy: IfNotPresent
      name: hello-echo      
      -------

Status part in the same YAML output shows the status history of the Pod. The latest container status is also shown at the end.

status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2020-07-30T15:13:05Z"
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2020-07-30T15:13:05Z"
    status: "True"
    type: ContainersReady
  - lastProbeTime: null
    lastTransitionTime: "2020-07-30T15:12:56Z"
    status: "True"
    type: Initialized
  - lastProbeTime: null
    lastTransitionTime: "2020-07-30T15:12:56Z"
    status: "True"
    type: PodScheduled
  containerStatuses:
  - image: k8s.gcr.io/echoserver:1.10
    name: hello-echo
    ready: true
    started: true
    state:
      running:
        startedAt: "2020-07-30T15:13:04Z"

 

Describe : Provides a more formatted Pod status & associate events

The describe provides a more concise and formatted Pod status information and the list associated event.

#Lines has been removed from the output for clarity
$ kubectl describe pod hello-echo | less
Name:         hello-echo
Namespace:    default
Node:         minikube/160.74.10.26
Labels:       run=hello-echo
Status:       Running
IP:           160.74.10.26
Containers:
  hello-echo:
    Container ID:   docker://7507054f5a3f34c60d080323e5f1f0c2f9b8c588e85a423e51f31b383bb011f3
    Image:          k8s.gcr.io/echoserver:1.10
    Port:           8080/TCP
    State:          Running

QoS Class:       BestEffort

Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  45m   default-scheduler  Successfully assigned default/hello-echo to minikube
  Normal  Pulled     45m   kubelet, minikube  Container image "k8s.gcr.io/echoserver:1.10" already present on machine
  Normal  Created    45m   kubelet, minikube  Created container hello-echo
  Normal  Started    45m   kubelet, minikube  Started container hello-echo

 

Delete the POD
$ kubectl delete pod hello-echo
pod "hello-echo" deleted

 

Conclusion

A Pod API object creates a single instance of a Pod. Creation and a deletion of each instance needs to be done manually.

The API objects like ReplicaSet, Deployment, Job, StatefulSets etc add features to automate the scaling, version upgrades of the Pod through configuration in the API objects. These API object use the Pod specification as a template to create the Pod instances.

The above paragraph just says, never use a Pod for deploying your application. Choose an appropriate Deployment API object to simplify your deployment.

Pod & Container Life-Cycle

This diagram consolidates Pod and Container life-cycle events to show the order of their occurrence in a normal course of action. Apart from the order, it shows how these life-cycle events are dependent on each other to get a better understanding of their significance.

 

 

Reference from Official Documents

Here are the related links from the Kubernetes official site for more information.

Kubernetes- Features

Kubernetes automates our deployment activities. It lets us define our desired system using its templates called the Kube API objects. As discussed in our previous article, it simplifies the infrastructure provisioning, network configuration and manages the containers at scale.

The diagram provides a summary of the features it provides. The diagram has our application at the center and shows various features the framework provides for its easy management.

 

 

Summary of the Features

Horizontal Scaling : Kubernets allows us to scale our applications through simple configurations. Moreover, the framework takes care of the distribution of the loads. And, it also allows us to add new servers to meet the extra resource requirements.

Auto Scaling : K8s enables us to auto-scale based on our usage. We may configure our application to add new instances as the current usage exceeds 90% of CPU or 85% of memory for instance. This is quite useful in addressing peak loads and unexpected in spikes in application usage.

Auto Recovery : Kubernetes keeps monitoring the state of the system at frequent intervals. In case of any deviation, it quickly starts its restoration process to bring the actual state back to the desired state. As it restores, its does this with its in-built best practices. For instance, it can stop the traffics towards the failed server or application instances and re-start it as the instance are restored.

Auto Placement : The best-practices in K8s ensures the optimal utilization of the resources. As part of this, every time we plan to create new a instance of our application, it uses a scheduler to evaluate the most suitable server.

Auto Deployment: It supports rolling update deployment which can automate our deployment and version upgrades. It ensures the availability of the application, even during deployment, as it migrates the instances one by one. Moreover, if the new instances fail to come up, it can stop deployment process. But, we need to ensure new versions are backward compatible to fully utilize a rolling deployment. Hence, to address compatibility concerns it supports other deployments as well.

Auto Discovery & Service Exposure: A traditional setup involves a lot of effort to address the discovery and service exposure. However, K8s automatically handles complexities using its meta-data and configuration in its API objects.

Flexible Storage Options : It supports plugging in a wide range of storage options including local and cloud storage.

Portable Applications : It provides an elegant solutions to externalize the configuration and confidential information. Thus, it makes it easy to build portable applications.

Flexible Server Choice : Kubernetes is portable. We can install it on a bare-metal, virtual machines or cloud servers. We can use it as a private cloud, move it to a public cloud or use it as a hybrid.

Its being a container management system, it also enables us to easily plug-in other cloud-native solutions.

Supporting Tools & Solutions

Kubernetes is one of the best tools for automating our deployment activities at scale. Its main focus is to manage the deployment infrastructure and the deployed applications.

We need a right set of supporting tools to effectively utilize this in our development process. Many of these tools are open-source and some are part of the K8s add-on. Here are some of those key supporting tools:

  • CI\CD & Image Registry :
    • Kubernetes starts after we are ready with our containerized builds. A private image registry (like nexus, JFrog, Docker etc) and a suitable CI\CD tool like Jenkins act as the input for our deployment process.
  • Logging and Monitoring Tools :
    • Essential tools for monitoring the health, critical issues, alerts and useful matrices.
    • Example Tools – K8s dashboard, Prometheus, Grafana,Weave Scope, EFK stack, cAdvisor, Kube-state-metrics
  • Tracing Tools :
    • They usually provide visual tracing which is very very useful in tracing transactions and performance bottlenecks in a micro-service based applications in distributed environments.
    • Example Tools – OpenTracing, ZipKin, DynaTracing and Jaeger
  • Right Ingress Solution :
    • These solution can greatly enhance the power of Ingress load balancing, performance and security features for external client communications.
    • Example solutions – NGNIX, Kong, AWS ALB , Ambassador, Gloo etc.

Kubernetes – Routing Services with Ingress

Kubernetes Ingress simplifies the routing of our external traffic (http & https) to our internal services. It can handle traffics from multiple domains for us.

Ingress supports implementations from multiple vendors such as NGNix, Kong, HAProxy, Ambassador and many others. They add many useful features to manage the communication with the outside world. They include features like static page caching, TLS termination, size compression, session affinity etc.

The Ingress module mainly consists of an Ingress API object that defines the requirements. And, an Ingress controller that fulfills the Ingress.

How does it simplify routing ?

Suppose we have two domains for two of our applications as shown.

Ingress lets us develop our features as separate services. For publishing, we can simply keep adding our services (service IP and port ) against the desired url paths as shown. And, its that simple! The Ingress controller is capable handling the rest for us.

How does the controller handle the Ingress routing request ?

To make the traffics from both the domains reach the Ingress Host, we can assign both the domains as host names to our Ingress Host IP. It is know as virtual hosting.

As the traffic from both the domains hit Ingress host, the Ingress controller would read the destination url in the request header. Based on the traffic routing rules, it would rout our requests to appropriate K8s services.

What other benefits do we get out of this routing feature ?

First, it separates the design of our back-end services from the our desired presentation. The reward feature and the beta version, as shown, even being deployed separately, are exposed under the same game app. It’s as if they are deployed together.

Second, it lets us share our Layer 4 Load Balancers for multiple services. A load balancer can manage the apps and expose them as internal services on different ports. Ingress can expose these services to outside, on the desired domains and url paths.

A sample Ingress object

The Ingress object shows the routing rules for the above example. A couple of path has been removed to keep it short.

The annotation under the metadata specifies the Ingress controller to be used. Here, we have used – nginx.ingress.kubernetes.io . The ngnix supports to import different features separately. ‘rewrite-target’ is one such feature.

As we can deploy multiple Ingress Controllers, it is important to specify this to point to the correct one.

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-routing-example
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: fungames.com
    http:
      paths:
      - path: /
        backend:
          serviceName: service-G1
          servicePort: 80
      - path: /reward
        backend:
          serviceName: service-G2
          servicePort: 8080
  - host: prettygifts.com
    http:
      paths:
      - path: /shop
        backend:
          serviceName: service-P1
          servicePort: 8090
  - http:
      paths:
      - path: /
        backend:
          serviceName: service-D
          servicePort: 8090
Secure a Service using TLS termination

This is another feature supported by Ingress. Using TLS termination, we can create a secured tls channel between the client and the Ingress load balancer.

The ingress and the secret API object shows the configuration for the TLS termination. The highlighted lines tell the host-name to secure and the secret to use. The keys tls.crt and tls.key in the secret are the certificate and private key for tls communication.

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-tls-demo
  namespace: my-prortal
spec:
  tls:
  - hosts:
      - ssl.demoservice.com
    secretName: tls-secret
  rules:
  - host: ssl.demoservice.com
    http:
      paths:
      - path: /cart
        backend:
          serviceName: cart-service
          servicePort: 8080 
apiVersion: v1
kind: Secret
metadata:
  name: tls-secret
  namespace: my-prortal
data:
  tls.crt: [base64 encoded cert]
  tls.key: [base64 encoded key]
type: kubernetes.io/tls
Demo : How to expose a K8s Services using Ingress.

As we discussed above, its as simple as adding routing rule to our service. We will try this out using a simple hello-app application from google-samples.

Step 1: Deploy Ingress Controller

This maps the url to our service as per the mappings in the Ingress object.

Ingress controllers are not started by default, we need to start it on our cluster. Use the below command to enable and verify it on a minikube.

$ minikube addons enable ingress
* The 'ingress' addon is enabled

$ kubectl get pods -n kube-system | grep ingress
nginx-ingress-controller-6fc5bcc8c9-4ztcn   1/1     Running   0          2m36s
Step 2: Deploy the services on a NodePort and ensure its accessible

As we will map this service, we will ensure its working.

Follow the steps under these two tabs to deploy and verify the service.

$ kubectl create deployment hello --image=gcr.io/google-samples/hello-app:1.0
deployment.apps/hello created

$ kubectl expose deployment hello --type=NodePort --port=8080
service/hello exposed
$ kubectl get service hello
NAME   TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
hello NodePort   10.109.247.168   <none>        8080:32321/TCP   78s

//Easy way to get the url
$ minikube service hello --url
http://160.74.10.26:32321

//Verify if the service is working 
$ curl $(minikube service hello --url)
Hello, world!
Version: 1.0.0
Hostname: web-557f59c6cf-rt9cv
Step 3: Create host name for our minikube

We are creating a host name that hits our ingress controller deployed on minikube.

Check our minikube ip and add a host name to /etc/hosts as shown.

$ minikube ip
160.74.10.26


//Add this entry to /etc/hosts
160.74.10.26 greetings.org
Step 4: Create the Ingress Object, add the routing rule and deploy

We will add a mapping for our service to expose it on a desired url.

test-ingress.yaml

apiVersion: networking.k8s.io/v1beta1 # for versions before 1.14 use extensions/v1beta1
kind: Ingress
metadata:
  name: example-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
   - host: greetings.org
     http:
       paths:
       - path: /say-hello
         backend:
           serviceName: hello
           servicePort: 8080

We have configured to access our hello app at http://greetings.org/say-hello.

Let us deploy using ‘apply’ command and verify :

$ kubectl apply -f ingress.yaml
ingress.networking.k8s.io/example-ingress created

$ kubectl get ingresses
NAME              HOSTS           ADDRESS       PORTS   AGE
example-ingress   greetings.org   160.74.10.26   80      3m36s

$ kubectl describe ingresses example-ingress |head -20
Name:             example-ingress
Namespace:        default
Address:          160.74.10.26
Default backend:  default-http-backend:80 (<none>)
Rules:
  Host           Path  Backends
  ----           ----  --------
  greetings.org
                 /say-hello   hello:8080 (160.78.0.7:8080)

We can verify our rules as well as the Host address where the Ingress is deployed as highlighted.

Step 6 – Lets verify if we are able access our app on our rout path

Time to test our Ingress Routing!

$ curl greetings.org/say-hello
Hello, world!
Version: 1.0.0
Hostname: hello-6649fc6cf7-tnhcn

Our routing is working ! We are able to access our service on our configured path.

Conclusion

Sitting at the edge server the Ingress can manage lot of features required as part of our communication with the outside world.

The major one being the Ingress (HTTP & HTTPS) traffic routing. This feature allows us to build applications using simpler services. It enables us to share our load balancers between multiple services. In simple terms, it provides the flexibility to expose any of our service on any url path available.

We can use the Ingress Controllers from multiple vendors. As a reverse proxy they provide many features for managing traffic. The features include TLS routing, session affinity, static page caching, Denial of Service, size compression etc.

Kubernetes – Job

A Kubernetes Job can run multiple instances of a Pod to complete a set of tasks. It can run them in sequence or in parallel or in a mixed mode. If all the instances complete successfully, the Job controller marks the Job as complete.

How to plan your execution ?

The Job includes one Pod template. It only creates multiple instances of the Pod and allows you to run them in sequence, parallel or in a mix mode. Each instance acts as worker node, as shown, to execute a part of the task. As regards the tasks, a file , a database or a queue might be holding these tasks. Here are the possible approaches that we can take:

  • Single Pod instance per Job – A single Pod can fetch and process all task in a queue in a loop.
  • Parallel Pod instances per Job – Multiple instances can be fetch and share the tasks from a given queue, to process them in parallel. Each instance gets over as it finds no more task.
  • Sequential Pod instances per Job – This can be a case of a known set of dependent steps that needs to be executed one after the other.
    • ‘Process Applications’, ‘Archive Rejected’ and ‘Daily Report’ can be 3 steps in a Job for which we can use 3 Pods in a sequence.

Kubernetes Jobs supports the parallel and the Sequential execution of pods using two simple configurations, as specified below:

spec:
  parallelism: 3
spec:
  completions: 3    

Let us start with creating a Job with a single Pod.

Create a Job with a Single Pod

demo-job.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: demo-job
spec:
  template:
    metadata:
      name: worker-pod
    spec:
      containers:
          - name: worker-pod
            image: busybox
            args:
            - /bin/sh
            - -c
            - "for task in 1 2 3 4 5; do echo Task -${task} completed!; done;echo 'Done !'"
      restartPolicy: OnFailure

demo-job.yaml is a simple Job that simply mocks few tasks and prints the completion statements. Lets deploy the Job using the below command and explore the output :

kubectl apply -f demo-job.yaml

$ kubectl get job,pod
NAME                 COMPLETIONS   DURATION   AGE
job.batch/demo-job   1/1           3s         37s

NAME                 READY   STATUS      RESTARTS   AGE
pod/demo-job-5v8mx   0/1     Completed   0          37s

$ kubectl logs demo-job-5v8mx
Task -1 completed!
Task -2 completed!
Task -3 completed!
Task -4 completed!
Task -5 completed!
Done !

As we notice, the Job has created a Pod based on the template. The Pod ideally implements the logic to fetch and execute a set of tasks as mentioned above. Here, we have simply printed some statements to mock those tasks. As the worker Pod completes, the Job finishes successfully showing 1\1 worker Pod has completed.

How to automate the cleanup of the completed Jobs ?

The Job controller retains the Job and its Pods to preserve the logs for verification and error analysis, even after its completion. You may delete them separately or delete the Job to delete them all.

To automate the cleanup, we can make use of the TTL mechanism as shown below which will do the cleanup 600 seconds after the completion of the Job. We need to enable the TTL mechanism for this.

apiVersion: batch/v1
kind: Job
metadata:
  name: demo-job
spec:
  ttlSecondsAfterFinished: 600
  template:
  . . .
  . . .
How to stop a Job in progress ?

A Job does not provide any option to stop or pause it in between. You can only delete the Job, which will delete its Pods too. If you are not storing the logs in an external storage, you will loose your logs.

To handle failure conditions, it provides with two parameters – backoffLimit which set max retries & activeDeadlineSeconds which sets a deadline to complete.

The below Job tells –

  • Stop the Job if the container or the Pod is restarted for 3 times.
    • The default value of backoffLimit is 6
  • Stop the Job if it exceeds 300 seconds.
    • The Job controller will terminate all the active Pods and mark the Job as failed.
apiVersion: batch/v1
kind: Job
metadata:
  name: demo-job
spec:
  completions: 2
  backoffLimit: 3
  activeDeadlineSeconds: 300
  template:
    metadata:
      name: worker-pod
    spec:
      containers:
          - name: worker-pod
            image: busybox
            args:
            - /bin/sh
            - -c
            - echo Started processing;'sleep 10;echo Task completed;
      restartPolicy: OnFailure
Execution Patterns – Sequential, Parallel & Mixed

A Kubernetes Job allows you to run a set of Pods in parallel or run them in a sequence or run in a mixed mode. All these patterns would be useful to solve different use cases as mentioned above.

Run Workers in Parallel

To run workers in parallel, use :

spec:
  parallelism: 3

If we set this value to our demo-job, we can see 3 Pods running in parallel, as shown:

$ kubectl get job,pod
NAME                          COMPLETIONS   DURATION   AGE
job.batch/demo-job   0/1 of 3      11s        11s

NAME                          READY   STATUS    RESTARTS   AGE
pod/demo-job-5ddwc   1/1     Running   0          11s
pod/demo-job-7pd5g   1/1     Running   0          11s
pod/demo-job-vq2dk   1/1     Running   0          11s

parallelism of N, starts N Pods in parallel. This internally sets the ‘completions’ to N.

Run Workers in Sequence

To run worker Pods in sequence, use :

spec:
  completions: 5

The ‘completions’ value fixes the max number of Pods in a Job. If we do not specify any value for ‘parallelism’, it retains the default value of 1, and all the Pods run in a sequence. As we can see below, the Job is creating the 3rd Pod after the 2nd one is complete.

$ kubectl get job,pod
NAME                 COMPLETIONS   DURATION   AGE
job.batch/demo-job   2/5           29s        29s

NAME                 READY   STATUS              RESTARTS   AGE
pod/demo-job-46d57   0/1     Completed           0          15s
pod/demo-job-b9h96   0/1     ContainerCreating   0          2s
pod/demo-job-wz52v   0/1     Completed           0          29s
Mix Parallel & Sequential

K8s allows us to mix the parallel and sequential execution together. In such a case, the ‘completions’ provides max number of Pods to run where as the ‘parallelism’ says how many to run in parallel. A sample Job definition is given below.

apiVersion: batch/v1
kind: Job
metadata:
  name: demo-job
spec:
  completions: 10
  parallelism: 3
  template:
    metadata:
      name: worker-pod
    spec:
      containers:
          - name: worker-pod
            image: busybox
            args:
            - /bin/sh
            - -c
            - echo Started processing;sleep 10;echo Task completed;
      restartPolicy: OnFailure
Conclusion

As we have seen, the Jobs supports the core features to deploy task specific applications that completes after the task is over.

The summary of the key points about a Job are:

  • A Job can run its Pod instances in sequence or in parallel or in a mixed.
  • The Pods has to stop successfully for a Job to succeed.
    • As a result, it is not allowed to have a restartPolicy: Always.
    • It can only use restartPolicy values – OnFailure or Never
  • backoffLimit & activeDeadlineSeconds – key attribute to stop a failed Job.
  • Jobs are not deleted by default to preserve the logs, we need to take measures to clean up.
  • As a closely related module, K8s provides CronJob that comes with a scheduler.
    • It can be used for automating the Jobs at desired intervals.

K8s – Deployment

A Deployment object is used to define the desired specification of your application instance including the scaling requirements.

The key in the deployment module lies in the way the Deployment Controller manages deployment objects. The deployment controller transitions the existing state of your application instances to the new desired state as per your latest deployment specifications, thus providing you an automated deployment process.

The transitioning is done in a controlled way using rolling over mechanism; thus it ensures minimal impact on the availability of the application during a deployment.

We will look at these with some examples in the sections below.

Key Features of Deployment

  • Supports the deployment of scalable applications
  • Manages the changes to the POD template
  • Manages the changes to the scaling requirements
  • Supports the auto scaling of the applications
  • Manages Deployment History and enables you to Roll-back to historical versions
  • Provides Rolling Update of the application instances
  • Supports Pausing and Resuming of the rolling deployments

The following sections describes how K8s provides these features in more detail.

Deploying an Application

demo-deploy.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-deploy
  labels:
    app: demo-deploy
spec:
  replicas: 4
  selector:
    matchLabels:
      app: demo-app
  template:
    metadata:
      labels:
        app: demo-app
    spec:
      containers:
      - name: demo-app
        image: k8s.gcr.io/echoserver:1.9
        ports:
        - containerPort: 8080

This is our sample deployment named : demo-deploy

The key components of the deployment are :

  • replicas – Defines the scaling
  • template -Defines the desired specification of the pod instances
  • selector – Defines the criteria to select and keep track of the instances deployed across the cluster nodes. Here, the label is ‘app: demo-app’

Lets deploy and explore the resultant components :

$ kubectl apply -f demo-deploy.yaml --record=true
deployment.apps/demo-deploy created

$ kubectl get deployment,rs,pod
NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/demo-deploy   4/4     4            4           42s

NAME                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/demo-deploy-595d5466b5   4         4         4       42s

NAME                               READY   STATUS    RESTARTS   AGE
pod/demo-deploy-595d5466b5-5v8kj   1/1     Running   0          42s
pod/demo-deploy-595d5466b5-hhtrr   1/1     Running   0          42s
pod/demo-deploy-595d5466b5-jf4ks   1/1     Running   0          42s
pod/demo-deploy-595d5466b5-w6sgx   1/1     Running   0          42s

Observation

  • The Deployment has created a Replicaset . Each deployment is managed by a separate replicaset.
  • The Replicaset used the template to create 4 pods as per the scaling requirement
    • Each replicaset shares the same hash code with its associated pods.
    • And, the pods, also, have another hash code to avoid the naming conflict.
  • Replicaset uses the selector criteria to select and monitor the pods

To look at the details of our deployment and it’s associated events we can use the following command. Let us look at the top part for now:

$ kubectl describe deployments | head -15
Name:                   demo-deploy
Namespace:              default
CreationTimestamp:      Wed, 08 Jul 2020 13:36:42 +0000
Labels:                 app=demo-deploy
Selector:               app=demo-app
Replicas:               4 desired | 4 updated | 4 total | 4 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=demo-app

Understanding Rolling Update

Now let us do a failed deployment by setting an invalid image to the deployment:

$ kubectl set image deployment.v1.apps/demo-deploy  demo-app=k8s.gcr.io/echoserver:1.9.xy --record=true

deployment.apps/demo-deploy image updated

The status of various components of the deployment is shown below. Now lets look at the ‘RollingUpdateStrategy‘ as highlighted under the pod description above and try to match the outcome below:

$ kubectl get deployment,rs,pod
NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/demo-deploy   3/4     2            3           6m58s

NAME                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/demo-deploy-595d5466b5   3         3         3       6m58s
replicaset.apps/demo-deploy-fd86759fb    2         2         0       4s

NAME                               READY   STATUS             RESTARTS   AGE
pod/demo-deploy-595d5466b5-5v8kj   1/1     Terminating        0          6m58s
pod/demo-deploy-595d5466b5-hhtrr   1/1     Running            0          6m58s
pod/demo-deploy-595d5466b5-jf4ks   1/1     Running            0          6m58s
pod/demo-deploy-595d5466b5-w6sgx   1/1     Running            0          6m58s
pod/demo-deploy-fd86759fb-jjfr7    0/1     ImagePullBackOff   0          4s
pod/demo-deploy-fd86759fb-nqgwf    0/1     ImagePullBackOff   0          4s
  • Only one pod (25% max unavailable out of scale=4) of the existing replicaset is terminating.
  • 2 pods (1 in place of terminating pod and 1 as part of 25% max surge) is coming up in the new replicaset.
  • And, since this is a rolling update and the new pod are not going to come up to the running status due to image issue, no more of pods of the existing replicaset will be taken down and the deployment will be stuck at this stage.
  • Advantage : The application will be still available with 75% of the pods because of the rolling update strategy.

Now fix the image version and do a redeployment as follows :

kubectl set image deployment.v1.apps/demo-deploy  demo-app=k8s.gcr.io/echoserver:1.10  --record=true

Running the following command shows the deployment is successful and the latest replicaset is having all the 4 pods ready.

$ kubectl rollout status deployment.v1.apps/demo-deploy
Waiting for deployment "demo-deploy" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "demo-deploy" rollout to finish: 3 of 4updated replicas are available...
deployment "demo-deploy" successfully rolled out

$ kubectl get rs
NAME                     DESIRED   CURRENT   READY   AGE
demo-deploy-595d5466b5   0         0         0       11m
demo-deploy-5f6f56cd49   4         4         4       118s
demo-deploy-fd86759fb    0         0         0       5m2s

Exploring Deployment History and doing a Roll Back

Now, lets say, the new version has some serious bug and we want to rollback and we want to check the history of the deployment:

$ kubectl rollout history deployment.v1.apps/demo-deploy
deployment.apps/demo-deploy
REVISION  CHANGE-CAUSE
1         kubectl apply --filename=pod.yaml --record=true
2         kubectl set image deployment.v1.apps/demo-deploy demo-app=k8s.gcr.io/echoserver:1.9.xy --record=true
3         kubectl set image deployment.v1.apps/demo-deploy demo-app=k8s.gcr.io/echoserver:1.10 --record=true

The change cause has been added due to –record=true flag used during the deployments.

A simple ‘rollout undo‘ will take the deployment back to a failed revision(revision=2) and to take it to an older version we can specify the revision as follows :

$ kubectl rollout undo deployment.v1.apps/demo-deploy  --to-revision=1
deployment.apps/demo-deploy rolled back

$ kubectl rollout history deployment.v1.apps/demo-deploy
deployment.apps/demo-deploy
REVISION  CHANGE-CAUSE
2         kubectl set image deployment.v1.apps/demo-deploy demo-app=k8s.gcr.io/echoserver:1.9.xy --record=true
3         kubectl set image deployment.v1.apps/demo-deploy demo-app=k8s.gcr.io/echoserver:1.10 --record=true
4         kubectl apply --filename=pod.yaml --record=true

Now the history shows that the roll back has used the revision 1 and renamed it to revision 4 after the rollback. Moreover, as we can see, it has reused the saved ‘pod-template-hash‘, the identity (595d5466b5) given by that deployment revision.

$ kubectl rollout history deployment.v1.apps/demo-deploy --revision=4
deployment.apps/demo-deploy with revision #4
Pod Template:
  Labels:       app=demo-app
        pod-template-hash=595d5466b5
  Annotations:  kubernetes.io/change-cause: kubectl apply --filename=pod.yaml --record=true
  Containers:
   demo-app:
    Image:      k8s.gcr.io/echoserver:1.9

Other Key Features – Pause, Restart, Scale , Autoscale

As we have seen so far, the deployment manages the desired state changes and keeps track of the each state history to support the requirement to roll back.

The above examples showed how we can change the image version. For major changes we can also deploy the updated version of deployment file where the name of the deployment remains the same:

# deploying an updated version of the app
$ kubectl apply -f demo-app-v-1.1.yaml
deployment.apps/demo-deploy configured

For scaling, autoscaling, pausing, resuming , updating comments to a deployment history, here are some useful commands:

# To pause a deployment in the middle
kubectl rollout pause deployment.v1.apps/demo-deploy

# To resume a paused deployment
kubectl rollout resume deployment.v1.apps/demo-deploy

# To scale a deplyment
kubectl scale deployment.v1.apps/demo-deploy --replicas=8

# Command to autoscale a deployment as the cpu usage increases beyond 90%
kubectl autoscale deployment.v1.apps/demo-deploy --min=10 --max=20 --cpu-percent=90

# To add a comment to the latest deployment history change-cause manually
kubectl annotate deployment.v1.apps/demo-deploy kubernetes.io/change-cause="successful roll-over to image-1.10"

Conclusion

The deployment is used to manage the desired state changes of the deployed applications. And, the applications used in deployment are long running stateless applications.

The default restart policy of a pod in a deployment is – Always.

The other key deployment supported by Kubenetes are :

  • Job & CronJob : Meant for managing jobs which are meant to run only till completion.
  • ReplicaSets : Meant for managing long running stateful applications.
  • Deamonset : Meant for managing applications which are required to present on all the desired nodes. These are not required to support scaling requirements as in the other deployments.

K8s – Secrets

K8s Secrets allow you to store and manage your confidential data such as passwords , ssh keys , authentication tokens separately in a cluster level object that the PODs can fetch for their usage at the time of deployment.

  • Secret being an cluster level API object, provides a safer place and the ability to apply better access control
    • Better compared to hard coding such data in Pod definition or in a container image
    • Allows you to manage your confidential from a central location.
    • We can apply policies to restrict user access to view the secrets
  • We can apply data encryption on the secret data stored on ETCD.
  • It works very similar to ConfigMap and you can access the data as env variables or volumes.
  • It stores data in in-memory, tmpfs volumes at Node level.
    • Thus if a node crashes , neither the node nor the associated containers hold on to the confidential data.

Creating Secrets

Using ‘create secret command

Similar to ConfigMaps we can use ‘create secret’ command to create the key value pairs using :

  • from-literal : Literal key becomes key & literal value becomes value
  • from-file : File name (or the file-key, if specified) becomes the key and the content becomes the value
  • from-env-file : Each entry in side the file creates a literal value.

The following is an example secret being created using two literals(run this on a K8s cluster, say a minikube) :

# my-db-secrete using --from-literal 

kubectl create secret generic my-db-secret \
--from-literal=username='admin' \
--from-literal=password='pa$sw@rd1)%'

The resultant secrete can be retrieved using :

kubectl get secret my-db-secret -o yaml

The secrete in the output would look as below :

apiVersion: v1
data:
  password: cGEkc3dAcmQxKSU=
  username: YWRtaW4=
kind: Secret
metadata:
  creationTimestamp: "2020-06-30T13:32:40Z"
  name: my-db-secret
  namespace: default
  resourceVersion: "1016"
  selfLink: /api/v1/namespaces/default/secrets/my-db-secret
  uid: b17cf73b-dbeb-4a7d-9b6b-14ac8649d4ae
type: Opaque

As we can see the values are shown encrypted with Base64 encryption. This is the default encryption and you can change the encryption using K8s EncryptionConfiguration .

Using the secret inside a POD

Again the usage is also very similar to ConfigMaps.

Import as Env variables

Let us use the following POD to import the ‘my-db-secret’ created above into the env variables :

secret-test-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: secret-test-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/echoserver:1.4
      envFrom:
      - secretRef:
          name: my-db-secret

Follow the steps for testing :

# Step - 1 : Create the secret my-db-secret

kubectl create secret generic my-db-secret \
--from-literal=username='admin' \
--from-literal=password='pa$sw@rd1)%'

#Step 2: Create the pod  and check the status
kubectl apply -f secret-test-pod.yaml

kubectl get pod,secret -o wide

#Step 3: When the pod is running , 
#              log in to its container shell and check the env variables
kubectl exec -it secret-test-pod -- /bin/bash

printenv

In the output, you should find the following two environment variables:

password=pa$sw@rd1)%
username=admin

Summary of importing Secret as Env variables

  • Each literal and each file entry in a secret, is imported as a separate environment variable
  • For literals, the value of the variable is the literal’s value
  • For the files, the value of the variable is the content of the file.

The above example is for importing all the keys in a secret. The comparison of the syntax to read few particular keys is as shown below :

All entries in a Secret

      envFrom:
      - secretRef:
          name: my-db-secret

Specific entries in a secret

env:
      - name: USERNAME
        valueFrom:
          secretKeyRef:
            name: my-db-secret
            key: username
      - name: PASSWORD
        valueFrom:
          secretKeyRef:
            name: my-db-secret
            key: password

Import into volume

secret-vol-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: secret-vol-pod
spec:
  containers:
  - name: test-container
    image: k8s.gcr.io/echoserver:1.4
    volumeMounts:
    - name: secret-volume
      readOnly: true
      mountPath: "/secret-volume"		  
  volumes:
  - name: secret-volume
    secret:
      secretName: my-db-secret

The syntax for importing secrets has been highlighted in the above pod definition.

Let us follow the below steps to try this out with an example:

# Step - 1 : Create the sample  secret 'my-db-secret'
kubectl create secret generic my-db-secret \
--from-literal=username='admin' \
--from-literal=password='pa$sw@rd1)%'

#Step 2: Create the pod  and check the status
kubectl apply -f secret-test-pod.yaml
kubectl get pod,secret -o wide

#Step 3: When the pod is running , 
#              log in to its container shell and move to the mount path
kubectl exec -it secret-test-pod -- /bin/bash

cd secret-volume

The contents of the mount path would be as follows:

#The mount path has two link file for two literal entries 
root@secret-vol-pod:/secret-volume# ls -l
total 0
lrwxrwxrwx 1 root root 15 Jun 30 15:48 password -> ..data/password
lrwxrwxrwx 1 root root 15 Jun 30 15:48 username -> ..data/username

#Content of username - its literal value in secret
root@secret-vol-pod:/secret-volume# cat username
admin

#Content of password- its literal value in secret
root@secret-vol-pod:/secret-volume# cat password
pa$sw@rd1)%

Summary of importing a Secret using a volume

  • Each data key in the secret will be listed as a linked file under the mount path.
  • For literals – the content of the linked file would be the literal’s value
  • For files – the content of the linked file would be the content of the file in the secret

The above example imports all the keys in a secret into a volume. Comparison of the syntax to import only few specific keys is as follows:

For all keys in a secret

volumes:
  - name: secret-volume
    secret:
      secretName: my-db-secret      

For specific keys in a secret

volumes:
  - name: secret-volume
    secret:
      secretName: my-db-secret
      items:
      - key: username
        path: USERNAME
      - key: password
        path: PASSWORD

K8s – Services

PODs are instantiated across various nodes by the scheduler and each POD gets it’s unique virtual IP. Moreover, these instances need to be scaled up or down as per our load requirements and, also, need to be replaced in case of failures.So, the set of the backing POD ips for an application may keep changing.

The service API objects in K8s enable us to:

  • keep track of the dynamic sets of backing PODs for an application
  • provide service urls for load balanced application access
  • expose the application to internal and\or external clients

Besides this it also provides ability to customize the default mechanism to address custom needs.

K8s Service – There are 4 basic types of service

K8s supports 4 basic types of service :

  • ClusterIP : Only for internal access
  • NodePort : It builds on top of the clusterIP service and provides exposure to external clients
  • LoadBalancer : A load balancer in front of NodePort service
  • ExternalName: A service for access external endpoints

As all the other services are like some variation of the cluster IP service, we will explore this in detail and then move on to see how other one vary from this.

1. ClusterIP : Used only for internal access.

The service API object is shown on the left side the diagram and when this gets deployed the following things happen internally.

  • K8S assigns a cluster IP to the service
  • The service create an Endpoints object based on spec.selector and keeps track of the backing POD IPs.
  • kube-proxy creates the routing rules from cluster ip (@port) to endpoint ips(@ target ports) for load balancing purpose.
  • Service Discovery: With the help of DNS service,if available, a dns entry for network access is also created as shown. This helps the clients not to worry about the clusterIP that gets assigned dynamically to the service.

Different options for customization:

  • Custom Endpoints : Say, some clustered MySql instances are running outside the K8s cluster. And, you want to access this DB cluster from applications running inside K8s.
    • You have the option to create a cluster service with no spec.selector
    • Create an Endpoints object with the same name as the cluster service.
    • Specify the targeted DB IPs in the Endpoints object.
  • ClusterIP: None – This creates a HeadlessService
    • No cluster IP is assigned and, hence, no routing rules in kube-proxy.
    • The service adds separate DNS entries for each of the backing PODs, instead of the regular single DNSentry for the service.
  • Specify a ClusterIP: You can hardcode a specific ip from the allowed range of cluster ips. The service will use this as the cluster IP as long as it is not taken up by another service.

Kube-proxy manages the load balancing (L4 type) as part of the ClusterIP service. In case you need to go for advanced L4 load balancing, you have the option of going with Headless Service and implement your own load balancing using the endpoint addresses available in the associated EndPoint object.

2. NodePort : The easiest option to provide external access.

  • K8s creates the regular ClusterIP service as discussed earlier.
  • K8s assigns a NodePort and creates proxy routing rules for routing requests from NodePorts on K8s Nodes to the ClusterIP service.
    • This enables traffics coming at the assigned NodePort on any K8s node to get routed to one of the endpoint PODs.

Although its quite an easy approach to expose your service to the external clients it comes with some serious drawbacks :

  • You need to open firewall access on the NodePort for a set of your K8s Nodes. Which is a serious security concern.
  • The clients need to be intimated about any changes in the node IPs as well as the assigned NodePort. Which leads to maintenance issues.

A LoadBalancer service helps in solving these two issues by adding a LoadBalancer at the top. Let us see, how?

3. LoadBalancer: Allows you to add a load balancer on top of a NodePort service.

  • The load balancer which connects to the NodePort service internally and provides a single point of access to the external clients.
  • From security point of view, you need not have to expose your kubernetes nodes directly to your client.
  • Keeping track of the available Nodes and any changes to the service NodePort is now the responsibility of the load balancer.

But, the load balancer solution has its own drawbacks:

  • Adding an external load-balancer, adds to your cost.
  • Moreover, if you have to expose multiple services , you can not expose them all on the same port(80) on a single load-balancer. So, you have to go for multiple load-balancers which will increase your cost many folds.

Luckily to overcome this cost issue of using multiple load balancers, K8s provides a nice solution called Ingress. We will look into this solution in a separate article.

4. External Name: A K8S service for external endpoints.

apiVersion: v1
kind: Service
metadata:
  name: my-db-service
  namespace: test
spec:
  type: ExternalName
  externalName: my.test.database.com
  • No Cluster ID is assigned, no Endpoints is used and no proxying is setup by K8s.
  • DNS name against the externalName contains the list of endpoints
  • The redirection happens at the DNS level

When a K8s application tries to access my-db-service, it gets modified with the contents of my.test.database.com

This is useful in pointing to set of external applications or database cluster (not managed by K8s).

The Journey Begins

Thanks for joining me!

Good company in a journey makes the way seem shorter. — Izaak Walton

post