相关配置和脚本
目录结构
# 位于/home/dns目录下
coredns_stand-alone
.
├── compose-coredns.yaml
├── coredns
│ └── conf
│ └── Corefile
├── etcd
│ ├── certs
│ │ ├── ca-config.json
│ │ ├── ca.csr
│ │ ├── ca-csr.json
│ │ ├── ca-key.pem
│ │ ├── ca.pem
│ │ ├── server.csr
│ │ ├── server-csr.json
│ │ ├── server-key.pem
│ │ └── server.pem
│ └── data
└── etcd-cert-gen.sh
执行流程
# 请执行按照前文提到的目录结构来存放这些文件
# 服务部署文件在当前项目的demo文件夹下
# Docker和Docker-compose的安装就略过
# 格式化脚本使用dos2unix
# 由于脚本文件在Windows下编辑过,其换行符与Unix不同
# 安装dos2unix
yum install -y dos2unix
# 脚本格式化
dos2unix etcd-cert-gen.sh
# 进入项目目录
cd coredns_stand-alone
# 脚本赋权
chmod +x etcd-cert-gen.sh
# 执行脚本,生成etcd的tls证书
./etcd-cert-gen.sh
# 启动coredns容器
docker-compose -f ./compose-coredns.yaml up -d
.env
############################################################
###### Global Setting ######
############################################################
COMPOSE_PROJECT_NAME=coredns
# etcd
# etcd uses gcr.io/etcd-development/etcd as a primary container registry, and quay.io/coreos/etcd as secondary.
# ETCD_IMAGE_NAME=registry.cn-hangzhou.aliyuncs.com/coreos_etcd/etcd:v3
ETCD_IMAGE_NAME=quay.io/coreos/etcd:v3.3.20
ETCD_API_VERSION=3
ETCD_DATA_DIR=./etcd/data
ETCD_CERT_DIR=./etcd/certs
# coredns
COREDNS_IMAGE_NAME=coredns/coredns:1.6.9
COREDNS_CONF_DIR=./coredns/conf
compose-coredns.yaml
version: '3'
services:
# etcd service
etcd0:
image: ${ETCD_IMAGE_NAME}
container_name: etcd0
restart: always
environment:
- ETCDCTL_API=${ETCD_API_VERSION}
- TZ=CST-8
- LANG=zh_CN.UTF-8
command:
- "/usr/local/bin/etcd"
# 成员
- "--name=etcd0"
- "--data-dir=/etcd-data"
- "--advertise-client-urls=https://0.0.0.0:2379"
- "--listen-client-urls=https://0.0.0.0:2379"
#- "--listen-peer-urls=https://0.0.0.0:2380"
# 集群
#- "--initial-advertise-peer-urls=https://0.0.0.0:2380"
#- "--initial-cluster-token=etcd-cluster"
#- "--initial-cluster"
#- "etcd0=https://0.0.0.0:2380"
#- "--initial-cluster-state=new"
# 安全
- "--trusted-ca-file=/etcd-certs/ca.pem"
- "--cert-file=/etcd-certs/etcd.pem"
- "--key-file=/etcd-certs/etcd-key.pem"
#- "--peer-trusted-ca-file=/etcd-certs/etcd-root-ca.pem"
#- "--peer-cert-file=/etcd-certs/etcd.pem"
#- "--peer-key-file=/etcd-certs/etcd-key.pem"
# 日志
- "--debug=true"
volumes:
- ${ETCD_DATA_DIR}:/etcd-data:rw
- ${ETCD_CERT_DIR}:/etcd-certs:ro
ports:
- 2379:2379
- 2380:2380
# coredns service
coredns:
image: ${COREDNS_IMAGE_NAME}
container_name: coredns
restart: always
network_mode: host
depends_on:
- etcd0
command: -conf /etc/coredns/Corefile
volumes:
- ${COREDNS_CONF_DIR}:/etc/coredns:ro
- ${ETCD_CERT_DIR}:/etcd-certs:ro
Corefile
.:53 {
# 监听tcp和udp的53端口
# 配置启用etcd插件,后面可以指定域名,例如 etcd test.com {}
etcd {
# 启用存根区域功能。 stubzone仅在位于指定的第一个区域下方的etcd树中完成
stubzones
# etcd里面的路径。默认为/coredns,以后所有的dns记录就是存储在该存根路径底下
path /coredns
# etcd访问地址,多个空格分开
endpoint https://127.0.0.1:2379
# upstream设置要使用的上游解析程序解决指向外部域名的在etcd(认为CNAME)中找到的外部域名。
upstream 114.114.114.114:53 8.8.8.8:53 8.8.4.4:53 /etc/resolv.conf
# 如果区域匹配但不能生成记录,则将请求传递给下一个插件
fallthrough
# 可选参数,etcd认证证书设置
# 格式: tls CERT KEY CACERT
tls /etcd-certs/etcd.pem /etcd-certs/etcd-key.pem /etcd-certs/ca.pem
# 指定访问etcd用户名和密码(根据实际情况使用)
# credentials USERNAME PASSWORD
}
# 健康
health
# 监控插件
prometheus
# 缓存时间
cache 160
# 自动加载时间间隔
reload 6s
# 负载均衡,开启DNS记录轮询策略
loadbalance
# 上面etcd未查询到的请求转发给设置的DNS服务器解析
forward . 8.8.8.8:53 8.8.4.4:53 /etc/resolv.conf
# 打印日志
log
# 输出错误
errors
}
etcd-cert-gen.sh
#!/usr/bin/env bash
# 文档在Window下编辑过,需要转换为Unix格式。
# 安装工具: yum install -y dos2unix
# 然后执行命令: dos2unix ./etcd-cert-gen.sh
CFSSL_FILE="/usr/local/bin/cfssl"
CFSSL_JSON_FILE="/usr/local/bin/cfssljson"
CFSSL_CERTINFO_FILE="/usr/local/bin/cfssl-certinfo"
# 下载curl工具
# -----------------------
yum install -y curl
# 下载cfssl工具
# -----------------------
if [[ ! -f "$CFSSL_FILE" ]]; then
curl -L https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 -o ${CFSSL_FILE}
chmod +x ${CFSSL_FILE}
echo "------> cfssl has been installed successfully !! <------"
else
echo "------> cfssl has already installed !! <------"
fi
if [[ ! -f "$CFSSL_JSON_FILE" ]]; then
curl -L https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64 -o ${CFSSL_JSON_FILE}
chmod +x ${CFSSL_JSON_FILE}
echo "------> cfssljson has been installed successfully !! <------"
else
echo "------> cfssljson has already installed !! <------"
fi
if [[ ! -f "$CFSSL_CERTINFO_FILE" ]]; then
curl -L https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64 -o ${CFSSL_CERTINFO_FILE}
chmod +x ${CFSSL_CERTINFO_FILE}
echo "------> cfssl-certinfo has been installed successfully !! <------"
else
echo "------> cfssl-certinfo has already installed !! <------"
fi
# 创建证书目录
# -----------------------
mkdir -p ./etcd/certs
cd ./etcd/certs
# CA机构配置:有效期10年
# -----------------------
cat > ca-config.json <<EOF
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"server": {
"expiry": "87600h",
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
]
},
"client": {
"expiry": "87600h",
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
]
}
}
}
}
EOF
# CA机构配置: 机构名称Comman Name,所在地Country国家, State省, Locality市
# -----------------------
cat > ca-csr.json <<EOF
{
"CN": "etcd CA",
"key": {
"algo": "rsa",
"size": 4096
},
"names": [
{
"C": "CN",
"L": "HuNan",
"O": "thyc",
"ST": "ChangSha"
}
]
}
EOF
# 如果是ETCD集群的话就直接在下面的hosts中添加IP或者域名。
# 向CA机构申请:证书注册 (中国,湖南省,长沙市), 提供服务的IP
# Organization Name, Common Name
# -----------------------
cat > server-csr.json <<EOF
{
"CN": "etcd",
"hosts": [
"127.0.0.1",
"192.168.1.111",
"etcd0"
],
"key": {
"algo": "rsa",
"size": 4096
},
"names": [
{
"C": "CN",
"L": "HuNan",
"O": "thyc",
"ST": "ChangSha"
}
]
}
EOF
# 使用定义好的配置初始化CA
cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
# 生成服务器证书
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server server-csr.json | cfssljson -bare etcd
etcd client(java)
pom依赖
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--日志-->
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<!--<dependency>-->
<!--<groupId>log4j</groupId>-->
<!--<artifactId>log4j</artifactId>-->
<!--<version>1.2.17</version>-->
<!--</dependency>-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
<!--<dependency>-->
<!--<groupId>dnsjava</groupId>-->
<!--<artifactId>dnsjava</artifactId>-->
<!--<version>2.1.8</version>-->
<!--</dependency>-->
<dependency>
<groupId>io.etcd</groupId>
<artifactId>jetcd-core</artifactId>
<version>0.5.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-tcnative-boringssl-static -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<version>2.0.29.Final</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<!-- finalName指定打包生成的文件名,默认为工程名-版本号 -->
<finalName>mini-dns</finalName>
<plugins>
<!-- 指定jdk版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<!-- 源码的编译器版本 -->
<source>${java.version}</source>
<!-- class的编译器版本 -->
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- 跳过测试 -->
<skip>true</skip>
</configuration>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
EtcdCluster.java
/**
* etcd node
*
* @author wzm
* @version 1.0.0
* @date 2020/5/8 17:21
**/
public class EtcdCluster {
private String ip;
private Integer port;
private Boolean tls;
private String endpoint;
public EtcdCluster(String ip, Integer port) {
this.ip = ip;
this.port = port;
}
protected void setTls(Boolean tls) {
this.tls = tls;
}
public String getEndpoint() {
return endpoint;
}
protected void generateFormattedEndpoint() {
String http = "http://";
String https = "https://";
if (tls) {
this.endpoint = https + ip + ":" + port.toString();
}else {
this.endpoint = http + ip + ":" + port.toString();
}
}
}
EtcdClient.java
import com.google.gson.JsonObject;
import io.etcd.jetcd.ByteSequence;
import io.etcd.jetcd.Client;
import io.etcd.jetcd.KV;
import io.etcd.jetcd.KeyValue;
import io.etcd.jetcd.kv.DeleteResponse;
import io.etcd.jetcd.kv.GetResponse;
import io.grpc.netty.GrpcSslContexts;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
* etcd Client
*
* @author wzm
* @version 1.0.0
* @date 2020/5/8 16:53
**/
public class EtcdClient {
private List<EtcdCluster> etcdClusters;
private String certPath;
/**
* etcd Client
*/
private static Client client;
/**
* Charset
*/
private static Charset charset = StandardCharsets.UTF_8;
/**
* Cache time
*/
private static final Integer TTL = 600;
/**
* DNS name
*/
private static final String DNS_NAME = "coredns";
public EtcdClient(List<EtcdCluster> etcdClusters) {
for (EtcdCluster etcd : etcdClusters) {
etcd.setTls(false);
etcd.generateFormattedEndpoint();
}
this.etcdClusters = etcdClusters;
initClient();
}
public EtcdClient(List<EtcdCluster> etcdClusters, String certPath) {
for (EtcdCluster etcd : etcdClusters) {
etcd.setTls(true);
etcd.generateFormattedEndpoint();
}
this.etcdClusters = etcdClusters;
this.certPath = certPath;
initClientWithTls();
}
public void close() {
client.close();
}
/**
* Initialize the etcd client to support clustering
*
* @author wzm
* @date 2019/11/1 10:19
*/
private void initClient() {
String[] endpoints = new String[etcdClusters.size()];
for (int i = 0; i < etcdClusters.size(); i++) {
endpoints[i] = etcdClusters.get(i).getEndpoint();
}
client = Client.builder().endpoints(endpoints).build();
}
/**
* Initialize the etcd client (TLS) to support clustering
*
* @author wzm
* @date 2019/11/1 10:20
*/
private void initClientWithTls() {
String[] endpoints = new String[etcdClusters.size()];
for (int i = 0; i < etcdClusters.size(); i++) {
endpoints[i] = etcdClusters.get(i).getEndpoint();
}
try (InputStream is = getClass().getResourceAsStream(certPath)) {
//还需要设置证书路径
client = Client.builder()
.endpoints(endpoints)
.sslContext(
GrpcSslContexts
.forClient()
.trustManager(is)
.build()
).build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* New/modified
*
* @param key 域名
* @param value ip
* @param difference 区别
* @author wzm
* @date 2019/11/1 9:45
*/
public boolean putRecord(String key, String value, String difference) {
try {
KV kv = client.getKVClient();
ByteSequence k = ByteSequence.from(formatKey(key, difference), charset);
ByteSequence v = ByteSequence.from(formatValue(value, TTL), charset);
// put the key-value
kv.put(k, v).join();
return kv.get(k).join().getCount() == 1;
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* query
*
* @param key key
* @param difference differ(The same domain name may correspond to more than one IP)
* @return java.util.Map
* @author wzm
* @date 2019/11/1 10:02
*/
public Map<String, String> getRecord(String key, String difference) {
try {
Map<String, String> map = new HashMap<>(1);
KV kvClient = client.getKVClient();
ByteSequence k = ByteSequence.from(formatKey(key, difference), charset);
// get the CompletableFuture
CompletableFuture<GetResponse> getFuture = kvClient.get(k);
// get the value from CompletableFuture
GetResponse response = getFuture.get();
List<KeyValue> keyValues = response.getKvs();
for (KeyValue kv : keyValues) {
map.put(getKeyFromKv(kv), getValueFromKv(kv));
}
return map;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* delete
*
* @param key dns name
* @param difference differ
* @author wzm
* @date 2019/11/1 10:17
*/
public boolean deleteRecord(String key, String difference) {
try {
KV kvClient = client.getKVClient();
CompletableFuture<GetResponse> getFeature = kvClient.get(ByteSequence.from(formatKey(key, difference), charset));
GetResponse resp = getFeature.get();
ByteSequence k = ByteSequence.from(formatKey(key, difference), charset);
DeleteResponse deleteResponse = kvClient.delete(k).get();
long f = deleteResponse.getDeleted();
return f == resp.getKvs().size();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Get the key from kv
*
* @param kv kv
* @return java.lang.String
* @author wzm
* @date 2019/11/1 9:59
*/
private static String getKeyFromKv(KeyValue kv) {
return kv.getKey().toString(charset);
}
/**
* Get the value from kv
*
* @param kv kv
* @return java.lang.String
* @author wzm
* @date 2019/11/1 9:59
*/
private static String getValueFromKv(KeyValue kv) {
return kv.getValue().toString(charset);
}
private static String formatKey(String domainName, String difference) {
String[] strings = domainName.split("\.");
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("/" + DNS_NAME);
for (int i = 0; i < strings.length; i++) {
String tmp = strings[strings.length - 1 - i];
stringBuilder.append("/").append(tmp);
}
String str = stringBuilder.toString();
if (difference == null || "".equals(difference.trim())) {
return str;
}
return str + "/" + difference;
}
private static String formatValue(String host, int ttl) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("host", host);
jsonObject.addProperty("ttl", ttl);
return jsonObject.toString();
}
//A记录
//etcdctl put /coredns/com/leffss/www '{"host":"1.1.1.1","ttl":10}'
//AAAA记录
//etcdctl put /coredns/com/leffss/www '{"host":"1002::4:2","ttl":10}'
//CNAME记录
//etcdctl put /coredns/com/leffss/www '{"host":"www.baidu.com","ttl":10}'
//SRV记录
//etcdctl put /coredns/com/leffss/www '{"host":"www.baidu.com","port":80,"ttl":10}'
//TXT记录
//etcdctl put /coredns/com/leffss/www '{"text":"This is text!","ttl":10}'
}
源码地址:https://github.com/wenming5112/minidns