Core DNS: The Kubernetes☸️ DNS Server

Core DNS: The Kubernetes☸️ DNS Server

Core DNS is a CNCF graduated project which serves as the default Kubernetes DNS server which is completely Open Source and licensed under Apache License version 2.0. It is a flexible DNS Server, written in Go and started in 2016 as a fork initiated by Miek Gieben, who played a pivotal role in its development of Caddy HTTP Server, due to his high contribution in Caddy server for transferring HHTP Server to DNS Server it started being called as the Caddy DNS for longtime engagement with a lot of contributors giving rise to the present architecture.

Plugins Architecture

Core DNS gradually evolved to one of the best DNS Server in the world and become default Kubernetes DNS Server. It is different from other DNS Server as it focuses on service discovery.

Core DNS has a plugin based architecture which allows Core DNS to easily get extended, thus if you want to work on with some feature which isn't present as a feature already with Core DNS, then as long as we know Golang we can still contribute to develop the plug-in with the feature we want to be included for our application with our Core DNS Server.

Soon we will be discussing more about the plugins architecture int he deep dive below, reach out if you came here to know about it specifically.

Protocols used within the Core DNS

Core DNS support serving different protocols via DNS, DNS over TLS, DNS over HTTP/2, DNS over gRPC, where gRPC does not serve as a DNS standard but is more like a customer implementation.


gRPC :

gRPC or Remote Procedure Call, is a high-performance, open-source framework developed by Google. It facilitates communication between distributed systems by allowing services to invoke methods on each other as if they were local, abstracting the complexity of data serialization and transmission. Built on HTTP/2 for efficiency, gRPC supports multiple programming languages and provides features such as bidirectional streaming and strong typing, making it an excellent choice for building fast, scalable, and interoperable microservices and distributed applications.


It also support forwarding to upstream via:

  • DNS

  • DNS over TLS

  • DNS over gRPC

So if you like to choose core DNS to serve your DNS traffic which is UDP and want to use Core DNS to upstream DNS server, then we can use transportation through: TCP or gRPC which are more reliable and from that standpoint, we can sync Core DNS as a frontend not only to get flexibility locally but also to use other communication channels to guarantee it's reliability from data syn point of view instead of going through DNS or UDP every time.

Collaborations with different platforms

Core DNS also collaborates with various different cloud vendors like Amazon Route53, Google Cloud and Azure DNS.

Amazon Route53

When you launch an Amazon EKS cluster with at least one node, two replicas of the CoreDNS image are deployed by default, regardless of the number of nodes deployed in your cluster. The CoreDNS Pods provide name resolution for all Pods in the cluster. The CoreDNS Pods can be deployed to Fargate nodes if your cluster includes an AWS Fargate profile with a namespace that matches the namespace for the CoreDNS deployment. For more information about CoreDNS, see Using CoreDNS for Service Discovery in the Kubernetes documentation. For more information you can read their documentation at https://docs.aws.amazon.com/eks/latest/userguide/managing-coredns.html

Google Kubernetes Engine

Google Kubernetes Engine defaults to kube-dns for DNS resolution. kube-dns cannot be downscaled to 0 and completely replaced. If you want to leverage CoreDNS features that kube-dns does not handle, such as rewriting request names. If you want to use the caching features that CoreDNS has, you can enable NodeLocal DNSCache which is available on Google Kubernetes Engine v1.15 or later.

In order to add CoreDNS resolution functionality to your Google Kubernetes Engine cluster, you will have to deploy a core-dns pod, expose it via a service, and configure a stub domain for kube-dns pointing it to the core-dns service IP. This way, all traffic matching the stub domain suffix will get routed to the core-dns pod. The non-matching stub domain traffic will be resolved by kube-dns. For more information you can read their documentation at https://cloud.google.com/knowledge/kb/how-to-run-coredns-on-kubernetes-engine-000004698

Azure DNS

