Kubernetes调度与服务暴露:从“定时任务”到“服务发现”的完全指南

Kubernetes调度与服务暴露:从"定时任务"到"服务发现"的完全指南

引言:让集群"记住"该做什么,并让外部"找到"该访问哪里

从Linux cron到K8s CronJob:定时任务的"进化论"

想象一下你需要在每天凌晨2点备份数据库,或者在每小时第15分钟清理日志。在Linux服务器上,我们用cron来设置这些周期性的任务。在Kubernetes集群中,同样需要这样的"定时器"------这就是CronJob

专业视角:CronJob是Kubernetes的工作负载API的一部分,用于创建和管理基于时间的、周期性的Job(即一次性任务)。它解决了容器化环境中自动化运维的定时需求,例如定期备份、报表生成、数据清理等。

从Pod直接访问到Service:服务暴露的"智慧门牌"

当你运行一组Pod提供Web服务时,这些Pod的IP地址会随着重启、扩缩容而变化。客户端如何稳定地访问它们?就像一栋大楼里有很多房间,每个房间的号码可能会变,但大楼的前台(Service)有一个固定的总机号码,你只要拨打总机,前台就会帮你转接到正确的房间。

专业视角:Service是Kubernetes中一种抽象,它定义了一组Pod的逻辑集合以及访问它们的策略。Service提供了一个固定的虚拟IP和DNS名称,作为前端,后端Pod的变化对客户端完全透明。


第一部分:CronJob------Kubernetes的"定时器"

CronJob是什么?为什么需要它?

类比 :Linux中有cron程序定时执行任务(比如每天凌晨运行备份脚本),Kubernetes的CronJob提供了完全类似的功能,只不过它调度的是Job(一次性Pod),而不是Shell命令。

核心特性

  • 基于标准的Cron时间表语法
  • 自动创建和管理Job对象
  • 支持并发控制(允许、禁止、替换)
  • 可配置成功/失败历史保留数量

命名限制:CronJob的名称必须是一个合法的DNS子域值,且不超过52个字符。因为CronJob控制器会自动在名称后追加11个字符(用于生成Job名称),而Job名称总长度不能超过63个字符。

快速上手:创建第一个CronJob

使用kubectl命令创建

bash

复制代码
# 最简单的例子:每2分钟输出一行文字
[root@master30 ~]# kubectl create cronjob mycronjob \
    --image=busybox \
    --schedule='*/2 * * * *' \
    -- echo hello k8s job!

等待几分钟,你会看到自动生成的Pod和Job:

bash

复制代码
[root@master30 ~]# kubectl get all
NAME                           READY   STATUS      RESTARTS   AGE
pod/mycronjob-28301160-gvm2v   0/1     Completed   0          5m18s
pod/mycronjob-28301162-25jll   0/1     Completed   0          3m18s
pod/mycronjob-28301164-h7mf7   0/1     Completed   0          78s

NAME                      SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronjob.batch/mycronjob   */2 * * * *   False     0        78s             36m

NAME                           COMPLETIONS   DURATION   AGE
job.batch/mycronjob-28301160   1/1           18s        5m18s
job.batch/mycronjob-28301162   1/1           19s        3m18s
job.batch/mycronjob-28301164   1/1           19s        78s
等效的YAML配置文件

yaml

复制代码
apiVersion: batch/v1beta1   # 注意:v1版本中为batch/v1
kind: CronJob
metadata:
  name: mycronjob
spec:
  schedule: "*/2 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            command: ["echo", "hello k8s job!"]
          restartPolicy: Never

专业解释

  • apiVersion:CronJob的API版本,在Kubernetes 1.21+为batch/v1,本文示例使用旧版batch/v1beta1
  • spec.schedule:Cron时间表达式,定义了触发频率。
  • spec.jobTemplate:Job模板,定义了每次触发时要创建的任务规格。
  • restartPolicy: Never:Job的Pod完成运行后不重启。

CronJob核心参数详解

1. 时间表语法(标准Cron)

text

