In this lab, you will learn a few additional capabilities of Istio.
This lab assumes you have bookinfo application from the main lab.
In this part of the lab, you will install and configure a sample websocket application
- Build a sample application
./websocket/dockerbuild.sh
- Push the application to GCP
./websocket/dockerpush.sh
- Deploy the application to Kubernetes
kubectl apply -f <(istioctl kube-inject -f ./websocket/websockets.yaml)
- Edit bookinfo virtual services to add new routes
kubectl edit virtualservice bookinfo
Add the following lines to the bookinfo virtual service
- match:
- uri:
exact: /v1/ws
route:
- destination:
host: websockets
port:
number: 3000
websocketUpgrade: true
- match:
- uri:
exact: /
route:
- destination:
host: websockets
port:
number: 3000
- Open a Chrome tab and go to the $GATEWAY endpoint. You should see this message
- Open developer tools in Chrome
- Open a websocket connection and test
The first step opens a websocket connection to the server. The second step registers a listener for incoming messages and prints the message from the server. The last step send a message to the server.
In this example, I'm sending hi
to the server and the server responds with helloworld
.
In this part of the lab, you will protect a websockets endpoints with apikeys and capture analytics using Apigee Edge.
- Create an API specification for the websockets application
kubectl apply -f ./websockets/websockets-api.yaml
- Create rule to enable API key protection for the websockets application
kubectl apply -f ./websockets/websokets-rule.yaml
- Test the endpoint
curl http://35.227.164.14/
OUTPUT:
PERMISSION_DENIED:apigee-handler.apigee.istio-system:missing authentication
This is an expected result. If you pass a valid apikey as a queryparam, then the call should succeed.
NOTE: To create a valid API key, please follow the steps here. It involves creating an API Product and a Developer App in Apigee Edge.
- Test the websocket connection
The first connection does not provide an apikey. The second connection provides an invalid apikey. The last connection provides a valid apikey.
NOTE: Istio does not call Mixer (istio-policy) once a connection is successfully established. Therefore Quota or Rate Limiting policies are not expected to work.
In this part of the lab, we will enable rate limiting on a service.
There are four parts to quota.
- The
memquota
adapter uses a sliding window of sub second resolution to enforce rate limits. ThemaxAmount
in the adapter configuration sets the default limit for all counters associated with a quota instance. This default limit applies if a quota override does not match the request. In the example, we will set a quota on thedetails
service for 3 per minute. - The kind
quota
specifies the quota identifier. In this case it is thedestination.service
. - The
QuotaSpec
determines the message weight. In this case, we will increase the counter by 1. - The
QuotaSpecBinding
determines to which services theQuotaSpec
must be applied to. In this case, thedetails
service. - The
rule
which specifies when the quota adapter should be active. In this case, there is no match condition, because we are usingQuotaSpecBinding
to control which servies the quota applies to.
If the number of requests exceeds the quota, Mixer returns a RESOURCE_EXHAUSTED message to the proxy. The proxy in turn returns status HTTP 429 to the caller.
- Apply Quota Policy
cat <<EOF | kubectl apply -f -
apiVersion: config.istio.io/v1alpha2
kind: memquota
metadata:
name: details-handler
namespace: istio-system
spec:
quotas:
- name: requestcount.quota.istio-system
maxAmount: 5000
validDuration: 1s
overrides:
- dimensions:
destination: details
maxAmount: 3
validDuration: 1m
---
apiVersion: config.istio.io/v1alpha2
kind: quota
metadata:
name: requestcount
namespace: istio-system
spec:
dimensions:
destination: destination.labels["app"] | destination.service | "unknown"
---
apiVersion: config.istio.io/v1alpha2
kind: QuotaSpec
metadata:
name: request-count-quotaspec
namespace: istio-system
spec:
rules:
- quotas:
- charge: "1"
quota: requestcount
---
apiVersion: config.istio.io/v1alpha2
kind: QuotaSpecBinding
metadata:
name: request-count-quotaspecbinding
namespace: istio-system
spec:
quotaSpecs:
- name: request-count-quotaspec
namespace: istio-system
services:
- name: details
namespace: default
---
apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
name: quota
namespace: istio-system
spec:
actions:
- handler: details-handler.memquota
instances:
- requestcount.quota
EOF
- Test the quota policy Run a set of curl commands to test the quota
while true; do curl $GATEWAY/details/0; done
OUTPUT:
{"id":0,"author":"William Shakespeare","year":1595,"type":"paperback","pages":200,"publisher":"PublisherA","language":"English","ISBN-10":"1234567890","ISBN-13":"123-1234567890"}{"id":0,"author":"William Shakespeare","year":1595,"type":"paperback","pages":200,"publisher":"PublisherA","language":"English","ISBN-10":"1234567890","ISBN-13":"123-1234567890"}{"id":0,"author":"William Shakespeare","year":1595,"type":"paperback","pages":200,"publisher":"PublisherA","language":"English","ISBN-10":"1234567890","ISBN-13":"123-1234567890"}RESOURCE_EXHAUSTED:Quota is exhausted for: requestcountRESOURCE_EXHAUSTED:Quota is exhausted for: requestcountRESOURCE_EXHAUSTED:Quota is exhausted for: requestcountRESOURCE_EXHAUSTED:Quota is exhausted for: requestcountRESOURCE_EXHAUSTED:Quota is exhausted for: requestcount
You'll notice that after three calls, the quota is exhausted.
In this part of the lab, you'll see how to expose external services (services not managed by Istio) to consumers within and outside the service mesh.
In this section, we will expose a service outside the mesh for consumption within the mesh. For this example, we will expose the service httpbin.org
to consumers within the mesh.
- Allows the mesh operator to control which services/endpoints are exposed inside the mesh
- Even if the endpoint changes the consumer is protected by these changes. Consumers access the external service using an internal name.
- Allows the mesh operator to apply security (TLS) before the call is made externally in one centralized location.
- Create a
ServiceEntry
A service entry describes the properties of a service (DNS name, VIPs ,ports, protocols, endpoints).
cat EOF << | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: httpbin-ext
namespace: default
spec:
hosts:
- httpbin.org
location: MESH_EXTERNAL
ports:
- name: http
number: 80
protocol: HTTP
resolution: DNS
EOF
- Create a
Service
apiVersion: v1
kind: Service
metadata:
labels:
app: httpbinapi
version: v1
name: httpbinapi
namespace: default
spec:
externalName: httpbin.org
selector:
app: httpbinapi
sessionAffinity: None
type: ExternalName
- Create a
VirtualService
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin-internal
namespace: default
spec:
gateways:
- mesh
hosts:
- httpbinapi.default.svc.cluster.local
http:
match:
- uri:
exact: /ip
rewrite:
authority: httpbin.org
uri: /ip
route:
- destination:
host: httpbin.org
Note the gateway uses the name mesh
. The reserved word mesh is used to imply all the sidecars in the mesh.
- Access the service from inside the mesh
kubectl exec -it mtlstest-5f4d7d858-zhcnc /bin/bash
curl httpbinapi.default.svc.cluster.local/ip -v
OUTPUT:
* Trying 34.238.48.57...
* TCP_NODELAY set
* Connected to httpbinapi.default.svc.cluster.local (34.238.48.57) port 80 (#0)
> GET /ip HTTP/1.1
> Host: httpbinapi.default.svc.cluster.local
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< server: envoy
< date: Mon, 23 Jul 2018 04:54:48 GMT
< content-type: application/json
< content-length: 28
< access-control-allow-origin: *
< access-control-allow-credentials: true
< via: 1.1 vegur
< x-envoy-upstream-service-time: 133
<
{"origin":"35.xxx.xxx.xxx"}
* Connection #0 to host httpbinapi.default.svc.cluster.local left intact
In this section, we will expose a service outside the mesh for consumption at the ingress. For this example, we will expose the service httpbin.org
at the ingress.
- Edit the
VirtualService
Add the following lines
- match:
- uri:
exact: /ip
rewrite:
authority: httpbin.org
uri: /ip
route:
- destination:
host: httpbin.org
- Access the service
curl $GATEWAY/ip -v
OUTPUT:
* Trying xx.xxx.xxx.203...
* TCP_NODELAY set
* Connected to xx.xxx.xxx.203 (35.203.186.203) port 80 (#0)
> GET /ip HTTP/1.1
> Host: xx.xxx.xxx.203
> User-Agent: curl/7.52.1
> Accept: */*
>
< HTTP/1.1 200 OK
< server: envoy
< date: Mon, 23 Jul 2018 04:57:57 GMT
< content-type: application/json
< content-length: 40
< access-control-allow-origin: *
< access-control-allow-credentials: true
< via: 1.1 vegur
< x-envoy-upstream-service-time: 135
<
{"origin":"10.138.0.7, xx.xxx.xxx.181"}
* Curl_http_done: called premature == 0
* Connection #0 to host xx.xxx.xxx.203 left intact
- Even if the endpoint changes the consumer is protected by these changes. Consumers access the external service using the ingress name.
- Allows the mesh operator to apply security (TLS) before the call is made externally in one centralized location.