K8s 系统介绍

系统构架简介

系统架构根据不同的设计原则和需求,可以分为多种类型。

单体应用

单体应用由很多个组件组成,这些组件紧密地耦合在一起,由于它们在同一个操作系统进程中运行,所以在开发、部署和管理时必须以同一个实体进行。

对单体应用来说,即使是某个组件的一个小修改,都需要重新部署整个应用。组件之间缺乏严格边界,互相依赖,系统复杂度逐渐提升。

运行一个单体应用,通常需要一台能为整个应用提供足够资源的高性能服务器。为了应对不断增长的系统负荷,需要垂直扩展,对服务器硬件进行升级,或者增加更多服务器进行水平扩展。垂直扩展通常会遇到性能瓶颈,水平扩展可能需要对原本应用程序代码进行大量改动。

微服务

微服务架构旨在解决大型单体应用程序臃肿的问题,将单体应用拆分为小而可独立部署的微服务组件。每个微服务都以独立的进程运行,并通过简单且定义良好的 API 接口与其他微服务进行通信。

服务器之间使用类似 HTTP 的同步协议或者像 AMQP 这样的异步协议进行通信。使用这些标准化协议使得微服务可以使用任意开发语言进行实现。只要提供相对稳定的 API,对一个微服务进行修改不会对其他微服务产生影响。

使用微服务的另一个好处是可以根据需要针对单个组件进行水平扩展,对无法水平扩展的组件进行垂直扩展,灵活组合以适应不同的环境。

然而,微服务也存在一些缺点。随着组件数量的增加,组件部署和依赖的组合数也在增加,配置工作变得冗杂且容易出错。由于微服务涉及多个进程和服务器,调试代码和定位异常调用变得更加困难。

K8s 系统

Kubernetes 源于谷歌内部的 Borg,是一个面向应用的容器集群部署和管理系统。Kubernetes 的目标是消除管理计算、网络和存储等基础设施的负担,通过对底层基础设施进行抽象,简化应用的开发、部署以及对开发和运维团队的管理。K8s 还提供基础平台,用于构建定制化的工作流程和高级的自动化任务。

从运维角度来看,只需在服务器上部署 K8s,就可以运行应用程序,无需安装其他依赖服务。Kubernetes 具有以下几个重要特性:

  • 自动装箱

    构建于容器之上,基于资源依赖和其他约束自动完成容器部署,不影响容器的可用性。通过调度机制,将关键和非关键应用的工作负载混合在同一节点上,提高资源利用率。在 K8s 中,所有工作节点被公开为一个部署平台,开发人员无需了解组成集群的服务器。如果需要将程序运行在特定类型的节点上,可以通过节点标签进行选择。

  • 健康检查和自我修复

    K8s 监控应用程序组件和运行节点,支持容器故障后的自动重启、节点故障后的重新调度容器、健康状态检查失败后的重新创建等自愈机制。

  • 水平扩展

    支持通过简单的命令手动进行水平扩展,并且基于硬件资源负载率的自动水平扩展机制。如果 K8s 运行在云基础设施上,可以根据程序使用的资源情况和需求自动添加或缩减节点。

  • 服务发现和负载均衡

    K8s 通过 KubeDNS(或 CoreDNS)为系统内置了服务发现功能。它为每个服务配置了 DNS 名称,允许集群内的客户端直接使用名称进行访问,而服务则通过 iptables 或 ipvs 内置了负载均衡机制。

  • 自动发布和回滚

    K8s 支持灰度更新应用程序或配置信息。它确保实例不会同时停止,一旦发生故障,会立即自动执行回滚操作。

  • 密钥和配置管理

    K8s 的 ConfigMap 实现了配置数据与 Docker 镜像的分离,无需重构镜像即可对配置进行更改。此外,对于敏感数据如密码和密钥等,有专门的 Secret 对象用于存放。

  • 存储编排

    K8s 支持按需自动挂载不同类型的存储系统到 Pod 对象中,包括本地存储、云存储和网络存储系统。

  • 批量处理执行

    除了服务型应用,K8s 还支持批处理作业和持续集成。它提供了丰富的工具和功能,用于管理和执行大规模的数据处理和计算任务。

