Skip to content

Commit eec6c8c

Browse files
committed
feat: make placeholder pages content-agnostic
Refactored placeholder pages to support any content type without automatic modifications. Users can now provide HTML, JSON, XML, or plain text content that will be served as-is during scale-from-zero. Changes: - Removed automatic script injection and default HTML template - Added Content-Type header support in placeholderConfig - Template variables (ServiceName, Namespace, RefreshInterval) work with any content format - Added examples for JSON, XML, and plain text responses - Fixed e2e tests and improved test helper reliability Signed-off-by: malpou <[email protected]>
1 parent 973f90d commit eec6c8c

File tree

18 files changed

+629
-481
lines changed

18 files changed

+629
-481
lines changed

config/crd/bases/http.keda.sh_httpscaledobjects.yaml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,30 +113,35 @@ spec:
113113
scale-from-zero
114114
properties:
115115
content:
116-
description: Inline content for placeholder page (can be HTML,
117-
JSON, plain text, etc.)
116+
description: |-
117+
Inline content for placeholder response. Supports any format (HTML, JSON, XML, plain text, etc.)
118+
Content is processed as a Go template with variables: ServiceName, Namespace, RefreshInterval, RequestID, Timestamp
119+
Content-Type should be set via the Headers field to match your content format
118120
type: string
119121
enabled:
120122
default: false
121-
description: Enable placeholder page when replicas are scaled
123+
description: Enable placeholder response when replicas are scaled
122124
to zero
123125
type: boolean
124126
headers:
125127
additionalProperties:
126128
type: string
127-
description: Additional HTTP headers to include with placeholder
128-
response
129+
description: |-
130+
Additional HTTP headers to include with placeholder response
131+
Use this to set Content-Type matching your content format (e.g., 'Content-Type: application/json')
129132
type: object
130133
refreshInterval:
131134
default: 5
132-
description: Refresh interval for client-side polling in seconds
135+
description: |-
136+
RefreshInterval is a template variable available in content (in seconds)
137+
This is just data passed to the template, not used by the interceptor for automatic refresh
133138
format: int32
134139
maximum: 60
135140
minimum: 1
136141
type: integer
137142
statusCode:
138143
default: 503
139-
description: HTTP status code to return with placeholder page
144+
description: HTTP status code to return with placeholder response
140145
format: int32
141146
maximum: 599
142147
minimum: 100

config/interceptor/deployment.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ spec:
5656
value: "1s"
5757
- name: KEDA_HTTP_PLACEHOLDER_DEFAULT_TEMPLATE_PATH
5858
value: "/placeholder-templates/default.html"
59-
- name: KEDA_HTTP_PLACEHOLDER_ENABLE_SCRIPT
60-
value: "true"
6159
ports:
6260
- name: admin
6361
containerPort: 9090

config/interceptor/kustomization.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,31 @@ labels:
1717
includeTemplates: true
1818
pairs:
1919
app.kubernetes.io/instance: interceptor
20+
images:
21+
- name: ghcr.io/kedacore/http-add-on-interceptor
22+
newName: ghcr.io/kedacore/http-add-on-interceptor
23+
newTag: main
24+
patches:
25+
- path: e2e-test/otel/deployment.yaml
26+
target:
27+
group: apps
28+
kind: Deployment
29+
name: interceptor
30+
version: v1
31+
- path: e2e-test/otel/scaledobject.yaml
32+
target:
33+
group: keda.sh
34+
kind: ScaledObject
35+
name: interceptor
36+
version: v1alpha1
37+
- path: e2e-test/tls/deployment.yaml
38+
target:
39+
group: apps
40+
kind: Deployment
41+
name: interceptor
42+
version: v1
43+
- path: e2e-test/tls/proxy.service.yaml
44+
target:
45+
kind: Service
46+
name: interceptor-proxy
47+
version: v1

config/interceptor/placeholder-templates.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ kind: ConfigMap
33
metadata:
44
name: interceptor-placeholder-templates
55
data:
6+
# Optional default template used when placeholderConfig.content is not specified
7+
# This is an HTML example - users can create any format (JSON, XML, plain text, etc.)
8+
# Users should always set the Content-Type header in their HTTPScaledObject to match their format
69
default.html: |
710
<!DOCTYPE html>
811
<html>

