基于gitlab的CI/CD实践

文章目录

  • 前言
  • 一、可以解决什么问题?
  • 二、gitlab-runner介绍
  • 三、安装前的准备
  • 四、安装gitlab(192.168.136.132)
  • 五、安装gitlab-runner(192.168.136.131)
  • 六、注册runner
  • 七、编写.gitlab-ci.yml
  • 八、构建基于springboot项目的cicd


前言

    gitlab,我相信大家一定不会陌生。现在大部分公司的代码托管已经从svn迁移到git上了。而gitlab又是使用最多的git项目托管平台。其实gitlab不仅仅只是用来做代码托管,他自带的CI/CD在持续集成,持续交付,持续部署方面也都有不俗的能力,甚至还可以作为docker镜像仓库来存储自己构建的镜像。下面我们就来一步一步的看下,怎么使用他。


这里我们以springboot项目为例

一、可以解决什么问题?

    在我们项目中经常会这样的问题,测试人员提出一个bug,开发人员修改bug,修改完了,要重新提交代码,编译打包,发布部署服务。这一整套下来,好多行命令,好几分钟过去了,即使你写个shell脚本,那也要手动去触发执行。如果系统少还好,如果你有几十个系统,那运维人员或者测试人员一整天别的事都不要干了。
    那能不能有一个套工具或软件,能够每次我push代码或merge代码的时候自动触发构建,编译,打包,部署呢?当然目前jekenis自动打包构建很流行,但是,本文我们只讨论gitlab,那我们就来看看,gitlab怎么去帮我们完成这一系列的事情的。

二、gitlab-runner介绍

    因为我们项目中代码托管的是在gitlab,正好gitlab-runner为我们提供了持续集成,的功能可以和gitlab无缝对接。
    gitlab-runner能为我们带来什么功能

  1. 持续集成(CI)。每次代码推送到gitlab仓库,都会触发代码构建,编译,测试,打包等操作
  2. 持续部署(CD)。每次构建完成,可以直接将新代码部署到服务器。
    我们来看下,gitlab的一个构建过程
    基于gitlab的CI/CD实践_第1张图片
    大致流程:
        ①、 开发者推送自己代码到gitlab苍鹭
        ②、 gitlab发现项目根目录下有.gitlab-ci.yml并且有指定的runner来运行,那就会触发构建工作(按照.gitlab-ci.yml里面的脚本进行构建)。可以直接打成jar包,然后把jar包传到远端服务器运行,也可以在runner里面构建docker镜像,然后把镜像推送到仓库,然后到远端服务器上pull镜像运行。
        ③、 使用shell脚本或者利用k8s来部署我们应用程序。

三、安装前的准备

好,下面我们就来一步一步的搭建我们的服务器。
首先准备最少三台虚拟机:(目前系统都为centos7 64位系统)
    1、192.168.136.132(gitlab服务器)
    2、192.168.136.131(gitlab-runner服务器)
    3、192.168.136.130(应用服务器)

这里gitlab与gitlab-runner最好分开
应用服务器是可以任意多少台
gitlab服务我们是有docker安装
gitlab-runner使用yum安装(也可以使用docker安装,但是最后运行时遇到远程免密码执行脚本问题,暂时还没有找到解决方案)

四、安装gitlab(192.168.136.132)

    我们这里使用docker安装gitlab,一键安装,非常快捷。

  1. 安装docker。docker安装就不在这里细说了,网上搜索有一大堆
  2. 安装gitlab(建议使用阿里云镜像加速,不然下载镜像会很慢,而且经常会失败)
安装Gitlab:
docker run \
-itd  \
-e TZ="Asia/Shanghai" \
-p 80:80 \
-p 9922:22 \
-p 4567:4567 \
-v /opt/gitlab/etc:/etc/gitlab  \
-v /opt/gitlab/log:/var/log/gitlab \
-v /opt/gitlab/opt:/var/opt/gitlab \
--restart always \
--privileged=true \
--name gitlab \
gitlab/gitlab-ce

