diff --git a/app/_includes/md/custom-plugin.md b/app/_includes/md/custom-plugin.md new file mode 100644 index 000000000000..820e1dd60239 --- /dev/null +++ b/app/_includes/md/custom-plugin.md @@ -0,0 +1,50 @@ + +## Create a custom plugin + +1. Create a directory with test plugin code. + + {:.note} + > If you already have a real plugin, you can skip this step. + + ```bash + mkdir myheader + echo 'local MyHeader = {} + + MyHeader.PRIORITY = 1000 + MyHeader.VERSION = "1.0.0" + + function MyHeader:header_filter(conf) + -- do custom logic here + kong.response.set_header("myheader", conf.header_value) + end + + return MyHeader + ' > myheader/handler.lua + + echo 'return { + name = "myheader", + fields = { + { config = { + type = "record", + fields = { + { header_value = { type = "string", default = "roar", }, }, + }, + }, }, + } + } + ' > myheader/schema.lua + ``` + + After your plugin code available in a directory, the directory should look like this: + + ```bash + tree myheader + ``` + + ```bash + myheader + ├── handler.lua + └── schema.lua + + 0 directories, 2 files + ``` diff --git a/app/_includes/md/kgo/prerequisites.md b/app/_includes/md/kgo/prerequisites.md index 471d44f02912..a5a8197046f1 100644 --- a/app/_includes/md/kgo/prerequisites.md +++ b/app/_includes/md/kgo/prerequisites.md @@ -2,7 +2,7 @@
-

Before you begin ensure that you have installed the {{site.kgo_product_name}} in your Kubernetes cluster {% if include.aiGateway %}with AI Gateway support enabled{% endif %}. {% if include.enterprise %}This guide requires an enterprise license.{% endif %}

+

Before you begin ensure that you have installed the {{site.kgo_product_name}} in your Kubernetes cluster {% if include.aiGateway %}with AI Gateway support enabled{% endif %}{% if include.kongplugininstallation %}with KongPluginInstallation support enabled{% endif %}. {% if include.enterprise %}This guide requires an enterprise license.{% endif %}

