MiniK8s的DNS部署


DNS

Overview

主要功能

Kubernetes中的DNS主要有以下几个功能:

  1. 服务发现

Kubernetes中的DNS服务可以帮助应用程序发现其他服务。当应用程序需要访问其他服务时,可以使用服务名称作为DNS记录的别名,而不需要知道服务的IP地址和端口号。在Kubernetes集群中,每个Service对象都有一个DNS记录,可以通过服务名称访问到。

  1. 域名解析

Kubernetes中的DNS服务可以解析不同对象的域名,包括服务、Pod和ServiceAccount等。这使得在集群内部通信时,不需要使用硬编码的IP地址和端口号,而可以使用相应对象的DNS名称。

  1. 集群内部DNS解析

Kubernetes中的DNS服务可以帮助解析Kubernetes内部的DNS名称,例如节点名称、服务IP地址和端口等。

  1. 集群外部DNS解析

Kubernetes中的DNS服务还可以帮助解析集群外部的DNS名称,例如解析外部服务的DNS名称或者解析公共DNS记录。

  1. 可扩展性

Kubernetes中的DNS服务使用了可扩展性的设计,可以支持多种不同的DNS插件,如CoreDNS、KubeDNS等,而且可以自定义域名后缀,以适应不同的网络拓扑结构和部署场景。

DNS 记录

以下对象会获得 DNS 记录:

  • Services
  • Pods

