京东运维小哥的自动化转型之旅

引言

随着业务系统和底层中间件服务的复杂度不断增加,传统手工运维方式面临着诸多挑战和限制。人工编写运维脚本显得非常低效,同时手动执行运维操作存在着巨大风险。在此情况下,推动运维自动化成为运维人员必须落地实施的工作。运维同学如果可以有地方自主通过编码的方式,实现各种自动化任务和运维功能。不仅可以提高效率,降低风险,还能为运维工作带来新的突破。

然而,要迈向这条运维自动化之路并不容易。我们需要克服传统运维的局限性,同时要掌握编码技能和提供适应的平台。

本文将介绍如何衡量运维自动化率的概念,并提供一个支持运维同学通过编码实现自动化的平台。通过编码实现运维自动化的转型之旅,让运维工作迈入新的时代。希望也能给大家提供一个全新的视角。

运维工作面临的挑战和限制

复杂的脚本管理和手动操作

过于依赖手动操作和编写复杂的手工脚本,容易引发故障,增加了运维工作量和风险:

脚本维护和版本控制:随着时间的推移,维护的脚本可能会变得越来越复杂、并且难以维护。同时,在团队多人协作的情况下,对脚本进行版本控制和管理也存在挑战,特别容易发生用错脚本的情况。

手工操作错误: 手动操作容易引入人为的操作错误,尤其是在处理线上任务时。一个小错误可能导致系统故障或数据丢失,从而增加了系统的不稳定性和风险。

人为失误和依赖个人技能

运维过程中的人为失误会导致系统故障和数据丢失,过度依赖个人技能的情况也会使得团队合作和知识传承困难:

依赖个别人员: 如果某个运维同学负责的任务过于依赖于个人技能和经验,那么当该同学离职或休假时,可能会影响运维工作的正常开展。

缺乏流程标准:人为操作缺乏标准化流程和规范,在处理任务时没有固化的流程提供指导和参考,容易造成人为失误风险。

个人成长和发展的局限

日常工作中,大家更容易关注到业务研发,而对于业务运维的工作容易忽视。这种情况给运维同学的个人成长和发展带来了一些局限性:

紧急情况和工作压力:运维同学通常需要在 7*24 小时待命用来处理问题和故障,以确保系统的稳定性和可用性。导致个人经常处于高压工作状态,个人的发展和学习可能受到限制。

发展和上升空间:随着云计算发展,部分运维工作正逐步被云厂商和 DevOps 自动化替代,特别是混合云时代,传统运维必须要转变思维深入到业务或者产品底层,从而提升个人竞争力。

运维自动化的重要性

成本

运维人员管着公司的服务器资源,每年公司需要为IT资源支付数十亿的成本,随着资源规模的不断增长,成本控制和策略变的至关重要。在这种情况下,完善的资源成本管理工具和自动化分摊机制变得尤为重要,否则成本管理将面临巨大的负担和压力。

效率

在运维工作当中,例如资源分配和管理、扩容缩容、日常巡检、版本更新、服务重启、集群管理等,这些都是运维最基础的日常工作,目前这些工作上大多都是偏日常和重复的,手工操作将浪费掉大部分的时间,如果通过自动化解决掉这些问题,将解放运维的生产力,提升运维效率,让运维的同学可以有更多的精力去做更有价值的事情。

稳定性

通过自动化提升运维效率的同时,也可以大幅降低人为失误,最大程度保障系统的稳定性运行,即使出现问题,也能够通过自动化快速发现响应和自动恢复。

编码实现的运维自动化

上面提到了运维自动化的重要性。自从今年4月份加入技术保障部门以来,我一直在思考如何提升运维的自动化水平,并希望能找到一种衡量该提升的方法。因此,在4月份就提出了一个运维自动化率这样的一个衡量指标。

运维自动化率的定义

运维自动化率的定义范围是技术保障部门的所有运维人员。该指标可以通过以下公式计算:

运维自动化率 = 自动化操作次数(通过泰山麒麟) / 手工操作次数(通过堡垒机登录) + 自动化操作次数

其中,分子表示通过泰山麒麟进行的自动化操作次数。这些操作可以是自动化运维命令、运维功能或自动化编排任务。分母表示通过堡垒机登录之后进行的手工操作的次数,再加上分子的数量。

通过这个指标,可以衡量在给定时间内运维人员使用自动化工具相对于手工操作的比例,从而评估运维的自动化水平。较高的自动化率意味着更多的任务可以通过自动化完成,减少了手工操作的工作量,提高了效率和稳定性,从 4月份衡量开始,技术保障部的运维同学运维自动化率从 Q2 的 3% 提升到 目前为 63%。

