Expose your kubernetes sevices using gateway-api with envoy-gateway.
I wanted to see what benefits the gateway-api has in comparison with other service types on kubernetes that each has their own use case with their pros and cons. After I have research different providers I decided to use envoy-gateway as it was the most stable and complete integration. I never used envoy in the past therefore it had it's own struggles to figure out how they work. After all everything came together nicely and I decided to write this post to share my experience with others.
First I deployed the envoy proxy via helm chart. At this stage I am assuming you are familiar how the helm works therefore I will not show how to install the helm on your computer. First I have installed the CRD's and the envoy-gateway:
#helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.1.2 -n envoy-gateway-system --create-namespace
After a bit of time the helm-gateway will be up and running in the envoy-gateway-system
namespace.
#kubectl get pods -n envoy-gateway-system
NAME READY STATUS RESTARTS AGE
envoy-gateway-655745744f-fj8jb 1/1 Running 0 12h
Once the CRD's and envoy-gateway is deployed we need to start configuring it. The configuration is done by creating different custom resources. First we need to create a gatewayClass. In the parametersRef
I am setting up the name and type of the configuration where the gatewayClass should apply the config from to the gateway using the name common-external-config
and kind EnvoyProxy
. Note different gateway implementation will use different naming:
#cat << EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: external-gateway-class
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
parametersRef:
group: gateway.envoyproxy.io
kind: EnvoyProxy
name: common-external-config
namespace: envoy-gateway-system
EOF
Different providers use different annotations for their loadbalancers to configure the property of the loadbalancer. Since I am using VPSie platform I need to configure the annotations and the services for the envoy gateway loadbalancers. This way I also configure the horizontal autoscaling policies. This can be done using Envoy Proxy custom resource:
#cat << EOF | kubectl apply -f -
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
name: common-external-config
namespace: envoy-gateway-system
spec:
logging:
level:
default: warn
provider:
kubernetes:
envoyHpa:
maxReplicas: 10
metrics:
- resource:
name: cpu
target:
averageUtilization: 60
type: Utilization
type: Resource
minReplicas: 2
envoyService:
annotations:
service.beta.kubernetes.io/vpsie-lb-protocol: tcp
service.beta.kubernetes.io/vpsie-loadbalancer-plan: basic
service.beta.kubernetes.io/vpsie-private-loadbalancer: 'false'
service.beta.kubernetes.io/vpsie-vpc-name: NY2-k8s-test
externalTrafficPolicy: Cluster
type: LoadBalancer
type: Kubernetes
EOF
This way I have configured to have a number of minimum 2 replicas and do the horizonta autoscaling based on the CPU if the pod CPU load is above 60% and have the maximum number of pods not higher than 10. The annotations tells that the loadbalance type is TCP, what payment plan to be used for the loadbalancer, if the loadbalancer is a public or private, and the VPC name where the internal interface would need to listen to.
Now that I have the configuration it is time to configure the Gateway custom resource that will create the LoadBalancer type of service:
#cat << EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: external-gateway
namespace: envoy-gateway-system
spec:
gatewayClassName: external-gateway-class
listeners:
- allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
shared-gateway-access: 'true'
name: http
port: 80
protocol: HTTP
EOF
This tells that the gateway will be controlled by the external-gateway-class
gatewayClass. It has one listener on port 80 that uses the http protocol called http and the gateway is usable by any namespace that has the shared-gateway-access=true
label. Since my nginx app is runnig on the default
namespace I need to label it:
#kubectl label Namespace default shared-gateway-access=true
After a short while if all was configured correctly you can see that the gateway was approved and has a public ip address assigned to it.
#kubectl -n envoy-gateway-system get gateways
NAME CLASS ADDRESS PROGRAMMED AGE
external-gateway external-gateway-class 162.xx.xx.xx True 17h
Now that from the infrastructure point of view everything is setup. I am going to deploy an nginx app and then setup a HTTPRoute type of custom resource that would expose the service to the internet.
I have the nginx app setup using the app=nginx
matchLabel also a service called web that listens on port 8080
:
#kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web ClusterIP 10.111.105.145 <none> 8080/TCP 16h
To add the HTTPRoute CR I am running the following command:
#apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web
namespace: default
spec:
hostnames:
- test.zozoo.io
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: external-gateway
namespace: envoy-gateway-system
sectionName: http
rules:
- backendRefs:
kind: Service
name: web
port: 8080
- matches:
- path:
type: PathPrefix
value: /
In this object I am setting the domain name based on which the routing to happen, the gateway name that will be used to route this request, the namespace where the gateway was deployed to and if it has more sections then the sectionName. This only have one section for now but I am planning on adding another https section that would terminate the TLS connections and use an ssl certificate provided by the certmanager. You can check this link on how to set that up.
To test if the connection work I am going to run a curl request on the configured domain name:
#╰─ curl -IL http://test.zozoo.io
HTTP/1.1 200 OK
server: nginx/1.27.1
date: Thu, 03 Oct 2024 14:42:22 GMT
content-type: text/html
content-length: 615
last-modified: Mon, 12 Aug 2024 14:21:01 GMT
etag: "66ba1a4d-267"
accept-ranges: bytes
You can do more than just hostname based routing using gateway-api. You can do path based routing, header based routing. You can expose TCP ports and then use TCPRoute to send the traffic to a specific service based on the port that you connecting to.