Skip to content

Commit e42e584

Browse files
Create a separate controller for generating availability metrics
1 parent 64c5943 commit e42e584

File tree

5 files changed

+105
-32
lines changed

5 files changed

+105
-32
lines changed

pkg/controllers/controllers.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

2828
v1 "github.com/aws/karpenter-provider-aws/pkg/apis/v1"
2929
sdk "github.com/aws/karpenter-provider-aws/pkg/aws"
30+
"github.com/aws/karpenter-provider-aws/pkg/controllers/metrics"
3031
"github.com/aws/karpenter-provider-aws/pkg/controllers/nodeclass"
3132
nodeclasshash "github.com/aws/karpenter-provider-aws/pkg/controllers/nodeclass/hash"
3233
controllersinstancetype "github.com/aws/karpenter-provider-aws/pkg/controllers/providers/instancetype"
@@ -97,6 +98,7 @@ func NewControllers(
9798
status.NewController[*v1.EC2NodeClass](kubeClient, mgr.GetEventRecorderFor("karpenter"), status.EmitDeprecatedMetrics),
9899
controllersversion.NewController(versionProvider, versionProvider.UpdateVersionWithValidation),
99100
capacityreservation.NewController(kubeClient, cloudProvider),
101+
metrics.NewController(kubeClient, cloudProvider),
100102
}
101103
if options.FromContext(ctx).InterruptionQueue != "" {
102104
sqsapi := servicesqs.NewFromConfig(cfg)
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
Licensed under the Apache License, Version 2.0 (the "License");
3+
you may not use this file except in compliance with the License.
4+
You may obtain a copy of the License at
5+
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License.
13+
*/
14+
15+
package metrics
16+
17+
import (
18+
"context"
19+
"time"
20+
21+
"github.com/awslabs/operatorpkg/singleton"
22+
"github.com/samber/lo"
23+
"k8s.io/apimachinery/pkg/util/sets"
24+
controllerruntime "sigs.k8s.io/controller-runtime"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
"sigs.k8s.io/controller-runtime/pkg/manager"
27+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
28+
karpv1 "sigs.k8s.io/karpenter/pkg/apis/v1"
29+
"sigs.k8s.io/karpenter/pkg/cloudprovider"
30+
)
31+
32+
type metricDimensions struct {
33+
instanceType string
34+
capacityType string
35+
zone string
36+
}
37+
38+
type Controller struct {
39+
kubeClient client.Client
40+
cloudProvider cloudprovider.CloudProvider
41+
}
42+
43+
func NewController(kubeClient client.Client, cloudProvider cloudprovider.CloudProvider) *Controller {
44+
return &Controller{
45+
kubeClient: kubeClient,
46+
cloudProvider: cloudProvider,
47+
}
48+
}
49+
50+
func (c *Controller) Reconcile(ctx context.Context) (reconcile.Result, error) {
51+
nodePools := &karpv1.NodePoolList{}
52+
if err := c.kubeClient.List(ctx, nodePools); err != nil {
53+
return reconcile.Result{}, err
54+
}
55+
availability := map[metricDimensions]bool{}
56+
price := map[metricDimensions]float64{}
57+
for _, nodePool := range nodePools.Items {
58+
instanceTypes, err := c.cloudProvider.GetInstanceTypes(ctx, &nodePool)
59+
if err != nil {
60+
return reconcile.Result{}, err
61+
}
62+
for _, instanceType := range instanceTypes {
63+
zones := sets.New[string]()
64+
for _, offering := range instanceType.Offerings {
65+
dimensions := metricDimensions{instanceType: instanceType.Name, capacityType: offering.CapacityType(), zone: offering.Zone()}
66+
availability[dimensions] = availability[dimensions] || offering.Available
67+
price[dimensions] = offering.Price
68+
zones.Insert(offering.Zone())
69+
}
70+
for zone := range zones {
71+
dimensions := metricDimensions{instanceType: instanceType.Name, capacityType: karpv1.CapacityTypeReserved, zone: zone}
72+
if _, ok := availability[dimensions]; !ok {
73+
availability[dimensions] = false
74+
price[dimensions] = 0
75+
}
76+
}
77+
}
78+
}
79+
80+
for dimensions, available := range availability {
81+
InstanceTypeOfferingAvailable.Set(float64(lo.Ternary(available, 1, 0)), map[string]string{
82+
instanceTypeLabel: dimensions.instanceType,
83+
capacityTypeLabel: dimensions.capacityType,
84+
zoneLabel: dimensions.zone,
85+
})
86+
}
87+
for dimensions, p := range price {
88+
InstanceTypeOfferingPriceEstimate.Set(p, map[string]string{
89+
instanceTypeLabel: dimensions.instanceType,
90+
capacityTypeLabel: dimensions.capacityType,
91+
zoneLabel: dimensions.zone,
92+
})
93+
}
94+
return reconcile.Result{RequeueAfter: time.Minute}, nil
95+
}
96+
97+
func (c *Controller) Register(_ context.Context, m manager.Manager) error {
98+
return controllerruntime.NewControllerManagedBy(m).
99+
Named("cloudprovider.metrics").
100+
WatchesRawSource(singleton.Source()).
101+
Complete(singleton.AsReconciler(c))
102+
}

pkg/providers/instancetype/offering/metrics.go renamed to pkg/controllers/metrics/metrics.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,12 @@ See the License for the specific language governing permissions and
1212
limitations under the License.
1313
*/
1414

15-
package offering
15+
package metrics
1616

1717
import (
1818
opmetrics "github.com/awslabs/operatorpkg/metrics"
1919
"github.com/prometheus/client_golang/prometheus"
2020
crmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
21-
2221
"sigs.k8s.io/karpenter/pkg/metrics"
2322
)
2423

pkg/providers/instancetype/metrics.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ import (
2525
const (
2626
cloudProviderSubsystem = "cloudprovider"
2727
instanceTypeLabel = "instance_type"
28-
capacityTypeLabel = "capacity_type"
29-
zoneLabel = "zone"
3028
)
3129

3230
var (

pkg/providers/instancetype/offering/provider.go renamed to pkg/providers/instancetype/offering/offering.go

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -78,34 +78,6 @@ func (p *DefaultProvider) InjectOfferings(
7878
allZones,
7979
subnetZones,
8080
)
81-
82-
reservedAvailability := map[string]bool{}
83-
for _, of := range offerings {
84-
// If the capacity type is reserved we need to determine if any of the reserved offerings are available. Otherwise,
85-
// we can update the availability metric directly.
86-
if of.CapacityType() == karpv1.CapacityTypeReserved {
87-
reservedAvailability[of.Zone()] = reservedAvailability[of.Zone()] || of.Available
88-
} else {
89-
InstanceTypeOfferingAvailable.Set(float64(lo.Ternary(of.Available, 1, 0)), map[string]string{
90-
instanceTypeLabel: it.Name,
91-
capacityTypeLabel: of.Requirements.Get(karpv1.CapacityTypeLabelKey).Any(),
92-
zoneLabel: of.Requirements.Get(corev1.LabelTopologyZone).Any(),
93-
})
94-
}
95-
InstanceTypeOfferingPriceEstimate.Set(of.Price, map[string]string{
96-
instanceTypeLabel: it.Name,
97-
capacityTypeLabel: of.Requirements.Get(karpv1.CapacityTypeLabelKey).Any(),
98-
zoneLabel: of.Requirements.Get(corev1.LabelTopologyZone).Any(),
99-
})
100-
}
101-
for zone := range allZones {
102-
InstanceTypeOfferingAvailable.Set(float64(lo.Ternary(reservedAvailability[zone], 1, 0)), map[string]string{
103-
instanceTypeLabel: it.Name,
104-
capacityTypeLabel: karpv1.CapacityTypeReserved,
105-
zoneLabel: zone,
106-
})
107-
}
108-
10981
// NOTE: By making this copy one level deep, we can modify the offerings without mutating the results from previous
11082
// GetInstanceTypes calls. This should still be done with caution - it is currently done here in the provider, and
11183
// once in the instance provider (filterReservedInstanceTypes)

0 commit comments

Comments
 (0)