为容器化的 Go 程序搭建 CI
本文介绍如何使用 Jenkins 的声明式 pipeline 为一个简单的 Golang web 应用搭建 CI 环境。如果你还不太了解 Jenkins 及其声明式 pipeline,请先参考笔者的 Jenkins 系列文章,或者直接到 Jenkins 官网进行学习。说明:本文的演示环境为 ubuntu 16.04。
准备 Jenkins 环境
鉴于篇幅原因,本文不再介绍 Jenkins 环境的搭建。本文演示的 demo 只要求 Jenkins server 连接了一个带有 go 标签的 agent,该 agent 上安装了 docker:

如果你希望可以收到 CI 中的邮件通知,请配置 Jenkins 邮件通知中的 SMTP server。
demo 程序
笔者创建了一个简单的 Golang web 程序用于演示,大家可以从这里下载该程序。
app.go
app.go 文件包含主程序,其内容如下:
package main import (
"fmt"
"net/http"
"strings"
) func getNameLen(name string) int {
return len(name)
} func sayHello(w http.ResponseWriter, r *http.Request) {
message := r.URL.Path
message = strings.TrimPrefix(message, "/")
message = "Hello " + message + " : " + fmt.Sprintf("%d", getNameLen(message))
w.Write([]byte(message))
} func main() {
http.HandleFunc("/", sayHello)
if err := http.ListenAndServe(":8088", nil); err != nil {
panic(err)
}
}
该程序的功能非常简单,如果你在 url 中域名后面的部分添加了自己的名字,它会向你问好并计算出你名字的长度:

app_test.go
app_test.go 文件包含了函数 getNameLen() 的单元测试:
package main import (
"testing"
) func Test_GetNameLen_1(t *testing.T) {
if l := getNameLen("nick"); l != {
t.Error("test failed, the length of nick is not correct.")
} else {
t.Log("test passed.")
}
} func Test_GetNameLen_2(t *testing.T) {
if l := getNameLen(""); l != {
t.Error("test failed, the length of empty string is not correct.")
} else {
t.Log("test passed.")
}
}
Dockerfile
Dockerfile 文件用于构建 docker 镜像,其内容如下:
FROM golang:1.11.0
WORKDIR /go/src/gowebdemo/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o gowebdemo . FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/gowebdemo .
EXPOSE 8088
CMD ["./gowebdemo"]
在准备好上面的内容后,让我们开始 CI 的配置。
Jenkinsfile
为了实现 pipeline as code,我们把配置 Jenkins 的 pipeline 内容保存到 Jenkinsfile 文件中,并和代码一起 checkin 到代码中。该 demo 的 Jenkinsfile 内容如下:
pipeline {
agent {
label 'go'
}
stages {
stage('UnitTest') {
steps {
script {
if( sh(script: 'docker run --rm -v $(pwd):/go/src/gowebdemo -w /go/src/gowebdemo golang:1.11.0 /bin/bash -c "/go/src/gowebdemo/rununittest.sh"', returnStatus: true ) != ){
currentBuild.result = 'FAILURE'
}
}
junit '*.xml'
script {
if( currentBuild.result == 'FAILURE' ) {
sh(script: "echo unit test failed, please fix the errors.")
sh "exit 1"
}
}
}
}
stage('Build') {
steps {
sh './buildapp.sh'
}
}
stage('Deploy') {
steps {
sh './deployapp.sh'
}
}
}
post {
failure {
mail bcc: '', body: "<b>gopro build failed</b><br>Project: ${env.JOB_NAME} <br>Build Number: ${env.BUILD_NUMBER} <br> URL de build: ${env.BUILD_URL}", cc: '', charset : 'UTF-8', from: '', mimeType: 'text/html', replyTo: '', subject: "ERROR CI: Project name -> ${env.JOB_NAME}", to: "your email address";
}
success {
mail bcc: '', body: "<b>gopro build success</b><br>Project: ${env.JOB_NAME} <br>Build Number: ${env.BUILD_NUMBER} <br> URL de build: ${env.BUILD_URL}", cc: '', charset: 'UTF-8', from: '', mimeType: 'text/html', replyTo: '', subject: "SUCCESS CI: Project name -> ${env.JOB_NAME}", to: "your email address";
}
}
}
label 'go'
agent 中的 label 指定该 pipeline 运行在带有 go 标签的 agent 上。
stage('UnitTest')
该部分运行代码中的单元测试,并根据单元测试的结果确定是否继续执行后面的流水线操作。其中的脚本文件 rununittest.sh 内容如下:
#!/bin/bash set -x
go get -d -v golang.org/x/net/html
go get -u github.com/jstemmer/go-junit-report
go test -v >& > tmp
status=$?
$GOPATH/bin/go-junit-report < tmp > test_output.xml exit ${status}
该脚本执行单元测试操作,并把运行单元测试命令的结果作为脚本运行的结果返回。这一点很重要,我们就是通过这种方式来知道单元测试是否完全通过,如果没有完全通过就让该次持续集成失败,而停止后续的操作。同时使用 go-junit-report 组件把单元测试的结果保存为 junit 格式的文件 test_output.xml。junit '*.xml' 则可以分析该单元测试的结果,并以图表的方式展示:

stage('Build')
该部分执行脚本 buildapp.sh,其内容如下:
#!/bin/bash set -ex
docker build -t gowebdemo . # remove all none tag images
if [ ! -z "$(docker images -q --filter 'dangling=true')" ]; then
docker rmi $(docker images -q --filter "dangling=true")
fi
首先执行 docker build -t gowebdemo . 命令,以 Dockerfile 中的指令构建应用程序并打包为容器镜像。然后移除系统中没有标签的镜像释放磁盘空间。
在比较正式的环境中,一般会把构建好的容器镜像推送到私有的镜像库中,这里为了简化过程,就把镜像存放在 agent 上,并在下一步中在 agent 上部署一个应用的实例。
stage('DeployApp')
该部分执行脚本 deployapp.sh,脚本的内容如下:
#!/bin/bash set -ex
# remove the current app instance
if [ -n "$(docker ps -aq -f name=nickwebdemo)" ]; then
docker rm -f nickwebdemo
fi # run a new app instance
docker run -d \
-p : \
--name nickwebdemo \
--restart=always \
gowebdemo
脚本先检查是不是已经有同名的容器实例,如果有就先删除掉该实例,然后运行一个新的实例。
在最后的 post 部分,我们根据该次持续集成的状态来发送不同的邮件通知,比如整个过程没有错误发生,单元测试也都通过了,就发送成功的通知,否则发送失败的通知。
配置 Jenkins Job
在 Jenkins 中创建 pipeline 类型的 Job,并设置从 SCM 获得 pipeline 脚本:

因为笔者放置代码的库是公开的,所以只用指定代码库的路径就可以了,不需要添加相关的认证信息。
现在就可以触发 CI 过程了,下图是笔者机器上运行完成后的截图:
虽然只有两个单元测试的 case,但显示的结果还不错!并且 web app 被成功的部署到了 agent 上。
checkin 代码进行演示
下面我们配置 Jenkins 每隔一分钟检查一次代码是否有变更,有的话就触发 CI。在 Build Triggers 中选择 Poll SCM,然后输入 5 个由空格分隔的 * 号:

保存该的配置,接下来让我们添加一个单元测试的 case:
func Test_GetNameLen_3(t *testing.T) {
if l := getNameLen("andrew"); l != {
t.Error("test failed, the length of andrew string is not correct.")
} else {
t.Log("test passed.")
}
}
这里笔者故意算错了字符串 "andrew" 的长度,checkin 这段代码,然后看看 Jenkins 中持续集成的过程:

持续集成的过程被自动触发了,但是由于单元测试中有失败的 case 导致整个过程都失败了,并且单元测试后面的过程都没有被执行:

如果你正确配置了邮件服务器并且把 Jenkinsfile 中的邮件地址改成的你自己的邮件地址,那么不管持续集成是成功还是失败你都会收到相关的通知。
现在把失败的单元测试修改正确,再提交一次,这样就开启了我们的持续集成之旅!
总结
本文只是介绍了一个非常简单的 demo 场景,但是一旦一个简单的环境能够运行起来了,你就可以不断的往上添砖加瓦,比如创建集成测试的环境,添加集成测试,并最终销毁集成测试环境等内容,最终让它成为一个能够满足需求的持续集成流水线。
参考:
Building a CI for Golang test
Building a CI system for Go, with Jenkins
为容器化的 Go 程序搭建 CI的更多相关文章
- Docker容器化【Dockerfile编写&&搭建与使用Docker私有仓库】
# Docker 学习目标: 掌握Docker基础知识,能够理解Docker镜像与容器的概念 完成Docker安装与启动 掌握Docker镜像与容器相关命令 掌握Tomcat Nginx 等软件的常用 ...
- 一次容器化springboot程序OOM问题探险
背景 运维人员反馈一个容器化的java程序每跑一段时间就会出现OOM问题,重启后,间隔大概两天后复现. 问题调查 一查日志 由于是容器化部署的程序,登上主机后使用docker logs Contain ...
- ASP.NET Core应用程序容器化、持续集成与Kubernetes集群部署(一)(转载)
本文结构 ASP.NET Core应用程序的构建 ASP.NET Core应用程序容器化所需注意的问题 应用程序的配置信息 端口侦听 ASP.NET Core的容器版本 docker镜像构建上下文(B ...
- 【转帖】使用容器化和 Docker 实现 DevOps 的基础知识
使用容器化和 Docker 实现 DevOps 的基础知识 https://www.kubernetes.org.cn/6730.html 2020-02-24 15:20 灵雀云 分类:容器 阅读( ...
- Java 服务 Docker 容器化最佳实践
转载自:https://mp.weixin.qq.com/s/d2PFISYUy6X6ZAOGu0-Kig 1. 概述 当我们在容器中运行 Java 应用程序时,可能希望对其进行调整参数以充分利用资源 ...
- 利用 ELK 搭建 Docker 容器化应用日志中心
利用 ELK 搭建 Docker 容器化应用日志中心 概述 应用一旦容器化以后,需要考虑的就是如何采集位于 Docker 容器中的应用程序的打印日志供运维分析.典型的比如SpringBoot应用的日志 ...
- 详解利用ELK搭建Docker容器化应用日志中心
概述 应用一旦容器化以后,需要考虑的就是如何采集位于Docker容器中的应用程序的打印日志供运维分析.典型的比如SpringBoot应用的日志 收集.本文即将阐述如何利用ELK日志中心来收集容器化应用 ...
- .NET 7 SDK 开始 支持构建容器化应用程序
微软于 8 月 25 日在.NET官方博客上,.NET 7 SDK 将包括对创建容器化应用程序的支持,作为构建发布过程的一部分,从而绕过需要.显式 Docker 构建阶段. 这一决定背后的基本认知是简 ...
- Atitit s2018.6 s6 doc list on com pc.docx Atitit s2018.6 s6 doc list on com pc.docx Aitit algo fix 算法系列补充.docx Atiitt 兼容性提示的艺术 attilax总结.docx Atitit 应用程序容器化总结 v2 s66.docx Atitit file cms api
Atitit s2018.6 s6 doc list on com pc.docx Atitit s2018.6 s6 doc list on com pc.docx Aitit algo fi ...
随机推荐
- noi2018还没想好记
前面说点什么.. 没想到吧 嘴上说着不写的彩笔博主最后还是写了这篇东西.. Day -inf 在雅礼集训,打了四场模拟赛.. 真正说打得好的.. 也就那么一场 身体很差 心态很差 状态很差 虽然有书读 ...
- PHP生成图表pChart
pChart是一个开源的图表生成库,主要涉及3个Class:pChart.class, pData.class, pCache.class,可生成20多种简单或复杂的图表,支持PNG,JPG,GIF通 ...
- oracle基础语句练习
1. 创建相关表结构 Emp----员工信息表 Ename ), --姓名 Empno ), --编号 Deptno ), --所在部门 Job ), --工种(人员类别),如:manager 经理, ...
- 关于docker jenkins启动时失败的问题处理
最近在做持续集成,然后使用docker 运行jenkins docker run -d -p 8088:8080 -p 50000:50000 -v /home/docker/jenkins_hom ...
- 反调试——jmp到那个地址
目录 1.前言 2.原理讲解 3.代码实现 前言 这节的反调试是通过构造代码来干扰正常的分析.反调试参考CrypMic勒索病毒 原理讲解 在逆向分析汇编代码时,一般都是通过汇编指令call或jmp跳到 ...
- select标签默认选项
1.selected:默认选择该选项: 2.disabled:该选项不能被鼠标选择:(注:选项没有被隐藏的时候) 3.style="display:none":隐藏该选项:(注:该 ...
- tomcat的下载与安装
(1)下载Tomcat 官网地址:http://tomcat.apache.org/whichversion.html (2)安装Tomcat Tomcat有安装版和解压版(绿色版) 安装版以.exe ...
- sv时序组合和时序逻辑
input a; input b; input c; reg d; wire e; reg f; // 时序逻辑,有寄存器 always@(posedge clk)begin 'b1)begin d ...
- linux下SS 网络命令详解
ss命令用来显示处于活动状态的套接字信息. ss命令可以用来获取socket统计信息,它可以显示和netstat类似的内容. 但ss的优势在于它能够显示更多更详细的有关TCP和连接状态的信息,而且比n ...
- VS启动调试速度异常的缓慢问题
方法1: 1. 进入vs2017 2.工具 --选项 -- IntelliTrace 关闭此功能 方法2: 1.由于缓存数据太多,需要重置下vs的开发环境 2.打开visual studio 的命名窗 ...