-
Notifications
You must be signed in to change notification settings - Fork 833
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add IDestinationResolver
for resolving cluster destination addresses
#2210
Add IDestinationResolver
for resolving cluster destination addresses
#2210
Conversation
bed91e8
to
3b6e552
Compare
Is someone able to advise me on the best way to implement the abovementioned |
I spoke with @Tratcher about how to flow the |
19269d9
to
80648df
Compare
Alright, I consider this ready for review. |
Co-authored-by: Arvin Kahbazi <akahbazi@gmail.com>
@Tratcher @MihaZupan PTAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few minor suggestions, otherwise this LGTM, thank you.
Might be worth adding a mention in the docs somewhere that this is an available extension point.
src/ReverseProxy/Transforms/RequestHeaderOriginalHostTransform.cs
Outdated
Show resolved
Hide resolved
test/ReverseProxy.Tests/Transforms/Builder/TransformBuilderTests.cs
Outdated
Show resolved
Hide resolved
test/ReverseProxy.Tests/Transforms/Builder/TransformBuilderTests.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
bdb88e8
to
45cabf3
Compare
Feedback addressed, thank you @MihaZupan |
Added a short doc on destination resolvers |
I'll add an implementation of |
Ok, I've added a DNS |
Alternatively, we could scan over the query results and choose to prefer only IPv4 addresses or only IPv6 addresses if there is a mix of IPv4 and IPv6 addresses. I'm open to feedback. |
BTW note that I placed |
_logger.LogError(ex, "Error resolving destinations"); | ||
throw; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anti-pattern: Don't log and throw, this causes duplicate logs. The caller will log it right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The caller will rethrow on first load, with "Unable to load or apply the proxy configuration."
On reload, it will log "Failed to reload config. Unable to register for change notifications, polling for changes until successful."
i.e, it will be logged the same as an IProxyConfig.GetConfig()
exception. Is that fine?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's fine. If you want a more specific log, log without the full exception, maybe just the message?
DestinationConfig originalConfig, | ||
CancellationToken cancellationToken) | ||
{ | ||
var originalUri = new Uri(originalConfig.Address); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uri.TryParse? There's no telling what's in the config.
This is running before filters, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What should it do if the TryParse fails?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Log and return the original destination?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that valid behavior? Eg, a Warning level log saying Destination {DestinationId} has an unsupported Address. Addresses must be valid URIs.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're careful not to over-validate/restrict destination addresses because people can plug in alternative transports like named pipes and use custom address formats. I think we only validate that it's not null or empty. A warning log seems appropriate in this case.
/// <summary> | ||
/// Creates a <see cref="UriBuilder"/> from the provided address, only setting the Port property if it is not the default value. | ||
/// </summary> | ||
private static UriBuilder CreateUriBuilder(Uri uri) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is this different from new UriBuilder(uri)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the Port
. When I just use new UriBuilder(uri)
, it will include ":80" in the Host, which is ugly (it's probably not a showstopper). It should only include the port if it's a non-default port for the scheme. The doc comment tries to indicate this: "only setting the Port property if it is not the default value."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And there's no way to make it clear the default port later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not from what I could tell, and not from what Stack Overflow seemed to indicate 😁
/// <remarks> | ||
/// Defaults to 5 minutes. | ||
/// </remarks> | ||
public TimeSpan? RefreshPeriod { get; set; } = TimeSpan.FromMinutes(5); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you really expecting changes this often?
Ideally we'd use a dns library that could read the TTL data and refresh based on that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, but this is what gRPC ships with too (we should check their impl). Anything else requires external dependencies.
cc @JamesNK
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gRPC keeps open connections with resolved endpoints. This allows it to track the health of endpoints in realtime. If one of those connections goes down, such as because a k8s pod instance is restarted, then gRPC immediately asks the resolver to refresh itself. k8s will then return the new set of IP addresses, with the old pod instance removed and the new instance added, and gRPC attempts to connect to the new address.
The gRPC's DnsResolver
also has the option to configure an explicit refresh interval. This is mostly useful in situations where k8s scales up. Periodically checking DNS lets the client notice that there are new endpoints available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does DnsDestionationResolver need some kind of health check integration to trigger a refresh?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could integrate YARP's health checks here, potentially to reload the IProxyConfig
which the faulting destination belongs to. With DNS, we have no notification to tell us to perform a reload, but a Kubernetes-native solution could subscribe to those events by watching the relevant k8s Service resource for changes.
@@ -240,22 +242,31 @@ public void DefaultsCanBeDisabled() | |||
var results = transformBuilder.BuildInternal(route, new ClusterConfig()); | |||
Assert.NotNull(results); | |||
Assert.False(results.ShouldCopyRequestHeaders); | |||
Assert.Empty(results.RequestTransforms); | |||
Assert.Single(results.RequestTransforms); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unfortunate that this can't be turned off completely like before. I don't want us to end up with default behavior that the user can't override. They can still replace or remove the host themselves, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that this is somewhat unfortunate. The developer can still replace or remove the host. I had a version of this which moved the behavior to the front of the transform pipeline inside StructuredTransformer
itself, but after speaking with Miha about it, I moved it to the existing transform at the end of the pipeline instead.
Co-authored-by: Chris Ross <Tratcher@Outlook.com>
Woohoo, looks like this is green now. Ready to merge? |
This PR adds
IDestinationResolver
, an abstraction for expanding theClusterConfig.Destinations
property, resolving the destination addresses into one or more addresses. The intention is to better support service discovery in YARP.The PR also currently adds a
Host
property to theDestinationConfig
, which is indented to act as the HTTP host name (for virtual host & SNI support).