OceaBase开发者大会落地上海!4月20日共同探索数据库前沿趋势!报名戳 了解详情
写点什么

每天部署数千个容器实例,扩缩容复杂性该如何管理?

  • 2020-12-29
  • 本文字数:6048 字

    阅读完需:约 20 分钟

每天部署数千个容器实例,扩缩容复杂性该如何管理?

无论是大型软件公司还是小型软件公司,现在每天都要部署数千个容器实例,这种扩缩容的复杂性是他们必须要管理的。本文介绍了如何将 Kubernetes 纳入到现有的传统 CI/CD 管道中,并实现服务的高可用性,以及随时在生产环境中进行代码变更。


基于容器的微服务架构改变了开发和运维团队测试和部署现代应用程序 / 服务的方式。容器通过简化应用程序的扩缩容和部署来帮助公司实现现代化,但容器也创建了一个全新的基础设施生态系统,从而引入了新的挑战和更多的复杂性。


无论是大型软件公司还是小型软件公司,现在每天都要部署数千个容器实例,这种扩缩容的复杂性是他们必须要管理的。那么他们是怎么做的呢?



秘诀就是 Kubernetes


Kubernetes 最初由 Google 开发,是一个开源的容器编排平台,旨在自动化容器化应用程序的部署、扩缩容和管理。


在本教程中,我们将介绍如何将 Kubernetes 纳入到现有的传统 CI/CD 管道中,并实现服务的高可用性,以及随时(是的,任何时候都不会影响服务)在生产环境中进行代码变更。


使用到的工具


本文是基于你对如下主题有基本 / 良好的理解的基础上的。


  • Kubernetes(我们的服务在 Kubernetes 上运行)

  • Jenkins 以及 Jenkins 共享库(CI/CD 工具)

  • GIT(SCM 工具)

  • HAProxy(网络负载均衡器)

  • Ansible(配置管理工具)


CI/CD 安装


  • Jenkins 服务器——与 Docker、Ansible 和 Kubectl 一起安装(将 Kube 管理配置.kube 从 K8s master 复制到 Jenkins 服务器的主目录中)

  • 具有一主两从三个节点的 K8s 集群

  • HAProxy 服务器


使用 K8s 的 CI/CD


GIT & Jenkins


让我们来详细介绍一下如何配置所有这些不同的工具,并使其能够完美地运行。


我们的示例应用程序,名为 shoppingapp,在 GitHub 中有三个存储库,每个库都具有特定的微服务。


  • shoppingapp-home:首页微服务

  • shoppingapp-kids :儿童版页面微服务

  • shoppingapp-mens:男士版页面微服务


类似地,针对这三个库我们有三个 Jenkins 管道作业,并在存储库中设置了 GitHub webhook,以便在有新提交时自动启动构建。



Jenkins 管道作业


我们来看下 shoppingapp-home 作业的配置。




Jenkins 管道配置


在 shoppingapp-home 的管道作业中,仓库 URL 指向 shoppingapp-home 的 GitHub 存储库。类似地,另外两个管道作业也指向各自的存储库。


下面是 shoppingapp-home 存储库中的 Jenkinsfile。


@Library('jenkins-shared-library') _pipeline {   agent any   environment {       app = 'shoppingapp'       service = 'shoppingapp-home'       registryCredential = 'dockerhub'       dockerImage = ''       imageid = "deepanmurugan/shoppingapp-home:$BUILD_NUMBER"   }   stages {       stage('Build') {            steps {        script {            dockerImage = dockerbuild(imageid)        }        }         }       stage('Test') {           steps {                        testcase()           }       }       stage('Publish') {           steps{               script {            imagepush(imageid)                   }           }       }       stage('Pull Playbook Repo') {        steps {        dir('/tmp/ansible-playbooks/') {            gitcheckout(                branch: "master",                repoUrl: "https://github.com/deepanmurugan/Ansible_Playbook.git"            )        }    }       }       stage ('Deploy') {           steps {           dir('/tmp/ansible-playbooks/') {               script{            deploytok8s(imageid,app,service)               }           }           }       }   }}
复制代码


