Friday, June 18, 2021

Kubernetes for Developers #17: Expose service using Kubernetes Ingress

In the previous article(Kubernetes for Developers #16: Kubernetes Service Types - ClusterIP, NodePort, LoadBalancer and ExternalName) we discussed “LoadBalancer” service type would be the most preferable technique to expose service outside the cluster. However, it requires one LoadBalancer per service with public IP address. It is cost inefficient if you have bunch of services needs to be exposed outside the cluster.

We can solve this problem by implementing single Kubernetes Ingress resource with multiple service mapping. 

K8 Ingress works based on URL based HTTP routing. So, we can map multiple services to multiple HTTP URLs. When a client sends an HTTP request to the Ingress, the URL host and path in the request determine which service the request is supposed to navigate.

Kubernetes Ingress configuration has two parts.

1. Ingress Resource: It is YAML manifest file which contain set of HTTP routing rules mapped to services

2. Ingress Controller: It is K8 Pod running inside the cluster. It is responsible to process Ingress Resource YAML file and route the request to appropriate service. Ingress controller Pods are not started automatically with a cluster. Different K8 environments use different implementations of the controller. 

Note: DNS name should resolve to the IP address of Ingress controller to access K8 service from the ingress controller.

As per diagram,
  • when you initiate a http call from the browser (i.e. shop.com/cart), it performs DNS lookup and returns a IP address of the Ingress controller. So, make sure that DNS name should map to IP address of the Ingress controller.
  • Browser sends HTTP request to the Ingress controller, it determines the service which is mapped to the given http URL based on HTTP headers
  • Ingress controller find the Pod IPs through Endpoints associated with the service
  • HTTP request will be forwarded to one of the Pod. 
  • Ingress controller uses the service to find Pod IPs, then the controller will process the request. 
Enable the Nginx Ingress controller

1. run the following kubectl command to enable Nginx Ingress controller on Docker Desktop

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.47.0/deploy/static/provider/cloud/deploy.yaml


run the following command if you are using minikube
$ minikube addons enable ingress

2. run the following kubectl command to check the “ingress-nginx-controller” Pod running status

$ kubectl get pods -n ingress-nginx
NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-controller-84dcb9867d-dqc9h   1/1     Running     0          11h


Create Kubernetes Deployment

Before creating an Ingress, first create two different versions of helloworld app using K8 Deployment and service

apiVersionapps/v1
kindDeployment
metadata:
  namehello-v1-deployment
spec:
  replicas1
  selector:
    matchLabels:
      appv1-web
  template:
    metadata:
      labels:
        appv1-web
    spec:
      containers:
        - namehello-v1
          imagegcr.io/google-samples/hello-app:1.0
          ports:
            - containerPort8080
---
apiVersionv1
kindService
metadata:
  namehello-v1-service
spec:
  typeClusterIP
  selector:
    appv1-web
  ports:
    - protocolTCP
      port8080
      targetPort8080

// save above yaml manifest as hello-v1.yaml and run below command
$ kubectl apply -f hello-v1.yaml
deployment.apps/hello-v1-deployment created
service/hello-v1-service created


apiVersionapps/v1
kindDeployment
metadata:
  namehello-v2-deployment
spec:
  replicas1
  selector:
    matchLabels:
      appv2-web
  template:
    metadata:
      labels:
        appv2-web
    spec:
      containers:
        - namehello-v2
          imagegcr.io/google-samples/hello-app:2.0
          ports:
            - containerPort8080
---
apiVersionv1
kindService
metadata:
  namehello-v2-service
spec:
  typeClusterIP
  selector:
    appv2-web
  ports:
    - protocolTCP
      port8080
      targetPort8080



// save above yaml manifest as hello-v2.yaml and run below command
$ kubectl apply -f hello-v2.yaml
deployment.apps/hello-v2-deployment created
service/hello-v2-service created

Create an Ingress YAML manifest to expose multiple services on single host

apiVersionextensions/v1beta1
kindIngress
metadata:
  namehello-ingress
spec:
  rules:
    - hostshop.com
      http:
        paths:
          - path/product
            backend:
              serviceNamehello-v1-service
              servicePort8080
          - path/order
            backend:
              serviceNamehello-v2-service
              servicePort8080

// create an ingress resource
$ kubectl apply -f hello-ingress.yaml
ingress.extensions/hello-ingress created

// get an ingress details
$ kubectl get ingress
NAME            CLASS    HOSTS      ADDRESS     PORTS   AGE
hello-ingress   <none>   shop.com   localhost   80      73s

// open windows host file (C:\Windows\System32\drivers\etc) and map shop.com to localhost
127.0.0.1 shop.com

// make curl command to shop.com/product to get hello-v1 app details
$ curl shop.com/product
Hello, world!
Version: 1.0.0
Hostname: hello-v1-deployment-855c95579b-d99pp

// make curl command to shop.com/order to get hello-v2 app details
$ curl shop.com/order
Hello, world!
Version: 2.0.0
Hostname: hello-v2-deployment-5fc58f68df-hwgxx


Create an Ingress YAML manifest to expose multiple services on sub domains
apiVersionextensions/v1beta1
kindIngress
metadata:
  namehello-ingress
spec:
  rules:
    - hostproduct.shop.com
      http:
        paths:
          - path/
            backend:
              serviceNamehello-v1-service
              servicePort8080
    - hostorder.shop.com
      http:
        paths:
          - path/
            backend:
              serviceNamehello-v2-service
              servicePort8080


// create an ingress resource to map multiple sub domains
$ kubectl apply -f hello-multi-domain-ingress.yaml
ingress.extensions/hello-ingress created

// get an ingress details
$ kubectl get ingress
NAME            CLASS    HOSTS                             ADDRESS   PORTS   AGE
hello-ingress   <none>   product.shop.com,order.shop.com             80      6s

// open windows host file (C:\Windows\System32\drivers\etc) and map shop.com to localhost
127.0.0.1 product.shop.com
127.0.0.1 order.shop.com

// make curl command to product.shop.com to get hello-v1 app details
$ curl product.shop.com
Hello, world!
Version: 1.0.0
Hostname: hello-v1-deployment-855c95579b-d99pp

// make curl command to order.shop.com to get hello-v2 app details
$ curl order.shop.com
Hello, world!
Version: 2.0.0
Hostname: hello-v2-deployment-5fc58f68df-hwgxx

Kubernetes for Developers Journey.

Happy Coding :)

Friday, June 11, 2021

Kubernetes for Developers #16: Kubernetes Service Types - ClusterIP, NodePort, LoadBalancer and ExternalName

In the previous article (Kubernetes for Developers #15: Kubernetes Service YAML manifest in-detail) we have seen how to create K8 Service and access the Pods inside the cluster. 

There are four types of services available to access the Pods in and outside cluster.

1. ClusterIP

  • It exposes the service within the Kubernetes cluster only.
  • Only Pods within the K8 Cluster can communicate using this service. You cannot access directly from the browser using Service IP.   
  • Kubernetes controller creates unique virtual IP address (i.e., which is called ClusterIP) whenever the service is created.
  • This is the default serviceType

As per above diagram, 
  • The K8 service "nginx-service" has been created with "ClusterIP" type. 
  • nginx-service mapped to Pod labels app:nginx
  • nginx-service mapped service port 8081(i.e. Port ), container port 80 (i.e. targetPort
  • As it is "ClusterIP" type, we can't access the service outside the cluster. So, enter into any Pod and make curl using service IP or name or FQDN (<servicename>.<namespace>.svc.cluster.local) i.e. curl http://10.107.40.61:8081 (or) curl http://nginx-service:8081 (or) curl http://nginx-service.default.svc.cluster.local:8081

Create Kubernetes Deployment

Before creating service, first create K8 Deployment and make nginx Pods are up and running with label app:nginx


apiVersionapps/v1
kindDeployment
metadata:
  namenginx-deployment
spec:
  replicas2
  selector:
    matchLabels:
      appnginx
  template:
    metadata:
      labels:
        appnginx
    spec:
      containers:
        - namenginx
          imagenginx:1.14.2
          ports:
            - containerPort80

// Create a Deployment based on YAML file
$ kubectl apply -f ./nginx-deployment.yaml
deployment.apps/nginx-deployment created

// Display information about all deployments 
$ kubectl get deployments
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   2/2     2            2           64s

// Display information about Pods with labels
$ kubectl get pods --show-labels                                                                             
NAME                                READY   STATUS    RESTARTS   AGE   LABELS                                
nginx-deployment-6b474476c4-8mx72   1/1     Running   0          30h   app=nginx
nginx-deployment-6b474476c4-cdhvb   1/1     Running   0          30h   app=nginx

Create ClusterIP  Service using YAML manifest
apiVersionv1
kindService
metadata:
  namenginx-service
spec:
  typeClusterIP
  selector:
    appnginx
  ports:
    - protocolTCP
      port8081
      targetPort80


// Create a Service using YAML file
$ kubectl apply -f ./nginx-service-cluserIP.yaml
service/nginx-service created 

// Display information about Services
$ kubectl get services
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
nginx-service   ClusterIP   10.105.224.36   <none>        8081/TCP    112s

K8 Endpoint object will be created automatically when the Service is created with same service name. It will track all the Pod IPs where label as app:nginx
// Display information about the Endpoints 
$ kubectl get endpoints
NAME            ENDPOINTS                                AGE
nginx-service   10.1.1.61:80,10.1.1.63:80   19m

NAME: Name of the Endpoint which is same as Service name
ENDPOINTS: list all the Pod IPs where label as app:nginx

By default, Kubernetes Service IP address (i.e. Cluster-IP) and Port will be accessed within the cluster only. So, enter into one of the nginx Pod and make curl using service IP address/name and Port to view nginx webpage
// Enter into nginx Pod shell
$ kubectl exec -it nginx-deployment-6b474476c4-8mx72 -- bin/bash

// update packages
# apt-get update

// install curl
# apt-get install curl

// make curl using service name(or IP ) and port to access nginx webpage
# curl http://nginx-service:8081 
(or)
# curl http://10.105.224.36:8081

There is another way to test service by port-forwarding from service port to local computer port
// use kubectl port-forward to test the service in local computer
$ kubectl port-forward service/nginx-service 8081:8081
Forwarding from 127.0.0.1:8081 -> 80
Forwarding from [::1]:8081 -> 80

// Go to browser and hit http://localhost:8081 to view nginx webpage

2. NodePort
  • It exposes the service both in and outside the cluster
  • It exposes the service on each Worker Node’s IP at a static port (i.e., which is called NodePort).
  • A ClusterIP Service will be created automatically whenever the NodePort service is created. It means, The external traffic enter into the cluster using <NodeIP>:<NodePort> (ex: 100.72.40.61 : 30010) and it direct traffic to the ClusterIP
  • NodePort must be within the range from 30000-32767

As per above diagram, 
  • The K8 service "nginx-service" has been created with "NodePort" type. 
  • nginx-service mapped to Pod labels app:nginx
  • nginx-service mapped service port 8081(i.e. Port ), container port 80 (i.e. targetPort ) and node Port 30010
  • As it is "NodePort" type, we can access the service both inside and outside the cluster .
  • To access inside cluster, enter into any Pod and make curl using service IP or name or FQDN (<servicename>.<namespace>.svc.cluster.local) i.e. curl http://10.107.40.61:8081 (or) curl http://nginx-service:8081 (or) curl http://nginx-service.default.svc.cluster.local:8081
  • To access outside cluster, go to browser and hit http://<nodeIP>:<nodePort> i.e. http://100.17.20.31:30010
Create NodePort  Service using YAML manifest
apiVersionv1
kindService
metadata:
  namenginx-service
spec:
  typeNodePort
  selector:
    appnginx
  ports:
    - protocolTCP
      port8081
      targetPort80
      nodePort30010


// Create a Service using YAML file
$ kubectl apply -f ./nginx-service-nodeport.yaml
service/nginx-service created 

// Display information about Services
$ kubectl get services
NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/nginx-service   NodePort    10.105.224.36   <none>        8081:30010/TCP   15h

K8 Endpoint object will be created automatically when the Service is created with same service name. It will track all the Pod IPs where label as app:nginx
// Display information about the Endpoints 
$ kubectl get endpoints
NAME            ENDPOINTS                                AGE
nginx-service   10.1.1.61:80,10.1.1.63:80   19m

NAME: Name of the Endpoint which is same as Service name
ENDPOINTS: list all the Pod IPs where label as app:nginx

Go to browser and enter http://100.17.20.31:30010 to view nginx webpage.
Go to browser and enter http://localhost:30010 if you are testing locally using docker-desktop or minikube.

3. LoadBalancer
  • It exposes the service both in and outside the cluster
  • It exposes the service externally using cloud providers load balancer.
  • NodePort and ClusterIP services will be created automatically whenever the LoadBalancer service is created.
  • The LoadBalancer service redirects traffic to the node port across all the nodes.
  • The external clients connect to the service through load balancer IP
  • The is the most preferable approach to expose service outside the cluster
As per above diagram, 
  • The K8 service "nginx-service" has been created with "LoadBalancer" type. 
  • nginx-service mapped to Pod labels app:nginx
  • nginx-service mapped service port 8081(i.e. Port ), container port 80 (i.e. targetPort ) and node Port 30010
  • As it is "LoadBalancer" type, we can access the service both inside and outside the cluster .
  • To access inside cluster, enter into any Pod and make curl using service IP or name or FQDN (<servicename>.<namespace>.svc.cluster.local) i.e. curl http://10.107.40.61:8081 (or) curl http://nginx-service:8081 (or) curl http://nginx-service.default.svc.cluster.local:8081
  • To access outside cluster, go to browser and hit http://<loadBalancerIP> i.e. http://2343260762.us-east-1.elb.amazonaws.com
Create LoadBalancer  Service using YAML manifest
apiVersionv1
kindService
metadata:
  namenginx-service
spec:
  typeLoadBalancer
  selector:
    appnginx
  ports:
    - protocolTCP
      port8081
      targetPort80
      nodePort30010


// Create a Service using YAML file
$ kubectl apply -f ./nginx-service-lb.yaml
service/nginx-service created 

// Display information about Services
NAME                    TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/nginx-service   LoadBalancer   10.105.224.36   2343260762.us-east-1.elb.amazonaws.com     8081:30010/TCP   2h

Go to browser and enter http://2343260762.us-east-1.elb.amazonaws.com to view nginx webpage.
Go to browser and enter http://localhost:30010 if you are testing locally using docker-desktop or minikube.

4. ExternalName
  • It maps service to a DNS name , typically domain/subdomain name (ex: foo.example.com )
  • It will redirect a request to domain specified in the externalName
  • There won't be any difference while accessing service inside Pod. (i.e it should be external-service.default.svc.cluster.local )
apiVersionv1
kindService
metadata:
  nameexternal-service
spec:
  typeExternalName
  externalNameexample.api.com

// Create a Service using YAML file
$ kubectl apply -f ./external-service.yaml
service/external-service created 

// Display information about Services
NAME                       TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)          AGE
service/external-service   ExternalName   <none>          example.api.com   <none>           9m31s


Kubernetes for Developers Journey.

Happy Coding :)

