Merge pull request #709 from worldtiki/exclude-labels

Copy labels from canary to primary workloads based on prefix rules
This commit is contained in:
Stefan Prodan
2020-10-21 18:12:51 +03:00
committed by GitHub
10 changed files with 131 additions and 43 deletions
+1
View File
@@ -125,6 +125,7 @@ Parameter | Description | Default
`serviceAccount.name` | The name of the service account to create or use. If not set and `serviceAccount.create` is `true`, a name is generated using the Flagger fullname | `""`
`serviceAccount.annotations` | Annotations for service account | `{}`
`ingressAnnotationsPrefix` | Annotations prefix for ingresses | `custom.ingress.kubernetes.io`
`includeLabelPrefix` | List of prefixes of labels that are copied when creating primary deployments or daemonsets. Use * to include all | `""`
`rbac.create` | If `true`, create and use RBAC resources | `true`
`rbac.pspEnabled` | If `true`, create and use a restricted pod security policy | `false`
`crd.create` | If `true`, create Flagger's CRDs (should be enabled for Helm v2 only) | `false`
+3
View File
@@ -106,6 +106,9 @@ spec:
{{- if .Values.ingressAnnotationsPrefix }}
- -ingress-annotations-prefix={{ .Values.ingressAnnotationsPrefix }}
{{- end }}
{{- if .Values.includeLabelPrefix }}
- -include-label-prefix={{ .Values.includeLabelPrefix }}
{{- end }}
{{- if .Values.ingressClass }}
- -ingress-class={{ .Values.ingressClass }}
{{- end }}
+5 -1
View File
@@ -43,6 +43,7 @@ var (
logLevel string
port string
msteamsURL string
includeLabelPrefix string
slackURL string
slackUser string
slackChannel string
@@ -74,6 +75,7 @@ func init() {
flag.StringVar(&slackChannel, "slack-channel", "", "Slack channel.")
flag.StringVar(&eventWebhook, "event-webhook", "", "Webhook for publishing flagger events")
flag.StringVar(&msteamsURL, "msteams-url", "", "MS Teams incoming webhook URL.")
flag.StringVar(&includeLabelPrefix, "include-label-prefix", "", "List of prefixes of labels that are copied when creating primary deployments or daemonsets. Use * to include all.")
flag.IntVar(&threadiness, "threadiness", 2, "Worker concurrency.")
flag.BoolVar(&zapReplaceGlobals, "zap-replace-globals", false, "Whether to change the logging level of the global zap logger.")
flag.StringVar(&zapEncoding, "zap-encoding", "json", "Zap logger encoding.")
@@ -184,7 +186,9 @@ func main() {
configTracker = &canary.NopTracker{}
}
canaryFactory := canary.NewFactory(kubeClient, flaggerClient, configTracker, labels, logger)
includeLabelPrefixArray := strings.Split(includeLabelPrefix, ",")
canaryFactory := canary.NewFactory(kubeClient, flaggerClient, configTracker, labels, includeLabelPrefixArray, logger)
c := controller.NewController(
kubeClient,
+15 -12
View File
@@ -22,11 +22,12 @@ var (
// DaemonSetController is managing the operations for Kubernetes DaemonSet kind
type DaemonSetController struct {
kubeClient kubernetes.Interface
flaggerClient clientset.Interface
logger *zap.SugaredLogger
configTracker Tracker
labels []string
kubeClient kubernetes.Interface
flaggerClient clientset.Interface
logger *zap.SugaredLogger
configTracker Tracker
labels []string
includeLabelPrefix []string
}
func (c *DaemonSetController) ScaleToZero(cd *flaggerv1.Canary) error {
@@ -76,7 +77,7 @@ func (c *DaemonSetController) ScaleFromZero(cd *flaggerv1.Canary) error {
// Initialize creates the primary DaemonSet, scales down the canary DaemonSet,
// and returns the pod selector label and container ports
func (c *DaemonSetController) Initialize(cd *flaggerv1.Canary) (err error) {
err = c.createPrimaryDaemonSet(cd)
err = c.createPrimaryDaemonSet(cd, c.includeLabelPrefix)
if err != nil {
return fmt.Errorf("createPrimaryDaemonSet failed: %w", err)
}
@@ -200,7 +201,7 @@ func (c *DaemonSetController) GetMetadata(cd *flaggerv1.Canary) (string, string,
return label, labelValue, ports, nil
}
func (c *DaemonSetController) createPrimaryDaemonSet(cd *flaggerv1.Canary) error {
func (c *DaemonSetController) createPrimaryDaemonSet(cd *flaggerv1.Canary, includeLabelPrefix []string) error {
targetName := cd.Spec.TargetRef.Name
primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name)
@@ -215,6 +216,9 @@ func (c *DaemonSetController) createPrimaryDaemonSet(cd *flaggerv1.Canary) error
targetName, cd.Namespace, canaryDae.Spec.UpdateStrategy.Type)
}
// Create the labels map but filter unwanted labels
labels := includeLabelsByPrefix(canaryDae.Labels, includeLabelPrefix)
label, labelValue, err := c.getSelectorLabel(canaryDae)
primaryLabelValue := fmt.Sprintf("%s-primary", labelValue)
if err != nil {
@@ -239,11 +243,10 @@ func (c *DaemonSetController) createPrimaryDaemonSet(cd *flaggerv1.Canary) error
// create primary daemonset
primaryDae = &appsv1.DaemonSet{
ObjectMeta: metav1.ObjectMeta{
Name: primaryName,
Namespace: cd.Namespace,
Labels: map[string]string{
label: primaryLabelValue,
},
Name: primaryName,
Namespace: cd.Namespace,
Labels: makePrimaryLabels(labels, primaryLabelValue, label),
Annotations: canaryDae.Annotations,
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(cd, schema.GroupVersionKind{
Group: flaggerv1.SchemeGroupVersion.Group,
+16 -13
View File
@@ -20,18 +20,19 @@ import (
// DeploymentController is managing the operations for Kubernetes Deployment kind
type DeploymentController struct {
kubeClient kubernetes.Interface
flaggerClient clientset.Interface
logger *zap.SugaredLogger
configTracker Tracker
labels []string
kubeClient kubernetes.Interface
flaggerClient clientset.Interface
logger *zap.SugaredLogger
configTracker Tracker
labels []string
includeLabelPrefix []string
}
// Initialize creates the primary deployment, hpa,
// scales to zero the canary deployment and returns the pod selector label and container ports
func (c *DeploymentController) Initialize(cd *flaggerv1.Canary) (err error) {
primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name)
if err := c.createPrimaryDeployment(cd); err != nil {
if err := c.createPrimaryDeployment(cd, c.includeLabelPrefix); err != nil {
return fmt.Errorf("createPrimaryDeployment failed: %w", err)
}
@@ -202,15 +203,18 @@ func (c *DeploymentController) GetMetadata(cd *flaggerv1.Canary) (string, string
return label, labelValue, ports, nil
}
func (c *DeploymentController) createPrimaryDeployment(cd *flaggerv1.Canary) error {
func (c *DeploymentController) createPrimaryDeployment(cd *flaggerv1.Canary, includeLabelPrefix []string) error {
targetName := cd.Spec.TargetRef.Name
primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name)
canaryDep, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(context.TODO(), targetName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("deplyoment %s.%s get query error: %w", targetName, cd.Namespace, err)
return fmt.Errorf("deployment %s.%s get query error: %w", targetName, cd.Namespace, err)
}
// Create the labels map but filter unwanted labels
labels := includeLabelsByPrefix(canaryDep.Labels, includeLabelPrefix)
label, labelValue, err := c.getSelectorLabel(canaryDep)
primaryLabelValue := fmt.Sprintf("%s-primary", labelValue)
if err != nil {
@@ -240,11 +244,10 @@ func (c *DeploymentController) createPrimaryDeployment(cd *flaggerv1.Canary) err
// create primary deployment
primaryDep = &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: primaryName,
Namespace: cd.Namespace,
Labels: map[string]string{
label: primaryLabelValue,
},
Name: primaryName,
Namespace: cd.Namespace,
Labels: makePrimaryLabels(labels, primaryLabelValue, label),
Annotations: canaryDep.Annotations,
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(cd, schema.GroupVersionKind{
Group: flaggerv1.SchemeGroupVersion.Group,
+19 -15
View File
@@ -8,34 +8,38 @@ import (
)
type Factory struct {
kubeClient kubernetes.Interface
flaggerClient clientset.Interface
logger *zap.SugaredLogger
configTracker Tracker
labels []string
kubeClient kubernetes.Interface
flaggerClient clientset.Interface
logger *zap.SugaredLogger
configTracker Tracker
labels []string
includeLabelPrefix []string
}
func NewFactory(kubeClient kubernetes.Interface,
flaggerClient clientset.Interface,
configTracker Tracker,
labels []string,
includeLabelPrefix []string,
logger *zap.SugaredLogger) *Factory {
return &Factory{
kubeClient: kubeClient,
flaggerClient: flaggerClient,
logger: logger,
configTracker: configTracker,
labels: labels,
kubeClient: kubeClient,
flaggerClient: flaggerClient,
logger: logger,
configTracker: configTracker,
labels: labels,
includeLabelPrefix: includeLabelPrefix,
}
}
func (factory *Factory) Controller(kind string) Controller {
deploymentCtrl := &DeploymentController{
logger: factory.logger,
kubeClient: factory.kubeClient,
flaggerClient: factory.flaggerClient,
labels: factory.labels,
configTracker: factory.configTracker,
logger: factory.logger,
kubeClient: factory.kubeClient,
flaggerClient: factory.flaggerClient,
labels: factory.labels,
configTracker: factory.configTracker,
includeLabelPrefix: factory.includeLabelPrefix,
}
daemonSetCtrl := &DaemonSetController{
logger: factory.logger,
+15
View File
@@ -4,6 +4,7 @@ import (
"crypto/rand"
"fmt"
"io"
"strings"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"
@@ -75,6 +76,20 @@ func makeAnnotations(annotations map[string]string) (map[string]string, error) {
return res, nil
}
func includeLabelsByPrefix(labels map[string]string, includeLabelPrefixes []string) map[string]string {
filteredLabels := make(map[string]string)
for key, value := range labels {
for _, includeLabelPrefix := range includeLabelPrefixes {
if includeLabelPrefix == "*" || strings.HasPrefix(key, includeLabelPrefix) {
filteredLabels[key] = value
break
}
}
}
return filteredLabels
}
func makePrimaryLabels(labels map[string]string, labelValue string, label string) map[string]string {
res := make(map[string]string)
for k, v := range labels {
+55
View File
@@ -0,0 +1,55 @@
package canary
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIncludeLabelsByPrefix(t *testing.T) {
labels := map[string]string{
"foo": "foo-value",
"bar": "bar-value",
"lorem": "ipsum",
}
includeLabelPrefix := []string{"foo", "lor"}
filteredLabels := includeLabelsByPrefix(labels, includeLabelPrefix)
assert.Equal(t, filteredLabels, map[string]string{
"foo": "foo-value",
"lorem": "ipsum",
// bar excluded
})
}
func TestIncludeLabelsByPrefixWithWildcard(t *testing.T) {
labels := map[string]string{
"foo": "foo-value",
"bar": "bar-value",
"lorem": "ipsum",
}
includeLabelPrefix := []string{"*"}
filteredLabels := includeLabelsByPrefix(labels, includeLabelPrefix)
assert.Equal(t, filteredLabels, map[string]string{
"foo": "foo-value",
"bar": "bar-value",
"lorem": "ipsum",
})
}
func TestMakePrimaryLabels(t *testing.T) {
labels := map[string]string{
"lorem": "ipsum",
"foo": "old-bar",
}
primaryLabels := makePrimaryLabels(labels, "new-bar", "foo")
assert.Equal(t, primaryLabels, map[string]string{
"lorem": "ipsum", // values from old map
"foo": "new-bar", // overriden value for a specific label
})
}
@@ -87,7 +87,7 @@ func newDaemonSetFixture(c *flaggerv1.Canary) daemonSetFixture {
KubeClient: kubeClient,
FlaggerClient: flaggerClient,
}
canaryFactory := canary.NewFactory(kubeClient, flaggerClient, configTracker, []string{"app", "name"}, logger)
canaryFactory := canary.NewFactory(kubeClient, flaggerClient, configTracker, []string{"app", "name"}, []string{""}, logger)
ctrl := &Controller{
kubeClient: kubeClient,
@@ -115,7 +115,7 @@ func newDeploymentFixture(c *flaggerv1.Canary) fixture {
KubeClient: kubeClient,
FlaggerClient: flaggerClient,
}
canaryFactory := canary.NewFactory(kubeClient, flaggerClient, configTracker, []string{"app", "name"}, logger)
canaryFactory := canary.NewFactory(kubeClient, flaggerClient, configTracker, []string{"app", "name"}, []string{""}, logger)
ctrl := &Controller{
kubeClient: kubeClient,