本文发布时间已超过一年。较旧的文章可能包含过时的内容。请检查页面上的信息自发布以来是否已过时。

使用 Jenkins 在 Kubernetes 中实现零停机部署

自从我们将 Kubernetes 持续部署Azure 容器服务 插件添加到 Jenkins 更新中心以来,“如何创建零停机部署”是我们最常被问到的问题之一。我们在 Azure 上创建了一个快速入门模板,以演示零停机部署的样子。虽然我们的示例使用了 Azure,但这个概念很容易应用于所有 Kubernetes 安装。

滚动更新

Kubernetes 支持滚动更新策略,以逐步替换旧的 Pod,同时继续为客户端提供服务,而不会导致停机。要执行滚动更新部署,请执行以下操作:

  • .spec.strategy.type 设置为 RollingUpdate(默认值)。
  • .spec.strategy.rollingUpdate.maxUnavailable.spec.strategy.rollingUpdate.maxSurge 设置为一些合理的值。
    • maxUnavailable:在更新过程中可能不可用的 Pod 的最大数量。这可以是绝对数量或副本计数的百分比;默认值为 25%。
    • maxSurge:在所需的 Pod 数量之上可以创建的最大 Pod 数量。同样,这可以是绝对数量或副本计数的百分比;默认值为 25%。
  • 为你的服务容器配置 readinessProbe,以帮助 Kubernetes 确定 Pod 的状态。Kubernetes 将仅将客户端流量路由到具有健康存活探针的 Pod。

我们将使用官方 Tomcat 镜像的部署来演示这一点

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: tomcat-deployment-rolling-update
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: tomcat
        role: rolling-update
    spec:
      containers:
      - name: tomcat-container
        image: tomcat:${TOMCAT_VERSION}
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /
            port: 8080
  strategy:
    type: RollingUpdate
    rollingUp      maxSurge: 50%

如果当前部署中运行的 Tomcat 版本为 7,我们可以将 ${TOMCAT_VERSION} 替换为 8,并将其应用到 Kubernetes 集群。使用 Kubernetes 持续部署Azure 容器服务 插件,可以从环境变量中获取该值,从而简化部署过程。

在后台,Kubernetes 像这样管理更新

Deployment Process

  • 最初,所有 Pod 都运行 Tomcat 7,并且前端服务将流量路由到这些 Pod。
  • 在滚动更新期间,Kubernetes 会关闭一些 Tomcat 7 Pod,并创建相应的新 Tomcat 8 Pod。它确保
    • 所需 Pod 中最多 maxUnavailable 个 Pod 不可用,也就是说,至少应有 (replicas - maxUnavailable) 个 Pod 为客户端流量提供服务,在我们的例子中为 2-1=1 个。
    • 在更新过程中最多可以创建 maxSurge 个 Pod,即在我们的例子中为 2*50%=1 个。
  • 一个 Tomcat 7 Pod 被关闭,并创建一个 Tomcat 8 Pod。Kubernetes 不会将流量路由到任何一个 Pod,因为它们的就绪探针尚未成功。
  • 当新的 Tomcat 8 Pod 准备就绪(由就绪探针确定)时,Kubernetes 将开始将流量路由到它。这意味着在更新过程中,用户可能会看到旧服务和新服务。
  • 滚动更新继续关闭 Tomcat 7 Pod 并创建 Tomcat 8 Pod,然后将流量路由到准备就绪的 Pod。
  • 最后,所有 Pod 都运行 Tomcat 8。

