自建 DNS Server

Gary Ng
20 min readJul 21, 2024

--

以下範例將使用 Ubuntu 18.04 建置 CoreDns 以及一台 Ubuntu 18.04 做測試。

CoreDns 簡單介紹:

CoreDns 是一個由 Golang 所開發的一套 Dns, 相當的靈活可以自行添加套件,而當 CoreDns 安裝完成後預設會先裝上 30個套件。

在要安裝 CoreDns 前因為他本身是由 golang 所開發的,所以環境需要先安裝 golang。

  1. 先安裝 git, 因為會從 github pull dns server repo 下來
sudo apt-get update -y
sudo apt-get install git -y

2. 安裝 make 指令

sudo apt-get -y install make

3. 安裝 go 套件

先下載 go.tar.gz 壓縮檔下來

wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz

解壓縮 go.tar.gz

tar xvf go1.22.5.linux-amd64.tar.gz

將解壓縮出來的檔案移動至 /usr/local

sudo mv go /usr/local

在 ~/.bashrc 設定環境變數

export GOPATH=/home/ubuntu/go
export GOROOT=/usr/local/go
export GOBIN=$GOPATH/bin
export PATH=$GOPATH/bin:$GOROOT/bin:$PATH

設定完成後重新加載 ~/.bashrc

source ~/.bashrc

因為 GOPATH 設定的位置是 /home/ubuntu/go 所以要在 /home/ubuntu 建立 go 資料夾

cd ~ && mkdir go && mkdir ~/go/src ~/go/pkg ~/go/bin

參考資源:

4. 將 coredns pull 下來且進行編譯

git clone https://github.com/coredns/coredns

將 coredns 移到 GOPATH 下

sudo mv coredns ~/go

編譯 coredns

cd ~/go/coredns && make

參考資源:

設定 coredns 設定檔, coredns 設定檔的預設名稱為 Corefile。

編輯 Corefile

.:1053 {
whoami
log # log
}

執行 coredns 監聽的 port 改為 1053

./coredns -dns.port 1053

coredns 起來後再使用 dig 看能否查出 dns 資訊

dig @127.0.0.1 -p 1053

coredns 套件文件:

log Format 調整

log 的部分也可以自定義輸出格式, 修改 Corefile

將上方的 log 改為以下部分

log . "Protocol: {proto} Name: {name}, Remote: {remote}, Type: {type}, RequestSize: {size}, ResponseDuration: {duration}, ResponseCode: {rcode}, ResponseSize: {rsize}"

參數說明:

proto: 協定
name: domain name
remote: client ip
type: 請求種類, A Record 等
size: 請求的大小
duration: 回應的時間
rcode: 回應的代碼資訊
rsize: 回應的資料大小

設定完之後重新啟動並且執行 dig 即可以看到以下資訊

Reload Plugin

reload plugin 會每隔一段時間自動重新加載 Corefile。 我們在全域的 zone 增加 reload 設定

Corefile

. {
reload 10s
}

重新啟動 coredns, 會發現多輸出了 SHA512, reload 就是去檢查 SHA 是否有改變,有改變的話重新加載 Corefile

可以透過修改 log format 進行測試,觀察 Corefile 是否會重新加載。

Prometheus Plugin

可以將 CoreDns 的一些資訊 export 給 Prometheus。

在 Corefile 全域增加 prometheus 設定

Corefile

. {
prometheus
}

倘若有設定 reload 的話可以等待 reload 新的設定, 而 Prometheus 取得 CoreDns 資訊的路徑為 /metrics, 可以透過以下指令檢查是否可以取得 metric。

curl -X "GET" http://localhost:9153/metrics

Cache Plugin

設定 DNS 快取的時間, cache 的時間單位為秒。

在 Corefile 增加 cache 設定

. {
// 之前的設定

# 自行定義 cache 的時間長度
cache 10s
}

Etcd

可以將 DNS 的對應寫在 etcd 裡面。

注意! etcd 的對應是反過來的也就是,倘若網址為 test.helloworld, 那 push 到 etcd 的路徑要為 helloworld/test。

範例:

<Zone> {
etcd {
path /skydns # 可以自己設定
endpoint http://<public ip or private ip>:2379
fallthrough
}
}

以範例來講 test.helloworld 就會被設定在 etcd 的 /skydns/helloworld/test 路徑下。

設定 etcd 的指令如下

etcdctl put /skydns/helloworld/test '{"host":"<public ip>","ttl":60}'

Etcd 基本介紹可以參考這篇

DNS Server 設定

這邊因為此範例運行在 ec2 上,而 ec2 的 53 port 已被佔用了,所以使用以下指令進行關閉。

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

關閉完資後透過以下指令檢查是否已關閉

systemd-resolve --status

倘若沒有輸出資料代表已關閉。

且記得在 /etc/hosts 將 private ip 加入進去, 不然 sudo 會出現錯誤訊息。

127.0.0.1 ip-xxx-xx-x-xx

緊接著因為原先的 ec2 dns 被關閉了,所以在進行 apt-get 操作的時候域名解析失敗,所以要在 Corefile 增加 forward, 透過其他的 DNS Server 轉方出去。

Corefile

. {
forward . 8.8.8.8 1.1.1.1
reload 10s
prometheus
log
errors
}

然後 Corefile 可以再增加自己的 domain 資訊

{domain} {
file /home/ubuntu/go/coredns/config/{domain}
log
whoami
}

此處的 /home/ubuntu/go/coredns/config/{domain} 是設定 domain 的資訊

@       3600 IN SOA {nameserver1} {nameserver2} (
2017042745 ; serial
7200 ; refresh (2 hours)
3600 ; retry (1 hour)
1209600 ; expire (2 weeks)
3600 ; minimum (1 hour)
)

