Skip to content

0DPE (Downstream Protocol Error) caused by HTTP2 Filter #25218

Open

Description

@Stono

Bug description
I don't even know where to start with this one... I have no idea what went wrong but I'm going to share what info I do have here in case it gets anyones attention because it was quite a significant failure which in the end was recovered by restarting the source workload (nginx).

I know correlation != causation, but we've been stable on 1.5.0-1.5.6 for some time, without issue, and upgraded to 1.5.7 4 days ago.

Symptoms:
We started to see waves of request fail between two specific workloads on the cluster (all other workloads were fine). The source workload being nginx and the destination workload being consumer-gateway.

Screenshot 2020-07-05 at 19 16 21

The errors came in waves of decreasing duration, you can see the scatter plot from Jaeger here how you get a spike, they gradually fail faster before eventually recovering.

image

There was no correlation on the source workload:
Screenshot 2020-07-05 at 19 35 31

The errors primarily manifested in two ways:

1. 503UC from the source sidecar
This represented the majority of errors recorded, a 503 was recorded from the source (nginx) sidecar. There was no span recorded from the destination (consumer-gateway) leading me to believe the request was never even sent.

Screenshot 2020-07-05 at 18 47 57

2. 0DPE
There was also a fair few of these "Downstream Protocol Errors" recorded by the destination (consumer-gateway) sidecar. These are interesting because it shows the request reached consumer-gateway

Screenshot 2020-07-05 at 19 16 57

So far I've checked the following:

  • There was nothing in pilot logs, nothing in the istio-proxy logs (although we run those in warn).
  • There was no correlation from an infrastructure perspective (we observed failures across several nodes, across all zones). The only correlation was the fact the issue was between just these two workloads.
  • We have rafts of network testing which indicated no issue.
  • I wondered if this could be something odd to do with mtls cert rotation, but doesn't look like the certificates on either have changed recently:
❯ k -n consumer-gateway get secret istio.default -o json | jq -r '.data["cert-chain.pem"]' | base64 --decode | openssl x509 -noout -text | grep Before
            Not Before: Jun 18 17:57:06 2020 GMT

❯ k -n ingress-nginx get secret istio.default -o json | jq -r '.data["cert-chain.pem"]' | base64 --decode | openssl x509 -noout -text | grep Before
            Not Before: Jun 18 17:53:33 2020 GMT

Both workloads have been running for 4 days, since we upgraded to 1.5.7 (which is actually the only thing which has changed recently).

It's worth nothing we use both mTLS and http2 (via EnvoyFilter):

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: consumer-gateway
  namespace: consumer-gateway
spec:
  host: app
  trafficPolicy:
    connectionPool:
      http:
        http1MaxPendingRequests: 2147483646
        http2MaxRequests: 2147483646
        idleTimeout: 30m
        maxRequestsPerConnection: 2147483646
        maxRetries: 2147483646
      tcp:
        maxConnections: 2147483646
        tcpKeepalive:
          interval: 30s
          time: 30s
    loadBalancer:
      simple: RANDOM
    outlierDetection:
      baseEjectionTime: 2m
      consecutiveErrors: 2
      interval: 5s
      maxEjectionPercent: 50
      minHealthPercent: 50
    tls:
      mode: ISTIO_MUTUAL
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: h2-consumer-gateway
  namespace: istio-system
spec:
  configPatches:
  - applyTo: CLUSTER
    match:
      cluster:
        service: app.consumer-gateway.svc.cluster.local
      context: SIDECAR_OUTBOUND
    patch:
      operation: MERGE
      value:
        http2_protocol_options:
          max_concurrent_streams: 47707

However neither of these things have changed in way over 6 months

[ ] Configuration Infrastructure
[ ] Docs
[ ] Installation
[x] Networking
[ ] Performance and Scalability
[ ] Policies and Telemetry
[x] Security
[ ] Test and Release
[ ] User Experience
[ ] Developer Infrastructure

Expected behavior
Not to break :-)

Steps to reproduce the bug
I've not managed to reproduce this

Version (include the output of istioctl version --remote and kubectl version and helm version if you used Helm)
1.5.7

How was Istio installed?
Helm

Environment where bug was observed (cloud vendor, OS, etc)
GKE 1.15

