Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9ba5bdf
feat(plugins): support ConfigMap inline local plugins alongside hostPath
Aug 4, 2025
278c337
feat(plugins): Add tests for support ConfigMap inline local plugins a…
Aug 4, 2025
4a43f73
feat(plugins): rm consecutive blank lines
Aug 4, 2025
12bf0a2
feat(plugins): add example comment
Aug 4, 2025
552cffa
Merge branch 'master' into feat/support-cm-local-plugins
cilasbeltrame Aug 5, 2025
b6df316
feat(plugins): add hello world example
Aug 8, 2025
0826e5d
Merge branch 'feat/support-cm-local-plugins' of github.com-cilasbeltr…
Aug 8, 2025
1ddb67e
feat(): rm blank spaces
Aug 8, 2025
dfaa36d
Merge branch 'master' into feat/support-cm-local-plugins
cilasbeltrame Aug 8, 2025
bc2af2f
feat(): add helper for local plugin cm and remove $root
Aug 11, 2025
1945596
Merge branch 'feat/support-cm-local-plugins' of github.com-cilasbeltr…
Aug 11, 2025
959cf65
Merge branch 'master' into feat/support-cm-local-plugins
cilasbeltrame Aug 14, 2025
bd92882
Merge branch 'master' into feat/support-cm-local-plugins
cilasbeltrame Sep 1, 2025
eb1cfaf
feat(): rm production deployment quote as it still under experimental
Sep 1, 2025
a7d0639
Merge branch 'master' into feat/support-cm-local-plugins
cilasbeltrame Sep 1, 2025
b33f4f5
feat(): add backwards compatibility with previous localPlugins declar…
Sep 1, 2025
4d31c24
feat(): fix schema
Sep 2, 2025
da1b2e3
feat(): fix helm tests
Sep 2, 2025
1a03526
feat(): update docs
Sep 2, 2025
24fa2cb
feat(): update docs
Sep 2, 2025
f7603c7
feat(): link markdown
Sep 2, 2025
b6fce53
feat(): type conditions
Sep 6, 2025
5dd9f84
Merge branch 'master' into feat/support-cm-local-plugins
Sep 27, 2025
1878378
feat(): fix unitest
Sep 27, 2025
38924a7
feat(): add source to provide cm data
Oct 21, 2025
53c6eb9
feat(): update migration example
Oct 21, 2025
14bb9e7
Merge branch 'master' into feat/support-cm-local-plugins
cilasbeltrame Oct 21, 2025
d284e50
Merge branch 'master' into feat/support-cm-local-plugins
cilasbeltrame Oct 23, 2025
1d95811
feat(): add required source key for inline plugin
Oct 30, 2025
5314ae9
Merge branch 'feat/support-cm-local-plugins' of github.com-cilasbeltr…
Oct 30, 2025
6ef72eb
Update EXAMPLES.md
cilasbeltrame Nov 5, 2025
d8c2c91
Update EXAMPLES.md
cilasbeltrame Nov 5, 2025
0d5b2a0
Update EXAMPLES.md
cilasbeltrame Nov 5, 2025
da6fa84
Update EXAMPLES.md
cilasbeltrame Nov 5, 2025
380a308
Merge branch 'master' into feat/support-cm-local-plugins
cilasbeltrame Nov 5, 2025
c7c10c0
feat(): rm readme files
Nov 6, 2025
a63f783
Merge branch 'feat/support-cm-local-plugins' of github.com-cilasbeltr…
Nov 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,62 @@ experimental:
>[!IMPORTANT]
> When using ``hostPath`` volumes, the plugin source code must be available on every node where Traefik pods might be scheduled.

### Using Inline Plugin

For testing or production deployments, embed plugin source directly in values.yaml:

```yaml
experimental:
localPlugins:
helloworld-plugin:
moduleName: github.com/example/helloworldplugin
mountPath: /plugins-local/src/github.com/example/helloworldplugin
inlinePlugin:
go.mod: |
module github.com/example/helloworldplugin

go 1.23
.traefik.yml: |
displayName: Hello World Plugin
type: middleware

import: github.com/example/helloworldplugin

summary: |
This is a simple plugin that prints "Hello, World!" to the response.

testData:
message: "Hello, World!"
main.go: |
package helloworldplugin

import (
"context"
"net/http"
)

type Config struct{}

func CreateConfig() *Config {
return &Config{}
}

type HelloWorld struct {
next http.Handler
}

func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
return &HelloWorld{next: next}, nil
}

func (h *HelloWorld) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
rw.Write([]byte("Hello, World!"))
h.next.ServeHTTP(rw, req)
}
```

> **Advantages**: No need for plugins on every node, better for containerized environments, supports up to 1MB of plugin code.

## Use Traefik native Let's Encrypt integration, without cert-manager

