Coredns 开发系列 -- Sidecar容器实现第二网卡在Coredns的注册
在 Kubernetes 中,通过 Sidecar 容器结合 client-go 动态创建 Service 和 Endpoints 资源,将 Pod 的第二网卡 IP 注册到 CoreDNS。
以下是完整的实现步骤和代码示例:
1. 核心流程
-
Sidecar 容器职责:
- 监听第二网卡(如
net1)的 IP 变化。 - 动态创建或更新 Headless Service 和对应的 Endpoints。
- 确保 CoreDNS 生成
<service-name>.<namespace>.svc.cluster.local格式的 DNS 记录。
- 监听第二网卡(如
-
技术栈:
- Go 语言 + client-go:与 Kubernetes API 交互。
- RBAC 权限:授予 Sidecar 容器操作 Service 和 Endpoints 的权限。
2. 实现代码
(1) Go 主程序 (main.go)
package main
import (
"context"
"fmt"
"log"
"net"
"os"
"time"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/util/retry"
)
func main() {
// 获取环境变量
podName := os.Getenv("POD_NAME")
namespace := os.Getenv("POD_NAMESPACE")
macvlanInterface := getEnv("MACVLAN_INTERFACE", "net1") // 默认网卡名称为 net1
// 初始化 Kubernetes 客户端
config, err := rest.InClusterConfig()
if err != nil {
log.Fatalf("Error loading in-cluster config: %v", err)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
log.Fatalf("Error creating clientset: %v", err)
}
var currentIP string
for {
ip, err := getMacvlanIP(macvlanInterface)
if err != nil {
log.Printf("Error getting Macvlan IP: %v", err)
time.Sleep(10 * time.Second)
continue
}
if ip != currentIP {
log.Printf("Detected new Macvlan IP: %s", ip)
if err := syncServiceAndEndpoints(clientset, podName, namespace, ip); err != nil {
log.Printf("Failed to sync resources: %v", err)
} else {
currentIP = ip
}
}
time.Sleep(30 * time.Second)
}
}
// 获取网卡 IP
func getMacvlanIP(ifaceName string) (string, error) {
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
return "", fmt.Errorf("interface %s not found: %v", ifaceName, err)
}
addrs, err := iface.Addrs()
if err != nil {
return "", fmt.Errorf("failed to get addresses for %s: %v", ifaceName, err)
}
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String(), nil
}
}
}
return "", fmt.Errorf("no IPv4 address found on %s", ifaceName)
}
// 创建或更新 Service 和 Endpoints
func syncServiceAndEndpoints(clientset *kubernetes.Clientset, podName, namespace, ip string) error {
serviceName := fmt.Sprintf("macvlan-%s", podName) // 生成唯一 Service 名称
// 创建或更新 Service (Headless)
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace},
Spec: corev1.ServiceSpec{
ClusterIP: "None",
Ports: []corev1.ServicePort{{Port: 80}},
},
}
if _, err := clientset.CoreV1().Services(namespace).Create(context.TODO(), service, metav1.CreateOptions{}); err != nil && !errors.IsAlreadyExists(err) {
return fmt.Errorf("failed to create Service: %v", err)
}
// 创建或更新 Endpoints
endpoints := &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{{IP: ip, TargetRef: &corev1.ObjectReference{
Kind: "Pod", Name: podName, Namespace: namespace,
}}},
Ports: []corev1.EndpointPort{{Port: 80}},
}},
}
return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
_, err := clientset.CoreV1().Endpoints(namespace).Update(context.TODO(), endpoints, metav1.UpdateOptions{})
if errors.IsNotFound(err) {
_, err = clientset.CoreV1().Endpoints(namespace).Create(context.TODO(), endpoints, metav1.CreateOptions{})
}
return err
})
}
// 辅助函数:读取环境变量,支持默认值
func getEnv(key, defaultValue string) string {
value := os.Getenv(key)
if value == "" {
return defaultValue
}
return value
}
(2) Dockerfile
FROM golang:1.17 AS builder
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o sidecar .
FROM alpine:3.14
COPY --from=builder /app/sidecar /sidecar
ENTRYPOINT ["/sidecar"]
3. Kubernetes 资源配置
(1) ServiceAccount 和 RBAC
# rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: macvlan-dns-registrar
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: macvlan-dns-registrar
rules:
- apiGroups: [""]
resources: ["services", "endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: macvlan-dns-registrar
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: macvlan-dns-registrar
subjects:
- kind: ServiceAccount
name: macvlan-dns-registrar
(2) Pod 配置示例
# pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod
annotations:
k8s.v1.cni.cncf.io/networks: macvlan-network # Multus 附加第二网卡
spec:
serviceAccountName: macvlan-dns-registrar
containers:
- name: main-app
image: nginx
- name: macvlan-sidecar
image: my-registry/macvlan-sidecar:latest # 替换为实际镜像地址
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: MACVLAN_INTERFACE
value: "net1" # 根据实际网卡名称修改
4. 验证 DNS 记录
-
部署 Pod:
kubectl apply -f rbac.yaml kubectl apply -f pod.yaml -
查看生成的 Service 和 Endpoints:
kubectl get svc -l 'service=macvlan' kubectl get endpoints -l 'service=macvlan' -
测试 DNS 解析:
kubectl exec -it another-pod -- nslookup macvlan-my-pod.default.svc.cluster.local # 预期输出: # Name: macvlan-my-pod.default.svc.cluster.local # Address: 192.168.1.10 # Macvlan 分配的 IP
5. 总结
- 核心逻辑:Sidecar 容器通过
client-go动态管理 Service 和 Endpoints,实现第二网卡 IP 到 CoreDNS 的注册。 - 优势:
- 自动化:IP 变化时自动更新 DNS 记录。
- 隔离性:每个 Pod 生成独立的 Service 和 Endpoints,避免冲突。
- 灵活性:支持通过环境变量配置网卡名称。
- 适用场景:需要为 Pod 的辅助网络接口(如 Macvlan)提供 DNS 可发现性的场景。

浙公网安备 33010602011771号