作者 | Curtis Yanko
原文 | https://dzone.com/articles/continuous-integration-in-the-age-of-containers-pa
当我在2012年运作一个DevOps团队的时候(在容器之前),我们学习到了一些沉重的教训。在这些教训之中,我们有一些自动化工作,就是观察下游的客户并且接受他们的“验收测试”,让他们成为我们的“检验标准”。我们与QA的小伙伴们一同工作,而且在我们把刚刚更新的环境交给他们“之前”,开始运行他们的测试。这是个大事,因为我们从拿走了他们的一些工作,并且逐渐建立起信心,相信我们向他们移交的环境已经为QA测试做好了准备。这种shift left测试是持续集成的核心所在,容器可以帮助我们更进一步。
为了让我自己更好理解这点,和传统web应用的容器一样,我开始转向我最喜欢的项目之一:OWASP Webgoat。如果回头看这个项目的第6版,我们将看到它被作为一个WAR文件分发,其中一个嵌入式的Tomcat服务器,这正是企业应用的组成部分。Webgoat第8版,不管怎样,现在是Docker镜像,这个应用现在作为一个Spring Boot JAR文件构建,这是一个很可能的模式——就是有多少人会将他们的web应用转为docker镜像。
这个理念就是构建Spring Boot JAR并且运行单元测试,然后构建容器和在发布图片到私有注册中心前进行完整测试,如果我们仅仅只从主分支上构建(我假设有一个GitHub工作流,虽然我还没加入,但是从分支部署到prod)。
我们从构建步骤开始,如下:
stage ('Build') {
steps {
sh '''
echo "PATH = $"
echo "M2_HOME = $"
mvn -B install
'''
}
post {
always {
junit '**/target/surefire-reports/**/*.xml'
}
}
}
我们可以看到一个典型的Maven构建将运行单元测试,和不管结果如何,将发布单元测试结果。失败的测试是常见的,特别是在测试驱动开发中,因此我们不要太陷入失败感中。
在下个阶段,我们利用并行化来保持速度:
stage('Scan App - Build Container') {
steps{
parallel('IQ-BOM': {
nexusPolicyEvaluation failBuildOnNetworkError: false,
iqApplication: 'webgoat8',
iqStage: 'build',
iqScanPatterns: [[scanPattern: '']],
jobCredentialsId: ''
},
'Static Analysis': {
echo '...run SonarQube or other SAST tools here'
},
'Build Container': {
sh '''
cd webgoat-server
mvn -B docker:build
'''
})
}
}
在这个部分,我们想做扫描,所以我在构建阶段运行Nexus Lifecycle扫描,并且有一个占位符来静态分析,通过像Sonarqube或Static Code Analysis这类工具。我还在这构建了一个容器来节省整个管道的时间。我们可以在这选择打断构建,但是我自己的策略是设置“警告”,因为在我的经验看来,我希望在我停止管道前做完所有的测试。下面是在Jenkins中,当IQ服务器策略被设置为“警告”时,构建将会是什么样子:
下一个部分强调我在Jenkinsfile-fu方面的不足,因为我还没有弄清楚如何并行做这两步和检查失败。我有提到我接受pull请求了吗?无论如何,这就是测试得到真正容器的地方,允许通过它的步调简单的树立起一个应用实例或服务。
stage('Test Container') {
steps{
echo '...run container and test it'
}
post {
success {
echo '...the Test Scan Passed!'
}
failure {
echo '...the Test FAILED'
error("...the Container Test FAILED")
}
}
}
stage('Scan Container') {
steps{
sh "docker save webgoat/webgoat-8.0 -o / $/webgoat.tar"
nexusPolicyEvaluation failBuildOnNetworkError: false,
iqApplication: 'webgoat8',
iqStage: 'release',
iqScanPatterns: [[scanPattern: '*.tar']],
jobCredentialsId: ''
}
post {
success {
echo '...the IQ Scan PASSED'
}
failure {
echo '...the IQ Scan FAILED'
error("...the IQ Scan FAILED")
}
}
}
当我第一次测试的时候,这个理念实际上就运行了容器,执行功能/系统测试,并且监控日志和其他指标(如性能数据)。我们检查错误并且在这抛出一个“错误”来破坏构建。我用容器的Lifecycle扫描来重复这个模式,通过设置扫描模式为 *.tar。有趣的是,扫描获得了比扫描整个容器并同样在运行层面开始报告时更多的组件,在这个情况下这是一个Java JRE。在第二部分中,我们将了解这些基本镜像是如何制作和测试的,并看看这些容器的真正的价值。因为Webgoat是故意不稳定的,如下所示,这个扫描会是会失败的。
Jenkinsfile中的最后一个逻辑将把容器发布到一个私有的Docker注册表(有时称为可信的Docker注册表),如果我们在主分支上,以上所有测试都已经通过了。
stage('Publish Container') {
when {
branch 'master'
}
steps {
sh '''
docker tag webgoat/webgoat-8.0 / mycompany.com:5000/webgoat/webgoat-8.0:8.0
docker push mycompany.com:5000/webgoat/webgoat-8.0
'''
}
}
我们使用一些分支逻辑来确保我们是在主服务器上,然后标记并将容器推到Nexus Repository Manager上,我在之前的文章中提到使用docker-compose。它已经被推到注册,但在一个一天有10个构建的世界之后,我们的竞争对手会让你等待执行Lifecycle(如扫描),你真的想在你的注册表中放100个的已知的坏的容器,只是为了在验收测试后贴上“坏”标签吗?对我来说,这就是将“验收测试”转移到“检验标准”的好处。“只有通过所有测试的容器才会进入注册中心,从那里他们可以完成他们的生产旅程。”在下游传递缺陷并不能帮助任何人,只是浪费时间、存储、计算和网络资源。
希望这个示例说明了为什么shift left是如此重要,以及同样测试移动的价值,包括应用安全性,在DevSecOps旅程中尽早帮助到你。
领取专属 10元无门槛券
私享最新 技术干货