shoppingapp-home 存储库的 Jenkinsfile 以下是管道作业中的不同步骤。作业声明了几个变量,app——指我们的应用程序名,service——指我们的服务名,registryCredentials——是我们保存在 Jenkins 中的 dockerhub 用户名和密码,imageid——带有标签的 Docker 容器镜像名,这里的标签将是 Jenkins 的内部版本号。


在上面的 Jenkinsfile 中,我使用了共享库(如果你不熟悉共享库的话,请参考 Jenkins 网站)。


在“Build”(构建)阶段,我在共享库中调用了函数dockerbuild,并将 imageid 作为参数进行传递。以下是dockerbuild函数的定义。


def call(String dockerImage) {  script {    docker.build "${dockerImage}"  }}
复制代码


Build 阶段


它仅使用我们传递的 imageid 作为参数调用 docker.build,就会触发docker build -t imageid, 并基于 Dockerfile 创建一个 Docker 镜像。我们稍后再看 Dockerfile。


下一步是“Test”(测试),此时我们可以运行测试用例。我目前不执行任何测试用例,因为这是一个非常基础的应用程序。


def call() {    sh """        echo "Testing the docker built image"    """}
复制代码


Test 阶段


接下来,我们进入“Publish”(发布)阶段,该阶段将再次调用imagepush函数,其中 imageid 作为共享库中的参数。该函数基本上要登录到 Docker Hub,并将镜像推送到 Docker Hub 中。


def call(String dockerImage) {  echo "${dockerImage}"  withCredentials([usernamePassword(credentialsId: 'dockerhub', usernameVariable: 'hubUsername', passwordVariable: 'hubPassword')]) {        sh """            docker login --username="${hubUsername}" --password="${hubPassword}"            docker push "${dockerImage}"        """    }}
复制代码


Publish 阶段


下一步是“Pull Playbook Repo”,仅克隆存储库,该库中包含了我们所有的 playbook。


def call(Map stageParams){  checkout([$class: 'GitSCM', branches: [[name: stageParams.branch]], userRemoteConfigs: [[credentialsId: 'github_repo', url: stageParams.repoUrl]]])}
复制代码


Pull Playbook Repo 阶段


接下来是“Deploy”(部署)阶段,该阶段触发 playbook 将容器部署到 Kubernetes 集群中。


def call(String dockerImage, String app, String service) {    sh """        ansible-playbook deploy_k8s.yml --extra-vars \"image_id=${dockerImage} app_name=${app} service_name=${service}\"    """}
复制代码


Deploy 阶段


现在,我们对如何开发和维护每个微服务的存储库、如何配置 Jenkins 管道来创建 Docker 镜像并将镜像推送到 Docker Hub 以及触发 Ansible Playbook 将容器部署到 K8s 集群都已经相当清楚了。那我们来看一下它的 Dockerfile 是什么样的。


Dockerize 应用程序


FROM python:3.7.3-alpine3.9RUN mkdir -p /appWORKDIR /appCOPY ./src/requirements.txt /app/requirements.txtRUN pip install -r requirements.txtCOPY ./src/ /appENV FLASK_APP=server.pyCMD flask run -h 0.0.0.0 -p 5000
复制代码


Dockerfile


Dockerfile 使用 python:3.7.3-alpine3.9 作为基础镜像,我们安装了一个 flask 应用程序来打印某些文本。该应用程序运行在端口 5000 上。


使用 Ansible Playbook 来进一步接管


下面是 Ansible Playbook 的 deploy_k8s.yml,它是由 Jenkins 管道作业触发的。


- hosts: localhost  user: ubuntu  tasks:  - name: Deploy the service    k8s:      state: present      definition: "{{ lookup('template', 'k8s/{{app_name}}/{{service_name}}/deployment.yml') | from_yaml }}"      validate_certs: no      namespace: default  - name: Deploy the application    k8s:      state: present      validate_certs: no      namespace: default      definition: "{{ lookup('template', 'k8s/{{app_name}}/{{service_name}}/service.yml') | from_yaml }}"  - name: Deploy the Ingress    k8s:      state: present      validate_certs: no      namespace: default      definition: "{{ lookup('template', 'k8s/{{app_name}}/common/ingress.yml') | from_yaml }}"
复制代码


使用 Ansible 部署 K8s 组件


Kubernetes 集群


现在我们来看一下 K8s 集群。集群中有一个主节点和两个工作节点。


ubuntu@kube-master:~$ kubectl get nodesNAME STATUS ROLES AGE VERSIONkube-master Ready master 10d v1.19.4kube-worker1 Ready <none> 10d v1.19.4kube-worker2 Ready <none> 9d v1.19.4
复制代码


此外,我们还安装了 Traefik Ingress 控制器,作为集群中两个副本的部署(deployment)。


ubuntu@kube-master:~$ kubectl get pods -n kube-system|grep ingresstraefik-ingress-controller-6b7f594d46–5jqzq 1/1 Running 0 7d12htraefik-ingress-controller-6b7f594d46-vvfch 1/1 Running 0 8d
复制代码


为了无缝地部署 / 扩展我们的服务,我们以特定的格式组织了 Ansible Playbook 库。



K8s 服务定义的目录结构


每个服务都有两个特定于该服务的文件 deployment 和 service,common 目录中则包含了 ingress 配置定义。


apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app: "{{ app_name }}"  name: {{ service_name }}-deloymentspec:  replicas: 2  selector:    matchLabels:      app: "{{ app_name }}"      task: "{{ service_name }}"  template:    metadata:      labels:        app: "{{ app_name }}"        task: "{{ service_name }}"    spec:      containers:      - name: {{ service_name }}-pod        image: "{{ image_id }}"        imagePullPolicy: Always        ports:        - containerPort: 5000      imagePullSecrets:        - name: dockerhubsecret  strategy:    type: RollingUpdate    rollingUpdate:      maxSurge: 1      maxUnavailable: 0
复制代码


deployment.yml


上面的 deployment 文件是通用的,它从 Jenkins 管道作业中获取所有传输到 deploy_k8s.yml 的变量,并将其传输到该 K8s 的 deployment.yml 中。因此,若要用更多的微服务来扩展应用程序,只需在 Ansible Playbook 库中创建适当的目录结构以及具有 ingress 配置的 Jenkins 管道,即可实现完美地运行。


在创建部署之后,我们需要创建一个服务来公开该部署。该服务将端口 80 映射到 pod 的端口 5000。


apiVersion: v1kind: Servicemetadata:  name: {{ service_name }}-servicespec:  selector:    app: "{{ app_name }}"    task: "{{ service_name }}"  ports:    - protocol: TCP      port: 80      targetPort: 5000      name: http
复制代码


service.yml


Deployment.yml 和 Service.yml 文件对于 shoppingapp 服务下的所有微服务都是通用的。


另外,我们看一下 common/ingress.yml 中 ingress 配置。ingress 的主机(host)名是 app.shoppingapp.com,它具有基于路径的规则,可将流量重定向到特定的服务。


apiVersion: extensions/v1beta1kind: Ingressmetadata:  name: {{ app_name }}-ingress  annotations:    kubernetes.io/ingress.class: traefik    traefik.frontend.rule.type: PathPrefixStripspec:  rules:  - host: app.shoppingapp.com    http:      paths:      - path: /home        backend:          serviceName: shoppingapp-home-service          servicePort: http      - path: /kids        backend:          serviceName: shoppingapp-kids-service          servicePort: http      - path: /mens        backend:          serviceName: shoppingapp-mens-service          servicePort: http
复制代码


ingress.yml


引入 HAProxy


另外,我还使用了 HAProxy 负载均衡器来平衡 K8s 集群中两个节点之间的流量。我已经将前端和后端添加到现有的 HAProxy 配置文件中。后端服务器是 K8s 集群中的工作节点。


frontend http_frontbind *:80mode httpdefault_backend http_back
复制代码


backend http_backbalance roundrobinserver kube 172.31.35.122:32365 checkserver kube 172.31.40.13:32365 check
复制代码


端口 32365 是 traefik-ingress-service 服务公开的端口。DNS 是为 ingress.yml 中提到的主机名 app.shoppingapp.com 创建的,该主机名会被解析为 HAProxy IP(我使用的是 AWS Route 53 内部域名来创建的域名和 DNS 记录)


ubuntu@jenkins_ansible:~$ kubectl describe svc traefik-ingress-service -n kube-systemName: traefik-ingress-serviceNamespace: kube-systemLabels: <none>Annotations: <none>Selector: k8s-app=traefik-ingress-lbType: NodePortIP: 10.102.149.216Port: web 80/TCPTargetPort: 80/TCPNodePort: web 32365/TCPEndpoints: 10.244.1.99:80,10.244.3.19:80Port: admin 8080/TCPTargetPort: 8080/TCPNodePort: admin 31387/TCPEndpoints: 10.244.1.99:8080,10.244.3.19:8080Session Affinity: NoneExternal Traffic Policy: ClusterEvents: <none>
复制代码


真实的流程


对于这个为我们的微服务应用程序而创建的 CI/CD 管道,其所需的所有组件我们都已经了解了。现在,让我们将这些组件组装起来,以了解该流程的工作原理。


  • 开发人员提交代码到微服务仓库(shoppingapp-kids)

  • GitHub 中配置的 Webhook 通知 Jenkins 并触发相应的管道作业(shoppingapp kids)

  • 管道作业克隆 shoppingapp-kids 存储库,并开始执行 Jenkinsfile。

  • Jenkinsfile 中的构建阶段使用克隆的 shoppingapp-kids 存储库中的 Dockerfile 创建 Docker 镜像(deepanmurugan/shoppingapp-kids:21)。

  • 测试 Docker 镜像,如果测试通过,则将镜像推送到 Docker Hub。

  • 触发 Ansible Playbook,使用 app_name、service_name 以及 image_id 变量将 Docker 镜像部署到 K8s 集群。

  • Ansible Playbook 将读取所有与 deployment/servive/ingress 相关的 K8s 定义,并在集群中创建所需的组件。


向管道中添加新的微服务


假设我们有一个请求,要求在该架构中再添加一个名为 shoppingapp-ladies 的微服务。开发人员创建了一个名为 shoppingapp-ladies 的存储库,并使用相同的 Docker 和 Jenkinsfile 提交了代码,唯一的变化是 Jenkinsfile 中的 service_name=shoppingapp-ladies 变量。



修改文件夹结构后,添加新的微服务


修改 common/ingress.yml,并添加一个名为 /ladies 的新路径,端点是 shoppingapp-ladies-service。


- path: /ladiesbackend:serviceName: shoppingapp-ladies-serviceservicePort: http
复制代码


拷贝现有的 Jenkins 管道作业,并创建一个名为 shoppingapp-ladies 的新管道作业。



Jenkins 管道作业列表


在新作业 shoppingapp-ladies 的管道中,只需添加新的存储库 URL,然后执行管道作业,仅此而已。它将创建一个新的部署和服务并修改 ingress。


ubuntu@jenkins_ansible:/opt/python$ curl app.shoppingapp.com/ladiesWelcome to shoppingapp — Ladies section — shoppingapp-ladies-deloyment-656f6f9d9f-dms8w
复制代码


原文链接


https://medium.com/awsblogs/ci-cd-with-kubernetes-3c29e8073c38

2020-12-29 17:073814

评论 1 条评论

发布
用户头像
部署图画的很形象
2020-12-30 12:44
回复
没有更多了
发现更多内容

华为19级大佬10年心血终成百页负载均衡高并发网关设计实战文档

做梦都在改BUG

Java 负载均衡 高并发 网关设计

人人自媒体的时代,程序员该如何利用好自己的优势?我记住了这些神器...

浅羽技术

工具 自媒体 写作技巧 三周年连更

【问题解决】解决 swagger2 默认地址失效

Java你猿哥

Java JAVA开发 swagger2 java项目

高可靠多层板制造服务再获认可!华秋荣获创想三维优秀质量奖

华秋电子

太强了!阿里技术官新产"Spring高级源码阅读指南",爆火全网

Java你猿哥

Java spring SSM框架 spring ioc spring aoc

EMQX Cloud BYOC版本发布:在您的云上体验全托管的MQTT消息服务

EMQ映云科技

物联网 IoT 云服务 mqtt 企业号 4 月 PK 榜

OpenHarmony社区运营报告(2023年3月)

OpenHarmony开发者

OpenHarmony

应用部署引起上游服务抖动问题分析及优化实践方案

京东科技开发者

应用部署 jsf 企业号 4 月 PK 榜 上游服务抖动

更安全、更低耗的微服务架构改造之道

华为云开发者联盟

云计算 后端 华为云 华为云开发者联盟 企业号 4 月 PK 榜

SpringBoot 集成 atomikos 实现分布式事务

做梦都在改BUG

【4.7-4.14】写作社区优秀技术博文一览

InfoQ写作社区官方

热门活动 优质创作周报

揭开“虚拟化已死”的5大谎言与真相

科技热闻

业内首份!医疗数据安全政策汇编发布(附下载)

极盾科技

数据安全

SLBR通过自校准的定位和背景细化来去除可见的水印

合合技术团队

人工智能 图像处理 水印消除

总结一下Redis的缓存雪崩、缓存击穿、缓存穿透

做梦都在改BUG

新手测试必学的 API 接口文档知识

Apifox

测试 入门 接口文档 API API 文档

文盘Rust -- 用Tokio实现简易任务池

京东科技开发者

rust runtime tokio 企业号 4 月 PK 榜

BAT必刷!GitHub顶级“2023并发编程全优笔记”晋升公司架构组!

Java你猿哥

Java 多线程 面经 SSM框架 多线程并发

活久见,java8 lamdba Collectors.toMap()报NPE

做梦都在改BUG

Java中的异常处理详解(try、catch、finally、throw、throws) | 社区征文

共饮一杯无

Java 异常处理 三周年连更

HummerRisk V1.0.0:架构全面升级,开启新篇章

HummerCloud

云原生安全

全新适配鸿蒙生态,Cocos引擎助力3D应用开发

HarmonyOS开发者

HarmonyOS

后端开挂!一个接口实现CRUD操作,这款工具绝了!

Java你猿哥

Java 接口 后端 crud

阿里P8架构师爆肝分享内部开源的JVM垃圾回收PDF文档,共23.3W字

做梦都在改BUG

Java JVM 垃圾回收

Netty框架详解:高性能网络编程的设计与实现

做梦都在改BUG

网络编程 Netty 高性能

一名开发者眼中的TiDB与MySQL选择

TiDB 社区干货传送门

数据库架构选型

得帆云DeMDM,业内首家基于低代码技术构建的主数据管理平台

得帆信息

低代码 数据治理 数据清洗 主数据管理 主数据管理平台

被裁后,狂刷大牛分享的607页JUC源码分析笔记,立马拿蚂蚁offer

做梦都在改BUG

Java 高并发 JUC JCF 集合框架

从零学习SDK(6)调试和测试SDK的库

MobTech袤博科技

华秋PCB生产工艺 | 第十二道主流程之FQC

华秋电子

爆肝一月!527页文档详解SpringCloud微服务和分布式系统实践

小小怪下士

Java 分布式 微服务 后端 SpringCloud

每天部署数千个容器实例,扩缩容复杂性该如何管理?_服务革新_Deep_InfoQ精选文章