Gitlab+Jenkins+k8s+Helm 的自动化部署实践

本文从实践角度介绍如何结合我们常用的 Gitlab 与 Jenkins,通过 K8s 来实现项目的自动化部署,示例将包括基于 SpringBoot 的服务端项目与基于 Vue.js 的 Web 项目。
Gitlab —— 常用的源代码管理系统 Jenkins,Jenkins Pipeline —— 常用的自动化构建、部署工具,Pipeline 以流水线的方式将构建、部署的各个步骤组织起来 Docker,Dockerfile —— 容器引擎,所有应用最终都要以 Docker 容器运行,Dockerfile 是 Docker 镜像定义文件 Kubernetes —— Google 开源的容器编排管理系统 Helm —— Kubernetes 的包管理工具,类似 Linux 的 yum,apt,或 Node 的 npm 等包管理工具,能将 Kubernetes 中的应用及相关依赖服务以包(Chart)的形式组织管理
已使用 Gitlab 做源码管理,源码按不同的环境建立了 develop(对应开发环境),pre-release(对应测试环境),master(对应生产环境)分支 已搭建了 Jenkins 服务 已有 Docker Registry 服务,用于 Docker 镜像存储(基于 Docker Registry 或Harbor 自建,或使用云服务,本文使用阿里云容器镜像服务) 已搭建了 K8s 集群
预期效果:
分环境部署应用,开发环境、测试环境、生产环境分开来,部署在同一集群的不同namespace,或不同集群中(比如开发测试部署在本地集群的不同 namespace中,生产环境部署在云端集群)
配置尽可能通用化,只需要通过修改少量配置文件的少量配置属性,就能完成新项目的自动化部署配置
开发测试环境在push代码时自动触发构建与部署,生产环境在 master 分支上添加版本 tag 并且 push tag 后触发自动部署
整体交互流程如下图

项目配置文件

