Jenkins Pipeline脚本优化:为Kubernetes应用部署增加状态检测

news2025/1/21 18:45:19

引言

在软件部署的世界中,Jenkins已经成为自动化流程的代名词。不断变化的技术环境要求我们持续改进部署流程以满足现代应用部署的需要。在本篇博客中,作为一位资深运维工程师,我将分享如何将Jenkins Pipeline进化至不仅能支持部署应用直至Running状态检测,同时也能兼顾Deployment和StatefulSet资源的轮询更新,并详细介绍滚动更新策略的配置方法。

初始Jenkins Pipeline分析

参照前文:Jenkins Pipeline 脚本优化实践:从繁琐到简洁,初始化pipeline如下:

pipeline {
    agent none // Use none at the top level, each stage will define its own agent.

    environment {
        REGISTRY = "swr.cn-north-4.myhuaweicloud.com/master-metaspace"
        KUBE_CONFIG = "--namespace=master-metaspace --context=master"
        KUBE_YAML_PATH = "/home/jenkins/workspace/yaml/master-metaspace"
        // Assume that 'data' is defined elsewhere or injected as a parameter.
        BASE_WORKSPACE = "xxxxxxx" // 定义一个基础工作空间路径
    }

    stages {
        stage("GetCode") {
            agent { label "build01" }
            steps {
                script {
                    checkout scm: [
                        $class: 'GitSCM',
                        branches: [[name: env.branchName]],
                        extensions: [[$class: 'CloneOption', depth: 1, noTags: false, shallow: true]],
                        userRemoteConfigs: [[credentialsId: 'xxxx', url: env.gitHttpURL]]
                    ]
                }
            }
        }
        
        stage("Docker Builds") {
            parallel {
                stage('Build dataloader-game-ucenter') {
                    agent { label "build01" }
                    when { environment name: 'dataloader', value: 'true' }
                    steps {
                        buildAndPushDockerImage("dataloader-game-ucenter", env.data, env.BASE_WORKSPACE)
                    }
                }
                stage('Build datawriter-game-ucenter') {
                    agent { label "build01" }
                    when { environment name: 'datawriter', value: 'true' }
                    steps {
                        buildAndPushDockerImage("datawriter-game-ucenter", env.data, env.BASE_WORKSPACE)
                    }
                }
                stage('Build game-ucenter') {
                    agent { label "build01" }
                    when { environment name: 'game-ucenter', value: 'true' }
                    steps {
                        buildAndPushDockerImage("game-ucenter", env.data, env.BASE_WORKSPACE)
                    }
                }
            }
        }
        
        stage('Development Deployment') {
            parallel {
                stage("Deploy datawriter-game-ucenter") {
                    when { environment name: 'datawriter-game-ucenter', value: 'true' }
                    agent { label  "huaweiyun-xx" }
                    steps {
                        deployToKubernetes("datawriter-game-ucenter")
                    }
                }
                stage("Deploy dataloader-game-ucenter") {
                    when { environment name: 'dataloader', value: 'true' }
                    agent { label  "huaweiyun-xx" }
                    steps {
                        deployToKubernetes("dataloader-game-ucenter")
                    }
                }
                stage("Deploy game-ucenter") {
                    when { environment name: 'game-ucenter', value: 'true' }
                    agent { label  "huaweiyun-xx" }
                    steps {
                        deployToKubernetes("game-ucenter")
                    }
                }
            }
        }
    }
}

// Define methods outside pipeline to avoid repetition

def buildAndPushDockerImage(String imageName, String tag, String workspacePath) {
    sh "cd ${workspacePath} && echo 'Current directory: \$(pwd)'" // 使用基础工作空间变量
    sh "cd ${workspacePath}/${imageName}&& docker build --build-arg NODE_ENV=$imageName -t $REGISTRY/$imageName:$tag ."
    withCredentials([usernamePassword(credentialsId: 'hw-registry', passwordVariable: 'dockerPassword', usernameVariable: 'dockerUser')]) {
        sh "docker login -u $dockerUser -p $dockerPassword $REGISTRY"
        sh "docker push $REGISTRY/$imageName:$tag"
    }
}