为什么要运维自主编码实现

最初的原因是发现各个运维小团队都使用自己独立的运维工具。经过分析,这是因为不同的运维团队有不同的需求,为了满足各自的需求,每个团队都会开发自己的运维工具。随着时间的推移,就出现了许多不同的运维工具平台。因此开始思考是否可以提供一个平台来满足所有运维人员的需求。

然而,问题又来了,这些需求应该由谁来开发呢?最合理的解决方案是由运维人员自己来开发。因为只有运维人员最了解自己的需求。

降低沟通成本: 运维同学最了解自己的需求,运维团队可以根据业务需求和环境特点开发定制化的运维工具和脚本,确保功能与业务需求完美契合。这样可以降低与平台方的沟通成本,减少需求解释和理解的时间和精力。

快速响应需求:运维团队能够快速开发或修改运维功能,及时响应业务变化和运维需求。不必等待平台方的排期支持或更新,可以迅速满足需求变化,提高运维的灵活性和响应速度。

节约维护成本:相对于各个团队自建运维工具,通过自行编码可以节省许多公共部分的维护成本。运维同学只需要关注自己的业务逻辑,而不用担心整个运维工具的维护。这样可以降低维护成本,并提高工作效率。

助力专业成长:通过编码实现运维功能,可以促进运维人员的技术成长。他们可以提升自己的编程能力、系统理解能力和问题解决能力。

通过让所有运维同学都参与其中,可以为运维同学提供更广阔的学习和成长机会,可以发挥出更大的价值。这样做不仅可以提升运维团队的整体能力,还能助力个体运维人员的个人成长和职业发展。

案例分析:ChubaoFS的运维自动化

接入步骤和示例

1、申请运维系统菜单

联系泰山麒麟平台管理员创建运维系统菜单。在这个过程中,平台管理员将创建对应运维系统的菜单名称,并根据菜单分配用户私有的鉴权文件。这个鉴权文件将在后续的 Controller 开发中被使用。

yaml 复制代码
apiVersion: v1
clusters:
- cluster:
    certificate-authority: ca.pem
    server: https://xxx.jd.com:80
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubecfg
  name: default 
current-context: default
kind: Config
preferences: {}
users:
- name: kubecfg
  user:
    client-certificate-data: xxxxx(拥有菜单对应的namespace所有权限)
    client-key-data: xxxxx(拥有菜单对应的namespace所有权限)

2、创建运维功能

在泰山麒麟平台中创建运维功能时,支持两种实现方式。一种是基于运维同学提供的 HTTP 接口服务,另一种是基于运维自己编码实现的自定义(基于 Kubernetes CRD)模式的 Controller。本文重点将介绍基于自定义模式的 Controller 实现方式。

创建运维功能步骤:

① 进入开发者模式,点击"新建"按钮,在窗口中可以选择原子类型为自定义,后续需要运维同学开发相应的 Controller 代码

②上面步骤"确认"后,会在列表页出现对应名称的运维原子记录,点击操作栏里的"字段维护"按钮开始配置描述该运维功能的具体数据结构。

经过以上两步,得到了一个描述运维功能的参数

json 复制代码
{
  "apiVersion": "test.sops.com/v1",
  "kind": "Binlog",
  "spec": {
    "sqlTypes": "delete",
    "dbs": "test",
    "tbs": "recycle_test",
    "ip": " 127.0.0.1",
    "workType": "rollback",
    "startTime": "2023-12-25 08:00:00",
    "stopTime": "2023-12-25 11:00:00",
    "type": "recoverer"
  },
  "status": {
    "custom": {},
    "state": "succeed",
    "message": ""
  }
}

根据以上参数,接下来是编写执行该运维功能的代码逻辑了。

3、编写运维功能代码

泰山麒麟平台提供了代码模版,建议下载提供的模版进行编写具体的 Controller 部分,并加入平台分配的鉴权文件,这样可以确保 Controller 运行时可以 Watch 到麒麟平台的操作消息。

代码模版包含两个例子,一个是 Controller 实现单一功能样例,另一个是 Controller 实现多功能样例,主要包含以下2个核心文件:

go 复制代码
package main
import (
    "controllers/example/api/web/service"
    "flag"
    "os"
    examplev1 "controllers/example/api/v1"
    "controllers/example/controllers"
    "k8s.io/apimachinery/pkg/runtime"
    clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/log/zap"
    // +kubebuilder:scaffold:imports
)