To use CoreDNS with Azure Virtual Machines in a Kubernetes cluster, you firstly need to deploy Kubernetes in your Azure VM instances. Next, you will configure CoreDNS as the DNS service for your cluster.

In this process, you will need to use azure-native.connectedvmwarevsphere.VirtualMachineInstance to create Azure Virtual Machines and kubernetes-coredns.CoreDNS to configure CoreDNS in your Kubernetes cluster.

Core DNS is also fully embedded Cloud Native ecosystem with integration of Prometheous, Open Tracing and OPA. Since Core DNS is default DNS for Kubernetes, so whenever we will be using Kubernetes, we will observe a power up and running with the name of core DNS. For more information you can their documentation at https://www.pulumi.com/ai/answers/tp4J2fWxswLYpKwU8QopFo/integrating-kubernetes-coredns-with-azure-vms

Updates

With version CoreDNS-1.11.1, fixes a major performance regression introduced in 1.11.0 that affected DoT (TLS) forwarded connections. It also adds a new option to dnstap to add metadata to the dnstap extra field, and fixes a configuration parsing bug in cache.

There have been various plugins been added as default at Core DNS which were addressed at the last year's CNCF KubeCon NA 2022 like geoip & header plugin.

geoip: This plugin is used to report where query comes from, this plugin has been included as in default plugin of Core DNS.

header: This plugin is used to fiddle with header bits, means we can modify or adjust these bit according to our needs which might be useful for various purposes such as customization or optimization of DNS queries.

There are a lot more interesting facts and working which were brought by the developers at Core DNS at KubeCon North America 2022, so don't forget to check there channel:

Security Audit🛡️

In the past, it was possible to pass secret and write down in core file and save it locally which recently got revealed by audience in one secret which obviously proved that it may not have been a good practice form the Security standpoint.

Core DNS removed plain text secret saved in the core file, thus if you have passed secrets in recent version then you will have to get some other way or get it done by variables which is much safer from security standpoint.

Thus, it is highly recommended to keep the updated version of the Core DNS to keep track of the security measures and updates. For example the earlier version of golang: 1.17.6 got a lot of security vulnerabilities in the past.

So the bottom line is, to get regular updates and version updates at regular interval to stay away from the security vulnerabilities.

Participation in Community Events

Core DNS have been participating continuously in various Open Source communties and events.

  • Have been participating in Google Summer of Code (GSOC) previously from more than 7 years.

  • Have been a part of The Linux Foundation Program for handling out projects to Open Source Contributors.

Deep Dive

Core DNS is different form the most of the previous DNS Server is the plugin architecture, the major idea behind it is, Core DNS use a request processing pipeline, so a DNS server comes in an unpacked server to hand over to this pipeline and then you are setting your core file i.e. configuration file for Core DNS, then we are just enabling different plugins within that pipeline and configuring them to tweak that request.

Extending Plug-ins

Enabling an Extensive Plug-in

So there are the additional plug-ins in Kubernetes provided by the providers or they pulled it form the Docker Hub. Although, if you want to, let's say back your Core DNS in layer 2 Redis cache then Core DNS has an external plug-in for that, while doesn't even require your Golang knowledge to use them due to their easy access.

Plug-ins aren't loaded dynamically, they are built at compile time and plug-ins ordering is fixed at compile time so that processing of that requirement through pipeline, you can't change order of that without rebuilding Core DNS.

So, in short:


Rebuilding with External Plugins:

  • "External":

    • No building into standard binaries and Docker images.

    • Not supported by core team.

  • No dynamic loading of plug-ins

    • Plug-ins are built in at completions.

    • Controlled by plugin.cfg


Plugin architecture lends itself to extensibility. So there are 3 basic ways to extend it:

  1. External Plug-ins

Prerequisites: Docker and Shell

Steps to add External plug-ins:

  1. Clone Core DNS

  2. Modify plugin.cfg

  3. Build Core DNS