通过这些特性和功能,Kubernetes 提供了一个强大而灵活的容器管理平台,为应用程序的部署、扩展、监控和管理提供了全面的解决方案。无论是在本地环境还是云环境中,K8s 都能够有效地管理和调度容器化的应用程序,提供稳定可靠的服务。

资源对象、节点和组件

在 Kubernetes(K8s)中,节点和组件共同协作,使得 Kubernetes 能够实现容器化应用程序的高效部署、自动伸缩和弹性管理。

资源对象

K8s 中的基本概念和术语大多围绕着资源对象(Resource Object)展开。资源对象可分为两类:

  • 某种资源的对象,例如 Node、Pod、Service、Volume 等。
  • 与资源对象相关的事物和动作,例如 Label、Namespace、PVC、HPA 等。

资源对象一般包括几个通用属性:

  • 版本:包括此对象所属的资源组信息,不同版本可能有不同的资源对象属性。
  • 类别:定义资源对象的类型。
  • 名称:资源对象的名称必须唯一。
  • 标签:用于标识资源对象的特征类别,可以通过标签对不同的资源对象进行筛选和操作。
  • 注解:一种特殊的标签,常用于自定义扩展资源对象的属性。

节点

节点是集群的工作单元,用于运行容器化应用程序。每个节点都是一个独立的机器,可以是物理服务器或虚拟机。节点上安装了容器运行时环境,例如 Docker、containerd 等,以便运行和管理容器。

K8s 集群通常包含多个节点,它们协同工作以提供高可用性和可扩展性。以下是两种节点类型:

  • 主节点(Master): 集群的控制节点,负责控制和管理整个集群系统。主节点承载任务如为用户暴露 API、跟踪其他服务器的健康状态、以最优的方式调度工作负载以及编排其他组件之间的通信等。可以运行多个主节点以提高可用性。

  • 工作节点(Node): 工作节点负责运行用户实际部署的应用,由主节点进行管理。它接收来自主节点的工作指令,并根据指令创建或销毁 Pod 对象,以及调整网络规划以合理地路由和转发流量。

节点可以在运行期间动态加入到集群中,K8s 将所有节点的资源集结在一起,形成一台超级服务器。当用户将应用部署在集群上时,主节点会使用调度算法将其自动指派给某个特定的工作节点运行。

主节点和工作节点中的组件联系如下图所示:

节点组件

主节点

主节点的控制面板负责控制并使整个集群正常运转。它包含多个组件,这些组件可以运行在单个主节点上,也可以通过副本部署在多个主节点上以确保高可用性。这些组件包括:

  • API 服务器(kube-apiserver): 负责控制面板组件之间的通信,提供关键的 HTTP REST 接口服务,是集群的前端接口。
  • Scheduler 调度器(kube-scheduler): 负责调度应用程序,为每个可部署组件分配工作节点。
  • Controller Manager 控制器管理器(kube-controller-manager): 执行集群级别的功能,如复制组件、持续跟踪工作节点、处理节点失败等,以确保资源处于预期状态。
  • etcd 分布式持久化存储: 一个可靠的分布式数据存储,用于持久化存储群集配置和各种资源的状态信息。当数据发生变化时,etcd 会快速通知相关组件。

可以通过 API 服务器暴露的 ComponentStatus 的 API 资源查询这些组件的状态:

[root@server4-master ~]$ kubectl get componentstatuses
Warning: v1 ComponentStatus is deprecated in v1.19+
NAME                 STATUS      MESSAGE                         ERROR
scheduler            Healthy     ok 
controller-manager   Healthy     ok     
etcd-0               Healthy     {"health":"true","reason":""}    

工作节点

工作节点是运行容器化应用程序的机器。它负责运行、监控和管理应用服务,以下是工作节点中的组件:

  • 容器运行时(Container runtime): 负责镜像管理以及 Pod 和容器的实际运行,使用容器运行时接口(CRI)。默认使用 Docker Engine,也可以使用其他类型的容器引擎。
  • Kubelet(kubelet): 与 API 服务器通信,并管理所在节点的容器,与容器运行时进行交互,是工作节点的客户端。调度器确定在某个节点上运行 Pod 后,会将具体配置信息发送给 Kubelet,以创建和运行容器,并向主节点报告运行状态。同时,Kubelet 还负责卷(CSI)和网络(CNI)的管理。
  • 代理服务(kube-proxy): 每个节点上都运行代理服务,它负责将对服务的访问流量转发到后端容器(服务发现)。如果有多个 Pod 副本,代理服务还负责组件之间的负载均衡和网络流量转发。

