-
Notifications
You must be signed in to change notification settings - Fork 339
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
feat(dns): embedded DNS server instead of coreDNS #13124
base: master
Are you sure you want to change the base?
Conversation
For DNS proxy we'd like to reuse the same configFetcher mechanism Split it out to its own package and define an api to add handlers Also add support for the handlers to handle etag to avoid reloading when the config doesn't change Signed-off-by: Charly Molter <[email protected]>
Also add support for etag caching Signed-off-by: Charly Molter <[email protected]>
Signed-off-by: Charly Molter <[email protected]>
I've made a few separate commits as this is a bit of a mouthful |
Reviewer Checklist🔍 Each of these sections need to be checked by the reviewer of the PR 🔍:
|
Signed-off-by: Charly Molter <[email protected]>
Signed-off-by: Charly Molter <[email protected]>
Signed-off-by: Charly Molter <[email protected]>
}) | ||
prometheus.MustRegister(handlerTickCount) | ||
handlerTickDuration := prometheus.NewSummary(prometheus.SummaryOpts{ | ||
Name: "kuma_dp_envoyconfigfetcher_handler_call_duration_seconds", |
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.
why in seconds
? aren't these calls usually less than a second? also I think in other places we mostly use milliseconds
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'm not sure it's always milliseconds
:
Help: "Time Blocked waiting for new connection in seconds", |
Expect(test_metrics.FindMetric(metrics, "api_server_http_request_duration_seconds")).ToNot(BeNil()) |
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.
Look at the prom API it takes float64 and it's meant to be seconds (that's also why Duration.Seconds() returns a float64)
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 see, I just saw we have lots of .Observe(float64(time.Since(start).Milliseconds()))
❯ rg Observe\(.*Milliseconds\(\)' .
./pkg/dns/vips_allocator.go
68: d.metrics.VipGenerations.Observe(float64(core.Now().Sub(start).Milliseconds()))
./pkg/insights/resyncer.go
365: r.idleTime.Observe(float64(startProcessingTime.Sub(start).Milliseconds()))
366: r.timeToProcessItem.Observe(float64(startProcessingTime.Sub(event.time).Milliseconds()))
401: r.itemProcessingTime.WithLabelValues(reason, result).Observe(float64(time.Since(startProcessingTime).Milliseconds()))
./pkg/metrics/store/counter.go
74: s.metric.Observe(float64(core.Now().Sub(start).Milliseconds()))
...
but apparently, we haven't named some metrics properly, i.e. it's called vip_generation
but actually reports milliseconds. I'll open an issue to track this
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 not dramatic because the metric doesn't contain a unit:
kuma/pkg/dns/metrics/metrics.go
Line 16 in aae7e5e
Name: "vip_generation", |
But prometheus generally recommends using seconds: https://prometheus.io/docs/practices/naming/#base-units though it also recommends using the unit name in the metric name to help with ambiguity
response := &dns.Msg{} | ||
// In case it was never loaded | ||
s.dnsMap.CompareAndSwap(nil, &dnsMap{ | ||
ARecords: make(map[string]*dnsEntry), |
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.
shouldn't we also handle SRV records like envoy do?
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 would we do with SRV records? While Envoy handles SRV record the map we configure is empty so Envoy would return NXDOMAIN and fallback to proxying
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.
In general looks promising, a bunch of questions and nitpicks. Also would love to see a simple test checking integration with a DNS client.
- Is the PR title satisfactory? Is this part of a larger feature and should be grouped using
> Changelog
? - PR description is clear and complete. It Links to relevant issue as well as docs and UI issues
- This will not break child repos: it doesn't hardcode values (.e.g "kumahq" as an image registry)
- IPv6 is taken into account (.e.g: no string concatenation of host port)
- Tests (Unit test, E2E tests, manual test on universal and k8s)
- Don't forget
ci/
labels to run additional/fewer tests
- Don't forget
- Does this contain a change that needs to be notified to users? In this case,
UPGRADE.md
should be updated. - Does it need to be backported according to the backporting policy? (this GH action will add "backport" label based on these file globs, if you want to prevent it from adding the "backport" label use no-backport-autolabel label)
app/kuma-dp/cmd/run.go
Outdated
time.Second*5, | ||
time.Minute, |
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 usually make this configurable or use:
rt.Config().General.ResilientComponentBaseBackoff.Duration,
rt.Config().General.ResilientComponentMaxBackoff.Duration,
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 useful to be configurable though?
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'd have to dig the history to see why this was first introduced - https://github.com/kumahq/kuma/pull/9892/files - looks like for resiliency e2e test 🤷 - maybe @Automaat can chip in. Basically I wasn't sure if we should follow how the others are set up or not.
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.
This is for the CP though right? Here we're talking about the DP
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.
Made a config for it
func (cf *ConfigFetcher) stepForHandler(h *handlerInfo) (bool, error) { | ||
ctx, cancel := context.WithTimeout(context.Background(), cf.perHandlerTimeout) | ||
defer cancel() | ||
r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost%s", h.handler.Path()), nil) |
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 localhost a valid unix domain socket address? Wasn't there a comment or something in the MeshMetric
code?
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.
Host is just the host:
header when doing http over unix domain socket because there's no TCP.
}) | ||
prometheus.MustRegister(handlerTickCount) | ||
handlerTickDuration := prometheus.NewSummary(prometheus.SummaryOpts{ | ||
Name: "kuma_dp_envoyconfigfetcher_handler_call_duration_seconds", |
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'm not sure it's always milliseconds
:
Help: "Time Blocked waiting for new connection in seconds", |
Expect(test_metrics.FindMetric(metrics, "api_server_http_request_duration_seconds")).ToNot(BeNil()) |
AAAARecords: make(map[string]*dnsEntry), | ||
}) | ||
var dnsEntry *dnsEntry | ||
if len(req.Question) > 0 { // Apparently most DNS doesn't support multiple questions so let's just support the first one |
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.
should we log if it's > 1?
continue | ||
} | ||
switch { | ||
case strings.Contains(ipStr, "."): |
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 guess this will work but isn't it safer to just call:
case ip.To4() != nil:
?
as per docs:
// To4 converts the IPv4 address ip to a 4-byte representation.
// If ip is not an IPv4 address, To4 returns nil.
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 doesn't matter, I found the govalidator
repo does the same way.
https://github.com/asaskevich/govalidator/blob/e11347878e2323a0777b40d33eeffd37a185e31b/validator.go#L818-L828
}) | ||
prometheus.MustRegister(upstreamRequestDuration) | ||
requestDuration := prometheus.NewSummary(prometheus.SummaryOpts{ | ||
Name: "kuma_dp_embeddeddns_proxy_request_duration_seconds", |
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.
why does it have proxy
in the name?
@@ -389,6 +389,8 @@ type BuiltinDNS struct { | |||
Port uint32 `json:"port,omitempty" envconfig:"kuma_runtime_kubernetes_injector_builtin_dns_port"` | |||
// Turn on query logging for DNS | |||
Logging bool `json:"logging,omitempty" envconfig:"kuma_runtime_kubernetes_injector_builtin_dns_logging"` | |||
// Use the embedded DNS instead (This is an experimental feature) | |||
UseEmbedded bool `json:"embedded,omitempty" envconfig:"kuma_runtime_kubernetes_injector_builtin_dns_embedded"` |
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.
isn't builtin and embedded the same thing?
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.
Nope builtin the container embedded in the binary. In any case these 2 are not meant to be present together for too long
"github.com/kumahq/kuma/pkg/dns/dpapi" | ||
) | ||
|
||
var log = core.Log.WithName("embeededdns") |
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.
nitpick:
var log = core.Log.WithName("embeededdns") | |
var log = core.Log.WithName("embeddeddns") |
upstreamHostPort string | ||
} | ||
|
||
func NewServer(address string, port uint32) *Server { |
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.
do we have a test for this? just starting the server, seeding it and having a client query it and seeing if the results match?
Signed-off-by: Charly Molter <[email protected]>
@lobkovilya pushed a new commit which simplifies the handler API as it doesn't deal with shutdowns anymore (that's what components are for) |
if err != nil { | ||
s.metrics.UpstreamRequestFailureCount.Inc() | ||
log.Error(err, "Failed to write message to upstream") | ||
response = &dns.Msg{} |
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 already empty when initial
response = &dns.Msg{} |
if ctx.Err() != nil { | ||
return ctx.Err() | ||
} |
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.
Please add some comments here to explain why we put the ctx check conditional here. (ctx operation overtime)
continue | ||
} | ||
switch { | ||
case strings.Contains(ipStr, "."): |
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 doesn't matter, I found the govalidator
repo does the same way.
https://github.com/asaskevich/govalidator/blob/e11347878e2323a0777b40d33eeffd37a185e31b/validator.go#L818-L828
Signed-off-by: Charly Molter <[email protected]>
Motivation
Add an embedded DNS server that resolves mesh local hostnames.
This feature is disabled by default and needs to be enabled with
kuma_dns_embedded_proxy_port
on DPs in universal or simply withkuma_runtime_kubernetes_injector_builtin_dns_embedded=true
on CP in Kubernetes (this leverages sidecar injection).Implementation information
We leveraged the same system as MeshMetric. The DNS map is exposed in the
_kuma:dynamicconfig
listener, we poll this from the DP at a set frequency and reload the data.We improved the whole configfetcher to make it reusable across different components. We also setup the endpoint so that it works with etag caching while avoids unnecessary reloading and processing, we can therefore refresh the configs more quickly as well.
For the moment I added a new matrix for e2e test. If this is stable I'll switch the default.