To compile CoreDNS, we assume you have a working Go setup. See various tutorials if you don’t have that already configured.

First, make sure your golang version is 1.20 or higher as go mod support and other api is needed. Then, check out the project and run make to compile the binary:

$ git clone https://github.com/coredns/coredns
$ cd coredns
$ make

This should yield a coredns binary. For building Core DNS, the docker image we already had and build it to the Core DNS binary.

  1. Core DNS as Library

Here we will be running the Core DNS itself and embedding the Core DNS in another Binary. We can use this to strip out plugins which we don't really care.

This feature is used by the feature: Node local DNS, which is a really important feature as it runs with Core DNS just for Catching on every node and redirects all the DNS requests from that node to the local catch and then for requirement that needs to go to the central cluster DNS. It upgrades the connection to TCP which fixes some Kernal logs, issues and race conditions.

So, Local DNS can use to solve out the weird Kubernetes DNS issues because there are Race Conditions, Kernal Bugs and all sorts of subtle things that can happen underload.

  1. Writing a Plugin

Just as in the Unix pipeline, the major forms is that each of these pipelines remain composable and can later be used with other plug-ins and thus the efforts are made to scope them into some small piece of functionality so that we can pull them into some external plug-in at different Core DNS instances.

Backend plug-in

Backend plug-in is the source of the data and information. Kubernetes plug-ins is a backend plug-in which is pulling data from Kube API Server and publishing it as DNS, Amazon Route53 which read from cloud providers, APIs and then represent data as DNS. Postgress is also another example of external backend plugin for storing data. All of the remaining plugins in the Backend plug-ins are:

  • file: Reads DNS data from files.

  • forward: Forwards DNS queries to another DNS server.

  • hosts: Reads DNS data from the local hosts file.

  • clouddns: Integrates with cloud DNS providers.

  • templates: Generates responses based on customizable templates.

Mutators

Mutators are those which muck with the information by requirements, deny and change it. Let's say we have a rewrite plugin like some queries and name which we don't want to reach out to that person due to any confidentiality, security or any other reason, then we are going to rewrite the query and send it to upstream and reply with that IP address.

AspectAuthoritative DNS ServerRecursive DNS Server
PurposeProvides authoritative answers for a specific domain by hosting the DNS records for that domain.Responds to client queries by fetching and providing the requested DNS information on behalf of the client.
ResponsibilityHolds the official DNS records for a domain, such as IP addresses associated with domain names.Acts as an intermediary between clients and authoritative DNS servers, fetching DNS information as needed.
Query HandlingAnswers queries with authoritative information for the domains it manages.Resolves queries by recursively querying other DNS servers until it obtains the authoritative answer.
Zone KnowledgeHas complete knowledge of the DNS zone(s) it is authoritative for.May not have complete knowledge of all domains but can query authoritative servers to obtain needed information.
Requests SourceTypically responds to DNS queries from other DNS servers or clients.Often receives DNS queries from end-user devices or local network devices seeking domain resolution.
CachingMay cache records to improve response times but primarily focuses on serving authoritative information.Heavily relies on caching to store previously resolved DNS records, optimizing response times for frequently accessed domains.

Limitation to Core DNS probably because Recursive DNS Servers are hard to write. While the remaining plugins are:

  • acl: Controls access to CoreDNS based on Access Control Lists.

  • cache: Implements caching of DNS responses for improved performance.

  • rewrite: Modifies DNS queries or responses based on defined rules.

  • nsid: Modifies DNS queries or responses to include the NSID (Name Server Identifier).

Configurations

There are various pieces that can be configured in CoreDNS. The first is determining which plugins you want to compile into CoreDNS. The binaries we provide have all plugins, as listed in plugin.cfg, compiled in. Adding or removing is easy, but requires a recompile of CoreDNS.

Thus most users use the Corefile to configure CoreDNS. When CoreDNS starts, and the -conf flag is not given, it will look for a file named Corefile in the current directory. That file consists of one or more Server Blocks. Each Server Block lists one or more Plugins. Those Plugins may be further configured with Directives.