@@ -47,6 +47,17 @@ kubectl set env -n kong-system deployments/kgo-gateway-operator-controller-manag ``` {% endif %} +{%- if include.kongplugininstallation %} + +### Enable the KongPluginInstallation controller + +As this guide uses the experimental KongPluginInstallation feature, we need to explicitly enable it. + +```bash +kubectl set env -n kong-system deployments/kgo-gateway-operator-controller-manager -c manager GATEWAY_OPERATOR_ENABLE_CONTROLLER_KONGPLUGININSTALLATION="true" +``` +{% endif %} + {% if include.enterprise %} ### Enterprise License diff --git a/app/_src/gateway-operator/guides/plugin-distribution.md b/app/_src/gateway-operator/guides/plugin-distribution.md index 159f53156703..0b816f86063a 100644 --- a/app/_src/gateway-operator/guides/plugin-distribution.md +++ b/app/_src/gateway-operator/guides/plugin-distribution.md @@ -1,5 +1,5 @@ --- -title: Kong Custom plugin distribution +title: Kong custom plugin distribution with KongPluginInstallation --- @@ -8,69 +8,274 @@ title: Kong Custom plugin distribution {% include md/kgo/prerequisites.md version=page.version release=page.release kongplugininstallation=true %} -## Create and package a custom plugin +{% include md/custom-plugin.md %} -1. Create a directory with test plugin code. +2. Build container image with the plugin code. + + It is expected to have plugin related files in the root of the image. Thus for aforementioned plugin, the Dockerfile would look like this: + + ```bash + echo 'FROM scratch + + COPY myheader / + ' > Dockerfile + ``` + + where `myheader` is a directory that contains `handler.lua` and `schema.lua`. + + Build the image: + + ```bash + docker build -t myheader:1.0.0 . + ``` + + next push it to a registry that is available to the Kubernetes cluster where {{ site.kgo_product_name }} is running. + + ```bash + docker tag myheader:1.0.0 /myheader:1.0.0 + docker push /myheader:1.0.0 + ``` - {:.note} - > If you already have a real plugin, you can skip this step. + The plugin from the example above is available in public Docker Hub as `kong/plugin-example:1.0.0`. You can use it too to follow this guide. - ```bash - $ mkdir myheader - $ echo 'local MyHeader = {} +3. Install the plugin using the `KongPluginInstallation` resource. It makes it available to be referenced by `Gateway` resources. - MyHeader.PRIORITY = 1000 - MyHeader.VERSION = "1.0.0" + ```yaml + echo ' + kind: KongPluginInstallation + apiVersion: gateway-operator.konghq.com/v1alpha1 + metadata: + name: custom-plugin-myheader + spec: + image: kong/plugin-example:1.0.0 + ' | kubectl apply -f - + ``` - function MyHeader:header_filter(conf) - -- do custom logic here - kong.response.set_header("myheader", conf.header_value) - end + Verify that the plugin is fetched and available by examining the status of the `KongPluginInstallation` resource: - return MyHeader - ' > myheader/handler.lua + ```bash + kubectl get kongplugininstallations.gateway-operator.konghq.com -o jsonpath-as-json='{.items[*].status}' + ``` - $ echo 'return { - name = "myheader", - fields = { - { config = { - type = "record", - fields = { - { header_value = { type = "string", default = "roar", }, }, - }, - }, }, - } + The output should look like this: + + ```json + [ + { + "conditions": [ + { + "lastTransitionTime": "2024-10-09T19:39:39Z", + "message": "plugin successfully saved in cluster as ConfigMap", + "observedGeneration": 1, + "reason": "Ready", + "status": "True", + "type": "Accepted" + } + ], + "underlyingConfigMapName": "custom-plugin-myheader-hnzf9" } - ' > myheader/schema.lua - ``` + ] + ``` - After your plugin code available in a directory, the directory should look like this: + in case of problems respective `conditions` will provide more information. - ```bash - $ tree myheader - myheader - ├── handler.lua - └── schema.lua + > `KonpluginInstalaltion` resource creates a ConfigMap with the plugin content. Some additional ConfigMaps are created + > when plugin is referenced by other resources. Lifecycle of all these ConfigMaps is managed automatically by the operator. - 0 directories, 2 files - ``` +4. Make plugin available in a `Gateway` resource by referencing it in the `spec.pluginsToInstall` field. + Plugins can be referenced cross-namespace without any additional configuration (for Secret used to authenticate + to container registry in case of cross-namespace `ReferenceGrant` in place is needed). -2. Build container image with the plugin code. + ```yaml + echo ' + kind: GatewayConfiguration + apiVersion: gateway-operator.konghq.com/v1beta1 + metadata: + name: kong + namespace: default + spec: + dataPlaneOptions: + deployment: + replicas: 2 + podTemplateSpec: + spec: + containers: + - name: proxy + image: kong/kong-gateway:3.8 + readinessProbe: + initialDelaySeconds: 1 + periodSeconds: 1 + pluginsToInstall: + - name: custom-plugin-myheader + controlPlaneOptions: + deployment: + podTemplateSpec: + spec: + containers: + - name: controller + image: kong/kubernetes-ingress-controller:3.3.1 + readinessProbe: + initialDelaySeconds: 1 + periodSeconds: 1 + --- + apiVersion: gateway.networking.k8s.io/v1 + kind: GatewayClass + metadata: + name: kong + spec: + controllerName: konghq.com/gateway-operator + parametersRef: + group: gateway-operator.konghq.com + kind: GatewayConfiguration + name: kong + namespace: default + --- + apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: kong + namespace: default + spec: + gatewayClassName: kong + listeners: + - name: http + protocol: HTTP + port: 80 + ' | kubectl apply -f - + ``` + +5. Deploy an example service and expose it by configuring`HTTPRoute` with custom plugin. + + Example service: + + ```yaml + echo ' + apiVersion: v1 + kind: Service + metadata: + name: echo + spec: + ports: + - protocol: TCP + name: http + port: 80 + targetPort: http + selector: + app: echo + --- + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app: echo + name: echo + spec: + replicas: 1 + selector: + matchLabels: + app: echo + template: + metadata: + labels: + app: echo + spec: + containers: + - name: echo + image: registry.k8s.io/e2e-test-images/agnhost:2.40 + command: + - /agnhost + - netexec + - --http-port=8080 + ports: + - containerPort: 8080 + name: http + env: + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + resources: + requests: + cpu: 10m + ' | kubectl apply -f - + ``` + + `HTTPRoute` with custom plugin, setting to a plugin are provided like for any other plugin with `KongPlugin` CRD where + field `plugin` is set to the name of the `KongPluginInstallation` resource. + + ```yaml + echo ' + apiVersion: configuration.konghq.com/v1 + kind: KongPlugin + metadata: + name: myheader + plugin: custom-plugin-myheader + config: + header_value: "my-first-plugin" + --- + apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-echo + namespace: default + annotations: + konghq.com/strip-path: "true" + konghq.com/plugins: myheader + spec: + parentRefs: + - name: kong + rules: + - matches: + - path: + type: PathPrefix + value: /echo + backendRefs: + - name: echo + kind: Service + port: 80 + ' | kubectl apply -f - + ``` + + The `HTTPRoute` above will route requests to the `echo` service and apply the plugin to respons. -It is expected to have plugin related files in the root of the image. Thus for aforementioned plugin, the Dockerfile would look like this: + 6. Ensure that everything is up and running and make a request to the service: -```Dockerfile -FROM scratch + To call the API, fetch the PROXY_IP for the Gateway: -COPY myheader / -``` + ```bash + export PROXY_IP=$(kubectl get gateway kong -o jsonpath='{.status.addresses[0].value}') + ``` -where `myheader` is a directory that contains `handler.lua` and `schema.lua`. + Finally, make a `curl` request to the service: -Build the image: + ```bash + curl -I $PROXY_IP/echo + ``` -```bash -docker build -t myheader:1.0.0 . -``` + The response should include the custom header set by the plugin: -next push it to a registry that is available to the Kubernetes cluster where {{ site.kgo_product_name }} is running. + ```txt + HTTP/1.1 200 OK + Content-Type: text/plain; charset=utf-8 + Content-Length: 61 + Connection: keep-alive + Date: Wed, 09 Oct 2024 20:21:23 GMT + Server: kong/3.8.0.0-enterprise-edition + myheader: my-first-plugin + X-Kong-Upstream-Latency: 3 + X-Kong-Proxy-Latency: 0 + Via: 1.1 kong/3.8.0.0-enterprise-edition + X-Kong-Request-Id: 6eec26150170fe3547bc1a4a20e93d74 + ``` diff --git a/app/_src/kubernetes-ingress-controller/plugins/custom.md b/app/_src/kubernetes-ingress-controller/plugins/custom.md index 338ba787a232..e569715ff32d 100644 --- a/app/_src/kubernetes-ingress-controller/plugins/custom.md +++ b/app/_src/kubernetes-ingress-controller/plugins/custom.md @@ -7,54 +7,9 @@ purpose: | Install a custom plugin in Kong without using a Docker build. -## Create a custom plugin - -1. Create a directory with test plugin code. - - {:.note} - > If you already have a real plugin, you can skip this step. - - ```bash - $ mkdir myheader - $ echo 'local MyHeader = {} - - MyHeader.PRIORITY = 1000 - MyHeader.VERSION = "1.0.0" - - function MyHeader:header_filter(conf) - -- do custom logic here - kong.response.set_header("myheader", conf.header_value) - end - - return MyHeader - ' > myheader/handler.lua - - $ echo 'return { - name = "myheader", - fields = { - { config = { - type = "record", - fields = { - { header_value = { type = "string", default = "roar", }, }, - }, - }, }, - } - } - ' > myheader/schema.lua - ``` - - After your plugin code available in a directory, the directory should look like this: - - ```bash - $ tree myheader - myheader - ├── handler.lua - └── schema.lua - - 0 directories, 2 files - ``` +{% include md/custom-plugin.md %} -1. Create a ConfigMap or Secret with the plugin code. If you're not sure which option is correct, use a `ConfigMap`. +2. Create a ConfigMap or Secret with the plugin code. If you're not sure which option is correct, use a `ConfigMap`. If you would like to install a plugin which is available as a rock from Luarocks, then you need to download it, unzip it and create a ConfigMap or secret from all the Lua files of the plugin.