what is Jenkins
Jenkins是一款由Java編寫(xiě)的開(kāi)源的持續(xù)集成工具珠月。
what is continuous integration
根據(jù) ThoughtWorks 的 Martin Fowler 的觀點(diǎn),持續(xù)集成(continuous
integration)是一種軟件開(kāi)發(fā)實(shí)踐背犯,要求團(tuán)隊(duì)成員經(jīng)常集成他們的工作翁垂。開(kāi)發(fā)人員每次代碼合并都會(huì)觸發(fā)持續(xù)集成服務(wù)器進(jìn)行自動(dòng)構(gòu)建膏秫,這個(gè)過(guò)程包括了編譯提佣、單元測(cè)試荒叼、集成測(cè)試涎劈、質(zhì)量分析等步驟广凸,通過(guò)自動(dòng)化的過(guò)程進(jìn)行驗(yàn)證阅茶,以盡快檢測(cè)集成錯(cuò)誤。這種方法會(huì)使得集成問(wèn)題大幅減少谅海,更快地實(shí)現(xiàn)有凝聚力的軟件開(kāi)發(fā)脸哀。
業(yè)界普遍認(rèn)同的持續(xù)集成的原則包括:
1)需要版本控制軟件保障團(tuán)隊(duì)成員提交的代碼不會(huì)導(dǎo)致集成失敗。常用的版本控制軟件有
Git扭吁、CVS撞蜂、Subversion 等;
2)開(kāi)發(fā)人員必須及時(shí)向版本控制庫(kù)中提交代碼侥袜,也必須經(jīng)常性地從版本控制庫(kù)中更新代碼到本地蝌诡;
3)需要有專(zhuān)門(mén)的集成服務(wù)器來(lái)執(zhí)行集成構(gòu)建。根據(jù)項(xiàng)目的具體實(shí)際枫吧,集成構(gòu)建可以被軟件的修改來(lái)直接觸發(fā)浦旱,也可以定時(shí)啟動(dòng),如每半個(gè)小時(shí)構(gòu)建一次九杂;
4)必須保證構(gòu)建的成功颁湖。如果構(gòu)建失敗,修復(fù)構(gòu)建過(guò)程中的錯(cuò)誤是優(yōu)先級(jí)最高的工作例隆。一旦修復(fù)甥捺,需要手動(dòng)啟動(dòng)一次構(gòu)建。
Jenkins對(duì)持續(xù)集成的支持
what is DSL
Domain Specific Language 專(zhuān)門(mén)針對(duì) 一個(gè)特定的問(wèn)題領(lǐng)域
含有建模所需的語(yǔ)法和語(yǔ)義镀层,在與問(wèn)題域相同的抽象層次對(duì)概念建模
DSL示例
pipeline
stage('docker build') {
steps {
//生成代碼鏡像1
script {
customImage = docker.build(docker_tag)
}
}
}
stage('push image') {
steps {
script {
docker.withRegistry("https://harbor.xxx.xxx", 'harbor_xxx_net') {
customImage.push()
}
}
}
}
stage('deploy') {
steps {
script {
deployK8s config.GROUP, config.PROJECT_NAME, IMAGE_TAG, params.TARGET, config.TOKEN, true
}
}
}
geb
import geb.Browser
Browser.drive {
go "http://myapp.com/login"
assert $("h1").text() == "Please Login"
$("form.login").with {
username = "admin"
password = "password"
login().click()
}
assert $("h1").text() == "Admin Section"
}
gradle
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
why Jenkins need pipeline
pipeline可以進(jìn)入版本控制镰禾,以jenkinsfile的方式和源碼放在一起
比起shell,代碼更強(qiáng)大鹿响,健壯羡微,容易復(fù)用,可以以面向?qū)ο蟮姆绞竭M(jìn)行開(kāi)發(fā)惶我、官方提供了大量與部署相關(guān)的方法妈倔、關(guān)鍵字
可以在發(fā)布過(guò)程中有更多控制手段
pipeline的基礎(chǔ)概念
Stage:
一個(gè)Pipeline可以劃分為若干個(gè)Stage,每個(gè)Stage代表一組操作绸贡。注意盯蝴,Stage是一個(gè)邏輯分組的概念,可以跨多個(gè)Node听怕。
Node:
一個(gè)Node就是一個(gè)Jenkins節(jié)點(diǎn)捧挺,或者是Master,或者是Agent尿瞭,是執(zhí)行Step的具體運(yùn)行期環(huán)境闽烙。
Step:
Step是最基本的操作單元,小到創(chuàng)建一個(gè)目錄,大到構(gòu)建一個(gè)Docker鏡像黑竞,由各類(lèi)Jenkins
Plugin提供捕发。
pipeline in Groovy
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'make'
}
}
stage('Test'){
steps {
sh 'make check'
junit 'reports/**/*.xml'
}
}
stage('Deploy') {
steps {
sh 'make publish'
}
}
}
}
DSL的種類(lèi)
外部DSL(external DSL)
在主程序語(yǔ)言之外,用一種單獨(dú)的語(yǔ)言表示領(lǐng)域?qū)S谜Z(yǔ)言很魂,是從零開(kāi)發(fā)的DSL扎酷,在詞法分析、解析技術(shù)遏匆、解釋法挨、編譯、代碼生成等方面擁有獨(dú)立的設(shè)施幅聘。開(kāi)發(fā)外部DSL近似于從零開(kāi)始實(shí)現(xiàn)一種擁有獨(dú)特語(yǔ)法和語(yǔ)義的全新語(yǔ)言(markdown凡纳、xml、html)
內(nèi)部DSL(internal DSL)
將現(xiàn)有的編程語(yǔ)言作為宿主語(yǔ)言喊暖,基于其設(shè)施建立專(zhuān)門(mén)面向特定領(lǐng)域的各種語(yǔ)義惫企。(rails,gradle陵叽,pipeline)
Groovy適合用來(lái)構(gòu)建DSL的原因
方法調(diào)用的語(yǔ)法
println('demo')
println 'demo'
運(yùn)算符重載
強(qiáng)大的閉包狞尔,函數(shù)當(dāng)作第一類(lèi)對(duì)象
class PizzaShop {
def config = [:]
def static order(closure) {
PizzaShop shop = new PizzaShop()
shop.with closure
}
def size(theSize) { println "size is $theSize" }
def toppings(String[] theToppings) { println "Toppings received $theToppings" }
def methodMissing(name, args) {
}
}
PizzaShop.order {
size 'Large'
toppings 'Olives', 'Bell Pepper', 'Onions'
}
強(qiáng)大的元編程能力
use(groovy.time.TimeCategory) {
//直接用數(shù)字的寫(xiě)法
println 1.minute.from.now //一分鐘以后
println 30.days.ago //30天前的時(shí)間
// 還可以與日期型的混用
def someDate = new Date()
println someDate - 3.months //三個(gè)月前的時(shí)間
}
Integer.metaClass.getDays { ->
delegate
}
Integer.metaClass.getAgo { ->
def today = Calendar.instance
today.add(Calendar.DAY_OF_MONTH, -delegate)
today
}
GregorianCalendar.metaClass.at { Double time ->
def timeDbl = time.doubleValue()
def hours=(int)timeDbl
def minutes=(int)((timeDbl-hours)*100)
delegate.set(Calendar.HOUR_OF_DAY,hours)
delegate.set(Calendar.MINUTE,minutes)
delegate.time
}
println 2.days.ago.at(4)
屬性賦值的優(yōu)雅
class Config {
def map=[:]
def methodMissing(String name,args){
this.map[name]=args[0]
println(args[0])
}
private String text;
public String getMessage() {
return "GET " + text;
}
public void setMessage(final String text) {
this.text = "SET " + text
}
}
config=new Config()
config.name "John"
println config.map['name']
def s2 = new Config(message: 'Groovy constructor') // Named parameter in constructor.
assert 'GET SET Groovy constructor' == s2.getMessage()
def s3 = new Config()
s3.message = 'Groovy style' // = assigment for property.
assert 'GET SET Groovy style' == s3.message // get value with . notation.
可控的DSL沙盒
dsl = new File('./deploy.dsl')
def g = new GeneralBuildXml(xml)
def binding = new Binding()
binding.setProperty('exclude', new MethodClosure(g, 'exclude'))
binding.setProperty("out", new PrintWriter(new StringWriter()))
CompilerConfiguration conf = new CompilerConfiguration();
SecureASTCustomizer customizer = new SecureASTCustomizer();
customizer.with {
closuresAllowed = true // 用戶(hù)能寫(xiě)閉包
methodDefinitionAllowed = true // 用戶(hù)能定義方法
importsWhitelist = [] // 白名單為空意味著不允許導(dǎo)入
tokensWhitelist = [
PLUS,
EQUAL
].asImmutable()
//將用戶(hù)所能定義的常量類(lèi)型限制為數(shù)值類(lèi)型
constantTypesClassesWhiteList = [
String.class,
Object.class,
].asImmutable()
}
customizer.setReceiversWhiteList(Arrays.asList(
"java.lang.Object"
));
conf.addCompilationCustomizers(customizer);
new GroovyShell(binding, conf).evaluate(dsl)
超出沙盒允許的調(diào)用將會(huì)失敗
exclude {
dir "test"+"1", ".idea"
file "composer.lock"
}
new File('demo').write('test')
java.lang.SecurityException: Method calls not allowed on [java.io.File]
at org.codehaus.groovy.control.customizers.SecureASTCustomizer$SecuringCodeVisitor.visitMethodCallExpression(SecureASTCustomizer.java:924)
at org.codehaus.groovy.ast.expr.MethodCallExpression.visit(MethodCallExpression.java:70)
at org.codehaus.groovy.control.customizers.SecureASTCustomizer$SecuringCodeVisitor.visitExpressionStatement(SecureASTCustomizer.java:846)
at org.codehaus.groovy.ast.stmt.ExpressionStatement.visit(ExpressionStatement.java:42)
at org.codehaus.groovy.control.customizers.SecureASTCustomizer$SecuringCodeVisitor.visitBlockStatement(SecureASTCustomizer.java:806)
at org.codehaus.groovy.ast.stmt.BlockStatement.visit(BlockStatement.java:71)
at org.codehaus.groovy.control.customizers.SecureASTCustomizer.call(SecureASTCustomizer.java:616)
at org.codehaus.groovy.control.CompilationUnit.applyToPrimaryClassNodes(CompilationUnit.java:1087)
at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:631)
at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:609)
at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:586)
at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:354)
at groovy.lang.GroovyClassLoader.access$300(GroovyClassLoader.java:87)
at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:323)
at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:320)
at org.codehaus.groovy.runtime.memoize.ConcurrentCommonCache.getAndPut(ConcurrentCommonCache.java:147)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:318)
at groovy.lang.GroovyShell.parseClass(GroovyShell.java:547)
at groovy.lang.GroovyShell.parse(GroovyShell.java:559)
at groovy.lang.GroovyShell.evaluate(GroovyShell.java:443)
at groovy.lang.GroovyShell.evaluate(GroovyShell.java:491)
at groovy.lang.GroovyShell$evaluate.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:128)
at main.run(main.groovy:42)
at groovy.lang.GroovyShell.runScriptOrMainOrTestOrRunnable(GroovyShell.java:264)
at groovy.lang.GroovyShell.run(GroovyShell.java:377)
at groovy.lang.GroovyShell.run(GroovyShell.java:366)
at groovy.ui.GroovyMain.processOnce(GroovyMain.java:589)
at groovy.ui.GroovyMain.run(GroovyMain.java:332)
at groovy.ui.GroovyMain.access$1400(GroovyMain.java:69)
at groovy.ui.GroovyMain$GroovyCommand.process(GroovyMain.java:291)
at groovy.ui.GroovyMain.processArgs(GroovyMain.java:134)
at groovy.ui.GroovyMain.main(GroovyMain.java:116)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.codehaus.groovy.tools.GroovyStarter.rootLoader(GroovyStarter.java:114)
at org.codehaus.groovy.tools.GroovyStarter.main(GroovyStarter.java:136)
1 error
小結(jié)
Jenkins在引入pipeline之后變得更加強(qiáng)大和易于維護(hù),也給我們一個(gè)啟示巩掺,當(dāng)我們?cè)谀骋粋€(gè)領(lǐng)域經(jīng)常需要解決重復(fù)性問(wèn)題時(shí)偏序,可以考慮實(shí)現(xiàn)一個(gè)
DSL 專(zhuān)門(mén)用來(lái)解決這些類(lèi)似的問(wèn)題。 而使用嵌入式 DSL
來(lái)解決這些問(wèn)題是一個(gè)非常好的辦法胖替,我們并不需要重新實(shí)現(xiàn)解釋器研儒,也可以利用宿主語(yǔ)言的抽象能力。