AWS Load Balancer Controller is a controller to help manage Elastic Load Balancers for a Kubernetes cluster.
The controller can provision the following resources:
-
An AWS Application Load Balancer when you create a Kubernetes
Ingress. -
An AWS Network Load Balancer when you create a Kubernetes
Serviceof typeLoadBalancer.
Application Load Balancers work at L7 of the OSI model, allowing you to expose Kubernetes service using ingress rules, and supports external-facing traffic. Network load balancers work at L4 of the OSI model, allowing you to leverage Kubernetes Services to expose a set of pods as an application network service.
The controller enables you to simplify operations and save costs by sharing an Application Load Balancer across multiple applications in your Kubernetes cluster.
The AWS Load Balancer Controller has already been installed in our cluster, so we can get started creating resources.
Kubernetes uses services to expose pods outside of a cluster. One of the most popular ways to use services in AWS is with the LoadBalancer type. With a simple YAML file declaring your service name, port, and label selector, the cloud controller will provision a load balancer for you automatically.
apiVersion: v1
kind: Service
metadata:
name: search-svc # the name of our service
spec:
type: loadBalancer
selector:
app: SearchApp # pods are deployed with the label app=SearchApp
ports:
- port: 80
This is great because of how simple it is to put a load balancer in front of your application. The service spec has been extended over the years with annotations and additional configuration. A second option is to use an ingress rule and an ingress controller to route external traffic into Kubernetes pods.
In this chapter we'll demonstrate how to expose an application running in the EKS cluster to the Internet using a layer 4 Network Load Balancer.
We can confirm our microservices are only accessible internally by taking a look at the current Service resources in the cluster:
~$kubectl get svc -l app.kubernetes.io/created-by=eks-workshop -A
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
assets assets ClusterIP 172.20.119.246 <none> 80/TCP 1h
carts carts ClusterIP 172.20.180.149 <none> 80/TCP 1h
carts carts-dynamodb ClusterIP 172.20.92.137 <none> 8000/TCP 1h
catalog catalog ClusterIP 172.20.83.84 <none> 80/TCP 1h
catalog catalog-mysql ClusterIP 172.20.181.252 <none> 3306/TCP 1h
checkout checkout ClusterIP 172.20.77.176 <none> 80/TCP 1h
checkout checkout-redis ClusterIP 172.20.32.208 <none> 6379/TCP 1h
orders orders ClusterIP 172.20.146.72 <none> 80/TCP 1h
orders orders-mysql ClusterIP 172.20.54.235 <none> 3306/TCP 1h
rabbitmq rabbitmq ClusterIP 172.20.107.54 <none> 5672/TCP,4369/TCP,25672/TCP,15672/TCP 1h
ui ui ClusterIP 172.20.62.119 <none> 80/TCP 1h
All of our application components are currently using ClusterIP services, which only allows access to other workloads in the same Kubernetes cluster. In order for users to access our application we need to expose the ui application, and in this example we'll do so using a Kubernetes Service of type LoadBalancer.
First, lets take a closer look at the current specification of the Service for the ui component:
~$kubectl -n ui describe service ui
Name: ui
Namespace: ui
Labels: app.kubernetes.io/component=service
app.kubernetes.io/created-by: eks-workshop
app.kubernetes.io/instance=ui
app.kubernetes.io/managed-by=Helm
app.kubernetes.io/name=ui
helm.sh/chart=ui-0.0.1
Annotations: <none>
Selector: app.kubernetes.io/component=service,app.kubernetes.io/instance=ui,app.kubernetes.io/name=ui
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 172.20.62.119
IPs: 172.20.62.119
Port: http 80/TCP
TargetPort: http/TCP
Endpoints: 10.42.105.38:8080
Session Affinity: None
Events: <none>
As we saw earlier this is currently using a type ClusterIP and our task in this module to is change this so that the retail store user interface is accessible over the public Internet.
Creating the load balancer
Let's create an additional Service that provisions a load balancer with the following kustomization:
~/environment/eks-workshop/modules/exposing/load-balancer/nlb/nlb.yaml
apiVersion: v1
kind: Service
metadata:
name: ui-nlb
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: external
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: instance
namespace: ui
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
name: http
selector:
app.kubernetes.io/name: ui
app.kubernetes.io/instance: ui
app.kubernetes.io/component: service
This Service will create a Network Load Balancer that listens on port 80 and forwards connections to the ui Pods on port 8080. An NLB is a layer 4 load balancer that on our case operates at the TCP layer.
~$kubectl apply -k ~/environment/eks-workshop/modules/exposing/load-balancer/nlb
Let's inspect the Service resources for the ui application again:
~$kubectl get service -n ui
We see two separate resources, with the new ui-nlb entry being of type LoadBalancer. Most importantly note it has an "external IP" value, this the DNS entry that can be used to access our application from outside the Kubernetes cluster.
The NLB will take several minutes to provision and register its targets so take some time to inspect the load balancer resources the controller has created.
First, take a look at the load balancer itself:
~$aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-ui-uinlb`) == `true`]'
[
{
"LoadBalancerArn": "arn:aws:elasticloadbalancing:us-west-2:1234567890:loadbalancer/net/k8s-ui-uinlb-e1c1ebaeb4/28a0d1a388d43825",
"DNSName": "k8s-ui-uinlb-e1c1ebaeb4-28a0d1a388d43825.elb.us-west-2.amazonaws.com",
"CanonicalHostedZoneId": "Z18D5FSROUN65G",
"CreatedTime": "2022-11-17T04:47:30.516000+00:00",
"LoadBalancerName": "k8s-ui-uinlb-e1c1ebaeb4",
"Scheme": "internet-facing",
"VpcId": "vpc-00be6fc048a845469",
"State": {
"Code": "active"
},
"Type": "network",
"AvailabilityZones": [
{
"ZoneName": "us-west-2c",
"SubnetId": "subnet-0a2de0809b8ee4e39",
"LoadBalancerAddresses": []
},
{
"ZoneName": "us-west-2a",
"SubnetId": "subnet-0ff71604f5b58b2ba",
"LoadBalancerAddresses": []
},
{
"ZoneName": "us-west-2b",
"SubnetId": "subnet-0c584c4c6a831e273",
"LoadBalancerAddresses": []
}
],
"IpAddressType": "ipv4"
}
]
What does this tell us?
-
The NLB is accessible over the public internet
-
It uses the public subnets in our VPC
We can also inspect the targets in the target group that was created by the controller:
~$ALB_ARN=$(aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-ui-uinlb`) == `true`].LoadBalancerArn' | jq -r '.[0]')
~$TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN | jq -r '.TargetGroups[0].TargetGroupArn')
~$aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN
{
"TargetHealthDescriptions": [
{
"Target": {
"Id": "i-06a12e62c14e0c39a",
"Port": 31338
},
"HealthCheckPort": "31338",
"TargetHealth": {
"State": "healthy"
}
},
{
"Target": {
"Id": "i-088e21d0af0f2890c",
"Port": 31338
},
"HealthCheckPort": "31338",
"TargetHealth": {
"State": "healthy"
}
},
{
"Target": {
"Id": "i-0fe2202d18299816f",
"Port": 31338
},
"HealthCheckPort": "31338",
"TargetHealth": {
"State": "healthy"
}
}
]
}
The output above shows that we have 3 targets registered to the load balancer using the EC2 instance IDs (i-) each on the same port. The reason for this is that by default the AWS Load Balancer Controller operates in "instance mode", which targets traffic to the worker nodes in the EKS cluster and allows kube-proxy to forward traffic to individual Pods.
You can also inspect the NLB in the console by clicking this link:
Get the URL from the Service resource:
~$kubectl get service -n ui ui-nlb -o jsonpath="{.status.loadBalancer.ingress[*].hostname}{'\n'}"
k8s-ui-uinlb-a9797f0f61.elb.us-west-2.amazonaws.com
To wait until the load balancer has finished provisioning you can run this command:
~$wait-for-lb $(kubectl get service -n ui ui-nlb -o jsonpath="{.status.loadBalancer.ingress[*].hostname}{'\n'}")