var (
    scheme   = runtime.NewScheme()
    setupLog = ctrl.Log.WithName("setup")
)

func init() {
    _ = clientgoscheme.AddToScheme(scheme)
    _ = examplev1.AddToScheme(scheme)
    // +kubebuilder:scaffold:scheme
}

func main() {
    var metricsAddr string
    var enableLeaderElection bool
    //设置启动参数
    flag.StringVar(&metricsAddr, "metrics-addr", ":8090", "The address the metric endpoint binds to.")
    flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
        "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
    flag.Parse()

    //配置日志打印参数
    ctrl.SetLogger(zap.New(func(o *zap.Options) {
        o.Development = true
    }))
    //加入到controller manager管理
    mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
        Scheme:             scheme,
        MetricsBindAddress: metricsAddr,
        LeaderElection:     enableLeaderElection,
        Port:               9443,
    })
    if err != nil {
        setupLog.Error(err, "unable to start manager")
        os.Exit(1)
    }
    //不需要web能力可以删除此行
    go service.RunServer(mgr)
        
    //核心代码,注册CRD,与麒麟平台自定的资源建立watch机制
    if err = (&controllers.ExampleKindReconciler{
        Client: mgr.GetClient(),
        Log:    ctrl.Log.WithName("controllers").WithName("ExampleKind"),
        Scheme: mgr.GetScheme(),
    }).SetupWithManager(mgr); err != nil {
        setupLog.Error(err, "unable to create controller", "controller", "ExampleKind")
        os.Exit(1)
    }
    // +kubebuilder:scaffold:builder
    setupLog.Info("starting manager")
    if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
        setupLog.Error(err, "problem running manager")
        os.Exit(1)
    }
}
erlang 复制代码
package controllers
import (
    "context"
    "strconv"
    "github.com/go-logr/logr"
    "k8s.io/apimachinery/pkg/runtime"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/client"
    examplev1 "controllers/example/api/v1"
)

// ExampleKindReconciler reconciles a ExampleKind object
type ExampleKindReconciler struct {
    client.Client
    Log    logr.Logger
    Scheme *runtime.Scheme
}

var num = 0
// +kubebuilder:rbac:groups=example.sreplat.com,resources=examplekinds,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=example.sreplat.com,resources=examplekinds/status,verbs=get;update;patch
//当麒麟平台上执行一个运维过能时,controller就会watch参数,并携带参数信息进入到这个函数。
func (r *ExampleKindReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
    num += 1
    ctx := context.Background()
    _ = r.Log.WithValues("examplekind", req.NamespacedName)
    example := &examplev1.ExampleKind{}
        
    // your logic here
    //下面都是样例代码,用户直接实现自己的业务逻辑即可
    if err := r.Get(ctx, req.NamespacedName, example); err != nil {
        r.Log.V(1).Info("couldn't find module:" + req.String())
    } else {
        r.Log.V(1).Info("接收Moduler资源的变更", "Resource.spec", example.Spec)
        r.Log.V(1).Info("接收Moduler资源的变更", "Status", example.Status)
    }
    if example.Status.Event == "created" {
        example.Status.Event = "created_done"
        example.Spec.Ba += strconv.Itoa(num)
        r.Log.V(1).Info("创建业务结束了,资源的状态更新为done", "num:", num, "example.Status.Event", example.Status.Event)
        r.Update(ctx, example)
    }
    if example.Status.Event == "updated" {
        example.Status.Event = "updated_done"
        example.Spec.Ba += strconv.Itoa(num)
        r.Log.V(1).Info("更新业务结束了,资源的状态更新为done", "num:", num, "example.Status.Event", example.Status.Event)
        r.Update(ctx, example)
    }
    if example.Status.Event == "list" {
        example.Status.Event = "list_done"
        example.Spec.Ba += strconv.Itoa(num)
        r.Log.V(1).Info("查询业务结束了,资源的状态更新为done", "num:", num, "example.Status.Event", example.Status.Event)
        r.Update(ctx, example)
    }
    if example.Status.Event == "deleted" {
        example.Status.Event = "deleted_done"
        example.Spec.Ba += strconv.Itoa(num)
        r.Log.V(1).Info("删除业务结束了,资源的状态更新为done", "num:", num, "example.Status.Event", example.Status.Event)
        r.Update(ctx, example)
    }
    return ctrl.Result{}, nil
}

func (r *ExampleKindReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&examplev1.ExampleKind{}).
        Complete(r)
}

4、部署运维功能代码

