Jenkins是一个开源的持续集成的服务器,Jenkins开源帮助我们自动构建各类项目。Jenkins强大的插件式,使得Jenkins可以集成很多软件,可能帮助我们持续集成我们的工程项目。
一、什么是CI、CD
Devops 也就是开发运维一体化 ,而随着Devops的兴起,出现了持续集成 、持续交付 以及持续部署 的新方法,传统的软件开发和交付方法正在迅速变得过时。从历史上看,在敏捷时代,大多数公司会每月、每季度、每两年甚至每年发布部署或发布软件。然而,在DevOps时代,每周、每天、甚至每天多次。当SaaS正在占领世界时,我们可以轻松地动态更新应用程序,而无需强迫客户下载新组件。很多时候,他们甚至都不会意识到正在发生变化。开发团队通过软件交付流水线(Pipeline)实现自动化,以缩短交付周期,大多数团队都有自动化流程来检查代码并部署到新环境。
持续集成 的重点是将各个开发人员的工作集合到一个代码仓库中。通常,每天都要进行几次,主要目的是尽早发现集成错误,使团队更加紧密结合,更好地协作。
持续交付 的目的是最小化部署或释放过程中固有的摩擦。它的实现通常能够将构建部署的每个步骤自动化,以便任何时刻能够安全地完成代码发布(理想情况下)。
持续部署 是一种更高程度的自动化,无论何时对代码进行重大更改,都会自动进行构建/部署。
而Jenkins 就是来实现以上持续集成工作的服务器。
二、持续集成环境搭建 Jenkins流程图:
2.1 Gitlab代码托管服务器安装 Gitlab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的Web服务。
开启postfix服务(支持gitlab发信功能)
1 2 systemctl start postfix systemctl enable postfix
配置gitlab的yum源
1 2 3 4 5 6 7 8 9 vim /etc/yum.repos.d/gitlab-cd.repo [gitlab-ce] name=Gitlab CE Repository baseurl=https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el$releasever/ gpgcheck=0 enabled=1 yum clean all yum makecache
安装gitlab
1 yum -y install gitlab-ce
修改gitlab配置文件
1 2 3 4 5 6 7 8 9 vim /etc/gitlab/gitlab.rb # 用户访问所使用的URL,域名或者IP地址 external_url 'http://192.168.88.133' # 时区 gitlab_rails['time_zone'] = 'Asia/Shanghai' # 启用SMTP邮箱功能 gitlab_rails['smtp_enable'] = 'ture' # 使用SSH协议拉取代码所使用的连接端口 gitlab_rails['gitlab_shell_ssh_port'] = '22'
刷新配置
启动服务
1 2 3 gitlab-ctl restart gitlab-ctl status ......
docker-compose.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 version: '3.8' services: gitlab: container_name: gitlab image: gitlab/gitlab-ce:latest restart: always environment: GITLAB_OMNIBUS_CONFIG: | external_url 'http://192.168.88.30' gitlab_rails['time_zone'] = 'Asia/Shanghai' gitlab_rails['gitlab_shell_ssh_port'] = '22' ports: - '80:80' - '23:22' volumes: - '/root/cicd/gitlab/config:/etc/gitlab' - '/root/cicd/gitlab/logs:/var/log/gitlab' - '/root/cicd/gitlab/data:/var/opt/gitlab'
进入容器查看默认用户名和修改密码
1 2 3 4 5 6 7 8 9 gitlab-rails console # 查找root用户 u=User.where(id:1).first # 修改密码 u.password='toortoor' # 确认修改密码 u.password_confirmation='toortoor' # 保存修改 u.save
2.2 Gitlab创建组、用户、项目 Gitlab创建组:
添加组
设置组名和权限
Gitlab创建用户:
添加用户
配置选项
将用户添加到项目组
Gitlab创建项目:
在指定组中添加项目
设置项目名称
2.5 项目上传到Gitlab
2.4 Jenkins安装
安装JDK
1 yum -y install java-1.8.0-openjdk*
放行端口
1 2 firewall-cmd --zone=public --add-port=8888/tcp --permanent firewall-cmd --reload
添加Jenkins官方源并安装
1 2 3 wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key yum -y install jenkins
修改配置文件
1 2 3 vim /etc/sysconfig/jenkins JENKINS_USER="root" JENKINS_PORT="8888"
查看初始密码
1 2 cat /var/lib/jenkins/secrets/initialAdminPassword ......
docker-compose.yaml
1 2 3 4 5 6 7 8 9 10 11 version: '3.8' services: jenkins: container_name: jenkins image: jenkins:2.60.3 restart: always ports: - '8080:8080' - '50000:50000' volumes: - '/root/cicd/jenkins/home:/var/jenkins_home'
2.5 Jenkins中文插件安装
由于Jenkins官方下载插件很慢,我们修改为国内Jenkins插件地址,jenkins -> Manage jenkins -> Manage Plugins
更换配置文件中的地址
1 2 3 4 5 cp /var/lib/jenkins/updates/default.json /var/lib/jenkins/updates/default.json.backup sed -i 's/https:\/\/updates.jenkins.io\/download/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/g' /var/lib/jenkins/updates/default.json && sed -i 's/http:\/\/www.google.com/https:\/\/www.baidu.com/g' /var/lib/jenkins/updates/default.json systemctl restart jenkins
安装中文插件
2.6 Jenkins用户权限管理
由于Jenkins功能较为简介,都要靠安装插件来丰富体验,所以用户管理需要安装role-based插件
在Configure Global Security开启刚刚安装的插件
在Manage and Assign Roles中创建角色baserole并分配具体权限
用户没加入角色前是没有访问权限的,将用户加入到角色baserole即可,但只有部分权限
2.7 Jenkins安装凭证管理插件
安装Credential Binding插件,安装完就可以看到多了凭据的功能
2.8 Jenkins普通用户密码认证
为了Jenkins能够拉取gitlab的代码,需要安装git插件
创建普通用户凭证,注意这里的用户是gitlab创建好的用户
创建项目添加普通用户凭证
build now构建
Jenkins主机上查看是否构建成功
2.9 Jenkins使用ssh免密认证
在Jenkins主机上生产公钥私钥
在gitlab上添加公钥
在Jenkins上添加ssh凭证
创建项目添加ssh凭证
build now构建项目后
2.10 Jenkins安装Maven
下载maven
1 2 3 4 wget https://mirrors.bfsu.edu.cn/apache/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz mkdir maven tar -xf apache-maven-3.6.3-bin.tar.gz cp apache-maven-3.6.3-bin.tar.gz/* maven/
配置环境变量
1 2 3 4 5 6 vim /etc/profile export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk export MAVEN_HOME=/root/maven export PATH=$PATH:$JAVA_HOME/bin:/$MAVEN_HOME/bin source /etc/profile mvn -v
全局工具配置里新增jdk和maven
在系统配置里添加三个变量
修改maven配置文件
1 2 3 4 5 6 7 8 9 mkdir /root/repo vim /root/maven/conf/settings.xml <localRepository>/root/repo</localRepository> <mirror> <id>aliyunmaven</id> <mirrorOf>*</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>
测试maven构建项目
2.11 Tomcat安装和配置
安装jdk和tomcat
1 2 3 4 5 6 yum -y install java-1.8.0-openjdk* wget https://downloads.apache.org/tomcat/tomcat-9/v9.0.43/bin/apache-tomcat-9.0.43.tar.gz tar -xf apache-tomcat-9.0.43.tar.gz mkdir /root/tomcat mv apache-tomcat-9.0.43.tar.gz/* /root/tomcat ./root/tomcat/bin/startup.sh
添加tomcat管理用户
1 2 3 4 5 6 7 8 9 vim /root/tomcat/conf/tomcat-users.xml <role rolename="tomcat"/> <role rolename="role1"/> <role rolename="manager-gui"/> <role rolename="manager-script"/> <role rolename="admin-gui"/> <role rolename="admin-script"/> <role rolename="manager-status"/> <user username="tomcat" password="toortoor" roles="manager-gui,manager-script,tomcat,admin-gui,admin-script"/>
1 2 3 4 5 vim /root/tomcat/webapps/manager/META-INF/context.xml <!-- <Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" /> -->
测试
三、Jenkins构建项目 Jenkins构建的项目类型主要为以下三种:
自由风格软件项目(freestyle project)
Maven项目(maven project)
流水线项目(pipeline project)
3.1 自由风格软件项目构建
要将项目部署到tomcat服务器上,需要安装deploy to container插件
Jenkins -> 新建items
使用ssh免密登录来拉取代码
使用maven编译打包
部署
再次构建后可以回到tomcat查看是否部署成功
3.2 Maven项目构建
安装Maven Integration插件
创建maven项目
构建设置,其余设置都相同
构建后查看是否部署成功
3.3 Pipeline project简介 pipeline就是一套运行在Jenkins上的工作流框架,将独立运行的单个或多个节点的任务连接起来,实现复杂流程的编排和可视化的工作。
优点有:
自动地为所有分支创建流水线构建过程并拉取请求。
在流水线上代码复查/迭代 (以及剩余的源代码)。
对流水线进行审计跟踪。
该流水线的真正的源代码,可以被项目的多个成员查看和编辑。
3.4 Pipeline流水线项目构建
安装pipeline插件
新建流水线项目
声明式流水线
3.5 Jenkinsfile脚本文件 pipeline脚本内容放在Jenkins服务器不好管理,所以就在项目添加一个Jenkinsfile文件并推送到gitlab上,实现直接拉取执行。
在项目下添加一个Jenkinsfile,将pipeline内容复制进去并推送到gitlab上
在pipeline项目中修改为在gitlab拉取Jenkinsfile文件
四、Jenkins构建细节 4.1 Jenkins常用的构建触发器 Jenkins内置4种构建触发器:
触发远程构建
其它工程构建后触发
定时构建
轮询SCM
4.2 触发远程构建
在项目中设置触发器
在浏览器输入JENKINS_URL/job/pipeline-project/build?token=
TOKEN_NAME即可触发
4.3 其它工程构建后触发 是指某一工程构建后才会触发构建,这里测试freestyle工程构建后触发pipeline构建
在pipeline配置构建后触发
freestyle工程构建后就会触发pipeline构建
4.4 定时构建 五个*分别代表分时日月周,H/2则代表每分时日月周
4.5 轮询SCM 和定时构建一样是设置时间来进行构建,不过轮询只有在代码仓库发生变化后才会触发,相当于定时检查
注意:轮询会定时扫描仓库的代码,增大系统的开销,不建议使用
4.6 Gitlab Hook自动触发构建 SCM轮询是Jenkins服务器主动检测gitlab中的代码有没有发生变化,而gitlab的webhook可以实现代码变更后向Jenkins服务器主动发送构建请求,实现自动构建。
在Jenkins安装gitlab和gitlab hook插件
在gitlab配置中允许发出请求
在项目中添加第一步中的地址
配置Jenkins允许接受gitlab发送过来的请求
4.7 Jenkins参数化构建 前边实操演示的都是默认从master下拉取代码,如果要实现在其他分支拉取代码,就需要设置参数化构建。
在Jenkins的项目中添加参数,这里选择字符串参数
修改Jenkinsfile文件中拉取代码部分的master为变量,并推送到gitlab
在项目中添加一个v1分支
在Jenkins中拉取v1代码构建
4.8 配置邮件服务器发送构建结果
安装Email Extension插件
在Jenkins中配置
在项目中创建一个email.html文件,添加以下内容并推送到gitlab中
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 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" > <title > ${PROJECT_NAME}-第${BUILD_NUMBER}次构建日志</title > </head > <body leftmargin ="8" marginwidth ="0" topmargin ="8" marginheight ="4" > <table width ="95%" cellpadding ="0" cellspacing ="0" style ="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif" > <tr > <td > (本邮件是程序自动下发的,请勿回复!)<br /> </td > </tr > <tr > <td > <h2 > <font color ="#0000FF" > 构建结果 - ${BUILD_STATUS}</font > </h2 > </td > </tr > <tr > <td > <br /> <b > <font color ="#0B610B" > 构建信息</font > </b > <hr size ="2" width ="100%" align ="center" /> </td > </tr > <tr > <td > <ul > <li > 项目名称 : ${PROJECT_NAME}</li > <li > 构建编号 : 第${BUILD_NUMBER}次构建</li > <li > 触发原因: ${CAUSE}</li > <li > 构建日志: <a href ="${BUILD_URL}console" > ${BUILD_URL}console</a > </li > <li > 构建 Url : <a href ="${BUILD_URL}" > ${BUILD_URL}</a > </li > <li > 工作目录 : <a href ="${PROJECT_URL}ws" > ${PROJECT_URL}ws</a > </li > <li > 项目 Url : <a href ="${PROJECT_URL}" > ${PROJECT_URL}</a > </li > </ul > </td > </tr > <tr > <td > <b > <font color ="#0B610B" > Changes Since Last Successful Build:</font > </b > <hr size ="2" width ="100%" align ="center" /> </td > </tr > <tr > <td > <ul > <li > 历史变更记录 : <a href ="${PROJECT_URL}changes" > ${PROJECT_URL}changes</a > </li > </ul > ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br /> %c<br /> ",showPaths=true,changesFormat="<pre > [%a]<br /> %m</pre > ",pathFormat=" %p"} </td > </tr > <tr > <td > <hr size ="2" width ="100%" align ="center" /> </td > </tr > <tr > <td > </td > </tr > <tr > <td > <b > <font color ="#0B610B" > 构建情况总览:</font > </b > ${TEST_COUNTS,var="fail"}<br /> <hr size ="2" width ="100%" align ="center" /> </td > </tr > <tr > <td > <textarea cols ="80" rows ="30" readonly ="readonly" style ="font-family: Courier New" > ${BUILD_LOG,maxLines=23}</textarea > </td > </tr > </table > </body > </html >
在Jenkinsfile中添加发送email功能,post即指构建后操作
1 2 3 4 5 6 7 8 9 post { always { emailext( subject: '构建通知:${PROJECT_NAME} - Build # ${BUILD_NUMBER} - ${BUILD_STATUS}!', body: '${FILE,path="email.html"}', to: 'chenqiming13@qq.com' ) } }
构建测试是否收到邮件
五、Jenkins+SonarQube代码审查
sonarqube是一个用于管理代码质量的开放平台,可以快速定位代码中潜在的错误。
环境要求:
JDK11
PostgreSQL
5.1 安装PostgreSQL
新版本的sonarqube不再支持MySQL,所以在Jenkins服务器上安装PostgreSQL并创建一个sonar数据库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # 安装postgresql yum -y install https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm yum -y install postgresql96-server postgresql96-contrib # 初始化postgresql postgresql-9.6-setup initdb systemctl enable postgresql-9.6.service systemctl start postgresql-9.6.service # 初始化之后会自动创建postgres用户,切换用户 su - postgres # psql进入命令行模式配,\q退出 psql alter user postgres with password 'toortoor'; create database sonar; create user sonar; alter user sonar with password 'toortoor'; alter role sonar createdb; alter role sonar superuser; alter role sonar createrole; alter database sonar owner to sonar; \q
开启远程访问和远程连接
1 2 3 4 5 vim /var/lib/pgsql/9.6/data/postgresql.conf listen_addresses = '*' vim /var/lib/pgsql/9.6/data/pg_hba.conf host all all 127.0.0.1/32 trust host all all 192.168.88.1/32 trust
重启服务
5.2 安装SonarQube
安装sonarqube
1 2 3 4 5 6 wget https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-8.7.1.42226.zip unzip sonarqube-8.7.1.42226.zip mv sonarqube-8.7.1.42226 sonarqube useradd sonar chown -R sonar:sonar sonarqube/* mv sonarqube /opt/sonarqube
修改sonar配置文件
1 2 3 4 5 6 vim sonarqube/conf/sonar.properties sonar.jdbc.username=sonar sonar.jdbc.password=toortoor sonar.jdbc.url=jdbc:postgresql://localhost/sonarqube?currentSchema=my_schema sonar.jdbc.url=jdbc:postgresql://localhost/sonar?currentSchema=public sonar.web.port=9000
启动sonarqube
1 2 3 # 只能用sonar用户启动 su sonar ./opt/sonarqube/bin/linux-x86-64/sonar.sh start
遇到的问题
1 2 3 4 5 6 # sonar用户线程数不够,*代表所有用户 cat /etc/security/limits.conf * soft nofile 65536 * hard nofile 65536 * soft noproc 65535 * hard noproc 65535
deploy.yaml
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 apiVersion: apps/v1 kind: Deployment metadata: name: sonarqube labels: app: sonarqube spec: replicas: 1 selector: matchLabels: app: sonarqube template: metadata: name: sonarqube labels: app: sonarqube spec: initContainers: - name: init-sysctl image: busybox imagePullPolicy: IfNotPresent command: ["sysctl" , "-w" , "vm.max_map_count=262144" ] securityContext: privileged: true containers: - name: sonarqube image: sonarqube:lts-community imagePullPolicy: IfNotPresent ports: - containerPort: 9000 env: - name: SONARQUBE_JDBC_USERNAME value: "sonarqube" - name: SONARQUBE_JDBC_PASSWORD value: "dangerous" - name: SONARQUBE_JDBC_URL value: "jdbc:postgresql://dcs-installer-gitlab-postgresql.dcs-system:5432/sonarqube" livenessProbe: httpGet: path: /sessions/new port: 9000 initialDelaySeconds: 60 periodSeconds: 30 readinessProbe: httpGet: path: /sessions/new port: 9000 initialDelaySeconds: 60 periodSeconds: 30 failureThreshold: 6 resources: limits: cpu: 2000m memory: 2048Mi requests: cpu: 1000m memory: 1024Mi --- apiVersion: v1 kind: Service metadata: name: sonarqube spec: type: NodePort selector: app: sonarqube ports: - port: 9000 targetPort: 9000 protocol: TCP
5.3 Jenkins整合SonarQube
安装sonarqube scanner插件
在Jenkins安装sonarqube scanner
在sonarqube中生成密钥
在系统配置中配置sonarqube server,利用刚刚生成的密钥
5.4 实现代码审查 非流水线项目添加代码审查
在项目中添加构建步骤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 sonar.projectKey =freestyle_project sonar.projectName =freestyle_project sonar.projectVersion =1.0 sonar.sources =. sonar.exclusions =**/test/**,**/target/** sonar.java.source =11 sonar.java.target =11 sonar.sourceEncoding =UTF-8
构建项目
回到sonarqube就可以看到提交的代码审查了
流水线项目添加代码审查
在项目中创建sonar-project.properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 sonar.projectKey =pipeline_project sonar.projectName =pipeline_project sonar.projectVersion =1.0 sonar.sources =. sonar.exclusions =**/test/**,**/target/** sonar.java.source =11 sonar.java.target =11 sonar.sourceEncoding =UTF-8
修改Jenkinsfile文件
1 2 3 4 5 6 7 8 9 10 11 12 stage('code checking'){ steps { script { //引入scanner工具 scannerHome = tool 'sonar-scanner' } //引入sonarqube服务器环境 withSonarQubeEnv('sonarqube') { sh "${scannerHome}/bin/sonar-scanner" } } }
将Jenkinsfile和sonar-project.properties推送到gitlab
构建项目后在sonarqube就可以看到代码审查了
六、Jenkins+Docker+SpringCloud微服务持续集成
大致流程:
开发人员push代码到gitlab。
Jenkins服务器进行编译打包并构建镜像推送到harbor镜像仓库(服务器的JAVA环境要与项目对应)。
Jenkins服务器触发远程命令使生产服务器(tomcat)从私有仓库拉取镜像。
生产服务器(tomcat)生成容器,项目上线。
用户访问。
6.1 Harbor部署
下载
1 curl -O https://github.com/goharbor/harbor/releases/download/v2.3.4/harbor-offline-installer-v2.3.4.tgz
修改 harbor.yml
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 cp harbor.yml.tmpl harbor.yml egrep -v '^#|^$|*#' harbor.yml hostname: 192.168.88.30 http: port: 81 harbor_admin_password: toortoor database: password: toortoor max_idle_conns: 100 max_open_conns: 900 data_volume: /root/cicd/harbor/data trivy: ignore_unfixed: false skip_update: false insecure: false jobservice: max_job_workers: 10 notification: webhook_job_max_retry: 10 chart: absolute_url: disabled log: level: info local: rotate_count: 50 rotate_size: 200M location: /root/cicd/harbor/var _version: 2.3.0 proxy: http_proxy: https_proxy: no_proxy: components: - core - jobservice - trivy
docker-compose.yaml 文件中指定 harbor 访问端口为 80,避免与 gitlab 冲突, 修改为 81
通过脚本部署
创建私有仓库
修改 daemon.json,让 docker 信任此仓库
1 2 3 4 { "registry-mirrors" : [ "https://5v5rh037.mirror.aliyuncs.com" ] , "insecure-registries" : [ "192.168.88.30:81" ] }
登录远程镜像仓库
1 docker login -u admin -p toortoor 192.168.88.30:81
6.2 提交代码到Gitlab
6.3 Jenkins创建流水线工程
创建工程
在项目根目录下创建 Jenkinsfile,通过语法生成器进行编写
Jenkinsfile 拉取代码部分
在流水线工程中创建参数,如果有多个服务就可以根据参数选择进行构建
Jenkinsfile 添加编译打包微服务工程部分,以及通过 Dockerfile 构建镜像部分
在 pom.xml 中添加 Dockerfile 依赖
1 2 3 4 5 6 7 8 9 10 <plugin > <groupId > com.spotify</groupId > <artifactId > dockerfile-maven-plugin</artifactId > <version > 1.4.10</version > <configuration > <repository > ${project.artifactId}</repository > <buildArgs > <JAR_NAME > target/${project.build.finalname}.jar</JAR_NAME > </buildArgs > </plugin >
1 2 3 4 5 6 FROM openjdk:11 ARG JAR_NAMEWORKDIR /usr/src/myapp EXPOSE 8080 COPY ./${JAR_NAME} /usr/src/myapp/ ENTRYPOINT ["java" , "-jar" , "/usr/src/myapp/${JAR_NAME} " ]
Jenkinsfile 添加上传镜像至 Harbor 部分
在 Jenkins 中添加 Harbor 账户凭证,以便于上传代码,这里用到的是 Harbor 的用户名和密码
在语法生成器中生成新语法用于登录 Harbor 仓库
Jenkins 安装 Publish Over SSH 插件,能够对远程主机发送 shell 命令,安装完后先给远程主机发送公钥
1 ssh-copy-id root@192.168.88.30
接着生成流水线语法,去远程执行脚本文件 test.sh
test.sh
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 # !/bin/bash harbor_url=$1 project_name=$2 image_name=$3 service_port=$4 # 删除none容器 echo "docker rmi none..." docker images | grep none | awk '{ print "docker rmi "$3 }' | sh &>/dev/null # 检查是否有已运行容器 num=`docker ps | grep $project_name | wc -l` if [ $num -gt 0 ] then docker stop $project_name && docker rm $project_name &>/dev/null if [ `echo $?` -eq 0 ] then echo "docker stop $project_name && docker rm $project_name [OK]" else echo "ERROR: docker stop $project_name && docker rm $project_name" fi fi # 检查是否有重命名镜像 temp=`docker images | grep $image_name | awk '{ print $1":"$2 }' | wc -l` if [ $temp -eq 1 ] then docker rmi $image_name &>/dev/null if [ `echo $?` -eq 0 ] then echo "docker rmi $image_name [OK]" else echo "ERROR: docker rmi $image_name" fi fi # 登录镜像仓库 echo "docker login repository..." docker login -u admin -p toortoor $harbor_url &>/dev/null if [ `echo $?` -ne 0 ] then echo "ERROR: failed to login repository" else echo "docker login repository [OK]" fi # 拉取镜像 echo "docker pull image..." docker pull $image_name &>/dev/null if [ `echo $?` -ne 0 ] then echo "ERROR: failed to pull image" else echo "docker pull image [OK]" fi # 运行容器 echo "docker run container..." docker run -d --name $project_name -p $service_port:8080 $image_name &>/dev/null if [ `echo $?` -ne 0 ] then echo "ERROR: failed to run container" else echo "docker run container [OK]" fi
最终的 Jenkinsfile
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 def git_auth = "bad37d0d-7260-43a2-a350-9abf6e99753a" def git_url = "http://192.168.88.30/test_group/test_project.git" def harbor_url = "192.168.88.30:81" def harbor_repository = "test" def harbor_auth = "c147d241-a254-48d6-be1c-0d5f0964ce9a" def image_version = "1.0" def project_name = "test" def service_port = "8081" node { stage('Pull Code' ) { git branch: "${branch}" , credentialsId: "${git_auth}" , url: "${git_url}" } stage('Mvn clean package and Docker build' ) { sh "mvn clean package dockerfile:build" } stage('Push image to Harbor' ) { def image_name = "${project_name}:${image_version}" sh "docker tag ${project_name}:latest ${harbor_url}/${harbor_repository}/${image_name} && docker rmi ${project_name}:latest" withCredentials([usernamePassword(credentialsId: "${harbor_auth}" , passwordVariable: 'harbor_password' , usernameVariable: 'harbor_user' )]) { sh "docker login -u ${harbor_user} -p ${harbor_password} ${harbor_url}" } sh "docker push ${harbor_url}/${harbor_repository}/${project_name}:${image_version}" } stage('Pull image and Running container' ) { def image_name = "${project_name}:${image_version}" sshPublisher(publishers: [sshPublisherDesc(configName: '192.168.88.30' , transfers: [sshTransfer(cleanRemote: false , excludes: '' , execCommand: './root/test.sh $harbor_url $project_name $harbor_url/$harbor_repository/image_name $service_port' , execTimeout: 120000 , flatten: false , makeEmptyDirs: false , noDefaultExcludes: false , patternSeparator: '[, ]+' , remoteDirectory: '' , remoteDirectorySDF: false , removePrefix: '' , sourceFiles: '' )], usePromotionTimestamp: false , useWorkspaceInPromotion: false , verbose: false )]) } }
6.4 微服务架构CICD优化 从以上配置中可以看到,每次构建都只能选择一个服务,且部署的服务器也只有一台,是不符合生产环境的,主要需求有以下三个:
多个微服务同时构建流水线工程
批量处理镜像
将微服务部署服务器集群
安装 Extended Choice Parameter 插件,实现多个微服务同时构建
在创建流水线时设置选项
将公钥下发,并在系统设置的 Publish over SSH 中添加多台主机
在流水线工程中添加多一个多选项配置,用于选择要部署的服务器
修改流水线语法
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 def git_auth = "bad37d0d-7260-43a2-a350-9abf6e99753a" def git_url = "http://192.168.88.30/test_group/test_project.git" def harbor_url = "192.168.88.30:81" def harbor_repository = "test" def harbor_auth = "c147d241-a254-48d6-be1c-0d5f0964ce9a" def image_version = "1.0" node { def selectedProjectNames = "${project_name}" .split("," ) def selectedServices = "${publish_server}" .split("," ) stage('Pull Code' ) { git branch: "${branch}" , credentialsId: "${git_auth}" , url: "${git_url}" } stage('Mvn clean package and Docker build' ) { sh "mvn clean package dockerfile:build" } stage('Push image to Harbor' ) { for (int i=0 ; i<selectedProjectNames.length; i++) { def project_info = selectedProjectNames[i] def current_project_name = "${project_info}" .split("@" )[0 ] def image_name = "${current_project_name}:${image_version}" sh "docker tag ${current_project_name}:latest ${harbor_url}/${harbor_repository}/${image_name} && docker rmi ${current_project_name}:latest" withCredentials([usernamePassword(credentialsId: "${harbor_auth}" , passwordVariable: 'harbor_password' , usernameVariable: 'harbor_user' )]) { sh "docker login -u ${harbor_user} -p ${harbor_password} ${harbor_url}" } sh "docker push ${harbor_url}/${harbor_repository}/${current_project_name}:${image_version}" } } stage('Pull image and Running container' ) { for (int i=0 ; i<selectedProjectNames.length; i++) { def project_info = selectedProjectNames[i] def current_project_name = "${project_info}" .split("@" )[0 ] def current_project_port = "${project_info}" .split("@" )[1 ] def image_name = "${current_project_name}:${image_version}" for (int j=0 ; j<selectedServices.length; j++) { def current_server = selectedServices[j] if (current_server == "192.168.88.31" ) { activeProfile = activeProfile+"serviceName-server1" }else if (current_server == "192.168.88.32" ) { activeProfile = activeProfile+"serviceName-server2" } sshPublisher(publishers: [sshPublisherDesc(configName: "${current_server}" , transfers: [sshTransfer(cleanRemote: false , excludes: '' , execCommand: './root/test.sh $harbor_url $current_project_name $harbor_url/$harbor_repository/$image_name $current_project_port $activeProfile' , execTimeout: 120000 , flatten: false , makeEmptyDirs: false , noDefaultExcludes: false , patternSeparator: '[, ]+' , remoteDirectory: '' , remoteDirectorySDF: false , removePrefix: '' , sourceFiles: '' )], usePromotionTimestamp: false , useWorkspaceInPromotion: false , verbose: false )]) } } } }
修改 test.sh
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 # !/bin/bash harbor_url=$1 project_name=$2 image_name=$3 service_port=$4 profile=$6 # 删除none容器 echo "docker rmi none..." docker images | grep none | awk '{ print "docker rmi "$3 }' | sh &>/dev/null # 检查是否有已运行容器 num=`docker ps | grep $project_name | wc -l` if [ $num -gt 0 ] then docker stop $project_name && docker rm $project_name &>/dev/null if [ `echo $?` -eq 0 ] then echo "docker stop $project_name && docker rm $project_name [OK]" else echo "ERROR: docker stop $project_name && docker rm $project_name" fi fi # 检查是否有重命名镜像 temp=`docker images | grep $image_name | awk '{ print $1":"$2 }' | wc -l` if [ $temp -eq 1 ] then docker rmi $image_name &>/dev/null if [ `echo $?` -eq 0 ] then echo "docker rmi $image_name [OK]" else echo "ERROR: docker rmi $image_name" fi fi # 登录镜像仓库 echo "docker login repository..." docker login -u admin -p toortoor $harbor_url &>/dev/null if [ `echo $?` -ne 0 ] then echo "ERROR: failed to login repository" else echo "docker login repository [OK]" fi # 拉取镜像 echo "docker pull image..." docker pull $image_name &>/dev/null if [ `echo $?` -ne 0 ] then echo "ERROR: failed to pull image" else echo "docker pull image [OK]" fi # 运行容器 echo "docker run container..." docker run -d --name $project_name -p $service_port:8080 $image_name $profile &>/dev/null if [ `echo $?` -ne 0 ] then echo "ERROR: failed to run container" else echo "docker run container [OK]" fi
七、基于K8S的CICD 基于 Kubernetes 平台来实现 CICD 功能。
7.1 Jenkins对接K8S
首先通过 bitnami helm 部署 Jenkins(Master),需开放 jnlp 50000 端口
1 2 3 4 5 6 7 # 添加repo源 helm repo add bitnami https://charts.bitnami.com/bitnami # 下载chart到本地 helm pull bitnami/jenkins # 根据需求修改values.yaml # 部署 helm install jenkins jeknins/
RBAC 授权,SA 名为 jenkins,后续会用到
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 apiVersion: v1 kind: ServiceAccount metadata: name: jenkins namespace: nextcloud --- kind: Role apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: jenkins namespace: nextcloud rules: - apiGroups: ["" ] resources: ["pods" ] verbs: ["create" ,"delete" ,"get" ,"list" ,"patch" ,"update" ,"watch" ] - apiGroups: ["" ] resources: ["pods/exec" ] verbs: ["create" ,"delete" ,"get" ,"list" ,"patch" ,"update" ,"watch" ] - apiGroups: ["" ] resources: ["pods/log" ] verbs: ["get" ,"list" ,"watch" ] - apiGroups: ["" ] resources: ["secrets" ] verbs: ["get" ] - apiGroups: ["extensions" , "apps" ] resources: ["deployments" ] verbs: ["get" , "list" , "watch" , "create" , "update" , "patch" , "delete" ] - apiGroups: ["" ] resources: ["services" ] verbs: ["get" , "list" , "watch" , "create" , "update" , "patch" , "delete" ] --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: RoleBinding metadata: name: jenkins namespace: nextcloud roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: jenkins subjects: - kind: ServiceAccount name: jenkins namespace: nextcloud
1 kubectl get secret jenkins-token-xxx -oyaml | awk '/ca.crt/{ print $2 }' | base64 -d
设置 Pod Template,即 Jenkins Slave,将包含 jnlp 一个容器,来实现编译打包等功能。
这里需挂载宿主机的 docker.sock、docker 和 kubectl 的二进制文件。
然后使用到刚刚创建的 SA。
测试链接。
7.2 构建前准备 需要在项目的根目录下准备好 Dockerfile、Jenkinsfile、deploy.yaml。
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 def git_auth = "764f29f3-8955-4551-a23a-659aa4c8deaa" def git_url = "http://192.168.159.12:30098/root/nextcloud.git" def harbor_url = "192.168.159.101" def harbor_repository = "nextcloud" def harbor_auth = "7ebc36f8-73a3-417f-864f-24856490b1a2" def project_name = "nextcloud" node('jenkins-slave' ) { container('jnlp' ) { stage('Git Clone' ) { git branch: "${branch}" , credentialsId: "${git_auth}" , url: "${git_url}" } stage('Docker Build' ) { sh "docker build -t ${harbor_url}/${harbor_repository}/${project_name}:${branch}-${version} ." } stage('Docker Push' ) { withCredentials([usernamePassword(credentialsId: "${harbor_auth}" , passwordVariable: 'harbor_password' , usernameVariable: 'harbor_user' )]) { sh "docker login -u ${harbor_user} -p ${harbor_password} ${harbor_url}" } sh "docker push ${harbor_url}/${harbor_repository}/${project_name}:${branch}-${version}" sh "docker rmi ${harbor_url}/${harbor_repository}/${project_name}:${branch}-${version}" } } stage('Sed YAML' ) { sh "sed -i 's#build-tag#${branch}-${version}#g' deploy.yaml" } stage('Deploy to K8s' ) { sh "kubectl apply -f deploy.yaml" } }
7.3 创建流水线项目 这里添加了两个字符参数,用于构建前选择分支和 tag 的标签定义。
构建测试,会发现集群中创建了一个 jenkins-slave-xxx 的 pod,pod 中包含 jnlp 一个容器,通过观察日志可发现与 Jenkins Master 进行了连接。
Jenkins 中查看流水线进度。
流水线结束后,通过 kubectl 可看到项目已部署,且使用的镜像是刚刚构建好的。