运维上线应用:
before:
开发同学提交代码->开发同学打包文件给运维->运维同学收到后,手动部署到服务器;
after:
开发同学提交代码->自动部署到服务器;

手动部署带来的一系列问题:

  1. 基于快速交付的大背景下,效率低下;
  2. 手动部署容易出现人为失误;
  3. 开发与运维频繁沟通,产生内耗;
  4. 运维人员必须登录服务器,出现安全问题;

自动化构建不仅极大提高上线效率,而且也降低了安全风险;

1.Gitlab搭建

  1. cat docker-compose.yml
  2. version: '2'
  3. services:
  4. gitlab:
  5. image: gitlab/gitlab-ce:11.3.6-ce.0
  6. restart: always
  7. network_mode: "bridge"
  8. environment:
  9. GITLAB_OMNIBUS_CONFIG: |
  10. external_url 'http://gitlab.monkey.com'
  11. gitlab_rails['time_zone'] = 'Asia/Shanghai'
  12. ports:
  13. - "8088:80"
  14. volumes:
  15. - ./data:/var/opt/gitlab
  16. - ./logs:/var/log/gitlab
  17. - ./config:/etc/gitlab

2.Jenkins搭建

  1. cat docker-compose.yml
  2. version: '3.2'
  3. services:
  4. jenkins:
  5. image: jenkinsci/blueocean:latest
  6. restart: always
  7. privileged: true
  8. user: root
  9. container_name: jenkins
  10. network_mode: "host"
  11. environment:
  12. - TZ=Asia/Shanghai
  13. volumes:
  14. - $PWD/jenkins-data:/var/jenkins_home
  15. - /var/run/docker.sock:/var/run/docker.sock
  16. ports:
  17. - "8080"
  18. - "50000"

3.ansible

  1. docker push monkeyyoung/ansible

4.实战:

在实际运用中,Jenkinsfile文件通常提交在各个git项目的根目录中,这样会出现一个问题,当git项目多了之后(特别是微服务),开发同学提交代码时或多或少会干涉到Jenkinsfile文件。
这里我们需要把Jenkinsfile从git项目中分离出来,并将所有项目的Jenkinsfile文件统一到一个Git项目中管理。

安装插件分离插件:

Pipeline: Multibranch with defaults image.png

新增Multibranch Pipeline with defaults 任务:
image.png

配置git地址:
可根据设置发现分支或标签,以及过滤规则
image.png

配置默认Jenkinsfile:
这里的Script ID 是系统设置中 Managed files 中的文件ID
image.png
image.png

默认Jenkinsfile:
看过文件后需要注意,${job_name} 参数对应你的git地址中的项目名称,如果有分支也需要对应的分支名目录

  1. #!/usr/bin/env groovy
  2. import groovy.transform.Field
  3. @Field def job_name=""
  4. node()
  5. {
  6. // 获取当前job名称。也可以按需自定义
  7. job_name="${env.JOB_NAME}".replace('%2F', '/').split('/')
  8. job_name=job_name[0]
  9. // 自定义workspace
  10. workspace="workspace/${job_name}/${env.BRANCH_NAME}"
  11. ws("$workspace")
  12. {
  13. dir("pipeline")
  14. {
  15. // clone Jenkinsfile项目
  16. git url:'http://gitlab.yshome.com:8081/root/jenkinsfile_out.git'
  17. // 根据job name、构建分支,自动加载对应的Jenkinsfile
  18. def check_groovy_file="${job_name}/${env.BRANCH_NAME}/Jenkinsfile"
  19. load "${check_groovy_file}"
  20. }
  21. }
  22. }
  23. // 该脚本的作用:clone包含所有Jenkinsfile的代码库,根据项目名称load对应的Jenkinsfile.

配置webhook:
安装:Multibranch Scan Webhook Trigger 插件
image.png
gitlab webhook:
image.png

Jenkinsfile参考:

//项目类型:WEB
pipeline {
  agent any
  environment {
    APP_NAME = 'pro_im'
    BRANCH = 'pro'
    HARBOR_HOST = '******' //镜像仓库地址
    DOCKER_IMAGE = 'yshome' //镜像仓库名
    LOGDIR = '/opt/logs/im/'
    PORT = '7015'

    docker_network = 'docker-network-app'
    Jenkins_workspace = '/var/jenkins_home/workspace'
    Jenkins_base_workspace = '/home/docker-jenkins/jenkins-data/workspace'

    CHAT_WEBHOOK_URL='https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key'
    CHAT_CONTENT_TYPE='Content-Type: application/json'
    CHAT_WEBHOOK_KEY='*******'
  }
  stages {
    stage('打印环境变量') {
      steps{
        sh '''
            printenv
        '''
      }  
    }
   stage('微信开始通知') {
      steps{
        sh 'printenv'
        sh """
            curl "${CHAT_WEBHOOK_URL}=${CHAT_WEBHOOK_KEY}" \
            -H "${CHAT_CONTENT_TYPE}" \
            -d '{
               "msgtype": "markdown",
               "markdown": {
                   "content": "#### Jenkins构建通知:\n>项目: <font color='comment'>${APP_NAME}</font>\n>分支: <font color=#C71585>${GIT_BRANCH}</font>\n>状态: <font color='blue'>开始</font>"
               }
              }'

          """
      }  
    }

      stage('编译打包') {
            agent {
                docker {
                    image 'maven:3-alpine'
                    args '-v /root/.m2:/root/.m2'
                }
            }
            steps {
                echo '2. 代码编译打包阶段.'
                sh 'mvn clean package -Dmaven.test.skip=true'
            }
        }

    stage('构建镜像'){
      agent any
      steps {
        withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId:'AliHarbor', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD']]){
          sh '''

                cp -rf ${Jenkins_workspace}/${APP_NAME}/pipeline/${APP_NAME}/Dockerfile .
                docker login ${HARBOR_HOST} -u ${USERNAME} -p ${PASSWORD}
                docker build -t ${HARBOR_HOST}/${DOCKER_IMAGE}/${APP_NAME}:${TAG_NAME} .
                docker push ${HARBOR_HOST}/${DOCKER_IMAGE}/${APP_NAME}:${TAG_NAME}        
                docker rmi  ${HARBOR_HOST}/${DOCKER_IMAGE}/${APP_NAME}:${TAG_NAME}



          '''
          }
      }
    }
    stage('部署'){
      agent {
        docker {
          image 'ansible:latest'
          args "-v /root/.ssh:/root/.ssh -v ${Jenkins_base_workspace}/${APP_NAME}/pipeline/${APP_NAME}/ansible/conf:/tmp "
        }
      }
      steps {
        sh '''
          ansible-playbook -i ${Jenkins_workspace}/${APP_NAME}/pipeline/${APP_NAME}/ansible/hosts -e LOGDIR=${LOGDIR} -e PORT=${PORT} -e HARBOR_HOST=${HARBOR_HOST} -e DOCKER_IMAGE=${DOCKER_IMAGE} -e Tag=${TAG_NAME} -e BRANCH=${BRANCH} -e APP_NAME=${APP_NAME} -e docker_network=${docker_network} ${Jenkins_workspace}/${APP_NAME}/pipeline/${APP_NAME}/ansible/main.yml
        '''
      }
    }
  }
  post {
    success {
        sh """
            curl "${CHAT_WEBHOOK_URL}=${CHAT_WEBHOOK_KEY}" \
            -H "${CHAT_CONTENT_TYPE}" \
            -d '{
               "msgtype": "markdown",
               "markdown": {
                   "content": "#### Jenkins构建通知:\n>项目: <font color='comment'>${APP_NAME}</font>\n>分支: <font color=#C71585>${GIT_BRANCH}</font>\n>状态: <font color=#32CD32>成功</font>"
               }
              }'

          """

    }

    failure {
        sh """
            curl "${CHAT_WEBHOOK_URL}=${CHAT_WEBHOOK_KEY}" \
            -H "${CHAT_CONTENT_TYPE}" \
            -d '{
               "msgtype": "markdown",
               "markdown": {
                   "content": "#### Jenkins构建通知:\n>项目: <font color='comment'>${APP_NAME}</font>\n>分支: <font color=#C71585>${GIT_BRANCH}</font>\n>状态: <font color=#FF0000>失败</font>"
               }
              }'

          """
    }
  }
}

Dockerfile:

FROM telegant/oraclejdk:1.8
#FROM fansin/oraclejdk:latest

MAINTAINER MonkeyYoung <448119056@qq.com>

ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
ENV TZ=Asia/Shanghai


RUN  yum -y install nmap-ncat

ADD BaseRoute/target/im.jar /opt/apps/im.jar

EXPOSE 7015/tcp

ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/opt/apps/im.jar","--spring.profiles.active=pro","--netty.port=7015"]

ansible/main.yml

---
- hosts: "{{ BRANCH }}"
  remote_user: root
  tasks:

      - name: mkdir_dir
        shell: 'mkdir -p /home/app/{{ APP_NAME }}'

      - name: copy_file
        template: src=/tmp/{{ item }}.j2 dest=/home/app/{{ APP_NAME }}/{{ item }}
        with_items:
        - docker-compose.yml

      - name: stop_docker-compose
        shell: 'docker-compose -f /home/app/{{ APP_NAME }}/docker-compose.yml stop'
        ignore_errors: True #忽略错误

      - name: test_container
        shell: 'docker ps -a | grep {{ APP_NAME }}'
        register: ps_result #注册变量
        ignore_errors: True #忽略错误

      - name: remove_docker
        shell: 'docker rm {{ APP_NAME }}'
        when: ps_result | succeeded

      - name: start_docker-compose
        shell: 'docker-compose -f /home/app/{{ APP_NAME }}/docker-compose.yml up -d'

ansible/conf/docker-compose.yml.j2

version: '3.2'
services:
  {{ APP_NAME }}:
      image: {{ HARBOR_HOST }}/{{ DOCKER_IMAGE }}/{{ APP_NAME }}:{{ Tag }}
      ports:
        - "{{ PORT }}:{{ PORT }}"
      container_name: "{{ APP_NAME }}"
      restart: always
      networks:
        - docker-network
      volumes:
        - /home/app/{{ APP_NAME }}/logs/:{{ LOGDIR }}
        - /etc/localtime:/etc/localtime
networks:
  docker-network:
    external:
      name: {{ docker_network }}