config/operator/kustomization.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ labels:
1010
includeTemplates: true
1111
pairs:
1212
app.kubernetes.io/instance: operator
13+
images:
14+
- name: ghcr.io/kedacore/http-add-on-operator
15+
newName: ghcr.io/kedacore/http-add-on-operator
16+
newTag: main

config/scaler/kustomization.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,14 @@ labels:
1111
includeTemplates: true
1212
pairs:
1313
app.kubernetes.io/instance: external-scaler
14+
images:
15+
- name: ghcr.io/kedacore/http-add-on-scaler
16+
newName: ghcr.io/kedacore/http-add-on-scaler
17+
newTag: main
18+
patches:
19+
- path: e2e-test/otel/deployment.yaml
20+
target:
21+
group: apps
22+
kind: Deployment
23+
name: scaler
24+
version: v1

docs/ref/vX.X.X/http_scaled_object.md

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ spec:
3838
refreshInterval: 5
3939
statusCode: 503
4040
headers:
41+
Content-Type: "text/html; charset=utf-8"
4142
X-Service-Status: "warming-up"
4243
content: |
4344
<!DOCTYPE html>
@@ -154,49 +155,81 @@ This is the target value for the scaling configuration.
154155

155156
## `placeholderConfig`
156157

157-
This optional section enables serving placeholder pages when the workload is scaled to zero. When enabled, instead of returning an error while waiting for the workload to scale up, the interceptor will serve a customizable HTML page that refreshes automatically.
158+
This optional section enables serving placeholder responses when the workload is scaled to zero. When enabled, instead of returning an error while waiting for the workload to scale up, the interceptor will serve a customizable response with any content format.
158159

159160
### `enabled`
160161

161162
>Default: false
162163

163-
Whether to enable placeholder pages for this HTTPScaledObject.
164+
Whether to enable placeholder responses for this HTTPScaledObject.
164165

165166
### `refreshInterval`
166167

167168
>Default: 5
168169

169-
The interval in seconds at which the placeholder page will refresh. This should be set based on your expected cold start time.
170+
A template variable (in seconds) that can be used in your content template. This is just data passed to the template - it does not automatically refresh the response. You can use it in your content for client-side refresh logic if needed (e.g., `<meta http-equiv="refresh" content="{{.RefreshInterval}}">`).
170171

171172
### `statusCode`
172173

173174
>Default: 503
174175

175-
The HTTP status code to return with the placeholder page. Common values are 503 (Service Unavailable) or 202 (Accepted).
176+
The HTTP status code to return with the placeholder response. Common values are 503 (Service Unavailable) or 202 (Accepted).
176177

177178
### `headers`
178179

179180
>Default: {}
180181

181-
A map of custom HTTP headers to include in the placeholder response. Useful for adding service-specific headers.
182+
A map of custom HTTP headers to include in the placeholder response. **Important**: Use this to set the `Content-Type` header to match your content format. For example:
183+
- `Content-Type: text/html; charset=utf-8` for HTML
184+
- `Content-Type: application/json` for JSON
185+
- `Content-Type: text/plain` for plain text
182186

183187
### `content`
184188

185-
>Default: Built-in template
189+
>Default: ConfigMap-provided template (if configured), otherwise returns simple text
186190

187-
Custom HTML content for the placeholder page. Supports Go template syntax with the following variables:
191+
Custom content for the placeholder response. Supports any format (HTML, JSON, XML, plain text, etc.). Content is processed as a Go template with the following variables:
188192
- `{{.ServiceName}}` - The name of the service from scaleTargetRef
189193
- `{{.Namespace}}` - The namespace of the HTTPScaledObject
190-
- `{{.RefreshInterval}}` - The configured refresh interval
194+
- `{{.RefreshInterval}}` - The configured refresh interval value (just a number)
191195
- `{{.RequestID}}` - The X-Request-ID header value if present
192196
- `{{.Timestamp}}` - The current timestamp in RFC3339 format
193197

194-
### `contentConfigMap`
198+
**Examples:**
195199

196-
The name of a ConfigMap containing the placeholder page template. This is an alternative to inline `content`.
197-
198-
### `contentConfigMapKey`
200+
HTML with client-side refresh:
201+
```yaml
202+
content: |
203+
<!DOCTYPE html>
204+
<html>
205+
<head>
206+
<title>Service Starting</title>
207+
<meta http-equiv="refresh" content="{{.RefreshInterval}}">
208+
</head>
209+
<body>
210+
<h1>{{.ServiceName}} is starting...</h1>
211+
</body>
212+
</html>
213+
headers:
214+
Content-Type: "text/html; charset=utf-8"
215+
```
199216

