From Zero to Gloo Edge in 15 Minutes: Amazon EKS Edition
How long does it take to configure your a cloud-native application on an open-source API gateway using Envoy Proxy with EKS?
How about 15 minutes? Give us that much time and we’ll give you a Kubernetes-hosted application accessible via a gateway configured with policies for routing, service discovery, timeouts, debugging, access logging, and observability. We’ll host all of this on Amazon Elastic Kubernetes Service (EKS), arguably the most popular public-cloud options for hosting Kubernetes workloads. The Gloo Edge API gateway builds on the foundation of Envoy Proxy, the open-source proxy in Istio, itself one of the most influential enterprise cloud projects available today.
Not an Amazon EKS user? Not a problem. Check out one of these alternative approaches:
- Install-Free Experience on Instruqt
- Local KinD Cluster
- Google Kubernetes Engine (GKE)
- Red Hat OpenShift
- GitOps with ArgoCD
If you have questions, please reach out on the Solo #edge-quickstart Slack channel.
Ready? Set? Go!
Prerequisites for Envoy Proxy with EKS
For this exercise, all you’ll need to get started is your local workstation, access to an EKS cluster, and the CLI utilities kubectl, curl, and jq. Make sure these are all available to you before jumping into the next section. I’m building this on MacOS, but other platforms should be perfectly fine as well.
Installing Envoy Proxy with EKS
Let’s start by installing the platform and application components we need for this exercise.
Establish an EKS Cluster
If you do not have access to an Amazon EKS cluster today, check with your local AWS administrator to help you establish one. You can also sign up for a personal AWS account here and build a cluster in your own domain, although EKS is not one of the products that is eligible for the AWS Free Tier.
Once you have AWS access with sufficient permissions to access or create your own EKS cluster, then you’re ready to go. If you’re creating your own cluster, then you have two options: use the AWS console or the eksctl
CLI.
If you choose the console route, this interface provides you with a “Create EKS cluster” wizard to walk you through cluster creation, once you have established your identity.
If you already have AWS access, then the eksctl
CLI utility may provide a simpler alternative to the console. See this documentation if you would like to try that approach.
Try this command with the eksctl CLI to create a modest demo cluster for this exercise:
eksctl create cluster --name zero-to-gateway --region us-east-2
With this command, your cluster should be created in the Ohio (us-east-2) region. If you don’t specify a region, eksctl defaults to the Oregon (us-west-2) region. A full AWS region list with corresponding codes is available here.
If you have adequate permissions inside your AWS organization, then you should see output that looks something like this:
[ℹ] eksctl version 0.34.0 [ℹ] using region us-east-2 [ℹ] setting availability zones to [us-east-2c us-east-2a us-east-2b] [ℹ] subnets for us-east-2c - public:192.168.0.0/19 private:192.168.96.0/19 [ℹ] subnets for us-east-2a - public:192.168.32.0/19 private:192.168.128.0/19 [ℹ] subnets for us-east-2b - public:192.168.64.0/19 private:192.168.160.0/19 [ℹ] nodegroup "ng-02e2ba93" will use "ami-0170c40fb8fc775fd" [AmazonLinux2/1.18] [ℹ] using Kubernetes version 1.18 [ℹ] creating EKS cluster "zero-to-gateway" in "us-east-2" region with un-managed nodes [ℹ] will create 2 separate CloudFormation stacks for cluster itself and the initial nodegroup [ℹ] if you encounter any issues, check CloudFormation console or try 'eksctl utils describe-stacks --region=us-east-2 --cluster=zero-to-gateway' [ℹ] CloudWatch logging will not be enabled for cluster "zero-to-gateway" in "us-east-2" [ℹ] you can enable it with 'eksctl utils update-cluster-logging --enable-types={SPECIFY-YOUR-LOG-TYPES-HERE (e.g. all)} --region=us-east-2 --cluster=zero-to-gateway' [ℹ] Kubernetes API endpoint access will use default of {publicAccess=true, privateAccess=false} for cluster "zero-to-gateway" in "us-east-2" [ℹ] 2 sequential tasks: { create cluster control plane "zero-to-gateway", 3 sequential sub-tasks: { tag cluster, create addons, create nodegroup "ng-02e2ba93" } } [ℹ] building cluster stack "eksctl-zero-to-gateway-cluster" [ℹ] deploying stack "eksctl-zero-to-gateway-cluster" [✔] tagged EKS cluster (used-by=jimb) [ℹ] building nodegroup stack "eksctl-zero-to-gateway-nodegroup-ng-02e2ba93" [ℹ] --nodes-min=2 was set automatically for nodegroup ng-02e2ba93 [ℹ] --nodes-max=2 was set automatically for nodegroup ng-02e2ba93 [ℹ] deploying stack "eksctl-zero-to-gateway-nodegroup-ng-02e2ba93" [ℹ] waiting for the control plane availability... [✔] saved kubeconfig as "/Users/jibarton/.kube/config" [ℹ] no tasks [✔] all EKS cluster resources for "zero-to-gateway" have been created [ℹ] adding identity "arn:aws:iam::410461945957:role/eksctl-zero-to-gateway-nodegroup-NodeInstanceRole-15JOF7LDMA91L" to auth ConfigMap [ℹ] nodegroup "ng-02e2ba93" has 0 node(s) [ℹ] waiting for at least 2 node(s) to become ready in "ng-02e2ba93" [ℹ] nodegroup "ng-02e2ba93" has 2 node(s) [ℹ] node "ip-192-168-25-133.us-east-2.compute.internal" is ready [ℹ] node "ip-192-168-91-116.us-east-2.compute.internal" is ready [ℹ] kubectl command should work with "/Users/jibarton/.kube/config", try 'kubectl get nodes' [✔] EKS cluster "zero-to-gateway" in "us-east-2" region is ready
Running a kubectl config
command should confirm that you’re ready to go:
kubectl config get-contexts
Note the new context containing the name of your freshly created EKS cluster (containing the string zero-to-gateway
in this case). It should also be selected as the current context that you’re using.
CURRENT NAME CLUSTER AUTHINFO NAMESPACE * jim@zero-to-gateway.us-east-2.eksctl.io zero-to-gateway.us-east-2.eksctl.io jim@zero-to-gateway.us-east-2.eksctl.io kind-demo kind-demo kind-demo kind-demo
If it isn’t selected as the current context, you can remedy that with kubectl, using something like this:
kubectl config use-context jim@zero-to-gateway.us-east-2.eksctl.io
The response should match:
Switched to context "jim@zero-to-gateway.us-east-2.eksctl.io".
Install the htttpbin Application
HTTPBIN is a great little REST service that can be used to test a variety of http operations and echo the response elements back to the consumer. We’ll use it throughout this exercise. We’ll install the httpbin service on our kind cluster. Run this command:
kubectl apply -f https://raw.githubusercontent.com/solo-io/solo-blog/main/zero-to-gateway/httpbin-svc-dpl.yaml
You should see:
serviceaccount/httpbin created service/httpbin created deployment.apps/httpbin created
You can confirm that the httpbin pod is running by searching for pods with an app
label of httpbin
:
kubectl get pods -l app=httpbin
And you will see something like this:
NAME READY STATUS RESTARTS AGE httpbin-66cdbdb6c5-2cnm7 1/1 Running 0 21m
Install the glooctl Utility
GLOOCTL is a command-line utility that allows users to view and manage Gloo Edge deployments, much like a Kubernetes user employs the kubectl utility. Let’s install glooctl on our local workstation:
curl -sL https://run.solo.io/gloo/install | sh
export PATH=$HOME/.gloo/bin:$PATH
We’ll test out the installation using the glooctl version
command. It responds with the version of the CLI client that you have installed. However, the server version is undefined since we have not yet installed Gloo Edge. Enter:
glooctl version
Which responds:
Client: {"version":"1.7.10"} Server: version undefined, could not find any version of gloo running
Install Gloo Edge for Envoy Proxy with EKS
Finally, we will complete the INSTALL phase by configuring an instance of open-source Gloo Edge on our kind cluster:
glooctl install gateway
And you’ll see:
Creating namespace gloo-system... Done. Starting Gloo Edge installation... Gloo Edge was successfully installed!
It should take less than a minute for the full Gloo Edge system to be ready for use. You can use this bash script to notify you when everything is ready to go:
until kubectl get ns gloo-system do sleep 1 done until [ $(kubectl -n gloo-system get pods -o jsonpath='{range .items[*].status.containerStatuses[*]}{.ready}{"\n"}{end}' | grep false -c) -eq 0 ]; do echo "Waiting for all the gloo-system pods to become ready" sleep 1 done echo "Gloo Edge deployment is ready :-)"
The system will respond:
NAME STATUS AGE gloo-system Active 15s Waiting for all the gloo-system pods to become ready Waiting for all the gloo-system pods to become ready Waiting for all the gloo-system pods to become ready Gloo Edge deployment is ready :-)
That’s all you need to do to install Gloo Edge. Notice that we did not install or configure any kind of external database to manage Gloo artifacts. That’s because the product was architected from Day 1 to be Kubernetes-native. All artifacts are expressed as Kubernetes Custom Resources (CRDs), and they are all stored in native etcd storage. Consequently, Gloo Edge leads to more resilient and less complex systems than alternatives that are either shoe-horned into Kubernetes or require external moving parts.
Note that everything we do in this getting-started exercise runs on the open-source version of Gloo Edge. There is also an enterprise edition of Gloo Edge that adds many features to support advanced authentication and authorization, rate limiting, and observability, to name a few. You can see a feature comparison here. If you’d like to work through this blog post using Gloo Edge Enterprise instead, then please request a free trial here.
Discover K8s services with Envoy Proxy with EKS
A unique feature of Gloo Edge is its ability to discover Kubernetes (K8s) services and wrap them into an Upstream
abstraction. Upstreams represent targets to which request traffic can be routed. To learn more about how Upstreams operate in a Gloo Edge environment, see the product documentation here and here, and the API reference here.
Explore Service Discovery
Let’s use the glooctl
utility to explore the catalog of Upstreams that Gloo Edge has already compiled within our kind cluster. You can run:
glooctl get upstreams
And you’ll see:
+-------------------------------+------------+----------+------------------------------+ | UPSTREAM | TYPE | STATUS | DETAILS | +-------------------------------+------------+----------+------------------------------+ | default-httpbin-8000 | Kubernetes | Accepted | svc name: httpbin | | | | | svc namespace: default | | | | | port: 8000 | | | | | | | default-kubernetes-443 | Kubernetes | Accepted | svc name: kubernetes | | | | | svc namespace: default | | | | | port: 443 | | | | | | ... abridged ... | kube-system-kube-dns-9153 | Kubernetes | Accepted | svc name: kube-dns | | | | | svc namespace: kube-system | | | | | port: 9153 | | | | | | +-------------------------------+------------+----------+------------------------------+
Notice in particular the default-httpbin-8000
Upstream that corresponds to the httpbin service we installed earlier.
Explore Function Discovery with OpenAPI
We could begin routing to this newly discovered httpbin Upstream right away. Before we do that, let’s explore advanced function discovery features that ship with open-source Gloo Edge. Function discovery is supported for both OpenAPI / REST and gRPC interfaces. In this example, we will associate an OpenAPI document with the httpbin Upstream and then observe the discovery feature at work.
We need to enable function discovery on the default
namespace where the service is deployed. Since not all users employ OpenAPI or gRPC interfaces, and because function discovery can become resource-intensive, it is disabled by default. We could have enabled it via helm values at installation time; instead, we will do it here with kubectl:
kubectl label namespace default discovery.solo.io/function_discovery=enabled
Which confirms:
namespace/default labeled
Second, we will modify the httpbin Upstream to associate an OpenAPI document. There’s nothing unique or Gloo-specific in the OpenAPI document itself; it’s just an OpenAPI spec for a standard REST interface. You can see the full spec for httpbin here and interact with the individual operations in the httpbin sandbox here.
Let’s take a look at the modifications to the generated httpbin Upstream. All we’re doing is adding a URL to the Upstream that locates the OpenAPI specification for the httpbin service.
serviceSpec: rest: swaggerInfo: url: https://raw.githubusercontent.com/jameshbarton/solo-resources/main/zero-to-gateway/httpbin-openapi.json
Now we’ll apply this change to the Upstream:
kubectl apply -f https://raw.githubusercontent.com/solo-io/solo-blog/main/zero-to-gateway/httpbin-openapi-us.yaml
And it confirms:
upstream.gloo.solo.io/default-httpbin-8000 configured
Now when we use glooctl to inspect the upstream and compare it with what we had before, you can see that Gloo Edge has discovered (with the guidance of the OpenAPI document) a number of individual operations being published from the httpbin service. This will allow us to be much more precise and avoid errors as we establish routing rules. Run:
glooctl get upstream default-httpbin-8000
You should see:
+----------------------+------------+----------+------------------------+ | UPSTREAM | TYPE | STATUS | DETAILS | +----------------------+------------+----------+------------------------+ | default-httpbin-8000 | Kubernetes | Accepted | svc name: httpbin | | | | | svc namespace: default | | | | | port: 8000 | | | | | REST service: | | | | | functions: | | | | | - /anything | | | | | - /base64 | | | | | - /brotli | | | | | - /bytes | | | | | - /cache | | | | | - /deflate | | | | | - /delay | | | | | - /delete | | | | | - /get | | | | | - /gzip | | | | | - /headers | | | | | - /ip | | | | | - /patch | | | | | - /post | | | | | - /put | | | | | - /redirect-to | | | | | - /response-headers | | | | | - /status | | | | | - /stream | | | | | - /user-agent | | | | | - /uuid | | | | | - /xml | | | | | | +----------------------+------------+----------+------------------------+
Controlling Envoy Proxy with EKS
In this section, we’ll establish and test routing rules that are the core of the proxy configuration, and also show how to establish timeout policies from the proxy.
Configure Simple Routing with CLI
Let’s begin our routing configuration with the simplest possible route to expose the /get
operation on httpbin. This endpoint simply replies with its response the headers and any other arguments passed into the service.
We’ll use the glooctl utility to get started:
glooctl add route \ --path-exact /api/httpbin/get \ --dest-name default-httpbin-8000 \ --prefix-rewrite /get
And can see the output:
{"level":"info","ts":"2021-06-023T14:22:58.502-0400","caller":"add/route.go:156","msg":"Created new default virtual service","virtualService":"virtual_host:{domains:\"*\" routes:{matchers:{exact:\"/api/httpbin/get\"} route_action:{single:{upstream:{name:\"default-httpbin-8000\" namespace:\"gloo-system\"}}} options:{prefix_rewrite:{value:\"/get\"}}}} status:{} metadata:{name:\"default\" namespace:\"gloo-system\" resource_version:\"437677\" generation:1}"} +-----------------+--------------+---------+------+---------+-----------------+----------------------------------+ | VIRTUAL SERVICE | DISPLAY NAME | DOMAINS | SSL | STATUS | LISTENERPLUGINS | ROUTES | +-----------------+--------------+---------+------+---------+-----------------+----------------------------------+ | default | | * | none | Pending | | /api/httpbin/get -> | | | | | | | | gloo-system.default-httpbin-8000 | | | | | | | | (upstream) | +-----------------+--------------+---------+------+---------+-----------------+----------------------------------+
This glooctl invocation created a Gloo Edge VirtualService
component, which is named default
by default. It routes any request to the path /api/httpbin/get
to the httpbin /get
endpoint. Attempting to reach any other endpoint on the httpbin service will be rejected.
Note that when the route is initially created, the status of the route is Pending
. Issue the glooctl get virtualservice default
command and observe that the status has now changed from Pending
to Accepted
.
+-----------------+--------------+---------+------+----------+-----------------+----------------------------------+ | VIRTUAL SERVICE | DISPLAY NAME | DOMAINS | SSL | STATUS | LISTENERPLUGINS | ROUTES | +-----------------+--------------+---------+------+----------+-----------------+----------------------------------+ | default | | * | none | Accepted | | /api/httpbin/get -> | | | | | | | | gloo-system.default-httpbin-8000 | | | | | | | | (upstream) | +-----------------+--------------+---------+------+----------+-----------------+----------------------------------+
Confirm a Load Balancer is established
Now that a VirtualService is in place, Gloo Edge establishes an AWS load balancer that can be accessed via its hostname to invoke service endpoints published via the gateway. You can see this hostname when you look at this entry for the gateway-proxy
(Envoy proxy) service:
kubectl get service gateway-proxy -n gloo-system
Here is the response with the load balancer’s hostname reported as EXTERNAL-IP
:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE gateway-proxy LoadBalancer 10.100.10.46 a207de2527e7742b5a2096dd2bae1b55-2061871899.us-east-2.elb.amazonaws.com 80:30593/TCP,443:32752/TCP 6h37m
You can also see the newly configured load balancer in the AWS console:
By default, Gloo Edge establishes an AWS Classic Load Balancer (CLB) to expose our gateway to the outside world. However, both Natural (NLB) and Application (ALB) Load Balancers are supported as well. Many Gloo Edge users have had success using the TCP Layer 4 NLBs in conjunction with the Gloo Edge Layer 7 gateway. For further information on AWS load balancer integration, see the product documentation.
Test the Simple Route with Curl
We will test our new Route via our AWS load balancer in this section. Let’s use the glooctl CLI to inspect the URL exposed from the proxy and assign that to a shell variable, like this:
proxy_url=$(glooctl proxy url) echo $proxy_url
And it responds with the http prefix of the exposed AWS load balancer URL:
http://a207de2527e7742b5a2096dd2bae1b55-2061871899.us-east-2.elb.amazonaws.com:80
We will now use this glooctl utility with curl to invoke httpbin endpoints. In this case, we will add the curl -i
option to show the HTTP response code and headers:
curl ${proxy_url}/api/httpbin/get -i
This command should complete successfully.
HTTP/1.1 200 OK server: envoy date: Wed, 23 Jun 2021 20:30:41 GMT content-type: application/json content-length: 404 access-control-allow-origin: * access-control-allow-credentials: true x-envoy-upstream-service-time: 2 { "args": {}, "headers": { "Accept": "*/*", "Host": "a207de2527e7742b5a2096dd2bae1b55-2061871899.us-east-2.elb.amazonaws.com", "User-Agent": "curl/7.64.1", "X-Envoy-Expected-Rq-Timeout-Ms": "5000", "X-Envoy-Original-Path": "/api/httpbin/get" }, "origin": "192.168.77.109", "url": "http://a207de2527e7742b5a2096dd2bae1b55-2061871899.us-east-2.elb.amazonaws.com/get" }
Note that if we attempt to invoke another valid endpoint /delay
on the httpbin service, it will fail with a 404 Not Found
error. Why? This is because our VirtualService routing policy is only exposing access to /get
, one of the many endpoints available on the service. If we enter:
curl ${proxy_url}/api/httpbin/delay/1 -i
You’ll see:
HTTP/1.1 404 Not Found date: Fri, 23 Jun 2021 20:32:01 GMT server: envoy content-length: 0
Explore Complex Routing with Regex Patterns
Let’s assume that now we DO want to expose other httpbin endpoints like /delay
. Our initial VirtualService is inadequate, because it is looking for an exact path match with /api/httpbin/get
. Here is the core YAML for that VirtualService as constructed by the glooctl add route
command we issued earlier.
apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: default namespace: gloo-system spec: virtualHost: domains: - '*' routes: - matchers: - exact: /api/httpbin/get options: prefixRewrite: /get routeAction: single: upstream: name: default-httpbin-8000 namespace: gloo-system
This time, rather than using the glooctl CLI, let’s manipulate the VirtualService directly. We’ll modify the matchers:
stanza to match the path prefix /api/httpbin
and replace it with /
. So a path like /api/httpbin/delay/1
will be sent to httpbin with the path /delay/1
. Now it will look like this:
apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: default namespace: gloo-system spec: virtualHost: domains: - '*' routes: - matchers: - prefix: /api/httpbin options: regexRewrite: pattern: regex: '/api/httpbin/' substitution: '/' routeAction: single: upstream: name: default-httpbin-8000 namespace: gloo-system
Let’s apply the modified VirtualService and test. Note that throughout this exercise, we are managing Gloo Edge artifacts using Kubernetes utilities like kubectl. That’s an important point because it allows developers to work with familiar tools when working with Gloo Edge configuration. It also benefits organization using GitOps strategies to manage deployments, as tools like ArgoCD and Flux are able to easily handle Gloo artifacts on Kubernetes.
kubectl apply -f https://raw.githubusercontent.com/solo-io/solo-blog/main/zero-to-gateway/httpbin-vs-regex.yaml
Note that you can safely ignore the “kubectl apply” warning below. As long as kubectl responds that the default
VirtualService was configured, then your change was applied.
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply virtualservice.gateway.solo.io/default configured
Test Routing with Regex Patterns
When we used only a single route with an exact match pattern, we could only exercise the httpbin /get
endpoint. Let’s now use curl to confirm that both /get
and /delay
work as expected.
% curl ${proxy_url}/api/httpbin/get -i HTTP/1.1 200 OK server: envoy date: Wed, 23 Jun 2021 20:33:22 GMT content-type: application/json content-length: 404 access-control-allow-origin: * access-control-allow-credentials: true x-envoy-upstream-service-time: 1 { "args": {}, "headers": { "Accept": "*/*", "Host": "a207de2527e7742b5a2096dd2bae1b55-2061871899.us-east-2.elb.amazonaws.com", "User-Agent": "curl/7.64.1", "X-Envoy-Expected-Rq-Timeout-Ms": "5000", "X-Envoy-Original-Path": "/api/httpbin/get" }, "origin": "192.168.77.109", "url": "http://a207de2527e7742b5a2096dd2bae1b55-2061871899.us-east-2.elb.amazonaws.com/get" } % curl ${proxy_url}/api/httpbin/delay/1 -i HTTP/1.1 200 OK server: envoy date: Wed, 23 Jun 2021 20:33:25 GMT content-type: application/json content-length: 458 access-control-allow-origin: * access-control-allow-credentials: true x-envoy-upstream-service-time: 1002 { "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Host": "a207de2527e7742b5a2096dd2bae1b55-2061871899.us-east-2.elb.amazonaws.com", "User-Agent": "curl/7.64.1", "X-Envoy-Expected-Rq-Timeout-Ms": "5000", "X-Envoy-Original-Path": "/api/httpbin/delay/1" }, "origin": "192.168.77.109", "url": "http://a207de2527e7742b5a2096dd2bae1b55-2061871899.us-east-2.elb.amazonaws.com/delay/1" }
Perfect! It works just as expected! For extra credit, try out some of the other endpoints published via httpbin as well.
Configure Timeouts
Don’t you hate it when you visit a website and the request just gets “lost”? You wait and wait. Maybe you see the network connection established, but then you wait some more, and still the request never completes.
Gloo Edge provides an easy-to-configure set of timeouts that you can apply to spare your valuable users this frustration. Like other Gloo features, it can be added to your policy with standard Kubernetes tooling, and without touching the source application. All we need to do add a timeout
directive to our VirtualService. In this case, we will apply the timeout in the simplest fashion at the httpbin route level by adding timeout
to our route options.
routes: - matchers: - prefix: /api/httpbin options: timeout: '5s' # Adding 5-second timeout HERE regexRewrite: pattern: regex: '/api/httpbin/' substitution: '/'
Let’s apply this VirtualService change using kubectl:
kubectl apply -f https://raw.githubusercontent.com/solo-io/solo-blog/main/zero-to-gateway/httpbin-vs-timeout.yaml
It confirms:
virtualservice.gateway.solo.io/default configured
Test Timeouts with httpbin delays
We will confirm that our new timeout policy works by using the httpbin /delay
endpoint. We’ll specify a one second delay, and we expect everything to work just fine. Second, we’ll specify a longer delay, say eight seconds, and we will expect our timeout policy to be triggered and return a 504 Gateway Timeout
error. Run:
curl ${proxy_url}/api/httpbin/delay/1 -i
This returns:
HTTP/1.1 200 OK server: envoy date: Wed, 23 Jun 2021 20:37:14 GMT content-type: application/json content-length: 458 access-control-allow-origin: * access-control-allow-credentials: true x-envoy-upstream-service-time: 1003 { "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Host": "a207de2527e7742b5a2096dd2bae1b55-2061871899.us-east-2.elb.amazonaws.com", "User-Agent": "curl/7.64.1", "X-Envoy-Expected-Rq-Timeout-Ms": "5000", "X-Envoy-Original-Path": "/api/httpbin/delay/1" }, "origin": "192.168.77.109", "url": "http://a207de2527e7742b5a2096dd2bae1b55-2061871899.us-east-2.elb.amazonaws.com/delay/1"
Note that the operation completed successfully and that the one second delay was applied. The response header x-envoy-upstream-service-time: 1003
indicates that the request spent 1,003 milliseconds being processed by Envoy.
Now let’s switch to an eight second delay and see if our five second timeout triggers as expected. Execute:
% curl ${proxy_url}/api/httpbin/delay/8 -i HTTP/1.1 504 Gateway Timeout content-length: 24 content-type: text/plain date: Fri, 23 Jun 2021 20:39:03 GMT server: envoy upstream request timeout
BOOM! Our simple timeout policy works just as expected, triggering a 504 Gateway Timeout
error when the five second threshold is exceeded by our eight second httpbin delay.
Debugging Envoy Proxy with EKS
Sometimes debugging bad software configurations can be a pain. Gloo Edge engineers have made this as easy as possible, with documentation like this. However, as we have all experienced, it can be a challenge with any complex system. Here we’ll explore how to use the glooctl utility to assist in some simple debugging tasks.
Solve a Problem with glooctl CLI
A common source of Gloo Edge configuration errors is mistyping an upstream reference, perhaps when copy/pasting it from another source but “missing a spot” when changing the name of the Upstream target. In this example, we’ll simulate making an error like that, and then demonstrate how glooctl can be used to detect it.
Let’s apply a change to simulate the mistyping of an upstream config so that it is targeting a non-existent default-httpbin-8080
Upstream, rather than the correct default-httpbin-8000:
kubectl delete virtualservice default -n gloo-system sleep 5 kubectl apply -f https://raw.githubusercontent.com/solo-io/solo-blog/main/zero-to-gateway/httpbin-vs-bad-port.yaml
You should see:
virtualservice.gateway.solo.io "default" deleted virtualservice.gateway.solo.io/default created
Note that we applied a sleep
between deleting the VirtualService and re-creating it to ensure that we clear the working route from Envoy’s route cache before we create the new mis-configured route.
Now if we try to access one of our httpbin endpoints:
curl ${proxy_url}/api/httpbin/get -i
It likely fails with an error like this:
curl: (7) Failed to connect to 34.75.42.228 port 80: Connection refused
So we’ll deploy one of the Gloo Edge debugging features, the glooctl check
utility, to help you manage Envoy Proxy with EKS. It performs checks on a number of Gloo resources, confirming that they are configured correctly and are interconnected with other resources correctly. For example, in this case, glooctl will detect the error in the mis-connection between a VirtualService and its Upstream target:
glooctl check
You can see the checks respond:
Checking deployments... OK Checking pods... OK Checking upstreams... OK Checking upstream groups... OK Checking auth configs... OK Checking rate limit configs... OK Checking secrets... OK Checking virtual services... 2 Errors! Checking gateways... OK Checking proxies... 1 Errors! Error: 3 errors occurred: * Found virtual service with warnings: gloo-system default (Reason: warning: Route Warning: InvalidDestinationWarning. Reason: *v1.Upstream { gloo-system.default-httpbin-8080 } not found) * Virtual service references unknown upstream: (Virtual service: gloo-system default | Upstream: gloo-system default-httpbin-8080) * Found proxy with warnings: gloo-system gateway-proxy Reason: warning: Route Warning: InvalidDestinationWarning. Reason: *v1.Upstream { gloo-system.default-httpbin-8080 } not found
The detected errors clearly identify that the VirtualService is pointed at an invalid destination.
So let’s reapply the previous configuration, and then we’ll confirm that the configuration is again clean:
kubectl apply -f https://raw.githubusercontent.com/solo-io/solo-blog/main/zero-to-gateway/httpbin-vs-timeout.yaml
Now we get confirmation:
virtualservice.gateway.solo.io/default configured
Re-run glooctl check
and observe that there are no problems. Our curl commands to the httpbin endpoint will also work again as expected:
Checking deployments... OK Checking pods... OK Checking upstreams... OK Checking upstream groups... OK Checking auth configs... OK Checking rate limit configs... OK Checking secrets... OK Checking virtual services... OK Checking gateways... OK Checking proxies... OK No problems detected.
Observing Envoy Proxy with EKS
Finally, let’s tackle an exercise where we’ll learn about some simple observability tools that ship with open-source Gloo Edge.
Configure Simple Access Logging
The default Envoy Proxy logging configurations are very quiet by design. When working in extremely high-volume environments, verbose logs can potentially impact performance and consume excessive storage.
However, access logs are quite important at development and test time, and potentially in production as well. So let’s explore how to set up and consume simple access logs.
So far we have discussed Gloo Edge custom resources like Upstreams and VirtualServices. Upstreams represent the target systems to which Gloo routes traffic. VirtualServices represent the policies that determine how external requests are routed to those targets.
With access logging, we will consider another Gloo Edge component called a gateway. A gateway is a custom resource that configures the protocols and ports on which Gloo Edge listens for traffic. For example, by default Gloo Edge will have a gateway configured for HTTP and HTTPS traffic. More information on gateways is available here. Gloo Edge allows you to customize the behavior of your gateways in multiple ways. One of those ways is by adding access logs.
Let’s add the simplest, default access log configuration. We’ll simply add the following options
to activate access logging. You can see the full gateway YAML that we’ll apply here.
options: accessLoggingService: accessLog: - fileSink: path: /dev/stdout stringFormat: ""
Now let’s apply this change to enable access logging from our Envoy data plane:
kubectl apply -f https://raw.githubusercontent.com/solo-io/solo-blog/main/zero-to-gateway/gateway-basic-access-logs.yaml
As before, you can safely ignore the benign kubectl warning:
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply gateway.gateway.solo.io/gateway-proxy configured
Now let’s generate some traffic to produce some access logs:
curl ${proxy_url}/api/httpbin/get curl ${proxy_url}/api/httpbin/delay/1 curl ${proxy_url}/api/httpbin/delay/8
You should be able to view the resulting access logs by using the kubectl logs
commands against the Envoy data plane pod:
kubectl logs -n gloo-system deploy/gateway-proxy
Here’s what we saw:
[2021-06-23T20:41:25.459Z] "GET /api/httpbin/get HTTP/1.1" 200 - 0 404 2 2 "-" "curl/7.64.1" "8bdb62de-d066-4d5b-a848-bdfda1cdcd8f" "a207de2527e7742b5a2096dd2bae1b55-2061871899.us-east-2.elb.amazonaws.com" "192.168.29.133:80" [2021-06-23T20:41:25.558Z] "GET /api/httpbin/delay/1 HTTP/1.1" 200 - 0 458 1002 1002 "-" "curl/7.64.1" "28be8891-c6b6-48e7-b430-90ba1e0bfed5" "a207de2527e7742b5a2096dd2bae1b55-2061871899.us-east-2.elb.amazonaws.com" "192.168.29.133:80" [2021-06-23T20:41:26.687Z] "GET /api/httpbin/delay/8 HTTP/1.1" 504 UT 0 24 5002 - "-" "curl/7.64.1" "aa441de0-47f4-49a0-92e9-195bf2d299d8" "a207de2527e7742b5a2096dd2bae1b55-2061871899.us-east-2.elb.amazonaws.com" "192.168.29.133:80"
Notice from the output the default string formatted access log for each of the operations we executed. You can see the paths of the operations, plus the HTTP response codes: 200 for the first two, and 504 (Gateway Timeout). Plus there is a host of other information.
While we are viewing these access logs using kubectl, you may want to export it for use with an enterprise log aggregator like ELK, Splunk, or Datadog. For example, Gloo Edge provides guidance for integrating with popular platforms like Datadog.
Customize Access Logging
Gloo Edge gateways can also be configured to produce access logs in other formats like JSON, and to customize the actual content that is published to those logs. We will do both of those things by replacing our previous access log configuration in the gateway component with this:
options: accessLoggingService: accessLog: - fileSink: jsonFormat: # HTTP method name httpMethod: '%REQ(:METHOD)%' # Protocol. Currently either HTTP/1.1 or HTTP/2. protocol: '%PROTOCOL%' # HTTP response code. Note that a response code of ‘0’ means that the server never sent the # beginning of a response. This generally means that the (downstream) client disconnected. responseCode: '%RESPONSE_CODE%' # Total duration in milliseconds of the request from the start time to the last byte out clientDuration: '%DURATION%' # Total duration in milliseconds of the request from the start time to the first byte read from the upstream host targetDuration: '%RESPONSE_DURATION%' # Value of the "x-envoy-original-path" header (falls back to "path" header if not present) path: '%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%' # Upstream cluster to which the upstream host belongs to upstreamName: '%UPSTREAM_CLUSTER%' # Request start time including milliseconds. systemTime: '%START_TIME%' # Unique tracking ID requestId: '%REQ(X-REQUEST-ID)%' path: /dev/stdout
More information on customizing access log content is provided here.
Now we will apply this gateway change, generate some additional traffic to our proxy, and view the resulting logs.
kubectl apply -f https://raw.githubusercontent.com/solo-io/solo-blog/main/zero-to-gateway/gateway-json-access-logs.yaml
This responds:
gateway.gateway.solo.io/gateway-proxy configured
We’ll use the same curl commands as before to generate some traffic:
curl ${proxy_url}/api/httpbin/get curl ${proxy_url}/api/httpbin/delay/1 curl ${proxy_url}/api/httpbin/delay/8
Note that it may take a few seconds until the access log content is flushed to the logs:
kubectl logs -n gloo-system deploy/gateway-proxy | grep ^{ | jq
In the end, you should be able to see our customized JSON content, looking something like this:
{ "upstreamName": "default-httpbin-8000_gloo-system", "clientDuration": 2, "path": "/api/httpbin/get", "httpMethod": "GET", "requestId": "0b005aa4-b7c9-42da-a40e-f5442ccb3388", "targetDuration": 2, "protocol": "HTTP/1.1", "systemTime": "2021-06-23T20:43:07.436Z", "responseCode": 200 } { "targetDuration": 1002, "path": "/api/httpbin/delay/1", "protocol": "HTTP/1.1", "requestId": "2d812b40-add7-4b83-a2f5-2dbe18da88d3", "clientDuration": 1002, "upstreamName": "default-httpbin-8000_gloo-system", "systemTime": "2021-06-23T20:43:07.528Z", "httpMethod": "GET", "responseCode": 200 } { "requestId": "a123ac4e-5bf6-4aa6-84fe-3a81efa2ab90", "protocol": "HTTP/1.1", "upstreamName": "default-httpbin-8000_gloo-system", "systemTime": "2021-06-23T20:43:08.657Z", "targetDuration": null, "path": "/api/httpbin/delay/8", "responseCode": 504, "clientDuration": 5004, "httpMethod": "GET" }
Explore Envoy Proxy Metrics
Envoy publishes many metrics that may be useful for observing system behavior. In our very modest kind cluster for this exercise, you can count over 3,000 individual metrics! You can learn more about them in the Envoy documentation here.
Let’s take a quick look at a couple of the useful metrics that Envoy produces for every one of our Upstream targets.
We’ll port-forward the Envoy administrative port 19000 to our local workstation:
kubectl port-forward -n gloo-system deploy/gateway-proxy 19000 &
This shows:
Forwarding from 127.0.0.1:19000 -> 19000 Forwarding from [::1]:19000 -> 19000 Handling connection for 19000
Then let’s view two of the metrics that are most relevant to this exercise: one that counts the number of successful (HTTP 200) requests processed by our httpbin Upstream, and another that counts the number of gateway timeout (HTTP 504) requests against that same upstream:
curl -s http://localhost:19000/stats | grep -E 'cluster.default-httpbin-8000_gloo-system.upstream_rq_(200|504)'
Which gives us:
cluster.default-httpbin-8000_gloo-system.upstream_rq_200: 7 cluster.default-httpbin-8000_gloo-system.upstream_rq_504: 2
As you can see, on my instance we’ve processed seven good requests and two bad ones. If we apply the same three curl requests as before, we’d expect the number of 200 requests to increment by two, and the number of 504 requests to increment by one:
curl ${proxy_url}/api/httpbin/get curl ${proxy_url}/api/httpbin/delay/1 curl ${proxy_url}/api/httpbin/delay/8
curl -s http://localhost:19000/stats | grep -E 'cluster.default-httpbin-8000_gloo-system.upstream_rq_(200|504)'
And that is exactly what we see!
cluster.default-httpbin-8000_gloo-system.upstream_rq_200: 9 cluster.default-httpbin-8000_gloo-system.upstream_rq_504: 3
If you’d like to have more tooling and enhanced visibility around system observability, we recommend taking a look at an Enterprise subscription to Gloo Edge so you can run Envoy Proxy with EKS in your own AWS environment. You can sign up for a free trial here.
Gloo Edge Enterprise provides out-of-the-box integration with both Prometheus and Grafana, allowing you to replace curl and grep with per-Upstream generated dashboards like this.
You can learn more about creating your own Grafana dashboards from Gloo Edge Enterprise metrics in this blog post.
Cleanup
If you’d like to cleanup the work you’ve done, you can delete your EKS cluster from the Google Cloud console or by using the eksctl
CLI:
eksctl delete cluster --name zero-to-gateway --region us-east-2
You should see output similar to this if successful:
[ℹ] eksctl version 0.34.0 [ℹ] using region us-east-2 [ℹ] deleting EKS cluster "zero-to-gateway" [ℹ] deleted 0 Fargate profile(s) [✔] kubeconfig has been updated [ℹ] cleaning up AWS load balancers created by Kubernetes objects of Kind Service or Ingress [ℹ] 2 sequential tasks: { delete nodegroup "ng-02e2ba93", delete cluster control plane "zero-to-gateway" [async] } [ℹ] will delete stack "eksctl-zero-to-gateway-nodegroup-ng-02e2ba93" [ℹ] waiting for stack "eksctl-zero-to-gateway-nodegroup-ng-02e2ba93" to get deleted [ℹ] will delete stack "eksctl-zero-to-gateway-cluster" [✔] all cluster resources were deleted
Learn More
In this blog post, we explored how you can get started with the open-source edition of Gloo Edge in 15 minutes on your own workstation and Envoy Proxy with EKS. We walked through the process of establishing access to an EKS cluster, installing an application, and then managing it with policies for routing, service discovery, timeouts, debugging, access logging, and observability. All of the code used in this guide is available on github.
A Gloo Edge Enterprise subscription offers even more value to users who require:
- integration with identity management platforms like Auth0 and Okta;
- configuration-driven rate limiting;
- securing your application network with WAF, ModSecurity, or Open Policy Agent;
- an API Portal for publishing and managing OpenAPI and gRPC interfaces; and
- enhanced observability with batteries-included Prometheus and Grafana instances.
- Explore the documentation for Gloo Edge.
- Request a live demo or trial for Gloo Edge Enterprise.
- See video content on the solo.io YouTube channel.
- Questions? Join the Solo.io Slack community and check out the #edge-quickstart and #gloo-edge channels.