使用gitlab和gitlab-runner实现DevOps

持续集成和部署相关概念

名称 说明
DevOps DevOps 是一种方法论,是一组过程、方法与系统的统称,用于促进应用开发、应用运维和质量保障(QA)部门之间的沟通、协作与整合。
AD 敏捷开发(Agile Development)
CI 持续集成(Continuous Integration),例如免费版Gitlab+Gitlab CI、Gitlab+Jenkins
CD 持续交付或部署(Continuous Delivery or Deployment)

DevOps


持续集成和部署完整流程图

devops



1 安装gitlab

GitLab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的web服务,具体使用详情查看gitlab官网。可以直接命令行安装或在docker上安装gitlab。

1.1 直接命令安装gitlab

使用vagrant创建一个新的虚拟机,虚拟机自动安装了docker,gitlab,配置文件Vagrantfile内容如下:

Vagrant.require_version ">= 1.6.0"

opts = {
      :name => "gitlab",
      :mem => "4096",
      :cpu => "2",
      :private_ip => "192.168.6.100",
      :script_path => "./setup.sh"
}

Vagrant.configure(2) do |config|
  config.vm.box = "centos/7"
  config.vm.define opts[:name] do |demo|
    demo.vm.hostname = opts[:name]

    demo.vm.provider "virtualbox" do |v|
      v.customize ["modifyvm", :id, "--memory", opts[:mem]]
      v.customize ["modifyvm", :id, "--cpus", opts[:cpu]]
    end

    demo.vm.network "private_network", ip: opts[:private_ip]

    demo.vm.provision "shell", path: opts[:script_path]

  end
end


Vagrantfile调用的外部脚本文件setup.sh内容如下: 如果不是使用虚拟机,直接执行下面脚本安装gitlab和docker。

#/bin/sh

# 设置时区
/bin/cp -rf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

# -------------------------安装docker-----------------------------
# 安装一些工具
yum install -y git vim gcc glibc-static telnet bridge-utils net-tools

# 安装docker
yum -y install docker

# 把用户vagrant添加docker用户组
groupadd docker
gpasswd -a vagrant docker

# 设置开机启动docker和启动docker
systemctl enable docker
systemctl start docker

# 查看docker版本信息
docker version


# ---------------------------安装gitlab---------------------------
# 更新和安装一些工具
yum install -y upgrade
yum install -y net-tools curl policycoreutils java-1.8.0-openjdk.x86_64

# 开启sshd服务
yum install -y openssh-server openssh-clients
systemctl enable sshd
systemctl start sshd

# 开启邮件服务
yum install -y postfix
systemctl  enable postfix
systemctl start postfix

# 打开防火墙
firewall-cmd --permanent --add-service=http
systemctl reload firewalld

# 安装gitlab
curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash
yum install -y gitlab-ce

# 启动gitlab
gitlab-ctl reconfigure

# 查看启动状态
gitlab-ctl status


安装完gitlab后,使用命令vagrant ssh gitlab进入虚拟机,修改gitlab配置,其中域名配置为自己使用的域名。

vim /etc/gitlab/gitlab.rb

# 找到字段external_url,修改为自己的域名

# 重启gitlab
gitlab-ctl reconfigure


1.2 在docker上安装gitlab

如果不想直接安装gitlab,可以使用docker安装gitlab,使用docker安装时需要添加port,hostname, volume参数,这些参数根据自己情况设定。

# 拉取gitlab镜像
docker pull gitlab/gitlab-ce

# 启动gitlab容器
docker run --detach \
--hostname demo.gitlab.com \
--publish 8443:443 --publish 8080:80 --publish 2222:22 \
--name gitlab \
--restart always \
--volume /usr/local/gitlab/config:/etc/gitlab \
--volume  /usr/local/gitlab/logs:/var/log/gitlab \
--volume  /data/gitlab:/var/opt/gitlab \
gitlab/gitlab-ce


1.3 在本地为gitlab添加一个测试域名

如果是在云上部署gitlab,又有实名认证过的域名,直接用域名解析gitlab安装的云主机ip即可。

