Coredns 开发系列 -- Sidecar容器实现第二网卡在Coredns的注册

在 Kubernetes 中,通过 Sidecar 容器结合 client-go 动态创建 Service 和 Endpoints 资源,将 Pod 的第二网卡 IP 注册到 CoreDNS。
以下是完整的实现步骤和代码示例:


1. 核心流程

  1. Sidecar 容器职责

    • 监听第二网卡(如 net1)的 IP 变化。
    • 动态创建或更新 Headless Service 和对应的 Endpoints。
    • 确保 CoreDNS 生成 <service-name>.<namespace>.svc.cluster.local 格式的 DNS 记录。
  2. 技术栈

    • 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 记录

  1. 部署 Pod

    kubectl apply -f rbac.yaml
    kubectl apply -f pod.yaml
    
  2. 查看生成的 Service 和 Endpoints

    kubectl get svc -l 'service=macvlan'
    kubectl get endpoints -l 'service=macvlan'
    
  3. 测试 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 可发现性的场景。
posted @ 2025-03-24 11:06  静水深耕,云停风驻  阅读(12)  评论(0)    收藏  举报