--- tags: [k8s, deployment, replicaset, devops] create time: 2026-07-07 15:48 --- # Deployment 与 ReplicaSet ## 概述 Deployment 是 Kubernetes 中最常用的无状态工作负载控制器,它在 ReplicaSet 之上提供了声明式更新、滚动发布、版本回滚等高级能力。日常开发中,绝大多数无状态服务(Web API、微服务后端、定时任务 Worker)都通过 Deployment 来管理 Pod 的生命周期。理解 Deployment 与 ReplicaSet 的协作关系,是掌握 Kubernetes 应用交付模型的基础。 ## ReplicaSet 详解 ### 什么是 ReplicaSet ReplicaSet(简称 RS)的核心职责只有一件事:**确保指定数量的 Pod 副本始终处于运行状态**。如果某个 Pod 意外退出或被删除,RS 会立即创建新的 Pod 来弥补差额;如果 Pod 数量超过期望值,RS 会删除多余的 Pod。 ```yaml # 一个独立的 ReplicaSet 示例(通常不直接创建,由 Deployment 自动管理) apiVersion: apps/v1 kind: ReplicaSet metadata: name: nginx-rs namespace: default spec: replicas: 3 # 期望副本数 selector: matchLabels: app: nginx # Label Selector:通过标签匹配 Pod template: metadata: labels: app: nginx # Pod 必须携带此标签才能被 RS 管理 spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80 ``` ### Label Selector 机制 RS 通过 **Label Selector** 来发现和管理 Pod,而非维护一个固定的 Pod 列表。这意味着: - 手动创建的 Pod 只要标签匹配,也会被 RS 纳入管理(数量超出则删除,不足则新建) - 修改某个 Pod 的标签可以将其从 RS 管理中"摘除",这在调试时非常有用 ```bash # 将一个 Pod 从 RS 中移除(不删除 Pod,仅修改标签使其脱离管理) kubectl label pod nginx-rs-xxxx app=detached --overwrite # 此时 RS 会发现少了一个副本,自动新建一个 Pod 来补足 ``` > [!tip] 调试技巧 > 需要排查某个 Pod 问题时,先把它从 RS 中"摘出来"(修改标签),这样 RS 不会因为 Pod 异常而自动重建,你可以从容排查。 ### ReplicaSet vs ReplicationController ReplicationController(RC)是 RS 的前身,两者核心区别在于 Selector 语义: | 对比维度 | ReplicationController | ReplicaSet | |----------|----------------------|------------| | Selector 类型 | 仅支持等式(`env=prod`) | 支持等式 + 集合(`env in (prod, staging)`) | | 标签匹配灵活性 | 低 | 高 | | 推荐程度 | 已废弃,不建议使用 | 推荐使用 | | 滚动更新 | 需要 `kubectl rolling-update`(客户端执行) | 由 Deployment 在服务端协调 | RC 已经是遗留概念,新资源应统一使用 ReplicaSet。不过实践中你几乎不会直接创建 RS,而是通过 Deployment 间接管理。 ## Deployment 核心机制 ### 声明式管理 Deployment 采用声明式模型:你只需描述"期望状态"(desired state),Kubernetes 控制面会持续将其与"实际状态"(actual state)做对比,自动执行必要的操作来弥合差距。这个循环叫做 **Reconcile Loop**(调谐循环)。 ```mermaid graph LR A["用户提交 Deployment
(desired state)"] --> B["Deployment Controller"] B --> C{"desired == actual ?"} C -->|No| D["创建/删除/更新
ReplicaSet / Pod"] D --> B C -->|Yes| E["状态稳定
不做任何操作"] ``` 工作流程: 1. 用户通过 `kubectl apply` 提交 Deployment YAML,声明 `replicas: 3`、`image: nginx:1.25` 等期望状态 2. Deployment Controller 观察到当前没有匹配的 RS,创建一个新的 RS 3. RS Controller 观察到 Pod 数量为 0,创建 3 个 Pod 4. 持续监控:如果某个 Pod 崩溃,RS Controller 检测到 actual < desired,立即补充新 Pod ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy namespace: default spec: replicas: 3 # 声明:我想要 3 个副本 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 # 声明:我想用这个镜像 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi ``` ### 滚动更新(Rolling Update) 滚动更新是 Deployment 最核心的价值。当你修改了 Pod 模板(比如升级镜像版本),Deployment 不会一次性杀掉所有旧 Pod,而是**逐批替换**,在更新过程中始终保持服务可用。 #### 关键参数 | 参数 | 默认值 | 含义 | |------|--------|------| | `maxSurge` | 25% | 更新过程中允许超出 `replicas` 的最大 Pod 数量 | | `maxUnavailable` | 25% | 更新过程中允许不可用的最大 Pod 数量 | > [!info] 参数计算 > 对于 `replicas: 4`、`maxSurge: 25%`、`maxUnavailable: 25%` 的配置: > - maxSurge = ceil(4 * 0.25) = 1,最多可以有 5 个 Pod 同时存在 > - maxUnavailable = floor(4 * 0.25) = 1,至少保证 3 个 Pod 可用 > > 这意味着每批替换 1 个 Pod,共需 4 批完成更新。 #### 滚动更新过程 ```mermaid sequenceDiagram participant U as 用户 participant D as Deployment Controller participant RS1 as ReplicaSet v1 participant RS2 as ReplicaSet v2 U->>D: kubectl apply(image: nginx:1.26) D->>RS2: 创建新 RS(image: 1.26, replicas: 0) D->>RS2: scale up +1(新建 1 个新 Pod) Note over RS1,RS2: maxSurge=1, 新 Pod 启动 D->>RS1: scale down -1(销毁 1 个旧 Pod) Note over RS1,RS2: maxUnavailable=1, 保证可用 D->>RS2: scale up +1 D->>RS1: scale down -1 Note over RS1,RS2: 重复直到 RS1=0, RS2=4 D->>RS1: 保留 RS1(revision=1, 可回滚) ``` 逐批替换的过程(以 `replicas: 4` 为例): | 阶段 | RS v1 (旧) | RS v2 (新) | 总 Pod 数 | 可用 Pod | |------|-----------|-----------|----------|---------| | 初始 | 4 | 0 | 4 | 4 | | 第 1 批 | 4 → 3 | 0 → 1 | 5 | 4 | | 第 2 批 | 3 → 2 | 1 → 2 | 4 | 4 | | 第 3 批 | 2 → 1 | 2 → 3 | 4 | 4 | | 第 4 批 | 1 → 0 | 3 → 4 | 4 | 4 | #### 滚动更新配置 ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy spec: replicas: 4 strategy: type: RollingUpdate # 更新策略 rollingUpdate: maxSurge: 1 # 最多多 1 个 Pod maxUnavailable: 1 # 最多少 1 个 Pod selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 readinessProbe: # 重要:就绪探针决定新 Pod 何时接收流量 httpGet: path: /healthz port: 80 initialDelaySeconds: 5 periodSeconds: 10 ``` > [!warning] readinessProbe 的重要性 > 滚动更新依赖 `readinessProbe` 判断新 Pod 是否就绪。如果没有配置就绪探针,新 Pod 一创建就被视为 Ready,可能导致流量打到尚未初始化完成的 Pod 上,造成请求失败。 ### 回滚策略 Kubernetes 为每次 Deployment 变更自动记录 revision(版本号),支持快速回滚到任意历史版本。 ```bash # 查看 rollout 历史 kubectl rollout history deployment nginx-deploy # 输出示例: # REVISION CHANGE-CAUSE # 1 # 2 # 3 # 查看某个 revision 的详细信息 kubectl rollout history deployment nginx-deploy --revision=2 # 回滚到上一个版本 kubectl rollout undo deployment nginx-deploy # 回滚到指定版本 kubectl rollout undo deployment nginx-deploy --to-revision=1 # 查看 rollout 状态(是否完成、耗时等) kubectl rollout status deployment nginx-deploy ``` #### revisionHistoryLimit 配置 默认保留 10 个历史 revision。过多的历史 RS 会占用 etcd 资源,过少则可能丢失回滚能力。 ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy spec: revisionHistoryLimit: 5 # 只保留最近 5 个 revision # 设为 0 则禁用回滚(不推荐) replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx annotations: kubernetes.io/change-cause: "升级到 nginx 1.26" # 记录变更原因,显示在 rollout history 中 spec: containers: - name: nginx image: nginx:1.26 ``` > [!tip] 记录变更原因 > 在 Pod template 的 `annotations` 中添加 `kubernetes.io/change-cause`,这样 `kubectl rollout history` 会显示每次变更的原因,方便排查。 ### 扩缩容 #### 手动扩缩容 ```bash # 命令行直接扩缩 kubectl scale deployment nginx-deploy --replicas=5 # 条件扩缩(只有当前副本数为 3 时才执行) kubectl scale deployment nginx-deploy --replicas=5 --current-replicas=3 ``` 也可以直接修改 YAML 中的 `replicas` 字段后 `kubectl apply`。 #### HPA 自动扩缩 Horizontal Pod Autoscaler(HPA)根据 CPU、内存或自定义指标自动调整 Deployment 的副本数。 ```yaml # 前置条件:集群中需要安装 Metrics Server apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: nginx-hpa namespace: default spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nginx-deploy # 目标 Deployment minReplicas: 2 # 最小副本数 maxReplicas: 10 # 最大副本数 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60 # CPU 使用率超过 60% 时扩容 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 # 内存使用率超过 80% 时扩容 behavior: scaleUp: stabilizationWindowSeconds: 60 # 扩容稳定窗口:60s 内取最大值 policies: - type: Pods value: 4 # 每次最多扩 4 个 Pod periodSeconds: 60 scaleDown: stabilizationWindowSeconds: 300 # 缩容稳定窗口:300s,防止抖动 policies: - type: Percent value: 10 # 每次最多缩 10% periodSeconds: 60 ``` ```bash # 创建 HPA kubectl apply -f hpa.yaml # 或用命令行快速创建 kubectl autoscale deployment nginx-deploy --cpu-percent=60 --min=2 --max=10 # 查看 HPA 状态 kubectl get hpa nginx-hpa ``` > [!warning] HPA 与手动 scale 冲突 > 如果同时使用 HPA 和手动 `kubectl scale`,HPA 会在下一次指标采集时覆盖手动设置。两者不应混用。 ## Deployment 更新策略 Deployment 支持两种更新策略,通过 `spec.strategy.type` 指定: | 对比维度 | RollingUpdate | Recreate | |----------|--------------|----------| | 更新方式 | 逐批替换 Pod | 先杀全部旧 Pod,再创建全部新 Pod | | 更新期间可用性 | 始终可用(前提:正确配置探针) | 有短暂不可用窗口 | | 资源占用 | 可能临时需要更多资源(maxSurge > 0 时) | 只需要新版本的资源 | | 适用场景 | 大多数无状态服务 | 不能有两个版本同时运行的场景 | | 典型用例 | Web 服务、API 后端 | 涉及全局独占资源(如本地文件锁、全局数据库迁移) | ```yaml # Recreate 策略示例 apiVersion: apps/v1 kind: Deployment metadata: name: legacy-app spec: replicas: 3 strategy: type: Recreate # 全量替换,更新期间服务不可用 selector: matchLabels: app: legacy template: metadata: labels: app: legacy spec: containers: - name: app image: legacy-app:2.0 ``` > [!info] 什么时候用 Recreate? > 当新旧版本不能共存时使用 Recreate,比如:应用使用了本地文件锁、需要执行不可逆的数据库 Schema 迁移、或者旧版本会干扰新版本的初始化。大多数情况下 RollingUpdate 是更好的选择。 ## 金丝雀发布与蓝绿部署 Deployment 本身不直接支持金丝雀发布和蓝绿部署,但可以通过组合多个 Deployment + Service 来实现。 ### 金丝雀发布(Canary Release) 核心思路:同时运行两个版本的 Deployment,通过控制副本比例来控制流量分配。 ```yaml # 主 Deployment:稳定版本,占 90% 流量 apiVersion: apps/v1 kind: Deployment metadata: name: app-stable spec: replicas: 9 # 9 个副本 selector: matchLabels: app: myapp track: stable template: metadata: labels: app: myapp track: stable # 核心标签 app: myapp 相同,Service 可以选中 spec: containers: - name: app image: myapp:v1.0 --- # 金丝雀 Deployment:新版本,占 10% 流量 apiVersion: apps/v1 kind: Deployment metadata: name: app-canary spec: replicas: 1 # 1 个副本 selector: matchLabels: app: myapp track: canary template: metadata: labels: app: myapp track: canary spec: containers: - name: app image: myapp:v2.0 ``` ```yaml # Service 通过 app: myapp 同时选中两个 Deployment 的 Pod apiVersion: v1 kind: Service metadata: name: myapp-svc spec: selector: app: myapp # 不指定 track,两个版本都会被选中 ports: - port: 80 targetPort: 8080 ``` 流量分配:9 个旧版本 Pod + 1 个新版本 Pod = 约 10% 流量到达新版本。验证无误后,逐步调整副本比例(如 `canary: 5, stable: 5`),最终全部切换到新版本。 ```mermaid graph LR Client --> Service Service -->|~90%| Pod1["Pod v1.0 x9"] Service -->|~10%| Pod2["Pod v2.0 x1"] ``` ### 蓝绿部署(Blue-Green Deployment) 核心思路:同时运行蓝(当前版本)和绿(新版本)两套完整的 Deployment,通过切换 Service 的 selector 来瞬间完成流量切换。 ```yaml # 蓝色 Deployment(当前生产版本) apiVersion: apps/v1 kind: Deployment metadata: name: app-blue spec: replicas: 3 selector: matchLabels: app: myapp version: blue template: metadata: labels: app: myapp version: blue spec: containers: - name: app image: myapp:v1.0 --- # 绿色 Deployment(新版本,先部署但不接收流量) apiVersion: apps/v1 kind: Deployment metadata: name: app-green spec: replicas: 3 selector: matchLabels: app: myapp version: green template: metadata: labels: app: myapp version: green spec: containers: - name: app image: myapp:v2.0 ``` ```yaml # Service:切换 selector.version 即可瞬间切换流量 apiVersion: v1 kind: Service metadata: name: myapp-svc spec: selector: app: myapp version: blue # 当前指向蓝色,切换为 green 即完成上线 ports: - port: 80 targetPort: 8080 ``` ```bash # 切换流量到绿色版本 kubectl patch service myapp-svc -p '{"spec":{"selector":{"version":"green"}}}' # 回滚:切换回蓝色版本 kubectl patch service myapp-svc -p '{"spec":{"selector":{"version":"blue"}}}' ``` 蓝绿部署的优点是切换瞬间完成(无中间状态),缺点是需要双倍资源。适合对上线/回滚速度要求极高的场景。 ## Pod 模板字段详解 `spec.template.spec` 是 Pod 模板的核心,以下是常用字段的逐一说明: ```yaml spec: template: metadata: labels: # Pod 标签,用于 Service Selector 和 ReplicaSet 匹配 app: myapp version: v1 annotations: # 非标识性元数据,供工具或控制器使用 kubernetes.io/change-cause: "初始部署" spec: # ---- 容器定义 ---- containers: - name: app # 容器名称,Pod 内唯一 image: myapp:v1.0 # 镜像地址 imagePullPolicy: IfNotPresent # Always / IfNotPresent / Never ports: - containerPort: 8080 # 容器监听端口(声明性质,不强制) env: # 环境变量 - name: APP_ENV value: "production" - name: DB_PASSWORD valueFrom: secretKeyRef: # 从 Secret 读取敏感信息 name: db-secret key: password resources: # 资源请求与限制 requests: # 调度依据:节点至少需要这么多资源 cpu: 100m memory: 128Mi limits: # 上限:超出会被 OOM Kill 或 CPU 限流 cpu: 500m memory: 256Mi readinessProbe: # 就绪探针:决定 Pod 是否接收流量 httpGet: path: /healthz port: 8080 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: # 存活探针:失败则重启容器 httpGet: path: /healthz port: 8080 initialDelaySeconds: 15 periodSeconds: 20 startupProbe: # 启动探针:慢启动应用专用,通过前忽略 liveness httpGet: path: /healthz port: 8080 failureThreshold: 30 periodSeconds: 10 # ---- 初始化容器 ---- initContainers: # 在主容器启动前顺序执行 - name: init-db-migration image: myapp:v1.0 command: ["./migrate.sh"] # ---- 卷挂载 ---- volumes: - name: config-vol configMap: name: app-config # 挂载 ConfigMap 作为文件 - name: data-vol emptyDir: {} # 临时卷,Pod 删除后数据丢失 # ---- 调度相关 ---- nodeSelector: # 简单节点选择 disk: ssd affinity: # 高级亲和性规则 nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/os operator: In values: ["linux"] tolerations: # 容忍污点 - key: "dedicated" operator: "Equal" value: "gpu" effect: "NoSchedule" # ---- 其他 ---- terminationGracePeriodSeconds: 30 # 优雅终止等待时间 serviceAccountName: myapp-sa # 使用的 ServiceAccount ``` > [!tip] imagePullPolicy 最佳实践 > - 使用 `:latest` 标签时,必须设置 `imagePullPolicy: Always`,否则可能使用本地缓存的旧镜像 > - 使用固定版本标签(如 `:v1.2.3`)时,`IfNotPresent` 更高效,避免重复拉取 > - `Never` 仅用于本地开发环境(如 minikube 中直接使用本地构建的镜像) ## 常见陷阱与最佳实践 ### 更新卡住排查 滚动更新卡住是常见问题,通常由以下原因导致: ```bash # 1. 查看 Deployment 状态 kubectl rollout status deployment nginx-deploy # 可能输出:Waiting for rollout to finish: 2 out of 4 new replicas have been updated... # 2. 查看 Deployment 的 Conditions kubectl get deployment nginx-deploy -o yaml | grep -A 20 conditions: # 常见条件: # - Progressing: False(更新未推进) # - Available: True/False(是否有足够可用 Pod) # 3. 查看新 ReplicaSet 的事件 kubectl describe rs nginx-deploy-xxxxxx # 查看新 RS 的 Events # 常见原因: # - FailedScheduling:资源不足,没有节点能满足 requests # - ImagePullBackOff:镜像拉取失败(镜像名错、私有仓库无凭证) # - CrashLoopBackOff:新容器启动后立即崩溃 # 4. 查看 Pod 事件 kubectl get pods -l app=nginx --sort-by=.metadata.creationTimestamp kubectl describe pod nginx-deploy-xxxxxx-yyyyy ``` 常见原因和解决方案: | 症状 | 原因 | 解决方案 | |------|------|---------| | ImagePullBackOff | 镜像不存在或无权限 | 检查镜像名称、imagePullSecrets | | CrashLoopBackOff | 应用启动失败 | 查看 Pod 日志 `kubectl logs` | | Pending | 资源不足或节点不满足约束 | 检查 `kubectl describe pod` 中的 Events | | readinessProbe 一直失败 | 健康检查路径或端口错误 | 确认探针配置与应用实际端口一致 | > [!warning] 超时回滚 > Deployment 有一个 `progressDeadlineSeconds` 参数(默认 600 秒),如果更新在该时间内未完成,Deployment 会标记为 `Progressing=False`。注意:这**不会自动回滚**,只是标记状态。需要手动执行 `kubectl rollout undo` 或编写自动化脚本。 ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy spec: progressDeadlineSeconds: 300 # 5 分钟内未完成更新则标记失败 minReadySeconds: 10 # Pod Ready 后等待 10 秒才视为 Available replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.26 ``` ### imagePullPolicy 导致的镜像缓存问题 ```bash # 场景:修改了 YAML 中的 image 字段但没改 tag,apply 后 Pod 没有更新 # 原因:imagePullPolicy 默认为 IfNotPresent,本地已有旧镜像就不会重新拉取 # 解决方案 1:始终使用不同的 tag(推荐) image: myapp:v1.0.1 # 而不是 myapp:latest # 解决方案 2:强制设置 imagePullPolicy imagePullPolicy: Always # 解决方案 3:使用 SHA256 digest(最精确) image: myapp@sha256:abc123... ``` ### Deployment vs StatefulSet 选择 | 特性 | Deployment | StatefulSet | |------|-----------|-------------| | Pod 名称 | 随机(`nginx-5d6b8f7c9-abc12`) | 有序(`web-0`, `web-1`, `web-2`) | | Pod 启动顺序 | 并行 | 有序(0 → 1 → 2) | | Pod 终止顺序 | 并行(默认) | 逆序(2 → 1 → 0) | | 网络标识 | 不稳定 | 稳定(`web-0.ng-svc`) | | 持久存储 | 共享或无 | 每个 Pod 独立 PVC | | 适用场景 | 无状态服务 | 有状态服务(数据库、MQ、ZooKeeper) | | 滚动更新 | 替换 Pod | 逐个更新 | > [!info] 选择原则 > 如果你的应用满足以下任意条件,应考虑 StatefulSet: > - 需要稳定的网络标识(如 ZooKeeper 集群节点互相发现) > - 需要持久化的、与 Pod 绑定的存储(如 MySQL 数据目录) > - 需要有序的部署/扩缩/更新 > > 其余场景一律使用 Deployment。 ### 其他最佳实践 1. **始终配置资源请求和限制**:没有 requests 的 Pod 会被调度器视为零消耗,可能导致多个 Pod 被调度到同一节点,引发资源争抢。 2. **始终配置 readinessProbe**:没有就绪探针,新 Pod 创建后立即被标记为 Ready,流量可能打到未初始化完成的 Pod。 3. **不要直接管理 Deployment 创建的 ReplicaSet**:RS 的生命周期由 Deployment 控制器管理,手动修改 RS(如 scale)会被 Deployment 覆盖。 4. **使用 `kubectl apply` 而非 `kubectl create`**:apply 支持声明式管理,可以幂等执行,适合 GitOps 工作流。 5. **合理设置 minReadySeconds**:该参数让 Pod Ready 后再等待一段时间才被视为 Available,可以用来检测"启动后很快崩溃"的场景。 ```yaml # 推荐的最小 Deployment 模板 apiVersion: apps/v1 kind: Deployment metadata: name: myapp labels: app: myapp spec: replicas: 3 revisionHistoryLimit: 5 progressDeadlineSeconds: 300 minReadySeconds: 10 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 # 保证更新期间始终有足够的可用 Pod selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: terminationGracePeriodSeconds: 30 containers: - name: myapp image: myapp:v1.0.0 imagePullPolicy: IfNotPresent ports: - containerPort: 8080 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 15 periodSeconds: 20 ``` ## 延伸阅读 - [[k8s-03-pod]] — Pod 的生命周期、调度机制、资源管理 - [[k8s-05-service-and-networking]] — Service 类型、Ingress、网络策略 - [Kubernetes 官方文档 - Deployments](https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/deployment/) - [Kubernetes 官方文档 - ReplicaSet](https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/replicaset/) - [Kubernetes 官方文档 - HPA](https://kubernetes.io/zh-cn/docs/tasks/run-application/horizontal-pod-autoscale/)