def deployToKubernetes(String kubernetesComponent) {
    String templateFile = "${KUBE_YAML_PATH}/${kubernetesComponent}.tpl"
    String outputFile = "${KUBE_YAML_PATH}/${kubernetesComponent}.yaml"
    sh "sed -e 's/{data}/$data/g' $templateFile > $outputFile"
    sh "sudo kubectl apply -f $outputFile $KUBE_CONFIG"
}

初始的Jenkins Pipeline定义了一个基本的CI/CD流程,涵盖了代码拉取、Docker镜像构建、推送及在Kubernetes环境中的部署。然而,流程中缺少了对部署状态的检查,这是在确保部署稳定性方面至关重要的一个环节。

进化 I:探针引入Deployment部署

现代应用部署不仅仅需要一个“部署到Kubernetes”的指令,更需要在部署后进行健康检查。对于Deployment类型的应用来说,我们需要在所有Pods运行并处于READY状态后才认为部署成功。

状态检测方法介绍

为此,我们引入了checkKubernetesResourceStatus方法来检查资源的状态。该方法通过kubectl的get命令和jsonpath查询输出来轮询检查ready副本数。如果指定时间内资源不达状态,则流程失败。

Jenkinsfile变更详解:

引入checkKubernetesResourceStatus方法来检测deployment各个阶段部署的状态。

def checkKubernetesResourceStatus(String deploymentName, String namespace) {
    int attempts = 30 // Set the number of retry attempts
    int sleepTime = 10 // Set the sleep time between attempts in seconds
    String readyReplicasJsonPath = ".status.readyReplicas"
    for (int i = 1; i <= attempts; i++) {
        // Check the deployment status
        String statusCheck = sh (
            script: "kubectl get deployment ${deploymentName} --namespace=${namespace} -o jsonpath=\"{${readyReplicasJsonPath}}\"",
            returnStdout: true
        ).trim()

        // If the number of ready replicas is not empty and greater than 0
        if (statusCheck && statusCheck.isInteger() && statusCheck.toInteger() > 0) {
            echo "Deployment ${deploymentName} is ready."
            return
        } else {
            echo "Waiting for Deployment ${deploymentName} to be ready. Attempt ${i}/${attempts}"
            sleep sleepTime
        }
    }
    error "Deployment ${deploymentName} did not become ready after ${attempts} attempts"
}

Deploy game-ucenter stage为例:

                stage("Deploy game-ucenter") {
                    when { environment name: 'game-ucenter', value: 'true' }
                    agent { label  "xxxx" }
                    steps {
                        deployToKubernetes("game-ucenter")
                        checkKubernetesResourceStatus("game-ucenter", "master-metaspace")
                    }
                }

game-ucenter模板文件如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: game-ucenter
spec:
  replicas: 1
  selector:
    matchLabels:
      app: game-ucenter
  template:
    metadata:
      labels:
        app: game-ucenter
    spec:
      containers:
        - name: game-ucenter
          image: xxxxxx/xxxx/game-ucenter:{data}
          envFrom:
          - configMapRef:
              name: deploy
          ports:
            - containerPort: 80
          resources:
            requests:
              memory: "4096M"
              cpu: "2000m"
            limits:
              memory: "4096M"
              cpu: "2000m" 
          livenessProbe:
            httpGet:
              scheme: HTTP
              path: /test.html
              port: 80
            initialDelaySeconds: 20
            periodSeconds: 120
            successThreshold: 1
            failureThreshold: 3
          readinessProbe:
            httpGet:
              scheme: HTTP
              path: /test.html
              port: 80
            initialDelaySeconds: 20
            periodSeconds: 120
      imagePullSecrets:                                              
        - name: xxxx
---

apiVersion: v1
kind: Service
metadata:
  name: game-ucenter
  labels:
    app: game-ucenter
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: game-ucenter


image.png
尝试修改以下**replicas: 3 **重新运行以下pipeline:
image.png
也没有问题,pipeline 脚本有效!

进化 II:兼容StatefulSet的健康检查

考虑到某些应用可能采用StatefulSet作为工作负载类型,我们必须确保Jenkins Pipeline能够针对不同的工作负载类型执行健康检查。