Activity

  1. Stono commented on Jul 5, 2020

    @Stono
    ContributorAuthor

    I had a thought that maybe the DPE's (whatever caused those) in turn caused outlier detection to kick in, unfortunately we weren't scraping envoy metrics into prometheus so i don't have historical data, but one of the pods had:

    ❯ cat ~/Desktop/metrics|grep outlier_dete | grep consumer
    envoy_cluster_outlier_detection_ejections_enforced_total{cluster_name="outbound|80||app.consumer-gateway.svc.cluster.local"} 112
    envoy_cluster_outlier_detection_ejections_active{cluster_name="outbound|80||app.consumer-gateway.svc.cluster.local"} 3
    

    For context there are 6 nginx and 6 consumer-gateway pods.

    However that doesn't explain why we were getting request failures as we have a max ejection % of 50? It’s like all hosts were evicted and the requests just stalled resulting in a 503.

    I've raised #25219 which would be nice, ability to enable outlier logs on a pod level rather than globally

  2. Stono commented on Jul 5, 2020

    @Stono
    ContributorAuthor

    Also it looks as if outlierDetection is not working as documented, we're getting evictions on 5xx codes despite explicit configuration to exclude them: #25220

  3. Stono commented on Jul 5, 2020

    @Stono
    ContributorAuthor

    @mandarjog could this be some sort of unexpected interaction with http2+outlier?

    @howardjohn and ideas? It was quite a nasty incident for us :(

  4. rshriram commented on Jul 6, 2020

    @rshriram
    Member

    For posterity, 1.5.7 was a security release and nothing more. The only commits were pertaining to the CVEs https://istio.io/latest/news/releases/1.5.x/announcing-1.5.7/

    As you can see from the commit log https://github.com/istio/istio/commits/1.5.7 since 1.5.6 (june 15th), the only change has been to update the proxy SHA. If this issue is continuing to happen, can you try 1.5.6 proxy image (I know it has some CVEs mentioned above. So try it in some safer setup).

  5. Stono commented on Jul 6, 2020

    @Stono
    ContributorAuthor

    We've only had one occurrence of this so far (thankfully) which unfortunately means that rolling back won't give a timely, deterministic data point. If we see it pop again, that will be what we try next.

  6. howardjohn commented on Jul 6, 2020

    @howardjohn
    Member

    @lambdai I have never seen a DPE before.. any ideas what can cause this?

  7. lambdai commented on Jul 6, 2020

    @lambdai
    Contributor

    WRT maxEjectionPercent not working as expected: I recently fixed a bug in upstream envoy but not back ported. That config is activated only if the stats is enabled until the fix. Could you double check if the stats is enabled at your istio? That cluster stats is NOT enabled by default
    envoyproxy/envoy#11629

  8. lambdai commented on Jul 6, 2020

    @lambdai
    Contributor

    Explained in #25220 that outlier detector sees a wider occurrence of 5xx. The metric shows outlier detector is triggered, likely the gateway error.

  9. lambdai commented on Jul 6, 2020

    @lambdai
    Contributor

    "DPE" is new to me, too. I need to dig

  10. Stono commented on Jul 6, 2020

    @Stono
    ContributorAuthor

    WRT maxEjectionPercent not working as expected: I recently fixed a bug in upstream envoy but not back ported. That config is activated only if the stats is enabled until the fix. Could you double check if the stats is enabled at your istio? That cluster stats is NOT enabled by default

    Hey I've provided you my config dumps and metrics - can you confirm if i'm perhaps being hit by that bug @lambdai please and if so what the impact is as it isn't clear to me?

    This is my stats config:

    sidecar.istio.io/statsInclusionSuffixes: outlier_detection.ejections_active,outlier_detection.ejections_overflow,outlier_detection.ejections_enforced_total,upstream_rq_total,upstream_cx_active,upstream_cx_total
    
  11. lambdai commented on Jul 6, 2020

    @lambdai
    Contributor

    GHSA-8hf8-8gvw-ggvx is one of the vulnerability fix in 1.5.7
    I am not fully sure your case falls into this bucket, but envoy is more aggressive in resetting streams and dest envoy says DPE (due to flood detection). Meanwhile the source envoy would interpret as 5XX(upstream failure)

  12. lambdai commented on Jul 6, 2020

    @lambdai
    Contributor

    istio/envoy@ea2d62e#diff-0cde9df72a69112b1d9af9cc414a107bR315

    If the timeout is hit in this case, the :ref:`tx_flush_timeout <config_http_conn_man_stats_per_codec>` counter will be incremented.
    

    Is there any luck this counter is captured by prometheus?

  13. Stono commented on Jul 7, 2020

    @Stono
    ContributorAuthor

    Yes that metric is there but its 0:

    # TYPE envoy_cluster_http2_tx_flush_timeout counter
    envoy_cluster_http2_tx_flush_timeout{cluster_name="xds-grpc"} 0
    

    It's worth noting btw we only really see this under heavy load. Right now (about 50% load) we're hardly getting any outlier ejections active.

    Unfortunately I was not able to enable the outlierLog because of another bug! #25269

    I'm going to try rolling back to 1.5.6 to see what happens.

    @lambdai I still don't understand if what that stats bug you fixed does, or if i'm impacted by it?

  14. 32 remaining items

  15. added
    lifecycle/staleIndicates a PR or issue hasn't been manipulated by an Istio team member for a while
    on Oct 29, 2020
  16. added
    lifecycle/staleproofIndicates a PR or issue has been deemed to be immune from becoming stale and/or automatically closed
    and removed
    lifecycle/staleIndicates a PR or issue hasn't been manipulated by an Istio team member for a while
    on Oct 29, 2020
  17. Stono commented on Dec 7, 2020

    @Stono
    ContributorAuthor

    @howardjohn @mandarjog @lambdai we recently tried turning on http2 again on 1.7 (this issue was raised on 1.5) and can confirm after a few days (3) we started to see the same failures, and backed it out. I don't really have any more information other than what has been shared on this thread.

    The extremely difficult thing here it is takes days to start manifesting and when it does it's only on a service that receives very high throughput - so capturing istio-proxy logs can be difficult.

  18. Stono commented on Dec 7, 2020

    @Stono
    ContributorAuthor

    In fact one other piece of information I was able to glean from our edge logs was that the issues affected all different services routing through consumer-gateway.

    So as a refresher, consumer-gateway is a zuul proxy to a variety (20+) downstream apps. During the latest incident, as you can see here, all different paths (so subsequently all different services) started having errors. This points more towards an Istio level issue, rather than an application level issue like @lambdai pointed out (eg duplicated headers etc) as the impact was simply too broad (and all consumer-gateway does is blind reverse-proxy, and was not modified during this time).

    Screenshot 2020-12-07 at 09 43 26

  19. Stono commented on Feb 28, 2021

    @Stono
    ContributorAuthor

    As part of #31136 i've turned http2 on for one of our services again. This time we're on 1.8.3

  20. Stono commented on Oct 29, 2021

    @Stono
    ContributorAuthor

    @howardjohn as you saw in slack we decided to make http2 the default again.
    After a indeterminate amount of time, we start to observe 503UC.

    Screenshot 2021-10-29 at 06 58 07

    You can see from the trace it's effectively the first "hop" that fails:

    Screenshot 2021-10-29 at 07 01 00

    With a DPE:
    Screenshot 2021-10-29 at 07 01 27

    So whatever this issue is, it's still present

  21. Stono commented on Oct 29, 2021

    @Stono
    ContributorAuthor

    Interestingly, it's the same service as last year (ingress-nginx -> consumer-gateway)

  22. mandarjog commented on Oct 29, 2021

    @mandarjog
    Contributor

    What are the response flag details ?
    Is the following the correct flow?
    Nginx-ingress + envoy —> (h2) —> envoy sidecar + zuul ( consumer gw)
    This means the sidecar is encoding h2 which it cannot decode on the other side…

  23. Stono commented on Oct 29, 2021

    @Stono
    ContributorAuthor

    What are the response flag details ?

    What do you mean sorry? Is there some other info I can get for you?

    Is the following the correct flow?

    Yes that's the correct flow

    This means the sidecar is encoding h2 which it cannot decode on the other side…

    Yeah but why would that happen? And also, why would it just degrade without any releases or code changes, and be immediately fixed by toggling http2 on and off! This happened at 6am one morning, http2 had been enabled on consumer-gateway config quite some time before that.

  24. mandarjog commented on Oct 29, 2021

    @mandarjog
    Contributor

    https://istio.io/latest/docs/tasks/observability/logs/access-log/#default-access-log-format Look for %RESPONSE_CODE_DETAILS%

    It is possible that this is data dependent. So some specific headers may bring out this behaviour in h2 processing .

    @lambdai is there a way to dump full request contents when a DPE is detected? We may need a special istio-proxy build.

  25. Stono commented on Oct 29, 2021

    @Stono
    ContributorAuthor

    So we don't have the access log enabled by default as its noisy and gives us little over what Jaeger does.
    And restarting the workload will make the problem temporarily go away (so we can't add annotations).
    Is there any way for me to enable access log, at run time?
    Or perhaps an EnvoyFilter that would log out the request context when response 0DPE?

  26. swarnar87 commented on Apr 30, 2022

    @swarnar87

    We have the exact same issue. Any way to log ingress request headers? This is the error in the logs:
    [debug][http] [source/common/http/conn_manager_impl.cc:322] [C18109] dispatch error: http/1.1 protocol error: HPE_INVALID_HEADER_TOKEN

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/networkinglifecycle/staleproofIndicates a PR or issue has been deemed to be immune from becoming stale and/or automatically closed

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions