Configure Upstream Health Checks
Configure active and passive upstream health checks for APISIX or API7 Ingress Controller.
Applies to:API7 Ingress Controller (drives API7 Gateway)APISIX Ingress Controller (drives Apache APISIX)
Goal
Configure active and passive health checks for upstream Services using the Ingress Controller and verify how the gateway marks nodes healthy or unhealthy.
Before you begin
- Complete Set Up Ingress Controller and Gateway.
- You have
kubectlconfigured against the cluster used in the setup guide. - The examples use the
aicnamespace and APISIX Control API on port9090. For API7 Gateway, replaceGATEWAY_POD_NAMEwith a gateway Pod name.
Set the variables this guide uses:
export UPSTREAM1="nginx1"
export UPSTREAM2="nginx2"
export UPSTREAM1_PORT="8080"
export UPSTREAM2_PORT="8081"
export UPSTREAM_TARGET_PORT="80"
export GATEWAY_SERVICE_NAME="apisix-gateway"
export GATEWAY_HTTP_PORT="80"
export FORWARD_PORT="9080"
export CONTROL_PORT="9090"
export CONTROL_FORWARD_PORT="9090"
export APISIX_CONTROL_SERVICE="apisix-control"
export GATEWAY_POD_NAME="api7-gateway-0" # replace with your API7 Gateway Pod nameSteps
Step 1: Deploy the example upstream services
Create a manifest for two NGINX Services in the aic namespace:
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: aic
name: nginx1
spec:
replicas: 1
selector:
matchLabels:
app: nginx1
template:
metadata:
labels:
app: nginx1
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: nginx1
spec:
selector:
app: nginx1
ports:
- name: http
port: 8080
targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: aic
name: nginx2
spec:
replicas: 1
selector:
matchLabels:
app: nginx2
template:
metadata:
labels:
app: nginx2
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: nginx2
spec:
selector:
app: nginx2
ports:
- name: http
port: 8081
targetPort: 80Apply the manifest and wait for the Pods to become ready:
kubectl apply -f nginx.yaml
kubectl wait --for=condition=ready pod -l app=nginx1 -n aic --timeout=120s
kubectl wait --for=condition=ready pod -l app=nginx2 -n aic --timeout=120sPort-forward both Services to your local machine and verify they respond:
kubectl port-forward -n aic "svc/${UPSTREAM1}" "${UPSTREAM1_PORT}:${UPSTREAM1_PORT}" &
kubectl port-forward -n aic "svc/${UPSTREAM2}" "${UPSTREAM2_PORT}:${UPSTREAM2_PORT}" &for port in ${UPSTREAM1_PORT} ${UPSTREAM2_PORT}; do
curl -s "http://127.0.0.1:${port}" | grep -q "Welcome to nginx" &&
echo "NGINX welcome page available on port ${port}."
doneExpected output:
NGINX welcome page available on port 8080.
NGINX welcome page available on port 8081.Step 2: Enable the Control API
Export the current Helm values:
helm get values -n aic apisix --all > values.yamlAdd the Control API configuration in values.yaml:
apisix:
enabled: true
control:
enabled: trueUpgrade the release:
helm upgrade -n aic apisix apisix/apisix -f values.yamlPort-forward the Control API Service:
kubectl port-forward -n aic "service/${APISIX_CONTROL_SERVICE}" "${CONTROL_FORWARD_PORT}:${CONTROL_PORT}" &Create or update values.yaml to enable the Control API:
control:
enabled: true
ip: 127.0.0.1
port: 9090Port-forward the Control API from a gateway Pod:
kubectl port-forward -n aic "pod/${GATEWAY_POD_NAME}" "${CONTROL_FORWARD_PORT}:${CONTROL_PORT}" &Step 3: Configure active health checks
Active checks determine the health of upstream services by periodically sending requests and seeing how they respond.
Create manifests that configure active health checks on the two NGINX Services and a route to both:
apiVersion: apisix.apache.org/v1alpha1
kind: BackendTrafficPolicy
metadata:
namespace: aic
name: nginx1
spec:
targetRefs:
- name: nginx1
kind: Service
group: ""
sectionName: http
healthCheck:
active:
type: http
httpPath: /
healthy:
interval: 2s
successes: 1
unhealthy:
interval: 1s
timeouts: 3
---
apiVersion: apisix.apache.org/v1alpha1
kind: BackendTrafficPolicy
metadata:
namespace: aic
name: nginx2
spec:
targetRefs:
- name: nginx2
kind: Service
group: ""
sectionName: http
healthCheck:
active:
type: http
httpPath: /
healthy:
interval: 2s
successes: 1
unhealthy:
interval: 1s
timeouts: 3
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: example-hc-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /
backendRefs:
- name: nginx1
port: 8080
weight: 1
- name: nginx2
port: 8081
weight: 1apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: nginx
spec:
ingressClassName: apisix
externalNodes:
- type: Service
name: nginx1
port: 8080
- type: Service
name: nginx2
port: 8081
healthCheck:
active:
type: http
httpPath: /
healthy:
interval: 2s
successes: 1
unhealthy:
interval: 1s
timeout: 3
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: example-hc-route
spec:
ingressClassName: apisix
http:
- name: nginx-hc
match:
paths:
- /
upstreams:
- name: nginxApply the manifest:
kubectl apply -f active-health-checks.yamlsectionName selects a Service port by spec.ports[].name. If the named port does not exist, the policy is not attached. Check the policy status with:
kubectl describe backendtrafficpolicy ${UPSTREAM1} -n aicStep 4: Check active health check status changes
Forward the gateway HTTP port to your local machine:
kubectl port-forward -n aic "svc/${GATEWAY_SERVICE_NAME}" "${FORWARD_PORT}:${GATEWAY_HTTP_PORT}" &Send a request to the route to start health checks:
curl "http://127.0.0.1:${FORWARD_PORT}/"Query the health check endpoint:
curl "http://127.0.0.1:${CONTROL_FORWARD_PORT}/v1/healthcheck"You should see both upstream nodes with status: healthy.
Make one upstream unavailable
Scale one deployment down:
kubectl scale deployment ${UPSTREAM1} -n aic --replicas=0Wait a few seconds and query the health check endpoint again:
curl "http://127.0.0.1:${CONTROL_FORWARD_PORT}/v1/healthcheck"You should see one node marked unhealthy with timeout_failure counts.
Send a request to the route:
curl -i "http://127.0.0.1:${FORWARD_PORT}/"You should receive HTTP/1.1 200 OK.
Make both upstreams unavailable
Scale both deployments down:
kubectl scale deployment ${UPSTREAM2} -n aic --replicas=0Query the health check endpoint:
curl "http://127.0.0.1:${CONTROL_FORWARD_PORT}/v1/healthcheck"Both nodes should be unhealthy. Send a request to the route:
curl -i "http://127.0.0.1:${FORWARD_PORT}/"You should receive HTTP/1.1 502 Bad Gateway.
Recover both upstreams
Scale both deployments back up:
kubectl scale deployment -n aic ${UPSTREAM1} ${UPSTREAM2} --replicas=1Wait a few seconds and query the health check endpoint:
curl "http://127.0.0.1:${CONTROL_FORWARD_PORT}/v1/healthcheck"Both nodes should return to healthy.
Step 5: Configure active health checks that always report unhealthy
The gateway can still forward client requests to upstream services even when all active health statuses are unhealthy. To demonstrate, configure active health checks that probe a path that returns 404:
apiVersion: apisix.apache.org/v1alpha1
kind: BackendTrafficPolicy
metadata:
namespace: aic
name: nginx1
spec:
targetRefs:
- name: nginx1
kind: Service
group: ""
healthCheck:
active:
type: http
httpPath: /404
healthy:
interval: 2s
successes: 1
unhealthy:
interval: 1s
httpFailures: 2
---
apiVersion: apisix.apache.org/v1alpha1
kind: BackendTrafficPolicy
metadata:
namespace: aic
name: nginx2
spec:
targetRefs:
- name: nginx2
kind: Service
group: ""
healthCheck:
active:
type: http
httpPath: /404
healthy:
interval: 2s
successes: 1
unhealthy:
interval: 1s
httpFailures: 2
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: example-hc-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /
backendRefs:
- name: nginx1
port: 8080
weight: 1
- name: nginx2
port: 8081
weight: 1apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: nginx
spec:
ingressClassName: apisix
externalNodes:
- type: Service
name: nginx1
port: 8080
- type: Service
name: nginx2
port: 8081
healthCheck:
active:
type: http
httpPath: /404
healthy:
interval: 2s
successes: 1
unhealthy:
interval: 1s
httpFailures: 2
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: example-hc-route
spec:
ingressClassName: apisix
http:
- name: nginx-hc
match:
paths:
- /
upstreams:
- name: nginxApply the manifest:
kubectl apply -f active-health-checks.yamlStep 6: Test request forwarding when all upstreams are unhealthy
Send a request to the route:
curl -i "http://127.0.0.1:${FORWARD_PORT}/"You should receive HTTP/1.1 200 OK.
Query the health check endpoint:
curl "http://127.0.0.1:${CONTROL_FORWARD_PORT}/v1/healthcheck"Both nodes should be marked unhealthy with http_failure counters. This confirms the gateway still forwards client requests even when all upstream health statuses are unhealthy.
Step 7: Configure passive health checks
known issue
There is a known issue in the gateway where the passive health check data displayed through the Control API do not accurately reflect the actual health statuses, so your testing results may differ from the example shown. This issue is being actively resolved. However, the passive health check mechanism itself is functioning correctly and continues to route requests as intended.
To use passive health checks, you must also configure active health checks so the gateway can detect when an upstream recovers. The following manifest adds passive health checks to the existing active checks:
apiVersion: apisix.apache.org/v1alpha1
kind: BackendTrafficPolicy
metadata:
namespace: aic
name: nginx1
spec:
targetRefs:
- name: nginx1
kind: Service
group: ""
healthCheck:
active:
type: http
httpPath: /
healthy:
interval: 99999s
successes: 1
unhealthy:
interval: 30s
passive:
healthy:
httpCodes: [200, 201, 202, 300, 301, 302]
successes: 1
unhealthy:
httpCodes: [429, 404, 500, 501, 502, 503, 504, 505]
httpFailures: 3
---
apiVersion: apisix.apache.org/v1alpha1
kind: BackendTrafficPolicy
metadata:
namespace: aic
name: nginx2
spec:
targetRefs:
- name: nginx2
kind: Service
group: ""
healthCheck:
active:
type: http
httpPath: /
healthy:
interval: 99999s
successes: 1
unhealthy:
interval: 30s
passive:
healthy:
httpCodes: [200, 201, 202, 300, 301, 302]
successes: 1
unhealthy:
httpCodes: [429, 404, 500, 501, 502, 503, 504, 505]
httpFailures: 3
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: example-hc-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /404
backendRefs:
- name: nginx1
port: 8080
weight: 1
- name: nginx2
port: 8081
weight: 1apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: nginx
spec:
ingressClassName: apisix
externalNodes:
- type: Service
name: nginx1
port: 8080
- type: Service
name: nginx2
port: 8081
healthCheck:
active:
type: http
httpPath: /
healthy:
interval: 99999s
successes: 1
unhealthy:
interval: 30s
passive:
healthy:
httpCodes: [200,201,202,300,301,302]
successes: 1
unhealthy:
httpCodes: [429,404,500,501,502,503,504,505]
httpFailures: 3
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: example-hc-route
spec:
ingressClassName: apisix
http:
- name: nginx-hc
match:
paths:
- /404
upstreams:
- name: nginxApply the manifest:
kubectl apply -f passive-health-checks.yamlStep 8: Test passive health check behavior
Send a request to the /404 route:
curl -i "http://127.0.0.1:${FORWARD_PORT}/404"You should see HTTP/1.1 404 Not Found.
Query the health check endpoint:
curl "http://127.0.0.1:${CONTROL_FORWARD_PORT}/v1/healthcheck"One of the nodes should show an http_failure count and a mostly_healthy status, meaning the node is currently healthy but has started to receive unhealthy indications.
Generate consecutive 404 responses:
resp=$(seq 10 | xargs -I{} curl "http://127.0.0.1:${FORWARD_PORT}/404" -o /dev/null -s -w "%{http_code}\n") && \
count=$(echo "$resp" | grep "404" | wc -l) && \
echo "Invoked ${count} responses with 404 status code."Query the health check endpoint again:
curl "http://127.0.0.1:${CONTROL_FORWARD_PORT}/v1/healthcheck"Both nodes should now be unhealthy. Wait at least 30 seconds for active checks to probe / and mark them as healthy again. Then query the endpoint once more:
curl "http://127.0.0.1:${CONTROL_FORWARD_PORT}/v1/healthcheck"Both nodes should return to healthy.
Validate
As a final check, confirm the health check endpoint reports both nodes as healthy after the upstreams recover:
kubectl scale deployment -n aic ${UPSTREAM1} ${UPSTREAM2} --replicas=1
curl "http://127.0.0.1:${CONTROL_FORWARD_PORT}/v1/healthcheck"Expected: both nodes have status: healthy.
Roll back / Clean up
Delete the manifests and stop the port-forwards:
kubectl delete -f passive-health-checks.yaml
kubectl delete -f active-health-checks.yaml
kubectl delete -f nginx.yamlkill %1 2>/dev/null || true
kill %2 2>/dev/null || trueIf you updated the gateway Helm values for the Control API, re-apply the original values file or remove the control section.
Troubleshoot
| Symptom | Cause | Fix |
|---|---|---|
| The health check endpoint returns no data. | The Control API is not enabled, or the port-forward is not running. | Verify the control section is enabled and that CONTROL_FORWARD_PORT is forwarded. |
| Scaling down one deployment does not mark the node unhealthy. | The active check intervals are too long, or the health check has not had time to run. | Wait for the unhealthy.interval and timeouts thresholds configured in the BackendTrafficPolicy or ApisixUpstream. |
Passive checks show mostly_healthy but not unhealthy. | Not enough httpFailures have been accumulated from 404 responses. | Send more consecutive requests to the /404 route or lower httpFailures. |
The gateway returns 502 when only one node is down. | Both deployments are scaled to zero, or the HTTPRoute status is not Accepted. | Check kubectl get httproute example-hc-route -n aic and scale at least one upstream back up. |