假如我们有几十个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-keygen
ssh-copy-id server1
# 仅供说明,请根据实际情况配置
- 安装ansible插件
- 安装gitlab插件
2. 配置
凭据-系统-全局凭据-添加凭据
类型选择 SSH Username with private key
添加两个凭据,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.创建一个流水线项目
注,如果需要划分权限,可借助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上查看错误信息