
这周的目标是让学员快速具备Go的读写能力,重点不是语法大全,而是能看懂Kubernetes、Prometheus的源码片段,能写出规范的Go项目。
我们从go mod开始,强调Go项目布局(参照golang-standards/project-layout),并上手了Makefile统一构建流程。
示例:一个标准运维工具的项目骨架
cmd/ # 可执行文件入口
agent/ # 运维代理程序
main.go
internal/ # 内部包(不对外暴露)
config/
collector/
pkg/ # 可复用的公共库
logger/
httputil/
api/ # API定义(proto或OpenAPI)
configs/ # 配置文件示例
deployments/ # 部署相关(Dockerfile, k8s yaml)
Makefile
go.modMakefile 示例(编译+测试+打包)
BINARY_NAME=ops-agent
VERSION=1.0.0
build:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/${BINARY_NAME} cmd/agent/main.go
test:
go test -v -race -coverprofile=coverage.out ./...
lint:
golangci-lint run
docker:
docker build -t ${BINARY_NAME}:${VERSION} .运维开发中最常见的需求是“同时监控1000台机器的状态”,这需要高效的并发处理。我们花了大量时间实践工作池(worker pool) 模式。
实战代码:并发执行健康检查
package main
import (
"fmt"
"sync"
"time"
)
type Node struct {
IP string
Port int
}
// 模拟健康检查
func checkNode(node Node) bool {
time.Sleep(100 * time.Millisecond) // 模拟网络请求
return true // 假设都成功
}
func main() {
nodes := make([]Node, 1000)
for i := 0; i < 1000; i++ {
nodes[i] = Node{IP: fmt.Sprintf("10.0.0.%d", i), Port: 8080}
}
// 并发检查,控制并发数20
concurrency := 20
ch := make(chan Node, len(nodes))
var wg sync.WaitGroup
// 启动worker
for i := 0; i < concurrency; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for node := range ch {
if checkNode(node) {
fmt.Printf("%s:%d OK\n", node.IP, node.Port)
}
}
}()
}
// 投递任务
for _, node := range nodes {
ch <- node
}
close(ch)
wg.Wait()
}这个模式几乎出现在所有运维工具中(采集器、巡检器、日志处理器),我们要求学员必须手写并理解其原理。
运维工具需要与各类系统交互:HTTP API、gRPC、MQ、数据库。这周我们重点攻克HTTP服务端/客户端、gRPC、中间件(鉴权、限流、日志)。
我们实现了一个轻量级的运维API服务,提供节点注册、状态查询、任务下发等接口,并集成了JWT认证和请求日志。
代码片段:路由与中间件
package main
import (
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"net/http"
)
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing token"})
return
}
// 验证JWT(省略具体逻辑)
c.Next()
}
}
func main() {
r := gin.Default()
r.Use(gin.Logger()) // 记录请求日志
r.Use(gin.Recovery()) // panic恢复
api := r.Group("/api/v1")
api.Use(AuthMiddleware())
{
api.POST("/nodes", registerNode)
api.GET("/nodes/:id", getNodeStatus)
api.POST("/tasks", createTask)
}
r.Run(":8080")
}运维开发的必修课是“和K8s打交道”。我们用client-go编写了一个控制器,监听Pod事件并自动注入sidecar。
示例:获取所有Pod并过滤状态
package main
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
func main() {
config, _ := clientcmd.BuildConfigFromFlags("", "/home/user/.kube/config")
clientset, _ := kubernetes.NewForConfig(config)
pods, _ := clientset.CoreV1().Pods("default").List(context.TODO(), metav1.ListOptions{})
for _, pod := range pods.Items {
fmt.Printf("Pod: %s, Status: %s\n", pod.Name, pod.Status.Phase)
}
}这个模块为后续的Operator开发打下基础。
运维平台离不开数据持久化(监控数据、配置、任务队列)。我们分别实践了MySQL + GORM、Redis缓存和RabbitMQ任务分发。
设计了一个告警规则表,并实现CRUD接口。
type AlertRule struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"uniqueIndex"`
Metric string
Threshold float64
Duration int // 持续分钟数
Action string
}
// 创建规则
func CreateRule(db *gorm.DB, rule *AlertRule) error {
return db.Create(rule).Error
}
// 根据指标查询规则
func GetRulesByMetric(db *gorm.DB, metric string) ([]AlertRule, error) {
var rules []AlertRule
err := db.Where("metric = ?", metric).Find(&rules).Error
return rules, err
}在运维任务调度中,防止多个worker同时执行同一任务(如全量备份)。我们用Redis实现了一个简易分布式锁。
import "github.com/go-redis/redis/v8"
func acquireLock(client *redis.Client, key string, ttl time.Duration) bool {
ctx := context.Background()
// SETNX + EXPIRE
ok, err := client.SetNX(ctx, key, "locked", ttl).Result()
if err != nil {
return false
}
return ok
}
func releaseLock(client *redis.Client, key string) {
client.Del(context.Background(), key)
}这是第一次综合实战:开发一个跨平台的监控Agent,采集CPU、内存、磁盘、网络指标,通过HTTP上报到中心Server,并支持远程配置更新。
技术选型:
gopsutil(Go的系统信息库)核心采集代码:
package collector
import (
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/mem"
"time"
)
type Metric struct {
Timestamp int64
CPUPercent float64
MemPercent float64
DiskUsage float64
}
func Collect() Metric {
cpuPerc, _ := cpu.Percent(1*time.Second, false)
memStat, _ := mem.VirtualMemory()
return Metric{
Timestamp: time.Now().Unix(),
CPUPercent: cpuPerc[0],
MemPercent: memStat.UsedPercent,
// Disk omitted for brevity
}
}Agent每秒采集一次,每10秒批量上报一次,并实现了指数退避重试。
学员成果:所有学员都完成了Agent开发,并制作了Docker镜像,部署到测试环境运行一周,平均内存占用仅12MB,CPU占用<1%。
训练营的压轴项目是开发一个自定义Operator,实现“MySQL主从自动切换”的自动化运维。我们使用controller-runtime框架,定义CRD(MysqlCluster),实现Reconcile循环。
apiVersion: ops.example.com/v1alpha1
kind: MysqlCluster
metadata:
name: my-cluster
spec:
replicas: 1
version: "8.0"
storageSize: "100Gi"
backupSchedule: "0 2 * * *"func (r *MysqlClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
// 获取CR实例
var cluster opsv1alpha1.MysqlCluster
if err := r.Get(ctx, req.NamespacedName, &cluster); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// 确保StatefulSet存在
sts := &appsv1.StatefulSet{}
if err := r.Get(ctx, types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}, sts); err != nil {
// 不存在则创建
return r.createStatefulSet(ctx, &cluster)
}
// 检查副本数是否一致
if *sts.Spec.Replicas != cluster.Spec.Replicas {
// 更新StatefulSet
patch := client.MergeFrom(sts.DeepCopy())
sts.Spec.Replicas = &cluster.Spec.Replicas
if err := r.Patch(ctx, sts, patch); err != nil {
return ctrl.Result{}, err
}
}
// 检查备份CronJob(略)
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}整个项目持续两周,学员分组完成,最终所有团队都实现了自动创建StatefulSet + Service + ConfigMap + 定时备份CronJob的核心功能。部分优秀学员还实现了主从切换的故障自愈逻辑(基于探针+Leader选举)。
sync.Pool复用buffer,上报QPS提升30%。两期结束后,我们统计了学员的能力提升:
学员留言(匿名):
“以前看Kubernetes源码像天书,现在能看懂controller逻辑了,也知道如何给Prometheus写exporter。Go让我对运维的理解从‘操作层面’上升到了‘系统层面’。”
net/http、context、client-go的实现,比任何教程都管用。testing非常强大,训练营要求单元测试覆盖率不低于70%,有效减少了生产bug。原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。