Skip to content

Commit e4984cc

Browse files
committed
LOG-7535: Add MaxUnavailable to API and enable kube caching
1 parent aa0ce7d commit e4984cc

40 files changed

+164
-266
lines changed

api/observability/v1/clusterlogforwarder_types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package v1
1717
import (
1818
corev1 "k8s.io/api/core/v1"
1919
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20+
"k8s.io/apimachinery/pkg/util/intstr"
2021
)
2122

2223
// ClusterLogForwarderSpec defines the desired state of ClusterLogForwarder
@@ -134,6 +135,13 @@ type CollectorSpec struct {
134135
// +kubebuilder:validation:Optional
135136
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Network Policy",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:text"}
136137
NetworkPolicy *NetworkPolicy `json:"networkPolicy,omitempty"`
138+
139+
// Define the maxUnavailable pod rollout strategy which defaults to 100% when not set
140+
//
141+
// Value can be a number (e.g., 50) or a percentage string (e.g., "50%").
142+
// +kubebuilder:validation:Type=string
143+
// +kubebuilder:validation:Pattern="^(?:[0-9]{1,2}|100)%?$"
144+
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`
137145
}
138146

139147
type NetworkPolicy struct {

api/observability/v1/conditions.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@ const (
3535
// ConditionTypeMaxUnavailable validates the value of the max-unavailable-rollout annotation
3636
ConditionTypeMaxUnavailable = GroupName + "/MaxUnavailableAnnotation"
3737

38-
// ConditionTypeUseKubeCache validates the value of the use-apiserver-cache annotation
39-
ConditionTypeUseKubeCache = GroupName + "/UseKubeCacheAnnotation"
40-
4138
// ConditionTypeReady indicates the service is ready.
4239
//
4340
// Ready=True means the operands are running and providing some service.

api/observability/v1/zz_generated.deepcopy.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bundle/manifests/observability.openshift.io_clusterlogforwarders.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,17 @@ spec:
948948
type: array
949949
type: object
950950
type: object
951+
maxUnavailable:
952+
anyOf:
953+
- type: integer
954+
- type: string
955+
description: |-
956+
Define the maxUnavailable pod rollout strategy which defaults to 100% when not set
957+
958+
Value can be a number (e.g., 50) or a percentage string (e.g., "50%").
959+
pattern: ^(?:[0-9]{1,2}|100)%?$
960+
type: string
961+
x-kubernetes-int-or-string: true
951962
networkPolicy:
952963
description: Define the Network Policy for the Collector
953964
nullable: true

config/crd/bases/observability.openshift.io_clusterlogforwarders.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,17 @@ spec:
948948
type: array
949949
type: object
950950
type: object
951+
maxUnavailable:
952+
anyOf:
953+
- type: integer
954+
- type: string
955+
description: |-
956+
Define the maxUnavailable pod rollout strategy which defaults to 100% when not set
957+
958+
Value can be a number (e.g., 50) or a percentage string (e.g., "50%").
959+
pattern: ^(?:[0-9]{1,2}|100)%?$
960+
type: string
961+
x-kubernetes-int-or-string: true
951962
networkPolicy:
952963
description: Define the Network Policy for the Collector
953964
nullable: true

docs/reference/operator/api_observability_v1.adoc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ Type:: object
7474
|Property|Type|Description
7575

7676
|affinity|object| Define scheduling rules that influence pod placement based on node or pod affinity/anti-affinity constraints.
77+
|maxUnavailable|object| Define the maxUnavailable pod rollout strategy which defaults to 100% when not set
78+
79+
Value can be a number (e.g., 50) or a percentage string (e.g., "50%").
7780
|networkPolicy|object| Define the Network Policy for the Collector
7881
|nodeSelector|object| Define nodes for scheduling the pods.
7982

@@ -877,6 +880,19 @@ Type:: object
877880

878881
Type:: array
879882

883+
=== .spec.collector.maxUnavailable
884+
885+
Type:: object
886+
887+
[options="header"]
888+
|======================
889+
|Property|Type|Description
890+
891+
|IntVal|int|
892+
|StrVal|string|
893+
|Type|int|
894+
|======================
895+
880896
=== .spec.collector.networkPolicy
881897

882898
Type:: object

internal/collector/collector.go

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ package collector
22

33
import (
44
"fmt"
5-
"k8s.io/apimachinery/pkg/util/intstr"
65
"strings"
76

7+
"k8s.io/apimachinery/pkg/util/intstr"
8+
89
log "github.com/ViaQ/logerr/v2/log/static"
910
"github.com/openshift/cluster-logging-operator/internal/auth"
1011
"github.com/openshift/cluster-logging-operator/internal/collector/common"
@@ -22,6 +23,9 @@ import (
2223
)
2324

2425
const (
26+
//DefaultMaxUnavailable is the maxUnavailable collector setting when not defined by spec.collector.maxUnavailable
27+
DefaultMaxUnavailable = "100%"
28+
2529
defaultAudience = "openshift"
2630
clusterLoggingPriorityClassName = "system-node-critical"
2731
MetricsPort = int32(24231)
@@ -69,9 +73,7 @@ type Factory struct {
6973
PodLabelVisitor PodLabelVisitor
7074
ResourceNames *factory.ForwarderResourceNames
7175
isDaemonset bool
72-
LogLevel string
73-
UseKubeCache bool
74-
MaxUnavailable string
76+
annotations map[string]string
7577
}
7678

7779
// CollectorResourceRequirements returns the resource requirements for a given collector implementation
@@ -89,12 +91,22 @@ func (f *Factory) NodeSelector() map[string]string {
8991
func (f *Factory) Tolerations() []v1.Toleration {
9092
return f.CollectorSpec.Tolerations
9193
}
92-
9394
func (f *Factory) Affinity() *v1.Affinity {
9495
return f.CollectorSpec.Affinity
9596
}
97+
func (f *Factory) MaxUnavailable() intstr.IntOrString {
98+
if f.CollectorSpec.MaxUnavailable != nil {
99+
return *f.CollectorSpec.MaxUnavailable
100+
}
101+
if f.annotations != nil {
102+
if value, found := f.annotations[constants.AnnotationMaxUnavailable]; found {
103+
return intstr.Parse(value)
104+
}
105+
}
106+
return intstr.Parse(DefaultMaxUnavailable)
107+
}
96108

97-
func New(confHash, clusterID string, collectorSpec *obs.CollectorSpec, secrets internalobs.Secrets, configMaps map[string]*v1.ConfigMap, forwarderSpec obs.ClusterLogForwarderSpec, resNames *factory.ForwarderResourceNames, isDaemonset bool, logLevel string, useCache bool, maxUnavailable string) *Factory {
109+
func New(confHash, clusterID string, collectorSpec *obs.CollectorSpec, secrets internalobs.Secrets, configMaps map[string]*v1.ConfigMap, forwarderSpec obs.ClusterLogForwarderSpec, resNames *factory.ForwarderResourceNames, isDaemonset bool, annotations map[string]string) *Factory {
98110
if collectorSpec == nil {
99111
collectorSpec = &obs.CollectorSpec{}
100112
}
@@ -113,16 +125,14 @@ func New(confHash, clusterID string, collectorSpec *obs.CollectorSpec, secrets i
113125
ResourceNames: resNames,
114126
PodLabelVisitor: vector.PodLogExcludeLabel,
115127
isDaemonset: isDaemonset,
116-
LogLevel: logLevel,
117-
UseKubeCache: useCache,
118-
MaxUnavailable: maxUnavailable,
128+
annotations: annotations,
119129
}
120130
return factory
121131
}
122132

123133
func (f *Factory) NewDaemonSet(namespace, name string, trustedCABundle *v1.ConfigMap, tlsProfileSpec configv1.TLSProfileSpec) *apps.DaemonSet {
124134
podSpec := f.NewPodSpec(trustedCABundle, f.ForwarderSpec, f.ClusterID, tlsProfileSpec, namespace)
125-
ds := factory.NewDaemonSet(namespace, name, name, constants.CollectorName, constants.VectorName, f.MaxUnavailable, *podSpec, f.CommonLabelInitializer, f.PodLabelVisitor)
135+
ds := factory.NewDaemonSet(namespace, name, name, constants.CollectorName, constants.VectorName, f.MaxUnavailable(), *podSpec, f.CommonLabelInitializer, f.PodLabelVisitor)
126136
ds.Spec.Template.Annotations[constants.AnnotationSecretHash] = f.Secrets.Hash64a()
127137
return ds
128138
}
@@ -143,6 +153,7 @@ func (f *Factory) NewPodSpec(trustedCABundle *v1.ConfigMap, spec obs.ClusterLogF
143153
TerminationGracePeriodSeconds: utils.GetPtr[int64](10),
144154
Tolerations: append(constants.DefaultTolerations(), f.Tolerations()...),
145155
Affinity: f.Affinity(),
156+
146157
Volumes: []v1.Volume{
147158
{Name: metricsVolumeName, VolumeSource: v1.VolumeSource{Secret: &v1.SecretVolumeSource{SecretName: f.ResourceNames.SecretMetrics}}},
148159
{Name: tmpVolumeName, VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{Medium: v1.StorageMediumMemory}}},
@@ -172,7 +183,7 @@ func (f *Factory) NewPodSpec(trustedCABundle *v1.ConfigMap, spec obs.ClusterLogF
172183

173184
addTrustedCABundle(collector, podSpec, trustedCABundle)
174185

175-
f.Visit(collector, podSpec, f.ResourceNames, namespace, f.LogLevel)
186+
f.Visit(collector, podSpec, f.ResourceNames, namespace, LogLevel(f.annotations))
176187

177188
podSpec.Containers = []v1.Container{
178189
*collector,
@@ -413,3 +424,10 @@ func hasTrustedCABundle(configMap *v1.ConfigMap) (string, bool) {
413424
caBundle, ok := configMap.Data[constants.TrustedCABundleKey]
414425
return caBundle, ok && caBundle != ""
415426
}
427+
428+
func LogLevel(annotations map[string]string) string {
429+
if level, ok := annotations[constants.AnnotationVectorLogLevel]; ok {
430+
return level
431+
}
432+
return "warn"
433+
}

internal/collector/collector_test.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package collector
22

33
import (
4+
"os"
5+
"path"
6+
47
. "github.com/onsi/ginkgo/v2"
58
. "github.com/onsi/gomega"
69
obs "github.com/openshift/cluster-logging-operator/api/observability/v1"
@@ -14,14 +17,40 @@ import (
1417
"github.com/openshift/cluster-logging-operator/internal/tls"
1518
"github.com/openshift/cluster-logging-operator/internal/utils"
1619
. "github.com/openshift/cluster-logging-operator/test/matchers"
17-
"os"
18-
"path"
19-
2020
v1 "k8s.io/api/core/v1"
2121
"k8s.io/apimachinery/pkg/api/resource"
2222
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
"k8s.io/apimachinery/pkg/util/intstr"
2324
)
2425

26+
var _ = Describe("Factory#MaxUnavalable", func() {
27+
var (
28+
factory Factory
29+
)
30+
BeforeEach(func() {
31+
factory = Factory{
32+
CollectorSpec: obs.CollectorSpec{},
33+
annotations: make(map[string]string),
34+
}
35+
})
36+
37+
Context("when evaluating MaxUnavailable", func() {
38+
It("should apply the default when nothing is not defined", func() {
39+
Expect(factory.MaxUnavailable().StrVal).To(Equal(DefaultMaxUnavailable))
40+
})
41+
It("should prefer spec over the deprecated annotation", func() {
42+
exp := intstr.Parse("30%")
43+
factory.CollectorSpec.MaxUnavailable = &exp
44+
Expect(factory.MaxUnavailable()).To(Equal(exp))
45+
})
46+
It("should honor the deprecated annotation", func() {
47+
exp := intstr.Parse("30%")
48+
factory.annotations[constants.AnnotationMaxUnavailable] = exp.StrVal
49+
Expect(factory.MaxUnavailable()).To(Equal(exp))
50+
})
51+
})
52+
})
53+
2554
var _ = Describe("Factory#Daemonset", func() {
2655
var (
2756
podSpec v1.PodSpec
@@ -99,7 +128,9 @@ var _ = Describe("Factory#Daemonset", func() {
99128

100129
It("should set VECTOR_LOG env variable with debug value", func() {
101130
logLevelDebug := "debug"
102-
factory.LogLevel = logLevelDebug
131+
factory.annotations = map[string]string{
132+
constants.AnnotationVectorLogLevel: logLevelDebug,
133+
}
103134

104135
podSpec = *factory.NewPodSpec(nil, obs.ClusterLogForwarderSpec{}, "1234", tls.GetClusterTLSProfileSpec(nil), constants.OpenshiftNS)
105136
collector = podSpec.Containers[0]

internal/constants/annotations.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,7 @@ const (
1818

1919
AnnotationSecretHash = "observability.openshift.io/secret-hash"
2020

21-
// AnnotationKubeCache is used to enable caching for requests to the kube-apiserver using vector kubernetes_logs source.
22-
// Tech-Preview feature
23-
//
24-
// While enabling cache can significantly reduce Kubernetes control plane
25-
// memory pressure, the trade-off is a chance of receiving stale data.
26-
AnnotationKubeCache = "observability.openshift.io/use-apiserver-cache"
27-
28-
// AnnotationMaxUnavailable configures the maximum number of DaemonSet pods that can be unavailable during a rolling update.
29-
// Tech-Preview feature
30-
//
21+
// AnnotationMaxUnavailable (Deprecated) configures the maximum number of DaemonSet pods that can be unavailable during a rolling update.
3122
// This can be an absolute number (e.g., 1) or a percentage (e.g., 10%). Default is 100%.
3223
AnnotationMaxUnavailable = "observability.openshift.io/max-unavailable-rollout"
3324
)

internal/controller/observability/collector.go

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"github.com/openshift/cluster-logging-operator/internal/runtime/serviceaccount"
2424
"github.com/openshift/cluster-logging-operator/internal/tls"
2525
"github.com/openshift/cluster-logging-operator/internal/utils"
26-
"github.com/openshift/cluster-logging-operator/internal/validations/observability"
2726
corev1 "k8s.io/api/core/v1"
2827
"sigs.k8s.io/controller-runtime/pkg/client"
2928
)
@@ -45,11 +44,6 @@ func ReconcileCollector(context internalcontext.ForwarderContext, pollInterval,
4544
options = context.AdditionalContext
4645
}
4746

48-
// Set kubeapi and rollout options based on annotation (LOG-7196)
49-
// TODO: replace with API fields
50-
SetKubeCacheOption(context.Forwarder.Annotations, options)
51-
SetMaxUnavailableRolloutOption(context.Forwarder.Annotations, options)
52-
5347
if internalobs.Outputs(context.Forwarder.Spec.Outputs).NeedServiceAccountToken() {
5448
// temporarily create SA token until collector is capable of dynamically reloading a projected serviceaccount token
5549
var sa *corev1.ServiceAccount
@@ -115,9 +109,7 @@ func ReconcileCollector(context internalcontext.ForwarderContext, pollInterval,
115109
context.Forwarder.Spec,
116110
resourceNames,
117111
isDaemonSet,
118-
LogLevel(context.Forwarder.Annotations),
119-
factory.IncludesKubeCacheOption(options),
120-
factory.GetMaxUnavailableValue(options),
112+
context.Forwarder.Annotations,
121113
)
122114

123115
if err = collectorFactory.ReconcileCollectorConfig(context.Client, context.Reader, context.Forwarder.Namespace, collectorConfig, ownerRef); err != nil {
@@ -171,7 +163,7 @@ func ReconcileCollector(context internalcontext.ForwarderContext, pollInterval,
171163
func GenerateConfig(k8Client client.Client, clf obs.ClusterLogForwarder, resourceNames factory.ForwarderResourceNames, secrets internalobs.Secrets, op framework.Options) (config string, err error) {
172164
tlsProfile, _ := tls.FetchAPIServerTlsProfile(k8Client)
173165
op[framework.ClusterTLSProfileSpec] = tls.GetClusterTLSProfileSpec(tlsProfile)
174-
//EvaluateAnnotationsForEnabledCapabilities(clusterRequest.Forwarder, op)
166+
EvaluateAnnotationsForEnabledCapabilities(clf.Annotations, op)
175167
g := forwardergenerator.New()
176168
generatedConfig, err := g.GenerateConf(secrets, clf.Spec, clf.Namespace, clf.Name, resourceNames, op)
177169

@@ -195,41 +187,6 @@ func EvaluateAnnotationsForEnabledCapabilities(annotations map[string]string, op
195187
if strings.ToLower(value) == "true" {
196188
options[generatorhelpers.EnableDebugOutput] = "true"
197189
}
198-
case constants.AnnotationKubeCache:
199-
// Matching the validate_annotations logic
200-
if observability.IsEnabledValue(value) {
201-
options[framework.UseKubeCacheOption] = "true"
202-
}
203-
case constants.AnnotationMaxUnavailable:
204-
// Matching the validate_annotations logic
205-
if observability.IsPercentOrWholeNumber(value) {
206-
options[framework.MaxUnavailableOption] = value
207-
}
208-
}
209-
}
210-
}
211-
212-
func LogLevel(annotations map[string]string) string {
213-
if level, ok := annotations[constants.AnnotationVectorLogLevel]; ok {
214-
return level
215-
}
216-
return "warn"
217-
}
218-
219-
func SetKubeCacheOption(annotations map[string]string, options framework.Options) {
220-
if value, found := annotations[constants.AnnotationKubeCache]; found {
221-
if observability.IsEnabledValue(value) {
222-
log.V(3).Info("Kube cache annotation found")
223-
options[framework.UseKubeCacheOption] = "true"
224-
}
225-
}
226-
}
227-
228-
func SetMaxUnavailableRolloutOption(annotations map[string]string, options framework.Options) {
229-
if value, found := annotations[constants.AnnotationMaxUnavailable]; found {
230-
if observability.IsPercentOrWholeNumber(value) {
231-
log.V(3).Info("Max Unavailable annotation found")
232-
options[framework.MaxUnavailableOption] = value
233190
}
234191
}
235192
}

0 commit comments

Comments
 (0)