• 菜鸟学Kubernetes(K8s)系列——(二)关于Deployment、StatefulSet、DaemonSet、Job、CronJob


    Kubernetes(K8s)系列——(二)关于Deployment、StatefulSet、DaemonSet、Job、CronJob


    Kubernetes系列文章主要内容
    菜鸟学Kubernetes(K8s)系列——(一)关于Pod和Namespace通过本文你将学习到:
    (1)什么是Pod,为什么需要它、如何创建Pod、Pod的健康检查机制(三种探针)
    (2)什么是标签、标签选择器
    (3)什么是Namespace、他能做什么、如何创建它等等
    菜鸟学Kubernetes(K8s)系列——(三)关于Service、Ingress通过本文你将学习到:
    (1)什么是Service,如何创建它、它的服务发现能力、对外暴露方式
    (2)什么是NodePort类型的Service,它的使用方式、工作原理
    (3)什么是Ingress,如何创建它、为什么需要Ingress
    (4)什么的Ingress-Nginx、他和Nginx是什么关系、Ingress的各种功能
    (5)什么是headless服务等等
    菜鸟学Kubernetes(K8s)系列——(四)关于Volume卷(PV、PVC、StorageClass等)通过本文你将学习到:
    (1)什么是Volume卷、它的几种类型(emptyDir、hostPath、NFS)、这几种类型是使用方式
    (2)什么是PV-PVC、为什么要用他们、他们是怎么协作的、如何使用PV-PVC
    (3)动态配置持久卷是什么,它是怎么工作的、如何实现动态的分配PV等等
    菜鸟学Kubernetes(K8s)系列——(五)关于ConfigMap和Secret通过本文你将学习到:
    (1)什么是ConfigMap,如何创建它、它能用来做什么事情、在实战中怎么使用ConfigMap
    (2)什么是Secret,如何创建它,怎么使用它等等
    菜鸟学Kubernetes(K8s)系列——(七)关于Kubernetes底层工作原理通过本文你将学习到:
    (1)Kubernetes的核心组件:Etcd、Api-Server、Scheduler、Controller-Manager、Kubelet、Kube-proxy的工作方式,工作原理。
    (2)Kubernetes集群中核心组件的协作方式、运行原理。
    菜鸟学Kubernetes(K8s)系列——(番外)实现Deployment的动态扩缩容能力(HPA)通过本文你将学会实现Deployment的动态扩缩容能力(HPA)
    菜鸟学Kubernetes(K8s)系列——(番外)安装Ingress-Nginx(工作原理)通过本文你将学会安装Ingress-Nginx

    三、Deployment

    之前有个ReplicationController也可以实现扩缩容,但目前他已经过时了。现在通过Deployment+ReplicaSet来实现这一功能,而且功能更加强大,因为Deployment具有更加便捷的滚动更新能力、ReplicaSet可以实现更加复杂的标签选择器等特性。
    Deployment + ReplicaSet > ReplicationController

    实际上在开发中我们并不会直接手动的去创建Pod,而是创建一个Deployment这样的工作负载(还有其他的工作负载类型,比如StatefulSet、DaemonSet等,后面会一一提到),由他们来创建并管理实际的Pod。我们可能会想,为什么要用Deployment来管理Pod呢,直接创建Pod不香吗?其实真的不香,通过Deployment管理的Pod具有自愈能力、动态扩缩容能力、滚动升级能力。Deployment才是真香!(至于为什么香,感兴趣的可以自己去看看已经过时的ReplicationController的滚动升级的过程。)

    1、Deployment的自愈功能

    我们可以直接通过一个实验就能明白什么是自愈功能。

    1. 首先部署一个Deployment(这时就会创建一个或多个pod,可以通过命令kubectl get pod -owide查看这个pod部署在哪个节点上)
      在这里插入图片描述

    2. 然后在master上监听这些pod(命令:watch -n 1 kubectl get pod

    3. 接着在部署pod的节点上去删掉这个Pod中部署的容器(docker rm -f 容器id),模拟Pod内容器异常。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7xi2teCY-1657877161920)(Kubernetes_Blog.assets/image-20220715145717597.png)]

    4. 这时我们在查看Pod(kubectl get pod),会发现有一个Pod处于容器创建的状态,因为我们刚刚删除了这个Pod中的容器,所以他现在在重新创建这个Pod中的容器。

      在这里插入图片描述

      这就是Department的自愈能力,只要由Deployment管理的Pod内的容器出现了异常,那么他会重启这个容器。
      而如果是Pod被删除了,那么他会重新拉起一个Pod

      如果一个被单独创建的Pod被删除了是不会有人再重新拉起这个Pod的。

      在这里插入图片描述

      需要注意的是:自愈机制不能保证这个Pod还在当前机器上被重启!
      通过下面这个图我们再来看看由Deployment管理的Pod的自愈能力:
      在这里插入图片描述

    2、Deployment升级应用

    我们知道Pod中运行的是我们的应用程序,那如果我们对应用程序进行了修改,要怎么做到零停机的更新呢?Deployment可以实现这一操作。

    2.1 通过Yaml创建Deployment

    这里我们首先创建一个Deployment,部署一组Pod。

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name:  dep-v2  ### 遵循域名编写规范
      namespace: default
      labels:
        app: test-01
    ### 期望状态
    spec:
      replicas: 5   ### 期望副本你数量
      selector:   ### 选择器,指定Deployment要控制的所有的Pod的共同标签(即帮助Deployment筛选他要控制哪些Pod)
        matchLabels:
          pod-name: ppp    ### 和模板template里面的Pod的标签必须一样
      ### 编写Pod
      template:
        metadata:    ### Pod的metadata
          labels:
            pod-name: ppp
        spec:
          containers:
          - name: nginx-01
            image: nginx
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    部署:kubectl apply -f dep-v2.yaml --record (–record选项会记录历史版本号,后面会有用)

    2.2 Deployment和ReplicaSet、Pod的关系

    创建一个Deployment会产生三个资源:Deployment资源、ReplicaSet资源(RS只提供了副本数量的控制功能)、Pod资源。

    他们之间的关系是:Deployment控制RS、RS创建和管理Pod

    这里可能有一个疑问,为什么非得由Deployment来控制ReplicaSet呢?其实他是为了方便后面的滚动升级的过程。

    在这里插入图片描述
    在这里插入图片描述

    我们在创建一个Deployment后可以发现,由他产生的5个Pod名称中均包含一串额外的数字,这是什么?

    这个数字实际上对应Deployment和ReplicaSet中的Pod模板的哈希值。Pod是由ReplicaSet管理的,所以我们再看看ReplicaSet的样子

    在这里插入图片描述

    我们发现ReplicaSet的名称中也包含了其Pod模板的哈希值。后面会说到**同一个Deployment会创建多个ReplicaSet,用来对应和管理多组不同版本的Pod模板。**像这样使用Pod模板的哈希值,就可以让Deployment始终对给定版本的Pod模板创建相同的ReplicaSet。

    2.3 升级Deployment

    Deployment的升级策略有两种

    • 滚动更新(RollingUpdate),这是默认的升级策略

      该策略会逐渐地删除旧的Pod,与此同时创建新的Pod,使应用程序在整个升级过程中都处于可用状态,并确保其处理请求的能力没有因为升级而有所影响。

    • 重新创建(Recreate)

      该策略在会一次性删除所有旧版本的Pod,之后才开始创建新的Pod。如果你的应用程序不支持多个版本同时对外提供服务,需要在启动新版本之前完全停用旧版本,那么需要使用这种策略。但是使用这种策略的话,会导致应用程序出现短暂的不可用。

    有一点我们需要了解:仅当Deployment中定义的Pod模板(即 .spec.template )发生改变时,那么Deployment会使用更新后的Pod模板来创建新的实例,同时会删除旧的Pod。

    • 如果仅仅是修改Deployment的副本数等是不会触发Deployment的升级动作的。
    • 每次滚动升级后产生的新的Pod是会由一个新的RS进行控制,所以一个Deployment可能会对应多个RS。而旧的RS仍然会被保留,这个旧的RS会在下面回滚的时候被用到!

    2.4 回滚Deployment

    比如我使用V1版本的镜像部署了第一版本的Deployment,然后再对其进行升级,改为V2版本的镜像。但是这时V2版本的镜像存在一个BUG,这个BUG还不是那么好改。为了不让用户感知到升级导致的内部服务器错误,尽快修复问题。我们可以采用回滚,让他先回滚到V1版本,正常为用户提供服务,然后我们再下来慢慢修改V2版本,修改完后重新升级Deployment。

    回滚命令(默认回滚到上一个版本)

    kubectl rollout undo deployment dep-v2

    查看滚动升级历史

    kubectl rollout history deployment dep-v2

    注意,我们前面在部署Deployment时在命令后面加了个 --record 参数,这样会在版本历史中的CHANGE-CAUSE栏记录部署信息,加上这个 --record参数主要是为了方便用户辨别每次版本做了哪些修改。

    在这里插入图片描述

    回滚到指定的Deployment版本

    通过在undo命令中指定一个特定的版本号,便可以回滚到特定的版本。

    例如,想要回滚到第一个版本: kubectl rollout undo deployment dep-v2 --to-revision=1

    2.5 滚动更新的原理

    创建新的RS,准备就绪后,替换旧的RS,最后旧的RS会被保留!
    在这里插入图片描述

    回滚升级之所以这么快能完成,主要是因为Deployment始终保持着升级的版本历史记录。这个历史版本号实际上是保存在ReplicaSet中的(每个RS都用特定的版本号来保存Deployment不同版本的完整信息),滚动升级成功后,老版本的ReplicaSet也不会被删掉,这也使得回滚操作可以回滚到任何一个历史版本,而不仅仅是上一个版本。

    我们可以通过指定Deployment的revisionHistoryLimit属性来限制一个Deployment所能保存的ReplicaSet(历史版本)数量。

    2.6 Deployment升级的一些属性

    • spec.strategy:指定新Pod替换旧Pod的策略

      • spec.strategy.type:指定替换策略,有两个属性值

        • Recreate:重新创建。将之前的Pod直接先杀死再重新创建Pod(这种方式不推荐)
        • RollingUpdate:滚动更新。如果选择的这个属性,则可以通过spec.strategy.rollingUpdate指定滚动更新策略
      • spec.strategy.rollingUpdate:指定滚动更新速率

        下面这两个属性用于控制Deployment的滚动升级的速率。他们可以决定在Deployment滚动升级期间一起可以替换多少个Pod。

        • spec.strategy.rollingUpdate.maxSurge:指定除Deployment期望的副本数外,最多允许创建超出的Pod的数量,可以指定百分比,也可以指定数字
        • spec.strategy.rollingUpdate.maxUnavailable:指定在滚动升级期间,最多可以允许有多少个Pod不可用(反过来说就是保证集群中至少有多少Pod是可以处理请求的)

      示例:

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name:  mydeploy
        namespace: default
        labels:
          dep:  test
      spec:
        strategy:
          type: RollingUpdate  ### 滚动更新
          rollingUpdate:  ### 下面这个设置的含义是:在滚动更新时,新创建的Pod数量最多为副本数量的20%个,杀死的Pod数量最多不能超过2个
            maxUnavailable: 2
            maxSurge: 20%
        replicas: 10
        selector: 
          matchLabels: 
            pod-name:  zaq  ### 和模板template里面的pod的标签必须一样
        template:
          metadata: 
            labels:
              pod-name:  zaq
          spec:
            containers:
            - name:  nginx-test
              image:  nginx
      
      • 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

    3、Deployment怎么关联Pod?

    我们前面也提到了Deployment不直接管理Pod,而是间接的通过ReplicaSet来管理Pod。那么ReplicaSet是怎么知道哪些Pod是需要他来进行管理的呢?

    答:他通过标签选择器来选择具有特定标签的Pod

    3.1 ReplicaSet的更富表达力的标签选择器

    由于创建一个Deployment会默认创建一个RS,所以这里直接创建一个Deployment,在这个Deployment中我们可以指定标签选择器。

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: test
      namespace: default
      labels:
        test: zaq
    spec:
      selector:
        matchExpressions:   ### 注意这里是匹配表达式而不是匹配标签
        - key: test        ### 该选择器要求该Pod包含名为 test 的标签
          operator: In
          values:
          - zaq                        ### 标签的值必须是zaq
      replicas: 2
      template:
        metadata:
          labels:
            test: zaq
        spec:
          containers:
          - name: nginx
            image: nginx
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    我们发现,新的标签选择器(matchExpressions)可以采用额外的表达式(这是ReplicationController所不能实现的)。

    operator的四个值:

    • In:上面key的值必须与下面其中一个指定的values匹配。

    • NotIn:上面key的值与下面任何指定的values不匹配。

    • Exists:Pod必须包含一个指定名称的标签(这里指的是必须要有指定名称的key,values值是什么不重要),使用此运算符时,不应指定values字段。

      matchExpressions:  
      - key: test       
        operator: Exists
      
      • 1
      • 2
      • 3
    • DoesNotExist:Pod不得包含指定名称的标签。values属性不得指定。

    如果指定了多个表达式,则所有这些表达式都必须为true才能使选择器与Pod匹配。如果同时指定matchLabels和matchExpressions,则所有标签都必须匹配,并且所有表达式必须计算为true以使该Pod与选择器匹配。

    4、动态扩缩容(HPA)

    动态扩缩容可以戳这里!>>>

    5、蓝绿部署

    蓝绿部署:即系统存在两个版本,一个绿版本(正常),一个蓝版本(等待验证的版本)。如果蓝版本经过反复的测试、修改、验证,确定达到上线标准之后,则可以将流量请求切换到蓝版本,绿版本不接受请求,但是还依然存在。如果蓝版本在生产环境出现了问题,则可以立刻将请求转为绿版本。当确定蓝版本可以稳定正常运行时,就可以将原来的绿版本进行销毁,释放资源,然后蓝版本变为绿版本。(注意,蓝绿版本的转换只是我们人为的这么定义的,而并不是说需要配置什么东西才能实现他们角色的转换。)

    在这里插入图片描述

    6、金丝雀部署

    金丝雀部署

    金丝雀对瓦斯这种气体十分敏感。空气中哪怕有极其微量的瓦斯,金丝雀也会停止歌唱;而当瓦斯含量超过一定限度时,虽然鲁钝的人类毫无察觉,金丝雀却早已毒发身亡。当时在采矿设备相对简陋的条件下,工人们每次下井都会带上一只金丝雀作为“瓦斯检测指标”,以便在危险状况下紧急撤离。

    金丝雀部署的意义在于,同时存在两个版本V1和V2,V1版本和V2版本都能接收到流量请求,但是V2刚开始只能接收到少量的请求,所以这时候V2版本也不需要部署到多台机器上。当到达V2版本的请求都能成功处理了,他不存在任何BUG了,那逐步开始增加V2版本的部署,移除V1版本的部署,让更多的流量请求来到V2版本。直到彻底消除V1版本。

    在这里插入图片描述

    蓝绿部署和金丝雀部署的区别:

    蓝绿部署的方式两个版本同时存在,但是流量只会发送到一个版本上,而金丝雀部署的方式,两个版本同时存在,流量请求也会发送到这两个版本上。

    注意:金丝雀部署和滚动发布是不一样的。

    滚动发布的缺点?

    • 他和金丝雀部署的相同点是,滚动发布也同时存在两个版本,都能接收流量。但是他没法控制流量

    • 滚动发布短时间就直接结束,不能直接控制新老版本的存活时间。

    6.1 金丝雀部署案例

    这里涉及到了Service的概念,可以学完Service再来看这里。

    • 准备一个Service

      apiVersion: v1
      kind: Service
      metadata:
        name: canary-test
        namespace: default
      spec:
        selector:
          app: canary-nginx
        type: NodePort
        ports:
        - name: canary-test
          port: 80   
          targetPort: 80   ### 指Pod的访问端口
          protocol: TCP
          nodePort: 8888   ### 机器上开的端口,客户端访问的就是这个端口
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    • 准备版本v1的Deployment

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: canary-dep-v1
        namespace: default
        labels:
          app: canary-dep-v1
      spec:
        selector:
          matchLabels:
            app: canary-nginx
            version: v1
        replicas: 3
        template:
          metadata:
            labels:
              app: canary-nginx
              version: v1
          spec:
            containers:
            - name: nginx
              image: registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/nginx-test:env-msg   ### 这个版本的nginx访问页会输出 1111111
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
    • 准备版本v2的Deployment

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: canary-dep-v2
        namespace: default
        labels:
          app: canary-dep-v2
      spec:
        selector:
          matchLabels:
            app: canary-nginx
            version: v2
        replicas: 1   # 先让v2版本只有一个副本
        template:
          metadata:
            labels:
              app: canary-nginx
              version: v2
          spec:
            containers:
            - name: nginx
              image: nginx   ### v2版本使用默认的nginx,主要是为了打印出默认页面,和v1版本的做区分罢了
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
    • kubectl apply -f 上面的资源后,接下来访问:主机ip + 8888,我们可以发现3/4的请求会分配到v1版本。

    • 这时候我们再调大v2版本的副本数。

    • 我们发现v2版本基本上稳定了,没什么问题,这时候就可以删除v1版本:kubectl delete -f canary-dev-v1.yaml

    • 这时候v2版本就 上线成功了!

    • 这些复杂的步骤到时候会放在DevOps流水线中自动完成的

    四、StatefulSet

    学习此处需要先了解Volume和Service的概念,所以之后回过来再看这里。

    五、DaemonSet

    Deployment用于在Kubernetes集群中部署特定数量的Pod,而且他还不能保证把Pod部署到特定节点上。如果我们现在需要在集群中的所有节点上都仅部署一个Pod,怎么办?

    1、使用DaemonSet在每个节点上部署一个Pod

    DaemonSet控制器的作用就是确保所有的(或者一部分)节点都运行了一个指定的Pod副本。

    上面提到一部分,是通过pod模板中的nodeSelector属性指定的。

    • 它不存在副本数的概念。因为他的工作是确保一个Pod在每个节点(或特定节点)都存在一份
    • 如果一个节点中通过DaemonSet部署的Pod被删除了,那么他会在该节点上重启一个新的Pod。
    • 如果某个节点宕机,DaemonSet不会在其他机器上重新部署一个新的Pod。
    • 当一个新的节点被加入到集群中,DaemonSet会立刻部署一个新的Pod到这个新加入的节点上。

    2、DaemonSet 的典型使用场景有:

    3、使用DaemonSet只在特定的节点上运行Pod

    DaemonSet将Pod部署到集群中的所有节点上,除非指定这些Pod只在部分节点上运行。这是通过Pod模板中的nodeSelector属性指定的。

    创建一个简单的DaemonSet

    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      name: ssd-monitor
    spec:
      selector:
        matchLabels:
          app: ssd-monitor
      template:
        metadata:
          labels:
            app: ssd-monitor
        spec:
          nodeSelector:     ### 表示只会在有disk:ssd的节点上部署该Pod
            disk: ssd
          containers:
          - name: main
            image: luksa/ssd-monitor
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    kubectl apply -f test-daemonset.yaml

    上面DaemonSet描述的意思是:该DaemonSet只会在标有disk:ssd标签的节点上部署一个Pod。

    在这里插入图片描述

    如果我们这时候再给Node2节点打上disk=app标签,那么这时就会在Node2上启动一个Pod。
    如果我们这时候将Node3节点上的disk=app标签移除,那么这时Node3节点上的Pod就会被删除。

    六、Job、CronJob

    前面,通过Deployment、StatefulSet、DaemonSet创建的Pod都是一个持续运行的Pod,如果我们遇到一个只想运行完工作后就终止的情况怎么办?

    1、Job

    Kubernetes中的 Job 对象将创建一个或多个 Pod,并确保指定数量的 Pod 可以成功执行到进程正常结束:

    • 当 Job 创建的 Pod 执行成功并正常结束时,Job 将记录成功结束的 Pod 数量
    • 当成功结束的 Pod 达到指定的数量时,Job 将完成执行
    • 删除 Job 对象时,将清理掉由 Job 创建的 Pod
    • 在节点发送故障时,该节点上由Job管理的Pod将会被在其他的节点上被重新拉起。

    1.1 一个简单的Job

    apiVersion: batch/v1
    kind: Job
    metadata:
      name: pi
    spec:
      completions: 4   ### 期望Pod成功运行的次数
      template:
        spec:
          containers:
          - name: pi
            image: perl   ### 注意:用于执行job的镜像都必须是非阻塞的,也就是容器启动后执行完会自己退出(如果用了nginx镜像,那么他会在执行第一次时就一直阻塞着)
            command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
          restartPolicy: Never #Job情况下,不支持Always
      backoffLimit: 4 #任务4次都没成功就认为Job是失败的
      activeDeadlineSeconds: 10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这里需要特别说明的是:在一个Pod的定义中,可以指定在容器中运行的进程结束时,K8s会做什么,这是通过Pod配置的属性restartPolicy完成的,默认是Always。Job的Pod是不能使用这个默认策略的,因为他们并不是要无限期地运行。所以,需要明确指定Job的Pod的restartPolicy为OnFailure或Never。

    2、CronJob

    CronJob 按照预定的时间计划(schedule)创建 Job

    一个 CronJob 在时间计划中的每次执行时刻,都创建 大约 一个 Job 对象。这里用到了 大约 ,是因为在少数情况下会创建两个 Job 对象,或者不创建 Job 对象。尽管 K8S 尽最大的可能性避免这种情况的出现,但是并不能完全杜绝此现象的发生。因此,Job 程序必须是幂等的。

    当以下两个条件都满足时,Job 将至少运行一次:

    • startingDeadlineSeconds 被设置为一个较大的值,或者不设置该值(默认值将被采纳)
    • concurrencyPolicy 被设置为 Allow

    2.1 一个简单的CronJob

    apiVersion: batch/v1beta1
    kind: CronJob
    metadata:
      name: hello
    spec:
      schedule: "*/1 * * * *"    #分、时、日、月、周。他是基于 master 所在时区来进行计算的。
      jobTemplate:
        spec:
          template:
            spec:
              containers:
              - name: hello
                image: busybox
                args:
                - /bin/sh
                - -c
                - date; echo Hello from the Kubernetes cluster
              restartPolicy: OnFailure
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    未完,待续>>>

    参考:Kubernetes in Action

  • 相关阅读:
    代码随想录算法训练营第53天|1143. 最长公共子序列,1035. 不相交的线,53. 最大子数组和
    RHCSA之Linux基础
    1.Linux蓝牙基础
    Unity UI Toolkit学习笔记-USS
    记录一次在欧拉(openEuler22.03LTS-SP4)系统下安装(踩坑)Freeswitch1.10.11的全过程
    nginx配置参数详细解析
    Vue.js入门教程(六)
    CSS变量之var()函数的应用——动态修改样式 & root的使用
    java计算机毕业设计高校多媒体设备报修管理系统MyBatis+系统+LW文档+源码+调试部署
    ArcGIS:如何在地理数据库中创建关系类
  • 原文地址:https://blog.csdn.net/ysf15609260848/article/details/125809203