持续集成
(Continuous integration)是一种软件开发实践,即团队开发成员常常集成它们的工做,经过每一个成员天天至少集成一次,也就意味着天天可能会发生屡次集成。每次集成都经过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。html
持续部署
(continuous deployment)是经过自动化的构建、测试和部署循环来快速交付高质量的产品。某种程度上表明了一个开发团队工程化的程度,毕竟快速运转的互联网公司人力成本会高于机器,投资机器优化开发流程化相对也提升了人的效率,让 engineering productivity 最大化。linux
本次试验是基于Centos 7.3
, docker 17.03.2-ce
环境下的。docker的安装这里就不赘述了,提供了官方连接吧: Get Docker CE for CentOSgit
启动命令以下:github
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根据具体状况具体设置golang
启动命令以下:docker
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根据具体状况具体设置shell
咱们的集成和部署都须要放在一个容器里面进行,因此,须要制做一个镜像并安装一些必要的工具,用于集成和部署相关操做。目前咱们的项目都是基于golang 1.9.2的,这里也就基于golang:1.9.2的镜像制定一个特定的镜像。windows
Dockerfile内容以下:centos
# 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自动登陆远程服务器的工具,这里安装改工具是为了能够实现远程服务器端部署应用ruby
另外,在安装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下的。
环境准备好后,在服务器上执行如下命令,注册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 (以前本身制做的镜像)
复制代码
成功后,能够看到gitlab->你的项目->settings -> CI/CD ->Runners settings 页面下面有如下内容:
注册成功以后,还须要在原有的配置上作一些特定的配置,以下:
[[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拉取
在gitlab项目根目录建立.gitlab-ci.yml
文件,填写runner规则,具体语法课参考官方文档:docs.gitlab.com/ee/ci/yaml/
下面介绍几个golang常见的集成命令
go list ./...
复制代码
请注意,若是咱们要避免将咱们的工具应用于外部资源,并将其限制在咱们的代码中。 那么咱们须要去除vendor 目录,命令以下:
go list ./... | grep -v /vendor/
复制代码
_test.go
文件。可使用如下命令运行全部包的测试:go test -short $(go list ./... | grep -v /vendor/)
复制代码
go test -race -short $(go list . /…| grep - v /vendor/)
复制代码
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
复制代码
go build .
复制代码
$ golint -set_exit_status $(go list ./... | grep -v /vendor/)
复制代码
注意-set_exit_status选项。 默认状况下,golint仅输出样式问题,并带有返回值(带有0返回码),因此CI不认为是出错。 若是指定了-set_exit_status,则在遇到任何样式问题时,golint的返回码将不为0。
若是咱们不想在.gitlab-ci.yml
文件中写的太复杂,那么咱们能够把持续集成环境中使用的全部工具,所有打包在Makefile中,并用统一的方式调用它们。
这样的话,.gitlab-ci.yml
文件就会更加简洁了。固然了,Makefile一样也能够调用*.sh
脚本文件
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
复制代码
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
复制代码
#!/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";
复制代码
#!/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
复制代码
#!/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
复制代码
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
复制代码
#!/bin/bash
#映射ip
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
cd /go/src/demo/
./application
复制代码
如下为部署成功后的截图:
本文为原创,转载请注明出处: Golang基于Gitlab CI/CD部署方案