状态检测兼容性改进

为了适配StatefulSet,我们对checkKubernetesResourceStatus方法做了略微修改,使其可以接受一个resourceType参数来区分资源类型,进而查询对应的状态字段,代码片段如下:

def checkKubernetesResourceStatus(String resourceName, String namespace, String resourceType) {
    int attempts = 30 // Set the number of retry attempts
    int sleepTime = 10 // Set the sleep time between attempts in seconds
    String readyReplicasJsonPath = resourceType == "deployment" ? ".status.readyReplicas" : ".status.readyReplicas"
    for (int i = 1; i <= attempts; i++) {
        // Check the resource status
        String statusCheck = sh (
            script: "kubectl get ${resourceType} ${resourceName} --namespace=${namespace} -o jsonpath=\"{${readyReplicasJsonPath}}\"",
            returnStdout: true
        ).trim()

        // If the number of ready replicas is not empty and equal to the desired number
        if (statusCheck && statusCheck.isInteger() && statusCheck.toInteger() > 0) {
            echo "${resourceType} ${resourceName} is ready."
            return
        } else {
            echo "Waiting for ${resourceType} ${resourceName} to be ready. Attempt ${i}/${attempts}"
            sleep(sleepTime)
        }
    }
    error "${resourceType} ${resourceName} did not become ready after ${attempts} attempts"
}

修改game-ucenter stage:

                stage("Deploy game-ucenter") {
                    when { environment name: 'game-ucenter', value: 'true' }
                    agent { label  "k8s-node-06" }
                    steps {
                        deployToKubernetes("game-ucenter")
                        checkKubernetesResourceStatus("game-ucenter", "master-metaspace", "deployment")
                    }
                }

创建一个statefulset datawriter-game-ucenter stage:

                stage("Deploy datawriter-game-ucenter") {
                    when { environment name: 'datawriter-game-ucenter', value: 'true' }
                    agent { label  "xxxxx" }
                    steps {
                        deployToKubernetes("datawriter-game-ucenter")
                        checkKubernetesResourceStatus("datawriter-game-ucenter", "master-metaspace", "statefulset")
                    }
                }

image.png
注意:我这里截图还是用了game-ucenter做的测试,其实我想用我的datawriter-game-ucenter,but这个服务是一个node应用没有没有livenessProbe readinessProbe,所以截图我还是使用了game-ucenter!

进化 III:引入滚动更新策略配置和检测

当我们更新Deployment资源时,通常会采用滚动更新策略,以逐步替换旧Pods,最小化部署时的中断。

更新策略检测逻辑

def checkDeploymentUpdateStatus(String deploymentName, String namespace) {
    int attempts = 30 // Set the number of retry attempts
    int sleepTime = 10 // Set the sleep time between attempts in seconds
    echo "Checking the update status of Deployment: ${deploymentName}"

    for (int i = 1; i <= attempts; i++) {
        String updateStatus = sh (
            script: "kubectl rollout status deployment/${deploymentName} --namespace=${namespace}",
            returnStdout: true
        ).trim()

        if (updateStatus.contains("successfully rolled out")) {
            echo "Update status: ${updateStatus}"
            return
        } else {
            echo "Waiting for Deployment ${deploymentName} to successfully roll out. Attempt ${i}/${attempts}"
            sleep(sleepTime)
        }
    }

    error "Deployment ${deploymentName} did not successfully roll out after ${attempts} attempts"
}
  1. 我们增加了checkDeploymentUpdateStatus方法,该方法通过kubectl命令rollout status监控Deployment的更新状态。
  2. 当检测到successfully rolled out时,表示滚动更新成功。
  3. 如果在给定时间内更新没有成功,则流程将失败。

继续考虑一下如果statefulset多实例呢?不想写两个了整合成一个方法如下:

def checkRolloutStatus(String resourceName, String namespace, String resourceType) {
    int attempts = 30 // Set the number of retry attempts
    int sleepTime = 10 // Set the sleep time between attempts in seconds

    if (!(resourceType in ["deployment", "statefulset"])) {
        error "Unknown resource type: ${resourceType}. Only 'deployment' and 'statefulset' are supported."
    }

    echo "Checking the update status of ${resourceType} '${resourceName}' in namespace '${namespace}'"

    for (int i = 1; i <= attempts; i++) {
        String rolloutCommand = "kubectl rollout status ${resourceType}/${resourceName} --namespace=${namespace}"
        String updateStatus = sh (
            script: rolloutCommand,
            returnStdout: true
        ).trim()

        if (updateStatus.contains("successfully rolled out") || updateStatus.contains("partitioned roll out complete"))  {
            echo "Update status: ${updateStatus}"
            return
        } else {
            echo "Waiting for ${resourceType} '${resourceName}' to successfully roll out. Attempt ${i}/${attempts}."
            sleep(sleepTime)
        }
    }

    error "${resourceType} '${resourceName}' did not successfully roll out after ${attempts} attempts in namespace '${namespace}'"
}

Jenkinsfile更新实现

经过上述进化,Jenkinsfile中现在包含了完整的部署状态检查逻辑,以应对不同类型资源的部署监控需求。

pipeline {
    agent none // Use none at the top level, each stage will define its own agent.

    environment {
        REGISTRY = "ccr.ccs.tencentyun.com/xxxxx"
        KUBE_CONFIG = "--namespace=master-metaspace"
        KUBE_YAML_PATH = "/home/jenkins/workspace/yaml/master-metaspace"
        // Assume that 'data' is defined elsewhere or injected as a parameter.
        BASE_WORKSPACE = "xxxxxx" // 定义一个基础工作空间路径
    }

    stages {
        stage("GetCode") {
            agent { label "build01" }
            steps {
                script {
                    checkout scm: [
                        $class: 'GitSCM',
                        branches: [[name: env.branchName]],
                        extensions: [[$class: 'CloneOption', depth: 1, noTags: false, shallow: true]],
                        userRemoteConfigs: [[credentialsId: 'xxxxx', url: env.gitHttpURL]]
                    ]
                }
            }
        }
        
        stage("Docker Builds") {
            parallel {
                stage('Build dataloader-game-ucenter') {
                    agent { label "build01" }
                    when { environment name: 'dataloader-game-ucenter', value: 'true' }
                    steps {
                        buildAndPushDockerImage("dataloader-game-ucenter", env.data, env.BASE_WORKSPACE)
                    }
                }
                stage('Build datawriter-game-ucenter') {
                    agent { label "build01" }
                    when { environment name: 'datawriter-game-ucenter', value: 'true' }
                    steps {
                        buildAndPushDockerImage("datawriter-game-ucenter", env.data, env.BASE_WORKSPACE)
                    }
                }
                stage('Build game-ucenter') {
                    agent { label "build01" }
                    when { environment name: 'game-ucenter', value: 'true' }
                    steps {
                        buildAndPushDockerImage("game-ucenter", env.data, env.BASE_WORKSPACE)
                    }
                }
            }
        }
        
        stage('Development Deployment') {
            parallel {
                stage("Deploy datawriter-game-ucenter") {
                    when { environment name: 'datawriter-game-ucenter', value: 'true' }
                    agent { label  "xxxx" }
                    steps {
                        deployToKubernetes("datawriter-game-ucenter")
                        checkKubernetesResourceStatus("datawriter-game-ucenter", "master-metaspace", "statefulset")
                        checkRolloutStatus("datawriter-game-ucenter", "master-metaspace", "statefulset")
                    }
                }
                stage("Deploy dataloader-game-ucenter") {
                    when { environment name: 'dataloader-game-ucenter', value: 'true' }
                    agent { label  "xxxx" }
                    steps {
                        deployToKubernetes("dataloader-game-ucenter")
                        checkKubernetesResourceStatus("dataloader-game-ucenter", "master-metaspace", "statefulset")
                    }
                }
                stage("Deploy game-ucenter") {
                    when { environment name: 'game-ucenter', value: 'true' }
                    agent { label  "xxxx" }
                    steps {
                        deployToKubernetes("game-ucenter")
                        checkRolloutStatus("game-ucenter", "master-metaspace", "deployment")
                        checkKubernetesResourceStatus("game-ucenter", "master-metaspace", "deployment")
                    }
                }
            }
        }
    }
}