3600 IN NS {nameserver1}
3600 IN NS {nameserver2}

{cname} IN A {public ip}

以上的 nameserver1, nameserver2 使用 Cloudflare nameserver , 因為我的 domain 是託管在 cloudflare 上。

參考資源:

Server 2 設定

安裝編輯 resolve conf 套件

sudo apt install resolvconf -y

進入 resolv.conf.d

cd /etc/resolvconf/resolv.conf.d

編輯 head 文件,增加自己建置的 nameserver

# dns server ec2 的 private ip
nameserver 172.xx.xx.xxx

編輯完成後重新加載設定

sudo resolvconf -u

也可透過以下指令檢查是否已經吃到所設定的 nameserver

systemd-resolve --status

參考文件:

CoreDns 背景執行

創建 systemd

cd /etc/systemd/system
sudo vim coredns.service

coredns.service 內容如下

[Unit]
Description=CoreDns Service
Documentation=https://coredns.io
After=network.target

[Service]
User=root
WorkingDirectory=/home/ubuntu/go/coredns
ExecStart=/bin/bash -c "/home/ubuntu/go/coredns/coredns --conf /home/ubuntu/go/coredns/Corefile >> /home/ubuntu/coredns.log"
ExecReload=/bin/kill -SIGUSR1 $MAINPID
NoNewPrivileges=yes
Restart=on-failure


[Install]
WantedBy=multi-user.target

重新加載 systemd

sudo systemctl daemon-reload

啟動 coredns.service

sudo systemctl start coredns

設定開機時啟動

sudo systemctl enable coredns

檢查 coredns 是否正在運行

sudo systemctl status coredns

Private Subnet 設定

這邊為了安全起見將 DNS Server 設定為 Private Subnet , 導致 public 的服務無法存取的 DNS Server, 因為這邊的概念做法是透過 DNS Server 去 mapping 內部網路的一些服務(不公開的)。

首先在 AWS Dashboard 創建一個新的 VPC

  1. 點選 VPC and more

設定 VPC 名稱以及 IPv4 CIDR block 並且 disable IPv6

2.VPC 設定 public subnet, private subnet, 這邊的 AZ, public sunbet, private subnet 可以依照自己的想法去設定數量。

private subnet 因為沒有對外的連線方式,因此要為他的 Route Table 設定 NAT Gateway, 使他可以連到外面,否則在進行 apt 指令操作的時候會連線失敗。

3.建置 NAT Gateway

4. 將 NAT Gateway 設定至 private subnet 所屬的 Route Table

5. 創建隸屬於 public subnet 的 ec2, 記得要 auto assign ip

6. 創建屬於 private subnet 的 ec2, 要確認是否需要 auto assign ip

以下為了方便將 public subnet ec2 的簡稱為 public ec2, 反之 private subnet 的 ec2 簡稱為 private ec2

7. 確認 public ec2 是否可以 ping 到 private ec2

8. 確認外網是否 ping 不到 private ec2

參考資源:

兩個不同的 VPC 如何互連

倘若 DNS Server 跟 第二台 Server 處於不同的 VPC 下的話,需要建立 Peer Connection。

  1. 選擇 Peer Connection

2. 點選創建 Peer Connection

3. 設定請求方以及接收方的 VPC, 都設定完成後點選 Accept

4. 在請求方以及接收方的 Route Table 設定對應的 CIRD 以及 Peer Connection,也就請求方 A 的 Route Table 要新增接收方 B 的 CIRD ,接收方 B 的 Route Table 也要新增請求方 A 的 CIRD 至 Route Table。

5. 確認請求方跟接收方的 Security Group 的 ICMP 是否都開啟,因為待會會透過 ping 去確認是否已經可以互連。

https://habil.dev/connection-between-two-different-vpc-with-aws-peering/

因為 DNS Server 位於 private subnet ,當想要 ssh 進入到機器內的話有兩種方式,一種是使用跳板機,另外一個則是使用 aws session manager。

跳板設定

~/.ssh/config 設定

Host <跳板機>
HostName <跳板機 public ip>
User <跳板機 username>
Port 22
IdentityFile ~/.ssh/<key pair>
IdentitiesOnly yes


Host <private ec2 主機>
HostName <Private ip>
User <username>
Port 22
IdentityFile ~/.ssh/<key pair>
IdentitiesOnly yes
ProxyJump <跳板機 Host, 也就是上方的 Host 名稱>

上面是透過跳板機連到 private ec2, 也可以透過 session manager 連線到 private subnet

Session Manager 連線

1.安裝 session manager

curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/mac/sessionmanager-bundle.zip" -o "sessionmanager-bundle.zip"

2. 解壓縮 session manager

unzip sessionmanager-bundle.zip

3. 安裝 session manager

sudo ./sessionmanager-bundle/install -i /usr/local/sessionmanagerplugin -b /usr/local/bin/session-manager-plugin

4. 確認 session manager 是否安裝成功

./sessionmanager-bundle/install -h

5. 設定具有 ssm 連線的 role

點選 Create Role

Use Case 選擇 EC2

Permission 給予 AmazonSSMManagedInstanceCore, 後按下新增

6. 將 ssm role assign 給 ec2

7. 設定 ~/.ssh/config

Host <ec2 instance id>
User ubuntu
Port 22
IdentityFile <pem key>
ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p '"

8. 測試是否可以連線成功

ssh <ec2 instance id>

參考資源:

--

--

Gary Ng
Gary Ng

Written by Gary Ng

軟體工程師、後端工程師

No responses yet