Skip to content

Commit fea1e07

Browse files
Closure handling and varnish example
1 parent af9d4b4 commit fea1e07

12 files changed

Lines changed: 381 additions & 43 deletions

File tree

charts/ontoserver-extras/README.md

Lines changed: 88 additions & 41 deletions
Large diffs are not rendered by default.

charts/ontoserver-extras/templates/varnish-configmap.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,26 @@ data:
1919
.between_bytes_timeout = 60s;
2020
}
2121
22+
{{- if .Values.varnish.closureBackend }}
23+
# Dedicated backend for $closure — stateful operation requiring sticky routing to one instance
24+
backend closure {
25+
.host = "{{ .Values.varnish.closureBackend }}";
26+
.port = "80";
27+
.connect_timeout = 60s;
28+
.first_byte_timeout = 300s;
29+
.between_bytes_timeout = 60s;
30+
}
31+
{{- end }}
32+
2233
sub vcl_recv {
34+
{{- if .Values.varnish.closureBackend }}
35+
# Route $closure to a dedicated backend — $closure maintains per-session state
36+
# and must always hit the same Ontoserver instance.
37+
if (req.url ~ "^/fhir/ConceptMap/\$closure") {
38+
set req.backend_hint = closure;
39+
return (pass);
40+
}
41+
{{- end }}
2342
{{- if .Values.varnish.opentelemetry.enabled }}
2443
set req.http.X-Start-Time-Sec = std.time2real(now, 0.0);
2544
@@ -140,6 +159,12 @@ data:
140159
{{- end }}
141160
142161
sub vcl_backend_response {
162+
{{- if gt (int .Values.varnish.graceSeconds) 0 }}
163+
# Serve stale content for graceSeconds if the backend is unavailable
164+
# (e.g. during a rolling update of the Ontoserver StatefulSet)
165+
set beresp.grace = {{ .Values.varnish.graceSeconds }}s;
166+
{{- end }}
167+
143168
if (beresp.status == 200) {
144169
# Ignore any Cache-Control/Pragma no-cache directives from the origin
145170
# (mirrors Azure CDN "Ignore Origin No-Cache = 200" rule)

charts/ontoserver-extras/templates/varnish-deployment.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ metadata:
77
{{- include "ontoserver-extras.labels" . | nindent 4 }}
88
{{- include "ontoserver-extras.varnish.selectorLabels" . | nindent 4 }}
99
spec:
10-
replicas: 1
10+
replicas: {{ .Values.varnish.replicas }}
1111
selector:
1212
matchLabels:
1313
{{- include "ontoserver-extras.varnish.selectorLabels" . | nindent 6 }}

charts/ontoserver-extras/tests/varnish_configmap_test.yaml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,32 @@ tests:
7676
path: data["default.vcl"]
7777
pattern: 'beresp\.ttl = 2m'
7878

79+
- it: includes grace period by default
80+
set:
81+
varnish.enabled: true
82+
asserts:
83+
- matchRegex:
84+
path: data["default.vcl"]
85+
pattern: 'beresp\.grace = 30s'
86+
87+
- it: uses custom grace period from values
88+
set:
89+
varnish.enabled: true
90+
varnish.graceSeconds: 60
91+
asserts:
92+
- matchRegex:
93+
path: data["default.vcl"]
94+
pattern: 'beresp\.grace = 60s'
95+
96+
- it: omits grace period when graceSeconds is 0
97+
set:
98+
varnish.enabled: true
99+
varnish.graceSeconds: 0
100+
asserts:
101+
- notMatchRegex:
102+
path: data["default.vcl"]
103+
pattern: 'beresp\.grace'
104+
79105
- it: includes opentelemetry VCL blocks when otel enabled
80106
set:
81107
varnish.enabled: true
@@ -93,3 +119,38 @@ tests:
93119
- notMatchRegex:
94120
path: data["default.vcl"]
95121
pattern: 'OTEL_SPAN'
122+
123+
- it: omits closure backend and routing when closureBackend not set
124+
set:
125+
varnish.enabled: true
126+
asserts:
127+
- notMatchRegex:
128+
path: data["default.vcl"]
129+
pattern: 'backend closure'
130+
- notMatchRegex:
131+
path: data["default.vcl"]
132+
pattern: '\$closure'
133+
134+
- it: adds closure backend block when closureBackend is set
135+
set:
136+
varnish.enabled: true
137+
varnish.closureBackend: "my-release-statefulset-0.my-release-ontoserver-headless"
138+
asserts:
139+
- matchRegex:
140+
path: data["default.vcl"]
141+
pattern: 'backend closure'
142+
- matchRegex:
143+
path: data["default.vcl"]
144+
pattern: '\.host = "my-release-statefulset-0\.my-release-ontoserver-headless"'
145+
146+
- it: routes closure requests to closure backend and passes through
147+
set:
148+
varnish.enabled: true
149+
varnish.closureBackend: "my-release-statefulset-0.my-release-ontoserver-headless"
150+
asserts:
151+
- matchRegex:
152+
path: data["default.vcl"]
153+
pattern: 'req\.backend_hint = closure'
154+
- matchRegex:
155+
path: data["default.vcl"]
156+
pattern: 'ConceptMap/\\\$closure'

charts/ontoserver-extras/tests/varnish_deployment_test.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,23 @@ tests:
121121
name: trace-pipe
122122
emptyDir: {}
123123

124+
- it: defaults to 1 replica
125+
set:
126+
varnish.enabled: true
127+
asserts:
128+
- equal:
129+
path: spec.replicas
130+
value: 1
131+
132+
- it: uses replicas from values
133+
set:
134+
varnish.enabled: true
135+
varnish.replicas: 2
136+
asserts:
137+
- equal:
138+
path: spec.replicas
139+
value: 2
140+
124141
- it: sets tolerations when provided
125142
set:
126143
varnish.enabled: true

charts/ontoserver-extras/values.schema.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
"default": "",
2323
"description": "Override the Kubernetes service name Varnish proxies to. Defaults to RELEASE-ontoserver-service (the ontoserver chart's service). Only needed when the extras chart and ontoserver chart have different release names."
2424
},
25+
"closureBackend": {
26+
"type": "string",
27+
"default": "",
28+
"description": "Kubernetes hostname for the $closure operation backend. The $closure FHIR operation is stateful — set to a stable pod hostname (e.g. RELEASE-statefulset-0.RELEASE-ontoserver-headless) for scaled deployments. Empty string disables dedicated $closure routing."
29+
},
2530
"metrics": {
2631
"type": "object",
2732
"description": "Prometheus metrics exporter sidecar",
@@ -74,6 +79,18 @@
7479
}
7580
}
7681
},
82+
"replicas": {
83+
"type": "integer",
84+
"default": 1,
85+
"minimum": 0,
86+
"description": "Number of Varnish pod replicas"
87+
},
88+
"graceSeconds": {
89+
"type": "integer",
90+
"default": 30,
91+
"minimum": 0,
92+
"description": "Seconds to serve stale cached content when the backend is unavailable"
93+
},
7794
"image": {
7895
"type": "string",
7996
"default": "varnish:7.7.1",

charts/ontoserver-extras/values.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ varnish:
44
enabled: false
55
## @param varnish.backendServiceName Kubernetes service name for the Varnish backend (defaults to RELEASE-ontoserver-service)
66
backendServiceName: ""
7+
## @param varnish.closureBackend Kubernetes hostname for the $closure operation backend. The $closure FHIR operation is stateful — it requires all requests to hit the same instance. Set to a stable pod hostname (e.g. RELEASE-statefulset-0.RELEASE-ontoserver-headless) when using a scaled deployment. Empty string disables dedicated $closure routing.
8+
closureBackend: ""
9+
## @param varnish.replicas Number of Varnish pod replicas. For a scaled Ontoserver deployment consider 2+ to avoid a single point of failure. Note: multiple replicas each maintain their own independent cache.
10+
replicas: 1
11+
## @param varnish.graceSeconds Seconds to serve stale cached content when the backend is unavailable (e.g. during a rolling update). Set to 0 to disable grace mode.
12+
graceSeconds: 30
713
opentelemetry:
814
## @param varnish.opentelemetry.enabled Enable tracing VCL and sidecar containers (trace-converter + trace-forwarder)
915
enabled: false

charts/ontoserver/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ The recommended production topology separates content development from publicati
6767

6868
**Development / local** — ephemeral storage with the sidecar PostgreSQL avoids cloud disk provisioning. The index is rebuilt from syndication feeds on each start, which is acceptable at small scale.
6969

70-
> **`$closure` note:** The [`$closure` FHIR operation](https://www.hl7.org/fhir/conceptmap-operation-closure.html) is stateful. On a scaled read-only cluster it requires either sticky sessions (client-side routing) or a dedicated stateful instance alongside the cluster — the scaled cluster provides no built-in sticky session support.
70+
> **`$closure` routing:** The [`$closure` FHIR operation](https://www.hl7.org/fhir/conceptmap-operation-closure.html) is stateful — all requests for a given closure table must reach the same instance. For scaled StatefulSet deployments, the chart automatically creates a dedicated `RELEASE-ontoserver-pod0-service` that selects only pod-0, and routes `/fhir/ConceptMap/$closure` to it in both the Gateway HTTPRoute and Ingress — before the catchall `/` rule. This keeps `$closure` functional on a scaled cluster without requiring client-side sticky sessions. The routing is active whenever `deployment.kind: StatefulSet` and `deployment.type: scaled`, regardless of other settings.
7171
>
7272
> **Feeds must stay available:** New instances (after a pod is rescheduled or the cluster is scaled up) rebuild their local index from the syndication feeds that originally loaded the content. If those feeds become unavailable, new instances cannot complete startup and will not become ready.
7373

charts/ontoserver/templates/gateway.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,20 @@ spec:
6767
name: {{ $.Release.Name }}-gw
6868
{{- end }}
6969
rules:
70+
{{- if and (eq .Values.ontoserver.deployment.kind "StatefulSet") (eq .Values.ontoserver.deployment.type "scaled") }}
71+
# Route $closure to a dedicated pod — this FHIR operation is stateful and requires
72+
# all requests to always hit the same Ontoserver instance.
73+
- backendRefs:
74+
- group: ''
75+
kind: Service
76+
name: {{ .Release.Name }}-ontoserver-pod0-service
77+
port: 80
78+
weight: 1
79+
matches:
80+
- path:
81+
type: PathPrefix
82+
value: /fhir/ConceptMap/$closure
83+
{{- end }}
7084
- backendRefs:
7185
- group: ''
7286
kind: Service

charts/ontoserver/templates/ingress.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ spec:
3434
- host: {{ . | quote }}
3535
http:
3636
paths:
37+
{{- if and (eq $.Values.ontoserver.deployment.kind "StatefulSet") (eq $.Values.ontoserver.deployment.type "scaled") }}
38+
# Route $closure to a dedicated pod — this FHIR operation is stateful and requires
39+
# all requests to always hit the same Ontoserver instance.
40+
- path: /fhir/ConceptMap/$closure
41+
pathType: Prefix
42+
backend:
43+
service:
44+
name: {{ $releaseName }}-ontoserver-pod0-service
45+
port:
46+
number: 80
47+
{{- end }}
3748
- path: /
3849
pathType: Prefix
3950
backend:

0 commit comments

Comments
 (0)