复制代码
# ┌───────────── 分钟 (0 - 59)
# │ ┌───────────── 小时 (0 - 23)
# │ │ ┌───────────── 月的某天 (1 - 31)
# │ │ │ ┌───────────── 月份 (1 - 12)
# │ │ │ │ ┌───────────── 周的某天 (0 - 6)(周日到周六)
# │ │ │ │ │
# * * * * *

常用示例:

表达式 含义
*/5 * * * * 每5分钟执行一次
0 2 * * * 每天凌晨2点执行
0 9 * * 1 每周一上午9点执行
0 0 1 * * 每月1号午夜执行
0 0 1 1 * 每年1月1日午夜执行
2. 任务模板(jobTemplate)

与标准的Job定义完全一致,包含Pod模板、并行策略、完成计数等。你可以为模板化的Job指定通用的元数据(标签、注解)。

3. 启动截止期限(startingDeadlineSeconds)

概念:如果任务由于某种原因错过了预期的调度时间,该参数定义了可以延迟启动的最大秒数。超过这个时间,本次调度将被跳过(视为失败),而未来的调度不受影响。

典型场景:你设置了一个每天凌晨2点的备份任务,允许最多延迟30分钟。如果凌晨2点时控制器因故没有创建Job,那么只要在2:30之前创建,它仍会执行;超过2:30,本次备份被跳过,等待第二天。

配置方式

yaml

复制代码
spec:
  startingDeadlineSeconds: 200   # 最多延迟200秒

如果不设置,则没有最后期限,控制器会尽可能补执行错过的任务(但可能引发大量积压)。

4. 并发策略(concurrencyPolicy)

定义了当上一个任务尚未完成,新的调度时间又到来时的处理方式。

行为 适用场景
Allow(默认) 允许并发执行,新旧任务同时运行 任务可并发且资源充足
Forbid 禁止并发,新任务跳过,等待下一次调度 任务互斥(如操作同一文件)
Replace 终止当前正在运行的任务,用新任务替换 只关心最新状态的任务

注意:并发规则仅适用于同一个CronJob创建的任务。不同CronJob的任务总是允许并发。

5. 历史限制(successfulJobsHistoryLimit / failedJobsHistoryLimit)
  • successfulJobsHistoryLimit:保留的成功完成Job的数量(默认3)
  • failedJobsHistoryLimit:保留的失败Job的数量(默认1)

设置为0表示不保留对应类型的已完成Job。这些限制有助于防止大量历史Job堆积占用API资源。

综合案例

案例1:定期清理主机目录内容

需求 :每分钟清空worker31节点上的/var/data目录。