1、gitlab访问使用80端口,把宿主机的80端口映射到gitlab容器中
2、避免与宿主机的22端口冲突,这里把宿主机的9922端口映射到容器中
3、4567端口是我们使用gitlab来作为我们的镜像仓库,如果你使用其他做经常仓库,那这个端口可以不要映射
4、三条映射目录是把容器里面的配置文件,gitlab存储数据,日志等信息映射到宿主机上。以防止容器重启或销毁后数据丢失
5、后面就是制定随docker一起启动,获取宿主的root权限,容器名称,镜像名称

好,这样gitlab就安装好了。下面我们来修改下配置文件。因为我们已经把gitlab的配置文件目录映射到宿主机了,那么就直接在宿主机上修改

vi /opt/gitlab/etc/gitlab.rb 
直接在这个文件里面第一行添加下面三行配置,按照你的实际ip端口号配置:

#gitlab页面访问地址
external_url 'http://192.168.136.132:80'  

#gitlab ssh访问ip
gitlab_rails['gitlab_ssh_host'] = '192.168.136.132' 

# gitlab ssh访问的端口
gitlab_rails['gitlab_shell_ssh_port'] = 9922 

# 使用gitlab作为镜像仓库的配置 (如果不使用,则无需配置)
registry_external_url 'http://192.168.136.132:4567' 

保存退出,然后重启容器

docker restart gitlab

我们在gitlab里面创建一个测试项目。test-cicd

建立一个springboot项目,后面使用该项目做测试。

五、安装gitlab-runner(192.168.136.131)

安装runner,我们这里只讨论使用本地安装方法(非docker)
1、安装git

yum -y install http://opensource.wandisco.com/centos/7/git/x86_64/wandisco-git-release-7-2.noarch.rpm
yum -y install git

gitlab-runnerLimit会自带git,那为什么要单独安装git呢?因为gitlab-runner自带的git版本太低,运行时会报错,所以这边先单独安装git,然后在安装runner

2、安装gitlab-runner

curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
yum -y install gitlab-runner

3、修改配置,让gitlab-runner以root运行
    修改文件

vi /etc/systemd/system/gitlab-runner.service

[Service]
StartLimitInterval=5
StartLimitBurst=10
ExecStart=/usr/bin/gitlab-runner "run" "--working-directory" "/root/gitlab-runner" "--config" "/etc/gitlab-runner/config.toml" "--service" "gitlab-runner" "--user" "root"

修改里面working-directory对应的配置,config指定的文件,还有最后的–user的值修改为 root

如果不修改成root用户运行,那后面你会遇到各种各样的权限问题,所以,为了省事,这边直接改成root用户运行。

4、重启runner

systemctl daemon-reload
systemctl restart gitlab-runner

好了,这样我们的gitlab-runner就已经安装好了,下面我们来注册runner到gitlab
5、安装docker

六、注册runner

    我们需要在gitlab-runner服务器上注册runner与gitlab关联。注册之前,我们必须要到gitlab项目中获取我们的token,在gitlab中 具体项目→设置→cicd,然后展开runner的按钮,在这可以看到,红框中就是我们的token
基于gitlab的CI/CD实践_第2张图片

sudo gitlab-runner register -n \
  --url "http://192.168.136.132/" \
  --registration-token "DheqPXLa1t4xhjJsUtQY" \
  --executor "shell" \
  --description "test-cicd-runner"

url:就是gitlab访问的地址
token:就是上面的token
executor:我们这里选择的是sehll,也可以用docker
description:就是runner的描述(可以后面修改)

好了,这样我们的runner就注册好了。这个时候刷新一下刚才的获取token页面,就会在下面看到注册好的runner
基于gitlab的CI/CD实践_第3张图片
前面有绿色的小圆球,就说明注册成功了,如果不是绿色的,那说明注册的有问题。
点击后面的编辑小图标,勾上这个选项,保存
在这里插入图片描述
让我们没有指定标签的任务也能运行,不然要指定标签跟他一致才行。

