Backend CRD http_request_rule_list changes don't trigger HAProxy reload
Environment
| Component |
Version |
| HAProxy Ingress Controller |
3.2.6 |
| Helm Chart |
kubernetes-ingress-1.49.0 |
| HAProxy |
3.2.12 |
| Backend CRD API |
ingress.v3.haproxy.org/v3 |
| Kubernetes |
1.30 (AKS) |
Description
When modifying only the http_request_rule_list field in a Backend CRD (v3), HAProxy does not reload with the new rules. The rules are written to the controller's in-memory state but never committed to the HAProxy config parser transaction, so haproxy.cfg remains unchanged.
Steps to Reproduce
- Create a Backend CRD with an
http_request_rule_list:
apiVersion: ingress.v3.haproxy.org/v3
kind: Backend
metadata:
name: test-backend
namespace: default
spec:
name: test-backend
server_timeout: 30000
connect_timeout: 5000
http_request_rule_list:
- cond: if
cond_test: '{ req.hdr(X-Block-Me) -m found }'
deny_status: 403
type: deny
-
Create an Ingress referencing this Backend CRD via haproxy.org/cr-backend annotation
-
Verify the rule appears in haproxy.cfg:
kubectl exec -n <controller-ns> <haproxy-pod> -- grep "X-Block-Me" /etc/haproxy/haproxy.cfg
-
Modify the Backend CRD http_request_rule_list (e.g., change X-Block-Me to X-Block-Test)
-
Check HAProxy reload count:
kubectl exec -n <controller-ns> <haproxy-pod> -- sh -c "echo 'show proc' | socat stdio /var/run/haproxy-master.sock"
Expected: Reload count increases, haproxy.cfg contains new rule
Actual: Reload count unchanged, haproxy.cfg still has old rule
Root Cause Analysis
The bug is in pkg/haproxy/api/httprequest.go. The HTTPRequestRulesReplace function for parentType == "backend" only updates the in-memory c.backends map and returns without writing to the config parser transaction:
// pkg/haproxy/api/httprequest.go lines 116-125
func (c *clientNative) HTTPRequestRulesReplace(parentType, parentName string, rules models.HTTPRequestRules) error {
configuration, _ := c.nativeAPI.Configuration()
if parentType == "backend" {
backend, _ := c.backends[parentName]
backend.HTTPRequestRuleList = rules
c.backends[parentName] = backend
return nil // <-- Returns here, never calls configuration.ReplaceHTTPRequestRules()
}
// ... frontend handling ...
return configuration.ReplaceHTTPRequestRules(parentType, parentName, rules, c.activeTransaction, 0)
}
The flow:
httprequests.Populate() detects rule diff via rules.Diff(currentHTTPRequests)
- Calls
HTTPRequestRulesReplace("backend", name, rules) — updates only c.backends (in-memory)
- Calls
instance.Reload() — sets reload flag
- But config parser transaction is unchanged → hash unchanged → no commit →
haproxy.cfg unchanged
- Next cycle:
HTTPRequestRulesGet reads from c.backends (now updated) → no diff found → permanently stuck
Workaround
When changing http_request_rule_list, also change a BackendBase field like server_timeout by 1ms:
spec:
server_timeout: 30001 # was 30000
http_request_rule_list:
- ...new rules...
This triggers BackendBase.Diff() → proper reload → new rules take effect.
Related
Backend CRD
http_request_rule_listchanges don't trigger HAProxy reloadEnvironment
ingress.v3.haproxy.org/v3Description
When modifying only the
http_request_rule_listfield in a Backend CRD (v3), HAProxy does not reload with the new rules. The rules are written to the controller's in-memory state but never committed to the HAProxy config parser transaction, sohaproxy.cfgremains unchanged.Steps to Reproduce
http_request_rule_list:Create an Ingress referencing this Backend CRD via
haproxy.org/cr-backendannotationVerify the rule appears in
haproxy.cfg:Modify the Backend CRD
http_request_rule_list(e.g., changeX-Block-MetoX-Block-Test)Check HAProxy reload count:
Expected: Reload count increases,
haproxy.cfgcontains new ruleActual: Reload count unchanged,
haproxy.cfgstill has old ruleRoot Cause Analysis
The bug is in
pkg/haproxy/api/httprequest.go. TheHTTPRequestRulesReplacefunction forparentType == "backend"only updates the in-memoryc.backendsmap and returns without writing to the config parser transaction:The flow:
httprequests.Populate()detects rule diff viarules.Diff(currentHTTPRequests)HTTPRequestRulesReplace("backend", name, rules)— updates onlyc.backends(in-memory)instance.Reload()— sets reload flaghaproxy.cfgunchangedHTTPRequestRulesGetreads fromc.backends(now updated) → no diff found → permanently stuckWorkaround
When changing
http_request_rule_list, also change aBackendBasefield likeserver_timeoutby 1ms:This triggers
BackendBase.Diff()→ proper reload → new rules take effect.Related
http_response_rule_listnot working — likely same root cause