当完成代码后,可以将其部署在行云部署的容器中。为了方便统一管理,建议在"泰山麒麟(SOPS)"系统下自行申请应用。如果 Controller 对于运行环境有特殊要求,也可以选择自行部署。

5、运维功能发布

当完成运维功能开发和部署后,可以在泰山麒麟平台点击"发布"按钮,发布之后,可以通过"授权"功能,将这个功能提供给其他运维人员使用。

6、执行运维功能

支持两种执行模式,可以在运维功能下面直接针对运维功能进行执行,也可以通过运维编排功能,将运维功能编排成自动化的运维场景执行。

单个运维功能执行

运维编排功能执行

7、查看执行记录

在泰山麒麟平台中执行后,支持查看运维功能执行中的参数、过程、结果以及运行日志。

相关问题和方案

不知道哪些功能可以接入: 运维团队先整理了所有线上批量操作的需求和动作,然后按照优先级逐一进行接入。

缺乏动力接入,导致接入缓慢:早期平台仅提供了单一的功能,例如执行一条指令或执行一个操作,缺乏针对运维场景的任务,这导致运维团队缺乏接入的动力。后来,平台引入了"运维编排"功能,通过编排的方式支持复杂的运维场景,同时还能对高危风险操作进行人工审批,以确保安全。运维编排的引入满足了运维团队的需求,也提高了运维人员的接入动力。

成果和收获

截至目前,ChubaoFS已通过上述方式实现了43个运维功能原子,并设计了18个运维编排的自动化任务。每周平均执行自动化任务的次数约为500次。

泰山-麒麟平台

平台简介

麒麟平台通过扩展 Kubernetes 的自定义资源定义(Custom Resource Definitions, CRD)功能,为运维工程师提供可编程的统一运维平台。用户专注于运维功能开发,平台处理通用属性和集成工作。实现基础设施即代码(IaC)理念,并支持声明式 API。

通过 CRD 扩展 Kubernetes API,引入自定义资源类型,满足特定运维需求。工程师定义资源类型,编码实现运维功能,平台处理创建、更新、删除等通用操作和资源集成。采用声明式方式描述所需运维状态,无需关注底层实现细节。编写资源定义文件,提交给麒麟平台处理,自动完成操作,确保系统达到期望状态。

麒麟平台简化了运维工程师的开发工作,提高了可维护性和可扩展性,实现了基础设施即代码的理念,平台支持以下功能:

运维功能: 执行单个运维功能,配置审批,授权执行等属性

命令执行: 批量执行运维命令及执行过程控制,包括超时,并发,止损kill等通用属性

定时任务: 对运维功能或命令执行定时任务,如巡检,数据备份,日志清理等

资源展示: 展示资源信息,如 IT资产、数据库、中间件,应用程序等

资源操作: 对展示出来的资源添加一个具体的运维功能,需要时可以快速在平台上对该资源执行运维操作

运维编排: 通过图形界面提供流程编辑工具,可以将多个运维原子(运维功能和命令)和审批流串联起来,实现复杂运维作业自动化减少人工干预,并支持人工复核审批,大大提升了运维操作的安全性。

平台现状

截至目前,技术保障部内的运维团队已经将相关产品,包括JMQ、JIMDB、LogBook、ChubaoFS、数据库、对象存储、图片服务和应用运维接入了麒麟平台。参与编码的运维人员占比约为20%。共计定义了11个Controller,并实现了282个运维原子。平均每周执行次数约为2.3万次。

作者:京东零售技术平台 井亮亮

来源:京东零售技术 转载请注明来源

相关推荐
看山还是山,看水还是。39 分钟前
Redis 配置
运维·数据库·redis·安全·缓存·测试覆盖率
扣得君43 分钟前
C++20 Coroutine Echo Server
运维·服务器·c++20
keep__go1 小时前
Linux 批量配置互信
linux·运维·服务器·数据库·shell
矛取矛求1 小时前
Linux中给普通账户一次性提权
linux·运维·服务器
death bell2 小时前
Docker基础概念
运维·docker·容器
ʚɞ4963 小时前
应用程序部署(IIS的相关使用,sql server的相关使用)
运维·服务器
少陽君3 小时前
服务器显卡和桌面pc显卡有什么不同
运维·服务器
daizikui3 小时前
Linux文件目录命令
linux·运维·服务器
叫我龙翔4 小时前
【项目日记】仿mudou的高并发服务器 --- 整体框架搭建 ,实现时间轮模块
运维·服务器·网络·c++·github
花生的酱5 小时前
Shell编程之正则表达式与文本处理器
linux·运维·正则表达式