Skip to content

Commit b7251a9

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 <malthe@grundtvigsvej.dk>
1 parent 0b819a9 commit b7251a9

File tree

18 files changed

+629
-478
lines changed

18 files changed

+629
-478
lines changed

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,30 +137,35 @@ spec:
137137
scale-from-zero
138138
properties:
139139
content:
140-
description: Inline content for placeholder page (can be HTML,
141-
JSON, plain text, etc.)
140+
description: |-
141+
Inline content for placeholder response. Supports any format (HTML, JSON, XML, plain text, etc.)
142+
Content is processed as a Go template with variables: ServiceName, Namespace, RefreshInterval, RequestID, Timestamp
143+
Content-Type should be set via the Headers field to match your content format
142144
type: string
143145
enabled:
144146
default: false
145-
description: Enable placeholder page when replicas are scaled
147+
description: Enable placeholder response when replicas are scaled
146148
to zero
147149
type: boolean
148150
headers:
149151
additionalProperties:
150152
type: string
151-
description: Additional HTTP headers to include with placeholder
152-
response
153+
description: |-
154+
Additional HTTP headers to include with placeholder response
155+
Use this to set Content-Type matching your content format (e.g., 'Content-Type: application/json')
153156
type: object
154157
refreshInterval:
155158
default: 5
156-
description: Refresh interval for client-side polling in seconds
159+
description: |-
160+
RefreshInterval is a template variable available in content (in seconds)
161+
This is just data passed to the template, not used by the interceptor for automatic refresh
157162
format: int32
158163
maximum: 60
159164
minimum: 1
160165
type: integer
161166
statusCode:
162167
default: 503
163-
description: HTTP status code to return with placeholder page
168+
description: HTTP status code to return with placeholder response
164169
format: int32
165170
maximum: 599
166171
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
@@ -11,3 +11,7 @@ labels:
1111
includeTemplates: true
1212
pairs:
1313
app.kubernetes.io/instance: operator
14+
images:
15+
- name: ghcr.io/kedacore/http-add-on-operator
16+
newName: ghcr.io/kedacore/http-add-on-operator
17+
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
@@ -42,6 +42,7 @@ spec:
4242
refreshInterval: 5
4343
statusCode: 503
4444
headers:
45+
Content-Type: "text/html; charset=utf-8"
4546
X-Service-Status: "warming-up"
4647
content: |
4748
<!DOCTYPE html>
@@ -166,49 +167,81 @@ This is the target value for the scaling configuration.
166167

167168
## `placeholderConfig`
168169

169-
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.
170+
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.
170171

171172
### `enabled`
172173

173174
>Default: false
174175

175-
Whether to enable placeholder pages for this HTTPScaledObject.
176+
Whether to enable placeholder responses for this HTTPScaledObject.
176177

177178
### `refreshInterval`
178179

179180
>Default: 5
180181

181-
The interval in seconds at which the placeholder page will refresh. This should be set based on your expected cold start time.
182+
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}}">`).
182183

183184
### `statusCode`
184185

185186
>Default: 503
186187

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

189190
### `headers`
190191

191192
>Default: {}
192193

193-
A map of custom HTTP headers to include in the placeholder response. Useful for adding service-specific headers.
194+
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:
195+
- `Content-Type: text/html; charset=utf-8` for HTML
196+
- `Content-Type: application/json` for JSON
197+
- `Content-Type: text/plain` for plain text
194198

195199
### `content`
196200

197-
>Default: Built-in template
201+
>Default: ConfigMap-provided template (if configured), otherwise returns simple text
198202

199-
Custom HTML content for the placeholder page. Supports Go template syntax with the following variables:
203+
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:
200204
- `{{.ServiceName}}` - The name of the service from scaleTargetRef
201205
- `{{.Namespace}}` - The namespace of the HTTPScaledObject
202-
- `{{.RefreshInterval}}` - The configured refresh interval
206+
- `{{.RefreshInterval}}` - The configured refresh interval value (just a number)
203207
- `{{.RequestID}}` - The X-Request-ID header value if present
204208
- `{{.Timestamp}}` - The current timestamp in RFC3339 format
205209

206-
### `contentConfigMap`
210+
**Examples:**
207211

208-
The name of a ConfigMap containing the placeholder page template. This is an alternative to inline `content`.
209-
210-
### `contentConfigMapKey`
212+
HTML with client-side refresh:
213+
```yaml
214+
content: |
215+
<!DOCTYPE html>
216+
<html>
217+
<head>
218+
<title>Service Starting</title>
219+
<meta http-equiv="refresh" content="{{.RefreshInterval}}">
220+
</head>
221+
<body>
222+
<h1>{{.ServiceName}} is starting...</h1>
223+
</body>
224+
</html>
225+
headers:
226+
Content-Type: "text/html; charset=utf-8"
227+
```
211228

212-
>Default: "template.html"
229+
JSON response:
230+
```yaml
231+
content: |
232+
{
233+
"status": "warming_up",
234+
"service": "{{.ServiceName}}",
235+
"namespace": "{{.Namespace}}",
236+
"timestamp": "{{.Timestamp}}"
237+
}
238+
headers:
239+
Content-Type: "application/json"
240+
```
213241

214-
The key within the ConfigMap that contains the template content.
242+
Plain text:
243+
```yaml
244+
content: "{{.ServiceName}} is starting up. Please retry in a few seconds."
245+
headers:
246+
Content-Type: "text/plain"
247+
```
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)