滚动更新策略确保我们始终有一些就绪的后端 Pod 为客户端请求提供服务,因此不会发生服务停机。但是,需要格外小心

  • 在更新期间,旧 Pod 和新 Pod 都可能为请求提供服务。如果服务层中没有明确定义的会话亲和性,则用户可能会被路由到新 Pod,然后又返回到旧 Pod。
  • 这也要求您为数据和 API 维护良好定义的前向和后向兼容性,这可能具有挑战性。
  • Pod 启动后可能需要很长时间才能准备好接收流量。可能会有一个很长的窗口期,在此期间,与平时相比,为流量提供服务的后端 Pod 较少。通常,这应该不是问题,因为我们倾向于在服务不那么繁忙时进行生产升级。但这也会延长问题 1 的时间窗口。
  • 我们无法对正在创建的新 Pod 进行全面的测试。将应用程序更改从开发/ QA 环境转移到生产环境可能会带来破坏现有功能的持续风险。就绪探针可以执行一些工作来检查就绪情况,但是,它应该是一个可以定期运行的轻量级任务,不适合用作启动完整测试的入口点。

蓝/绿部署

TechTarget 引用的蓝/绿部署

蓝/绿部署是一种用于发布软件代码的变更管理策略。蓝/绿部署(也可能称为 A/B 部署)需要两个配置完全相同的相同硬件环境。当一个环境处于活动状态并为最终用户提供服务时,另一个环境保持空闲。

容器技术提供了运行所需服务的独立环境,这使得创建蓝/绿部署中所需的相同环境变得非常容易。松散耦合的服务 - ReplicaSets 和 Kubernetes 中基于标签/选择器的服务路由使得在不同的后端环境之间切换变得容易。使用这些技术,Kubernetes 中的蓝/绿部署可以按如下方式进行

  • 在部署之前,基础设施的准备工作如下:
    • 准备蓝部署和绿部署,分别将 TOMCAT_VERSION=7TARGET_ROLE 设置为 blue 或 green。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: tomcat-deployment-${TARGET_ROLE}
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: tomcat
        role: ${TARGET_ROLE}
    spec:
      containers:
      - name: tomcat-container
        image: tomcat:${TOMCAT_VERSION}
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /
            port: 8080
  • 准备公共服务端点,该端点最初路由到其中一个后端环境,例如 TARGET_ROLE=blue
kind: Service
apiVersion: v1
metadata:
  name: tomcat-service
  labels:
    app: tomcat
    role: ${TARGET_ROLE}
    env: prod
spec:
  type: LoadBalancer
  selector:
    app: tomcat
    role: ${TARGET_ROLE}
  ports:
    - port: 80
      targetPort: 8080
  • 可选地,准备一个测试端点,以便我们可以访问后端环境进行测试。它们与公共服务端点类似,但仅供开发/运维团队在内部访问。
kind: Service
apiVersion: v1
metadata:
  name: tomcat-test-${TARGET_ROLE}
  labels:
    app: tomcat
    role: test-${TARGET_ROLE}
spec:
  type: LoadBalancer
  selector:
    app: tomcat
    role: ${TARGET_ROLE}
  ports:
    - port: 80
      targetPort: 8080
  • 更新非活动环境中的应用程序,例如绿色环境。在部署配置中设置 TARGET_ROLE=greenTOMCAT_VERSION=8 以更新绿色环境。
  • 通过 tomcat-test-green 测试端点测试部署,以确保绿色环境已准备好为客户端流量提供服务。
  • 通过使用 TARGET_ROLE=green 更新服务配置,将前端服务路由切换到绿色环境。
  • 在公共端点上运行其他测试,以确保其正常工作。
  • 现在,蓝色环境处于空闲状态,我们可以
    • 使其保留旧应用程序,以便如果新应用程序出现问题,我们可以回滚
    • 将其更新,使其成为活动环境的热备份
    • 减少其副本计数以节省占用的资源

Resources

与滚动更新相比,蓝/绿更新* 公共服务要么路由到旧应用程序,要么路由到新应用程序,但永远不会同时路由到两者。

  • 新 Pod 准备就绪所需的时间不会影响公共服务质量,因为只有当所有 Pod 都经过测试准备就绪后,流量才会路由到新 Pod。
  • 我们可以在新环境为任何公共流量提供服务之前对其进行全面测试。请记住,这是在生产环境中,测试不应污染实时应用程序数据。

