Sidecar 模式是一种容器设计模式,它将应用程序的功能分解为独立的容器,这些容器与主应用程序容器一起部署在同一个 Pod 或容器组中。Sidecar 容器为主容器提供辅助功能,如日志收集、监控、网络代理、配置管理等。
基本概念 什么是 Sidecar 模式 Sidecar 模式(边车模式)得名于摩托车边车的概念。在容器编排中,Sidecar 容器就像”边车”一样,与主应用容器(”摩托车”)紧密相连,共享相同的生命周期和网络命名空间。
graph LR
A[主应用容器] --> B[共享网络]
C[Sidecar 容器] --> B
A --> D[共享存储]
C --> D
A --> E[共享生命周期]
C --> E
style A fill:#ffcccc
style C fill:#ccffcc
style B fill:#ccccff
style D fill:#ffffcc
style E fill:#ffccff
核心特性
共享网络命名空间 :Sidecar 和主容器共享同一个网络栈,可以通过 localhost 通信
共享存储卷 :可以共享文件系统,实现日志、配置等数据的共享
独立进程 :Sidecar 是独立的容器进程,不影响主应用的运行
生命周期绑定 :Sidecar 与主容器一起启动、停止和重启
职责分离 :主容器专注于业务逻辑,Sidecar 处理横切关注点
实现方式 Docker Compose 实现 在 Docker Compose 中,可以通过共享网络和卷来实现 Sidecar 模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 version: '3.8' services: app: image: myapp:latest volumes: - ./logs:/var/log/app - ./config:/etc/app networks: - app-network depends_on: - sidecar sidecar: image: fluentd:latest volumes: - ./logs:/var/log/app:ro - ./fluentd.conf:/etc/fluentd/fluentd.conf networks: - app-network command: fluentd -c /etc/fluentd/fluentd.conf
Kubernetes 实现 在 Kubernetes 中,Sidecar 模式通过 Pod 中的多个容器实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 apiVersion: v1 kind: Pod metadata: name: app-with-sidecar spec: containers: - name: app image: myapp:latest ports: - containerPort: 8080 volumeMounts: - name: shared-logs mountPath: /var/log/app - name: shared-config mountPath: /etc/app - name: log-collector image: fluent/fluentd:latest volumeMounts: - name: shared-logs mountPath: /var/log/app:ro env: - name: FLUENTD_CONF value: "fluentd.conf" - name: metrics-collector image: prom/node-exporter:latest ports: - containerPort: 9100 volumeMounts: - name: proc mountPath: /host/proc:ro - name: sys mountPath: /host/sys:ro volumes: - name: shared-logs emptyDir: {} - name: shared-config configMap: name: app-config - name: proc hostPath: path: /proc - name: sys hostPath: path: /sys
使用场景 1. 日志收集 主应用将日志写入共享卷,Sidecar 容器负责收集、处理和转发日志到集中式日志系统。
Go 实现示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 package mainimport ( "context" "fmt" "io/ioutil" "log" "os" "path/filepath" "time" "github.com/fsnotify/fsnotify" ) func mainApp () { logDir := "/var/log/app" os.MkdirAll(logDir, 0755 ) logFile := filepath.Join(logDir, "app.log" ) f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644 ) if err != nil { log.Fatal(err) } defer f.Close() logger := log.New(f, "" , log.LstdFlags) for i := 0 ; i < 10 ; i++ { logger.Printf("应用日志消息 %d: 处理用户请求\n" , i) time.Sleep(1 * time.Second) } fmt.Println("主应用完成" ) } func logCollectorSidecar () { logDir := "/var/log/app" logFile := filepath.Join(logDir, "app.log" ) watcher, err := fsnotify.NewWatcher() if err != nil { log.Fatal(err) } defer watcher.Close() err = watcher.Add(logDir) if err != nil { log.Fatal(err) } fmt.Println("日志收集 Sidecar 已启动,监控:" , logFile) readLogFile(logFile) for { select { case event, ok := <-watcher.Events: if !ok { return } if event.Op&fsnotify.Write == fsnotify.Write { if event.Name == logFile { readLogFile(logFile) } } case err, ok := <-watcher.Errors: if !ok { return } log.Println("监听错误:" , err) } } } func readLogFile (logFile string ) { content, err := ioutil.ReadFile(logFile) if err != nil { return } fmt.Printf("[日志收集器] 收集到日志:\n%s\n" , string (content)) } func main () { mode := os.Getenv("MODE" ) if mode == "sidecar" { logCollectorSidecar() } else { mainApp() } }
2. 服务网格代理 Sidecar 容器作为网络代理,处理服务发现、负载均衡、熔断、限流等功能。
Go 实现示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 package mainimport ( "context" "fmt" "io" "log" "net" "net/http" "time" ) func mainApp () { http.HandleFunc("/" , func (w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "主应用响应: %s\n" , time.Now().Format(time.RFC3339)) }) fmt.Println("主应用启动在 :8080" ) log.Fatal(http.ListenAndServe(":8080" , nil )) } func proxySidecar () { proxy := &http.Server{ Addr: ":8081" , Handler: http.HandlerFunc(handleProxy), } fmt.Println("代理 Sidecar 启动在 :8081" ) log.Fatal(proxy.ListenAndServe()) } func handleProxy (w http.ResponseWriter, r *http.Request) { start := time.Now() log.Printf("[代理] %s %s from %s" , r.Method, r.URL.Path, r.RemoteAddr) targetURL := "http://localhost:8080" + r.URL.Path if r.URL.RawQuery != "" { targetURL += "?" + r.URL.RawQuery } req, err := http.NewRequest(r.Method, targetURL, r.Body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } for key, values := range r.Header { for _, value := range values { req.Header.Add(key, value) } } client := &http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) if err != nil { http.Error(w, err.Error(), http.StatusBadGateway) return } defer resp.Body.Close() for key, values := range resp.Header { for _, value := range values { w.Header().Add(key, value) } } w.WriteHeader(resp.StatusCode) io.Copy(w, resp.Body) duration := time.Since(start) log.Printf("[代理] %s %s - %d - %v" , r.Method, r.URL.Path, resp.StatusCode, duration) } func main () { mode := os.Getenv("MODE" ) if mode == "sidecar" { proxySidecar() } else { mainApp() } }
3. 配置管理 Sidecar 容器从配置中心拉取配置,主应用从共享卷读取配置。
Go 实现示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 package mainimport ( "encoding/json" "fmt" "io/ioutil" "log" "os" "path/filepath" "time" ) type AppConfig struct { DatabaseURL string `json:"database_url"` APIKey string `json:"api_key"` Timeout int `json:"timeout"` Debug bool `json:"debug"` } func mainApp () { configPath := "/etc/app/config.json" for { config, err := loadConfig(configPath) if err != nil { log.Printf("读取配置失败: %v,使用默认配置" , err) config = &AppConfig{ DatabaseURL: "localhost:5432" , APIKey: "default-key" , Timeout: 30 , Debug: false , } } fmt.Printf("主应用使用配置: %+v\n" , config) time.Sleep(5 * time.Second) } } func configSidecar () { configPath := "/etc/app/config.json" os.MkdirAll(filepath.Dir(configPath), 0755 ) fmt.Println("配置管理 Sidecar 已启动" ) for { config := fetchConfigFromCenter() configData, err := json.MarshalIndent(config, "" , " " ) if err != nil { log.Printf("序列化配置失败: %v" , err) time.Sleep(10 * time.Second) continue } err = ioutil.WriteFile(configPath, configData, 0644 ) if err != nil { log.Printf("写入配置失败: %v" , err) } else { fmt.Printf("配置已更新: %+v\n" , config) } time.Sleep(30 * time.Second) } } func fetchConfigFromCenter () *AppConfig { return &AppConfig{ DatabaseURL: "db.example.com:5432" , APIKey: "secret-api-key-" + time.Now().Format("20060102150405" ), Timeout: 60 , Debug: true , } } func loadConfig (path string ) (*AppConfig, error ) { data, err := ioutil.ReadFile(path) if err != nil { return nil , err } var config AppConfig err = json.Unmarshal(data, &config) if err != nil { return nil , err } return &config, nil } func main () { mode := os.Getenv("MODE" ) if mode == "sidecar" { configSidecar() } else { mainApp() } }
4. 监控和指标收集 Sidecar 容器收集主应用的指标并暴露给监控系统。
Go 实现示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 package mainimport ( "fmt" "log" "net/http" "os" "strconv" "time" ) func mainApp () { http.HandleFunc("/health" , func (w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte ("OK" )) }) http.HandleFunc("/api/data" , func (w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte (`{"data": "some data"}` )) }) fmt.Println("主应用启动在 :8080" ) log.Fatal(http.ListenAndServe(":8080" , nil )) } func metricsSidecar () { go collectMetrics() http.HandleFunc("/metrics" , handleMetrics) fmt.Println("监控 Sidecar 启动在 :9090" ) log.Fatal(http.ListenAndServe(":9090" , nil )) } var ( requestCount = 0 errorCount = 0 lastRequestTime time.Time ) func collectMetrics () { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for range ticker.C { resp, err := http.Get("http://localhost:8080/health" ) if err != nil { errorCount++ log.Printf("健康检查失败: %v" , err) continue } resp.Body.Close() if resp.StatusCode != http.StatusOK { errorCount++ } else { requestCount++ } lastRequestTime = time.Now() } } func handleMetrics (w http.ResponseWriter, r *http.Request) { metrics := fmt.Sprintf(`# HELP app_requests_total 应用总请求数 # TYPE app_requests_total counter app_requests_total %d # HELP app_errors_total 应用总错误数 # TYPE app_errors_total counter app_errors_total %d # HELP app_last_request_time 最后请求时间(Unix 时间戳) # TYPE app_last_request_time gauge app_last_request_time %d # HELP app_health_status 应用健康状态(1=健康,0=不健康) # TYPE app_health_status gauge app_health_status %d ` , requestCount, errorCount, lastRequestTime.Unix(), func () int { if errorCount == 0 { return 1 } return 0 }(), ) w.Header().Set("Content-Type" , "text/plain" ) w.Write([]byte (metrics)) } func main () { mode := os.Getenv("MODE" ) if mode == "sidecar" { metricsSidecar() } else { mainApp() } }
5. 安全代理 Sidecar 容器处理 TLS 终止、身份验证、授权等安全功能。
Go 实现示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 package mainimport ( "crypto/tls" "fmt" "io" "log" "net" "net/http" "os" "time" ) func mainApp () { http.HandleFunc("/" , func (w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "主应用响应\n" ) }) fmt.Println("主应用启动在 :8080 (HTTP)" ) log.Fatal(http.ListenAndServe(":8080" , nil )) } func securitySidecar () { tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, } server := &http.Server{ Addr: ":8443" , TLSConfig: tlsConfig, Handler: http.HandlerFunc(handleSecureRequest), } fmt.Println("安全代理 Sidecar 启动在 :8443 (HTTPS)" ) log.Fatal(http.ListenAndServe(":8443" , http.HandlerFunc(handleSecureRequest))) } func handleSecureRequest (w http.ResponseWriter, r *http.Request) { apiKey := r.Header.Get("X-API-Key" ) if apiKey == "" { http.Error(w, "缺少 API Key" , http.StatusUnauthorized) return } if !validateAPIKey(apiKey) { http.Error(w, "无效的 API Key" , http.StatusUnauthorized) return } log.Printf("[安全代理] %s %s from %s (API Key: %s)" , r.Method, r.URL.Path, r.RemoteAddr, apiKey[:8 ]+"..." ) targetURL := "http://localhost:8080" + r.URL.Path if r.URL.RawQuery != "" { targetURL += "?" + r.URL.RawQuery } req, err := http.NewRequest(r.Method, targetURL, r.Body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } for key, values := range r.Header { if key != "X-API-Key" { for _, value := range values { req.Header.Add(key, value) } } } client := &http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) if err != nil { http.Error(w, err.Error(), http.StatusBadGateway) return } defer resp.Body.Close() for key, values := range resp.Header { for _, value := range values { w.Header().Add(key, value) } } w.WriteHeader(resp.StatusCode) io.Copy(w, resp.Body) } func validateAPIKey (apiKey string ) bool { validKeys := map [string ]bool { "valid-key-123" : true , "valid-key-456" : true , } return validKeys[apiKey] } func main () { mode := os.Getenv("MODE" ) if mode == "sidecar" { securitySidecar() } else { mainApp() } }
6. 数据同步 Sidecar 容器负责数据备份、同步到外部存储等。
Go 实现示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 package mainimport ( "encoding/json" "fmt" "io/ioutil" "log" "os" "path/filepath" "time" ) type DataRecord struct { ID string `json:"id"` Data string `json:"data"` Timestamp time.Time `json:"timestamp"` } func mainApp () { dataDir := "/var/data/app" os.MkdirAll(dataDir, 0755 ) for i := 0 ; i < 10 ; i++ { record := DataRecord{ ID: fmt.Sprintf("record-%d" , i), Data: fmt.Sprintf("数据内容 %d" , i), Timestamp: time.Now(), } data, _ := json.Marshal(record) filePath := filepath.Join(dataDir, record.ID+".json" ) ioutil.WriteFile(filePath, data, 0644 ) fmt.Printf("主应用写入数据: %s\n" , record.ID) time.Sleep(2 * time.Second) } } func syncSidecar () { dataDir := "/var/data/app" backupDir := "/var/backup" os.MkdirAll(backupDir, 0755 ) fmt.Println("数据同步 Sidecar 已启动" ) ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for range ticker.C { files, err := ioutil.ReadDir(dataDir) if err != nil { log.Printf("读取数据目录失败: %v" , err) continue } for _, file := range files { if file.IsDir() { continue } sourcePath := filepath.Join(dataDir, file.Name()) backupPath := filepath.Join(backupDir, file.Name()) if needsSync(sourcePath, backupPath) { data, err := ioutil.ReadFile(sourcePath) if err != nil { log.Printf("读取文件失败: %v" , err) continue } err = ioutil.WriteFile(backupPath, data, 0644 ) if err != nil { log.Printf("备份文件失败: %v" , err) continue } fmt.Printf("[数据同步] 已同步: %s -> %s\n" , file.Name(), backupPath) } } } } func needsSync (source, dest string ) bool { if _, err := os.Stat(dest); os.IsNotExist(err) { return true } sourceInfo, err := os.Stat(source) if err != nil { return false } destInfo, err := os.Stat(dest) if err != nil { return true } return sourceInfo.ModTime().After(destInfo.ModTime()) } func main () { mode := os.Getenv("MODE" ) if mode == "sidecar" { syncSidecar() } else { mainApp() } }
Kubernetes 完整示例 日志收集 Sidecar 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 apiVersion: v1 kind: Pod metadata: name: app-with-log-sidecar spec: containers: - name: app image: myapp:latest volumeMounts: - name: shared-logs mountPath: /var/log/app command: ["/app/myapp" ] args: ["--log-dir=/var/log/app" ] - name: fluentd-sidecar image: fluent/fluentd:latest volumeMounts: - name: shared-logs mountPath: /var/log/app:ro - name: fluentd-config mountPath: /fluentd/etc env: - name: FLUENTD_CONF value: "fluentd.conf" volumes: - name: shared-logs emptyDir: {} - name: fluentd-config configMap: name: fluentd-config
服务网格 Sidecar(Istio) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 apiVersion: v1 kind: Pod metadata: name: app-with-istio-sidecar labels: app: myapp spec: containers: - name: app image: myapp:latest ports: - containerPort: 8080 - name: istio-proxy image: istio/proxyv2:latest args: - proxy - sidecar - --configPath - /etc/istio/proxy - --binaryPath - /usr/local/bin/envoy - --serviceCluster - myapp - --drainDuration - 45s - --parentShutdownDuration - 1m0s - --discoveryAddress - istiod.istio-system.svc:15012
最佳实践 1. 资源限制 为 Sidecar 容器设置适当的资源限制,避免影响主应用:
1 2 3 4 5 6 7 8 9 containers: - name: sidecar resources: requests: memory: "64Mi" cpu: "100m" limits: memory: "128Mi" cpu: "200m"
2. 健康检查 为 Sidecar 容器配置健康检查,确保其正常运行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 containers: - name: sidecar livenessProbe: httpGet: path: /health port: 9090 initialDelaySeconds: 10 periodSeconds: 30 readinessProbe: httpGet: path: /ready port: 9090 initialDelaySeconds: 5 periodSeconds: 10
3. 启动顺序 使用 initContainers 或依赖关系确保 Sidecar 在主应用之前启动:
1 2 3 4 5 6 7 8 initContainers: - name: init-sidecar image: busybox command: ['sh' , '-c' , 'until nslookup sidecar-service; do sleep 2; done' ] containers: - name: app
4. 优雅关闭 实现优雅关闭机制,确保 Sidecar 在主应用关闭前完成清理工作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func gracefulShutdown (srv *http.Server) { quit := make (chan os.Signal, 1 ) signal.Notify(quit, os.Interrupt, syscall.SIGTERM) <-quit log.Println("开始优雅关闭..." ) ctx, cancel := context.WithTimeout(context.Background(), 30 *time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal("强制关闭:" , err) } log.Println("Sidecar 已关闭" ) }
5. 错误处理 实现完善的错误处理和重试机制:
1 2 3 4 5 6 7 8 9 10 11 func retryOperation (operation func () error , maxRetries int ) error { var err error for i := 0 ; i < maxRetries; i++ { err = operation() if err == nil { return nil } time.Sleep(time.Duration(i+1 ) * time.Second) } return err }
6. 监控和告警 为 Sidecar 添加监控指标和告警:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func exposeSidecarMetrics () { http.HandleFunc("/sidecar/metrics" , func (w http.ResponseWriter, r *http.Request) { metrics := fmt.Sprintf(`sidecar_uptime_seconds %d sidecar_processed_messages_total %d sidecar_errors_total %d ` , int (time.Since(startTime).Seconds()), processedCount, errorCount, ) w.Write([]byte (metrics)) }) }
Sidecar 模式的优缺点 优点
职责分离 :主应用专注于业务逻辑,横切关注点由 Sidecar 处理
技术栈独立 :Sidecar 可以使用不同的技术栈,不受主应用限制
可重用性 :同一个 Sidecar 可以服务多个主应用
易于维护 :Sidecar 的更新不影响主应用
资源隔离 :Sidecar 故障不会直接影响主应用
缺点
资源开销 :每个 Pod 需要运行额外的容器
网络延迟 :通过 Sidecar 代理可能增加延迟
调试复杂 :需要同时调试主应用和 Sidecar
启动顺序 :需要管理容器启动顺序
配置复杂 :需要配置共享卷、网络等
DaemonSet 详解 什么是 DaemonSet DaemonSet 是 Kubernetes 中的一种工作负载资源,它确保集群中的每个节点(或满足特定条件的节点)都运行一个 Pod 的副本。当有新节点加入集群时,DaemonSet 会自动在新节点上创建 Pod;当节点从集群中移除时,对应的 Pod 也会被回收。
graph TB
A[DaemonSet] --> B[Node 1]
A --> C[Node 2]
A --> D[Node 3]
A --> E[Node N]
B --> B1[Pod 1]
C --> C1[Pod 2]
D --> D1[Pod 3]
E --> E1[Pod N]
style A fill:#ffcccc
style B fill:#ccffcc
style C fill:#ccffcc
style D fill:#ccffcc
style E fill:#ccffcc
DaemonSet 核心特性
节点级部署 :确保每个节点运行一个 Pod 实例
自动扩展 :新节点加入时自动创建 Pod
自动回收 :节点移除时自动删除 Pod
节点选择器 :可以通过 nodeSelector 或 nodeAffinity 选择特定节点
更新策略 :支持 RollingUpdate 和 OnDelete 两种更新策略
DaemonSet 使用场景 1. 日志收集 在每个节点上运行日志收集代理,收集节点和 Pod 的日志。
Go 实现示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 package mainimport ( "fmt" "io/ioutil" "log" "os" "path/filepath" "time" "github.com/fsnotify/fsnotify" ) func logCollectorDaemonSet () { logDirs := []string { "/var/log/pods" , "/var/log/containers" , "/var/log/syslog" , } watcher, err := fsnotify.NewWatcher() if err != nil { log.Fatal(err) } defer watcher.Close() for _, dir := range logDirs { if _, err := os.Stat(dir); os.IsNotExist(err) { continue } err = watcher.Add(dir) if err != nil { log.Printf("无法监听目录 %s: %v" , dir, err) } } fmt.Println("日志收集 DaemonSet 已启动" ) for { select { case event, ok := <-watcher.Events: if !ok { return } if event.Op&fsnotify.Write == fsnotify.Write { handleLogFile(event.Name) } case err, ok := <-watcher.Errors: if !ok { return } log.Printf("监听错误: %v" , err) } } } func handleLogFile (filePath string ) { content, err := ioutil.ReadFile(filePath) if err != nil { return } fmt.Printf("[日志收集] 文件: %s, 大小: %d 字节\n" , filePath, len (content)) sendToLogAggregator(filePath, content) } func sendToLogAggregator (filePath string , content []byte ) { log.Printf("发送日志到聚合系统: %s" , filePath) } func main () { logCollectorDaemonSet() }
Kubernetes 配置示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd-log-collector namespace: kube-system spec: selector: matchLabels: name: fluentd-log-collector template: metadata: labels: name: fluentd-log-collector spec: tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: fluentd image: fluent/fluentd:latest volumeMounts: - name: varlog mountPath: /var/log - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true resources: limits: memory: 200Mi requests: cpu: 100m memory: 200Mi volumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers
2. 监控代理 在每个节点上运行监控代理,收集节点和 Pod 的指标。
Go 实现示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 package mainimport ( "fmt" "io/ioutil" "log" "net/http" "os" "path/filepath" "strings" "time" ) func monitoringDaemonSet () { go collectNodeMetrics() http.HandleFunc("/metrics" , handleMetrics) fmt.Println("监控代理 DaemonSet 启动在 :9100" ) log.Fatal(http.ListenAndServe(":9100" , nil )) } func collectNodeMetrics () { ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() for range ticker.C { cpuUsage := getCPUUsage() memUsage := getMemoryUsage() diskUsage := getDiskUsage() networkStats := getNetworkStats() fmt.Printf("节点指标 - CPU: %.2f%%, 内存: %.2f%%, 磁盘: %.2f%%\n" , cpuUsage, memUsage, diskUsage) sendMetrics(cpuUsage, memUsage, diskUsage, networkStats) } } func getCPUUsage () float64 { data, err := ioutil.ReadFile("/proc/stat" ) if err != nil { return 0 } lines := strings.Split(string (data), "\n" ) if len (lines) > 0 { } return 25.5 } func getMemoryUsage () float64 { data, err := ioutil.ReadFile("/proc/meminfo" ) if err != nil { return 0 } return 60.3 } func getDiskUsage () float64 { return 45.2 } func getNetworkStats () map [string ]interface {} { return map [string ]interface {}{ "bytes_sent" : 1024000 , "bytes_recv" : 2048000 , "packets_sent" : 1000 , "packets_recv" : 2000 , } } func sendMetrics (cpu, mem, disk float64 , network map [string ]interface {}) { log.Printf("发送指标: CPU=%.2f%%, Mem=%.2f%%, Disk=%.2f%%" , cpu, mem, disk) } func handleMetrics (w http.ResponseWriter, r *http.Request) { metrics := fmt.Sprintf(`# HELP node_cpu_usage CPU 使用率 # TYPE node_cpu_usage gauge node_cpu_usage %.2f # HELP node_memory_usage 内存使用率 # TYPE node_memory_usage gauge node_memory_usage %.2f # HELP node_disk_usage 磁盘使用率 # TYPE node_disk_usage gauge node_disk_usage %.2f ` , getCPUUsage(), getMemoryUsage(), getDiskUsage(), ) w.Header().Set("Content-Type" , "text/plain" ) w.Write([]byte (metrics)) } func main () { monitoringDaemonSet() }
Kubernetes 配置示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 apiVersion: apps/v1 kind: DaemonSet metadata: name: node-exporter namespace: monitoring spec: selector: matchLabels: app: node-exporter template: metadata: labels: app: node-exporter spec: hostNetwork: true hostPID: true containers: - name: node-exporter image: prom/node-exporter:latest args: - '--path.procfs=/host/proc' - '--path.sysfs=/host/sys' - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)' ports: - containerPort: 9100 hostPort: 9100 name: metrics volumeMounts: - name: proc mountPath: /host/proc readOnly: true - name: sys mountPath: /host/sys readOnly: true resources: requests: cpu: 100m memory: 180Mi limits: cpu: 200m memory: 220Mi volumes: - name: proc hostPath: path: /proc - name: sys hostPath: path: /sys
3. 网络插件 在每个节点上运行网络插件,管理 Pod 网络。
Kubernetes 配置示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 apiVersion: apps/v1 kind: DaemonSet metadata: name: kube-flannel-ds namespace: kube-system spec: selector: matchLabels: app: flannel template: metadata: labels: app: flannel spec: hostNetwork: true tolerations: - operator: Exists effect: NoSchedule serviceAccountName: flannel initContainers: - name: install-cni image: quay.io/coreos/flannel:v0.14.0 command: - cp args: - -f - /etc/kube-flannel/cni-conf.json - /etc/cni/net.d/10-flannel.conflist volumeMounts: - name: cni mountPath: /etc/cni/net.d - name: flannel-cfg mountPath: /etc/kube-flannel/ containers: - name: kube-flannel image: quay.io/coreos/flannel:v0.14.0 command: - /opt/bin/flanneld args: - --ip-masq - --kube-subnet-mgr resources: requests: cpu: 100m memory: 50Mi limits: cpu: 100m memory: 50Mi securityContext: privileged: false capabilities: add: ["NET_ADMIN" , "NET_RAW" ] env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace volumeMounts: - name: run mountPath: /run/flannel - name: flannel-cfg mountPath: /etc/kube-flannel/ - name: cni mountPath: /etc/cni/net.d volumes: - name: run hostPath: path: /run/flannel - name: cni hostPath: path: /etc/cni/net.d - name: flannel-cfg configMap: name: kube-flannel-cfg
4. 存储插件 在每个节点上运行存储插件,管理存储卷。
Kubernetes 配置示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 apiVersion: apps/v1 kind: DaemonSet metadata: name: local-volume-provisioner namespace: kube-system spec: selector: matchLabels: app: local-volume-provisioner template: metadata: labels: app: local-volume-provisioner spec: serviceAccountName: local-storage-admin containers: - image: quay.io/external_storage/local-volume-provisioner:v2.4.0 name: provisioner securityContext: privileged: true env: - name: MY_NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: MY_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace volumeMounts: - mountPath: /etc/provisioner/config name: provisioner-config readOnly: true - mountPath: /mnt/disks name: local-disks mountPropagation: "HostToContainer" volumes: - name: provisioner-config configMap: name: local-volume-provisioner-config - name: local-disks hostPath: path: /mnt/disks
DaemonSet 核心命令 创建 DaemonSet 1 kubectl create -f daemonset.yaml
查看 DaemonSet 1 2 3 4 5 6 7 8 kubectl get daemonset kubectl describe daemonset <name> kubectl get pods -l <label-selector>
更新 DaemonSet 1 2 3 4 5 6 7 8 kubectl set image daemonset/<name> <container>=<new-image> kubectl edit daemonset <name> kubectl apply -f daemonset.yaml
删除 DaemonSet 1 2 3 4 5 kubectl delete daemonset <name> kubectl delete daemonset <name> --cascade=orphan
DaemonSet 更新策略 RollingUpdate(滚动更新) 1 2 3 4 5 6 7 apiVersion: apps/v1 kind: DaemonSet spec: updateStrategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1
OnDelete(删除时更新) 1 2 3 4 5 apiVersion: apps/v1 kind: DaemonSet spec: updateStrategy: type: OnDelete
DaemonSet 节点选择 使用 nodeSelector 1 2 3 4 5 6 7 apiVersion: apps/v1 kind: DaemonSet spec: template: spec: nodeSelector: disktype: ssd
使用 nodeAffinity 1 2 3 4 5 6 7 8 9 10 11 12 13 14 apiVersion: apps/v1 kind: DaemonSet spec: template: spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/os operator: In values: - linux
DaemonSet 最佳实践
资源限制 :为 DaemonSet Pod 设置适当的资源限制
容忍度配置 :使用 tolerations 控制 Pod 调度
更新策略 :根据需求选择合适的更新策略
健康检查 :配置 liveness 和 readiness 探针
优先级 :使用 PriorityClass 确保重要 DaemonSet 优先调度
与其他模式的对比 Sidecar vs DaemonSet
特性
Sidecar
DaemonSet
部署粒度
Pod 级别(每个 Pod 一个 Sidecar)
节点级别(每个节点一个 Pod)
资源开销
每个 Pod 都有 Sidecar,开销较大
每个节点一个 Pod,开销较小
网络通信
共享网络命名空间,localhost 通信
独立网络,需要网络通信
存储共享
可以共享存储卷
通常使用 hostPath,访问节点文件系统
使用场景
应用级功能(日志、监控、代理)
节点级功能(日志收集、监控代理、网络插件)
生命周期
与主应用 Pod 绑定
与节点绑定
更新影响
更新 Sidecar 需要重启 Pod
更新 DaemonSet 影响节点上的所有 Pod
适用场景
微服务应用、需要应用级隔离
基础设施组件、需要节点级访问
选择建议 :
使用 Sidecar :
需要为每个应用 Pod 提供独立的功能(如服务网格代理)
需要与主应用紧密集成(共享网络、存储)
不同应用需要不同的配置或版本
使用 DaemonSet :
需要在每个节点上运行系统级服务(如日志收集、监控)
需要访问节点资源(如文件系统、网络接口)
需要统一管理节点级功能
Sidecar vs Ambassador
Sidecar :与主应用在同一 Pod 中,共享网络和存储
Ambassador :作为独立的服务,主应用通过网络调用
Sidecar vs Adapter
Sidecar :为主应用提供额外功能
Adapter :转换主应用的输出格式
总结 Sidecar 模式 Sidecar 模式是一种强大的容器设计模式,特别适用于微服务架构。它通过将横切关注点(日志、监控、安全等)从主应用中分离出来,实现了更好的关注点分离和可维护性。
适用场景 :
需要为每个应用 Pod 提供独立功能
需要与主应用紧密集成(共享网络、存储)
不同应用需要不同的配置或版本
DaemonSet DaemonSet 是 Kubernetes 中用于节点级服务部署的重要资源,确保集群中的每个节点都运行特定的 Pod。它特别适合部署基础设施组件和系统级服务。
适用场景 :
需要在每个节点上运行系统级服务
需要访问节点资源(文件系统、网络接口)
需要统一管理节点级功能
选择建议
选择 Sidecar :当需要应用级别的功能隔离和紧密集成时
选择 DaemonSet :当需要节点级别的统一管理和资源访问时
组合使用 :在实际生产环境中,Sidecar 和 DaemonSet 经常组合使用,例如 DaemonSet 负责节点级日志收集,Sidecar 负责应用级日志处理
在选择使用 Sidecar 模式或 DaemonSet 时,需要根据具体需求权衡资源开销、部署粒度和功能需求。对于需要高可靠性、可观测性和安全性的生产环境,合理使用这两种模式可以显著提升系统的可维护性和可扩展性。
参考文献 Sidecar
DaemonSet