这里在本地使用测试域名 demo.gitlab.com,首先查看虚拟机ip地址:

ip addr

然后在宿主机的/etc/hosts文件上添加一个域名和虚拟机ip地址映射(例如:192.168.6.100 demo.gitlab.com),然后在宿主机上测试是否能够ping通:

ping demo.gitlab.com

能够ping通说明域名解析成功。然后打开在浏览器输入地址 http://demo.gitlab.com 访问gitlab,第一次访问需要修改root用户密码,修改密码后重新登录,输入管理员账号root和密码登录。


1.4 测试一个简单项目helloworld

进入gitlab主界面,如果需要中文,可以在个人settings–>Preferred language选择语言,然后刷新页面即可。

首先创建一个组demo,填写组信息,然后在组里创建一个项目helloworld,填写项目信息,创建完后就可以获得项目地址,复制项目地址,然后在终端克隆helloworld项目到本地。

git clone http://demo.gitlab.com/demo/helloworld.git

然后进入本地项目helloworld文件夹,添加个README.md,随便填写内容,保存后提交到gitlab,在gitlab上helloworld项目下是否有看到README.md文件,有说明gitlab运行正常。



2 安装gitlab runner

gitLab runner 是用来运行定制的自定义任务(jobs),并把每个jobs执行结果返回给gitLab,配合gitLab内置的持续集(CI)和持续部署(CD)协调完成任务。gitlab runner是天然支持分布式的,在任意地方都可以安装,前提条件是能够和gitlab网络连通即可。可以直接命令行安装和在docker上安装两张方式。具体使用详情看gitlab runner官网

2.1 命令行方式安装gitlab runner

使用vagrant创建一个新的虚拟机,虚拟机自动安装了docker,gitlab runner,注意gitlab和gitlab runner不要直接安装在同一个虚拟机上。配置文件Vagrantfile内容如下:

Vagrant.require_version ">= 1.6.0"

opts = {
      :name => "gitlab-runner",
      :mem => "2048",
      :cpu => "2",
      :private_ip => "192.168.6.200",
      :script_path => "./setup.sh"
}

Vagrant.configure(2) do |config|
  config.vm.box = "centos/7"
  config.vm.define opts[:name] do |demo|
    demo.vm.hostname = opts[:name]

    demo.vm.provider "virtualbox" do |v|
      v.customize ["modifyvm", :id, "--memory", opts[:mem]]
      v.customize ["modifyvm", :id, "--cpus", opts[:cpu]]
    end

    demo.vm.network "private_network", ip: opts[:private_ip]

    demo.vm.provision "shell", path: opts[:script_path]

  end
end


Vagrantfile调用的外部脚本文件setup.sh内容如下: 如果不是使用虚拟机,直接执行下面脚本安装gitlab runner和docker。

#/bin/sh

# 设置时区
/bin/cp -rf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

# -------------------------安装docker-----------------------------
# 安装一些工具
yum install -y git vim gcc glibc-static telnet bridge-utils net-tools

# 安装docker
yum -y install docker

# 把用户vagrant添加docker用户组
groupadd docker
gpasswd -a vagrant docker

# 设置开机启动docker和启动docker
systemctl enable docker
systemctl start docker

# 查看docker版本信息
docker version


# ---------------------------安装gitlab runner---------------------------
# 更新和安装一些工具
yum install -y upgrade
yum install -y net-tools curl policycoreutils

# 开启sshd服务
yum install -y openssh-server openssh-clients
systemctl enable sshd
systemctl start sshd

# 打开防火墙
firewall-cmd --permanent --add-service=http
systemctl reload firewalld

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

# 添加gitlab-runner用户到docker组,使得gitlab-runner可以直接操作docker
usermod -aG docker gitlab-runner

# 重启docker和gitlab runner服务
systemctl restart docker
gitlab-ci-multi-runner restart

# 查看gitlab runner运行状态
gitlab-ci-multi-runner status


2.2 在docker上安装gitlab runner

valume参数根据自己情况设置。

