背景

云原生技术使组织能够在现代化,动态环境(例如公共云,私有云和混合云)中构建和运行可伸缩应用程序。容器,服务网格,微服务,不变的基础设施声明式 API 就是这种方法的例证。

这些技术使松耦合的系统具有弹性,可管理性和可观察性 结合强大的自动化功能,它们使工程师可以频繁且可预测地以最小的工作量进行高影响力的更改。

 

因此,软件开发、测试和部署解决方案也需要跟上,而我们将遇到这些挑战:

· 命令性思维不适用于声明性基础架构

· 安全不与开发和操作共享公共接口和操作模型

· 本地和云环境中的工具和操作模型不一致

· 手动流程会创建障碍,并在整个环境中引入不一致之处

归根结底,矛盾在于开发团队追求速度,而运维团队需要保障 SLO

平台

随着 Kubernetes 平台被越来越广泛的应用,我们总结出在 Kubernetes 上构建软件交付平台的一些原则:

· 安全要求

职责分离

漏洞管理

认证

访问控制

· 配置即代码

声明式配置

可审计的单一源

基于 GitOps 工作流程

· 自动化

为所有构建,测试和部署步骤创建端到端的流水线

全局策略更改自动传播

· 开放性与可扩展性

与现有系统集成

混合/多云可部署

继续利用现有技能

目标

那么,在我们设计 CI/CD 平台的时候,应该以下列三个目标为导向:

· 允许运营团队标准化开发团队的入场方式以及部署和维护应用程序的方式

· 允许安全团队集中对应用程序注入和部署策略和授权

· 确保开发人员可以自主且独立地部署和迭代其应用程序


实现示例

总体来说,一个现代化的 CI/CD 工作流如下图所示:

 

 

持续集成

持续集成工具的主要用户是开发人员,但是运维人员也经常使用持续集成来实现一些自动化工作。

我们通常应关注持续集成工具的这些特性:

1. 可复用性

随着持续集成概念的深入人心,在不同团队、不同项目之间出现了共同的需求,例如,对程序做静态分析已经成了标配。那么是否能将通用的逻辑抽象出来,做成可重用的模块,就是这里所讲的可复用性。

可组装性

同样的,在持续集成的发展过程中,不断的有新功能涌现出来,就会出现一个流水线的两端不断延伸的需求,这就会发生流水线之间的组装。

云原生

持续集成的流水线在运行的时候是需要计算资源的。如果一个持续集成的系统在运行时可以直接从一个云原生的环境中获取计算资源,管理员不再需要管理虚拟机网络存储这些基础设施,将会极大简化持续集成的运作难度,也极大提升了灵活性。这就是云原生的需求。

Tekton 是一个具有上述特点的持续集成工具。Tekton 也是流行的 Jenkins-X 的核心组件。

 

下图所示的流水线 (Pipeline) 展示了从构建到推送到测试环境的过程:

 

该流水线的代码附在本文的附件1中,该 Tekton 的流水线需要用附件2中的 Pipelinerun 对象来触发。

构建流水线的元素是 TaskTekton Github 仓库中有大量预先构建的Task定义,可以按需取用。如果需要的 Task 逻辑需要自定义,例如上面流水线例子中的 yaml-set 这个 task, 可以在附件3找到 task 定义,并且你需要用附件4 Dockerfile 来构建这个 task 所需的容器镜像。

GitOps 与持续交付

在之前的文章中,我们已经介绍过 GitOps 这个概念以及使用 Anthos Config Management 实现 GitOpsGitOps 是一种非常实用的的运维实践,采用 GitOps 将运维流程转化为如下的类似开发的流程:

 

其中,检查(Validate)这个过程可以用一个持续集成的流水线实现,而评议(Review)与合并(Merge)将更容易的在团队中实现合作。

但是 GitOps 只解决了存放配置文件的 git 仓库与运行工作负载的 K8S 集群的同步问题,此外,还有两个问题需要解决:

1. 配置文件的实例化

2. 发布策略的实现

配置文件的实例化是指,将环境相关的配置信息与开发、运维提供的配置模板合并,生成在该环境中实用的配置。这个过程有时又称作注水(Hydration)。例如:

 

Kpt 或者 helm 都可以完成这个任务。需要注意的是,从实践上来说,同时将 kpt(或 helm)的源文件和生成的目标文件同时签入 Git 仓库是一个推荐的实践,在检查这个过程中加入源-目标文件一致性检测则能更好的确保这个要求。

发布策略则更加具有挑战。GitOps 工具会将 Git 仓库中的配置信息一起同步到集群中,而现代的运维实践中往往需要用蓝绿发布或者灰度发布这样的渐进过程来完成发布。通过向 Git 仓库签入一系列的配置会加重运维人员的负担,也没有实现自动化。Argo-rollouts 提供了一个 K8S CRD 和控制器帮助实现声明式的发布策略。以灰度发布为例:

这个过程可以方便地在ArgoRollout里实现:

apiVersion: argoproj.io/v1alpha1

kind: Rollout

metadata:

  name: rollout-example

spec:

  ...

  strategy:

    canary:

      canaryService: canary-svc  

      stableService: stable-svc

  analysis:

        templates:

        - templateName: success-rate

        args:

        - name: service-name

          value: guestbook-svc.default.svc.cluster.local

      trafficRouting:

        istio:

          virtualService: 

            name: rollout-vsvc   

            routes:

            - primary            

      steps:

      - setWeight: 50

      - pause:

          duration: 10m

---

apiVersion: argoproj.io/v1alpha1

kind: AnalysisTemplate

metadata:

  name: success-rate

spec:

  args:

  - name: service-name

  metrics:

  - name: success-rate

    interval: 5m

    # NOTE: prometheus queries return results in the form of a vector.

    # So it is common to access the index 0 of the returned array to obtain the value

    successCondition: result[0] >= 0.95

    failureLimit: 3

    provider:

      prometheus:

        address: http://prometheus.example.com:9090

        query: |

          sum(irate(

            istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code!~"5.*"}[5m]

          )) / 

          sum(irate(

            istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m]

          ))

 

这样,我们的应用发布可以是变成这样:

 

总结

使用 TektonAnthos-Config-Management Argo Rollouts 这几个工具的集成,我们大体上实现了满足 SRE 实践要求的自动化工具,这样的框架具有高度的可扩展性,满足实际生产的需要。

附录

以下所有代码版权属于谷歌广告(上海)有限公司,以Apache2.0 License发行。

附件1

---

apiVersion: tekton.dev/v1beta1

kind: Pipeline

metadata:

  name: git2kanico2git

