bug: exit consul-dataplane when Envoy version is rejected by Consul server - #1252
Open
himanshu-8693 wants to merge 2 commits into
Open
bug: exit consul-dataplane when Envoy version is rejected by Consul server#1252himanshu-8693 wants to merge 2 commits into
himanshu-8693 wants to merge 2 commits into
Conversation
…erver When the Consul server closes the ADS gRPC stream because the connected Envoy version is too old, consul-dataplane previously kept running indefinitely. This is a permanent, non-retriable condition — no amount of retrying will allow the process to function. The zombie container blocks rolling deployments in orchestrated environments (e.g. ECS, Kubernetes) because the orchestrator treats the task/pod as healthy since the process is still alive. Fix: - Add an envoyLogScanner (io.Writer wrapper) that is set as the EnvoyErrorStream for the Envoy subprocess. It forwards every write to the underlying writer (os.Stderr) and scans for the substring 'is too old and is not supported by Consul' which Envoy emits when the Consul server closes the ADS stream with UNAVAILABLE status. - On detection it logs a clear ERROR-level message and calls xdsServer.closeExitedCh() which feeds into the existing doneCh-based shutdown path in consul_dataplane.go — killing the Envoy subprocess and returning a non-zero exit code. - closeExitedCh() uses sync.Once to prevent double-close panics from concurrent rejection writes. - Detection is done at the Envoy stderr layer because the rejection arrives as HTTP/2 trailers on the proxied gRPC stream, which the grpc-proxy library forwards to the Envoy client without surfacing them as a Go error in the stream interceptor chain. Tests added: - TestEnvoyLogScannerFatalExit: table-driven, covers exact message, different version numbers, unrelated lines, and empty writes. - TestEnvoyLogScannerCloseOnce: verifies sync.Once prevents double-close panics when the message is written multiple times concurrently. Validated against a live KIND cluster with Consul 2.0.0+ent: - Stock binary + Envoy 1.33.4: container remains Running indefinitely - Fixed binary + Envoy 1.33.4: container reaches Error state in <5s with the expected ERROR log and clean Envoy process teardown.
Go Test Coverage: 67.0%Patch coverage: 67.9% (19/28 changed lines covered) See the workflow run for the full per-package breakdown and downloadable HTML report. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1252 +/- ##
==========================================
+ Coverage 68.28% 68.67% +0.39%
==========================================
Files 19 19
Lines 2188 2203 +15
==========================================
+ Hits 1494 1513 +19
+ Misses 593 588 -5
- Partials 101 102 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When the Consul server closes the ADS gRPC stream because the connected Envoy version is too old,
consul-dataplanepreviously kept running indefinitely. This is a permanent, non-retriable condition — no amount of retrying will allow the process to serve traffic.The zombie container blocks rolling deployments in orchestrated environments (ECS, Kubernetes) because the orchestrator treats the task/pod as healthy since the process is still running, and new tasks wait for the old ones to drain.
Example Envoy log line emitted when this happens:
Related upstream issue: #1245
Root Cause Analysis
The Consul server closes the ADS stream using HTTP/2 trailers (
grpc-status: 3 UNAVAILABLE,grpc-message: Envoy X.Y.Z is too old and is not supported by Consul). Thegrpc-proxytransparent handler forwards those trailers to the Envoy-facing side and returnsio.EOF— not a gRPC status error — to the stream interceptor chain. This means aStreamServerInterceptorcannot observe the rejection as an actionable error value.Envoy itself always logs the full rejection message to its stderr stream (
cmd.Stderr = p.cfg.EnvoyErrorStreaminpkg/envoy/proxy.go). This is the reliable, version-agnostic interception point.Fix
Added an
envoyLogScanner— a lightweightio.Writerwrapper — that is set as theEnvoyErrorStreamfor the Envoy subprocess. It:Write()call transparently to the underlying writer (os.Stderr), so logging is unaffected."is too old and is not supported by Consul"(version-number-agnostic).ERROR-level message and callsxdsServer.closeExitedCh(), which feeds into the existingdoneCh-based shutdown path — killing the Envoy subprocess and returning a non-zero exit code.closeExitedCh()usessync.Onceto prevent double-close panics if multiple concurrent writes carry the same message.Files changed
pkg/consuldp/xds.goenvoyVersionUnsupportedMsgconstant;newEnvoyLogScanner()constructor;envoyLogScannerstruct andWrite()method;xdsServer.closeExitedCh()usingsync.Oncepkg/consuldp/consul_dataplane.gocloseOnce sync.OncetoxdsServerstruct;envoyProxyConfig()setsEnvoyErrorStream: cdp.newEnvoyLogScanner(os.Stderr);startXDSServerusescloseExitedCh()pkg/consuldp/xds_test.goTestEnvoyLogScannerFatalExit(table-driven, 4 cases);TestEnvoyLogScannerCloseOnce(concurrent double-write panic guard)Testing
Unit tests
All tests pass. Coverage:
exitedChclosed ✅exitedChclosed ✅exitedChstays open ✅exitedChstays open ✅sync.Onceguard) ✅Live cluster validation (KIND + Consul 2.0.0+ent)
Tested two images built with Envoy 1.33.4 (rejected by Consul ≥2.0):
stockRunningindefinitely — zombiefixedErrorstate in <5 secondsFixed binary log sequence on exit:
Changelog