思路

  • 使用hostPath卷将主机目录挂载到Pod中
  • 通过nodeName将Pod调度到指定节点
  • Pod执行rm -fr /var/data/*命令
  • 用CronJob每分钟触发一次

yaml

复制代码
apiVersion: batch/v1
kind: CronJob
metadata:
  name: clean
spec:
  schedule: "* * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          nodeName: worker31.whisky.cloud   # 固定到某个节点
          containers:
          - name: clean
            image: busybox
            command:
            - sh
            - -c
            - sleep 3 && rm -fr /var/data/*
            volumeMounts:
            - name: data
              mountPath: /var/data
          volumes:
          - name: data
            hostPath:
              path: /var/data
          restartPolicy: OnFailure

专业拓展 :生产环境不建议直接使用nodeName,而应使用nodeSelector或亲和性。此外,可以结合securityContext设置权限提升。

案例2:通过CronJob操作Kubernetes集群

需求 :每分钟删除namespace controller中标签为app=hello的所有Pod。

思路

  • 使用bitnami/kubectl镜像(内含kubectl客户端)
  • 将当前集群的kubeconfig通过ConfigMap挂载到Pod中
  • Pod执行kubectl delete pods -l app=hello -n controller

yaml

复制代码
# 1. 创建ConfigMap保存kubeconfig
[root@master ~]# kubectl create cm kubeconfig --from-file=config=/root/.kube/config

# 2. 创建CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
  name: clean-pods
spec:
  schedule: "* * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          containers:
          - name: kubectl
            image: bitnami/kubectl:latest
            imagePullPolicy: IfNotPresent
            args:
            - delete
            - pods
            - -l
            - app=hello
            - -n
            - controller
            volumeMounts:
            - name: kubeconfig
              mountPath: "/.kube"
          volumes:
          - name: kubeconfig
            configMap:
              name: kubeconfig

安全提醒:生产环境中应使用RBAC和服务账户代替直接挂载管理员kubeconfig。可以创建一个具有特定权限的ServiceAccount,并授予其删除Pod的权限,然后将该ServiceAccount关联到CronJob的Pod。


第二部分:Service------稳定访问Pod的"智能代理"

为什么需要Service?

Pod的生命周期是短暂的,它们可以被创建、销毁、重新调度。每次重启或重新调度,Pod的IP地址都可能改变。如果客户端直接访问Pod IP,那么当Pod发生变化时,客户端就需要不断更新地址------这显然不可行。

类比:Pod就像餐馆后厨的厨师,Service就是前台的接待员。顾客(客户端)只和接待员说话(统一的电话号码),接待员再把请求转给某个空闲的厨师。不管厨师怎么换班,顾客永远只需拨打同一个号码。

Service的核心功能

  1. 固定访问入口:提供稳定的虚拟IP(ClusterIP)和DNS名称。
  2. 负载均衡:将流入的请求分发到后端多个Pod。
  3. 服务发现:后端Pod的变化(扩缩容、更新)对客户端完全透明。

快速体验:从Pod直接暴露到使用Service

场景1:Pod直接使用hostPort(不推荐)

yaml

复制代码
# pod-web.yml
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: web
  name: web
spec:
  containers:
  - image: httpd
    name: web
    ports:
    - containerPort: 80
      hostPort: 8080   # 将容器端口映射到节点端口

问题

  • 客户端必须知道Pod所在节点的IP和端口(http://worker31:8080
  • 当Pod被删除或迁移时,客户端无法自动更新
  • 无法在同一节点上运行多个相同端口的Pod(端口冲突)
场景2:Deployment + hostPort(更糟)

bash

复制代码
kubectl create deployment web --image=httpd --replicas=4
# 如果为每个Pod都设置hostPort:8080,只有2个Pod能启动(另外2个Pending)
# 因为每个节点只能有一个Pod占用8080端口
场景3:引入Service(正确做法)

bash

复制代码
# 创建Deployment
kubectl create deployment web --image=httpd --replicas=3

# 创建Service(ClusterIP类型)
kubectl expose deployment web --port=8080 --target-port=80

现在,你可以通过Service的固定IP(如10.103.19.150:8080)访问,后端Pod的变化完全不可见。

Service基本管理

创建Service的方式

方式1:kubectl expose命令

bash

复制代码
kubectl expose deployment web --port=8080 --target-port=80

方式2:kubectl create service命令

bash

复制代码
kubectl create service clusterip web --tcp=8080:80

方式3:YAML文件

yaml

复制代码
apiVersion: v1
kind: Service
metadata:
  name: web
  labels:
    app: web
spec:
  ports:
  - port: 8080        # Service监听的端口
    protocol: TCP
    targetPort: 80    # 转发到Pod的端口
  selector:
    app: web          # 选择具有此标签的Pod
  type: ClusterIP
验证Service的负载均衡

bash

复制代码
# 获取Pod名称
kubectl get pods -l app=web -o name

# 修改每个Pod的首页内容,显示Pod名称
for pod in $(kubectl get pods -l app=web -o name | cut -d/ -f2); do
  kubectl exec $pod -- sh -c "echo $pod > /usr/local/apache2/htdocs/index.html"
done

# 多次访问Service,观察流量分发
for i in {1..30}; do curl -s 10.103.19.150:8080; done | sort | uniq -c
# 输出示例:三个Pod的访问次数大致相等
Service如何匹配Pod?

Service通过标签选择器(selector) 来动态确定后端Pod集合。只要Pod的标签与Service的selector匹配(无论该Pod是由Deployment创建、手动创建,还是其他控制器创建),Service就会自动将其纳入负载均衡池。

bash

复制代码
# 手动创建一个具有相同标签的Pod
kubectl run web-extra --image=httpd --labels="app=web"

# 现在Service的后端Pod数量变为4个

Service发现:集群内如何访问Service

Kubernetes为集群内的Pod提供了三种发现Service的方式:

1. 通过ClusterIP直接访问

每个Service被分配一个虚拟IP(ClusterIP),集群内的任意Pod都可以直接访问该IP。

bash

复制代码
# 在其他Pod中
curl http://10.103.19.150:8080
2. 通过环境变量访问

当Pod启动时,kubelet会为每个活跃的Service设置一系列环境变量。格式为:

  • {SVCNAME}_SERVICE_HOST
  • {SVCNAME}_SERVICE_PORT

示例:

bash

复制代码
# 启动一个busybox Pod并查看环境变量
kubectl run test --rm -it --image=busybox -- sh
/ # env | grep WEB
WEB_SERVICE_HOST=10.103.19.150
WEB_SERVICE_PORT=8080

限制 :环境变量只有在Pod创建之后创建的Service才不会被注入。因此,推荐使用DNS发现。

3. 通过DNS名称访问(推荐)

Kubernetes集群内置了CoreDNS,为每个Service创建DNS记录。记录格式为:
<service-name>.<namespace>.svc.cluster.local

同一命名空间内可简写为<service-name>

bash

复制代码
# 在任意Pod中
wget -qO- http://web.service:8080   # 跨命名空间
wget -qO- http://web:8080            # 同命名空间

示例 :部署WordPress+MySQL,WordPress使用DNS名称mysql连接数据库,而无需关心MySQL Service的实际IP。

Service类型详解

Kubernetes提供四种Service类型,以适应不同的访问场景。

类型 访问范围 典型用途
ClusterIP 仅集群内部 内部微服务通信
NodePort 集群外部通过节点IP+静态端口 开发测试、简单暴露
LoadBalancer 外部通过云负载均衡器IP 生产环境外部访问
ExternalName 映射到外部域名 访问外部服务(如数据库)
1. ClusterIP(默认)
  • 分配一个内部虚拟IP,仅在集群内可访问。
  • 是其他Service类型的基础。
2. NodePort
  • 在每个节点上开放一个固定端口(默认30000-32767)。
  • 外部客户端可以通过<任意节点IP>:<NodePort>访问Service。
  • 本质上是在ClusterIP基础上增加了节点端口映射。

yaml

复制代码
apiVersion: v1
kind: Service
metadata:
  name: web-nodeport
spec:
  type: NodePort
  ports:
  - port: 8080
    targetPort: 80
    nodePort: 30080   # 可选,不指定则自动分配
  selector:
    app: web

bash

复制代码
# 访问任一节点
curl http://10.1.8.30:30080
curl http://10.1.8.31:30080

iptables规则示意

text

复制代码
-A KUBE-NODEPORTS -p tcp --dport 30080 -j KUBE-SVC-XXXX
3. LoadBalancer
  • 在NodePort的基础上,向外部负载均衡器申请一个公网/私网IP。
  • 云提供商(AWS、GCP、Azure)或本地LB方案(如MetalLB)实现。
  • 外部客户端通过负载均衡器的IP直接访问。

使用MetalLB搭建本地LoadBalancer

bash

复制代码
# 部署MetalLB
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml

# 配置IP地址池
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
  - 10.1.8.40-10.1.8.80
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: example
  namespace: metallb-system
EOF

# 创建LoadBalancer类型的Service
kubectl expose deployment web --type=LoadBalancer --port=80 --target-port=80
kubectl get svc web
# NAME   TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
# web    LoadBalancer   10.109.101.67   10.1.8.40     80:32101/TCP   5s

流量路径

客户端 → MetalLB虚拟IP(10.1.8.40) → 节点kube-proxy → Service负载均衡 → Pod

4. ExternalName
  • 将Service映射到外部的DNS名称,不创建任何代理。
  • 集群内部Pod可以通过Service名称访问外部服务。

yaml

复制代码
apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  type: ExternalName
  externalName: database.example.com

当Pod解析external-db时,CoreDNS返回database.example.com的CNAME记录。

5. Headless Service
  • 设置clusterIP: None,不分配虚拟IP。
  • 用于需要直接访问每个Pod的场景(如StatefulSet)。
  • 核心DNS会返回Pod IP列表(A记录),而不是Service IP。

yaml

复制代码
apiVersion: v1
kind: Service
metadata:
  name: headless-svc
spec:
  clusterIP: None
  selector:
    app: myapp
  ports:
  - port: 80

Service高级特性

会话保持(Session Affinity)

问题:某些应用需要将同一个客户端的请求始终转发到同一个后端Pod(如购物车、WebSocket)。

解决方案 :设置sessionAffinity: ClientIP

bash

复制代码
# 修改现有Service
kubectl patch svc web -p '{"spec":{"sessionAffinity":"ClientIP"}}'

# 可选:设置超时时间(默认10800秒=3小时)
kubectl patch svc web -p '{"spec":{"sessionAffinityConfig":{"clientIP":{"timeoutSeconds":3600}}}}'

原理:kube-proxy根据客户端IP的哈希值选择后端Pod,相同IP的请求始终映射到同一个Pod。

注意:会话保持与后端Pod扩缩容不兼容------如果目标Pod被删除,该客户端的会话将中断。

金丝雀发布(Canary Deployment)结合Service

利用Service的标签选择器,可以实现新旧版本按比例流量切换。

场景:将10%的流量导入新版本(canary),其余90%仍在旧版本(stable)。

实现步骤

  1. 创建旧版本Deployment(stable)和新版本Deployment(canary),它们有共同的标签(如app: web, tier: frontend)。
  2. 创建Service,selector为app: web, tier: frontend,这样Service会把流量同时分发给两个版本。
  3. 通过调整两个Deployment的副本数来控制流量比例。如果Service使用默认的轮询负载均衡,副本数比例≈流量比例。

yaml

复制代码
# 旧版本 Deployment (stable)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-stable
spec:
  replicas: 9
  selector:
    matchLabels:
      app: web
      tier: frontend
      track: stable
  template:
    metadata:
      labels:
        app: web
        tier: frontend
        track: stable
    spec:
      containers:
      - image: nginx:1.28
        name: nginx

---
# 新版本 Deployment (canary)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
      tier: frontend
      track: canary
  template:
    metadata:
      labels:
        app: web
        tier: frontend
        track: canary
    spec:
      containers:
      - image: nginx:1.29
        name: nginx

---
# Service
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
    tier: frontend
  ports:
  - port: 80

bash

复制代码
# 测试流量比例
for i in {1..100}; do curl -s http://10.98.235.36; done | sort | uniq -c
# 输出大致为:stable版本约90次,canary约10次

# 逐步扩大canary副本数,减少stable副本数
kubectl scale deployment web-stable --replicas 5
kubectl scale deployment web-canary --replicas 5
# 再次测试,流量接近1:1

最终,当canary版本验证无误后,可以删除stable Deployment,只保留canary,并修改其标签为track: stable


知识点速查手册

1. CronJob 速查表

字段 作用 示例值
schedule Cron时间表达式 "*/5 * * * *"
jobTemplate Job模板 嵌套Job定义
startingDeadlineSeconds 错过调度的最大延迟秒数 200
concurrencyPolicy 并发策略 Allow/Forbid/Replace
successfulJobsHistoryLimit 保留的成功Job数 3
failedJobsHistoryLimit 保留的失败Job数 1