可以使用 kubectl describe 命令查询工作节点的详细信息:

[root@server4-master manifests]$ kubectl describe node server4-master 
Name:               server4-master
Roles:              control-plane,master
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=server4-master
                    kubernetes.io/os=linux

组件

除了控制面板和运行在节点上的组件,K8s 集群还依赖第三方组件以提供完整的功能。以下是主要的组件:

  • CoreDNS:提供集群的服务注册和服务发现的 DNS 解析服务。
  • Dashboard:用于通过 Web 用户界面管理集群中的应用程序和集群本身。
  • Heapster:已被弃用的容器和节点性能监控分析系统,现已由 Prometheus 结合其他组件取代。
  • Ingress:在应用层实现 HTTP 负载均衡机制,它是一组路由规则的集合,需要通过 Ingress Controller 来实现功能。
  • 容器网络接口插件:常用的插件有 Flannel 和 Calico。

K8s 系统组件之间的通信主要通过 API 服务器进行。API 服务器和其他组件的连接大多由组件发起。只有在使用 kubectl 获取日志、attach 到容器上(即连接到容器中运行的主进程)或者运行 kubectl 端口转发时,API 服务器才会与 Kubelet 建立连接。

控制面板的组件可以简单地部署在多台服务器上,多个实例可以并行工作以提高可用性。但是调度器和控制器管理器在任意时刻只能有一个实例起作用,其他实例处于待命状态。

控制面板的组件可以作为 Pod 运行,而 Kubelet 是唯一一个作为常规系统组件运行的组件:

[root@server4-master ~]$ kubectl get po -o custom-columns=组件:metadata.name,节点:spec.nodeName --sort-by spec.nodeName -n kube-system 
组件                                       节点
coredns-7f6cbbb7b8-jtqxp                 server4-master
coredns-7f6cbbb7b8-vvtxz                 server4-master
etcd-server4-master                      server4-master
kube-apiserver-server4-master            server4-master
kube-controller-manager-server4-master   server4-master
kube-proxy-d455c                         server4-master
kube-scheduler-server4-master            server4-master
kube-proxy-fnvff                         server5-node1
kube-proxy-wkgjf                         server6-node2
[root@server4-master ~]$ kubectl get po -o custom-columns=组件:metadata.name,节点:spec.nodeName --sort-by spec.nodeName -n calico-system
组件                                         节点
calico-kube-controllers-767ddd5576-b7dpp   server4-master
calico-node-qfsms                          server4-master
calico-typha-7b6f4887f8-mkf7m              server4-master
calico-node-2cp86                          server5-node1
calico-typha-7b6f4887f8-8755m              server5-node1
calico-node-gnfth                          server6-node2
calico-typha-7b6f4887f8-7qmzm              server6-node2

如上所示,所有控制面板组件作为 Pod 运行在主节点上。每个工作节点运行 kube-proxy,并使用 calico-system 命名空间的网络插件。

K8s 网络

在 Kubernetes(K8s)集群中,网络是一个重要的组成部分,它允许不同的容器和节点之间进行通信,并提供应用程序之间的连接和负载均衡。

网络插件

在 K8s 中,网络由 CNI(Container Network Interface)插件建立。CNI 插件定义了容器如何加入和离开网络,以及如何与其他容器进行通信。这些插件可以部署在 K8s 集群之外。无论使用哪种网络技术,都要满足 Pod 之间可以跨工作节点进行通信,并且通信时会保留源和目标 IP 地址,不使用 NAT(网络地址转换)操作。

Pod 和节点之间的通信也采用了无 NAT 通信,只有 Pod 和互联网上的服务通信时,才会将发送包的源地址改为节点的 IP 地址。

