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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -362,3 +362,4 @@ admin/Cargo.lock
*.csr
*.srl
*.ext
*.test
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ This changelog keeps track of work items that have been completed and are ready

### Other

- **General**: Get rid of kube-rbac-proxy ([#1123](https://github.com/kedacore/http-add-on/pull/1369))
- **CI**: Use GitHub-hosted ARM64 runners for e2e tests ([#1388](https://github.com/kedacore/http-add-on/issues/1388))
- **DevContainer**: Fix devcontainer build by updating deprecated Go tools ([#1347](https://github.com/kedacore/http-add-on/issues/1347))

Expand Down
4 changes: 3 additions & 1 deletion config/operator/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ spec:
- name: KEDA_HTTP_OPERATOR_WATCH_NAMESPACE
value: ""
ports:
- name: metrics
- name: metrics-http
containerPort: 8080
- name: metrics-https
containerPort: 8443
- name: probes
containerPort: 8081
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a separate port here and in config/operator/metrics_service.yaml ? And if, might it make sense to include metrics in its name if it's for metrics?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end one of this ports would point to nowhere. In my understanding this config folder is just for integration/end2end testing. Then it wouldn't be a problem. I adjusted the names.

livenessProbe:
Expand Down
2 changes: 2 additions & 0 deletions config/operator/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- metrics_service.yaml
- metrics_reader_role.yaml
- role.yaml
- role_binding.yaml
- service_account.yaml
Expand Down
10 changes: 10 additions & 0 deletions config/operator/metrics_reader_role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: operator-metrics-reader
rules:
- nonResourceURLs:
- "/metrics"
verbs:
- get
15 changes: 15 additions & 0 deletions config/operator/metrics_service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: operator-metrics
spec:
type: ClusterIP
ports:
- name: metrics-http
protocol: TCP
port: 8080
targetPort: 8080
- name: metrics-https
protocol: TCP
port: 8443
targetPort: 8443
12 changes: 12 additions & 0 deletions config/operator/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ rules:
- patch
- update
- watch
- apiGroups:
- authentication.k8s.io
resources:
- tokenreviews
verbs:
- create
- apiGroups:
- authorization.k8s.io
resources:
- subjectaccessreviews
verbs:
- create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
Expand Down
22 changes: 19 additions & 3 deletions config/operator/role_binding.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,39 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: operator
name: keda-http-operator-auth-delegator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: operator
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: keda-add-ons-http-operator
namespace: keda
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: keda-http-operator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: operator
subjects:
- kind: ServiceAccount
name: keda-add-ons-http-operator
namespace: keda
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: operator
namespace: keda
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: operator
subjects:
- kind: ServiceAccount
name: operator
name: keda-add-ons-http-operator
namespace: keda
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ require (
sigs.k8s.io/kustomize/kustomize/v5 v5.7.1
)

require (
cel.dev/expr v0.24.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/google/cel-go v0.26.0 // indirect
github.com/stoewer/go-strcase v1.3.1 // indirect
k8s.io/apiserver v0.33.5 // indirect
k8s.io/component-base v0.33.5 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
)

require (
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
Expand Down
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
Expand Down Expand Up @@ -64,6 +68,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g=
github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
Expand Down Expand Up @@ -172,6 +178,8 @@ github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wx
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs=
github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Expand Down Expand Up @@ -328,10 +336,14 @@ k8s.io/apiextensions-apiserver v0.33.5 h1:93NZh6rmrcamX/tfv/dZrTsMiQX69ufANmDcKP
k8s.io/apiextensions-apiserver v0.33.5/go.mod h1:JIbyQnNlu6nQa7b1vgFi51pmlXOk8mdn0WJwUJnz/7U=
k8s.io/apimachinery v0.33.5 h1:NiT64hln4TQXeYR18/ES39OrNsjGz8NguxsBgp+6QIo=
k8s.io/apimachinery v0.33.5/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/apiserver v0.33.5 h1:X1Gy33r4YkRLRqTjGjofk7X1/EjSLEVSJ/A+1qjoj60=
k8s.io/apiserver v0.33.5/go.mod h1:Q+b5Btbc8x0PqOCeh/xBTesKk+cXQRN+PF2wdrTKDeg=
k8s.io/client-go v0.33.5 h1:I8BdmQGxInpkMEnJvV6iG7dqzP3JRlpZZlib3OMFc3o=
k8s.io/client-go v0.33.5/go.mod h1:W8PQP4MxbM4ypgagVE65mUUqK1/ByQkSALF9tzuQ6u0=
k8s.io/code-generator v0.33.5 h1:KwkOvhwAaorjSwF2MQhhdhL3i8bBmAal/TWhX67kdHw=
k8s.io/code-generator v0.33.5/go.mod h1:Ra+sdZquRakeTGcEnQAPw6BmlZ92IvxwQQTX/XOvOIE=
k8s.io/component-base v0.33.5 h1:4D3kxjEx1pJRy3WHAZsmX3+LCpmd4ftE+2J4v6naTnQ=
k8s.io/component-base v0.33.5/go.mod h1:Zma1YjBVuuGxIbspj1vGR3/5blzo2ARf1v0QTtog1to=
k8s.io/gengo/v2 v2.0.0-20250820003526-c297c0c1eb9d h1:qUrYOinhdAUL0xxhA4gPqogPBaS9nIq2l2kTb6pmeB0=
k8s.io/gengo/v2 v2.0.0-20250820003526-c297c0c1eb9d/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
Expand All @@ -342,6 +354,8 @@ k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzk
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
knative.dev/pkg v0.0.0-20250602175424-3c3a920206ea h1:ukJPq9MzFTEH/Sei5MSVnSE8+7NSCKixCDZPd6p4ohw=
knative.dev/pkg v0.0.0-20250602175424-3c3a920206ea/go.mod h1:tFayQbi6t4+5HXuEGLOGvILW228Q7uaJp/FYEgbjJ3A=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
sigs.k8s.io/controller-tools v0.17.3 h1:lwFPLicpBKLgIepah+c8ikRBubFW5kOQyT88r3EwfNw=
Expand Down
27 changes: 23 additions & 4 deletions operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
"sigs.k8s.io/controller-runtime/pkg/metrics/server"

httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1"
Expand Down Expand Up @@ -56,10 +57,16 @@ func init() {

func main() {
var metricsAddr string
var metricsSecure bool
var metricsAuth bool
var metricsCertDir string
var enableLeaderElection bool
var probeAddr string
var profilingAddr string
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.BoolVar(&metricsSecure, "metrics-secure", false, "Enable secure serving for metrics endpoint.")
flag.BoolVar(&metricsAuth, "metrics-auth", false, "Enable authentication and authorization for metrics endpoint.")
flag.StringVar(&metricsCertDir, "metrics-cert-dir", "", "The directory that contains the server certificate and key (tls.crt and tls.key) for the metrics endpoint.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
Expand Down Expand Up @@ -117,11 +124,23 @@ func main() {
}
}

// Switch between HTTP (8080) and HTTPS (8443) based on metricsSecure flag
if metricsSecure {
metricsAddr = ":8443"
}

metricsOpts := server.Options{
BindAddress: metricsAddr,
SecureServing: metricsSecure,
CertDir: metricsCertDir,
}
if metricsAuth {
metricsOpts.FilterProvider = filters.WithAuthenticationAndAuthorization
}

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Metrics: server.Options{
BindAddress: metricsAddr,
},
Scheme: scheme,
Metrics: metricsOpts,
PprofBindAddress: profilingAddr,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
Expand Down
134 changes: 134 additions & 0 deletions tests/checks/operator_metrics/operator_metrics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//go:build e2e
// +build e2e

package operator_metrics_test

import (
"fmt"
"strings"
"testing"

"github.com/stretchr/testify/assert"

. "github.com/kedacore/http-add-on/tests/helper"
)

const (
testName = "operator-metrics-test"
)

var (
testNamespace = fmt.Sprintf("%s-ns", testName)
clientName = fmt.Sprintf("%s-client", testName)
kedaOperatorMetricsURL = "http://keda-add-ons-http-operator-metrics.keda:8080/metrics"
operatorPodSelector = "app.kubernetes.io/instance=operator"
)

type templateData struct {
TestNamespace string
ClientName string
}

const (
clientTemplate = `
apiVersion: v1
kind: Pod
metadata:
name: {{.ClientName}}
namespace: {{.TestNamespace}}
spec:
serviceAccountName: {{.ClientName}}
containers:
- name: {{.ClientName}}
image: curlimages/curl
command:
- sh
- -c
- "exec tail -f /dev/null"`

serviceAccountTemplate = `
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{.ClientName}}
namespace: {{.TestNamespace}}`

clusterRoleBindingTemplate = `
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{.ClientName}}-metrics-reader
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: keda-add-ons-http-operator-metrics-reader
subjects:
- kind: ServiceAccount
name: {{.ClientName}}
namespace: {{.TestNamespace}}`
)

func TestOperatorMetrics(t *testing.T) {
// setup
t.Log("--- setting up ---")
// Create kubernetes resources
kc := GetKubernetesClient(t)
data, templates := getTemplateData()
CreateKubernetesResources(t, kc, testNamespace, data, templates)

// Wait for client pod to be ready
assert.True(t, WaitForAllPodRunningInNamespace(t, kc, testNamespace, 6, 10),
"client pod should be running")

t.Log("--- testing operator metrics endpoint ---")

// Test 1: HTTPS endpoint should be accessible (will fail cert validation but should return metrics)
t.Log("Test 1: Verify HTTP endpoint is available")
testHTTPEndpoint(t)

// Test 2: Verify metrics are returned
t.Log("Test 2: Verify metrics content")
testMetricsContent(t)

// cleanup
DeleteKubernetesResources(t, testNamespace, data, templates)
}

func testHTTPEndpoint(t *testing.T) {
cmd := fmt.Sprintf("curl --max-time 10 %s", kedaOperatorMetricsURL)
out, errOut, err := ExecCommandOnSpecificPod(t, clientName, testNamespace, cmd)

// The endpoint should return something (even if authentication fails, it should respond)
assert.True(t, err == nil || strings.Contains(errOut, "Forbidden") || strings.Contains(out, "Forbidden"),
"HTTP endpoint should respond (either with metrics or authentication error)")
}

func testMetricsContent(t *testing.T) {
// Access metrics from the client pod using the service endpoint
// The client pod uses a ServiceAccount with the operator-metrics-reader ClusterRole
// This allows it to access the metrics endpoint with proper RBAC permissions

// Get the ServiceAccount token to authenticate
cmd := fmt.Sprintf("curl -H \"Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\" --max-time 10 %s", kedaOperatorMetricsURL)
out, errOut, err := ExecCommandOnSpecificPod(t, clientName, testNamespace, cmd)

if err != nil {
t.Logf("Metrics content test - Output: %s, Error output: %s, Error: %v", out, errOut, err)
}

// Verify that metrics are returned in Prometheus format
assert.NoError(t, err, "should be able to access metrics from client pod with RBAC permissions. Output: %s, Error: %s", out, errOut)
assert.True(t, strings.Contains(out, "# HELP") || strings.Contains(out, "# TYPE"),
"metrics should contain Prometheus format. Output: %s", out)
}

func getTemplateData() (templateData, []Template) {
return templateData{
TestNamespace: testNamespace,
ClientName: clientName,
}, []Template{
{Name: "serviceAccountTemplate", Config: serviceAccountTemplate},
{Name: "clusterRoleBindingTemplate", Config: clusterRoleBindingTemplate},
{Name: "clientTemplate", Config: clientTemplate},
}
}
Loading