# 拉取gitlab-runner镜像
docker pull gitlab/gitlab-runner

# 启动gitlab-runner容器
sudo docker run -d /
--name gitlab-runner /
--restart always /
-v /usr/loacal/gitlab-runner/config:/etc/gitlab-runner /
-v /usr/loacal/gitlab-runner/run/docker.sock:/var/run/docker.sock /
gitlab/gitlab-runner


安装完gitlab runner之后,需要把runner注册到gitlab服务中,让gitlab知道有这个runner存在。

因为当前使用本地测试域名,需要把测试域名和gitlab服务的ip地址添加到gitlab-runner虚拟机的/etc/hosts里。

vi /etc/hosts
    # 添加域名解析
    192.168.6.100    demo.gitlab.com

# 测试是否能够ping通
ping demo.gitlab.com


2.3 注册runner

如果在docker上安装的gitlab runner,需要进入docker操作

docker exec -it gitlab-runner bash


# 执行注册runner命令,根据提示填写信息
gitlab-ci-multi-runner register

    # 填写gitlab域名
    示例:http://demo.gitlab.com

    # 填写token值,获取token值方式,例如你要为helloworld这个项目注册一个runner,打开浏览器进入helloworld项目,点击菜单setting --> CI/CD --> Runners --> Setup a specific Runner manually 的第三个就是helloworld项目token值。
    示例:GidtNjipAo6Q9kYsHicy

    # 填写描述
    示例:test helloworld runner

    # 填写tags,到时候需要指定tag去构建
    示例:test,demo

    # 填写runner执行器,ocker-ssh, ssh, docker+machine, kubernetes, docker, parallels, shell, virtualbox, docker-ssh+machine
    示例:shell


# 查看runner列表,查看新注册的runner是否成功
gitlab-ci-multi-runner list

# 查看helloworld项目是否有runner注册成功,刷新一下页面点击菜单setting --> CI/CD --> Runners,展开查看Runners activated for this project下面是否有一个绿色runner。



3 一个简单的持续集成示例

以gitlab上的helloworld项目为例,当前默认是master分支上,新建一个runner配置文件.gitlab-ci.yml,具体语法参考官方文档。配置文件内容如下:

# 定义stages
stages:
    - build
    - test
    - deploy
    - release

# 在执行job之前可以设定当前工作目录,复制项目文件到工作目录等操作。
# before_script:


# 定义job
# (1)指定job的stage; 
# (2)指定runner的tag;
# (3)自定义运行的脚本,这个脚本执行器是runner注册的时候指定的,例如注册时使用shell,script也是使用shell脚本命令。
# (4) 可以指定哪些动作执行当前job,例如only tags或except tags 
job1:
    stage: build
    tags:
        - demo
    script:
        - echo "I am job1"
        - echo "I am in build stage"
    except:
        - tags

# 定义job
job2:
    stage: test
    tags:
        - demo
    script:
        - echo "I am job2"
        - echo "I am in test stage"
    except:
        - tags

# 定义job
job3:
    stage: deploy
    tags:
        - demo
    script:
        - echo "I am job3"
        - echo "I am in deploy stage"
    except:
        - tags

# 定义job
job4:
    stage: release
    tags:
        - demo
    script:
        - echo "I am job4"
        - echo "I am in release stage"
    only:
        - tags


gitlab runner根据配置文件.gitlab-ci.yml去执行各个job,执行结果如下图所示,如果第一个job执行失败,不会执行后面的job。

Pipline执行结果


如果只发布稳定版本,只会执行job4

打tags



4 一些实际使用中gitlab的设置

4.1 禁止直接修改master分支

在实际项目中需要设置保护master分支,不能让然任何人直接push修改内容到master分支,必须通过合并方式,设置保护master分支,settings –> repository –> Protected Branches

保护master分支


4.2 设置允许合并请求条件

合并请求也可以设置成有条件的合并,例如设置pipeline成功才允许发送merge请求,设置merge请求条件,settings –> general –> Merge request

设置merge请求条件


