Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions charts/flagger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ The following tables lists the configurable parameters of the Flagger chart and
| `serviceMonitor.namespace` | Namespace Servicemonitor is installed in | the same namespace |
| `serviceMonitor.labels` | labels for the ServiceMonitor passed to Prometheus Operator | `{}` |
| `configTracking.enabled` | If `true`, flagger will track changes in Secrets and ConfigMaps referenced in the target deployment | `true` |
| `configTracking.binaryData` | If `true`, changes to ConfigMap binaryData will trigger a canary analysis | `false` |
| `eventWebhook` | If set, Flagger will publish events to the given webhook | None |
| `slack.url` | Slack incoming webhook | None |
| `slack.proxyUrl` | Slack proxy url | None |
Expand Down
3 changes: 3 additions & 0 deletions charts/flagger/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ spec:
{{- end }}
{{- if .Values.configTracking }}
- -enable-config-tracking={{ .Values.configTracking.enabled }}
{{- if .Values.configTracking.binaryData }}
- -enable-config-binary-data-tracking={{ .Values.configTracking.binaryData }}
{{- end }}
{{- end }}
{{- if .Values.namespace }}
- -namespace={{ .Values.namespace }}
Expand Down
2 changes: 2 additions & 0 deletions charts/flagger/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ selectorLabels: ""
# when enabled, flagger will track changes in Secrets and ConfigMaps referenced in the target deployment (enabled by default)
configTracking:
enabled: true
# when enabled, changes to ConfigMap binaryData will trigger a canary analysis (disabled by default)
binaryData: false

