Skip to content

CDS Updates with many clusters often fail #12138

Open
@Starblade42

Description

@Starblade42

void CdsApiImpl::onConfigUpdate(const std::vector<Config::DecodedResourceRef>& added_resources,
const Protobuf::RepeatedPtrField<std::string>& removed_resources,
const std::string& system_version_info) {
Config::ScopedResume maybe_resume_eds;
if (cm_.adsMux()) {
const auto type_urls =
Config::getAllVersionTypeUrls<envoy::config::endpoint::v3::ClusterLoadAssignment>();
maybe_resume_eds = cm_.adsMux()->pause(type_urls);
}
ENVOY_LOG(info, "cds: add {} cluster(s), remove {} cluster(s)", added_resources.size(),
removed_resources.size());
std::vector<std::string> exception_msgs;
std::unordered_set<std::string> cluster_names;
bool any_applied = false;
for (const auto& resource : added_resources) {
envoy::config::cluster::v3::Cluster cluster;
try {
cluster = dynamic_cast<const envoy::config::cluster::v3::Cluster&>(resource.get().resource());
if (!cluster_names.insert(cluster.name()).second) {
// NOTE: at this point, the first of these duplicates has already been successfully applied.
throw EnvoyException(fmt::format("duplicate cluster {} found", cluster.name()));
}
if (cm_.addOrUpdateCluster(cluster, resource.get().version())) {
any_applied = true;
ENVOY_LOG(info, "cds: add/update cluster '{}'", cluster.name());
} else {
ENVOY_LOG(debug, "cds: add/update cluster '{}' skipped", cluster.name());
}
} catch (const EnvoyException& e) {
exception_msgs.push_back(fmt::format("{}: {}", cluster.name(), e.what()));
}
}
for (const auto& resource_name : removed_resources) {
if (cm_.removeCluster(resource_name)) {
any_applied = true;
ENVOY_LOG(info, "cds: remove cluster '{}'", resource_name);
}
}
if (any_applied) {
system_version_info_ = system_version_info;
}
runInitializeCallbackIfAny();
if (!exception_msgs.empty()) {
throw EnvoyException(
fmt::format("Error adding/updating cluster(s) {}", absl::StrJoin(exception_msgs, ", ")));
}
}

This is a performance issue, not a bug per se.

When doing CDS updates with many clusters, Envoy will often get "stuck" evaluating the CDS update. This manifests as EDS failing, and in more extreme cases, the envoy ceases to receive any XDS updates. When this happens, Envoy needs to be restarted to get it updating again.

In our case we're seeing issues with the current implementation of void CdsApiImpl::onConfigUpdate with a number of clusters in the 3000-7000 range. If envoy could speedily evaluate a CDS update with 10000 clusters this would represent a HUGE improvement in Envoy's behavior for us. Right now, only around 2500 clusters in a CDS update seems to evaluate in reasonable amount of time.

Because the function void CdsApiImpl::onConfigUpdate pauses EDS while doing CDS evaluation, envoy's config will drift. With many clusters in CDS, this can mean envoy is hundreds of seconds behind what is current, and results in 503's.

Some context:

  • Envoy in my test environment is being run without constraints on 8 core VMs that are running at a max of 30% CPU utilization and max 40% memory (of 32 GB):
        resources:
          limits:
            memory: "32212254720"
          requests:
            cpu: 100m
            memory: 256M

Contour currently doesn't use the incremental APIs of Envoy, so when K8s services change in the cluster it sends ALL of the current config again to Envoy, which means a small change like adding or removing a K8s SVC which maps to an envoy cluster results in Envoy having to re-evaluate ALL clusters.

With enough K8s Services behind an Ingress (7000+) envoy can spontaneously cease to receive any new updates indefinitely, and will fail to do EDS because it gets stuck in the CDS evaluation.

Given a high enough number of clusters this would be a Very Hard Problem, but given that we're in the low thousands, I'm hoping there are some things that could be done to improve performance without resorting to exotic methods.

Please let me know if there's any information I can provide that could help!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions