Golang基于Gitlab CI/CD部署方案

本文为转载,原文:Golang基于Gitlab CI/CD部署方案

docker

概述

持续集成 (Continuous integration)是一种软件开发实践,即团队开发成员经常集成它们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

持续部署(continuous deployment)是通过自动化的构建、测试和部署循环来快速交付高质量的产品。某种程度上代表了一个开发团队工程化的程度,毕竟快速运转的互联网公司人力成本会高于机器,投资机器优化开发流程化相对也提高了人的效率,让 engineering productivity 最大化。

1. 环境准备

本次试验是基于Centos 7.3, docker 17.03.2-ce环境下的。docker的安装这里就不赘述了,提供了官方链接吧: Get Docker CE for CentOS

1.1. docker启动gitlab

启动命令如下:

docker run --detach \
--hostname gitlab.chain.cn \
--publish 8443:443 --publish 8080:80 --publish 2222:22 \
--name gitlab \
--restart always \
--volume /Users/zhangzc/gitlab/config:/etc/gitlab \
--volume /Users/zhangzc/gitlab/logs:/var/log/gitlab \
--volume /Users/zhangzc/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce

port,hostname, volume根据具体情况具体设置

1.2. docker启动gitlab-runner

启动命令如下:

sudo docker run -d /
--name gitlab-runner /
--restart always /
-v /Users/zhangzc/gitlab-runner/config:/etc/gitlab-runner /
-v /Users/zhangzc/gitlab-runner/run/docker.sock:/var/run/docker.sock /
gitlab/gitlab-runner:latest

volume根据具体情况具体设置

1.3. 用于集成部署的镜像制作

我们的集成和部署都需要放在一个容器里面进行,所以,需要制作一个镜像并安装一些必要的工具,用于集成和部署相关操作。目前我们的项目都是基于golang 1.9.2的,这里也就基于golang:1.9.2的镜像制定一个特定的镜像。

Dockerfile内容如下:

# Base image: https://hub.docker.com/_/golang/
FROM golang:1.9.2
USER root
# Install golint
ENV GOPATH /go
ENV PATH ${GOPATH}/bin:$PATH
RUN mkdir -p /go/src/golang.org/x
RUN mkdir -p /go/src/github.com/golang
COPY source/golang.org /go/src/golang.org/x/
COPY source/github.com /go/src/github.com/golang/
RUN go install github.com/golang/lint/golint

# install docker
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

# install expect
RUN apt-get update
RUN apt-get -y install tcl tk expect

其中golint是用于golang代码风格检查的工具。
docker是由于需要在容器里面使用宿主的docker命令,这里就需要安装一个docker的可执行文件,然后在启动容器的时候,将宿主的 /var/run/docker.sock 文件挂载到容器内的同样位置。
expect是用于ssh自动登录远程服务器的工具,这里安装改工具是为了可以实现远程服务器端部署应用

另外,在安装golint的时候,是需要去golang.org下载源码的,由于墙的关系,go get命令是执行不了的。为了处理这个问题,首先通过其他渠道先下载好相关源码,放到指定的路径下,然后copy到镜像里,并执行安装即可。

下面有段脚本是用于生成镜像的:

#!/bin/bash

echo "提取构建镜像时需要的文件"
source_path="source"
mkdir -p $source_path/golang.org
mkdir -p $source_path/github.com
cp -rf $GOPATH/src/golang.org/x/lint $source_path/golang.org/
cp -rf $GOPATH/src/golang.org/x/tools $source_path/golang.org/
cp -rf $GOPATH/src/github.com/golang/lint $source_path/github.com

echo "构建镜像"
docker build -t go-tools:1.9.2 .

echo "删除构建镜像时需要的文件"
rm -rf $source_path

生成镜像后,推送到镜像仓库,并在gitlab-runner的服务器上拉取该镜像

本次试验的gitlab和gitlab-runner是运行在同一服务器的docker下的。

2. runner注册及配置

2.1. 注册

环境准备好后,在服务器上执行以下命令,注册runner:

docker exec -it gitlab-runner gitlab-ci-multi-runner register

按照提示输入相关信息

Please enter the gitlab-ci coordinator URL:
# gitlab的url, 如:https://gitlab.chain.cn/
Please enter the gitlab-ci token for this runner:
# gitlab->你的项目->settings -> CI/CD ->Runners settings
Please enter the gitlab-ci description for this runner:
# 示例:demo-test
Please enter the gitlab-ci tags for this runner (comma separated):
# 示例:demo
Whether to run untagged builds [true/false]:
# true
Please enter the executor: docker, parallels, shell, kubernetes, docker-ssh, ssh, virtualbox, docker+machine, docker-ssh+machine:
# docker
Please enter the default Docker image (e.g. ruby:2.1):
# go-tools:1.9.2 (之前自己制作的镜像)
token

