一、前言

在开发过程中项目的发布上线尤其是令人头疼的一个环节,代码更新、配置、编译、发布,每一个环节都需要人工参与的情况下,单个项目或许尚且能够接受,但如果多个项目的情况下,对于运维工作会是一场灾难。
另一方面,在部署项目时,系统的环境往往也是困扰着运维人员的一个因素,是Ubuntu还是CentOs,版本是8是7还是6,环境的因素很大程度上会影响部署的应用,更遑论每个应用间还会相互干扰。
因此引入了Docker+Jenkins,要实现的需求很简单,第一部署不再需要关心系统环境,每个应用独立运行;第二部署自动化,代码能够发布后不管。
再者,目前微服务盛行,而集群运维也越来越多的交给k8s,要上这两个首先容器化和部署自动化都是必须走的第一步。

二、准备

2.1 采用技术栈

  • 应用容器引擎:Docker
  • 容器编排工具:Docker-Compose
  • 可视化容器管理工具:Portainer
  • 持续集成工具:Jenkins

    2.2 工作空间目录结构

    1. ├── jenkins_home (jenkins工作空间映射目录)
    2. ├── nginx (nginx容器映射目录)
    3. ├── conf (nginx配置文件)
    4. ├── html (前端项目部署目录)
    5. └── logs (运行日志)
    6. ├── redis (redis容器映射目录)
    7. ├── conf (redis配置文件)
    8. └── logs (运行日志)
    9. ├── tomcat (tomcat容器映射目录)
    10. ├── log (运行日志)
    11. └── webapps (项目部署目录)
    12. └── logs (运行日志)
    13. └── docker-compose.yml (docker配置文件)

    三、安装Docker

    3.1 Docker本体安装

    CentOs下的Docker安装只需执行以下命令:
    curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
    
    其它系统环境下的安装方式

    3.2 容器编排工具Docker-Compose安装

    拉取稳定版Docker Compose进行安装(要安装其他版本可替换1.24.1):
    curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    
    将可执行权限应用于二进制文件:
    chmod +x /usr/local/bin/docker-compose
    
    创建软链接:
    ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
    

    3.3 可视化容器管理Portainer安装

    通过docker拉取最新版本的portainer,映射在宿主机的9000端口,赋予其管理其他docker容器的权限,并设置随docker启动:
    docker pull portainer/portainer
    docker run -d -p 9000:9000 -v /root/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock --restart=always --name dev-portainer portainer/portainer
    

    3.4 常用命令

    docker启动/重启:
    systemctl start docker
    systemctl restart docker
    
    查看运行容器信息:
    docker ps
    
    查询容器
    docker search 容器名
    

    四、编写配置Yaml文件

    4.1 常用配置项说明

    services:
    # 服务名
    tomcat:
    # 容器实例名,必须唯一
    container_name: "tomcat"
    # 镜像名,可以通过镜像名、镜像id、等方式定义
    image: tomcat:8.5.38
    # 映射端口,格式为:宿主机端口:容器端口
    ports:
     - "8001:8080"
    # 映射目录,格式为:宿主机目录:容器目录
    volumes:
     - "/usr/local/workspace/tomcat/log:/usr/local/tomcat/logs"
     - "/usr/local/workspace/tomcat/webapps:/usr/local/tomcat/webapps"
    # 配置环境变量,仅在容器初始化时生效
    environment:
    # 配置容器初始化时需要执行的命令
    command:
    # 使用网络配置,用于定义容器间互相通讯用的内部地址/别名等
    networks:
     workspace:
      ipv4_address: 172.19.0.80
    
    注意文件缩进,使用空格缩进,不要使用tab。
    更多配置项信息

    4.2 配置宿主机映射目录

    根据需要在宿主机上建立映射目录,如果拿不准容器下对应的目录路径,可以先不填,创建以后通过portainer登录容器控制端后查询,目前用到的几个目录如下:
    volumes:
     # tomcat日志
     - "/usr/local/workspace/tomcat/log:/usr/local/tomcat/logs"
     # tomcat项目部署目录
     - "/usr/local/workspace/tomcat/webapps:/usr/local/tomcat/webapps"
    volumes:
     # redis配置文件
     - "/usr/local/workspace/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf"
     # redis日志
     - "/usr/local/workspace/redis/log:/usr/local/etc/logs"
    volumes:
     # nginx配置文件
     - "/usr/local/workspace/nginx/conf/nginx.conf:/etc/nginx/nginx.conf"
     # 前端页面放置目录
     - "/usr/local/workspace/nginx/html:/usr/share/nginx/html"
     # nginx日志
     - "/usr/local/workspace/nginx/logs:/var/log/nginx"
     # 上传文件放置目录
     - "/usr/local/workspace/tomcat/webapps/upload:/usr/share/nginx/html/upload_group/upload"
     - "/usr/local/workspace/tomcat_stock/webapps/upload:/usr/share/nginx/html/upload_stock/upload"
    volumes:
     # jenkins项目工作空间
     - "/usr/local/workspace/jenkins_home:/var/jenkins_home"
     # 授权jenkins可以管理其他容器
     - "/var/run/docker.sock:/var/run/docker.sock"
    

    4.3 配置自定义网络

    在默认情况下Docker在启动容器时会为容器自动配置内网ip,ip地址与容器启动的顺序有关。但实际业务中往往需要为应用配置固定IP,这个时候可以使用自定义配置网络
    networks:
    # 自定义网络名
    workspace:
    ipam:
     driver: default
     config:
      # 子网掩码
      - subnet: 172.19.0.0/16
    

    4.4 参考配置yaml(这边例子有两个tomcat部署的方式,根据实际调整咯)

    # yml配置
    version: "3"
    # 自定义一个网络
    networks:
    workspace:
    ipam:
     driver: default
     config:
      - subnet: 172.19.0.0/16
    services:
    # tomcat1配置
    tomcat:
    container_name: "tomcat"
    image: tomcat:8.5.38
    ports:
     - "8001:8080"
    volumes:
     - "/usr/local/workspace/tomcat/log:/usr/local/tomcat/logs"
     - "/usr/local/workspace/tomcat/webapps:/usr/local/tomcat/webapps"
    networks:
     workspace:
      ipv4_address: 172.19.0.80
    # tomcat2配置
    tomcat_stock:
    container_name: "tomcat_stock"
    image: tomcat:8.5.38
    ports:
     - "8002:8080"
    volumes:
     - "/usr/local/workspace/tomcat_stock/log:/usr/local/tomcat/logs"
     - "/usr/local/workspace/tomcat_stock/webapps:/usr/local/tomcat/webapps"
    networks:
     workspace:
      ipv4_address: 172.19.0.100
    # mysql配置
    mysql:
    container_name: "mysql"
    image: mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: "12345qwerT.."
      MYSQL_ROOT_HOST: "%"
      MYSQL_USER: "cus"
      MYSQL_PASSWORD: "12345qwerT.."
      MYSQL_DATABASE: "xin_group_fixed_asset_db"
    networks:
     workspace:
    # redis配置
    redis:
    container_name: "redis"
    image: redis
    ports:
     - "6379:6379"
    volumes:
     - "/usr/local/workspace/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf"
     - "/usr/local/workspace/redis/log:/usr/local/etc/logs"
    command:
     "redis-server /usr/local/etc/redis/redis.conf"
    networks:
     workspace:
    # nginx配置
    nginx:
    container_name: "nginx"
    image: nginx
    ports:
     - "80:80"
     - "100:100"
    volumes:
     - "/usr/local/workspace/nginx/conf/nginx.conf:/etc/nginx/nginx.conf"
     - "/usr/local/workspace/nginx/html:/usr/share/nginx/html"
     - "/usr/local/workspace/nginx/logs:/var/log/nginx"
     - "/usr/local/workspace/tomcat/webapps/upload:/usr/share/nginx/html/upload_group/upload"
     - "/usr/local/workspace/tomcat_stock/webapps/upload:/usr/share/nginx/html/upload_stock/upload"
    networks:
     workspace:
    # jenkins配置
    jenkins:
    container_name: "jenkins"
    image: jenkins/jenkins:lts
    ports:
     - "8000:8080"
     - "50000:50000"
    volumes:
     - "/usr/local/workspace/jenkins_home:/var/jenkins_home"
     - "/var/run/docker.sock:/var/run/docker.sock"
    networks:
     workspace:
    
    注意配置文件名需命名为docker-compose.yaml,否则启动时需要指定配置文件名。编写好配置文件后跳转到配置文件所在目录,执行以下命令,docker-compose会自动拉取镜像进行配置安装:
    docker-compose up -d
    

    五、安装和配置Jenkins

    5.1 初始化及插件安装

    5.1.1 Jenkins安装及初始化
    使用官方提供的最新镜像进行安装(jenkins/jenkins:lts),避免出错
    初次访问需要登录Jenkins所在容器,执行查询命令获得初始密码
    /var/jenkins_home/secrets/initialAdminPassword
    
    image.png