spec:

  workspaces:

  - name: shared-workspace

  - name: dep-ws

  - name: dummyinput

  params:

  - name: image

    description: reference of the image to build

  - name: src-repo

    description: the url of source code repository

  - name: acm-config-repo

    description: the url of configurations repository used by ACM

  - name: target-container

    description: the name of the container using the application

  - name: target-manifest

    description: the file holding the kubenetes resource manifest

  tasks:

  - name: fetch-source-repository

    taskRef:

      name: git-clone

    workspaces:

    - name: output

      workspace: shared-workspace

    params:

    - name: url

      value: $(params.src-repo)

    - name: subdirectory

      value: ""

    - name: deleteExisting

      value: "true"

  - name: make-image

    taskRef:

      name: kaniko

    runAfter:

    - fetch-source-repository

    workspaces:

    - name: source

      workspace: shared-workspace

    params:

    - name: IMAGE

      value: $(params.image):$(tasks.fetch-source-repository.results.commit)

  - name: fetch-acm-repo

    runAfter:

    - make-image

    params:

    - name: GIT_USER_NAME

      value: tektonuser

    - name: GIT_USER_EMAIL

      value: tekton@k8s

    - name: GIT_SCRIPT

      value: |

        su - -s /bin/sh root -c "ln -s $HOME/.ssh ~/"

        git clone $(params.acm-config-repo)

    taskRef:

      kind: Task

      name: git-cli

    workspaces:

    - name: source

      workspace: dep-ws

    - name: input

      workspace: dummyinput

  - name: update-image-tag

    params:

    - name: YAML_SCRIPT

      value: |

        cd drepo

        yaml-set --change=".spec.template.spec.containers[name=$(params.target-container)].image" --value="$(params.image):$(tasks.fetch-source-repository.results.commit)" $(params.target-manifest)

    runAfter:

    - fetch-acm-repo

    taskRef:

      kind: Task

      name: yaml-set

    workspaces:

    - name: source

      workspace: dep-ws

  - name: acm-repo-git-push

    params:

    - name: GIT_USER_NAME

      value: tektonuser

    - name: GIT_USER_EMAIL

      value: tekton@k8s

    - name: GIT_SCRIPT

      value: |

        su - -s /bin/sh root -c "ln -s $HOME/.ssh ~/"

        cd drepo

        git add .

        git commit -m 'Edited manifest'

        git push origin main

    runAfter:

    - update-image-tag

    taskRef:

      kind: Task

      name: git-cli

    workspaces:

    - name: source

      workspace: dep-ws

    - name: input

      workspace: dummyinput

附件2

---

apiVersion: tekton.dev/v1alpha1

kind: PipelineRun

metadata:

  generateName: github-run-

spec:

  serviceAccountNames:

    - taskName: fetch-source-repository

      serviceAccountName: sa-for-pull

    - taskName: make-image

      serviceAccountName: registry-write-sa

    - taskName: fetch-acm-repo

      serviceAccountName: acm-git-service-account

    - taskName: acm-repo-git-push

      serviceAccountName: acm-git-service-account

  pipelineRef:

    name: git2kanico2git

  params:

  - name: image

    value: demoapp

  - name: src-repo

    value: git@github.com/somewhere/demoapp

  - name: acm-config-repo

    value: git@github.com/somewhere/configrepo

  - name: target-container

    value: demoapp

  - name: target-manifest

    value: namespaces/ns1/demoapp-deplotment.yaml

  workspaces:

  - name: shared-workspace

    volumeClaimTemplate:

      spec:

        accessModes: 

          - ReadWriteOnce

        resources:

          requests:

            storage: 500Mi

  - name: dep-ws

    volumeClaimTemplate:

      spec:

        accessModes: 

          - ReadWriteOnce

        resources:

          requests:

            storage: 500Mi

  - emptyDir: {}

    name: dummyinput

 

 

附件3

apiVersion: tekton.dev/v1beta1

kind: Task

metadata:

  name: yaml-set

spec:

  description: |-

    This task can be used to perform yaml operations.

    yaml-set command that needs to be run can be passed as a script to the task.

  params:

  - default: someregistry/yamlpath:3.4.0

    description: |

      The base image for the task.

    name: BASE_IMAGE

    type: string

  - default: |

      yaml-set --help

    description: The git script to run.

    name: YAML_SCRIPT

    type: string

  results:

  - description: The precise commit SHA after the git operation.

    name: commit

  steps:

  - image: $(params.BASE_IMAGE)

    name: yamlset

    resources: {}

    script: |2

 

      $(params.YAML_SCRIPT)

 

      EXIT_CODE="$?"

      if [ "$EXIT_CODE" != 0 ]

      then

        exit $EXIT_CODE

      fi

      # Make sure we don't add a trailing newline to the result!

      echo -n "$RESULT_SHA" > $(results.commit.path)

    workingDir: $(workspaces.source.path)

  workspaces:

  - description: A workspace that contains the yaml files needed to be changed.

    name: source

 

附件4

FROM python:3.9

RUN pip install yamlpath==3.4.0

ENTRYPOINT ["/usr/local/bin/yaml-set"]



相关推荐