包括:
Dockerfile 文件,用于构建 Docker 镜像的文件(参考 Docker笔记(十一):
Dockerfile 详解与最佳实践)
Helm 相关配置文件,Helm 是 Kubernetes 的包管理工具,可以将应用部署相关的Deployment,Service,Ingress 等打包进行发布与管理(Helm 的具体介绍我们后面再补充)
Jenkinsfile 文件,Jenkins 的 pipeline 定义文件,定义了各个阶段需执行的任务
Dockerfile
FROM frolvlad/alpine-java:jdk8-slim#在build镜像时可以通过 --build-args profile=xxx 进行修改ARG profileENV SPRING_PROFILES_ACTIVE=${profile}#项目的端口EXPOSE 8000WORKDIR /mnt#修改时区RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \&& apk add --no-cache tzdata \&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \&& echo "Asia/Shanghai" > /etc/timezone \&& apk del tzdata \&& rm -rf /var/cache/apk/* /tmp/* /var/tmp/* $HOME/.cacheCOPY ./target/your-project-name-1.0-SNAPSHOT.jar ./app.jarENTRYPOINT ["java", "-jar", "/mnt/app.jar"]
将 SPRING_PROFILES_ACTIVE 通过参数 profile 暴露出来,在构建的时候可以通过 —build-args profile=xxx 来进行动态设定,以满足不同环境的镜像构建要求。
SPRING_PROFILES_ACTIVE 本可以在 Docker 容器启动时通过 docker run -e SPRING_PROFILES_ACTIVE=xxx 来设定,因这里使用 Helm 进行部署不直接通过docker run 运行,因此通过 ARG 在镜像构建时指定
Helm 配置文件
Helm 是 Kubernetes 的包管理工具,将应用部署相关的 Deployment,Service,Ingress 等打包进行发布与管理(可以像 Docker 镜像一样存储于仓库中)。如上图中Helm 的配置文件包括:
helm - chart包的目录名├── templates - k8s配置模版目录│ ├── deployment.yaml - Deployment配置模板,定义如何部署Pod│ ├── _helpers.tpl - 以下划线开头的文件,helm视为公共库定义文件,用于定义通用的子模版、函数、变量等│ ├── ingress.yaml - Ingress配置模板,定义外部如何访问Pod提供的服务,类似于Nginx的域名路径配置│ ├── NOTES.txt - chart包的帮助信息文件,执行helm install命令成功后会输出这个文件的内容│ └── service.yaml - Service配置模板,配置访问Pod的服务抽象,有NodePort与ClusterIp等|── values.yaml - chart包的参数配置文件,各模版文件可以引用这里的参数├── Chart.yaml - chart定义,可以定义chart的名字,版本号等信息├── charts - 依赖的子包目录,里面可以包含多个依赖的chart包,一般不存在依赖,我这里将其删除了
apiVersion: v2name: your-chart-namedescription: A Helm chart for Kubernetestype: applicationversion: 1.0.0appVersion: 1.16.0
在 values.yaml 中定义模板文件中需要用到的变量,如
#部署Pod的副本数,即运行多少个容器replicaCount: 1#容器镜像配置image:repository: registry.cn-hangzhou.aliyuncs.com/demo/demopullPolicy: Always# Overrides the image tag whose default is the chart version.tag: "dev"#镜像仓库访问凭证imagePullSecrets:name: aliyun-registry-secret#覆盖启动容器名称nameOverride: ""fullnameOverride: ""#容器的端口暴露及环境变量配置container:port: 8000env: []#ServiceAccount,默认不创建serviceAccount:# Specifies whether a service account should be createdcreate: false# Annotations to add to the service accountannotations: {}name: ""podAnnotations: {}podSecurityContext: {}# fsGroup: 2000securityContext: {}# capabilities:# drop:# - ALL# readOnlyRootFilesystem: true# runAsNonRoot: true# runAsUser: 1000#使用NodePort的service,默认为ClusterIpservice:type: NodePortport: 8000#外部访问Ingress配置,需要配置hosts部分ingress:enabled: trueannotations: {}# kubernetes.io/ingress.class: nginx# kubernetes.io/tls-acme: "true"hosts:host: demo.compaths: ["/demo"]tls: []# - secretName: chart-example-tls# hosts:# - chart-example.local#.... 省略了其它默认参数配置
这里在默认生成的基础上添加了 container 部分,可以在这里指定容器的端口号而不用去改模板文件(让模板文件在各个项目通用,通常不需要做更改),同时添加env的配置,可以在helm部署时往容器里传入环境变量。将Service type从默认的ClusterIp改为了NodePort。部署同类型的不同项目时,只需要根据项目情况配置Chart.yaml与values.yaml两个文件的少量配置项,templates目录下的模板文件可直接复用。
# 登录Docker Registry生成/root/.docker/config.json文件sudo docker login --username=your-username registry.cn-shenzhen.aliyuncs.com# 创建 namespace develop(我这里是根据项目的环境分支名称建立namespace)kubectl create namespace develop# 在 namespace develop中创建一个secretkubectl create secret generic aliyun-registry-secret --from-file=.dockerconfigjson=/root/.docker/config.json --type=kubernetes.io/dockerconfigjson --namespace=develop
Jenkinsfile
image_tag = "default" //定一个全局变量,存储Docker镜像的tag(版本)pipeline {agent anyenvironment {GIT_REPO = "${env.gitlabSourceRepoName}" //从Jenkins Gitlab插件中获取Git项目的名称GIT_BRANCH = "${env.gitlabTargetBranch}" //项目的分支GIT_TAG = sh(returnStdout: true,script: 'git describe --tags --always').trim() //commit id或tag名称DOCKER_REGISTER_CREDS = credentials('aliyun-docker-repo-creds') //docker registry凭证KUBE_CONFIG_LOCAL = credentials('local-k8s-kube-config') //开发测试环境的kube凭证KUBE_CONFIG_PROD = "" //credentials('prod-k8s-kube-config') //生产环境的kube凭证DOCKER_REGISTRY = "registry.cn-hangzhou.aliyuncs.com" //Docker仓库地址DOCKER_NAMESPACE = "your-namespace" //命名空间DOCKER_IMAGE = "${DOCKER_REGISTRY}/${DOCKER_NAMESPACE}/${GIT_REPO}" //Docker镜像地址INGRESS_HOST_DEV = "dev.your-site.com" //开发环境的域名INGRESS_HOST_TEST = "test.your-site.com" //测试环境的域名INGRESS_HOST_PROD = "prod.your-site.com" //生产环境的域名}parameters {string(name: 'ingress_path', defaultValue: '/your-path', description: '服务上下文路径')string(name: 'replica_count', defaultValue: '1', description: '容器副本数量')}stages {stage('Code Analyze') {agent anysteps {echo "1. 代码静态检查"}}stage('Maven Build') {agent {docker {image 'maven:3-jdk-8-alpine'args '-v $HOME/.m2:/root/.m2'}}steps {echo "2. 代码编译打包"sh 'mvn clean package -Dfile.encoding=UTF-8 -DskipTests=true'}}stage('Docker Build') {agent anysteps {echo "3. 构建Docker镜像"echo "镜像地址:${DOCKER_IMAGE}"//登录Docker仓库sh "sudo docker login -u ${DOCKER_REGISTER_CREDS_USR} -p ${DOCKER_REGISTER_CREDS_PSW} ${DOCKER_REGISTRY}"script {def profile = "dev"if (env.gitlabTargetBranch == "develop") {image_tag = "dev." + env.GIT_TAG} else if (env.gitlabTargetBranch == "pre-release") {image_tag = "test." + env.GIT_TAGprofile = "test"} else if (env.gitlabTargetBranch == "master"){// master分支则直接使用Tagimage_tag = env.GIT_TAGprofile = "prod"}//通过--build-arg将profile进行设置,以区分不同环境进行镜像构建sh "docker build --build-arg profile=${profile} -t ${DOCKER_IMAGE}:${image_tag} ."sh "sudo docker push ${DOCKER_IMAGE}:${image_tag}"sh "docker rmi ${DOCKER_IMAGE}:${image_tag}"}}}stage('Helm Deploy') {agent {docker {image 'lwolf/helm-kubectl-docker'args '-u root:root'}}steps {echo "4. 部署到K8s"sh "mkdir -p /root/.kube"script {def kube_config = env.KUBE_CONFIG_LOCALdef ingress_host = env.INGRESS_HOST_DEVif (env.gitlabTargetBranch == "pre-release") {ingress_host = env.INGRESS_HOST_TEST} else if (env.gitlabTargetBranch == "master"){ingress_host = env.INGRESS_HOST_PRODkube_config = env.KUBE_CONFIG_PROD}sh "echo ${kube_config} | base64 -d > /root/.kube/config"//根据不同环境将服务部署到不同的namespace下,这里使用分支名称sh "helm upgrade -i --namespace=${env.gitlabTargetBranch} --set replicaCount=${params.replica_count} --set image.repository=${DOCKER_IMAGE} --set image.tag=${image_tag} --set nameOverride=${GIT_REPO} --set ingress.hosts[0].host=${ingress_host} --set ingress.hosts[0].paths={${params.ingress_path}} ${GIT_REPO} ./helm/"}}}}}
Code Analyze,可以使用 SonarQube 之类的静态代码分析工具完成代码检查,这里先忽略 Maven Build,启动一个 Maven 的 Docker 容器来完成项目的 maven 构建打包,挂载 maven 本地仓库目录到宿主机,避免每次都需要重新下载依赖包 Docker Build,构建 Docker 镜像,并推送到镜像仓库,不同环境的镜像通过tag区分,开发环境使用 dev.commitId 的形式,如 dev.88f5822,测试环境使用 test.commitId,生产环境可以将 webhook 事件设置为 tag push event,直接使用 tag名称 Helm Deploy,使用helm完成新项目的部署,或已有项目的升级,不同环境使用不同的参数配置,如访问域名,K8s 集群的访问凭证kube_config等
Jenkins 配置
Jenkins 任务配置

配置构建触发器,将目标分支设置为 develop 分支,生成一个 token,如图

记下这里的“GitLab webhook URL”及token值,在Gitlab配置中使用。

保存即完成了项目开发环境的Jenkins配置。测试环境只需将对应的分支修改为pre-release 即可
Jenkins 凭据配置
DOCKER_REGISTER_CREDS = credentials('aliyun-docker-repo-creds') //docker registry凭证KUBE_CONFIG_LOCAL = credentials('local-k8s-kube-config') //开发测试环境的kube凭证


添加 K8s 集群的访问凭证,在 master 节点上将 /root/.kube/config 文件内容进行 base64 编码,
base64 /root/.kube/config > kube-config-base64.txtcat kube-config-base64.txt
使用编码后的内容在 Jenkins 中创建一个 Secret text 类型的凭据,如图
在 Secret 文本框中输入 base64 编码后的内容。
Gitlab 配置
在 Gitlab 项目的 Settings - Integrations 页面配置一个 webhook,在 URL 与 Secret Token 中填入前面 Jenkins 触发器部分的“GitLab webhook URL”及token值,选中“Push events”作为触发事件,如图


总结
文章转载:DevOps技术栈
(版权归原作者所有,侵删)
