kubectl apply -f https:\/\/raw.githubusercontent.com\/kubernetes\/ingress-nginx\/main\/deploy\/static\/provider\/cloud\/deploy.yaml<\/code><\/pre>\n\n\n\nSample installation output;<\/p>\n\n\n\n
namespace\/ingress-nginx created\nserviceaccount\/ingress-nginx created\nserviceaccount\/ingress-nginx-admission created\nrole.rbac.authorization.k8s.io\/ingress-nginx created\nrole.rbac.authorization.k8s.io\/ingress-nginx-admission created\nclusterrole.rbac.authorization.k8s.io\/ingress-nginx created\nclusterrole.rbac.authorization.k8s.io\/ingress-nginx-admission created\nrolebinding.rbac.authorization.k8s.io\/ingress-nginx created\nrolebinding.rbac.authorization.k8s.io\/ingress-nginx-admission created\nclusterrolebinding.rbac.authorization.k8s.io\/ingress-nginx created\nclusterrolebinding.rbac.authorization.k8s.io\/ingress-nginx-admission created\nconfigmap\/ingress-nginx-controller created\nservice\/ingress-nginx-controller created\nservice\/ingress-nginx-controller-admission created\ndeployment.apps\/ingress-nginx-controller created\njob.batch\/ingress-nginx-admission-create created\njob.batch\/ingress-nginx-admission-patch created\ningressclass.networking.k8s.io\/nginx created\nvalidatingwebhookconfiguration.admissionregistration.k8s.io\/ingress-nginx-admission created\n<\/code><\/pre>\n\n\n\nThis will deploy Ingress controller as a Deployment resource in the ingress-nginx<\/strong> namespace.<\/p>\n\n\n\nkubectl get all -n ingress-nginx<\/code><\/pre>\n\n\n\nNAME READY STATUS RESTARTS AGE\npod\/ingress-nginx-admission-create-kl4mr 0\/1 Completed 0 85s\npod\/ingress-nginx-admission-patch-4zqjb 0\/1 Completed 1 85s\npod\/ingress-nginx-controller-7d4db76476-5h4cq 1\/1 Running 0 85s\n\nNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE\nservice\/ingress-nginx-controller LoadBalancer 10.108.54.181 <pending> 80:30580\/TCP,443:30896\/TCP 86s\nservice\/ingress-nginx-controller-admission ClusterIP 10.103.61.173 <none> 443\/TCP 85s\n\nNAME READY UP-TO-DATE AVAILABLE AGE\ndeployment.apps\/ingress-nginx-controller 1\/1 1 1 85s\n\nNAME DESIRED CURRENT READY AGE\nreplicaset.apps\/ingress-nginx-controller-7d4db76476 1 1 1 85s\n\nNAME STATUS COMPLETIONS DURATION AGE\njob.batch\/ingress-nginx-admission-create Complete 1\/1 5s 85s\njob.batch\/ingress-nginx-admission-patch Complete 1\/1 6s 85s\n<\/code><\/pre>\n\n\n\nTo render the controller details;<\/p>\n\n\n\n
kubectl describe deployment ingress-nginx-controller -n ingress-nginx<\/code><\/pre>\n\n\n\n\nName: ingress-nginx-controller\nNamespace: ingress-nginx\nCreationTimestamp: Mon, 19 Aug 2024 19:35:45 +0000\nLabels: app.kubernetes.io\/component=controller\n app.kubernetes.io\/instance=ingress-nginx\n app.kubernetes.io\/name=ingress-nginx\n app.kubernetes.io\/part-of=ingress-nginx\n app.kubernetes.io\/version=1.11.2\nAnnotations: deployment.kubernetes.io\/revision: 1\nSelector: app.kubernetes.io\/component=controller,app.kubernetes.io\/instance=ingress-nginx,app.kubernetes.io\/name=ingress-nginx\nReplicas: 1 desired | 1 updated | 1 total | 1 available | 0 unavailable\nStrategyType: RollingUpdate\nMinReadySeconds: 0\nRollingUpdateStrategy: 1 max unavailable, 25% max surge\nPod Template:\n Labels: app.kubernetes.io\/component=controller\n app.kubernetes.io\/instance=ingress-nginx\n app.kubernetes.io\/name=ingress-nginx\n app.kubernetes.io\/part-of=ingress-nginx\n app.kubernetes.io\/version=1.11.2\n Service Account: ingress-nginx\n Containers:\n controller:\n Image: registry.k8s.io\/ingress-nginx\/controller:v1.11.2@sha256:d5f8217feeac4887cb1ed21f27c2674e58be06bd8f5184cacea2a69abaf78dce\n Ports: 80\/TCP, 443\/TCP, 8443\/TCP\n Host Ports: 0\/TCP, 0\/TCP, 0\/TCP\n SeccompProfile: RuntimeDefault\n Args:\n \/nginx-ingress-controller\n --publish-service=$(POD_NAMESPACE)\/ingress-nginx-controller\n --election-id=ingress-nginx-leader\n --controller-class=k8s.io\/ingress-nginx\n --ingress-class=nginx\n --configmap=$(POD_NAMESPACE)\/ingress-nginx-controller\n --validating-webhook=:8443\n --validating-webhook-certificate=\/usr\/local\/certificates\/cert\n --validating-webhook-key=\/usr\/local\/certificates\/key\n --enable-metrics=false\n Requests:\n cpu: 100m\n memory: 90Mi\n Liveness: http-get http:\/\/:10254\/healthz delay=10s timeout=1s period=10s #success=1 #failure=5\n Readiness: http-get http:\/\/:10254\/healthz delay=10s timeout=1s period=10s #success=1 #failure=3\n Environment:\n POD_NAME: (v1:metadata.name)\n POD_NAMESPACE: (v1:metadata.namespace)\n LD_PRELOAD: \/usr\/local\/lib\/libmimalloc.so\n Mounts:\n \/usr\/local\/certificates\/ from webhook-cert (ro)\n Volumes:\n webhook-cert:\n Type: Secret (a volume populated by a Secret)\n SecretName: ingress-nginx-admission\n Optional: false\n Node-Selectors: kubernetes.io\/os=linux\n Tolerations: <none>\nConditions:\n Type Status Reason\n ---- ------ ------\n Available True MinimumReplicasAvailable\n Progressing True NewReplicaSetAvailable\nOldReplicaSets: <none>\nNewReplicaSet: ingress-nginx-controller-7d4db76476 (1\/1 replicas created)\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal ScalingReplicaSet 3m49s deployment-controller Scaled up replica set ingress-nginx-controller-7d4db76476 to 1\n<\/code><\/pre>\n\n\n\nCreate a Deployment and Service<\/h4>\n\n\n\n
In our environment, we have a custom web app with an home<\/strong> page and an about-us<\/strong> page running under ingress<\/strong> namespace.<\/p>\n\n\n\ncat deployment.yaml<\/code><\/pre>\n\n\n\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n name: nginx-deployment\n namespace: ingress\nspec:\n replicas: 1\n selector:\n matchLabels:\n app: nginx\n template:\n metadata:\n labels:\n app: nginx\n spec:\n containers:\n - name: nginx\n image: nginx:latest\n volumeMounts:\n - name: nginx-conf\n mountPath: \/etc\/nginx\/conf.d\n - name: index-html\n mountPath: \/usr\/share\/nginx\/html\n - name: about-us-html\n mountPath: \/usr\/share\/nginx\/html\/about\n volumes:\n - name: nginx-conf\n configMap:\n name: nginx.conf\n - name: index-html\n configMap:\n name: index.html\n - name: about-us-html\n configMap:\n name: about-us.html \n---\napiVersion: v1\nkind: Service\nmetadata:\n name: nginx-service\n namespace: ingress\nspec:\n selector:\n app: nginx\n ports:\n - protocol: TCP\n port: 80\n targetPort: 80\n type: LoadBalancer\n<\/code><\/pre>\n\n\n\nThe deployment is exposed via a Load balancer service type.<\/p>\n\n\n\n
kubectl get all -n ingress<\/code><\/pre>\n\n\n\nNAME READY STATUS RESTARTS AGE\npod\/nginx-deployment-78c6f7dd66-qxdsr 1\/1 Running 0 10s\n\nNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE\nservice\/nginx-service LoadBalancer 10.100.105.43 <pending> 80:31617\/TCP 10s\n\nNAME READY UP-TO-DATE AVAILABLE AGE\ndeployment.apps\/nginx-deployment 1\/1 1 1 10s\n\nNAME DESIRED CURRENT READY AGE\nreplicaset.apps\/nginx-deployment-78c6f7dd66 1 1 1 10s\n<\/code><\/pre>\n\n\n\nAs you can see, the external IP is still pending.<\/p>\n\n\n\n
Within the cluster, we can access the web pages mentioned above as;<\/p>\n\n\n\n
curl 10.100.105.43<\/code><\/pre>\n\n\n\n<!DOCTYPE html>\n<html>\n<head>\n <title>Home Page<\/title>\n<\/head>\n<body>\n <h1>Welcome to the Home Page!<\/h1>\n <p>This is the default custom page served at the root path.<\/p>\n <a href=\"\/about-us\">About Us<\/a>\n<\/body>\n<\/html>\n<\/code><\/pre>\n\n\n\ncurl 10.100.105.43\/about-us<\/code><\/pre>\n\n\n\n<!DOCTYPE html>\n<html>\n<head>\n <title>About Us<\/title>\n<\/head>\n<body>\n <h1>About Us<\/h1>\n <p>This is the custom About Us page.<\/p>\n <a href=\"\/\">Home<\/a>\n<\/body>\n<\/html>\n<\/code><\/pre>\n\n\n\nCreate an Ingress Resource<\/h4>\n\n\n\n
Next, we\u2019ll define an Ingress resource to expose the web server service. This resource will route external traffic to the Nginx service through the load balancer IP.<\/p>\n\n\n\n
You can create an Ingress controller via declarative method using a manifest YAML file or imperatively using kubect create ingress<\/strong> command. Imperative method however does not offer flexibility provided by the declarative method.<\/p>\n\n\n\nTo use a declarative method, proceed as follows.<\/p>\n\n\n\n
cat ingress-resource.yaml<\/code><\/pre>\n\n\n\napiVersion: networking.k8s.io\/v1\nkind: Ingress\nmetadata:\n name: nginx-ingress-resource\n namespace: ingress\nspec:\n rules:\n - host: kifarunix-demo.com\n http:\n paths:\n - path: \/\n pathType: Exact\n backend:\n service:\n name: nginx-service\n port:\n number: 80\n - path: \/about-us\n pathType: Exact\n backend:\n service:\n name: nginx-service\n port:\n number: 80\n<\/code><\/pre>\n\n\n\nApply;<\/p>\n\n\n\n
kubectl apply -f ingress-resource.yaml<\/code><\/pre>\n\n\n\nIf you want to create the ingress via command line, the command line syntax is;<\/p>\n\n\n\n
kubectl create ingress NAME --rule=host\/path=service:port[,tls[=secret]] [options]<\/code><\/pre>\n\n\n\nFor example, to create an ingress like the above via command line;<\/p>\n\n\n\n
kubectl create ingress nginx-ingress-resource -n ingress \\\n\t--rule=\"kifarunix-demo.com\/*=nginx-service:80\" \\\n\t--rule=\"kifarunix-demo.com\/about-us=nginx-service:80\"\n<\/code><\/pre>\n\n\n\nRead more on kubectl create ingress –help<\/strong>.<\/p>\n\n\n\nList Ingress resources on your namespace;<\/p>\n\n\n\n
kubectl get ingress -n ingress\n<\/code><\/pre>\n\n\n\n-n ingress<\/strong> specifies our namespace.<\/p>\n\n\n\nNAME CLASS HOSTS ADDRESS PORTS AGE\nnginx-ingress-resource <none> kifarunix-demo.com 80 15s\n<\/code><\/pre>\n\n\n\nFor the Ingress resource to work, you need to point the domain of your application to the external IP address of your Ingress controller.<\/p>\n\n\n\n
To find the external IP address assigned to your Ingress Controller service, run the following command to list the services in the ingress-nginx<\/strong> namespace.<\/p>\n\n\n\nkubectl get service -n ingress-nginx<\/code><\/pre>\n\n\n\nNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE\ningress-nginx-controller LoadBalancer 10.108.54.181 <pending> 80:30580\/TCP,443:30896\/TCP 36m\ningress-nginx-controller-admission ClusterIP 10.103.61.173 <none> 443\/TCP 36m\n<\/code><\/pre>\n\n\n\nFrom the output;<\/p>\n\n\n\n
\n- NAME<\/strong>: This column shows the name of the Kubernetes service.\n
\ningress-nginx-controller<\/code><\/strong>: This is the primary service for the Ingress Controller. It\u2019s responsible for handling incoming HTTP\/HTTPS traffic and routing it based on Ingress rules.<\/li>\n\n\n\ningress-nginx-controller-admission<\/code><\/strong>: This service is typically used for admission control purposes, such as validating or mutating Ingress resources before they are applied.<\/li>\n<\/ul>\n<\/li>\n\n\n\n- TYPE<\/strong>: This indicates the type of the service, which determines how it is exposed.\n
\nLoadBalancer<\/code><\/strong>: This type of service provisions a load balancer in cloud environments (like AWS, Azure, GCP) that provides an external IP address to route traffic to your service.<\/li>\n\n\n\nClusterIP<\/code><\/strong>: This type of service is only accessible within the Kubernetes cluster. It is not exposed to the outside world.<\/li>\n<\/ul>\n<\/li>\n\n\n\n- CLUSTER-IP<\/strong>: This is the internal IP address assigned to the service within the Kubernetes cluster.\n
\n10.108.54.181<\/strong><\/code>: This is the internal IP address for the ingress-nginx-controller<\/code> service.<\/li>\n\n\n\n10.103.61.173<\/code><\/strong>: This is the internal IP address for the ingress-nginx-controller-admission<\/code> service.<\/li>\n<\/ul>\n<\/li>\n\n\n\n- EXTERNAL-IP<\/strong>: This is the IP address that is exposed outside the Kubernetes cluster (for LoadBalancer services).\n
\n<pending><\/code><\/strong>: Indicates that an external IP address has not yet been assigned. This usually happens when the cloud provider has not yet provisioned the load balancer or assigned an IP. The external IP will be populated once the load balancer is set up. If you are running a local\/on-premise K8S cluster, you won’t get an external IP out of the box.<\/li>\n\n\n\n<none><\/code><\/strong>: This indicates that the service does not have an external IP because it is a ClusterIP<\/code> service and is only accessible within the cluster.<\/li>\n<\/ul>\n<\/li>\n\n\n\n- PORT(S)<\/strong>: Lists the ports that the service is listening on and the corresponding node ports (for
LoadBalancer<\/code> type services).\n\n80:30580\/TCP<\/code><\/strong>: The service is exposing port 80 internally and mapping it to port 30270 on the node.<\/li>\n\n\n\n443:30896\/TCP<\/code><\/strong>: The service is exposing port 443 internally and mapping it to port 32487 on the node.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\nSo, you would typically set the DNS entry of the application domain to point to the value of the EXTERNAL_IP address.<\/p>\n\n\n\n
If you’re running a non-cloud (on-premises or bare-metal) Kubernetes cluster, you won\u2019t have a cloud provider\u2019s load balancer automatically provisioning an external IP address for your LoadBalancer<\/code> service. Instead, you can use NodePort service type to expose your Ingress controller or simply use MetalLB<\/a><\/strong>, a load-balancer implementation for bare metal Kubernetes clusters.<\/p>\n\n\n\nIn this demo, we will use MetalLB<\/strong>. MetalLB allows you to create Kubernetes services of type LoadBalancer<\/code> in clusters that don\u2019t run on a cloud provider.<\/p>\n\n\n\nBefore you can proceed, check the kube-proxy<\/strong> mode from the cluster. iptables<\/strong> is the default mode. For example, run this command on the master node;<\/p>\n\n\n\ncurl localhost:10249\/proxyMode<\/code><\/pre>\n\n\n\nSample output in my case shows, iptables<\/strong>. If the output is IPVS, it shows you are using IPVS to manage and distribute network traffic.<\/p>\n\n\n\nIf IPVS is enabled, you have to edit the kube-proxy<\/strong> to and set strict ARP mode to true.<\/p>\n\n\n\nkubectl get configmap kube-proxy -n kube-system -o yaml | \\\nsed -e \"s\/strictARP: false\/strictARP: true\/\" | \\\nkubectl apply -f - -n kube-system\n<\/code><\/pre>\n\n\n\nNext, proceed to install MetalLB using manifest. Just get the current release version<\/a> and replace the value of VER below;<\/p>\n\n\n\nVER=v0.14.8<\/code><\/pre>\n\n\n\nkubectl apply -f https:\/\/raw.githubusercontent.com\/metallb\/metallb\/${VER}\/config\/manifests\/metallb-native.yaml<\/code><\/pre>\n\n\n\nSample installation output;<\/p>\n\n\n\n
namespace\/metallb-system created\ncustomresourcedefinition.apiextensions.k8s.io\/bfdprofiles.metallb.io created\ncustomresourcedefinition.apiextensions.k8s.io\/bgpadvertisements.metallb.io created\ncustomresourcedefinition.apiextensions.k8s.io\/bgppeers.metallb.io created\ncustomresourcedefinition.apiextensions.k8s.io\/communities.metallb.io created\ncustomresourcedefinition.apiextensions.k8s.io\/ipaddresspools.metallb.io created\ncustomresourcedefinition.apiextensions.k8s.io\/l2advertisements.metallb.io created\ncustomresourcedefinition.apiextensions.k8s.io\/servicel2statuses.metallb.io created\nserviceaccount\/controller created\nserviceaccount\/speaker created\nrole.rbac.authorization.k8s.io\/controller created\nrole.rbac.authorization.k8s.io\/pod-lister created\nclusterrole.rbac.authorization.k8s.io\/metallb-system:controller created\nclusterrole.rbac.authorization.k8s.io\/metallb-system:speaker created\nrolebinding.rbac.authorization.k8s.io\/controller created\nrolebinding.rbac.authorization.k8s.io\/pod-lister created\nclusterrolebinding.rbac.authorization.k8s.io\/metallb-system:controller created\nclusterrolebinding.rbac.authorization.k8s.io\/metallb-system:speaker created\nconfigmap\/metallb-excludel2 created\nsecret\/metallb-webhook-cert created\nservice\/metallb-webhook-service created\ndeployment.apps\/controller created\ndaemonset.apps\/speaker created\nvalidatingwebhookconfiguration.admissionregistration.k8s.io\/metallb-webhook-configuration created\n<\/code><\/pre>\n\n\n\nAs you can see, the LoadBalancer is installed in a separate namespace, metallb-system<\/strong>.<\/p>\n\n\n\nThe MetalLB components that are deployed include:<\/p>\n\n\n\n
\n- The
metallb-system\/controller<\/code> deployment which is the cluster-wide controller that handles IP address assignments.<\/li>\n\n\n\n- The
metallb-system\/speaker<\/code> daemonset which is the component that speaks the protocol(s) of your choice to make the services reachable.<\/li>\n\n\n\n- Service accounts for the controller and speaker, along with the RBAC permissions that the components need to function.<\/li>\n<\/ul>\n\n\n\n
kubectl get pods -n metallb-system<\/code><\/pre>\n\n\n\nNAME READY STATUS RESTARTS AGE\ncontroller-6dd967fdc7-brn6z 1\/1 Running 0 24s\nspeaker-cqms7 1\/1 Running 0 24s\nspeaker-f4d2t 1\/1 Running 0 24s\nspeaker-hjnhq 1\/1 Running 0 24s\nspeaker-lxpts 1\/1 Running 0 24s\nspeaker-wprwg 1\/1 Running 0 24s\nspeaker-xk4rg 1\/1 Running 0 24s\n<\/code><\/pre>\n\n\n\nBy default, MetalLB will allocate IPs from any configured address pool with free addresses.<\/em> As such, you need to configure it to use a pool of IP addresses for load balancing through the use of IPAddressPool<\/strong> resource.<\/p>\n\n\n\nYou also need to define an L2Advertisement<\/code><\/strong> resource to allow MetalLB<\/strong> to broadcast the presence of services with assigned external IPs to devices on the same local network.<\/p>\n\n\n\nWe will create these resources via YAML files as follows;<\/p>\n\n\n\n
cat metallb-resources.yaml<\/code><\/pre>\n\n\n\napiVersion: metallb.io\/v1beta1\nkind: IPAddressPool\nmetadata:\n name: demo-pool\n namespace: metallb-system\nspec:\n addresses:\n - 192.168.122.245-192.168.122.250\n---\napiVersion: metallb.io\/v1beta1\nkind: L2Advertisement\nmetadata:\n name: demo-l2\n namespace: metallb-system\nspec:\n ipAddressPools:\n - demo-pool\n<\/code><\/pre>\n\n\n\nAdjust the name and address space for the pool as well as the L2Advertisement<\/strong> name.<\/p>\n\n\n\nLet’s apply the resources;<\/p>\n\n\n\n
kubectl apply -f metallb-resources.yaml<\/code><\/pre>\n\n\n\nNow, if you check our Nginx ingress controller LoadBalancer, it should be having an EXTERNAL IP;<\/p>\n\n\n\n
kubectl get service -n ingress-nginx<\/code><\/pre>\n\n\n\nNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE\ningress-nginx-controller LoadBalancer 10.108.54.181 192.168.122.246 80:30580\/TCP,443:30896\/TCP 21h\ningress-nginx-controller-admission ClusterIP 10.103.61.173 <none> 443\/TCP 21h\n<\/code><\/pre>\n\n\n\n