# annotations prefix for NGINX ingresses
ingressAnnotationsPrefix: ""
Expand Down
9 changes: 6 additions & 3 deletions cmd/flagger/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ var (
enableLeaderElection bool
leaderElectionNamespace string
enableConfigTracking bool
enableConfigBinaryData bool
ver bool
kubeconfigServiceMesh string
clusterName string
Expand Down Expand Up @@ -119,6 +120,7 @@ func init() {
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, "Enable leader election.")
flag.StringVar(&leaderElectionNamespace, "leader-election-namespace", "kube-system", "Namespace used to create the leader election config map.")
flag.BoolVar(&enableConfigTracking, "enable-config-tracking", true, "Enable secrets and configmaps tracking.")
flag.BoolVar(&enableConfigBinaryData, "enable-config-binary-data-tracking", false, "Enable change detection for configmaps binaryData.")
flag.BoolVar(&ver, "version", false, "Print version")
flag.StringVar(&kubeconfigServiceMesh, "kubeconfig-service-mesh", "", "Path to a kubeconfig for the service mesh control plane cluster.")
flag.StringVar(&clusterName, "cluster-name", "", "Cluster name to be included in alert msgs.")
Expand Down Expand Up @@ -233,9 +235,10 @@ func main() {
var configTracker canary.Tracker
if enableConfigTracking {
configTracker = &canary.ConfigTracker{
Logger: logger,
KubeClient: kubeClient,
FlaggerClient: flaggerClient,
Logger: logger,
KubeClient: kubeClient,
FlaggerClient: flaggerClient,
TrackBinaryData: enableConfigBinaryData,
}
} else {
configTracker = &canary.NopTracker{}
Expand Down
6 changes: 6 additions & 0 deletions docs/gitbook/usage/how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ command flag in the Flagger deployment manifest under containers args
or by setting `--set configTracking.enabled=false` when installing Flagger with Helm,
but disabling config-tracking using the per Secret/ConfigMap annotation may fit your use-case better.

The primary copy of a ConfigMap includes both `data` and `binaryData`,
but only changes to `data` trigger a canary analysis by default.
To also detect changes to `binaryData`, use the `-enable-config-binary-data-tracking`
command flag or set `--set configTracking.binaryData=true` when installing Flagger with Helm.
Enabling or disabling this flag starts one analysis for each canary whose target references a ConfigMap with `binaryData`.

The autoscaler reference is optional, when specified,
Flagger will pause the traffic increase while the target and primary deployments are scaled up or down.
HPA can help reduce the resource usage during the canary analysis.
Expand Down
23 changes: 18 additions & 5 deletions pkg/canary/config_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ import (

// ConfigTracker is managing the operations for Kubernetes ConfigMaps and Secrets
type ConfigTracker struct {
KubeClient kubernetes.Interface
FlaggerClient clientset.Interface
Logger *zap.SugaredLogger
KubeClient kubernetes.Interface
FlaggerClient clientset.Interface
Logger *zap.SugaredLogger
TrackBinaryData bool
}

type ConfigRefType string
Expand Down Expand Up @@ -88,10 +89,21 @@ func (ct *ConfigTracker) getRefFromConfigMap(name string, namespace string) (*Co
return &ConfigRef{
Name: config.Name,
Type: ConfigRefMap,
Checksum: checksum(config.Data),
Checksum: ct.configMapChecksum(config),
}, nil
}

func (ct *ConfigTracker) configMapChecksum(config *corev1.ConfigMap) string {
if !ct.TrackBinaryData || len(config.BinaryData) == 0 {
return checksum(config.Data)
}

return checksum(struct {
Data map[string]string
BinaryData map[string][]byte
}{config.Data, config.BinaryData})
}

// getRefFromConfigMap transforms a Kubernetes Secret into a ConfigRef
// and computes the checksum of the Secret data
func (ct *ConfigTracker) getRefFromSecret(name string, namespace string) (*ConfigRef, error) {
Expand Down Expand Up @@ -332,7 +344,8 @@ func (ct *ConfigTracker) CreatePrimaryConfigs(cd *flaggerv1.Canary, refs map[str
Labels: labels,
OwnerReferences: ownerReferences,
},
Data: config.Data,
Data: config.Data,
BinaryData: config.BinaryData,
}

// update or insert primary ConfigMap
Expand Down
66 changes: 66 additions & 0 deletions pkg/canary/config_tracker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
k8sTesting "k8s.io/client-go/testing"
Expand Down Expand Up @@ -393,6 +394,71 @@ func TestConfigTracker_HasConfigChanged_ShouldReturnErrorWhenAPIServerIsDown(t *
})
}

func TestConfigTracker_ConfigMapBinaryData(t *testing.T) {
invalidUTF8A := map[string][]byte{"truststore.p12": {0xff}}
invalidUTF8B := map[string][]byte{"truststore.p12": {0xfe}}

t.Run("primary keeps binaryData", func(t *testing.T) {
mocks := newBinaryDataTestFixture(t, false, invalidUTF8A)
mocks.initializeCanary(t)

primary, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-vol-primary", metav1.GetOptions{})
require.NoError(t, err)
assert.Equal(t, invalidUTF8A, primary.BinaryData)
})

t.Run("checksum stays data only unless binaryData is tracked", func(t *testing.T) {
for _, mocks := range []deploymentControllerFixture{
newBinaryDataTestFixture(t, false, invalidUTF8A),
newBinaryDataTestFixture(t, true, nil),
} {
assert.Equal(t, checksum(newDeploymentControllerTestConfigMapVol().Data), (*mocks.canary.Status.TrackedConfigs)["configmap/podinfo-config-vol"])
}
})

t.Run("binaryData change", func(t *testing.T) {
for _, track := range []bool{false, true} {
mocks := newBinaryDataTestFixture(t, track, invalidUTF8A)
changed, err := mocks.controller.configTracker.HasConfigChanged(mocks.canary)
require.NoError(t, err)
assert.False(t, changed)

updateConfigMapVol(t, mocks, func(cm *corev1.ConfigMap) { cm.BinaryData = invalidUTF8B })
changed, err = mocks.controller.configTracker.HasConfigChanged(mocks.canary)
require.NoError(t, err)
assert.Equal(t, track, changed, "tracking %v", track)
}
})

t.Run("data change with binaryData tracked", func(t *testing.T) {
mocks := newBinaryDataTestFixture(t, true, invalidUTF8A)
updateConfigMapVol(t, mocks, func(cm *corev1.ConfigMap) { cm.Data["color"] = "blue" })

changed, err := mocks.controller.configTracker.HasConfigChanged(mocks.canary)
require.NoError(t, err)
assert.True(t, changed)
})
}

func newBinaryDataTestFixture(t *testing.T, trackBinaryData bool, binaryData map[string][]byte) deploymentControllerFixture {
mocks := newDeploymentFixture(deploymentConfigs{name: "podinfo", label: "name", labelValue: "podinfo"})
mocks.controller.configTracker.(*ConfigTracker).TrackBinaryData = trackBinaryData
updateConfigMapVol(t, mocks, func(cm *corev1.ConfigMap) { cm.BinaryData = binaryData })

configs, err := mocks.controller.configTracker.GetConfigRefs(mocks.canary)
require.NoError(t, err)
mocks.canary.Status.TrackedConfigs = configs
return mocks
}

func updateConfigMapVol(t *testing.T, mocks deploymentControllerFixture, update func(*corev1.ConfigMap)) {
configMap, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-vol", metav1.GetOptions{})
require.NoError(t, err)
update(configMap)
_, err = mocks.kubeClient.CoreV1().ConfigMaps("default").Update(context.TODO(), configMap, metav1.UpdateOptions{})
require.NoError(t, err)
}

func Test_fieldIsMandatory(t *testing.T) {
falsy := false
truthy := true
Expand Down