常用Cron表达式

text

复制代码
*/1 * * * *      # 每分钟
0 */2 * * *      # 每2小时
0 0 * * 0        # 每周日0点
0 0 1 */3 *      # 每季度(每3个月的第1天)

2. Service 速查表

类型 ClusterIP 外部访问 使用场景
ClusterIP 自动分配 内部服务
NodePort 自动分配 是(节点IP:NodePort) 测试、简单暴露
LoadBalancer 自动分配 是(LB IP) 生产外部访问
ExternalName 否(DNS别名) 访问外部服务
Headless None 否(直接Pod IP) StatefulSet

Service YAML核心字段

yaml

复制代码
spec:
  selector:           # 标签选择器,指定后端Pod
    app: web
  ports:
  - port: 8080        # Service监听的端口
    targetPort: 80    # Pod容器端口
    nodePort: 30080   # NodePort类型专用
  type: ClusterIP     # 服务类型
  sessionAffinity: ClientIP   # 会话保持
  clusterIP: None     # Headless Service

3. 服务发现方式比较

方式 优点 缺点 推荐度
环境变量 简单,无需额外组件 顺序依赖,仅注入已存在的Service 不推荐
ClusterIP 稳定,负载均衡 需要知道IP,IP会变(除非固定) 一般
DNS(CoreDNS) 名称稳定,自动更新,跨命名空间 需要DNS组件(已默认部署) 强烈推荐