image.png
选择安装推荐插件
image.png
因为网络的问题在安装过程中可能出现插件安装失败的情况,可以先将安装失败的插件名记录下来之后更换源后再重新下载,也可选择重试几次。
插件安装完成后设置管理员信息,一路保存进入主界面
image.png

5.1.2 其他所需安装插件

首先为Jenkins设置源地址,加快插件下载速度,在Jenkins管理界面上,进入管理Jenkins>插件管理>Advanced页签,找到Update Site,设置为 https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
image.png
在Jenkins管理界面上,进入管理Jenkins>插件管理,在可选插件标签页下查询可安装插件
image.png
image.png
需要安装的插件列表如下

  • Maven插件

image.png

  • NodeJs插件

image.png

  • Docker插件

image.png

  • Publish Over SSH插件

image.png

  • GitLab & Git Parameter & Generic Webhook Trigger

image.png
image.png
image.png

  • 中文语言包

image.png
image.png
安装完成后访问http://Jenkins地址/restart/,重启Jenkins,重启完成后在installed页签检查插件是否成功安装

5.2 系统配置及全局工具配置

5.2.1 中文语言设置

在系统设置中找到Locale一栏,输入zh_CN,并勾选下面的复选框
image.png

5.2.2 Maven插件配置

进入管理Jenkins>全局工具设置,找到Maven一栏(注意首先要安装Maven插件)
image.png
在Maven配置一栏中,可以选择使用默认配置(比较慢),也可以使用自定义配置,在宿主机的jenkins_home目录下放入maven配置,并在此处填写对应容器下的路径即可
image.png
在Maven一栏,点击Maven安装设置别名,勾选自动安装,选择从Apache安装,如此初次构建项目时Jenkins会自动在容器中安装Maven(如果自行安装了Maven则不需要勾选自动安装复选框)
image.png

