@@ -10,7 +10,6 @@ import (
1010 "maps"
1111 "net/http"
1212 "os"
13- "reflect"
1413 "sort"
1514 "strings"
1615 "time"
@@ -31,7 +30,6 @@ import (
3130 "helm.sh/helm/v3/pkg/storage/driver"
3231 batchv1 "k8s.io/api/batch/v1"
3332 corev1 "k8s.io/api/core/v1"
34- apierrors "k8s.io/apimachinery/pkg/api/errors"
3533 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3634 "k8s.io/apimachinery/pkg/runtime"
3735 "k8s.io/apimachinery/pkg/runtime/serializer"
@@ -44,6 +42,8 @@ import (
4442const (
4543 TraefikNamespace = "traefik"
4644 TraefikPrivateLinkServiceName = "traefik"
45+
46+ AzureLinuxImage = "mcr.microsoft.com/azurelinux/base/core:3.0"
4747)
4848
4949func (inst * Installer ) installTraefik (ctx context.Context , restConfigPromise * install.Promise [* rest.Config ], keyVaultClientManagedIdentityPromise * install.Promise [* armmsi.Identity ]) (any , error ) {
@@ -54,11 +54,6 @@ func (inst *Installer) installTraefik(ctx context.Context, restConfigPromise *in
5454
5555 log .Ctx (ctx ).Info ().Msg ("Installing Traefik" )
5656
57- clientset := kubernetes .NewForConfigOrDie (restConfig )
58- if err := inst .ensureTraefikDynamicConfigMap (ctx , clientset ); err != nil {
59- return nil , fmt .Errorf ("failed to ensure Traefik dynamic ConfigMap: %w" , err )
60- }
61-
6257 var serviceAnnotations map [string ]any
6358 if inst .Config .Cloud .PrivateNetworking {
6459 serviceAnnotations = map [string ]any {
@@ -112,6 +107,11 @@ func (inst *Installer) installTraefik(ctx context.Context, restConfigPromise *in
112107 "additionalArguments" : []string {
113108 "--entryPoints.websecure.http.tls=true" ,
114109 },
110+ "deployment" : map [string ]any {
111+ "additionalContainers" : []corev1.Container {
112+ getConfigReloaderSidecar (),
113+ },
114+ },
115115 }}
116116
117117 usingCertificate := keyVaultClientManagedIdentityPromise != nil
@@ -125,30 +125,29 @@ func (inst *Installer) installTraefik(ctx context.Context, restConfigPromise *in
125125 "azure.workload.identity/client-id" : * kvClientIdentity .Properties .ClientID ,
126126 }
127127
128- traefikConfig .Values ["volumes " ] = []any {
128+ traefikConfig .Values ["deployment" ].( map [ string ] any )[ "additionalVolumes " ] = []any {
129129 map [string ]any {
130- "name" : "traefik-dynamic" ,
131- "mountPath" : "/config" ,
132- "type" : "configMap" ,
130+ "name" : "traefik-dynamic" ,
131+ "emptyDir" : map [string ]any {},
133132 },
134- }
135-
136- traefikConfig .Values ["deployment" ] = map [string ]any {
137- "additionalVolumes" : []any {
138- map [string ]any {
139- "name" : "kv-certs" ,
140- "csi" : map [string ]any {
141- "driver" : "secrets-store.csi.k8s.io" ,
142- "readOnly" : true ,
143- "volumeAttributes" : map [string ]any {
144- "secretProviderClass" : inst .Config .Cloud .TlsCertificate .CertificateName ,
145- },
133+ map [string ]any {
134+ "name" : "kv-certs" ,
135+ "csi" : map [string ]any {
136+ "driver" : "secrets-store.csi.k8s.io" ,
137+ "readOnly" : true ,
138+ "volumeAttributes" : map [string ]any {
139+ "secretProviderClass" : inst .Config .Cloud .TlsCertificate .CertificateName ,
146140 },
147141 },
148142 },
149143 }
150144
151145 traefikConfig .Values ["additionalVolumeMounts" ] = []any {
146+ map [string ]any {
147+ "name" : "traefik-dynamic" ,
148+ "mountPath" : "/config" ,
149+ "readOnly" : true ,
150+ },
152151 map [string ]any {
153152 "name" : "kv-certs" ,
154153 "mountPath" : "/certs" ,
@@ -158,7 +157,7 @@ func (inst *Installer) installTraefik(ctx context.Context, restConfigPromise *in
158157
159158 traefikConfig .Values ["additionalArguments" ] = append (
160159 traefikConfig .Values ["additionalArguments" ].([]string ),
161- "--providers.file.filename =/config/dynamic.toml " )
160+ "--providers.file.directory =/config" )
162161 }
163162
164163 var overrides * HelmChartConfig
@@ -228,57 +227,42 @@ func (inst *Installer) installTraefik(ctx context.Context, restConfigPromise *in
228227 return nil , nil
229228}
230229
231- func (inst * Installer ) ensureTraefikDynamicConfigMap (ctx context.Context , clientset * kubernetes.Clientset ) error {
232- configMapName := "traefik-dynamic"
233- namespace := TraefikNamespace
234-
235- desiredData := map [string ]string {
236- "dynamic.toml" : `
237- # Dynamic configuration
238- [[tls.certificates]]
239- certFile = "/certs/tls.crt"
240- keyFile = "/certs/tls.key"
241- ` ,
242- }
243-
244- // Check if the ConfigMap already exists
245- existingConfigMap , err := clientset .CoreV1 ().ConfigMaps (namespace ).Get (ctx , configMapName , metav1.GetOptions {})
246- if err == nil {
247- // ConfigMap exists, check if the data matches
248- if reflect .DeepEqual (existingConfigMap .Data , desiredData ) {
249- // No update needed
250- return nil
251- }
252-
253- // Update the ConfigMap if the data is different
254- existingConfigMap .Data = desiredData
255- _ , err = clientset .CoreV1 ().ConfigMaps (namespace ).Update (ctx , existingConfigMap , metav1.UpdateOptions {})
256- if err != nil {
257- return fmt .Errorf ("failed to update ConfigMap: %w" , err )
258- }
259- return nil
260- }
261-
262- if ! apierrors .IsNotFound (err ) {
263- // Return any error other than "not found"
264- return fmt .Errorf ("failed to get ConfigMap: %w" , err )
265- }
266-
267- // ConfigMap does not exist, create it
268- newConfigMap := & corev1.ConfigMap {
269- ObjectMeta : metav1.ObjectMeta {
270- Name : configMapName ,
271- Namespace : namespace ,
230+ // This is a sidecar for the Traefik pod that writes a "dynamic" configuration file containing TLS certificate paths.
231+ // Whenever it detects that the cert has changed, it touches the configuration file to trigger Traefik to reload it.
232+ func getConfigReloaderSidecar () corev1.Container {
233+ script := `
234+ crt_hash=$(sha256sum /certs/tls.crt 2> /dev/null)
235+ echo '{"tls": {"certificates": [{"certFile":"/certs/tls.crt","keyFile":"/certs/tls.key"}]}}' > /config/dynamic-config.yml;
236+
237+ while true; do
238+ sleep 5m
239+ new_hash=$(sha256sum /certs/tls.crt 2> /dev/null)
240+ if [ "$crt_hash" != "$new_hash" ]; then
241+ echo "Certificate changed, updating Traefik configuration"
242+ touch /config/dynamic-config.yml
243+ crt_hash=$new_hash
244+ fi
245+ done
246+ `
247+
248+ configReloaderSidecar := corev1.Container {
249+ Name : "config-reloader" ,
250+ Image : AzureLinuxImage ,
251+ Command : []string {"bash" , "-c" , script },
252+ VolumeMounts : []corev1.VolumeMount {
253+ {
254+ Name : "traefik-dynamic" ,
255+ MountPath : "/config" ,
256+ ReadOnly : false ,
257+ },
258+ {
259+ Name : "kv-certs" ,
260+ MountPath : "/certs" ,
261+ ReadOnly : true ,
262+ },
272263 },
273- Data : desiredData ,
274- }
275-
276- _ , err = clientset .CoreV1 ().ConfigMaps (namespace ).Create (ctx , newConfigMap , metav1.CreateOptions {})
277- if err != nil {
278- return fmt .Errorf ("failed to create ConfigMap: %w" , err )
279264 }
280-
281- return nil
265+ return configReloaderSidecar
282266}
283267
284268func (inst * Installer ) installCertManager (ctx context.Context , restConfigPromise * install.Promise [* rest.Config ]) (any , error ) {
0 commit comments