文章目录
  1. 1. (一)现状
  2. 2. (二)整体方案
    1. 2.1. 2.1 实现原理
    2. 2.2. 2.2 逻辑架构
    3. 2.3. 2.3 拓扑结构
  3. 3. (三)方案实现
    1. 3.1. 3.1 代码管理
    2. 3.2. 3.2 交付模型
    3. 3.3. 3.3 Helm Chart管理
    4. 3.4. 3.4 流水线
  4. 4. (四)使用方式
    1. 4.1. 4.1 创建Dockerfile及配置Helm Chart模板参数
    2. 4.2. 4.2 在Jenkins上创建凭据
    3. 4.3. 4.3 在Jenkins创建一个流水线
    4. 4.4. 4.4 TAPD与Jenkins进行关联
  5. 5. (五)详细说明
    1. 5.1. 5.1 Jenkins的流水线参数
    2. 5.2. 5.2 提供的Helm Chart模板
    3. 5.3. 5.3 Helm Charts模板参数
    4. 5.4. 5.4 Jenkins地址
    5. 5.5. 5.5 Harbor地址
    6. 5.6. 5.6 注意事项

(一)现状

公司目前推进基础架构的云原生化,其中一个重要工作方向CI/CD,现状是:

  1. 缺乏统一的CI/CD方法论
  2. 缺乏统一的CI/CD工具链
  3. 缺乏统一的CI/CD操作平台

(二)整体方案

结合公司现状,和我们过往的经验,为尽快推进CI/CD落地,目前设计的一个简单方案:

2.1 实现原理

基于下图中的一种CI/CD工作模型
CICD整体方案.png

工具链:Git + Kubernetes + Harbor + Helm + Jenkins + TAPD(或自研CI/CD平台);

  1. 基于GitFlow的代码管理流程;
  2. 基于GitOps的持续交付模型;
  3. 基于K8S的基础运行平台;
  4. 基于Helm 的K8S 部署交付物包管理方式;
  5. 基于Harbor的Docker Image仓库、Helm Chart仓库
  6. 基于Jenkins的CI/CD基础服务;
  7. 基于Jenkins Pipeline 的CI/CD核心流程;
  8. 基于TAPD(后续增加自研平台)的CI/CD前端操作界面;

2.2 逻辑架构

2.3 拓扑结构

拓扑结构.png

(三)方案实现

3.1 代码管理

在研发流程的代码管理上采用GitFlow流程进行管理。

Git Flow流程.png

3.2 交付模型

3.3 Helm Chart管理

将应用部署到K8S的Deployment、StatefulSet等各种配置进行分解抽象,以Helm Chart模板的形式进行管理,新应用只需简单配置一些参数值,即可轻松部署到K8S中。

目前提供的Chart模板包括

  1. Deployment通用模板
  2. StatefulSet通用模板

3.4 流水线

将CI/CD的各个操作步骤进行分解与抽象,以流水线(Pipeline)的方式进行标准化,并针对不同开发语言提供对应的流水线模板,并可在该模板基础上进行定制;

可提供的流水线模板包括:

  1. Java Pipeline;
  2. PHP Pipeline(开发中);
  3. Go Pipeline(开发中);

(四)使用方式

4.1 创建Dockerfile及配置Helm Chart模板参数

  • 首先需要在代码根目录下创建docker目录

  • 如果项目为多模块,就需要在docker目录下创建多个目录,每一个目录名为模块名,如果为单模块,则忽略

  • 如果为单模块,则在docker目录下,创建四个目录,分别是:

    • dev
    • prod
    • stage
    • test

    如果为多模块,就需要在每一个模块下创建以上四个文件目录。以上四个目录,代表着四套不同的环境。

  • 在每一个环境目录下,需要配置两个文件:

    • Dockerfile
    • values.yaml,用于配置Helm Chart模板参数。参数在最后的详细说明Helm Charts模板参数章节。

