写点什么

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

  • 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:074021

评论 1 条评论

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

1688商品API接口:电商数据自动化的新引擎

Noah

虚幻引擎5与Perforce Helix Core集成使用案例:打造AAA级科幻游戏《Project Vesperi》

龙智—DevSecOps解决方案

版本控制 游戏开发 Perforce Helix Core

职场火焰杯测试开发大赛报名倒计时:最后一天!

测试人

软件测试

前端常见的页面自适应布局方案

秃头小帅oi

职场火焰杯测试开发大赛报名倒计时:最后一天!

测吧(北京)科技有限公司

测试

Python打包成exe的方法介绍

我再BUG界嘎嘎乱杀

Python 后端 开发 打包exe

数字先锋| 塞上江南新面貌:惠企便民政务兴!

天翼云开发者社区

云计算 云服务 政务云

云内GSLB技术及应用场景

天翼云开发者社区

云计算 容灾备份

谈谈天翼云VPCE

天翼云开发者社区

云计算 VPC终端节点

使用 Django 中的 filter 方法进行数据查询

我再BUG界嘎嘎乱杀

Python django 后端 开发

API面临哪些风险,如何做好API安全

德迅云安全杨德俊

自动化运维实战:Docker与TASKCTL在ETL调度中的深度应用

敏捷调度TASKCTL

运维 自动化运维 TASKCTL #docker

虚拟ECU:汽车空调压缩机控制系统

DevOps和数字孪生

虚拟ECU 汽车行业

面向对象变成VS函数式编程

FunTester

智算引领 AI启航,中国电信天翼云助推辽宁数智发展!

天翼云开发者社区

人工智能 云计算

穿越周期!天翼云laaS+PaaS全年市场份额跃居中国公有云市场第三!

天翼云开发者社区

云计算 云服务 IDC

一条数据包从收到发--交换芯片篇(一)

天翼云开发者社区

云计算 架构 交换芯片

在 Django 中设计爬虫系统的数据模型与多对多关系

我再BUG界嘎嘎乱杀

Python django 爬虫

.NET快速实现网页数据抓取

EquatorCoco

.net 网页

2023全球DDoS攻击态势分析,与众多行业专家共议DDoS破局之道

百度安全

总是被低估,从未被超越,揭秘QQ极致丝滑背后的硬核IM技术优化

JackJiang

即时通讯;IM;网络编程

英伟达一季净利暴增 6 倍;消息称 TikTok 计划本周大幅裁员丨 RTE 开发者日报 Vol.210

声网

迭代器的一些简单理解

不在线第一只蜗牛

迭代 迭代器

LinkedIn最新研究:图+向量数据库,客服解答时间缩短64%

Fabarta

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