5.2.3 NodeJs插件配置

进入管理Jenkins>全局工具设置,找到NodeJs一栏(注意首先要安装NodeJs插件),选择自动安装
image.png

5.2.4 Docker插件配置

*该项用于流水线构建,可以先不用设置
进入管理Jenkins>系统管理,在最下方找到Cloud(云)一栏,点击前往管理
image.png
在管理页面,点击add a new cloud按钮,在下拉框中选择docker,设置名称,并配置远程docker地址(远程宿主机需要先开放对应端口的访问,详见下一节),点击Test Connection检查配置情况
image.png

5.2.5 远程SSH插件配置

进入管理Jenkins>系统管理,找到Publish over SSH一栏,点击新增按钮,新增一条远程SSH配置,设置别名、链接Host、登录用户名、访问目录,点击高级,勾选Use password authentication, or use a different key复选框,输入登录密码
image.png

5.2.6 JDK配置

一般来说Jenkins安装应当自带JDK,可以登录容器查看是否安装java,如果没有同样可以自动安装
image.png

5.3 配置远程宿主机Docker开放访问

在宿主机下编辑docker配置信息

vim  /usr/lib/systemd/system/docker.service

找到ExecStart一项,将其修改为

ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock

注意服务器开放2376端口,修改完成后重启网络并重启docker

systemctl daemon-reload
systemctl restart docker

5.4 配置Maven项目

5.4.1 创建项目

进入Jenkins主界面,点击左侧新建Item按钮,创建一个新工程,如果正确安装并配置了Maven插件,在工程创建向导页面会出现 构建一个Maven项目 的选项
image.png
image.png
当几个工程配置都差不多时,可以输入已有工程的名称,复制已有工程的配置

5.4.2 构建策略设置

可以设置工作空间中最多可保持的构建文件数量和保存天数,这里节省空间选则保持1个保留2天
image.png

5.4.3 源码管理设置

这里设置git地址和对应要拉取的分支,注意git版本要写对
image.png

5.4.4 构建触发器设置

这里选择api触发,此处设置token后,可以通过地址http://JENKINS_URL/job/工程名/build?token=TOKEN_NAME触发构建
image.png

5.4.5 构建环境设置

正确安装publish over ssh插件后,能够看到在构建前/后通过SSH向远程服务器发生文件或执行命令选项,这里在构建前执行停止tomcat容器,删除war包命令;构建后传送war包并启动tomcat容器:
image.png
image.png
使用Ant进行构建,注意jdk版本
image.png

5.4.6 构建设置

设置root目录下的pom文件,一般为pom.xml,设置要执行的maven命令
image.png
点击右下角 高级 按钮,可以在弹出的配置选项中为maven设置使用自定义的配置文件
image.png
保存完成配置后,可以立即构建查看效果

5.5 配置Vue项目

5.4.1 创建项目

选择free style project创建工程
image.png

5.4.2 构建策略

同Maven项目,也可以根据实际情况另行设置

5.4.3 源码管理

同Maven项目

5.4.4 构建触发器

同Maven项目

5.4.5 构建环境