多模块的结构如下:

多模块.png

单模块的结构如下:

单模块.png

4.2 在Jenkins上创建凭据

会创建两个凭据,如下:

  1. harbor的账号与密码,ID可以指定为harbor-middleware或者其它特定有意义的ID,如果非harbor-middleware,则需要修改流水线中对应的ID
  2. gitlab的账号与密码,ID不能自定义,会生成全局唯一ID,此ID在从gitlab上拉取代码时会用到。

添加完成后,如下图:

Jenkins-凭据.png

4.3 在Jenkins创建一个流水线

配置如下:

  1. 勾选参数化构建过程,添加指定参数。参数在最后的详细说明Jenkins的流水线参数章节。

  2. 在流水线中的Pipeline script,加入如下的代码,并需要根据每个业务团队需求进行相应调整

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    def podLabel = "build-${JOB_NAME}-${UUID.randomUUID().toString()}"
    pipeline {
    agent {
    kubernetes {
    label podLabel
    yaml """
    apiVersion: v1
    kind: Pod
    metadata:
    labels:
    label: build-agent
    spec:
    serviceAccount: "jenkins-admin"
    imagePullSecrets:
    - name: kn-registry
    hostAliases:
    - ip: "118.25.63.51"
    hostnames:
    - "jcenter.kn.com"
    tolerations:
    - key: "node-role.kubernetes.io/devops"
    operator: "Exists"
    effect: "NoSchedule"
    containers:
    - name: maven
    image: registry-prod.kn.com/middleware/maven-jdk1.8.131:3.6.1
    command:
    - cat
    tty: true
    volumeMounts:
    - name: maven-cache
    mountPath: /root/.m2
    - name: docker
    image: docker:18.06.3-ce
    command:
    - cat
    tty: true
    volumeMounts:
    - name: docker-sock
    mountPath: /var/run/docker.sock
    - name: helm
    image: registry-prod.kn.com/middleware/helm:2.10.0
    command:
    - cat
    tty: true
    volumes:
    - name: docker-sock
    hostPath:
    path: /var/run/docker.sock
    - name: maven-cache
    nfs:
    path: /
    server: 10.10.2.2
    """
    }
    }

    stages {
    stage('一: 构建') {
    steps {
    echo "Pull代码"
    git branch: '${branch}', credentialsId: '69082cb5-1578-4e99-bf49-bd8d4496c4f6', url: '${projectGitUrl}'
    script {
    build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
    }

    container(name:'maven') {
    echo "编译打包"
    sh 'mvn clean install -Dmaven.test.skip=true'
    script {
    projectVersion = sh(returnStdout: true, script: "cat pom.xml | grep '<version>' | head -n 1 | awk -F '/' '{print \$(NF-1)}' | awk -F '>' '{print \$(NF)}' | awk -F '<' '{print \$(1)}'").trim()
    bizImage = "registry-prod.kn.com/${team}/${project}/${tier}:${projectVersion}_${branch}_${build_tag}"
    }
    }
    }
    }

    stage('二: 单元测试') {
    steps {
    echo "TODO: 代码扫描"
    echo "TODO: 单元测试"
    echo "TODO: 覆盖率统计"
    echo "TODO: 生成报告"
    }
    }

    stage('三: 部署') {
    steps {
    echo "构建镜像 & Push镜像"
    container(name:'docker') {
    sh "echo tag = ${build_tag}"
    withCredentials([usernamePassword(credentialsId: 'harbor-middleware', passwordVariable: 'harborPassword', usernameVariable: 'harborUsername')]) {
    script {
    sh "echo ${harborPassword} | docker login -u ${harborUsername} --password-stdin registry-prod.kn.com"
    docker.build("${bizImage}", "-f ${k8sConfig}/${environment}/Dockerfile .").push()
    }
    }
    }

    echo "部署上线"
    container(name:"helm") {
    script {
    echo "helm init"
    sh "helm init --client-only --skip-refresh"

    echo "helm repo remove local"
    sh "helm repo remove local"

    echo "helm repo remove stable"
    sh "helm repo remove stable"

    echo "helm repo add harbor"
    withCredentials([usernamePassword(credentialsId: 'harbor-middleware', passwordVariable: 'harborPassword', usernameVariable: 'harborUsername')]) {
    sh "helm repo add --username ${harborUsername} --password ${harborPassword} harbor https://registry-prod.kn.com/chartrepo/${team}"
    }

    echo "helm repo update"
    sh "helm repo update"

    echo "执行上线操作"

    def exitValue = sh(script: "helm upgrade --wait --timeout=600 --install --set team=${team} --set project=${project} --set tier=${tier} --set environment=${environment} --set deployImage=${bizImage} -f ${k8sConfig}/${environment}/values.yaml --namespace=${team}-${environment} ${tier}-${environment} --version=${helmChartVersion} harbor/${helmChartName}", returnStatus: true)
    if (exitValue != 0) {
    echo "上线失败,回滚"
    sh "helm rollback ${tier}-${environment} 0"
    }
    }
    }
    }
    }

    stage('四: 集成测试') {
    steps {
    echo "自动化测试"
    }
    }
    }

    post {
    always {
    echo '清理临时文件'
    }
    success {
    echo '系统上线成功!'
    }
    failure {
    echo '系统上线失败!'
    }
    }
    }