成功后,可以看到gitlab->你的项目->settings -> CI/CD ->Runners settings 页面下面有以下内容:

runner注册成功

2.2. 配置

注册成功之后,还需要在原有的配置上做一些特定的配置,如下:

[[runners]]
  name = "demo-test"
  url = "https://gitlab.chain.cn/"
  token = "c771fc5feb1734a9d4df4c8108cd4e"
  executor = "docker"
  [runners.docker]
    tls_verify = false
    image = "go-tools:1.9.2"
    privileged = false
    disable_cache = false
    volumes = ["/var/run/docker.sock:/var/run/docker.sock"]
    extra_hosts = ["gitlab.chain.cn:127.0.0.1"]
    network_mode = "host"
    pull_policy = "if-not-present"
    shm_size = 0
  [runners.cache]

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

volumes: 是为了在容器中可以执行宿主机的docker命令。

extra_hosts: 给gitlab添加个host映射,映射到127.0.0.1

network_mode: 令容器的网络与宿主机一致,只有这样才能通过127.0.0.1访问到gitlab。

pull_policy: 当指定的镜像不存在的话,则通过docker pull拉取

3. 定义规则

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

3.1. go集成命令

下面介绍几个golang常见的集成命令

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

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

go list ./... | grep -v /vendor/
  1. 单元测试
    这些是您可以在代码中运行的最常见的测试。每个.go文件需要一个能支持单元测试的_test.go文件。可以使用以下命令运行所有包的测试:
go test -short $(go list ./... | grep -v /vendor/)
  1. 数据竞争
    这通常是一个难以逃避解决的问题,go工具默认具有(但只能在linux / amd64、freebsd / amd64、darwin / amd64和windows / amd64上使用)
go test -race -short $(go list . /…| grep - v /vendor/)
  1. 代码覆盖
    这是评估代码的质量的必备工具,并能显示哪部分代码进行了单元测试,哪部分没有。
    要计算代码覆盖率,需要运行以下脚本:
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
  1. 构建
    最后一旦代码经过了完全测试,我们要对代码进行编译,从而构建可以执行的二进制文件。
go build .
  1. linter
    这是我们在代码中使用的第一个工具:linter。它的作用是检查代码风格/错误。这听起来像是一个可选的工具,或者至少是一个“不错”的工具,但它确实有助于在项目上保持一致的代码风格。
    linter并不是go本身的一部分,所以如果要使用,你需要手动安装它(之前的go-tools镜像我们已经安装过了)。
    使用方法相当简单:只需在代码包上运行它(也可以指向. go文件):
$ golint -set_exit_status $(go list ./... | grep -v /vendor/)

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

3.2. Makefile

如果我们不想在.gitlab-ci.yml文件中写的太复杂,那么我们可以把持续集成环境中使用的所有工具,全部打包在Makefile中,并用统一的方式调用它们。

这样的话,.gitlab-ci.yml文件就会更加简洁了。当然了,Makefile同样也可以调用*.sh脚本文件

3.3. 配置示例

3.3.1. .gitlab-ci.yml

image: go-tools:1.9.2

stages: 
  - build
  - test
  - deploy

before_script:
  - mkdir -p /go/src/gitlab.chain.cn/ZhangZhongcheng /go/src/_/builds
  - cp -r $CI_PROJECT_DIR /go/src/gitlab.chain.cn/ZhangZhongcheng/demo
  - ln -s /go/src/gitlab.chain.cn/ZhangZhongcheng /go/src/_/builds/ZhangZhongcheng  
  - cd /go/src/_/builds/ZhangZhongcheng/demo

unit_tests:
  stage: test
  script: 
    - make test
  tags: 
    - demo

race_detector:
  stage: test
  script:
    - make race
  tags: 
    - demo

code_coverage:
  stage: test
  script:
    - make coverage
  tags: 
    - demo

code_coverage_report:
  stage: test
  script:
    - make coverhtml
  only:
  - master
  tags: 
    - demo

lint_code:
  stage: test
  script:
    - make lint    

build:
  stage: build
  script:
    - pwd
    - go build .
  tags:
    - demo

build_image:
  stage: deploy
  script:
    - make build_image
  tags:
    - demo    

3.3.2. Makefile

PROJECT_NAME := "demo"
PKG := "gitlab.chain.cn/ZhangZhongcheng/$(PROJECT_NAME)"
PKG_LIST := $(shell go list ./... | grep -v /vendor/)
GO_FILES := $(shell find . -name '*.go' | grep -v /vendor/ | grep -v _test.go)

test: ## Run unittests
    @go test -v ${PKG_LIST}

lint: ## Lint the files
    @golint ${PKG_LIST} 

race: ## Run data race detector
    @go test -race -short ${PKG_LIST}

coverage: ## Generate global code coverage report
    ./scripts/coverage.sh;
    
coverhtml: ## Generate global code coverage report in HTML
    ./scripts/coverage.sh html;