记录格式

  • Pod

    • A/AAAA 记录
      一般而言,Pod 会对应如下 DNS 名字解析:

      【pod-ip-address】.【命名空间】.pod.cluster.local  

      例如,对于一个位于 default 名字空间,IP 地址为 172.17.0.3 的 Pod, 如果集群的域名为 cluster.local,则 Pod 会对应 DNS 名称:

      172-17-0-3.default.pod.cluster.local

      通过 Service 暴露出来的所有 Pod 都会有如下 DNS 解析名称可用:

      pod-ip-address.service-name.my-namespace.svc.cluster-domain.example

    • Pod 的 hostname 和 subdomain 字段
      当前,创建 Pod 时其主机名(从 Pod 内部观察)取自 Pod 的 metadata.name 值。
      Pod 规约中包含一个可选的 hostname 字段,可以用来指定一个不同的主机名。 当这个字段被设置时,它将优先于 Pod 的名字成为该 Pod 的主机名(同样是从 Pod 内部观察)。 举个例子,给定一个 spec.hostname 设置为 “my-host” 的 Pod, 该 Pod 的主机名将被设置为 “my-host”。
      Pod 规约还有一个可选的 subdomain 字段,可以用来表明该 Pod 是名字空间的子组的一部分。 举个例子,某 Pod 的 spec.hostname 设置为 “foo”,spec.subdomain 设置为 “bar”, 在名字空间 “my-namespace” 中,主机名称被设置成 “foo” 并且对应的完全限定域名(FQDN)为 “foo.bar.my-namespace.svc.cluster-domain.example”(还是从 Pod 内部观察)。
      如果 Pod 所在的名字空间中存在一个无头服务,其名称与子域相同, 则集群的 DNS 服务器还会为 Pod 的完全限定主机名返回 A 和/或 AAAA 记录。

  • Service

    • kubernetes会为Service创建域名,其域名格式为

      【servic名称】.【命名空间】.svc.cluster.local  

      cluster.local是k8s默认的集群域

      普通Service的DNS记录是Service本身的IP
      无头Service(Headless Service)的DNS记录则是其选择的 Pod IP 的集合,(无头Service的名称与Pod中配置subdomain一致

DNS总体架构

pic1

当pod1应用想通过dns域名的方式访问pod2则首先根据容器中/etc/resolv.conf内容配置的namserver地址,向dns服务器发出请求,由service将请求抛出转发给kube-dns service,由它进行调度后端的core-dns进行域名解析。解析后请求给kubernetes service进行调度后端etcd数据库返回数据,pod1得到数据后由core-dns转发目的pod2地址解析,最终pod1请求得到pod2。

DNS注册

在 Kubernetes 中,每个 Pod 都有一个 DNS 名称,称为 Pod DNS 名称。Pod DNS 名称由以下部分组成:

pod-ip-address.my-namespace.pod.cluster.local

其中,pod-ip-address 是 Pod 的 IP 地址,my-namespace 是 Pod 所在的命名空间,pod 是 Pod 的名称。

当 Pod 启动时,它会在集群 DNS 中注册自己的 DNS 记录。它会向 Kubernetes 内置的 DNS 服务器查询该 Service 的 DNS 域名,这个 DNS 服务器实际上就是 kubelet 启动的 coredns 容器。

除了 Pod DNS 名称,Kubernetes 还使用了一些其他的 DNS 名称。例如,Kubernetes Service 对应的 DNS 名称有:

my-service.my-namespace.svc.cluster.local

其中,my-service 是 Service 的名称,my-namespace 是 Service 所在的命名空间。

当 Service 创建时,它会向 kube-dns 服务注册自己的 DNS 记录。kube-dns 服务会自动将该记录与其他 DNS 记录结合起来,提供一个完整的服务发现机制。

总的来说,在 Kubernetes 中,DNS 注册的过程是自动完成的。当 Pod 或 Service 创建时,它们会向集群 DNS 注册自己的 DNS 记录。kube-dns 服务负责将这些记录与其他服务和 DNS 记录结合起来,提供一个完整的服务发现机制。这使得 Kubernetes 用户可以轻松地在集群中发现和连接其他容器和服务。

DNS注册的过程

一共支持两种注册方式:

  1. 通过config文件注册
    kubectl接收到client请求以后,发送http请求给apiserver, APIserver将内容转发到coreDNS
  2. 通过service、Endpoint、pod的etcd中的信息进行动态更新
    定期向apiserver发送请求,获取service、Endpoint、pod的etcd中的信息,然后更新到coreDNS中

coreDNS

core DNS是每个node上运行一个吗?还是控制面上?

CoreDNS是一个Kubernetes集群中的Kubernetes插件,它通常在Kubernetes控制平面上作为一个Deployment或DaemonSet运行。在每个节点上都有一个CoreDNS Pod实例,它监听Kubernetes API服务器的变化并自动更新DNS记录。因此,可以说CoreDNS是在控制平面上运行的。

coreDNS对于DNS记录的存储

CoreDNS内部包含一个DNS记录存储后端。该后端被称为CoreDNS的存储插件。CoreDNS存储插件的主要作用是管理DNS记录的存储和检索,例如将DNS记录存储在etcd、Consul或文件系统中,并在必要时从这些后端中检索记录。当客户端查询DNS记录时,CoreDNS将首先从存储插件中检索记录,然后将记录返回给客户端。如果记录不存在,则返回一个相应的错误。此外,存储插件还支持动态DNS更新,允许客户端通过API向CoreDNS添加、删除和修改DNS记录。

不同子路径对应不同的service

使用nginx做反向代理,将不同的path路由到不同的ip+port

具体实现思路

DNS配置

DNS的配置是通过yaml文件进行的,在具体操作的时候将其映射为一个apiobject (DNSRecord),支持的字段及其含义和要求文档中的基本一致,以下是一个例子

{
  "kind": "DNS",
  "apiVersion": "v1",
  "name": "dns-test1",
  "host": "node1.com",
  "paths": [
      {
          "address": "10.1.1.10",
          "service": "service1",
          "port": 8010
      },
      {
          "address": "10.1.1.11",
          "service": "service2",
          "port": 8011
      }
  ]
}

当相应的yaml文件被解析后,会生成一个DNSRecord对象,该对象的host和nginx server ip的对应会被存储到etcd中,从而实现了域名到ip的映射,并通过coreDNS实现了域名解析的动态加载

而nginx server ip下不同path到具体的service的映射则是通过nginx的配置文件实现的,具体的配置文件示例如下

worker_processes  5;  ## Default: 1
error_log  ./error.log debug;
pid        ./nginx.pid;
worker_rlimit_nofile 8192;

events {
  worker_connections  4096;  ## Default: 1024
}
http {
    
    server {
        listen 192.168.1.12:80;
        server_name node1.com;

        
        location /service1/ {
            access_log /var/log/nginx/access.log;
            proxy_pass http://127.1.1.10:8010/;
        }
        
        location /service2/ {
            access_log /var/log/nginx/access.log;
            proxy_pass http://127.1.1.11:8011/;
        }
        
    }
    
}

当DNSRecord被更新后,nginx的配置文件也会手动更新并重新加载,从而实现了不同path到不同service的映射

根据域名定位到IP和port的过程

  1. 通过域名找到nginx server ip

以上面的nginx配置文件为例,我们要访问http://node1.com:80/service1,首先会通过coreDNSnode1.com解析为nginx server ip 192.168.1.12,这个过程是通过coreDNS实现的

  1. 根据不同的path找到service

在nginx中根据location的名字找到对应的service ip和端口,比如在上面的例子中是127.1.1.10:8010,这个过程是通过nginx实现的

  1. 测试
  • etcd中插入相应的域名数据:

    etcdctl put /dns/com/node1 '{"host":"192.168.1.12"}'
  • 启动coreDNSnginx(见下面的安装运行)

  • nginx的配置如上所示

  • 启动http server

    mkdir /home/test
    
    nano test.html
    <!DOCTYPE html>
    <html>
    <head>
        <title>Test Page</title>
    </head>
    <body>
        <h1>Hello, world!</h1>
    </body>
    </html>
    
    python3 -m http.server --bind 127.1.1.10 8010
  • 新开terminal,使用curl测试

    curl -v http://node1.com:80/service1/test.html
  • 测试结果

    *   Trying 0.0.0.0:80...
    * TCP_NODELAY set
    * Connected to dns-test1 (127.0.0.1) port 80 (#0)
    > GET /service1/test.html HTTP/1.1
    > Host: dns-test1
    > User-Agent: curl/7.68.0
    > Accept: */*
    > 
    * Mark bundle as not supporting multiuse
    < HTTP/1.1 200 OK
    < Server: nginx/1.18.0 (Ubuntu)
    < Date: Sun, 14 May 2023 08:37:24 GMT
    < Content-Type: text/html
    < Content-Length: 117
    < Connection: keep-alive
    < Last-Modified: Sun, 14 May 2023 07:13:40 GMT
    < 
    <!DOCTYPE html>
    <html>
    <head>
        <title>Test Page</title>
    </head>
    <body>
        <h1>Hello, world!</h1>
    </body>
    </html>

环境准备

安装运行coreDNS

  • 安装版本:1.10.1 (linux amd64)
  • 安装命令

wget https://github.com/coredns/coredns/releases/download/v1.10.1/coredns_1.10.1_linux_amd64.tgz

tar -zxvf coredns_1.10.1_linux_amd64.tgz 
sudo cp coredns /usr/local/bin
  • 查看版本
coredns --version
  • 运行
    在home目录下

    首先创建Corefile

    nano Corefile
    
    .:53 {
        etcd {
            endpoint http://localhost:2380
            path /dns
            upstream /etc/resolv.conf
            fallthrough
        }
        forward . 114.114.114.114
        reload 6s
        errors
        loop
        prometheus  # 监控插件
        loadbalance
    }

    然后运行

./coredns -conf /home/Corefile

大概率会报如下错

root@node-2:/home# ./coredns -conf Corefile
Listen: listen tcp :53: bind: address already in use
root@node-2:/home# lsof -i :53
COMMAND      PID            USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
systemd-r 102009 systemd-resolve   12u  IPv4 933337      0t0  UDP localhost:domain 
systemd-r 102009 systemd-resolve   13u  IPv4 933338      0t0  TCP localhost:domain (LISTEN)

这是因为系统自带的DNS解析服务也在监听53端口。所以会报端口以及被占用的错误,所以把系统自带的服务关掉就行

sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved

再次执行
./coredns -conf /home/Corefile
  • 测试

    • 插入一条信息
    etcdctl put /dns/com/example/sub '{"host":"1.2.3.4"}'
    • 测试效果
      在本机上测试
    dig @localhost +short sub.example.com

    在其他机器上访问,首先要把/etc/resolv.conf里的nameserver换成装有coredns的服务器的IP,这里是192.168.1.12,然后执行下面指令

    dig @192.168.1.12 +short sub.example.com

将coreDNS部署成服务

省的每次都要自己手动在命令行启动

nano /usr/lib/systemd/system/coredns.service
[Unit]
Description=CoreDNS DNS server
Documentation=https://coredns.io
After=network.target
 
[Service]
LimitNOFILE=65536
LimitNPROC=512
ExecStart=/home/coredns -conf=/home/Corefile
Restart=on-failure
 
[Install]
WantedBy=multi-user.target


systemctl daemon-reload
systemctl enable coredns
systemctl start coredns && systemctl status coredns

安装运行nginx

  • 安装
    sudo apt update
    sudo apt install nginx
  • 启动
    sudo systemctl status nginx
    sudo systemctl start nginx//会使用系统自带的配置文件,建议不用,使用下面指定conf文件来运行
    指定conf文件,nginx.conf内容见前面
    nginx -c /home/nginx.conf
  • 停止
    pkill nginx
  • 查看log
    tail -f /var/log/nginx/error.log
    tail -f /var/log/nginx/access.log
  • 设置日志级别
    error_log  /var/log/nginx/error.log  debug;

为了使用etcd热更新:安装confd

最后采用的方案是go的text/template, confd一直报错:no template

  • 安装
    $ wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64
    Move the binary to an installation path, make it executable, and add to path
    mkdir -p /opt/confd/bin
    mv confd-0.16.0-linux-amd64 /opt/confd/bin/confd
    chmod +x /opt/confd/bin/confd
    export PATH="$PATH:/opt/confd/bin"
  • confd的配置
    • 创建confdir
      mkdir -p /etc/confd/{conf.d,templates}
      ll /etc/confd/

运行测试

dns部分和apiserver绑定在一起,由apiserver进行调度,不需要启动额外的应用程序
发送postman中的示例post dnsrecord请求以后,并在相应的机器3的ip和端口启动http server模拟service,然后在机器1上的容器内使用curl测试,结果如下
test1

补充

常用的nerdctl命令

当然可以,请参考以下表格:

命令 参数 描述
run -d: 在后台运行容器
-it: 分配一个交互式终端
-e key=value: 设置环境变量
-p hostPort:containerPort: 暴露端口
-v hostDir:containerDir: 挂载主机目录到容器目录
--entrypoint /bin/bash: 覆盖容器启动命令为 /bin/bash
运行一个容器
exec -it: 分配一个交互式终端
-e key=value: 设置环境变量
-w dir: 指定工作目录
--user: 指定用户身份
--privileged: 授予容器 root 权限
--net host: 使用主机网络


比如:nerdctl exec -it <container\_id> sh
在一个运行中的容器内部执行命令
attach 连接到一个运行中的容器
ps -a: 显示所有容器
-q: 只显示容器 ID
列出所有容器
logs -f: 实时跟踪日志
--tail N: 显示最后 N 条日志
查看容器日志
inspect 显示容器详细信息
rm -f: 强制删除容器
-v: 删除容器相关的数据卷
删除容器
pause 暂停容器
unpause 恢复容器
stop -t seconds: 等待容器停止的时间 停止容器
kill 强制停止容器
restart 重启容器
top 查看容器内部进程信息
stats 查看容器资源使用情况

注意:该表格仅列举了 nerdctl 常用命令和部分参数,更多详细信息可以通过 nerdctl --help 查看。

参考资料

(107条消息) etcd3+coredns设置域名解析_suiyingday的博客-CSDN博客


文章作者: Swung
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Swung !
  目录