Monday, October 11, 2021

Running Multi Node Zookeeper Cluster on Docker

Introduction 

I am working on installing Apache Nifi on multi node cluster. For this I needed a Zookeeper multi node cluster. 

Zookeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. 

Multi Node Cluster

We will run 3 node cluster on docker. We will use below docker-compose.yaml as below:

version: "3"
services:
  zk01:
    hostname: zk01
    container_name: zk01
    image: 'bitnami/zookeeper:3.7'
    ports:
      - '2181'
      - '2888'
      - '3888'
    environment:
      - ALLOW_ANONYMOUS_LOGIN=yes
      - ZOO_SERVER_ID=1
      - ZOO_SERVERS=0.0.0.0:2888:3888,zk02:2888:3888,zk03:2888:3888
    networks:
      - zk_net

  zk02:
    hostname: zk02
    container_name: zk02
    image: 'bitnami/zookeeper:3.7'
    ports:
      - '2181'
      - '2888'
      - '3888'
    environment:
      - ALLOW_ANONYMOUS_LOGIN=yes
      - ZOO_SERVER_ID=2
      - ZOO_SERVERS=zk01:2888:3888,0.0.0.0:2888:3888,zk03:2888:3888
    networks:
      - zk_net

networks:
  zk_net:
    driver: bridge

I am using bitnami-zookeeper image for it. You can use  latest version instead of 3.7 version if interested. 

This will launch a 3 node zookeeper cluster without publishing/exposing the nodes to the host machines. 

$  docker-compose -f docker-compose.yaml up
This will show the logs of all the 3 nodes as they start running.



After few minutes the log will be like below:



We can check the status by running below command:

$  docker ps
This will show all the 3 instances running.


You can now login to one of the instance/node using below command.

$ docker exec -it zk02 /bin/bash
I have no name!@zk02:/$
This will log you in to the zk02 node.

Testing the Cluster

We will now create some entry on zk02 and it will be reflected/replicated to all the other Zookeeper nodes immediately. We will start zookeeper cli for it.

$ zkCli.sh -server zk02:2181
/opt/bitnami/java/bin/java
Connecting to zk02:2181
2021-10-12 05:56:18,058 [myid:] - INFO  [main:Environment@98] - Client environment:zookeeper.version=3.7.0-e3704b390a6697bfdf4b0bef79e3da7a4f6bac4b, built on 2021-03-17 09:46 UTC
2021-10-12 05:56:18,064 [myid:] - INFO  [main:Environment@98] - Client environment:host.name=zk02
2021-10-12 05:56:18,065 [myid:] - INFO  [main:Environment@98] - Client environment:java.version=11.0.12
2021-10-12 05:56:18,069 [myid:] - INFO  [main:Environment@98] - Client environment:java.vendor=BellSoft

This will open below zk cli command prompt: 

2021-10-12 05:56:18,195 [myid:zk02:2181] - INFO  [main-SendThread(zk02:2181):ClientCnxn$SendThread@1005] - Socket connection established, initiating session, client: /172.28.0.2:43996, server: zk02/172.28.0.2:2181
2021-10-12 05:56:18,249 [myid:zk02:2181] - INFO  [main-SendThread(zk02:2181):ClientCnxn$SendThread@1438] - Session establishment complete on server zk02/172.28.0.2:2181, session id = 0x20010a8acf10000, negotiated timeout = 30000

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
[zk: zk02:2181(CONNECTED) 0] 
Now run create : 
[zk: zk02:2181(CONNECTED) 0] create /hello world
Created /hello
We can query it as below
[zk: zk02:2181(CONNECTED) 1] get /hello
world
We can also check by logging into other nodes. We will follow the above steps to login and query on other node.

Clean up

We can delete the entry:
[zk: zk02:2181(CONNECTED) 2] delete /hello

Happy Coding !!

Saturday, October 9, 2021

Expose Pod in Kubernetes

Introduction 

Today we will learn to expose the Pod Resource of Kubernetes.

Expose Pod

We will create and expose the pod using below yaml:

apiVersion: v1
kind: Pod
metadata:
  name: ashok-pod
  labels:
    app: web
spec:
  containers:
    - name: hello-app
      image: gcr.io/google-samples/hello-app:1.0
      ports:
      - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: ashok-svc
spec:
  type: LoadBalancer
  ports:
  - port: 8080
    targetPort: 8080
  selector:
    app: web

We will create the pod and service using:

$ kubectl apply -f pod2.yaml
pod/ashok-pod created
service/ashok-svc created
We can status using below:

$ kubectl get all
NAME            READY   STATUS    RESTARTS   AGE
pod/ashok-pod   1/1     Running   0          92s

NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
service/ashok-svc    LoadBalancer   10.108.156.134   <pending>     8080:30901/TCP   92s
service/kubernetes   ClusterIP      10.96.0.1        <none>        443/TCP          3d2h