七、编写.gitlab-ci.yml

    gitlab cicd最核心的文件,就是.gitlab-ci.yml。我们所有的编译、打包、部署的脚本都要在这里去编写。该文件存放目录就是项目的根目录,所以我们在刚才test-cicd项目根目录下创建一个文件.gitlab-ci.yml,看下,我们写个最简单的脚本

stages:
  - runner test
job1:
  stage: runner test
  script:
    - echo 'start to runner'
	- echo 'success runner'

stages:就是这个runner需要执行的步骤有哪些,可以多个
job1:就是需要执行的第一步,名称可以任意取
  job1.stage就是对应上面的步骤。名称要完成相同
  job1.script:就是需要执行的shell脚本。这里最简单打印两行信息
这个时候,我们把该文件push到gitlab,就会自动触发runner工作。
在项目的cicd→流水线菜单中,我们看到有这么一条数据
在这里插入图片描述
这个就是刚才我们push代码生成一条构建记录
点进去看下
基于gitlab的CI/CD实践_第4张图片
下面的job1就是我们.gitlab-ci,yml文件里面配置的数据
点进去看下输出信息
基于gitlab的CI/CD实践_第5张图片
果然按照我们的要求,输出了两行信息。这样一个最简单的一个cicd就完成了。当然我们实际项目中步骤肯定不止这么简单。下面我们就来做一个基于springboot项目的cicd

八、构建基于springboot项目的cicd

  1. 首先在刚才test-cicd中建立一个springboot项目,然后在提供一个接口,就叫test(怎么创建springboot项目就不在这里述说了)。看下代码
@RestController
public class TestController {

	@Value("${test.id}")
	private String testId;
	
	@RequestMapping("/test")
	public Object test(String str) {
		return "你好:testid="+ testId + "str=" + str;
	}
}
  1. 然后修改.gitlab-ci.yml

我们要在gitlab-ci里面做几件事:
1、maven编译,打包
2、docker构建镜像
3、推送镜像到镜像仓库(这里使用gitlab做镜像仓库)
4、远端服务器拉取镜像然后启动容器

好,修改我们的.gitlab-ci.yml

stages:
  # 我们分两步,1-打包编译;2-docker构建和部署
  - maven build
  - docker build and deploy