该流水线主要包含两个部分:

  • 在k8s环境中,创建用于执行CI/CD的Pod。针对Java,在该Pod中现阶段主要包含三个容器: maven、docker、helm。maven用于编译,docker用于镜像打包及推送,helm用于执行上线操作
  • 执行CICD操作,主要包含四个阶段:
    • 构建,主要有三个部分,第一个部分用于从git仓库中拉取代码;第二部分用于从代码中解析出版本号;第三部分则是编译项目,各个项目需要自行调整编译参数
    • 单元测试
    • 部署,包含两个部分:镜像推送和通过helm chart模板在k8s中创建应用
    • 集成测试

需要调整的部分如下:

  • gitlab的访问凭证ID,用于拉取代码
  • harbor的访问凭证ID,用于镜像的推送
  • 镜像仓库地址
  • 编译参数
  • 针对非java环境,需要调整Pod的创建,增加新的容器

4.4 TAPD与Jenkins进行关联

  • 在TAPD获取项目的项目ID,如下图:
    tapd-project-id.png
  • 将TAPD与Jenkins进行关联,如下图:

jenkins-params.png

到此,所有配置完成。完成配置后,即在TAPD上的流水线执行CI/CD操作。如下图:

TAPD运行示例.png

运行完成后,如下图:

TAPD执行CICD后的结果.png

(五)详细说明

5.1 Jenkins的流水线参数

参数名 类型 类别 描述
team 字符参数 基本信息 团队名,英文
project 字符参数 基本信息 项目名,英文
tier 字符参数 基本信息 模板名,英文
projectGitUrl 字符参数 代码信息 项目的gitlab地址
branch 字符参数 代码信息 gitlab分支
helmChartName 字符参数 helm配置 helm模板名称,现在支持deployment-common、statefulset-common
helmChartVersion 字符参数 helm 配置 helm模板版本,现在的支持的版本号为1.0.0、1.0.1、1.0.2、1.0.3
k8sConfig 字符参数 helm配置 k8s的配置文件目录及Dockerfile文件
environment 选项参数 运行环境 环境列表,取值为prod、stage、dev、test

5.2 提供的Helm Chart模板

现阶段中间件组提供两个Helm Chart 模板,分别是:

  • deployment-common

    提供基于无状态服务的公共模板,支持ingress、service、deployment的创建

  • statefulset-common

    提供基于有状态服务的公共模板,支持ingress、service、statefulset的创建