The ordering of the Plugins in the Corefile does not determine the order of the plugin chain. The order in which the plugins are executed is determined by the ordering in plugin.cfg.

It includes various plugins:

  • Bind: Configures CoreDNS to bind to a specific address and port.

  • log: Enables logging for CoreDNS.

  • health: Provides a health endpoint for monitoring the health of CoreDNS.

  • ready: Implements a readiness endpoint to indicate if CoreDNS is ready to serve requests.


In short we can bring all the major points in the following compact form as:

Writing a program:

  • Three category of plugins

  • Best practices: Stick to one of these in your plugins

  • Backends

    • Source of data

    • file, forward, hosts, clouddns, templates, kubernetes.

  • Mutators

    • Modify the inbound requirement, the outbound requirement or both

    • acl, cache, rewrite, nsid

  • Configurations

    • Modify internal state of functioning of Core DNS.

    • bind, log, health, ready


Four Function

  • Name: Literally just returns the name of plugin.

  • Server DNS: This is responsible for handling DNS requests. It receives incoming DNS queries and processes them according to the specified configuration, including interactions with other plugins. It acts as the central component for managing the DNS server functionality.

  • init: Register your plugin with Caddy.

  • Setup: This is useful in parsing your configuration is involved in the initialization and setup phases of CoreDNS. It performs essential setup tasks before the server starts handling DNS requests.

Functions in Code with Go

Let's discuss the functions in Core DNS with the code written in Go:

Name Function:

func(o*onlyone)Name()string{return"onlyone"}

Here this will just return the name of the plugin.

Server Function:

func (s Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
    state := request.Request{W: w, Req: r}

    // Check if the query should be handled by this plugin based on configured zones
    if plugin.Name(r.Question.Name).Matches(s.Zones) {
        // Process the DNS query as needed
        // This is where the specific logic for handling DNS requests would go

        // Call the next plugin in the chain
        return plugin.NextOrFailure(s.Name(), s.Next, ctx, w, r)
    }

    // If the query is not for the configured zones, proceed to the next plugin
    return plugin.NextOrFailure(s.Name(), s.Next, ctx, w, r)
}

Here it will check if the query is handled by the plugin based on configuration zones in the if plugin.Name(r.Question.Name).Matches(s.Zones) line then will process the DNS query as needed and then will call the next plugin in the chain in the return function return plugin.NextOrFailure(s.Name(), s.Next, ctx, w, r) then will proceed to the next plugin if the query isn't founded in the line return plugin.NextOrFailure(s.Name(), s.Next, ctx, w, r)

Init Function:

func init(){
    caddy.RegisterPlugin("onlyone", caddy.Plugin{
        ServerType: "dns",
        Action: setup,
    })
}

This function will register the plugin with the parsing routine so that we know what we are referring to, when using the directive while parsing.

Setup Function:

func steup(c * caddy.Controller)error{
    t, err := parse(c)
    if err != nil{
        return plugin.Error("onlyone", err)
    }

    dnsserver.GetConfig(c).AddPlugin(func(nextplugin.Handler)plugin.Handler{
    t.Next = next
    return t
    })

    return nil
}

This function will add plugin and will inserts it in the chain, so in that plugin in cfg, when we read the core file we aren't looking at the directions coming in order to initialize all of these chain but we are running through a fix order.


Hope you would have gained some knowledge through this blog, soon i will be dropping some more blogs on some interesting, impactful and newer projects as well of CNCF, as we still got a lot to cover🚀😄.

If you like my Article then please react to it and connect with me on Twitter if you are also a tech enthusiast. I would love to collaborate with people and share the experience of tech😄😄.

My Twitter Profile:

Aryan_2407

Did you find this article valuable?

Support Aryan Parashar by becoming a sponsor. Any amount is appreciated!