构建前清空远程宿主机nginx映射目录下对应压缩包
image.png
构建后传输压缩文件,跳转到映射目录并解压压缩包
image.png
如果成功安装NodeJs插件并完成配置,此处会看到Provide Node & npm bin/ folder to PATH选项,勾选使用node进行编译
image.png

5.4.6 构建

执行两个命令,一个编译一个压缩
image.png
点击保存完成设置

六、配置GitLab持续集成

6.1 知识背景

Jenkins+GitLab实现CI/CD的基本原理简单来说就是当GitLab上发生了指定的事件(例如分支提交,或者标签提交),GitLab就向指定的集成服务地址发送一个包含push信息的post请求,集成服务端就根据这个信息来决定是否要进行构建。
首先来看一下GitLab发送过来的消息长什么样:

{
    "object_kind":"push",
    "event_name":"push",
    "before":"aa78724f0ee82ba75a1aa8fac4f320e52c668875",
    "after":"932a3f37dd4f767fb48d6e09bd27672994f2c8a1",
    "ref":"refs/heads/dev_wph",
    "checkout_sha":"932a3f37dd4f767fb48d6e09bd27672994f2c8a1",
    "message":null,
    "user_id":22,
    "user_name":"xxx",
    "user_username":"xxx",
    "user_email":"",
    "user_avatar":"https://www.gravatar.com/avatar/96e763b00b6b7e8a2fdd4856f4c3b71d?s=80&d=identicon",
    "project_id":16,
    "project":{项目信息},
    "commits":[本次提交的详细内容],
    "total_commits_count":3,
    "push_options":{

    },
    "repository":{
        "name":"项目名",
        "url":"git@110.x.x.x:xinGroupFixedAsset/xinGroupFixedAssetApi.git",
        "description":"描述",
        "homepage":"http://110.x.x.x:8090/xinGroupFixedAsset/xinGroupFixedAssetApi",
        "git_http_url":"http://110.x.x.x:8090/xinGroupFixedAsset/xinGroupFixedAssetApi.git",
        "git_ssh_url":"git@110.x.x.x:xinGroupFixedAsset/xinGroupFixedAssetApi.git",
        "visibility_level":0
    }
}

这里要用到的关键数据有这几个:

  • git_http_url:git地址
  • object_kind:操作类型
  • ref:本次提交影响的分支
  • commitsId:本次提交的id

当接收到GitLab传来的消息后,我们需要使用JsonPath来获取到我们需要的关键数据,在JsonPath中,$代表根节点,例如我们要获取repository下的git_http_url,只需要这样写:

$.repository.git_http_url

6.2 GitLab配置

6.2.1 创建远程访问令牌

登录Gitlab,进入账号设置页面,点击左侧Access Tokens,进入令牌管理
image.png
输入名称,勾选api访问,要不要设置过期时间随意,点击下方绿色的生成令牌按钮
image.png
将生成的令牌保存下来,之后配置在Jenkins里,在Jenkins主动构建时就可以通过api获取和指定要构建的分支了

6.2.2 配置事件钩子

接下来为GitLab配置触发事件时需要推送的Jenkins地址,进入需要集成的项目,在左侧设置菜单下拉中找到Integrations(集成),在进入的页面中设置地址和需要触发的事件
image.png
地址填什么在接下来的Jenkins配置里细说,触发事件支持push触发/标签触发等,如果觉得push触发太频繁可以使用标签触发

6.3 Jenkins配置

6.3.1 系统配置

进入管理Jenkins>系统配置,找到Gitlab一栏,填写连接别名,访问地址(注意不是项目地址),设置访问令牌。这里的访问令牌就是6.2.1中我们创建的远程令牌,完毕后测试是否成功
image.png

6.3.2 项目配置

进入任意一个已经创建的job或者新建一个,在General页签下,GitLab Connection选择我们在系统配置中设置的Gitlab地址,勾选参数构建,在主动构建时允许我们选择分支
image.png
在源码管理页签下,指定分支里写 $ref,代表需要构建哪个分支从ref参数中取
image.png
最后在构建触发器页签下,勾选WebHook触发(Generic Webhook Trigger)
image.png
在Post content parameters一栏中添加需要从Gitlab推送消息中读取的数据,使用JsonPath语法读取
image.png
image.png
image.png
image.png
在Token一栏中配置访问令牌,在6.2.2中Gitlab配置的远程地址上需要带上这里设置的令牌
image.png
在Optional filter页签下设置过滤规则,使用正则表达式,可以用前面Post content parameters里设置的读取数据进行组合过滤,多个参数用 “_” 号隔开来,例如

$ref_$object_kind

这里只做了分支过滤
image.png