ChangeLog如下:

  • 1.0.0
  • 1.0.1,添加了针对环境变量的支持
  • 1.0.2,修复了statefulset滚动更新的异常
  • 1.0.3,添加了对于command和args的支持
  • 1.0.4,修改了关于prometheus的BUG

5.3 Helm Charts模板参数

deployment-common和statefulset-common的模板参数是一致的。

字段Key 是否必填 默认值 类别 描述
team 基本信息 团队名,英文名
project 基本信息
tier 基本信息 项目的模板名,英文名
environment 运行环境 环境,有四个选项,test、prod、stage(提供给其它组使用的环境)、dev
harborHost harbor配置 harbor仓库的host,不需要带上http或者https。现在测试环境为registry.kn.com,线上为registry-prod.kn.com
harborUsername harbor仓库的用户名 harbor配置
harborPassword harbor仓库的密码 harbor配置
deployReplicas deployment配置
deployImage deployment配置
deployCommand deployment配置 运行命令,数组
deployArgs deployment配置 运行参数,数组
deployContainerPort deployment配置 项目的服务端口号
deployProbeEnable false deployment配置 是否开启探针,探针包含两个部分,read探针和live探针
deployProbeURI deployment配置 如果deployProbeEnable为true,则需要配置探针的uri
deployLiveProbeInitialDelaySeconds 120 deployment配置 如果deployProbeEnable为true,用于配置live探针第一开始探测的延迟
deployLiveProbeInterval 5 deployment配置 如果deployProbeEnable为true,用于配置live探针的探测间隔
deployReadProbeInitialDelaySeconds 120 deployment配置 如果deployProbeEnable为true,用于配置read探针第一开始探测的延迟
deployReadProbeInterval 5 deployment配置 如果deployProbeEnable为true,用于配置read探针的探测间隔
deployResoucesLimits requests: {cpu: 1,memory: 1G }, limits: { cpu: 2, memory: 2G } deployment配置 设置每一个副本的资源限制
logCollectorEnable 是否开启sidecar的日志收集功能
logCollectorImage 当logCollectorEnable为true时,用于设置sidecar的镜像
envs 设置环境变量,例如: aa: 123
logDirType empty 日志目录 挂载目录类型,有三种类型,empty,host,pvc
logDirSize 日志目录 仅仅针对logDirType的类型为pvc时有效,设置pvc大小
logDirTargetPath /data/logs 日志目录 挂载到容器中的目录位置
prometheusCollectEnable false prometheus采集 是否开启允许prometheus采集数据
prometheusCollectMetricPort prometheus采集 当prometheusCollectEnable为true时生效,用于设置prometheus采集数据时,容器的数据端口
promethuesCollectMetricUri prometheus采集 当prometheusCollectEnable为true时生效,用于设置prometheus采集数据时,获取数据的uri
tolerationEnable false k8s的容忍与污点 是否开启容忍机制,仅仅在线上环境需要开启,在测试环境不用开启
tolerationKey k8s的容忍与污点 当tolerationEnable为true时生效,用于设置容忍的key
serviceEnable true k8s的service配置 是否需要创建k8s中的service
serviceSessionAffinityEnable false k8s的service配置 当serviceEnable为true时生效,用于设置是否开启session粘连功能,开启后,默认根据ClientIP进行粘连
serviceLoadBalancerEnable false k8s的service配置 是否将service的类型设置为loadbalancer,否则为clusterIp
serviceSubnetid k8s的service配置 针对serviceLoadBalancerEnable为true时,设置内网的id(找运维获取),线上和测试环境不是一样的
ingressEnabled false k8s的ingress配置 是否需要ingress
ingressType ingress-nginx k8s的ingress配置 ingress的实现类型
ingressHost false k8s的ingress配置 当ingressEnabled为true时生效,用于设置ingress匹配的host
ingressTlsEnable false k8s的ingress配置 当ingressEnabled为true时生效,用于设置ingress是否开启https
ingressSessionAffinityEnable false k8s的ingress配置 是否开启ingress的session亲近性
ingressSessionAffinityMode balanced k8s的ingress配置 ingress的session亲和性是根据cookie实现,此处定义session的生成方式,支持两种方式: balanced 和 persistent,balanced会根据pod的伸缩进行cookie调整,而persistent总会等到cookie过期后才会进行调整
ingressSessionAffinityCookieName INGRESSCOOKIE k8s的ingress配置 ingress的session亲和性是根据cookie实现,需要定义一个cookie名
ingressSessionAffinityCookieTtl k8s的ingress配置 ingress的session亲和性是根据cookie实现,需要定义一个cookie的最大保存时间,单位秒
ingressTlsCrt false k8s的ingress配置 当ingressTlsEnalbe为ture时有效,用于设置https证书的crt数据,需要base64加密
ingressTlsKey false k8s的ingress配置 当ingressTlsEnalbe为ture时有效,用于设置https证书的key数据,需要base64加密
ingressAuthVerifyClientEnable false k8s的ingress配置 当ingressTlsEnalbe为ture时有效,是否开启客户端的认证(即只允许公司内部的员工才能访问,双向认证)
ingressAuthVerifyClientCaData k8s的ingress配置 当ingressTlsEnalbe为ture,并且ingressAuthVerifyClient也为true时有效,用于配置根证书,数据为base64加密后的数据