常用的 CNI 插件包括 Calico、Flannel、Romana、Weave Net 等。要使用 CNI 插件,需要在启动 Kubelet 时添加 --network-plugin=cni 参数。

安装一个网络插件只需部署一个包含 DaemonSet 和其他支持资源的 YAML 文件。DaemonSet 会在所有集群节点上部署一个网络代理,并将 CNI 接口绑定到节点上。

IP 地址

K8s 中存在三种网络地址,它们之间的通信采用特殊的路由规则:

  • Node IP(节点 IP)

    每个节点的物理网卡上都有一个 IP 地址。当 K8s 集群外的节点访问集群内的服务时,必须通过节点 IP 进行通信。

  • Pod IP

    每个 Pod 都有一个 IP 地址,由 Docker 引擎根据 Docker0 网桥的 IP 地址段进行分配。这通常是一个虚拟的二层网络。

    Pod 之间可以直接通信,而真实的流量通过节点 IP 所在的物理网卡流出。

  • Cluster IP(集群 IP)

    Service 上的集群 IP 是一个虚拟 IP,来源于集群 IP 地址池,仅在 Service 对象上有效。

    集群 IP 无法通过 PING 进行通信,因为没有实际的网络对象来响应。它只能与 Service 端口结合使用,形成一个具体的通信端口。单独的 IP 本身没有通信能力。

同节点 Pod 通信

在基础容器(Pause 容器)启动之前,会为容器创建一对虚拟网络接口(veth pair),其中一个接口保留在主机的命名空间中(命名为 vethXXX,在节点上可直接查看),而另一个接口被移入容器的网络命名空间,并被重命名为 eth0。这两个虚拟接口就像管道的两端。

主机的网络命名空间接口会绑定到容器运行时配置使用的网络桥接上,从桥接的地址段中获取 IP 地址并赋值给容器内的 eth0 网络接口。所有容器的数据都通过 eth0 发送到桥接,然后再传递给目标 Pod。

如下图所示:

虚拟接口对

如果 Pod A 发送网络包给 Pod B,数据包将通过 Pod A 的 veth 对到达桥接,然后经过 Pod B 的 veth 对。所有节点上的容器都连接到同一个桥接上。

跨节点 Pod 通信

在连接不同节点的网桥方式中,可以使用覆盖网络(overlay)或底层网络(underlay),或者采用常规的三层路由。

跨整个集群的 Pod 的 IP 地址必须是唯一的,因此跨节点的网桥必须使用非重叠的地址段,以避免 IP 地址冲突。例如,节点 A 上的网桥可以使用 10.1.1.0/24 地址段,而节点 B 上的网桥可以使用 10.1.2.0/24 地址段。

通过三层网络来支持跨节点通信,两个节点的路由表会被配置成下图所示的方式:

跨节点通信

上述方案仅在节点之间连通到相同网关且没有其他路由时有效。否则,路由器会丢弃数据包,因为源和目标 IP 都是私有 IP 地址。

使用软件定义网络(SDN)技术可以简化问题,让节点忽略底层网络拓扑,就像连接到同一个网关。从 Pod 发出的报文会被封装,在网络上发送到其他 Pod 所在的网络,然后解封装以原始格式传递给目标 Pod。

高可用集群

高可用集群是指在计算机系统或网络中采用冗余设计和自动故障恢复机制,以提高系统的可用性和稳定性。在高可用集群中,多个节点协同工作,共同承担服务的负载和功能,并能够在节点故障或其他异常情况下自动进行故障转移,确保服务的持续可用性。

应用高可用

在 K8s 上运行应用的最重要目标是保证应用的持续运行,尽量减少由人工操作基础设置导致的宕机。

最常见的做法是使用 Deploy 资源来运行应用,配置适量的副本集,其余的交由 K8s 来处理。即使应用本身不支持水平扩展,仍然可以使用 Deploy 来发布单个副本。当应用不可用时,K8s 会快速替换为一个新的副本。

此外,还可以同时运行一个活跃的应用和一个附加的非活跃复制集,通过领导选举机制来确保只有一个副本处于活跃状态。这样,在领导者发生宕机时,非活跃复制集会立即接替领导者的角色。