// Define methods outside pipeline to avoid repetition

def buildAndPushDockerImage(String imageName, String tag, String workspacePath) {
    sh "cd ${workspacePath} && echo 'Current directory: \$(pwd)'" // 使用基础工作空间变量
    sh "cd ${workspacePath}/${imageName}&& docker build --build-arg NODE_ENV=game-ucenter -t $REGISTRY/$imageName:$tag ."
    withCredentials([usernamePassword(credentialsId: 'xxxxx', passwordVariable: 'dockerPassword', usernameVariable: 'dockerUser')]) {
        sh "docker login -u $dockerUser -p $dockerPassword $REGISTRY"
        sh "docker push $REGISTRY/$imageName:$tag"
    }
}

def deployToKubernetes(String kubernetesComponent) {
    String templateFile = "${KUBE_YAML_PATH}/${kubernetesComponent}.tpl"
    String outputFile = "${KUBE_YAML_PATH}/${kubernetesComponent}.yaml"
    sh "sed -e 's/{data}/$data/g' $templateFile > $outputFile"
    sh "sudo kubectl apply -f $outputFile $KUBE_CONFIG"
}

def checkRolloutStatus(String resourceName, String namespace, String resourceType) {
    int attempts = 30 // 设置重试次数
    int sleepTime = 10 // 设置重试间隔时间(秒)

    if (!(resourceType in ["deployment", "statefulset"])) {
        error "未知资源类型:${resourceType}。只支持 'deployment' 和 'statefulset' 。"
    }

    echo "正在检查${resourceType} '${resourceName}' 在命名空间 '${namespace}' 的更新状态"

    for (int i = 1; i <= attempts; i++) {
        String rolloutCommand = "kubectl rollout status ${resourceType}/${resourceName} --namespace=${namespace}"
        try {
            String updateStatus = sh (
                script: rolloutCommand,
                returnStdout: true
            ).trim()

            // 添加对 "partitioned roll out complete" 状态的检查
            if (updateStatus.contains("successfully rolled out") || updateStatus.contains("partitioned roll out complete")) {
                echo "更新状态:${updateStatus}"
                return
            } else {
                echo "等待 ${resourceType} '${resourceName}' 成功发布。尝试次数:${i}/${attempts}。"
                sleep(sleepTime)
            }
        } catch (Exception e) {
            echo "获取更新状态时发生错误:${e.getMessage()}。尝试次数:${i}/${attempts}。"
            sleep(sleepTime)
        }
    }

    error "${resourceType} '${resourceName}' 在命名空间 '${namespace}' 内未能在 ${attempts} 次尝试之后成功发布"
}
def checkKubernetesResourceStatus(String resourceName, String namespace, String resourceType) {
    int attempts = 30 // Set the number of retry attempts
    int sleepTime = 10 // Set the sleep time between attempts in seconds
    String readyReplicasJsonPath = resourceType == "deployment" ? ".status.readyReplicas" : ".status.readyReplicas"
    for (int i = 1; i <= attempts; i++) {
        // Check the resource status
        String statusCheck = sh (
            script: "kubectl get ${resourceType} ${resourceName} --namespace=${namespace} -o jsonpath=\"{${readyReplicasJsonPath}}\"",
            returnStdout: true
        ).trim()

        // If the number of ready replicas is not empty and equal to the desired number
        if (statusCheck && statusCheck.isInteger() && statusCheck.toInteger() > 0) {
            echo "${resourceType} ${resourceName} is ready."
            return
        } else {
            echo "Waiting for ${resourceType} ${resourceName} to be ready. Attempt ${i}/${attempts}"
            sleep(sleepTime)
        }
    }
    error "${resourceType} ${resourceName} did not become ready after ${attempts} attempts"
}// 更新后的Jenkins Pipeline代码详细定义参照本文开头给出的代码

image.png

总结

本篇博客通过对Jenkins Pipeline的进化过程展开讲解,展现了如何从简单的部署任务转变为一个健壮且兼顾各类工作负载状态监测的CI/CD流程。我们强化了状态检测的逻辑,引入了更新策略的检测,并保持了对不同Kubernetes资源类型的兼容性。这些改进确保了自动化流程能够与现代部署实践保持同步,给运维团队带来极大便利,并最大化地保障了部署的可靠性。