Sunday, June 6, 2021

Kubernetes for Developers #15: Kubernetes Service YAML manifest in-detail

In general, we use application IP address and Port to give access to the external world. However, In Kubernetes world, we are deploying our applications as K8 Pods and it has following limitations.
  • Pods are designed to be ephemeral in nature which means they can be destroyed and re-created with new IP address at any time. Hence, relaying on Pod IP address will not help us.
  • Kubernetes assigns an IP address to a Pod after the pod has been scheduled to a node and before getting started. Hence, we cannot know the Pod IP address prior. 
  • As we run multiple Pods of same application, all Pods should be accessible through a single IP address to the external world.
Kubernetes Service comes into the picture to solve the above problems.

Kubernetes Service provides a single entry or network access to a group of Pods. Each service has an IP address and port that never change while the service exists. All the Pods can communicate with other Pods within the cluster or outside cluster using K8 Service.

  1. Kubernetes Service find the Pods based on their labels which is similar to K8 Deployment matchLabels.
  2. Once service is created, All the Pods in the cluster can communicate with each other using K8 Service IP address or Service name
  3. K8 Service can be created using YAML file or ‘kubectl expose’ command
  4. Internally K8 Endpoints keep track of all selected Pod IPs and these endpoints will be attached to the Service
  5. Kube-proxy module is used to assign virtual IP address for all K8 services.
  6. K8 Service is responsible for enabling network access to set of Pods where as K8 Deployment is responsible for keeping set of Pods running
  7. K8 Service can be created with or without a Pod selector
  8. Once service is created, Kubernetes controller will create a resource called “Endpoints” with the same service name automatically. This “Endpoints” resource will keep track all the selected Pod IPs.
  9. K8 Service supports exposing more than one port from the application/container
  10. K8 Service port can be any number used to map targetPort. Try to use same number for both port and targetPort to avoid confusion.
