为容器化的 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 ...
随机推荐
- SparkStreaming+Kafka整合
SparkStreaming+Kafka整合 1.需求 使用SparkStreaming,并且结合Kafka,获取实时道路交通拥堵情况信息. 2.目的 对监控点平均车速进行监控,可以实时获取交通拥堵情 ...
- kubernetes之Kubeadm快速安装v1.12.0版
通过Kubeadm只需几条命令即起一个单机版kubernetes集群系统,而后快速上手k8s.在kubeadm中,需手动安装Docker和kubeket服务,Docker运行容器引擎,kubelet是 ...
- php7安装php-redis扩展
注:操作系统10.13.3 版本,其他版本的Mac系统应该也是可以的 先安装 按照顺序在命令行执行下面命令,如果当前用户权限不够的话,执行命令加上 sudo cd /usr/local/Cellar ...
- HTML图片标签路径解析
img标签中src属性表示的是引用的图片路径,有两种路径类型: 1. 绝对路径 2. 相对路径. 绝对路径:使用图片在硬盘上的绝对位置来访问图片,通常是从根目录开始,向下一个目录一个目录的寻找. ...
- python基础之面向对象1
一.面向对象VS面向过程 1.面向过程 2.面向对象 二.类与对象 1.类和对象 (1)基本概念 类和对象的内存图如下: 2.实例成员 (1)实例变量 (2)实例方法: 3.类成员: (1)类变量 ( ...
- 大数相加 Big Num
代码: #include<stdio.h>#include<algorithm>#include<iostream>#include<string.h> ...
- Python练手例子(16)
91.时间函数举例1. #!/usr/bin/python #coding=utf-8 import time if __name__ == '__main__': #time.time()返回当前的 ...
- seed实验——Set-UID Program Vulnerability实验
一.实验描述 Set-UID是Unix OS中的一个·非常重要的安全机制.当一个Set-UID程序运行的时候,它具有代码拥有者的权限.举个例子,如果代码的拥有者是root用户,那么不论任何用户运行该程 ...
- MongoDB 复制机制
一.复制原理 MongoDB的复制功能是使用操作日志oplog实现的,oplog包含主节点(Master)的每一次写操作,oplog是local本地数据库中的一个数据集合,其它非主节点(Seconda ...
- 记录k8s:k8s1.8.4无坑离线安装
安装部署: 1. 使用vagrant 准备3太虚拟机,自己使用Vbox 准备3太也可以. 2. 按照 https://github.com/gjmzj/kubeasz 安装. 3. 使用letsenc ...