后记

由于篇幅限制,本篇文章未作其他更详细演示。然而,在实际应用中,运维团队可以根据自己的具体需求和环境进一步丰富和细化每个步骤的实现,确保Pipeline的健壮性和高可用性,以适应不断变化的技术挑战。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1330550.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【电源专题】Buck电源上电震荡谁的错?

在文章:【电源专题】案例:Buck芯片上电瞬间波形震荡?从别的人案例中来学习软启参数中我们通过别人的文章了解到了Buck芯片上电瞬间波形震荡有几个方法可以解决,但主要还是围绕着软启动参数去学习。因为文章中无法知道编者所用的电源芯片和电路,所以无法进行分析。 最近我…

Mysql之约束下篇

Mysql之约束下篇 自增列(AUTO_INCREMENT)关键字特点和要求添加自增约束删除自增约束Mysql8.0新特性-自增变量的持久化 FOREIGN KEY 约束关键字主表和从表/父表和子表特点添加外键约束约束等级删除外键约束面试问题 DEFAULT约束作用关键字添加默认值约束删除默认值约束 CHECK约束…

Leetcode—445.两数相加II【中等】

2023每日刷题&#xff08;六十七&#xff09; Leetcode—445.两数相加II 实现代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2…

k8s启动docker容器Error: Could not find or load main class ${start-class}报错

前行提要&#xff1a; 今天部署采集点服务&#xff08;docker项目&#xff09;发现报这个错误。 提出假设&#xff1a; 1&#xff0c;配置文件错误&#xff08;工程需要配置的东西比较多&#xff09; 之后开始一一排查&#xff0c;发现配置有问题&#xff0c;但是不是这个错误…

Blazor 混合开发_MAUI+Vue_WPF+Vue

MAUI&#xff0b;Vue 混合开发 背景混合开发的核心为什么必须使用 wwwroot 文件夹放置 Web 项目文件 创建 MAUI 项目创建 wwwroot 文件夹服务注册创建 _import.razor添加 Main.razor 组件修改 MainPage.xaml 文件 创建 WPF 项目创建 wwwroot 文件夹服务注册创建 _import.razor添…

DALL-E:Zero-Shot Text-to-Image Generation

DALL-E 论文是一个文本生成图片模型。 训练分为两个阶段 第一阶段&#xff0c;训练一个dVAE&#xff08;discrete variational autoencoder离散变分自动编码器&#xff09;&#xff0c;其将256 x 256的RGB图片转换为32 x 32的图片token。目的&#xff1a;降低图片的分辨率。图…

12 Vue3中使用v-if指令实现条件渲染

概述 v-if指令主要用来实现条件渲染&#xff0c;在实际项目中使用得也非常多。 v-if通常会配合v-else-if、v-else指令一起使用&#xff0c;可以达到多个条件执行一个&#xff0c;两个条件执行一个&#xff0c;满足一个条件执行等多种场景。 下面&#xff0c;我们分别演示这三…

最新国内AI绘画Midjourney绘画提示词Prompt分享

一、Midjourney绘画工具 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭…

Linux的常用命令及用法案例

概述 Linux 文件与目录结构 Linux 文件 Linux 系统中一切皆文件。 Linux 目录结构 VI/VIM 编辑器 是什么 VI 是 Unix 操作系统和类 Unix 操作系统中最通用的文本编辑器。 VIM 编辑器是从 VI 发展出来的一个性能更强大的文本编辑器。可以主动的以字体颜 色辨别语法的正确性…

GrayLog日志平台的基本使用-docker容器日志接入

1、/etc/docker/daemon.json中加入如下配置并重启服务 [rootlocalhost src]# cat /etc/docker/daemon.json { "registry-mirrors": ["https://dhq9bx4f.mirror.aliyuncs.com"], "log-driver": "gelf", "log-opts":…

Vue2+Vue3组件间通信方式汇总(2)------$emit