Create a tunnel to access service in browser:
$ minikube tunnel
🏃  Starting tunnel for service ashok-svc.
Open the browser  http://localhost:8080/ :



Happy Coding !!

Running StorageClass Resource on Kubernetes

Introduction

We will learn creating dynamic volume using StorageClass on Minikube today. 

StorageClass

We will create StorageClass, PersistentVolumeClaim, Pod and Service using yaml:

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: slow
provisioner: k8s.io/minikube-hostpath
parameters:
  type: pd-ssd
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ashok-pv-claim
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: slow
  resources:
    requests:
      storage: 10Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: ashok-pv-pod
  labels:
    app: hello  
spec:
  volumes:
    - name: ashok-pv-storage
      persistentVolumeClaim:
        claimName: ashok-pv-claim
  containers:
    - name: ashok-pv-container
      image: nginx
      ports:
        - containerPort: 80
          name: "http-server"
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: ashok-pv-storage
---
apiVersion: v1
kind: Service
metadata:
  name: helloweb-svc
  labels:
    app: hello
spec:
  type: LoadBalancer
  ports:
  - port: 8080
    targetPort: 80
  selector:
    app: hello

We need to create an  index.html file for our nignx pod and then use this file in our PersistentVolumeClaim for storage in our Pod.

You can login to Pod using:

$ kubectl exec -it ashok-pv-pod -- /bin/bash
root@ashok-pv-pod:/#
Create index.html file at /usr/share/nginx/html/index.html :

$ echo 'Hello from Kubernetes storage using storage class' > /usr/share/nginx/html/index.html
Create a tunnel to access service in browser:
$ minikube tunnel
🏃  Starting tunnel for service helloweb-svc.
Open the browser  http://localhost:8080/ :



Happy Coding !!


Pass configs to Kubernetes using ConfigMap

Introduction

Today we will learn about ConfigMap Resource of Kubernetes. This is used to pass the configs to Kubernetes resources like Pod, Deployment etc.

ConfigMap

We will create a ConfigMap using yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: special-config
  namespace: default
data:
  special.how: very
  log_level: INFO
  SPECIAL_LEVEL: very
  SPECIAL_TYPE: charm
  example.property.file: |-
    property.1=value-1
    property.2=value-2
    property.3=value-3    

We will create the config from above yaml:

$ kubectl apply -f config.yaml
configmap/special-config created
We will use this config in below pod:
apiVersion: v1
kind: Pod
metadata:
  name: ashok-config-pod
spec:
  containers:
    - name: ashok-config-pod
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "env" ]
      env:
        - name: SPECIAL_LEVEL_KEY
          valueFrom:
            configMapKeyRef:
              name: special-config
              key: special.how
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: special-config
              key: log_level
  restartPolicy: Never
Let's apply above yaml:
$ kubectl apply -f pod.yaml
pod/ashok-config-pod created
We can check the status of the pod:
$ kubectl get po
NAME               READY   STATUS      RESTARTS   AGE
ashok-config-pod   0/1     Completed   0          48s
We can see the status is  completed for our pod. To check our configs passed as environment variables will be visible in the logs.
$ kubectl logs ashok-config-pod
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
LOG_LEVEL=INFO                         <--- Our config from the ConfigMap Resource
HOSTNAME=ashok-config-pod
SHLVL=1
HOME=/root
ASHOK_SVC_SERVICE_HOST=10.106.81.159 
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
ASHOK_SVC_PORT=tcp://10.106.81.159:80
ASHOK_SVC_SERVICE_PORT=80
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
ASHOK_SVC_PORT_80_TCP_ADDR=10.106.81.159
SPECIAL_LEVEL_KEY=very                 <--- Our config from the ConfigMap Resource
ASHOK_SVC_PORT_80_TCP_PORT=80
ASHOK_SVC_PORT_80_TCP_PROTO=tcp
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
PWD=/
KUBERNETES_SERVICE_HOST=10.96.0.1
ASHOK_SVC_PORT_80_TCP=tcp://10.106.81.159:80
We will use another pod yaml to see the example.property.file :
apiVersion: v1
kind: Pod
metadata:
  name: ashok-config-pod
spec:
  containers:
    - name: ashok-config-pod
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "ls /etc/config/" ]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        # Provide the name of the ConfigMap containing the files you want
        # to add to the container
        name: special-config
  restartPolicy: Never
Similarly you can check the configs in logs.

Happy Coding !!

Create PersistentVolume, PersistentVolume on Kubernetes

Introduction

I will show how to create PersistentVolume and PersistentVolumeClaim on Kubernetes. I am using Minikube for our learning.