Create Kubernetes Deployment
Before creating service, first create K8 Deployment and make nginx Pods are up and running with label app:nginx

Read complete article on k8 deployment Kubernetes for Developers #14: Kubernetes Deployment YAML manifest in-detail

apiVersionapps/v1
kindDeployment
metadata:
  namenginx-deployment
spec:
  replicas3
  selector:
    matchLabels:
      appnginx
  template:
    metadata:
      labels:
        appnginx
    spec:
      containers:
        - namenginx
          imagenginx:1.14.2
          ports:
            - containerPort80


// Create a Deployment based on YAML file
$ kubectl apply -f ./nginx-deployment.yaml
deployment.apps/nginx-deployment created

// Display information about all deployments 
$ kubectl get deployments
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   2/3     2            2           64s

// Display information about Pods with labels
$ kubectl get pods --show-labels                                                                             
NAME                                READY   STATUS    RESTARTS   AGE   LABELS                                
nginx-deployment-6b474476c4-8mx72   1/1     Running   0          30h   app=nginx
nginx-deployment-6b474476c4-cdhvb   1/1     Running   0          30h   app=nginx
nginx-deployment-6b474476c4-gw975   1/1     Running   0          30h   app=nginx

Create Kubernetes Service
K8 Service will be created in following two ways