build_image:
    ./scripts/buildDockerImage.sh   

3.3.3. coverage.sh

#!/bin/bash
#
# Code coverage generation

COVERAGE_DIR="${COVERAGE_DIR:-coverage}"
PKG_LIST=$(go list ./... | grep -v /vendor/)

# Create the coverage files directory
mkdir -p "$COVERAGE_DIR";

# Create a coverage file for each package
for package in ${PKG_LIST}; do
    go test -covermode=count -coverprofile "${COVERAGE_DIR}/${package##*/}.cov" "$package" ;
done ;

# Merge the coverage profile files
echo 'mode: count' > "${COVERAGE_DIR}"/coverage.cov ;
tail -q -n +2 "${COVERAGE_DIR}"/*.cov >> "${COVERAGE_DIR}"/coverage.cov ;

# Display the global code coverage
go tool cover -func="${COVERAGE_DIR}"/coverage.cov ;

# If needed, generate HTML report
if [ "$1" == "html" ]; then
    go tool cover -html="${COVERAGE_DIR}"/coverage.cov -o coverage.html ;
fi

# Remove the coverage files directory
rm -rf "$COVERAGE_DIR";

3.3.4. buildDockerImage.sh

#!/bin/bash

#检测GOPATH
echo "检测GOPATH"
if [ -z "$GOPATH" ];then
echo "GOPATH 未设定"
exit 1
else
echo "GOPATH=$GOPATH"
fi

#初始化数据
echo "初始化数据"
new_version="1.0.0"
old_version="1.0.0"
golang_version="1.9.2"
app_name="application"
projust_root="demo"
DOCKER_IMAGE_NAME="demo"
REGISTRY_HOST="xxx.xxx.xxx.xxx:5000"
path="/go/src/_/builds/ZhangZhongcheng/demo"


#当前容器更换为旧标签
echo "当前容器更换为旧标签"
docker rmi $REGISTRY_HOST/$DOCKER_IMAGE_NAME:$old_version

# 基于golang:1.9.2镜像启动的容器实例,编译本项目的二进制可执行程序
echo "基于golang:1.9.2镜像启动的容器实例,编译本项目的二进制可执行程序"
cd $path
go build -o $app_name

echo "检测 $app_name 应用"
FILE="$path/$app_name"
if [ -f "$FILE" ];then
echo "$FILE 已就绪"
else
echo "$FILE 应用不存在"
exit 1
fi

#docker构建镜像 禁止在构建上下文之外的路径 添加复制文件
#所以在此可以用命令把需要的文件cp到 dockerfile 同目录内 ,构建完成后再用命令删除
cd $path/scripts
echo "提取构建时需要的文件"
cp ../$app_name $app_name

# 基于当前目录下的Dockerfile构建镜像
echo "基于当前目录下的Dockerfile构建镜像"
echo "docker build -t $REGISTRY_HOST/$DOCKER_IMAGE_NAME:$new_version ."
docker build -t $REGISTRY_HOST/$DOCKER_IMAGE_NAME:$new_version .

# 删除本次生成的可执行文件 以及构建所需要的文件
echo "删除本次生成的可执行文件 以及构建所需要的文件"
rm -rf $app_name
rm -rf ../$app_name

#查看镜像
echo "查看镜像"
docker images | grep $DOCKER_IMAGE_NAME

#推送镜像
echo "推送镜像"
echo "docker push $REGISTRY_HOST/$DOCKER_IMAGE_NAME:$new_version"
docker push $REGISTRY_HOST/$DOCKER_IMAGE_NAME:$new_version

echo "auto deploy"
./automationDeployment.sh $new_version $old_version

3.3.5. automationDeployment.sh

#!/usr/bin/expect
#指定shebang
#设定超时时间为3秒
set ip xxx.xxx.xxx.xxx
set password "xxxxxxx"

set new_version [lindex $argv 0]
set old_version [lindex $argv 1]

spawn ssh root@$ip
expect {
    "*yes/no" { send "yes\r"; exp_continue}
    "*password:" { send "$password\r" }
}
expect "#*"
send "cd /root/demo/\r"
send "./docker_run_demo.sh $new_version $old_version\r"
expect eof

3.3.6. Dockerfile

FROM golang:1.9.2

#定义环境变量 alpine专用
#ENV TIME_ZONE Asia/Shanghai

ADD application /go/src/demo/

WORKDIR /go/src/demo

ADD run_application.sh /root/
RUN chmod 755 /root/run_application.sh
CMD sh /root/run_application.sh

EXPOSE 8080

3.3.7. run_application.sh

#!/bin/bash

#映射ip
cp /usr/share/zoneinfo/Asia/Shanghai  /etc/localtime

cd /go/src/demo/

./application

4. 结果

以下为部署成功后的截图:


result

转载请注明出处: Golang基于Gitlab CI/CD部署方案

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249