发布流程设计
准备基础环境:Harbor、Gitlab、Jenkins
Harbor镜像仓库
项目地址:https://github.com/goharbor/harbor
1.安装docker与docker-compose
2.解压离线包部署
hostname: 192.168.0.14 https: harbor_admin_password: Harbor12345
在Jenkins主机配置Docker可信任,如果是HTTPS需要拷贝证书
由于habor未配置https,还需要在docker配置可信任。
{"registry-mirrors" : ["https://b9pmyelo.mirror.aliyuncs.com" ], "insecure-registries" : ["192.168.0.12" ] }
Gitlab代码仓库
在Gitlab创建一个项目,然后提交微服务项目代码。
如果没有Gitlab可以使用Docker启动一个:
mkdir /opt/gitlab GITLAB_HOME=/opt/gitlab docker run --detach \ --publish 443:443 \ --publish 88:80 \ --publish 2222:22 \ --name gitlab \ --restart always \ --volume $GITLAB_HOME /config:/etc/gitlab \ --volume $GITLAB_HOME /logs:/var/log/gitlab \ --volume $GITLAB_HOME /data:/var/opt/gitlab \ gitlab/gitlab-ce:latest
访问地址:http://IP:88
初次会先设置管理员密码 ,然后登陆,默认管理员用户名 root,密码就是刚设置的。
Jenkins 发布系统
Jenkins是一款开源 CI&CD 系统,用于自动化各种任务,包括构建、测试和部署。
Jenkins官方提供了镜像:https://hub.docker.com/r/jenkins/jenkins
使用Deployment来部署这个镜像,会暴露两个端口:8080 Web访问端口,50000 Slave通 信端口,容器启动后Jenkins数据存储在/var/jenkins_home目录,所以需要将该目录使用 PV持久化存储。
配置PV持久化存储:
1、部署NFS共享服务器
2、找一个节点作为NFS共享存储服务器
mkdir -p /ifs/kubernetes/jenkins-datavim /etc/exports /ifs/kubernetes 192.168.0.0/24(rw,no_root_squash) systemctl start nfs systemctl enable nfs mount -t nfs 192.168.0.13:/ifs/kubernetes /mnt/ umount /mnt/
3、为Jenkins准备PV
apiVersion: v1 kind: PersistentVolume metadata: name: pv0001 spec: capacity: storage: 5Gi accessModes: ["ReadWriteOnce" ] nfs: path: /ifs/kubernetes/jenkins-data server: 192.168.0.13
在k8s中部署jenkins
kubectl apply -f jenkins.yml
先安装后面所需的插件:
Jenkins下载插件默认服务器在国外,会比较慢,建议修改国内源:
cd /ifs/kubernetes/jenkins-datased -i 's/https:\/\/updates.jenkins.io\/download/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/g' default.json sed -i 's/http:\/\/www.google.com/https:\/\/www.baidu.com/g' default.json kubectl delete pod jenkins-d58f4db66-9cthj -n ops
管理Jenkins->系统配置–>管理插件–>分别搜索Git Parameter/Git/Pipeline/kubernetes/Config File Provider, 选中点击安装。
Git:拉取代码
Git Parameter:Git参数化构建
Pipeline:流水线
kubernetes:连接Kubernetes动态创建Slave代理
Config File Provider:存储配置文件
Extended Choice Parameter:扩展选择框参数,支持多选
Jenkins在K8s中动态创建代理
Jenkins主从架构介绍
Jenkins Master/Slave架构,Master(Jenkins本身)提供Web页面 让用户来管理项目和从节点(Slave),项目任务可以运行在Master 本机或者分配到从节点运行,一个Master可以关联多个Slave,这样 好处是可以让Slave分担Master工作压力和隔离构建环境。
当触发Jenkins任务时,Jenkins会调用Kubernetes API 创建Slave Pod,Pod启动后会连接Jenkins,接受任务 并处理。
Kubernetes插件配置
Kubernetes插件:用于Jenkins在Kubernetes集群中运行动态代理
插件介绍:https://github.com/jenkinsci/kubernetes-plugin
配置插件:管理Jenkins->管理Nodes和云->管理云->添加Kubernetes
自定义Jenkins Slave镜像
jenkins-slave.zip
构建salve镜像
unzip jenkins-slave.zip cd jenkins-slave/
课件目录里涉及六个文件:
构建并推送到镜像仓库:
docker build -t 192.168.0.14/library/jenkins-slave-jdk:1.8 . docker push 192.168.0.14/library/jenkins-slave-jdk:1.8
测试主从架构是否正常
新建项目->流水线->Pipeline脚本(可生成示例)
pipeline { agent { kubernetes { label "jenkins-slave" yaml '' ' apiVersion: v1 kind: Pod metadata: name: jenkins-slave spec: containers: - name: jnlp image: "192.168.0.14/library/jenkins-slave-jdk:1.8" ' '' } } stages { stage('Main' ) { steps { sh 'hostname' } } } }
Jenkins Pipeline流水线
Jenkins Pipeline 介绍
Jenkins Pipeline是一套运行工作流框架,将原本独立运行单个或者多个节点的任 务链接起来,实现单个任务难以完成的复杂流程编排和可视化。
Jenkins Pipeline是一套插件,支持在Jenkins中实现持续集成和持续交付;
Pipeline通过特定语法对简单到复杂的传输管道进行建模;
Jenkins Pipeline的定义被写入一个文本文件,称为Jenkinsfile。
Jenkins Pipeline 语法
Jenkins Pipeline 示例
Stages 是 Pipeline 中最主要的组成部分,Jenkins 将会按照 Stages 中描述的顺序 从上往下的执行。
Stage:阶段,一个 Pipeline 可以划分为若干个 Stage,每个 Stage 代表一组操作, 比如:Build、Test、Deploy
Steps:步骤,Steps 是最基本的操作单元,可以是打印一句话,也可以是构建一 个 Docker 镜像,由各类 Jenkins 插件提供,比如命令:sh ‘mvn’,就相当于我 们平时 shell 终端中执行 mvn命令一样。
流水线自动发布微服务项目
发布需求
在将微服务项目自动化部署到K8s平台会有这些需求:
实现思路
Pipeline编写思路:
在微服务架构中,会涉及几个、几十个微服务,如果每个服务都创建一个item,势必 给运维维护成本增加很大,因此需要编写一个通用Pipeline脚本,将这些微服务部署 差异化部分使用Jenkins参数化,人工交互确认发布的微服务、环境配置等。 但这只是解决用户交互层面,在K8s实际部署项目用YAML创建对应资源,现在问题是 如何接收用户交互参数,自动化生成YAML文件,这就会用到Helm完成YAML文件高 效复用和微服务部署。
部署一个微服务项目,每个微服务的差异化部分在哪里?
服务名称
代码版本
镜像
端口
副本数
标签
域名
编写Pipeline流水线脚本
对于课件中的Pipeline脚本,重点修改这几个变量:
1、将harbor认证和gitlab认证保存到Jenkins凭据
管理Jenkins->安全–>管理凭据->Jnekins->添加凭据->Username with password
分别添加连接gitlab和harbor的用户名到Jenkins凭据,然后获取该凭据ID替换到脚本中docker_registry_auth和git_auth变量的值。
2、将kubeconfig存储在Jenkins,用于slave镜像里kubectl连接k8s集群
管理Jenkins-> Managed files->Add->Custom file ->Content字段内容是kubeconfig(kubeadm部署k8s默认路径在master节点 /root/.kube/config,如果你是二进制部署,需要自己生成,参考下面),然后复制ID替换上述脚本中k8s_auth变量的值。
说明:将kubectl、helm工具封装到Slave镜像中,并通过Config File Provider插件存储连接K8s集群的kubeconfig认证文件,然后挂载到 Slave容器中,这样就能用kubectl apply deploy.yaml --kubeconfig=config管理K8s应用了,为提高安全性,kubeconfig文件可分配权限。
3.上传 helm chart包到镜像仓库
ms-0.1.0.tgz
创建命名空间并部署eureka和MySQL服务(记得还要部署ingress控制器)
kubectl create namespace ms kubectl apply -f mysql.yaml ./docker_build.sh eureka-service kubectl apply -f ingress-controller.yaml
jenkinsfile
#!/usr/bin/env groovy // 所需插件: Git Parameter/Git/Pipeline/Config File Provider/kubernetes/Extended Choice Parameter // 公共 def registry = "192.168.0.14" // 项目 def project = "microservice" def git_url = "http://192.168.0.14:88/root/k8s-microservice.git" def gateway_domain_name = "gateway.ctnrs.com" def portal_domain_name = "portal.ctnrs.com" // 认证 def image_pull_secret = "registry-pull-secret" def harbor_auth = "dc8877f5-3238-407f-a9fb-96932501a9b0" def git_auth = "8d022667-0d69-4e5c-a6f8-6d0ac532f596" // ConfigFileProvider ID def k8s_auth = "cf3c93bc-dc97-4305-8379-53a669a2b2b7" pipeline { agent { kubernetes { label "jenkins-slave" yaml "" " apiVersion: v1 kind: Pod metadata: name: jenkins-slave spec: containers: - name: jnlp image: " ${registry} /library/jenkins-slave-jdk:1.8" imagePullPolicy: Always volumeMounts: - name: docker-cmd mountPath: /usr/bin/docker - name: docker-sock mountPath: /var/run/docker.sock - name: maven-cache mountPath: /root/.m2 volumes: - name: docker-cmd hostPath: path: /usr/bin/docker - name: docker-sock hostPath: path: /var/run/docker.sock - name: maven-cache hostPath: path: /tmp/m2 " "" } } parameters { gitParameter branch: '' , branchFilter: '.*' , defaultValue: 'origin/master' , description: '选择发布的分支' , name: 'Branch' , quickFilterEnabled: false , selectedValue: 'NONE' , sortMode: 'NONE' , tagFilter: '*' , type : 'PT_BRANCH' extendedChoice defaultValue: 'none' , description: '选择发布的微服务' , \ multiSelectDelimiter: ',' , name: 'Service' , type : 'PT_CHECKBOX' , \ value: 'gateway-service:9999,portal-service:8080,product-service:8010,order-service:8020,stock-service:8030' choice (choices: ['ms' , 'demo' ], description: '部署模板' , name: 'Template' ) choice (choices: ['1' , '3' , '5' , '7' ], description: '副本数' , name: 'ReplicaCount' ) choice (choices: ['ms' ], description: '命名空间' , name: 'Namespace' ) } stages { stage('拉取代码' ){ steps { checkout([$class : 'GitSCM' , branches: [[name: "${params.Branch} " ]], extensions: [], userRemoteConfigs: [[credentialsId: "${git_auth} " , url: "${git_url} " ]] ]) } } stage('代码编译' ) { // 编译指定服务 steps { sh "" " mvn clean package -Dmaven.test.skip=true " "" } } stage('构建镜像' ) { steps { withCredentials([usernamePassword(credentialsId: "${harbor_auth} " , passwordVariable: 'password' , usernameVariable: 'username' )]) { sh "" " docker login -u ${username} -p '${password} ' ${registry} for service in \$(echo ${Service} |sed 's/,/ /g'); do service_name=\${service%:*} image_name=${registry} /${project} /\${service_name}:${BUILD_NUMBER} cd \${service_name} if ls |grep biz &>/dev/null; then cd \${service_name}-biz fi docker build -t \${image_name} . docker push \${image_name} cd ${WORKSPACE} done " "" configFileProvider([configFile(fileId: "${k8s_auth} " , targetLocation: "admin.kubeconfig" )]){ sh "" " # 添加镜像拉取认证 kubectl create secret docker-registry ${image_pull_secret} --docker-username=${username} --docker-password=${password} --docker-server=${registry} -n ${Namespace} --kubeconfig admin.kubeconfig |true # 添加私有chart仓库 helm repo add --username ${username} --password ${password} myrepo http://${registry} /chartrepo/${project} " "" } } } } stage('Helm部署到K8S' ) { steps { sh "" " common_args=" -n ${Namespace} --kubeconfig admin.kubeconfig" for service in \$(echo ${Service} |sed 's/,/ /g'); do service_name=\${service%:*} service_port=\${service#*:} image=${registry} /${project} /\${service_name} tag=${BUILD_NUMBER} helm_args=" \${service_name} --set image.repository=\${image} --set image.tag=\${tag} --set replicaCount=${replicaCount} --set imagePullSecrets[0].name=${image_pull_secret} --set service.targetPort=\${service_port} myrepo/${Template} " # 判断是否为新部署 if helm history \${service_name} \${common_args} &>/dev/null;then action=upgrade else action=install fi # 针对服务启用ingress if [ \${service_name} == " gateway-service" ]; then helm \${action} \${helm_args} \ --set ingress.enabled=true \ --set ingress.host=${gateway_domain_name} \ \${common_args} elif [ \${service_name} == " portal-service" ]; then helm \${action} \${helm_args} \ --set ingress.enabled=true \ --set ingress.host=${portal_domain_name} \ \${common_args} else helm \${action} \${helm_args} \${common_args} fi done # 查看Pod状态 sleep 10 kubectl get pods \${common_args} " "" } } } }
最终效果图:
流水线脚本与源代码一起版本管理
Jenkinsfile文件建议与源代码一起版本管理,实现流水线即 代码(Pipeline as Code)。
这样做的好处:
自动为所有分支创建流水线脚本
方便流水线代码复查、追踪、迭代
可被项目成员查看和编辑
小结
使用Jenkins的插件
CI/CD环境特点
Slave弹性伸缩
基于镜像隔离构建环境
流水线发布,易维护
Jenkins参数化构建可帮助你完成更复杂环境CI/CD
回滚思路:
1.使用kubectl rollout ,将资源名称和命名空间等参数化传入
2.每次发布记录发布的镜像版本写到一个历史文件中,Extended choice Parameter从历史文件中作为选择项