We will first create an  index.html file for our Nginx pod and then use this file in our PersistentVolume and PersistentVolumeClaim for storage in our Pod.

Storage

You can login to Minikube using:

$ minikube ssh
Last login: Sun Oct 10 01:14:15 2021 from 192.168.49.1
docker@minikube:~$
We will now create index.html file at /mnt/data/index.html :
$ sudo mkdir /mnt/data
$ sudo sh -c "echo 'Hello from PVC example' > /mnt/data/index.html"

PersistentVolume

We can create a PersistentVolume using yaml:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: ashok-pv-vol
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/data"

Apply the PV:

$ kubectl apply -f pv.yaml
persistentvolume/ashok-pv-vol created

We can check the status of the PersistentVolume:

$ kubectl get pv
NAME           CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
ashok-pv-vol   20Gi       RWO            Retain           Available           manual                  4s

PersistentVolumeClaim

We will create an PersistentVolumeClaim using yaml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ashok-pv-claim
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi

PersistentVolumeClaim can be created as below:

$ kubectl apply -f pvc.yaml
persistentvolumeclaim/ashok-pv-claim created

We can check the status of the PersistentVolumeClaim:

$ kubectl get pvc
NAME             STATUS   VOLUME         CAPACITY   ACCESS MODES   STORAGECLASS   AGE
ashok-pv-claim   Bound    ashok-pv-vol   20Gi       RWO            manual         8s

The persistent volume status is now changed to bound :

$ kubectl get pv
NAME           CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                    STORAGECLASS   REASON   AGE
ashok-pv-vol   20Gi       RWO            Retain           Bound    default/ashok-pv-claim   manual                  2m31s

Let's create a pod for using the static volume.

Using PVC in Pod


We will create a pod using yaml:
apiVersion: v1
kind: Pod
metadata:
  name: ashok-pv-pod
spec:
  volumes:
    - name: ashok-pv-storage
      persistentVolumeClaim:
        claimName: ashok-pv-claim
  containers:
    - name: ashok-pv-container
      image: nginx
      ports:
        - containerPort: 80
          name: "http-server"
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: ashok-pv-storage

Let's create pod:
$ kubectl apply -f pv-pod.yaml
pod/ashok-pv-pod created
We will check the status of the pod:
$ kubectl get po
NAME           READY   STATUS    RESTARTS   AGE
ashok-pv-pod   1/1     Running   0          79s
To make this pod accessible from browser, you can use  kubectl port-forward :
$ kubectl port-forward nginx 8888:80
Forwarding from 127.0.0.1:8888 -> 80
Forwarding from [::1]:8888 -> 80

Open the browser  http://localhost:8888/ :

Clean Up

We can delete the pod, pvc and pv using below commands:

$ kubectl delete po ashok-pv-pod
pod "ashok-pv-pod" deleted
$ kubectl delete pvc ashok-pv-claim
persistentvolumeclaim "ashok-pv-claim" deleted
$ kubectl delete pv ashok-pv-vol
persistentvolume "ashok-pv-vol" deleted

Let me know if you need any help.

Happy Coding !!!

Wednesday, October 6, 2021

Running ReplicaSet on Kubernetes

Introduction

I was trying to understand the Replicaset and wanted to see if we can run it directly and can it be accessible like Deployment Resource.

ReplicaSet

The YAML for the replicaset is:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: helloweb-rs
  labels:
    app: hello
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
       - name: hello-app
         image: gcr.io/google-samples/hello-app:1.0
         ports:
         - containerPort: 8080

I launched the 3 pods using the above YAML.
$ kubectl apply -f replicaset.yaml
replicaset.apps/helloweb-rs created
The status of the K8 cluster:

$ kubectl get all
NAME                    READY   STATUS    RESTARTS   AGE
pod/helloweb-rs-8grmh   1/1     Running   0          6s
pod/helloweb-rs-9bstq   1/1     Running   0          6s
pod/helloweb-rs-rzjc8   1/1     Running   0          6s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   42h

NAME                          DESIRED   CURRENT   READY   AGE
replicaset.apps/helloweb-rs   3         3         3       6s

Created load balancer service YAML

apiVersion: v1
kind: Service
metadata:
  name: helloweb-svc
  labels:
    app: hello
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: hello

Launched it via kubectl:

$ kubectl apply -f service-lb.yaml
service/helloweb-svc created

The status of the service
$ kubectl get all
NAME                    READY   STATUS    RESTARTS   AGE
pod/helloweb-rs-8grmh   1/1     Running   0          3m9s
pod/helloweb-rs-9bstq   1/1     Running   0          3m9s
pod/helloweb-rs-rzjc8   1/1     Running   0          3m9s

NAME                   TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/helloweb-svc   LoadBalancer   10.102.140.85   localhost     80:30971/TCP   6s
service/kubernetes     ClusterIP      10.96.0.1       <none>        443/TCP        42h