组件间通信方式是前端必不可少的知识点&#xff0c;前端开发经常会遇到组件间通信的情况&#xff0c;而且也是前端开发面试常问的知识点之一。接下来开始组件间通信方式第二弹------$emit,并讲讲分别在Vue2、Vue3中的表现。 Vue2Vue3组件间通信方式汇总&#xff08;1&#xff0…

linux基础06—windows下打不开wsl的ubuntu的子系统

一小时前还好好的&#xff0c;然后就打不开了&#xff0c;显示如下白板&#xff1a; &#xff08;1&#xff09;检查wsl 命令行输入&#xff1a;wsl -l -v 看是否有反应&#xff0c;如下所示&#xff1a; ctrlc退出&#xff0c;如果问题是控制流保护(Control Flow Guard)导致的…

WT2605C音频蓝牙语音芯片:单芯片实现蓝牙+MP3+BLE+电话本多功能应用

在当今的电子产品领域&#xff0c;多功能、高集成度成为了一种趋势。各种产品都需要具备多种功能&#xff0c;以满足用户多样化的需求。针对这一市场趋势&#xff0c;唯创知音推出了一款集成了蓝牙、MP3播放、BLE和电话本功能的音频蓝牙语音芯片——WT2605C&#xff0c;实现了单…

多用户商城系统哪个好,我的B2B2C电商系统选型之路

选择适合自己的B2B2C电商系统需要考虑多个因素&#xff0c;包括系统功能、易用性、扩展性、安全性和成本等。以下是一些常见的多用户商城系统供您参考&#xff1a; 1. 商淘云 基本情况&#xff1a;广州商淘信息科技有限公司旗下品牌&#xff0c;这家起步过程在国内商户中算比较…

Zabbix监控原理概括

一、zabbix工作流程 zabbix监控是将zabbix客户端要安装在被监控设备上负责收集数据&#xff0c;并将数据发送给zabbix服务端&#xff0c;将zabbix客户端接收或采集的数据存储在数据库中。 zabbix的数据收集分为两种模式&#xff1a; 1、主动模式 zabbix客户端主动向zabbix …

Arduino中LCD1602液晶显示器使用

目录 一、LCD160中字符串和计数显示 1、硬件介绍 2、引脚和接线说明 3、代码 二、LCD1602中显示超声波测量距离 1、硬件介绍 2、测试效果 3、代码 一、LCD160中字符串和计数显示 1、硬件介绍 1602液晶显示器 2、引脚和接线说明 单片机和测试模块接线 3、代码 //添加…

【clickhouse】在CentOS中离线安装clickhouse

一、下载地址 通过以下链接进行rpm安装包的下载 https://packages.clickhouse.com/rpm/stable/ 根据需求下载对应版本 注意&#xff1a;ClickHouse 20.8.2.3版本新增加了 MaterializeMySQL 的 database 引擎&#xff0c;该 database 能映射到 MySQL 中的某个 database&#…

如何恢复SD卡剪切丢失的照片

SD卡照片剪切丢失是常见问题&#xff0c;这通常是由于多种原因造成的。一方面&#xff0c;SD卡可能存在质量问题&#xff0c;如制造缺陷或使用了低质量的存储芯片。另一方面&#xff0c;错误的操作方式也可能导致照片丢失&#xff0c;如没有先进行备份就直接剪切照片&#xff0…

前端常用的开发工具

前端常用的开发工具&#x1f516; 文章目录 前端常用的开发工具&#x1f516;1. Snipaste--截图工具2. ScreenToGif--gif图片录制3. Typora--Markdown编辑器4. notepad--文本代码编辑器5. uTools--多功能工具6. EV录屏--录屏软件7. Xmind--思维导图8. Apifox -- 接口调试9. Tor…

vue element plus 管理系统路由菜单简要设计(后端获取菜单)

1 需求 管理系统“菜单”由后端接口返回&#xff0c;前端需要根据后端返回的“菜单”数组&#xff0c;构造路由&#xff0c;渲染侧栏菜单有些菜单是子菜单&#xff0c;有对应的路由&#xff0c;但是不在侧栏显示&#xff08;比如一些详情页面&#xff09; 注&#xff1a;这里的…