Skip to content

Commit 6bf80f8

Browse files
author
Arvind Thirumurugan
committed
add approvalrequest controller helm chart
Signed-off-by: Arvind Thirumurugan <[email protected]>
1 parent e9afeeb commit 6bf80f8

File tree

18 files changed

+2061
-0
lines changed

18 files changed

+2061
-0
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Makefile for ApprovalRequest Controller
2+
3+
# Image settings
4+
IMAGE_NAME ?= approval-request-controller
5+
IMAGE_TAG ?= latest
6+
REGISTRY ?=
7+
8+
# Build settings
9+
GOARCH ?= amd64
10+
GOOS ?= linux
11+
12+
# Tools
13+
CONTROLLER_GEN_VERSION ?= v0.16.0
14+
CONTROLLER_GEN = go run sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION)
15+
16+
.PHONY: help
17+
help: ## Display this help
18+
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
19+
20+
##@ Code Generation
21+
22+
.PHONY: manifests
23+
manifests: ## Generate CRD manifests
24+
$(CONTROLLER_GEN) crd paths="./apis/..." output:crd:artifacts:config=config/crd/bases
25+
26+
.PHONY: generate
27+
generate: ## Generate DeepCopy code
28+
$(CONTROLLER_GEN) object:headerFile="../hack/boilerplate.go.txt" paths="./apis/..."
29+
30+
##@ Build
31+
32+
.PHONY: docker-build
33+
docker-build: ## Build docker image
34+
docker buildx build \
35+
--file docker/Dockerfile \
36+
--output=type=docker \
37+
--platform=linux/$(GOARCH) \
38+
--build-arg GOARCH=$(GOARCH) \
39+
--tag $(IMAGE_NAME):$(IMAGE_TAG) \
40+
--build-context kubefleet=.. \
41+
..
42+
43+
.PHONY: docker-push
44+
docker-push: ## Push docker image
45+
docker push $(REGISTRY)$(IMAGE_NAME):$(IMAGE_TAG)
46+
47+
##@ Development
48+
49+
.PHONY: run
50+
run: ## Run controller locally
51+
cd .. && go run ./approval-request-controller/cmd/approvalrequestcontroller/main.go
52+
53+
##@ Deployment
54+
55+
.PHONY: install
56+
install: ## Install helm chart
57+
helm install approval-request-controller ./charts/approval-request-controller \
58+
--namespace fleet-system \
59+
--create-namespace \
60+
--set image.repository=$(IMAGE_NAME) \
61+
--set image.tag=$(IMAGE_TAG)
62+
63+
.PHONY: upgrade
64+
upgrade: ## Upgrade helm chart
65+
helm upgrade approval-request-controller ./charts/approval-request-controller \
66+
--namespace fleet-system \
67+
--set image.repository=$(IMAGE_NAME) \
68+
--set image.tag=$(IMAGE_TAG)
69+
70+
.PHONY: uninstall
71+
uninstall: ## Uninstall helm chart
72+
helm uninstall approval-request-controller --namespace fleet-system
73+
74+
##@ Kind
75+
76+
.PHONY: kind-load
77+
kind-load: docker-build ## Build and load image into kind cluster
78+
kind load docker-image $(IMAGE_NAME):$(IMAGE_TAG) --name hub
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# ApprovalRequest Controller
2+
3+
The ApprovalRequest Controller is a standalone controller that runs on the **hub cluster** to automate approval decisions for staged updates based on workload health metrics.
4+
5+
## Overview
6+
7+
This controller:
8+
- Watches `ApprovalRequest` and `ClusterApprovalRequest` resources
9+
- Creates `MetricCollector` resources on member clusters via ClusterResourcePlacement
10+
- Monitors workload health via `MetricCollectorReport` objects
11+
- Automatically approves requests when all tracked workloads are healthy
12+
- Runs every 15 seconds to check health status
13+
14+
## Architecture
15+
16+
The controller is designed to run on the hub cluster and:
17+
1. Deploys MetricCollector instances to member clusters using CRP
18+
2. Collects health metrics from MetricCollectorReports
19+
3. Compares metrics against WorkloadTracker specifications
20+
4. Approves ApprovalRequests when all workloads are healthy
21+
22+
## Installation
23+
24+
### Prerequisites
25+
26+
The following CRDs must be installed on the hub cluster:
27+
- `approvalrequests.placement.kubernetes-fleet.io`
28+
- `clusterapprovalrequests.placement.kubernetes-fleet.io`
29+
- `metriccollectors.placement.kubernetes-fleet.io`
30+
- `metriccollectorreports.placement.kubernetes-fleet.io` (installed by this chart)
31+
- `workloadtrackers.placement.kubernetes-fleet.io`
32+
- `clusterresourceplacements.placement.kubernetes-fleet.io`
33+
- `clusterresourceoverrides.placement.kubernetes-fleet.io`
34+
- `clusterstagedupdateruns.placement.kubernetes-fleet.io`
35+
- `stagedupdateruns.placement.kubernetes-fleet.io`
36+
37+
### Install via Helm
38+
39+
```bash
40+
# Build the image
41+
make docker-build IMAGE_NAME=approval-request-controller IMAGE_TAG=latest
42+
43+
# Load into kind (if using kind)
44+
kind load docker-image approval-request-controller:latest --name hub
45+
46+
# Install the chart
47+
helm install approval-request-controller ./charts/approval-request-controller \
48+
--namespace fleet-system \
49+
--create-namespace
50+
```
51+
52+
## Configuration
53+
54+
See `charts/approval-request-controller/values.yaml` for all configuration options.
55+
56+
Key settings:
57+
- `controller.logLevel`: Log verbosity (default: 2)
58+
- `controller.resources`: Resource requests and limits
59+
- `rbac.create`: Create RBAC resources (default: true)
60+
- `crds.install`: Install MetricCollectorReport CRD (default: true)
61+
62+
## Development
63+
64+
### Build
65+
66+
```bash
67+
make docker-build
68+
```
69+
70+
### Test Locally
71+
72+
```bash
73+
go run ./cmd/approvalrequestcontroller/main.go
74+
```
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
Copyright 2025 The KubeFleet Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package v1beta1 contains API Schema definitions for the placement v1beta1 API group
18+
// +kubebuilder:object:generate=true
19+
// +groupName=placement.kubernetes-fleet.io
20+
package v1beta1
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright 2025 The KubeFleet Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// +kubebuilder:object:generate=true
18+
// +groupName=placement.kubernetes-fleet.io
19+
package v1beta1
20+
21+
import (
22+
"k8s.io/apimachinery/pkg/runtime/schema"
23+
"sigs.k8s.io/controller-runtime/pkg/scheme"
24+
)
25+
26+
var (
27+
// GroupVersion is group version used to register these objects
28+
GroupVersion = schema.GroupVersion{Group: "placement.kubernetes-fleet.io", Version: "v1beta1"}
29+
30+
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
31+
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
32+
33+
// AddToScheme adds the types in this group-version to the given scheme.
34+
AddToScheme = SchemeBuilder.AddToScheme
35+
)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
Copyright 2025 The KubeFleet Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1beta1
18+
19+
import (
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
)
22+
23+
// +genclient
24+
// +kubebuilder:object:root=true
25+
// +kubebuilder:resource:scope="Namespaced",shortName=mcr,categories={fleet,fleet-metrics}
26+
// +kubebuilder:storageversion
27+
// +kubebuilder:printcolumn:JSONPath=`.workloadsMonitored`,name="Workloads",type=integer
28+
// +kubebuilder:printcolumn:JSONPath=`.lastCollectionTime`,name="Last-Collection",type=date
29+
// +kubebuilder:printcolumn:JSONPath=`.metadata.creationTimestamp`,name="Age",type=date
30+
31+
// MetricCollectorReport is created by the MetricCollector controller on the hub cluster
32+
// in the fleet-member-{clusterName} namespace to report collected metrics from a member cluster.
33+
// The controller watches MetricCollector objects on the member cluster, collects metrics,
34+
// and syncs the status to the hub as MetricCollectorReport objects.
35+
//
36+
// Controller workflow:
37+
// 1. MetricCollector reconciles and collects metrics on member cluster
38+
// 2. Metrics include clusterName from workload_health labels
39+
// 3. Controller creates/updates MetricCollectorReport in fleet-member-{clusterName} namespace on hub
40+
// 4. Report name matches MetricCollector name for easy lookup
41+
//
42+
// Namespace: fleet-member-{clusterName} (extracted from CollectedMetrics[0].ClusterName)
43+
// Name: Same as MetricCollector name
44+
// All metrics in CollectedMetrics are guaranteed to have the same ClusterName.
45+
type MetricCollectorReport struct {
46+
metav1.TypeMeta `json:",inline"`
47+
metav1.ObjectMeta `json:"metadata,omitempty"`
48+
49+
// Conditions copied from the MetricCollector status.
50+
// +optional
51+
Conditions []metav1.Condition `json:"conditions,omitempty"`
52+
53+
// ObservedGeneration is the generation most recently observed from the MetricCollector.
54+
// +optional
55+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
56+
57+
// WorkloadsMonitored is the count of workloads being monitored.
58+
// +optional
59+
WorkloadsMonitored int32 `json:"workloadsMonitored,omitempty"`
60+
61+
// LastCollectionTime is when metrics were last collected on the member cluster.
62+
// +optional
63+
LastCollectionTime *metav1.Time `json:"lastCollectionTime,omitempty"`
64+
65+
// CollectedMetrics contains the most recent metrics from each workload.
66+
// All metrics are guaranteed to have the same ClusterName since they're collected from one member cluster.
67+
// +optional
68+
CollectedMetrics []WorkloadMetrics `json:"collectedMetrics,omitempty"`
69+
70+
// LastReportTime is when this report was last synced to the hub.
71+
// +optional
72+
LastReportTime *metav1.Time `json:"lastReportTime,omitempty"`
73+
}
74+
75+
// WorkloadMetrics represents metrics collected from a single workload pod.
76+
type WorkloadMetrics struct {
77+
// Namespace is the namespace of the pod.
78+
// +required
79+
Namespace string `json:"namespace"`
80+
81+
// ClusterName from the workload_health metric label.
82+
// +required
83+
ClusterName string `json:"clusterName"`
84+
85+
// WorkloadName from the workload_health metric label (typically the deployment name).
86+
// +required
87+
WorkloadName string `json:"workloadName"`
88+
89+
// Health indicates if the workload is healthy (true=healthy, false=unhealthy).
90+
// +required
91+
Health bool `json:"health"`
92+
}
93+
94+
// +kubebuilder:object:root=true
95+
96+
// MetricCollectorReportList contains a list of MetricCollectorReport.
97+
type MetricCollectorReportList struct {
98+
metav1.TypeMeta `json:",inline"`
99+
metav1.ListMeta `json:"metadata,omitempty"`
100+
Items []MetricCollectorReport `json:"items"`
101+
}
102+
103+
func init() {
104+
SchemeBuilder.Register(&MetricCollectorReport{}, &MetricCollectorReportList{})
105+
}

0 commit comments

Comments
 (0)