Golang基於Gitlab CI/CD部署方案

概述

持續集成 (Continuous integration)是一種軟件開發實踐,即團隊開發成員常常集成它們的工做,經過每一個成員天天至少集成一次,也就意味着天天可能會發生屢次集成。每次集成都經過自動化的構建(包括編譯,發佈,自動化測試)來驗證,從而儘早地發現集成錯誤。html

持續部署(continuous deployment)是經過自動化的構建、測試和部署循環來快速交付高質量的產品。某種程度上表明瞭一個開發團隊工程化的程度,畢竟快速運轉的互聯網公司人力成本會高於機器,投資機器優化開發流程化相對也提升了人的效率,讓 engineering productivity 最大化。linux

1. 環境準備

本次試驗是基於Centos 7.3, docker 17.03.2-ce環境下的。docker的安裝這裏就不贅述了,提供了官方連接吧: Get Docker CE for CentOSgit

1.1. docker啓動gitlab

啓動命令以下: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

1.2. docker啓動gitlab-runner

啓動命令以下: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

1.3. 用於集成部署的鏡像製做

咱們的集成和部署都須要放在一個容器裏面進行,因此,須要製做一個鏡像並安裝一些必要的工具,用於集成和部署相關操做。目前咱們的項目都是基於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下的。

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規則,具體語法課參考官方文檔: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 / amd6四、freebsd / amd6四、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部署方案

相關文章
相關標籤/搜索