假如我们有几十个Java项目,Python,node项目,安卓,IOS等各种项目,
而且又分为测试,预上线,正式等多个环境,不同环境的配置是不一样的,有的项目还可能同时有多个配置文件,
所以项目上线更新也是一个很大的工程,所以我们可以借助与Jenkins等CI/CD工具简化这个工作,需要上线时,只需要点下构建就行,由于上线这个过程可以保证没问题,所以这个上线权限可以直接交给开发人员。
大概思路:
我们可以给每个项目的配置文件创建一套配置模板,关键进行抽成模板语言变量,所有环境(测试,预上线,正式)共用这一套模板,构建时根据配置和模板渲染成配置文件进行替换,问题就是开发需要更新配置文件,需要在模板中添加,开发本地测试提到Git上的配置文件是不起作用的。
给每个环境创建一个配置文件(test.yml,release.yml,main.yml),
每一类项目创建一个playbook模板,项目的所有服务命名,配置文件位置文件名,启动方式,pom.xml等尽量统一。
playbook中定义所有动作:更新配置文件-打包(mvn, yarn,npm,go build, gradlew)-上传到服务器-重启服务
注,根据情况,playbook中可以添加你想添加的操作(比如上传到OSS,钉钉通知等),我们的服务管理用的是supervisord,所以服务器重启借助了supervisorctl
1.准备环境
- 安装jenkins+ansible
- 配置ansible服务器与其他服务器ssh密钥通信
ssh-keygenssh-copy-id server1
# 仅供说明,请根据实际情况配置
- 安装ansible插件
![0cdafff9fb1c53ad73b99d06e95fabd7.png](https://i-blog.csdnimg.cn/blog_migrate/8249e2a1e4883b191103652e1833f461.jpeg)
- 安装gitlab插件
![39d1170d6dc2c3970a0a8d17c30f067f.png](https://i-blog.csdnimg.cn/blog_migrate/e1e60d4e610ba19725c4aa433c6b9b04.jpeg)
2. 配置
凭据-系统-全局凭据-添加凭据
类型选择 SSH Username with private key
![ec90240e5dadfbc86158cdc0c140a85f.png](https://i-blog.csdnimg.cn/blog_migrate/3d6dc75c5eef420b2335a206fec02dba.jpeg)
添加两个凭据,1个ansible用,一个Git用,Private Key选择ssh的私钥文件
注,由于我们Git使用的是自己搭建的gitlab服务器,通过ssh拉取代码,所以添加两个同样的凭据,可以根据实际情况调整。
3. 从Git拉取代码
stage('拉取代码') { //def branch = input message: 'input branch name for this job', ok: 'ok', parameters: [string(defaultValue: 'develop', description: 'branch name', name: 'branch')] // 可以手动输入分支 git branch: "develop", credentialsId: 'f16ff4e6-98f', url: "${registry}" // credentialsId配置上面添加的Git凭据}
4. ansible配置
stage('执行构建') { ansiblePlaybook( credentialsId: 'b260876f6b0', // 配置上面添加的ansible凭据 disableHostKeyChecking: true, installation:"ansible", inventory: "/etc/ansible/inventory/hosts", // 指定ansible inventory playbook: "/etc/ansible/playbook/${playbook_template}", // 指定playbook extras: "-e job_name=${JOB_NAME} -e jenkins_home=${JENKINS_HOME} -e configfile=${configfile} -e proj=${proj} -e inventory=${inventory}", // 传递参数 ) }
5.创建一个流水线项目
![8aa83b2b6b478862c81ad05b3bef1d1d.png](https://i-blog.csdnimg.cn/blog_migrate/10db9ad5031e965d57a225cde971f4ab.jpeg)
注,如果需要划分权限,可借助Role-based Authorization Strategy插件,项目命名也要规范,test-*对应测试环境,release-*对应预上线环境,main-*对应生产环境,方便后续的授权。
6.配置项目
- General-描述:添加项目描述,可备注项目访问地址,端口,等信息
- 高级项目选项-显示名称:可以配置一个更可读的名称
- 流水线-Pipeline Script:Pipeline脚本
脚本如下,可先定义变量,然后其他项目可以使用复制功能,或者调用Jenkins API批量创建,只需要修改变量就行。
node { def branch = "develop" // Git 分支 def proj = 'app' //项目名称 def configfile = 'test.yml' //配置文件 def inventory = '192.168.1.1,server-app1,server-group' //主机组或者主机列表 def playbook_template = "app.yml" def registry = "git@git.example.com/app/app.git" // Git仓库地址 stage('拉取代码') { // for display purposes //def branch = input message: 'input branch name for this job', ok: 'ok', parameters: [string(defaultValue: 'develop', description: 'branch name', name: 'branch')] git branch: "${branch}", credentialsId: 'f16ff4e65284698f', url: "${registry}" } stage('执行构建') { ansiblePlaybook( credentialsId: 'b260897f776f6b0', disableHostKeyChecking: true, installation:"ansible", inventory: '/etc/ansible/inventory/hosts', playbook: "/etc/ansible/playbook/${playbook_template}", extras: "-e job_name=${JOB_NAME} -e jenkins_home=${JENKINS_HOME} -e proj=${proj} -e configfile=${configfile} -e inventory=${inventory} -e branch=${branch}", ) }}
参数说明:
- branch:定义Git分支,不同分支对应不同环境
- proj:项目名称,所有环境名称一致,playbook中会用到,可以根据这个名称构建指定项目,拷贝包或文件到指定目录,作为supervisorctl重启服务的名称
- configfie:配置文件,里面定义端口,调用地址,MySQL,MongoDB,Redis,ES,kafka地址账号等配置信息,对应不同环境
- inventory:主机组或者主机列表,可以同时更新多台服务器,多个主机用逗号隔开,主机名和主机组名称为ansible inventory/hosts的里面配置名称
- playbook_template:playbook的模板,不同项目操作不同(java,node,andriod),可以定义多个playbook模板,然后传入不同参数执行。
- registry:项目的Git地址,每个项目都是可变的,所以要抽出来。
注意:
修改credentialsId为自己的credentialsId,
可以使用def继续增量变量,但extras也要同步增加,在playbook中才能使用,不同playbook的extras根据实际情况调整
7. playbook的配置片段
主机和变量文件配置
- hosts: "{{ inventory }}" // 这个对应的是pipeline的inventory变量 gather_facts: false vars_files: - /etc/ansible/host_vars/common.yml // 定义一些公共变量如proxy_host,template_dir
更新配置文件,template_local是一个自定义的模块,可以用template模块代替
- name: update properties configure file run_once: true delegate_to: "{{ proxy_host }}" template_local: template_dir: "{{ template_dir }}/{{ proj }}" src_template_file: "{{ item }}.properties" dest_template_file: "{{ jenkins_home }}/workspace/{{ job_name }}/{{ proj }}/src/main/resources/config/{{ item }}.properties" var_file: /etc/ansible/host_vars/{{ configfile }} // configfile 对应Pipeline中的configfile with_items: - application - common
mvn 打包
- name: mvn package run_once: true delegate_to: "{{ proxy_host }}" shell: cd "{{ jenkins_home }}/workspace/{{ job_name }}/" && mvn -U -pl {{ proj }} -am clean package // 仅打包指定项目
node编译,这个编译是在Jenkins服务器上,所以除了第一次,之后执行yarn install会很快
- name: build node for andriod run_once: true delegate_to: "{{ proxy_host }}" shell: export PATH=$PATH:/data/apps/node/ && cd "{{ jenkins_home }}/workspace/{{ job_name }}/node " && yarn install && yarn build
apk打包
- name: build apk package run_once: true delegate_to: "{{ proxy_host }}" shell: cd "{{ jenkins_home }}/workspace/{{ job_name }}/Android/platforms/android/" && bash gradlew debug
jar包上传服务器进行更新
- name: copy jar to remote copy: src: "{{ jenkins_home }}/workspace/{{ job_name }}/{{ proj }}/target/{{ jar_version.stdout }}" // jar_version是写的另一个插件解析pom.xml获取jar/war的名字 dest: "/data/apps/{{ proj }}/" backup: yes // 开启备份
更新静态文件,这种HTML/css/js项目更新前需要备份的话,可先执行个shell拷贝
- name: rsync files to remote delegate_to: "{{ proxy_host }}" synchronize: src: "{{ jenkins_home }}/workspace/{{ job_name }}/html/" dest: /data/apps/{{ proj }}/app/ dest_port: "{{ ansible_port }}" checksum: yes delete: yes recursive: yes rsync_opts: - "--exclude=.svn*" - "--exclude=.git"
重启服务
- name: restart service shell: supervisorctl restart "{{ proj }}"
借助when可以根据分支来做备份(正式环境才备份)
- name: backup app dir become: yes become_method: sudo become_user: root shell: cd /data/{{ proj }}/app && cp -r dist dist_{{ build_time }} when: branch == "master"
docker容器镜像更新
- name: stop log service shell: "[[ $(docker ps -a | grep {{ item }} | wc -l) -eq 1 ]] && docker stop {{ item }} && docker rm {{ item }} || echo 0" with_items: - log-normal - log-video - log-judge - name: start log service shell: "docker run -d --name={{ item }} -v /data/apps/{{ item }}/conf:/conf -v /data/logs/trace:/data/logs/trace -v /etc/localtime:/etc/localtime harbor.aliyuncs.com/app/{{ repo }}" with_items: - log-normal - log-video - log-judge
使用template模块自动生成Dockerfile和deplyment.yml
ansible服务器需要能跟镜像仓库和k8s集群通信
- name: generate Dockerfile && deployment.yml run_once: true delegate_to: "{{ proxy_host }}" // 这个要为ansible服务器IP或者127.0.0.1 template: src: "{{ template_dir }}/docker/{{ item }}" // 提前创建好Dockerfile和deployment.yml模板 dest: "{{ jenkins_home }}/workspace/{{ job_name }}/{{ item }}" with_items: - Dockerfile - deployment.yml
注,如果执行playbook使用的是普通用户,一些操作需要给予sudo权限
become: yes
become_method: sudo
become_user: root
playbook执行报错,开发人员可以直接在Jenkins上查看错误信息
![c10ed03b6a1bfde730f7c6e0d42e65ac.png](https://i-blog.csdnimg.cn/blog_migrate/92cc8edb3242e0525b953e94d81cf897.jpeg)