4.2 实时显示徽章

在README上显示徽章,徽章包括pipleline状态、测试覆盖率、当前版本、编译状态、许可证license等

settings –> CI/CD –> General pipelines

选项 说明
Pipeline status pipleline状态
Coverage report 找到Test coverage parsing,根据对应语言填写正则表达式

也可以自定义设定Badaes,settings –> Badges



5 一个完整的golang持续集成和部署示例

5.1 介绍运行环境和项目结构

运行环境:一台主机,和2个centos7.3虚拟机

虚拟机名称 ip地址 安装服务
gitlab 192.168.6.100 gitlab服务、docker、docker私有仓库服务、本地DNS服务器,注:分配内存要4G以上
gitlab-ci 192.168.6.200 gitlib-runner服务、docker、自己制作的tool-go镜像

下面是一个完整的目录结构。

.
├── fileServer.go
├── fileServer_test.go
├── .gitlab-ci.yml
├── Makefile
├── README.md
└── scripts
    ├── coverage.sh
    ├── deploy_to_local
    │   ├── deploy_to_local.sh
    │   └── Dockerfile
    ├── deploy_to_remote
    │   ├── automationDeployment.sh
    │   ├── deploy_to_remote.sh
    │   ├── Dockerfile
    │   └── run_application.sh
    └── release
        ├── Dockerfile
        └── release.sh


与gitlab runner相关的目录介绍:

名称 说明
.gitlab-ci.yml gitlab runner运行规则配置文件,语法参考官方文档
Makefile 为了使.gitlab-ci.yml配置文档的结构清晰简单,把各个job的scripts入口存放到Makefile里。
scripts 存放Makefile里使用到的脚本文件,scripts里主要包含了自动部署到本地、自动部署到远程服务器、发布版本三大类


5.2 搭建一个本地DNS服务器

为了使docker的容器也能使用测试域名 demo.gitlab.com,在gitlab虚拟机的docker搭建一个DNS服务器。(当然也可以在其他机器上搭建一个DNS服务)

# 启动一个DNS服务器
docker run -d -p 53:53/udp --cap-add=NET_ADMIN --name dns-server --restart=always andyshinn/dnsmasq

# 进入容器
docker exec -it dns-server sh

# 配置DNS服务
vi /etc/resolv.dnsmasq
    # 添加解析规则:
    nameserver 114.114.114.114
    nameserver 8.8.8.8

# 配置本地解析,其中 192.168.6.100是gitlab虚拟机的ip
vi /etc/dnsmasqhosts
    # 添加解析规则:
    192.168.6.100 demo.gitlab.com

# 修改DNS配置文件,使用自定义配置文件
vi /etc/dnsmasq.conf
    # 修改内容:
    resolv-file=/etc/resolv.dnsmasq
    addn-hosts=/etc/dnsmasqhosts

# 重启DNS服务器
docker restart dns-server

# 回到gitlab-runner虚拟机,修改域名服务器解析地址,所有其他机器的域名解析指向DNS服务地址,都可以访问测试域名demo.gitlab.com。
vi /etc/resolv.conf
    # 修改nameserver时,必须把其他nameserver去掉,192.168.6.100是DNS服务器所在机器的ip地址,注:修改的配置在系统重启后会自动复原,每次都要手动加上,可以写脚本系统启动后自动设置。
    nameserver 192.168.6.100


5.3 搭建一个私人docker仓库

一般企业内部打包的镜像是不公开的,把镜像存放到私有仓库里,需要到找一台或多台主机搭建私有仓库,这里把私有仓库搭建在gitlab服务器上。在docker上启动一个管理镜像的容器:

docker run -d -v /opt/registry:/var/lib/registry -p 5000:5000 –restart=always –name=my-registry registry


测试私有仓库是否可用:

# 配置gitlab-runner所在服务器的docker,告诉docker私有仓库地址是可信赖的,注意:这里填写的是ip,如果填写域名,需要https证书。
vi /etc/docker/daemon.json
    # 添加内容:{"insecure-registries":["192.168.6.100:5000"]}