maven_build:
  stage: maven build
  script:
    ## mvn package
    - docker run -i --rm -v /root/.m2:/root/.m2 -v "$(pwd)":/opt/maven -w /opt/maven maven:3.6.3-openjdk-11 mvn clean package -Dmaven.test.skip=true
  artifacts:
    paths:
      - start/target/*.jar    # 将maven构建成功的jar包作为构建产出导出,可在下一个stage的任务中使用

docker_build:
  stage: docker build and deploy
  before_script:
    ## 登录到docker仓库,可以直接push镜像上去
    - docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password $CI_REGISTRY_PASSWORD
  script:
    ## 获取当前时间做docker的镜像tag
    - time=$(date +%Y%m%d%H%M%S)

    ## 构建镜像
    - docker build -t $CI_REGISTRY_IMAGE:$time .

    ## 推送镜像到镜像仓库
    - docker push $CI_REGISTRY_IMAGE:$time 
    - echo $DEPLOY_HOSTS
    ## 部署应用
    - sh /root/start.sh $DEPLOY_HOSTS $CI_PROJECT_NAME $CI_REGISTRY_IMAGE $time $SSH_USER $SSH_PORT $SERVER_PORT test
  only:
    # 只有master分支才执行这一步(根据项目实际而定)
    - master

解读一下这个文件

stages:
  # 我们分两步,1-打包编译;2-docker构建和部署
  - maven build
  - docker build and deploy

表示我们这次构建分两个步骤

maven_build:
  stage: maven build
  script:
    ## mvn package
    - docker run -i --rm -v /root/.m2:/root/.m2 -v "$(pwd)":/opt/maven -w /opt/maven maven:3.6.3-openjdk-11 mvn clean package -Dmaven.test.skip=true
  artifacts:
    paths:
      - start/target/*.jar    # 将maven构建成功的jar包作为构建产出导出,可在下一个stage的任务中使用

这一步是maven编译步骤
1、这里我们没有在runner机上安装jdk环境和maven环境,直接使用了docker的maven来编译java文件。而且在/root/.m2文件下面放置setting.xml文件,然后使用我们自己的seting文件来编译打包。maven的本地仓库也在/root/.m2目目录下,这样只要第一次打包的时候从中央仓库下载jar包,后面就都可以使用本地仓库了。
2、最后输出了我们的jar文件作为下一步的输入文件

docker_build:
  stage: docker build and deploy
  before_script:
    ## 登录到docker仓库,可以直接push镜像上去
    - docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password $CI_REGISTRY_PASSWORD
  script:
    ## 获取当前时间做docker的镜像tag
    - time=$(date +%Y%m%d%H%M%S)

    ## 构建镜像
    - docker build -t $CI_REGISTRY_IMAGE:$time .

    ## 推送镜像到镜像仓库
    - docker push $CI_REGISTRY_IMAGE:$time 
    - echo $DEPLOY_HOSTS
    ## 部署应用
    - sh /root/start.sh $DEPLOY_HOSTS $CI_PROJECT_NAME $CI_REGISTRY_IMAGE $time $SSH_USER $SSH_PORT $SERVER_PORT test
  only:
    - master

这里的步骤是
1、登录镜像仓库
2、构建镜像
3、推送镜像到仓库
4、使用ssh部署应用到远端服务器
5、这里传递了spring.profiles环境为test,如果是线上服务,则传递prod。
6、根据上面一条配置,可以知道,我们这里使用了springboot的一个特性,一次打包,到处运行。只要在编译环境编译一次,上传到镜像仓库,然后上线时,只要选择已经打好的镜像,传入pring.profiles.active=prod就可以了。
这里有一个步骤 before_script。这是在执行script之前的一些前置操作,我这里是加了登录镜像仓库的操作
这里使用当前时间作为镜像的tag
最后的 only.master 表示 只有在master分支才会触发这一步操作

这里使用了一些变量参数,有一些是gitlab-ci内置的参数,有一些是在gitlab-runner里面自己设置的参数
内置参数详情请见 gitlab-ci内置变量
自定义参数配置方法 gitlab项目→设置→cicd 页面里面的变量按钮展开即可。
基于gitlab的CI/CD实践_第6张图片
再来看下 /root/start.sh这个文件,放在runner机子上的这个目录

#!/bin/bash
DEPLOY_HOSTS=$1		#需要发布的服务地址,使用逗号分隔,需要与runner配置好免密码登录
PROJECT_NAME=$2		#项目名称
IMAGE_NAME=$3		#镜像名称
IMAGE_TAG=$4		#镜像tag
SSH_USER=$5			#登录到服务器的用户名
SSH_PORT=$6			#登录到服务器的端口号
SERVER_PORT=$7		#项目启动的端口号
SPRING_PROFILES=$8	#spring.profiles.active环境

echo 'DEPLOY_HOSTS= ' $DEPLOY_HOSTS
echo 'PROJECT_NAME= ' $PROJECT_NAME
echo 'IMAGE_NAME= ' $IMAGE_NAME
echo 'IMAGE_TAG= ' $IMAGE_TAG
echo 'SSH_USER= ' $SSH_USER
echo 'SSH_PORT= ' $SSH_PORT
echo 'SERVER_PORT= ' $SERVER_PORT
echo 'SPRING_PROFILES= ' $SPRING_PROFILES

#把服务器地址按照逗号分隔成数组,然后遍历,一台一台的发布
for host in `echo "$DEPLOY_HOSTS" | sed 's/,/\n/g'`
do 
        echo 'start to deploy with host= ' $host
        ssh -p $SSH_PORT $SSH_USER@$host 'bash -s'< /root/deploy.sh $host $PROJECT_NAME $IMAGE_NAME $IMAGE_TAG $SERVER_PORT $SPRING_PROFILES
        echo $host ' deploy success'
done

这里很简单,就是遍历需要发布的服务器,然后分别发布。

再看下/root/deploy.sh这个文件,由于我们使用了在远端指定本地文件的方式,所以这个文件也放在runner机子上

#!/bin/bash
HOST=$1					#服务器地址
PROJECT_NAME=$2			#项目名称
IMAGE_NAME=$3			#镜像名称
IMAGE_TAG=$4			#镜像tag
SERVER_PORT=$5			#应用的端口号
SPRING_PROFILES=$6		#spring.profiles.active环境

echo 'host=' $HOST
echo 'PROJECT_NAME=' $PROJECT_NAME
echo 'IMAGE_NAME=' $IMAGE_NAME
echo 'IMAGE_TAG=' $IMAGE_TAG
echo 'SERVER_PORT=' $SERVER_PORT
echo 'SPRING_PROFILES=' $SPRING_PROFILES

## 拉取新的镜像
docker pull $IMAGE_NAME:$IMAGE_TAG
echo 'image ' $IMAGE_NAME:$IMAGE_TAG ' pull success'

# 检查该进程是否正在运行
num=`docker ps |grep -w $PROJECT_NAME|grep -v grep|wc -l`
if [ $num -gt 0 ]; then
    ##存在以前的进程,停止掉
        echo 'docker container ' $PROJECT_NAME ' progress is running'

        ## 停止掉该进程
        docker stop $PROJECT_NAME
        echo 'stop container ' $PROJECT_NAME ' success'
fi

## 检查该容器是否存在
num=`docker ps -a |grep -w $PROJECT_NAME|grep -v grep|wc -l`
if [ $num -gt 0 ]; then
    ##容器存在,则删掉该容器
        echo 'docker container ' $PROJECT_NAME ' container is exists'

        ## 删掉该容器
        docker rm $PROJECT_NAME
        echo 'remove container ' $PROJECT_NAME ' success'
fi

##启动容器
docker run -d -e "SPRING_PROFILES_ACTIVE=$SPRING_PROFILES" --name $PROJECT_NAME -p $SERVER_PORT:$SERVER_PORT -v /Data/logs:/Data/logs $IMAGE_NAME:$IMAGE_TAG
echo 'run container ' $PROJECT_NAME ' success!!!!'

这个就是最终到远端服务器启动容器的脚本
里面包含了:
1、拉取新的镜像
2、判断是否有容器在运行,如果有,则停掉容器
3、判断是否有未启动的容器存在,如果有,则删除掉
4、启动新的容器
这里还差一步,检查容器是否启动成功,后面会把相应的脚本加上。

好了,配置都完成了。我们提交代码,push到gitlab,自动触发构建,等待构建完成。如果有报错请求,需要根据报错日志进行分析解决。
打开gitlab流水线,看到有新的任务在运行,点进去看,里面分两个步骤
基于gitlab的CI/CD实践_第7张图片
慢慢等待编译,打包,部署。
成功后,打开刚才的接口
http://192.168.136.130:8080/test/test,看看接口是否成功。

注意一点:
所有的应该服务器都需要安装docker环境,因为我们的应用服务器都是在docker内运行的。

最后还有一个文件,Dockerfile文件

FROM openjdk:11-oraclelinux7
 
ADD start/target/test-cicd.jar /test-cicd.jar
 
ENTRYPOINT ["java", "-Duser.timezone=GMT+0800", "-jar", "/test-cicd.jar"]

这个很简单,就是使用openjdk11来运行编译好的jar包。

如果你使用k8s去部署管理容器的话,那就可以更方便的管理我们容器的生命周期了。

好了,我们的基于gitlab的cicd构建已经成功,看看是不是很简单,跟以前的jekins比较,你觉得哪个使用更方便呢?

欢迎大家留言讨论!!

你可能感兴趣的:(dev-ops,java,spring,boot,docker,devops,spring)