NAME                          DESIRED   CURRENT   READY   AGE
replicaset.apps/helloweb-rs   3         3         3       3m9s

Open the http://localhost/ in browser:






Happy Coding !!

Monday, October 4, 2021

Types of Service on Kubernetes

Introduction

There are 4 types of service by which we can expose our deployments on Kubernetes. I am using docker-desktop as Kubernetes local cluster.

The application running in pods has their own IP address, they are given a single DNS name for a set od pods. Managing the connection of these pods to external world is not easy, we use K8 services resources to overcome this challenge. Services are an abstract way to expose an application running on a set of pods.

LoadBalancer

We are using the https://github.com/GoogleCloudPlatform/kubernetes-engine-samples/tree/main/hello-app app for deployment. It's already available publicly as docker image.

Load balancer type of service exposes the pod to external world.




I have created a deployment using below command:
$ kubectl create deployment hello-server --image=us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0
deployment.apps/hello-server created
Check the status using below command:
$ kubectl get all
NAME                                READY   STATUS    RESTARTS   AGE
pod/hello-server-5bd6b6875f-8p2c2   1/1     Running   0          18s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   47m

NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/hello-server   1/1     1            1           18s

NAME                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/hello-server-5bd6b6875f   1         1         1       18s
We will expose the above application running in pods using the load balancer type of service using below command:
$ kubectl expose deployment hello-server --type LoadBalancer --port 80 --target-port 8080
service/hello-server exposed
Check the status using below command:
$ kubectl get all
NAME                                READY   STATUS    RESTARTS   AGE
pod/hello-server-5bd6b6875f-8p2c2   1/1     Running   0          65s

NAME                   TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/hello-server   LoadBalancer   10.111.116.63   localhost     80:30770/TCP   5s
service/kubernetes     ClusterIP      10.96.0.1       <none>        443/TCP        47m

NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/hello-server   1/1     1            1           65s

NAME                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/hello-server-5bd6b6875f   1         1         1       65s
If you were running it on GCP then the service was accessible on External-Ip of the service. But here we are running on minikube so you need a tunnel. 

Run minikube tunnel  in a new terminal: 

$  minikube tunnel
🏃  Starting tunnel for service hello-server.
Open the browser  http://localhost/ :



To delete the service:

$ kubectl delete svc hello-server
service "hello-server" deleted

NodePort

For exposing the NodePort Service for a set of pods where each port listens on `targetPort` and maps it to `port` :
$ kubectl expose deployment hello-server --type NodePort --port 8080
service/hello-server exposed
Use port-forward to see the response in the browser:
$ kubectl port-forward service/hello-server 7080:8080
Forwarding from 127.0.0.1:7080 -> 8080
Forwarding from [::1]:7080 -> 8080

Open the browser  http://localhost:7080/ :

To delete the service:

$ kubectl delete svc hello-server
service "hello-server" deleted

ClusterIP


It is the default type. To configure service, use below command:

$ kubectl expose deployment hello-server --type=ClusterIP --port=80 --target-port 8080
service/hello-server exposed

Check the status of the service:

$ kubectl get svc
NAME           TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
hello-server   ClusterIP   10.98.172.12   <none>        80/TCP    15s
We can access the service via browser by ingress manifest. But you can check via entering the pod.

$ kubectl get po
NAME                            READY   STATUS    RESTARTS   AGE
hello-server-5bd6b6875f-8p2c2   1/1     Running   0          37m
Use the pod name, to login into the pod:
$ kubectl exec -it hello-server-5bd6b6875f-8p2c2 -- sh
/ #
There is no curl in the container. Install curl:
$ apk add --no-cache curl
/ # apk add --no-cache curl
fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/community/x86_64/APKINDEX.tar.gz
(1/5) Installing ca-certificates (20191127-r5)
(2/5) Installing brotli-libs (1.0.9-r5)
(3/5) Installing nghttp2-libs (1.43.0-r0)
(4/5) Installing libcurl (7.79.1-r0)
(5/5) Installing curl (7.79.1-r0)
Executing busybox-1.33.1-r3.trigger
Executing ca-certificates-20191127-r5.trigger
OK: 8 MiB in 19 packages
In the container, make a request to your Service by using your cluster IP address and port 80. Notice that 80 is the value of the port field of your Service. This is the port that you use as a client of the Service.

$ kubectl exec -it hello-server-5bd6b6875f-8p2c2 -- sh
/ # curl http://10.98.172.12/
Hello, world!
Version: 1.0.0
Hostname: hello-server-5bd6b6875f-8p2c2
To delete the service:

$ kubectl delete svc hello-server
service "hello-server" deleted

You can create an ingress and access it.

HeadLess

I will soon



Happy Coding !!!