4. 常见故障排查

症状 可能原因 排查命令
CronJob不创建Job schedule错误或concurrencyPolicy=Forbid且有未完成Job kubectl describe cronjob <name>
Pod一直Pending NodePort端口冲突或资源不足 kubectl describe pod <name>
Service无法访问 selector不匹配后端Pod kubectl get endpoints <svc>
外部无法访问NodePort 防火墙未开放端口或节点IP不可达 curl <nodeIP>:<nodePort>
DNS解析失败 CoreDNS Pod未运行 `kubectl get pods -n kube-system

5. 最佳实践清单

CronJob
  • 为CronJob设置startingDeadlineSeconds避免任务积压
  • 根据任务性质选择合适的并发策略
  • 设置合理的历史限制,防止API资源浪费
  • 对于重要任务,配置Pod级别的资源限制
  • 使用ServiceAccount而非管理员kubeconfig操作集群
Service
  • 使用DNS名称而非ClusterIP进行服务发现
  • 为重要Service设置标签(便于监控和选择)
  • 使用targetPort命名端口(便于修改容器端口)
  • 生产环境使用LoadBalancer或Ingress暴露服务
  • 为有状态服务配置sessionAffinity,但要注意Pod生命周期

文档版本 :v1.0
最后更新 :2026年4月
适用平台 :Kubernetes 1.20+
作者:基于课堂笔记扩展完善

重要提醒

  1. CronJob的时区默认使用kube-controller-manager的时区(通常是UTC),如需自定义时区,可使用CRON_TZ变量(需特定版本支持)。
  2. Service的负载均衡基于kube-proxy模式(iptables/IPVS),默认是随机或轮询;大规模集群推荐使用IPVS模式。
  3. 金丝雀发布是持续交付的重要策略,结合Service可以实现更精细的流量控制(如基于Header的路由需要Ingress)。

下一步学习

  • 学习Ingress Controller实现7层路由和TLS终止
  • 研究Helm管理CronJob和Service的模板化部署
  • 了解Service Mesh(如Istio)提供更高级的流量管理能力

本回答由 AI 生成,内容仅供参考,请仔细甄别。

相关推荐
白晨并不是很能熬夜3 小时前
【RPC】第 4 篇:服务发现 — Zookeeper + 缓存容错
java·后端·程序人生·缓存·zookeeper·rpc·服务发现
勤劳的进取家3 小时前
应用层基础
运维·网络·学习
hahaha 1hhh3 小时前
中文乱码 ubuntu autodl
linux·运维·前端
计算机安禾4 小时前
【Linux从入门到精通】第37篇:NFS网络文件系统——无状态的数据共享
linux·网络·php
图码4 小时前
矩阵数据结构入门指南:声明、初始化与基本操作
运维·数据结构·线性代数·算法·矩阵
暴力求解4 小时前
Linux---保存信号
linux·运维·服务器·开发语言·操作系统
Bruce_Liuxiaowei4 小时前
CVE-2026-31431 (Copy Fail) 漏洞复现与验证记录
linux·安全·漏洞复现·cve-2026-31431
Cyber4K4 小时前
【Kubernetes专项】温故而知新,重温技术原理(6)
云原生·容器·kubernetes
bqq198610264 小时前
Ubuntu vs CentOS
linux·服务器