Here you will learn how to configure and deploy an extension in Spring Cloud Gateway for Kubernetes. For more information, see Extensions Development.
Extensions can be added with two simple steps to any Gateway Instance, including those already running. In short, you need to create a Kubernetes ConfigMap and activate it in the desired Gateway Instance.
Note This guide is to configure an extension for a Kubernetes deployment. See Launch the Gateway if you are running a Standalone installation.
The requirements for these steps are:
gateway.enableExtensions=true
Extensions can be stored in Kubernetes ConfigMaps, OCI Images, or Volumes, each with their own considerations:
Provided you have the JAR package of less than 1MB, create a ConfigMap as follows:
$ kubectl create configmap extension-name --from-file=extension.jar -n gateway_namespace
The config map name will identify the extension later.
You can confirm that the ConfigMap was successfully created with the contents of the jar:
$ kubectl get configmap extension-name -o yaml
You will see something that looks like this:
apiVersion: v1
binaryData:
mycustomfilter-0.0.1-SNAPSHOT.jar: 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
kind: ConfigMap
metadata:
creationTimestamp: "2021-07-23T21:02:47Z"
name: my-custom-header
namespace: testing
resourceVersion: "10535421"
selfLink: /api/v1/namespaces/testing/configmaps/my-custom-header
uid: 405e72ce-b025-4552-bee3-5d795c9013ce
Note It's possible to create a config map with multiple jars, for example if you need a third-party library. Ensure that these do not cause classpath conflicts with project template. In case of doubt check the "Runtime description for Extensions developers" for full list of included libraries.
Once it is running, if you need to update it, you can easily do it and automatically restart the gateway with:
$ kubectl create configmap extension-name --from-file=extension.jar -o yaml --dry-run=client | kubectl apply -f -
Create a Dockerfile in the root of your project that contains the custom extension
FROM gradle:7-jdk17-alpine AS build
COPY --chown=gradle:gradle . /home/gradle/src
WORKDIR /home/gradle/src
RUN gradle build --no-daemon
FROM alpine
RUN mkdir -p /app/extensions
COPY --from=build /home/gradle/src/build/libs/*.jar /app/extensions/gateway-extension.jar
Now build the image using the docker command. You should specify the image registry you will push the extension to, this should be the same registry where the gateway and operator images reside. You can obtain it by running the command kubectl get deployment -o jsonpath="{..image}" -n spring-cloud-gateway
.
docker build . -t myregistry.example.com/scg-test-extensions:dev
Then push the image to the registry.
docker push myregistry.example.com/scg-test-extensions:dev
Now you can use the custom extension by creating a gateway like this:
apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGateway
metadata:
name: test-gateway
spec:
extensions:
custom:
- myregistry.example.com/scg-test-extensions:dev
This will create a gateway that uses a Kubernetes Init Container to load the custom extensions.
For extensions larger than 1MB, you can load extensions from Kubernetes Volumes.
The example below shows how to setup a Persistent Volume using exclusively Kubernetes tools. Please refer to your storage implementations in your Cloud/K8s provider, ideal solutions would provide direct access to the storage backend in order to update the contents of the volumes without additional Kubernetes components.
Persistent Volumes comes with some restrictions:
upload-extensions-pod
in the example below)./{mount_name}/extensions
./{mount_name}/lost+found/
directory that is not readable. This potential issue is mitigated by expecting the extensions in the /{mount_name}/extensions/
subdirectory.ReadWriteOnce
, the gateway pods must be scheduled on the same node to concurrently access the volume. For High-Availability scenarios (gateways across multiple cluster nodes), a StorageClass with support for ReadOnlyMany
is required. Consult specific storage implementations for your Cloud/K8s provider.ReadWriteMany
or ReadOnlyMany
, the gateway pods can be scheduled under different nodes. However, a cloud provider may decide to have the PersistentVolume only be accessible within one Availability Zone (AZ), so the gateway pods need to be scheduled to the same AZ. Consult specific storage implementations for your Cloud/K8s provider.To test this feature, create a PersistentVolumeClaim, along with a pod for uploading files. For details on how to create a matching compatible Volume, we suggest starting checking Kubernetes docs on Persistent Volumes.
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: extensions-pvc
spec:
storageClassName: standard
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: upload-extensions-pod
spec:
containers:
- name: task-pv-container
image: nginx
volumeMounts:
- mountPath: /mount
name: extensions
initContainers:
- name: init-extensions-dir
image: nginx
command: ['sh', '-c', 'mkdir -p /mount/extensions']
volumeMounts:
- mountPath: /mount
name: extensions
volumes:
- name: extensions
persistentVolumeClaim:
claimName: extensions-pvc
Next, copy your extension to /mount/extensions
$ kubectl cp add-my-custom-header.jar upload-extensions-pod:/mount/extensions/add-my-custom-header.jar -c task-pv-container
Finally, specify the PersistentVolumeClaim in the custom extensions array of the gateway and the custom route filter in the route config, as shown in the next section.
With the extension deployed in the cluster, update or create a new Gateway with the extensions
option.
apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGateway
metadata:
name: my-gateway
spec:
extensions:
custom:
- extension-name
This will automatically restart the Gateway with the new extension(s) available.
Now that the extension is available, it can be used in the respective Route Configuration.
apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGatewayRouteConfig
metadata:
name: my-gateway-routes
spec:
routes:
- uri: https://httpbingo.org
predicates:
- Path=/add-header/**
filters:
- AddMyCustomHeader
If there's no mapping already, add it to complete the configuration.
apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGatewayMapping
metadata:
name: test-gateway-mapping
spec:
gatewayRef:
name: my-gateway
routeConfigRef:
name: my-gateway-routes
With the deployment completed, activate traffic to the gateway kubectl port-forward service/my-gateway 8080:80
and open http://localhost:8080/add-header/get in your web browser.
You will be greeted with a response similar to the one below, for simplicity some data has been removed. There you should see the custom X-My-Header
header.
{
"args": {},
"headers": {
...
"Host": ["httpbingo.org"],
"X-Forwarded-Host": ["localhost:8080"],
"X-Forwarded-Prefix": ["/add-header"],
"X-My-Header": ["my-header-value"]
},
"url": "https://localhost:8080/get"
}
If you cannot see the extension working:
kubectl logs statefulset.apps/my-gateway
) to validate if your log traces appear.kubectl describe scg my-gateway
for diagnostics messages. If the extension could not be loaded you will see a message like ConfigMap '{extension_name}' not found. Skipping configuration.
Previous kubectl cp
approach can cause issues with providers that don't support ReadWriteMany
. For example Google Kubernetes Engine only supports ReadWriteOnce
and ReadOnlyMany
. That means that the upload pod cannot simultaneously run with multiple gateway instances in different nodes.
In those scenarios, to provide 100% availability you can use the automatic update features of SCG for K8s by switching to a different PersistentVolumeClaim. Provided you have:
You should:
extensions.custom
value with a new VolumeClaim name apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGateway
metadata:
name: my-gateway
spec:
extensions:
custom:
- extension-name-new
This will initiate a controlled update of the Gateway instances one by one ensuring no downtime.