In Traefik Proxy, ACME certificates are stored in a JSON file.
Expand Down
4 changes: 4 additions & 0 deletions traefik/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ Hash: {{ sha1sum ($cert.Cert | b64enc) }}
{{- end -}}
{{- end -}}

{{- define "traefik.localPluginCmName" -}}
{{ include "traefik.fullname" .context }}-local-plugin-{{ .pluginName | replace "." "-" }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{{ include "traefik.fullname" .context }}-local-plugin-{{ .pluginName | replace "." "-" }}
{{ include "traefik.fullname" .context }}-local-plugin-{{ .pluginName | replace "." "-" }}

{{- end -}}

{{- define "traefik.hasPluginsVolume" -}}
{{- $found := false -}}
{{- range . -}}
Expand Down
7 changes: 7 additions & 0 deletions traefik/templates/_podtemplate.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -943,8 +943,15 @@
{{- end }}
{{- range $localPluginName, $localPlugin := .Values.experimental.localPlugins }}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the ordering of volumes a bit weird looking at it now; probably the plugin-related volumes, i.e. this range and the /plugins-storage mount, should be grouped. However that is probably more of a follow-up thing to clean.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Later on, I'm happy to work on this in a separate PR.

- name: {{ $localPluginName | replace "." "-" }}
{{- if $localPlugin.hostPath }}
hostPath:
path: {{ $localPlugin.hostPath | quote }}
{{- else if $localPlugin.inlinePlugin }}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of values, I find it to be slightly weird that now basically it would be allowed to set both hostPath and inlinePlugin, which will (silently) result in just using the former.

However, I think it's probably still fine, as the alternatives I can come up with are not much better:

  • add a required type value to localPlugin = bad because it's a breaking change and introduces an overhead field
  • add an optional type value which defaults to host = bad because overhead field
  • create an entire new inlinePlugins fields = bad because of a lot of duplicated code or merging is required
  • add an explicit check in the first case that errs if both values are set = bad because a bit ugly

@mloiseleur WDYT?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @jnoordsij — agreed. My goal here was to avoid any breaking changes. Since localPlugin only landed in v37.0.0, it may not be widely used yet, but changing its shape now would still be a breaking change. One alternative would be keeping the current key path and introduce the “more organized” structure alongside it, then deprecate the old one gradually. Not sure how this kind of situation is handled for this helm chart.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cilasbeltrame What kind of "more organized" structure you have in mind ?
On helm chart, it's quite rare to maintain both solution and deprecate.

Usually, we:

  1. Advertise the breaking changes: major version + upgrade instructions
  2. Add a check in requirements.yaml that will fail gracefully in case user did not notice the change despite 1.

See here for an example PR of breaking change about plugins.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking to implement what @jnoordsij said:

  • having a typePlugin with the options of: inline, host and possibly a localPath, cause having hostPath is not that well recommended, especially for security reasons and it requires additional work to pull the plugins from a repo or blob storage for example and save it into k8s nodes filesystem. localPath could be another future feature using Blob storage with PV/PVC, taking advantage of safer solutions such as s3-csi-driver or FUSE CSI driver for Google.
  • keep backwards compatibility with both and alert it to be depricated soon
  • next major version, we remove the backward compatibility.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds perfectly fine to me!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jnoordsij could you pls check the commit b33f4f5

where I have enabled backwards compatibility with previous localPlugin declaration + added two new options: inlinePlugin (config map based) and localPathPlugin where we can mount plugins using pod mountpoints, it allows us to extend storage support such as PVC, CSI drivers (s3-csi-driver, FUSE), and other volume types.

configMap:
name: {{ include "traefik.localPluginCmName" (dict "context" $ "pluginName" $localPluginName) }}
{{- else }}
{{- fail (printf "ERROR: local plugin %s must specify either hostPath or inlinePlugin!" $localPluginName) }}
{{- end }}
{{- end }}
{{- if and (gt (len .Values.experimental.plugins) 0) (ne (include "traefik.hasPluginsVolume" .Values.deployment.additionalVolumes) "true") }}
- name: plugins
Expand Down
19 changes: 19 additions & 0 deletions traefik/templates/local-plugins-cm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{{- if .Values.experimental.localPlugins }}
{{- range $localPluginName, $localPlugin := .Values.experimental.localPlugins }}
{{- if $localPlugin.inlinePlugin }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "traefik.localPluginCmName" (dict "context" $ "pluginName" $localPluginName) }}
namespace: {{ template "traefik.namespace" $ }}
labels:
{{- include "traefik.labels" $ | nindent 4 }}
data:
{{- range $fileName, $fileContent := $localPlugin.inlinePlugin }}
{{ $fileName }}: |
{{- $fileContent | nindent 4 }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
Loading