Skip to content
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

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

lahabana
Copy link
Contributor

@lahabana lahabana commented Mar 17, 2025

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 with kuma_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.

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]>
@lahabana lahabana added the ci/run-full-matrix PR: Runs all possible e2e test combination (expensive use carefully) label Mar 17, 2025
@lahabana
Copy link
Contributor Author

I've made a few separate commits as this is a bit of a mouthful

Copy link
Contributor

Reviewer Checklist

🔍 Each of these sections need to be checked by the reviewer of the PR 🔍:
If something doesn't apply please check the box and add a justification if the reason is non obvious.

  • 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
  • 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)

Signed-off-by: Charly Molter <[email protected]>
@lahabana lahabana changed the title feat(dns): running embedded DNS server instead of coreDNS feat(dns): embedded DNS server instead of coreDNS Mar 18, 2025
@lahabana lahabana marked this pull request as ready for review March 18, 2025 12:49
@lahabana lahabana requested a review from a team as a code owner March 18, 2025 12:49
@lahabana lahabana requested review from slonka and lobkovilya March 18, 2025 12:49
@lahabana lahabana self-assigned this Mar 18, 2025
})
prometheus.MustRegister(handlerTickCount)
handlerTickDuration := prometheus.NewSummary(prometheus.SummaryOpts{
Name: "kuma_dp_envoyconfigfetcher_handler_call_duration_seconds",
Copy link
Contributor

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

Copy link
Contributor

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())

Copy link
Contributor Author

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)

Copy link
Contributor

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

Copy link
Contributor Author

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:

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

@bartsmykla bartsmykla self-requested a review March 19, 2025 07:04
response := &dns.Msg{}
// In case it was never loaded
s.dnsMap.CompareAndSwap(nil, &dnsMap{
ARecords: make(map[string]*dnsEntry),
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor

@slonka slonka left a 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
  • 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)

Comment on lines 262 to 263
time.Second*5,
time.Minute,
Copy link
Contributor

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,

Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Contributor Author

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)
Copy link
Contributor

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?

Copy link
Contributor Author

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",
Copy link
Contributor

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
Copy link
Contributor

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, "."):
Copy link
Contributor

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

})
prometheus.MustRegister(upstreamRequestDuration)
requestDuration := prometheus.NewSummary(prometheus.SummaryOpts{
Name: "kuma_dp_embeddeddns_proxy_request_duration_seconds",
Copy link
Contributor

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"`
Copy link
Contributor

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?

Copy link
Contributor Author

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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick:

Suggested change
var log = core.Log.WithName("embeededdns")
var log = core.Log.WithName("embeddeddns")

upstreamHostPort string
}

func NewServer(address string, port uint32) *Server {
Copy link
Contributor

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?

@lahabana
Copy link
Contributor Author

@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{}
Copy link
Contributor

@Icarus9913 Icarus9913 Mar 20, 2025

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

Suggested change
response = &dns.Msg{}

Comment on lines +158 to +160
if ctx.Err() != nil {
return ctx.Err()
}
Copy link
Contributor

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, "."):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed-off-by: Charly Molter <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ci/run-full-matrix PR: Runs all possible e2e test combination (expensive use carefully)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants