運維工作新時代:自主編碼實現運維自動化的轉型之旅

京东云开发者發表於2024-02-21

引言

隨著業務系統和底層中介軟體服務的複雜度不斷增加,傳統手工運維方式面臨著諸多挑戰和限制。人工編寫運維指令碼顯得非常低效,同時手動執行運維操作存在著巨大風險。在此情況下,推動運維自動化成為運維人員必須落地實施的工作。運維同學如果可以有地方自主透過編碼的方式,實現各種自動化任務和運維功能。不僅可以提高效率,降低風險,還能為運維工作帶來新的突破。

然而,要邁向這條運維自動化之路並不容易。我們需要克服傳統運維的侷限性,同時要掌握編碼技能和提供適應的平臺。

本文將介紹如何衡量運維自動化率的概念,並提供一個支援運維同學透過編碼實現自動化的平臺。透過編碼實現運維自動化的轉型之旅,讓運維工作邁入新的時代。希望也能給大家提供一個全新的視角。

運維工作面臨的挑戰和限制

複雜的指令碼管理和手動操作

過於依賴手動操作和編寫複雜的手工指令碼,容易引發故障,增加了運維工作量和風險:

指令碼維護和版本控制:隨著時間的推移,維護的指令碼可能會變得越來越複雜、並且難以維護。同時,在團隊多人協作的情況下,對指令碼進行版本控制和管理也存在挑戰,特別容易發生用錯指令碼的情況。

•手工操作錯誤:手動操作容易引入人為的操作錯誤,尤其是在處理線上任務時。一個小錯誤可能導致系統故障或資料丟失,從而增加了系統的不穩定性和風險。

人為失誤和依賴個人技能

運維過程中的人為失誤會導致系統故障和資料丟失,過度依賴個人技能的情況也會使得團隊合作和知識傳承困難:

•依賴個別人員:如果某個運維同學負責的任務過於依賴於個人技能和經驗,那麼當該同學離職或休假時,可能會影響運維工作的正常開展。

缺乏流程標準:人為操作缺乏標準化流程和規範,在處理任務時沒有固化的流程提供指導和參考,容易造成人為失誤風險。

個人成長和發展的侷限

日常工作中,大家更容易關注到業務研發,而對於業務運維的工作容易忽視。這種情況給運維同學的個人成長和發展帶來了一些侷限性:

緊急情況和工作壓力:運維同學通常需要在 7*24 小時待命用來處理問題和故障,以確保系統的穩定性和可用性。導致個人經常處於高壓工作狀態,個人的發展和學習可能受到限制。

發展和上升空間:隨著雲端計算發展,部分運維工作正逐步被雲廠商和 DevOps 自動化替代,特別是混合雲時代,傳統運維必須要轉變思維深入到業務或者產品底層,從而提升個人競爭力。

運維自動化的重要性

成本

運維人員管著公司的伺服器資源,每年公司需要為IT資源支付數十億的成本,隨著資源規模的不斷增長,成本控制和策略變的至關重要。在這種情況下,完善的資源成本管理工具和自動化分攤機制變得尤為重要,否則成本管理將面臨巨大的負擔和壓力。

效率

在運維工作當中,例如資源分配和管理、擴容縮容、日常巡檢、版本更新、服務重啟、叢集管理等,這些都是運維最基礎的日常工作,目前這些工作上大多都是偏日常和重複的,手工操作將浪費掉大部分的時間,如果透過自動化解決掉這些問題,將解放運維的生產力,提升運維效率,讓運維的同學可以有更多的精力去做更有價值的事情。

穩定性

透過自動化提升運維效率的同時,也可以大幅降低人為失誤,最大程度保障系統的穩定性執行,即使出現問題,也能夠透過自動化快速發現響應和自動恢復。

編碼實現的運維自動化

上面提到了運維自動化的重要性。自從今年4月份加入技術保障部門以來,我一直在思考如何提升運維的自動化水平,並希望能找到一種衡量該提升的方法。因此,在4月份就提出了一個運維自動化率這樣的一個衡量指標。

運維自動化率的定義

運維自動化率的定義範圍是技術保障部門的所有運維人員。該指標可以透過以下公式計算:

運維自動化率 = 自動化操作次數(透過泰山麒麟) / 手工操作次數(透過堡壘機登入) + 自動化操作次數

其中,分子表示透過泰山麒麟進行的自動化操作次數。這些操作可以是自動化運維命令、運維功能或自動化編排任務。分母表示透過堡壘機登入之後進行的手工操作的次數,再加上分子的數量。

透過這個指標,可以衡量在給定時間內運維人員使用自動化工具相對於手工操作的比例,從而評估運維的自動化水平。較高的自動化率意味著更多的任務可以透過自動化完成,減少了手工操作的工作量,提高了效率和穩定性,從 4月份衡量開始,技術保障部的運維同學運維自動化率從 Q2 的 3% 提升到 目前為 63%。

為什麼要運維自主編碼實現

最初的原因是發現各個運維小團隊都使用自己獨立的運維工具。經過分析,這是因為不同的運維團隊有不同的需求,為了滿足各自的需求,每個團隊都會開發自己的運維工具。隨著時間的推移,就出現了許多不同的運維工具平臺。因此開始思考是否可以提供一個平臺來滿足所有運維人員的需求。

然而,問題又來了,這些需求應該由誰來開發呢?最合理的解決方案是由運維人員自己來開發。因為只有運維人員最瞭解自己的需求。

降低溝通成本:運維同學最瞭解自己的需求,運維團隊可以根據業務需求和環境特點開發定製化的運維工具和指令碼,確保功能與業務需求完美契合。這樣可以降低與平臺方的溝通成本,減少需求解釋和理解的時間和精力。

快速響應需求:運維團隊能夠快速開發或修改運維功能,及時響應業務變化和運維需求。不必等待平臺方的排期支援或更新,可以迅速滿足需求變化,提高運維的靈活性和響應速度。

節約維護成本:相對於各個團隊自建運維工具,透過自行編碼可以節省許多公共部分的維護成本。運維同學只需要關注自己的業務邏輯,而不用擔心整個運維工具的維護。這樣可以降低維護成本,並提高工作效率。

助力專業成長:透過編碼實現運維功能,可以促進運維人員的技術成長。他們可以提升自己的程式設計能力、系統理解能力和問題解決能力。

透過讓所有運維同學都參與其中,可以為運維同學提供更廣闊的學習和成長機會,可以發揮出更大的價值。這樣做不僅可以提升運維團隊的整體能力,還能助力個體運維人員的個人成長和職業發展。

案例分析:ChubaoFS的運維自動化

接入步驟和示例

1、申請運維繫統選單

聯絡泰山麒麟平臺管理員建立運維繫統選單。在這個過程中,平臺管理員將建立對應運維繫統的選單名稱,並根據選單分配使用者私有的鑑權檔案。這個鑑權檔案將在後續的 Controller 開發中被使用。

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 程式碼

②上面步驟"確認"後,會在列表頁出現對應名稱的運維原子記錄,點選操作欄裡的“欄位維護”按鈕開始配置描述該運維功能的具體資料結構。

經過以上兩步,得到了一個描述運維功能的引數

{
  "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個核心檔案:

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)
    }
}
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、資料庫、物件儲存、圖片服務和應用運維接入了麒麟平臺。

作者:京東零售 井亮亮

來源:京東雲開發者社群 轉載請註明來源

相關文章