200-
>Default: "template.html"
217+
JSON response:
218+
```yaml
219+
content: |
220+
{
221+
"status": "warming_up",
222+
"service": "{{.ServiceName}}",
223+
"namespace": "{{.Namespace}}",
224+
"timestamp": "{{.Timestamp}}"
225+
}
226+
headers:
227+
Content-Type: "application/json"
228+
```
201229

202-
The key within the ConfigMap that contains the template content.
230+
Plain text:
231+
```yaml
232+
content: "{{.ServiceName}} is starting up. Please retry in a few seconds."
233+
headers:
234+
Content-Type: "text/plain"
235+
```
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Example: JSON placeholder for API-to-API communication
2+
# Use case: Microservices calling other microservices
3+
kind: HTTPScaledObject
4+
apiVersion: http.keda.sh/v1alpha1
5+
metadata:
6+
name: api-service
7+
spec:
8+
hosts:
9+
- api.example.com
10+
scaleTargetRef:
11+
name: api-backend
12+
kind: Deployment
13+
apiVersion: apps/v1
14+
service: api-backend
15+
port: 8080
16+
replicas:
17+
min: 0
18+
max: 10
19+
scalingMetric:
20+
concurrency:
21+
targetValue: 100
22+
placeholderConfig:
23+
enabled: true
24+
refreshInterval: 10
25+
statusCode: 202 # 202 Accepted - request received but not yet processed
26+
headers:
27+
Content-Type: "application/json"
28+
Retry-After: "10"
29+
X-Service-Status: "scaling"
30+
content: |
31+
{
32+
"status": "warming_up",
33+
"service": "{{.ServiceName}}",
34+
"namespace": "{{.Namespace}}",
35+
"retry_after_seconds": {{.RefreshInterval}},
36+
"timestamp": "{{.Timestamp}}",
37+
"message": "Service is scaling up to handle your request"
38+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Example: Plain text placeholder for simple clients
2+
# Use case: CLI tools, simple HTTP clients, or monitoring systems
3+
kind: HTTPScaledObject
4+
apiVersion: http.keda.sh/v1alpha1
5+
metadata:
6+
name: simple-service
7+
spec:
8+
hosts:
9+
- simple.example.com
10+
scaleTargetRef:
11+
name: simple-backend
12+
kind: Deployment
13+
apiVersion: apps/v1
14+
service: simple-backend
15+
port: 8080
16+
replicas:
17+
min: 0
18+
max: 10
19+
scalingMetric:
20+
concurrency:
21+
targetValue: 100
22+
placeholderConfig:
23+
enabled: true
24+
refreshInterval: 3
25+
statusCode: 503
26+
headers:
27+
Content-Type: "text/plain; charset=utf-8"
28+
content: |
29+
{{.ServiceName}} is currently unavailable.
30+
31+
The service is scaling up to handle your request.
32+
Please retry in {{.RefreshInterval}} seconds.
33+
34+
Namespace: {{.Namespace}}
35+
Timestamp: {{.Timestamp}}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Example: XML placeholder for legacy systems
2+
# Use case: SOAP services or XML-based APIs
3+
kind: HTTPScaledObject
4+
apiVersion: http.keda.sh/v1alpha1
5+
metadata:
6+
name: legacy-service
7+
spec:
8+
hosts:
9+
- legacy.example.com
10+
scaleTargetRef:
11+
name: legacy-backend
12+
kind: Deployment
13+
apiVersion: apps/v1
14+
service: legacy-backend
15+
port: 8080
16+
replicas:
17+
min: 0
18+
max: 5
19+
scalingMetric:
20+
concurrency:
21+
targetValue: 50
22+
placeholderConfig:
23+
enabled: true
24+
refreshInterval: 5
25+
statusCode: 503
26+
headers:
27+
Content-Type: "application/xml; charset=utf-8"
28+
content: |
29+
<?xml version="1.0" encoding="UTF-8"?>
30+
<response>
31+
<status>unavailable</status>
32+
<service>{{.ServiceName}}</service>
33+
<namespace>{{.Namespace}}</namespace>
34+
<message>Service is scaling up</message>
35+
<retryAfter>{{.RefreshInterval}}</retryAfter>
36+
</response>

0 commit comments

Comments
 (0)