Jenkins 自动化

Jenkins 提供易于设置的工作流程来自动化你的部署。通过 Pipeline 支持,可以灵活地构建零停机部署工作流程,并可视化部署步骤。为了方便 Kubernetes 资源的部署过程,我们发布了基于 kubernetes-client 构建的 Kubernetes 持续部署Azure 容器服务 插件。你可以将资源部署到 Azure Kubernetes Service (AKS) 或通用 Kubernetes 集群,而无需 kubectl,它支持资源配置中的变量替换,因此你可以将特定于环境的资源部署到集群,而无需更新资源配置。我们创建了一个 Jenkins Pipeline 来演示到 AKS 的蓝/绿部署。流程如下:

Jenkins Pipeline

  • 预清理:清理工作区。
  • SCM:从源代码管理系统中提取代码。
  • 准备镜像:准备应用程序 Docker 镜像并将其上传到某个 Docker 存储库。
  • 检查环境:确定活动和非活动环境,这驱动了以下部署。
  • 部署:将新的应用程序资源配置部署到非活动环境。使用 Azure 容器服务插件,可以通过以下方式完成:
acsDeploy azureCredentialsId: 'stored-azure-credentials-id',
          configFilePaths: "glob/path/to/*/resource-config-*.yml",
          containerService: "aks-name | AKS",
          resourceGroupName: "resource-group-name",
          enableConfigSubstitution: true
  • 验证暂存:验证到非活动环境的部署,以确保其正常工作。同样,请注意这是在生产环境中,因此请注意不要在测试期间污染实时应用程序数据。
  • 确认:可选地,发送电子邮件通知以进行手动用户批准,以继续进行实际的环境切换。
  • 切换:将前端服务端点路由切换到非活动环境。这只是对 AKS Kubernetes 集群的另一个服务部署。
  • 验证生产:验证前端服务端点在新环境中是否正常工作。
  • 后清理:对临时文件进行一些后清理。

对于滚动更新策略,只需将部署配置部署到 Kubernetes 集群,这是一个简单的单步操作。

整合在一起

我们在 Azure 上构建了一个快速入门模板,以演示如何使用 Jenkins 对 AKS (Kubernetes) 进行零停机部署。转到 Kubernetes 上的 Jenkins 蓝/绿部署,然后单击“部署到 Azure”按钮以获取工作演示。此模板将预配

  • 一个 AKS 集群,包含以下资源
    • 两个类似的部署,分别代表“blue”和“green”环境。两者最初都设置为 tomcat:7 镜像。
    • 两个测试端点服务 (tomcat-test-bluetomcat-test-green) 连接到相应的部署,可用于测试部署是否已准备好投入生产使用。
    • 一个生产服务端点 (tomcat-service) 代表用户将访问的公共端点。最初,它路由到 "蓝色" 环境。
  • 一个在 Ubuntu 16.04 VM 上运行的 Jenkins master,并配置了 Azure 服务主体凭据。Jenkins 实例有两个示例作业
    • AKS Kubernetes 滚动更新部署管道,用于演示到 AKS 的滚动更新部署。
    • AKS Kubernetes 蓝/绿部署管道,用于演示到 AKS 的蓝/绿部署。
    • 我们在快速入门模板中没有包含电子邮件确认步骤。要添加该步骤,您需要在 Jenkins 系统配置中配置电子邮件 SMTP 服务器详细信息,然后在“切换”之前添加一个 Pipeline 阶段
stage('Confirm') {
    mail (to: '[email protected]',
        subject: "Job '${env.JOB_NAME}' (${env.BUILD_NUMBER}) is waiting for input",
        body: "Please go to ${env.BUILD_URL}.")
    input 'Ready to go?'
}

按照 步骤 设置资源,您可以通过启动 Jenkins 构建作业来尝试。