1. Create  Service using YAML manifest

apiVersionv1
kindService
metadata:
  namenginx-service
spec:
  selector:
    appnginx
  ports:
    - protocolTCP
      port8081
      targetPort80


// Create a Service using YAML file
$ kubectl apply -f ./nginx-service.yaml
service/nginx-service created  

// Display information about Services
$ kubectl get services
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
nginx-service   ClusterIP   10.105.224.36   <none>        8081/TCP    112s

K8 Endpoint object will be created automatically when the Service is created with same service name. It will track all the Pod IPs where label as app:nginx
// Display information about the Endpoints 
$ kubectl get endpoints
NAME            ENDPOINTS                                AGE
nginx-service   10.1.1.68:80,10.1.1.69:80,10.1.1.70:80   19m

NAME: Name of the Endpoint which is same as Service name
ENDPOINTS: list all the Pod IPs where label as app:nginx

By default, Kubernetes Service IP address (i.e. Cluster-IP) and Port will be accessed within the cluster only. So, enter into one of the nginx Pod and make curl using service IP address/name and Port to view nginx webpage
// Enter into nginx Pod shell
$ kubectl exec -it nginx-deployment-6b474476c4-8mx72 -- bin/bash

// update packages
# apt-get update

// install curl
# apt-get install curl

// make curl using service name(or IP ) and port to access nginx webpage
# curl http://nginx-service:8081
(or)
# curl http://10.105.224.36:8081

There is another way to test service by port-forwarding from service port to local computer port
// use kubectl port-forward to test the service in local computer
$ kubectl port-forward service/nginx-service 8081:8081
Forwarding from 127.0.0.1:8081 -> 80
Forwarding from [::1]:8081 -> 80

// Go to browser and hit http://localhost:8081 to view nginx webpage


2. Create  Service using kubectl expose command

$ kubectl expose deployment nginx-deployment --name=nginx-service --port=8081 --target-port=80
service/nginx-service exposed

Kubernetes for Developers Journey.

Happy Coding :)