集群高可用性

在整个 K8s 中,Master 控制节点的重要性不言而喻。如果 Master 所在的服务器宕机,将导致整个 K8s 集群不可用。因此,可以通过运行多个主节点来提高可用性。以下是三个主节点构成的高可用集群示意图:

三主节点集群

Etcd 集群

由于 etcd 本身就是为分布式系统设计的,因此只需在适量的机器上运行 etcd,使它们能够相互感知即可。实现方法是修改每个实例的配置,包含其他实例的列表,etcd 会在实例之间进行数据复制。

3 个节点允许其中 1 个节点宕机,最佳实践是使用 5 到 7 个节点,这样集群可以容忍 2 或 3 个节点的宕机。超过 7 个节点的数量反而会影响性能。

多实例 API 服务器

API 服务器是无状态的,因为数据存储在 etcd 中,API 服务器不进行缓存。因此,可以运行任意数量的 API 服务器,它们之间不需要感知对方的存在。

通常,一个 API 服务器会与每个 etcd 实例配对,因为每个 API 服务器仅与本地的 etcd 实例通信。这样,etcd 实例就无需使用任何负载均衡器,只需要对 API 服务器进行负载均衡即可。

控制器和调度器的高可用性

由于控制器和调度器都会主动监听集群状态,并在发生变更时采取相应的操作,因此在多个实例中运行这些组件可能导致它们执行相同的操作并产生竞争状态,从而导致意外的影响。

因此,在运行这些组件的多个实例时,同一时间只有一个实例处于活动状态。它们通过选举机制确定活跃实例(默认情况下使用 --leader-elect 选项),其他实例处于待命状态。只有当领导者发生故障时,剩余的实例才会选举出新的领导者来接管工作。

控制器和调度器可以与 API 服务器和 etcd 配对运行。如果它们运行在不同的机器上,需要通过负载均衡器连接到 API 服务器。

领导者选举机制

控制平面组件使用的领导者选举机制不需要组件之间进行通信。其实现方式是在 API 服务器中创建一个资源,例如 Endpoint 资源:

[root@server7-master ~]$ kubectl get endpoints kube-scheduler -n kube-system -o yaml
apiVersion: v1
kind: Endpoints
metadata:
  annotations:
    control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"server7-master_6725412a-94ed-4d16-ae7f-3f6cd7543c40","leaseDurationSeconds":15,"acquireTime":"2021-11-03T07:46:00Z","renewTime":"2021-11-03T08:09:49Z","leaderTransitions":1}'

在资源的注解中,包含了一个名为 control-plane.alpha.kubernetes.io/leader 的字段,其中的 holderIdentity 字段包含了当前领导者的名称。

该机制的原理是通过乐观锁来确保如果多个实例尝试向资源中写入内容,只有一个实例会成功。第一个成功将自己的名称填入 holderIdentity 字段的实例将成为领导者。

一旦成为领导者,它默认每 2 秒必须更新 holderIdentity 字段,以便其他实例知道它仍在工作。一旦停止更新一段时间,其他实例将尝试将自己的名称写入资源中,以取代领导者的角色。

K8s 中应用发布流程

在 Kubernetes(K8s)中,应用发布是一个关键的流程,它确保应用程序能够在集群中顺利运行并具备高可用性。

应用发布流程

在 Kubernetes 中发布应用的流程如下:

  • 打包应用:首先,需要将应用程序打包到一个或多个容器镜像中。
  • 推送镜像:将打包好的镜像推送到镜像仓库中。
  • 发布应用描述:将应用的描述发布到 API 服务器。
  • 调度容器:API 服务器通过调度器,指示可用的工作节点上的 Kubelet 拉取镜像并运行容器。

下图提供了一个简单的示例来说明该流程:

发布流程

事件链

以一个包含 Deployment 清单的 YAML 文件举例,各控制器协同工作如下:

部署过程

  • 创建 Deployment 资源:kubectl 通过 HTTP POST 请求将清单发送到 API 服务器。API 服务器检查 Deployment 的定义,创建 Deployment 资源,并将其存储到 etcd,然后返回响应给 kubectl。
  • Deployment 控制器生成 ReplicaSet:当 Deployment 控制器监听到 API 服务器的新 Deployment 对象通知时,根据当前 Deployment 的定义,发送创建 ReplicaSet 的请求给 API 服务器,创建 ReplicaSet 资源。
  • ReplicaSet 控制器创建 Pod 资源:当 ReplicaSet 控制器接收到新的 ReplicaSet 对象通知时,根据 replica 数量和 Pod 选择器,检查是否有足够满足选择器的 Pod。然后,基于 ReplicaSet 的 Pod 模板,发送创建 Pod 的请求给 API 服务器,创建 Pod 资源。
  • 调度器分配节点给新的 Pod:新创建的 Pod 目前保存在 etcd 中,没有关联的运行节点。调度器监控到这样的 Pod 后,为其选择最佳节点分配,并将该节点分配给 Pod。Pod 的定义现在包含了它应该运行在哪个节点上。
  • Kubelet 运行 Pod 容器:在节点上,Kubelet 通过 API 服务器监听 Pod 的变更。当发现有分配到本节点的 Pod 后,Kubelet 检查 Pod 的定义,然后命令容器运行时启动 Pod 容器。

集群事件

在控制面板组件和 Kubelet 执行动作时,它们会向 API 服务器发送事件。通过创建事件资源来实现事件的发送。可以使用 kubectl get events 命令获取事件信息。

更常见的方式是使用 --watch 参数,在发布后持续观察事件:

[root@server4-master ~]$ kubectl delete -f kubia-st.yaml 
statefulset.apps "kubia" deleted
[root@server4-master manifests]$ kubectl get events --watch
LAST SEEN   TYPE     REASON    OBJECT        MESSAGE
0s          Normal   Killing   pod/kubia-0   Stopping container kubia
0s          Normal   Killing   pod/kubia-1   Stopping container kubia
0s          Normal   Killing   pod/kubia-2   Stopping container kubia
0s          Warning   FailedToUpdateEndpoint   endpoints/kubia-public   Failed to update endpoint default/kubia-public: Operation cannot be fulfilled on endpoints "kubia-public": the object has been modified; please apply your changes to the latest version and try again

发布后工作

应用程序在 Kubernetes 中运行后,以下是 Kubernetes 的工作:

  • API 服务器会持续确认应用的部署状态是否与描述一致。如果其中一个实例停止正常工作,例如进程崩溃或停止响应,Kubernetes 将自动重新启动该实例。
  • 如果整个工作节点宕机无法访问,故障节点上运行的所有容器将被调度到新节点上运行。
  • 在程序运行期间,可以手动随时调整副本数量,也可以根据节点的实时状态指标自动调整副本数。
  • 当容器在集群中频繁调度时,服务代理将确保服务始终可用。

K8s 中应用更新流程

Kubernetes 提供多种灵活和可控的方式来更新应用,保证应用在更新过程中的可用性和稳定性。

手动更新 Pod 内应用

假设 Pod 使用 ReplicaSet(RS)控制器来创建和管理,并且客户端通过 Service 来访问 Pod。如果要对 Pod 内的镜像进行升级,可以采取以下方式。

全量更新

直接删除所有现有的 Pod,然后创建新的 Pod。流程图如下:

手动升级

当使用 RS 控制器时,通过修改 RS 内的 Pod 模板镜像,然后删除旧的 Pod 实例,RS 会使用修改后的镜像来创建新的实例。

这种方式会导致应用在一定的时间内不可用。

蓝绿部署

先创建新的 Pod,成功运行后再删除旧的 Pod。流程图如下:

蓝绿部署

同样,先修改 RS 内的 Pod 模板镜像,创建新版本的 Pod 并确保正常运行后,可以修改 Service 的标签选择器(使用命令 kubectl set selector),将流量切换到新的 Pod,最后再删除旧版本的 Pod。这也叫做蓝绿部署,适用于不能中断服务的场景。

这种方法需要应用程序支持同时提供两个版本的服务,并且新版本应用不应对原有的关键数据格式或数据本身进行修改,以免导致之前版本的程序运行异常。

金丝雀发布

执行滚动升级操作流程图如下:

滚动升级

还可以执行滚动升级操作来逐步替代原有的 Pod,而不是同时创建新的 Pod 和删除旧的 Pod。具体操作是逐步缩减旧版本的 RS 控制器中的 Pod 数量,并扩展新版本的 RS 控制器中的 Pod 数量,以逐渐调整新旧版本 Pod 的比例。这就是金丝雀发布。同时,服务的选择器应包含两个版本的 Pod。

需要注意的是,手动执行滚动升级操作时容易出错,需要谨慎操作。

使用 RC 自动滚动升级

首先,创建一个 RC(Replication Controller)和服务,并使用 v1 版本的镜像:

[root@server4-master ~]$ vi kubia-v1.yaml
apiVersion: v1
kind: ReplicationController
metadata:
  name: kubia-v1
spec:
  replicas: 3
  template:
    metadata:
      name: kubia
      labels:
        app: kubia
    spec:
      containers:
      - image: luksa/kubia:v1
        name: nodejs
---
apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  type: NodePort
  selector:
    app: kubia
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 30002
[root@server4-master ~]$ kubectl create -f kubia-v1.yaml
replicationcontroller/kubia-v1 created
service/kubia created

发布成功后,将有 3 个 v1 版本的 pod 和相应的服务开始运行。通过浏览器访问 30002 端口,并保持输出:

[root@server4-master ~]$ while true; do curl http://192.168.2.204:30002; sleep 1; done
This is v1 running in pod kubia-v1-9ql94
This is v1 running in pod kubia-v1-52pvf
You've hit kubia-k2l4p
You've hit kubia-k2l4p
This is v1 running in pod kubia-v1-52pvf
You've hit kubia-p9wp5

使用 kubectl rolling-update 命令(已废弃)直接替换掉镜像,进行滚动更新:

[root@k8s-master 4]$ kubectl rolling-update kubia-v1 kubia-v2 --image=luksa/kubia:v2
Command "rolling-update" is deprecated, use "rollout" instead
Created kubia-v2
Scaling up kubia-v2 from 0 to 3, scaling down kubia-v1 from 3 to 0 (keep 3 pods available, don't exceed 4 pods)
Scaling kubia-v2 up to 1
Scaling kubia-v1 down to 2
Scaling kubia-v2 up to 2
Scaling kubia-v1 down to 1
Scaling kubia-v2 up to 3
Scaling kubia-v1 down to 0
Update succeeded. Deleting kubia-v1
replicationcontroller/kubia-v2 rolling updated to "kubia-v2"

在开发过程中,经常会推送修改后的应用到同一个镜像标签,这可能导致镜像不会被重新拉取。或者在没有拉取过旧版本镜像的节点上,会拉取新的镜像,因此可能会同时运行两个不同版本的 pod。

为了确保始终使用最新的镜像,可以将容器的 imagePullPolicy 属性设置为 Always。如果不指定镜像标签,即默认使用 latest 标签,则策略默认为 Always,但如果指定了其他标签,则策略默认为 IfNotPresent

自动滚动升级的步骤如下:

  • 通过复制 RC 控制器 kubia-v1 创建一个名为 kubia-v2 的 RC 控制器,并在 kubia-v2 的模板中将镜像版本改为 kubia:v2
  • 立即创建 RC 控制器 kubia-v2,并将初始期望副本数设置为 0。
  • 修改 kubia-v1kubia-v2 的标签选择器,添加一个额外的 deployment 标签。
  • 修改旧版本 pod 的标签,添加一个额外的 deployment 标签。
  • 以每次 1 个单位的步长,缩减 kubia-v1 的 pod 数量,并同时扩展 kubia-v2 的 pod 数量,新的 pod 也带有一个额外的 deployment 标签。
  • 最终,旧版本的 pod 数量被缩减到 0,全部被新版本的 pod 替代,旧的 RC 控制器 kubia-v1 被删除。

通过这种滚动升级的方式,所有的伸缩请求都由 kubectl 客户端而不是主节点执行。一旦 kubectl 在执行升级时失去连接,升级过程将被中断,pod 和 RC 最终会处于中间状态。一种更先进的方式是使用 Deployment 资源进行部署。