5.4 Jenkins地址

  • https://k8s-test-jenkins.kn.com/,测试环境的k8s的jenkins
  • http://jenkins-prod.kn.com/,线上k8s的jenkins

5.5 Harbor地址

线上的k8s集群不能访问测试环境的harbor仓库

5.6 注意事项

  • 获取代码仓库的版本号

    如果为Java项目的话,可以通过如下命令进行获取。

    1
    cat pom.xml | grep '<version>' | head -n 1  | awk -F '/' '{print \$(NF-1)}' | awk -F '>' '{print \$(NF)}' | awk -F '<' '{print \$(1)}'
  • 容忍与污点

    在线上环境中,需要开启容忍与污点机制,每一个小组的k8s节点是相互隔离,每个小组的资源只能负载到对应的节点上去。而在测试环境则无需要开启容忍与污点机制。

  • 网络

    在创建k8s的LoadBalance的Service时,默认会使用腾讯云的CLB,并生成一个外网IP。外网IP会收费。所以在创建内网的LoadBalance的Service时,需要添加一个annotaion,如下:

    1
    service.kubernetes.io/qcloud-loadbalancer-internal-subnetid:

    这个key的现阶段取值有两个:

    • 3a6sat1b,线上内网
    • 1lj7kqdt,测试环境内网
文章目录
  1. 1. (一)现状
  2. 2. (二)整体方案
    1. 2.1. 2.1 实现原理
    2. 2.2. 2.2 逻辑架构
    3. 2.3. 2.3 拓扑结构
  3. 3. (三)方案实现
    1. 3.1. 3.1 代码管理
    2. 3.2. 3.2 交付模型
    3. 3.3. 3.3 Helm Chart管理
    4. 3.4. 3.4 流水线
  4. 4. (四)使用方式
    1. 4.1. 4.1 创建Dockerfile及配置Helm Chart模板参数
    2. 4.2. 4.2 在Jenkins上创建凭据
    3. 4.3. 4.3 在Jenkins创建一个流水线
    4. 4.4. 4.4 TAPD与Jenkins进行关联
  5. 5. (五)详细说明
    1. 5.1. 5.1 Jenkins的流水线参数
    2. 5.2. 5.2 提供的Helm Chart模板
    3. 5.3. 5.3 Helm Charts模板参数
    4. 5.4. 5.4 Jenkins地址
    5. 5.5. 5.5 Harbor地址
    6. 5.6. 5.6 注意事项