四时宝库

程序员的知识宝库

Jenkins+ansible+pipeline自动化构建

假如我们有几十个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上查看错误信息

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接