写点什么

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

2020 年 12 月 29 日

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

无论是大型软件公司还是小型软件公司,现在每天都要部署数千个容器实例,这种扩缩容的复杂性是他们必须要管理的。本文介绍了如何将 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:071416

评论 1 条评论

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

用户密码验证函数

任小龙

架构师训练营 Week 11 作业

Wancho

极客大学架构师训练营 0 期 week 11 学习笔记

chun1123

安全 高可用系统的架构

系统不可用的原因和解决方案

李广富

架构师训练营 - 第 11 周作业

Jam

Flink算子状态-9

小知识点

scala 大数据 flink

第11周学习总结

刘卓

极客大学架构师训练营 0 期 week 11 作业

chun1123

高可用 密码校验

极客时间架构师训练营 - week11 - 作业 2

jjn0703

极客大学架构师训练营

系统高可用

阿飞

架构师

攻击与防护

GalaxyCreater

安全

WEB安全知识点汇总

林昱榕

WEB安全 漏洞

第11周总结

上山砍柴

week11学习总结

burner

高可用的系统架构

莫莫大人

极客大学架构师训练营

week11高可用

burner

极客大学架构师训练营第十一周学习总结

竹森先生

架构师训练营第十一周作业

子豪sirius

架构师训练营 Week 11 总结

Wancho

系统稳定高可用的方案以及用户密码验证函数

Acker飏

etcd的高可用

阿飞

Week 11 作业

鱼_XueTr

架构师训练营 第十一周 总结

CR

架构师课作业 - 第十一周

Tulane

【高并发】高并发秒杀系统架构解密,不是所有的秒杀都是秒杀!

冰河

高并发 架构设计 分布式限流 秒杀系统 异步削峰

架构师训练营 第11周总结

Glowry

第11周作业

刘卓

week11作业1

极客大学架构师训练营

架构师训练营 第十一周 作业

CR

架构师训练营第 0 期第 11 周作业

无名氏

架构师训练营 week11 - 学习总结

devfan

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