# 重启docker服务
systemctl daemon-reload
systemctl restart docker

# 从官网拉一个镜像下来,看能不能push到私有仓库
docker pull busybox

docker tag busybox 192.168.6.100:5000/busybox
docker push 192.168.6.100:5000/busybox

# 从私有仓库拉取镜像
docker pull 192.168.6.100:5000/busybox


5.4 制作自定义的tool-go镜像

由于是在docker容器里检测项目,所以要自己去制作一个新镜像,并把推送到镜像仓库,最后在gitlab-runner的服务器上拉取该镜像。

首先翻墙下载golint包,如果不能翻墙,去github下载同名的包,下载后放到$GOPATH/src,然后改路径golang.org/x/lint/golint。

go get golang.org/x/lint/golint

在宿主机上的GOPATH/src/tool-go新建Dockerfile文件,文件内容如下:

FROM golang:1.9.2
MAINTAINER vison "vison@126.com"
USER root

# 安装golang代码风格检查的工具 golint
ENV GOPATH /go
ENV PATH ${GOPATH}/bin:$PATH
RUN mkdir -p /go/src/golang.org/x/lint/golint
COPY ../golang.org/x/lint/golint /go/src/golang.org/x/lint/golint
RUN go install golang.org/x/lint/golint

# 安装 docker,由于需要在容器里面使用宿主的docker命令,这里就需要安装一个docker的可执行文件,然后在启动容器的时候,将宿主的 /var/run/docker.sock 文件挂载到容器内的同样位置。
RUN curl -O https://get.docker.com/builds/Linux/x86_64/docker-latest.tgz \
    && tar zxvf docker-latest.tgz \
    && cp docker/docker /usr/local/bin/ \
    && rm -rf docker docker-latest.tgz

# 安装 expect,expect工具可以实现远程服务器端部署应用
RUN apt-get update
RUN apt-get -y install tcl tk expect

然后打包镜像

docker build -t tool-go:1.9.2 .


5.5 golang项目的检测项介绍

(1) 包列表

正如在官方文档中所描述的那样,go项目是包的集合。下面介绍的大多数工具都将使用这些包,因此我们需要的第一个命令是列出包的方法。我们可以用go list子命令来完成

go list ./…

如果我们要避免将我们的工具应用于外部资源,并将其限制在我们的代码中。 那么我们需要去除vendor 目录,命令如下:

go list ./… | grep -v /vendor/


(2) 单元测试

这些是您可以在代码中运行的最常见的测试。每个.go文件需要一个能支持单元测试的_test.go文件。可以使用以下命令运行所有包的测试:

go test -short $(go list ./… | grep -v /vendor/)


(3) 数据竞争

这通常是一个难以逃避解决的问题,go工具默认具有(但只能在linux / amd64、freebsd / amd64、darwin / amd64和windows / amd64上使用)

go test -race -short $(go list . /…| grep - v /vendor/)


(4) 内存检错

Clang有一个很好的读未初始化的内存检测器,称为MemorySanitizer。go测试工具能很好地与Clang模块进行交互(只要在linux / amd64主机上,并使用最新版本的Clang / LLVM(> = 3.8.0)。运行命令如下:

go test -msan -short $(go list . /…| grep - v /vendor/)


(5) 代码覆盖

这是评估代码的质量的必备工具,并能显示哪部分代码进行了单元测试,哪部分没有。要计算代码覆盖率,需要运行以下脚本:

PKG_LIST=$(go list ./... | grep -v /vendor/)
for package in ${PKG_LIST}; do
    go test -covermode=count -coverprofile "cover/${package##*/}.cov" "$package" ;
done
tail -q -n +2 cover/*.cov >> cover/coverage.cov
go tool cover -func=cover/coverage.cov

如果我们想要获得HTML格式的覆盖率报告,我们需要添加以下命令:

go tool cover -html=cover/coverage.cov -o coverage.html


(6) golint

这是可选的检查代码风格、错误工具,它有助于在项目上保持一致的代码风格。

golint -set_exit_status $(go list ./… | grep -v /vendor/)

注意-set_exit_status选项。 默认情况下,golint仅输出样式问题,并带有返回值(带有0返回码),所以CI不认为是出错。 如果指定了-set_exit_status,则在遇到任何样式问题时,golint的返回码将不为0。


(7) 编译

最后一旦代码经过了完全测试,我们要对代码进行编译,从而构建可以执行的二进制文件。

go build


5.6 新建一个项目runner

在gitlab-runner服务器上,创建一个对应项目file-server新runner:

sudo gitlab-ci-multi-runner register
# Running in system-mode.

#Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
http://demo.gitlab.com
#Please enter the gitlab-ci token for this runner:
RKFj4GFZsBukNqrU4rCC
#Please enter the gitlab-ci description for this runner:

#Please enter the gitlab-ci tags for this runner (comma separated):
demo
#Registering runner... succeeded                     runner=RKFj4GFZ
#Please enter the executor: docker-ssh, virtualbox, docker+machine, #docker-ssh+machine, docker, shell, ssh, kubernetes, parallels:
docker
# Please enter the default Docker image (e.g. ruby:2.1):
# 新制作的镜像
tool-go:1.9.2
#Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!


这是创建runner的基本信息,还需要补充相关docker配置相关信息。

# 打开runner配置文件,找到刚刚创建的runner
vim /etc/gitlab-runner/config.toml

# 完整的配置如下:
[[runners]]
  name = "file"
  url = "http://demo.gitlab.com"
  token = "4e01f882e18534fc4f9a020de5fe4e"
  executor = "docker"
  [runners.docker]
    tls_verify = false
    image = "tool-go:1.9.2"
    privileged = false
    disable_cache = false
    shm_size = 0

    # 为了在容器中可以执行宿主机的docker命令
    volumes = ["/var/run/docker.sock:/var/run/docker.sock"]
    # 当指定的镜像不存在的话,则通过docker pull拉取
    pull_policy = "if-not-present"

    # 如果gitlab和gitlab-runner是安装在同一台主机的docker上,可以设置下面参数
    # 给gitlab添加个host映射,映射到127.0.0.1
    #extra_hosts = ["gitlab.chain.cn:127.0.0.1"]
    # 使容器的网络与宿主机一致,只有这样才能通过127.0.0.1访问到gitlab。
    #network_mode = "host"

  [runners.cache]

gitlab-runner在执行的时候,会根据上面的配置启动一个容器,即配置中的tool-gos:1.9.2,其中所有的启动参数都会在[runners.docker]节点下配置好,包括挂载、网络等。容器启动成功之后,会使用这个容器去gitlab上pull代码,然后根据自己定义的规则进行检验,全部检测成功之后便是部署了。


5.7 定义runner规则

在gitlab项目根目录创建.gitlab-ci.yml文件,填写runner规则,具体语法课参考官方文档

如果检测项目多而复杂的话,把所有规则都写到配置文件.gitlab-ci.yml,会使得.gitlab-ci.yml很复杂,通常把持续集成环境中使用的所有工具,全部打包在Makefile中,然后统一的方式调用,而且Makefile也可以调用*.sh脚本文件,可以分级调用,使得整体配置文件结构清晰简洁。

在golang代码基础上新建了.gitlab-ci.yml、Makefile文件和一个存放脚本文件的scripts文件夹。完整的项目代码地址 https://git.zhuyasen.com/devops-demo/file-server


5.7 遇到的问题

(1) job一直在pending状态

提交代码后,job突然一直pending状态,提示This job has not started yet,可能gitlab-runner出现了异常,重启一下gitlab-runner就正常了。

gitlab-ci-multi-runner restart


(2) 无权限执行脚本

.sh文件没有课执行权限,造成make报错make: execvp: *.sh: Permission denied,如果从其他git仓库导入到gitlab后,.sh文件缺少可执行权限。

修改项目中的.sh文件权限:

for file in $(find ./ -name *.sh | grep -v /vendor/); do chmod +x $file;done



专题「工具」的其它文章 »