現(xiàn)有混合云平臺的場景下,即有線下和線上的環(huán)境,又有測試與正式的場景,而且結合了Docker,導致打包內(nèi)容有所區(qū)分,且服務的發(fā)布流程復雜起來,手工打包需要在編譯階段就要根據(jù)環(huán)境到處更改配置,因此純手工發(fā)布增加了實施的難度,需要一個統(tǒng)一的適應各種環(huán)境部署的方案。 基于微服務的發(fā)布流程手動/自動構建 -> Jenkins 調度 K8S API ->動態(tài)生成 Jenkins Slave pod -> Slave pod 拉取 Git 代碼/編譯/打包鏡像 ->推送到鏡像倉庫 Harbor -> Slave工作完成,Pod 自動銷毀 ->部署到測試或生產(chǎn) Kubernetes(K8S)平臺。 上面是理想狀況下的將服務編譯打包成鏡像上傳到鏡像庫后部署到Kubernetes平臺的一個流程,但問題是:
就上面現(xiàn)實問題,我們將發(fā)布流程簡化: 關鍵點: Docker鏡像的打包使用com.spotify的docker-maven-plugin插件結合Dockerfile,調用遠程服務器的Docker環(huán)境生成鏡像。 K8S服務部署采用的是ssh方式,將Deployment文件上傳到K8S集群服務器,然后執(zhí)行部署命令。 如何利用Dockerfile打包鏡像之前也是用com.spotify的docker-maven-plugin插件來打包鏡像并推送到私有鏡像倉庫,但問題是無法根據(jù)環(huán)境寫條件判斷,如動態(tài)選擇是否需要啟動pinpoint,線上線下庫地址動態(tài)更換,導致鏡像名前綴也是要動態(tài)變化的,此時直接配置無法滿足,需要結合Dockerfile來實現(xiàn)。 先更改pom文件,指定本項目的Dockerfile文件地址,默認是放在項目根目錄下: <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>1.2.0</version> <configuration> <!--覆蓋相同標簽鏡像--> <forceTags>true</forceTags> <!-- 與maven配置文件settings.xml一致 --> <serverId>nexus-releases</serverId> <!--私有倉庫地址 --> <registryUrl>https://${docker.repostory}</registryUrl> <!--遠程Docker地址 --> <dockerHost>http://10.3.87.210:2375</dockerHost> <!-- 注意imageName一定要是符合正則[a-z0-9-_.]的,否則構建不會成功 --> <!--指定鏡像名稱 倉庫/鏡像名:標簽--> <imageName>${docker.repostory}/${project.artifactId}:${project.version}</imageName> <dockerDirectory>${project.basedir}</dockerDirectory> <resources> <resource> <!-- 指定要復制的目錄路徑,這里是當前目錄 --> <!-- 將打包文件放入dockerDirectory指定的位置 --> <targetPath>/app/</targetPath> <!-- 指定要復制的根目錄,這里是target目錄 --> <directory>${project.build.directory}</directory> <!-- 指定需要拷貝的文件,這里指最后生成的jar包 --> <include>${project.build.finalName}.jar</include> </resource> </resources> </configuration> </plugin> <registryUrl>https://${docker.repostory}</registryUrl> 指定遠程倉庫地址,在主項目的<properties>中指定,這里默認線上倉庫<docker.repostory>39.95.40.97:5000</docker.repostory> <dockerHost>http://10.3.87.210:2375</dockerHost> 指定Docker鏡像打包服務器,這里指定線下服務器。 <imageName>${docker.repostory}/${project.artifactId}:${project.version}</imageName> 指定鏡像名稱 倉庫/鏡像名:標簽 <dockerDirectory>${project.basedir}</dockerDirectory> 指定Dockerfile文件地址,此處指定項目根目錄 Dockerfile內(nèi)容 FROM join:0.2 MAINTAINER {description} Join ADD /app/{artifactId}-{version}.jar /app/ ENTRYPOINT ["java", "-Xmx512m","-Dspring.profiles.active={active}",{jarparam} "-jar", "/app/{artifactId}-{version}.jar"] 基礎鏡像用join:0.2,里面包含了pinpoint和監(jiān)控jvm的promethus客戶端包。 Jarparam會在Jenkins中動態(tài)替換運行時參數(shù),active 指定當前運行環(huán)境,這里可能有人提議根據(jù)項目yml文件中指定內(nèi)容自動匹配,因為要考慮到如果自動匹配 更換線上線下環(huán)境就需要更改yml配置文件后又要上傳到gitlab,如此沒有必要多做一步,直接在Jenkins中當作參數(shù)指定最為便捷。 此處Dockerfile是通用模板,如果有特殊內(nèi)容添加,可自行更改,此時的域名交易模板需要在Jenkins運行時替換參數(shù)后才有用,如果想直接在本機運行打包,可手動替換參數(shù)內(nèi)容后運行: clean package -DskipTests docker:build 推送 clean package -DskipTests docker:build -DpushImage Jenkins發(fā)布流程利用Jenkins的pipeline構建流水線Pipeline也就是構建流水線,對于程序員來說,最好的解釋是:使用代碼來控制項目的構建、測試、部署等。使用它的好處有很多,包括但不限于: l 使用Pipeline可以非常靈活的控制整個構建過程; l 可以清楚的知道每個構建階段使用的時間,方便構建的優(yōu)化; l 構建出錯,使用stageView可以快速定位出錯的階段; l 一個job可以搞定整個構建,方便管理和維護等。 Pipeline 支持兩種語法,聲明式和腳本式。這兩種方法都支持構建持續(xù)交付流水線,都可以通過 web UI 或 Jenkinsfile 文件來定義 Pipeline(通常認為創(chuàng)建 Jenkinsfile 文件并上傳到源代碼控制倉庫是最佳實踐) Jenkinsfile 就是一個包含對 Jenkins Pipeline 定義的文本文件,會上傳到版本控制中。下面的 Pipeline 實現(xiàn)了基本的 3 段持續(xù)交付流水線。 聲明式 Pipeline: // Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Build') { steps { echo 'Building..' } } stage('Test') { steps { echo 'Testing..' } } stage('Deploy') { steps { echo 'Deploying....' } } } } 對應的腳本式 Pipeline: // Jenkinsfile (Scripted Pipeline) node { stage('Build') { echo 'Building....' } stage('Test') { echo 'Building....' } stage('Deploy') { echo 'Deploying....' } } 注意,所有的 Pipeline 都會有這三個相同的 stage,可以在所有項目的一開始就定義好它們。下面演示在 Jenkins 的測試安裝中創(chuàng)建和執(zhí)行一個簡單的 Pipeline。 假設項目已經(jīng)設置好了源代碼控制倉庫,并且已經(jīng)按照入門章節(jié)的描述在 Jenkins 中定義好了 Pipeline。 使用文本編輯器(最好支持 Groovy 語法高亮顯示),在項目根目錄中創(chuàng)建 Jenkinsfile。 上面的聲明式 Pipeline 示例包含了實現(xiàn)一個持續(xù)交付流水線所需的最少步驟。必選指令 agent 指示 Jenkins 為 Pipeline 分配執(zhí)行程序和工作空間。沒有 agent 指令的話,聲明式 Pipeline 無效,無法做任何工作!默認情況下 agent 指令會確保源代碼倉庫已經(jīng)檢出,并且可用于后續(xù)步驟。 stage 和 step 指令在聲明式 Pipeline 中也是必須的,用于指示 Jenkins 執(zhí)行什么及在哪個 stage 中執(zhí)行。 對于腳本式 Pipeline 的更高級用法,上面的示例節(jié)點是至關重要的第一步,因為它為 Pipeline 分配了一個執(zhí)行程序和工作空間。如果沒有 node,Pipeline 不能做任何工作!在 node 內(nèi),業(yè)務的第一階段是檢出此項目的源代碼。由于 Jenkinsfile 是直接從源代碼控制中提取的,因此 Pipeline 提供了一種快速簡單的方法來訪問源代碼的正確版本: // Jenkinsfile (Scripted Pipeline) node { checkout scm /* .. snip .. */ } 這個 checkout 步驟會從源代碼控制中檢查代碼,scm 是特殊變量,它指示運行檢出步驟,復制觸發(fā)了這次 Pipeline 運行的指定版本。 最終的流程樣式: 一般用聲明式來構建流水,實際操作過程中還是發(fā)現(xiàn)腳本式構建更順手,而且Groovy語言更方便查資料,因此下面以腳本構建為主演示一個流程。 1.新建任務 2.填寫任務名和描述,由于防止構建歷史太多,只保留3個。 3.添加構建時全局構建參數(shù),用來構建流程動態(tài)選擇環(huán)境,這里有兩種方式,一種是直接在頁面上添加,如下圖,一種是在Jenkinsfile中添加(第一次構建時不會出現(xiàn)選項,第二次構建才會出現(xiàn),因此首次構建需要試構建,暫停再刷新頁面才會有選擇框),兩種最張效果一樣,這里為了方便采用Jenkinsfile來添加全局參數(shù)。 Jenkinsfile中添加 properties([ parameters([string(name: 'PORT', defaultValue: '7082', description: '程序運行端口'),choice(name: 'ACTIVE_TYPE', choices: ['dev', 'prd', 'local'], description: '程序打包環(huán)境'),choice(name: 'ENV_TYPE', choices: ['online', 'offline'], description: '線上、還是線下環(huán)境'),booleanParam(name: 'ON_PINPOINT', defaultValue: true, description: '是否添加Pinpoint監(jiān)控'),booleanParam(name: 'ON_PROMETHEUS', defaultValue: true, description: '是否添加Prometheus監(jiān)控'),string(name: 'EMAIL', defaultValue: '104@qq.com', description: '打包結果通知')]) ]) 4.選擇源碼代碼庫: 需要添加認證,將Jenkins的ssh秘鑰添加到GitLab的頁面中,且需要將此處gitlab中joint用戶添加到需要拉取代碼的項目中才有權限拉取代碼。 Jenkinsfile位置放在項目的根目錄。 5. Jenkinsfile中指定maven目錄地址 MVNHOME = '/opt/maven354' 為防止手工填寫項目名和版本號等一系列信息,因此直接讀取pom文件中要編譯項目的這些信息給全局變量: pom = readMavenPom file: 'pom.xml' echo "group: ${pom.groupId}, artifactId: ${pom.artifactId}, version: ${pom.version} ,description: ${pom.description}" artifactId = "${pom.artifactId}" version = "${pom.version}" description = "${pom.description}" 根據(jù)選擇的線上環(huán)境還是線下環(huán)境,替換鏡像倉庫ip if (params.ENV_TYPE == 'offline' || params.ENV_TYPE == null) { sh "sed -i 's#39.95.40.97:7806#10.3.87.51:8080#g' pom.xml" image = "10.3.87.51:8080/${artifactId}:${version}" } 6.編譯 利用maven構建,利用上面的內(nèi)容先替換掉Dockerfile、Deployment中的變量,再根據(jù)選擇的條件是否啟用pinpoint和promethus,最后編譯。 def jarparam='' def pinname = artifactId if( pinname.length() > 23) { pinname = artifactId.substring(0,23) } //添加pinpoint if(params.ON_PINPOINT) { jarparam = '"-javaagent:/app/pinpoint-agent/pinpoint-bootstrap-1.8.0.jar","-Dpinpoint.agentId={pinname}", "-Dpinpoint.applicationName={pinname}",' } //添加prometheus if(params.ON_PROMETHEUS) { jarparam = jarparam + '"-javaagent:/app/prometheus/jmx_prometheus_javaagent-0.11.0.jar=1234:/app/prometheus/jmx.yaml",' } sh "sed -i 's#{jarparam}#${jarparam}#g' Dockerfile" sh "sed -i 's#{description}#${description}#g;s#{artifactId}#${artifactId}#g;s#{version}#${version}#g;s#{active}#${params.ACTIVE_TYPE}#g;s#{pinname}#${pinname}#g' Dockerfile" sh "sed -i 's#{artifactId}#${artifactId}#g;s#{version}#${version}#g;s#{port}#${params.PORT}#g;s#{image}#${image}#g' Deployment.yaml" sh "'${MVNHOME}/bin/mvn' -DskipTests clean package" 需要注意的是pinpoint的pinpoint.applicationName不能操作24個字符,否則啟用不成功,因此超過的直接截斷。 Department文件詳情看后文。 跳過測試編譯打包 '${MVNHOME}/bin/mvn' -DskipTests clean package 需要在Jenkins服務器安裝maven環(huán)境,還有指定maven的jar包私有倉庫地址。 7. Docker打包 前提是上一步指定pom文件中的鏡像倉庫和Dockerfile中的內(nèi)容是替換后的完整內(nèi)容。 sh "'${MVNHOME}/bin/mvn' docker:build" 8. 推送鏡像 sh "'${MVNHOME}/bin/mvn' docker:push" 如何發(fā)布服務到K8S集群前面幾步已經(jīng)將項目打包并生成了鏡像并推送到了私有倉庫,下面就是部署服務到K8S集群。 先看看Department.yaml文件: --- apiVersion: apps/v1 kind: Deployment metadata: name: {artifactId} namespace: default labels: app: {artifactId} version: {version} spec: selector: matchLabels: app: {artifactId} replicas: 1 template: metadata: labels: app: {artifactId} annotations: prometheus.io.jmx: "true" prometheus.io.jmx.port: "1234" spec: containers: - name: {artifactId} image: {image} # IfNotPresent\Always imagePullPolicy: Always ports: - name: prometheusjmx containerPort: 1234 livenessProbe: #kubernetes認為該pod是存活的,不存活則需要重啟 httpGet: path: /health port: {port} scheme: HTTP initialDelaySeconds: 60 ## 設置為系統(tǒng)完全啟動起來所需的最大時間+若干秒 timeoutSeconds: 5 successThreshold: 1 failureThreshold: 5 readinessProbe: #kubernetes認為該pod是啟動成功的 httpGet: path: /health port: {port} scheme: HTTP initialDelaySeconds: 40 ## 設置為系統(tǒng)完全啟動起來所需的最少時間 timeoutSeconds: 5 successThreshold: 1 failureThreshold: 5 env: - name: eureka-server value: "eureka-server.default.svc.cluster.local" - name: eureka-server-replica value: "eureka-server-replica.default.svc.cluster.local" resources: # 5%的CPU時間和700MiB的內(nèi)存 requests: # cpu: 50m memory: 700Mi # 最多允許它使用 limits: # cpu: 100m memory: 1000Mi # 指定在容器中掛載路徑 volumeMounts: - name: logs-volume mountPath: /logs - name: host-time mountPath: /etc/localtime readOnly: true - name: host-timezone mountPath: /etc/timezone readOnly: true - name: pinpoint-config mountPath: /app/pinpoint-agent/pinpoint.config volumes: - name: logs-volume hostPath: # 宿主機上的目錄 path: /logs - name: host-time hostPath: path: /etc/localtime - name: host-timezone hostPath: path: /usr/share/zoneinfo/Asia/Shanghai - name: pinpoint-config configMap: name: pinpoint-config # 運行在指定標簽的節(jié)點,前提是先給節(jié)點打標 kubectl label nodes 192.168.0.113 edgenode=flow # nodeSelector: # edgenode: flow --- apiVersion: v1 kind: Service metadata: name: {artifactId} namespace: default labels: app: {artifactId} version: {version} spec: selector: app: {artifactId} ports: - name: tcp-{port}-{port} protocol: TCP port: {port} targetPort: {port} 里面的變量會在前面幾步自動替換掉。 添加了prometheus收集jvm的內(nèi)容: prometheus.io.jmx: "true" containerPort: 1234 將pinpoint的配置內(nèi)容pinpoint.config用configMap 保存,方便更改內(nèi)容。 其它內(nèi)容不在此詳解,可自行google。 網(wǎng)上資料一般發(fā)布服務都是直接kubectl deploy,這種情況只適用于jenkins的服務器已包含在K8S服務器集群中。第二種情況是在K8S集群服務器里面生成Jenkins的一個slave節(jié)點,然后在pipeline里面設置node(“k8s”){ ……} 里面發(fā)布,具體方法自行google。 這里為了避免麻煩,采用直接SSH到K8S服務器集群的方案發(fā)布服務。 配置sshagentSSH Agent Plugin :sshagent方法支持,用于上傳構建產(chǎn)物到目標服務器 在Jenkins插件庫搜索后直接下載安裝(需要連外網(wǎng)環(huán)境),生產(chǎn)環(huán)境已安裝,直接使用。 使用: sshagent(credentials: ['deploy_ssh_key_23']) { sh "scp -P 2222 -r Deployment.yaml root@39.95.40.97:/docker/yaml/Deployment-${artifactId}.yaml" sh "ssh -p 2222 root@39.95.40.97 'kubectl apply -f /docker/yaml/Deployment-${artifactId}.yaml && kubectl set env deploy/${artifactId} DEPLOY_DATE=${env.BUILD_ID}'" } 先用ssh遠程到K8S集群中的服務器,將Deployment文件上傳,然后再遠程執(zhí)行kubectl apply發(fā)布服務。 為了避免誤操作,在發(fā)布前做了發(fā)布確認提示判斷。 timeout(time: 10, unit: 'MINUTES') { 以上流程已完成整個流程,然后可以去K8S環(huán)境去看服務是否有正常運行。 關于測試上面的過程沒有加入代碼測試、代碼質量分析SonarQube、發(fā)布后服務測試的階段,后續(xù)可以接入。 如何進行多模塊如何構建很多項目采用的是多模塊構成,因此每個項目配置和發(fā)布要求不一樣,需要單獨編譯到部署,所以每個模塊都需要獨立的Dockerfile和Deployment文件,Jenkinsfile通用一份,然后在發(fā)布時自動彈出模塊列表,選擇需要發(fā)布的模塊進行編譯發(fā)布。 //需要處理的項目多項目時先進入子項目 projectwk = "." mainpom = readMavenPom file: 'pom.xml' //存在多個模塊時,選擇其中一個進行編譯 if(mainpom.modules.size() > 0 ) { echo "項目擁有模塊==${mainpom.modules}" timeout(time: 10, unit: 'MINUTES') { def selproj = input message: '請選擇需要處理的項目', parameters: [choice(choices: mainpom.modules, description: '請選擇需要處理的項目', name: 'selproj')] //, submitterParameter: 'project' projectwk = selproj echo "選擇項目=${projectwk}" } } 讀取主項目的pom中的modules判斷是否包含多個模塊,供用戶選擇。 然后根據(jù)選擇的模塊進行編譯,dir進入選擇的模塊讀取信息并編譯。 dir("${projectwk}") { pom = readMavenPom file: 'pom.xml' echo "group: ${pom.groupId}, artifactId: ${pom.artifactId}, version: ${pom.version} ,description: ${pom.description}" artifactId = "${pom.artifactId}" version = "${pom.version}" description = "${pom.description}" } 完整的Jenkinsfile properties([ parameters([string(name: 'PORT', defaultValue: '7082', description: '程序運行端口'),choice(name: 'ACTIVE_TYPE', choices: ['dev', 'prd', 'local'], description: '程序打包環(huán)境'),choice(name: 'ENV_TYPE', choices: ['online', 'offline'], description: '線上、還是線下環(huán)境'),booleanParam(name: 'ON_PINPOINT', defaultValue: true, description: '是否添加Pinpoint監(jiān)控'),booleanParam(name: 'ON_PROMETHEUS', defaultValue: true, description: '是否添加Prometheus監(jiān)控'),string(name: 'EMAIL', defaultValue: '1041126478@qq.com', description: '打包結果通知')]) ]) node { stage('Prepare') { echo "1.Prepare Stage" MVNHOME = '/opt/maven354' //echo "UUID=${UUID.randomUUID().toString()}" checkout scm //需要處理的項目多項目時先進入子項目 projectwk = "." mainpom = readMavenPom file: 'pom.xml' repostory = "${mainpom.properties['docker.repostory']}" //存在多個模塊時,選擇其中一個進行編譯 if(mainpom.modules.size() > 0 ) { echo "項目擁有模塊==${mainpom.modules}" timeout(time: 10, unit: 'MINUTES') { def selproj = input message: '請選擇需要處理的項目', parameters: [choice(choices: mainpom.modules, description: '請選擇需要處理的項目', name: 'selproj')] //, submitterParameter: 'project' projectwk = selproj echo "選擇項目=${projectwk}" } } dir("${projectwk}") { pom = readMavenPom file: 'pom.xml' echo "group: ${pom.groupId}, artifactId: ${pom.artifactId}, version: ${pom.version} ,description: ${pom.description}" artifactId = "${pom.artifactId}" version = "${pom.version}" description = "${pom.description}" } script { GIT_TAG = sh(returnStdout: true, script: '/usr/local/git/bin/git rev-parse --short HEAD').trim() echo "GIT_TAG== ${GIT_TAG}" } image = "192.168.4.2:5000/${artifactId}:${version}" if (params.ENV_TYPE == 'offline' || params.ENV_TYPE == null) { sh "sed -i 's#39.95.40.97:5000#10.3.80.50:5000#g' pom.xml" image = "10.3.80.50:5000/${artifactId}:${version}" } } if(mainpom.modules.size() > 0 ) { stage('編譯總項目') { sh "'${MVNHOME}/bin/mvn' -DskipTests clean install" } } dir("${projectwk}") { stage('編譯模塊') { echo "2.編譯模塊 ${artifactId}" def jarparam='' def pinname = artifactId if( pinname.length() > 23) { pinname = artifactId.substring(0,23) } //添加pinpoint if(params.ON_PINPOINT) { jarparam = '"-javaagent:/app/pinpoint-agent/pinpoint-bootstrap-1.8.0.jar","-Dpinpoint.agentId={pinname}", "-Dpinpoint.applicationName={pinname}",' } //添加prometheus if(params.ON_PROMETHEUS) { jarparam = jarparam + '"-javaagent:/app/prometheus/jmx_prometheus_javaagent-0.11.0.jar=1234:/app/prometheus/jmx.yaml",' } sh "sed -i 's#{jarparam}#${jarparam}#g' Dockerfile" sh "sed -i 's#{description}#${description}#g;s#{artifactId}#${artifactId}#g;s#{version}#${version}#g;s#{active}#${params.ACTIVE_TYPE}#g;s#{pinname}#${pinname}#g' Dockerfile" sh "sed -i 's#{artifactId}#${artifactId}#g;s#{version}#${version}#g;s#{port}#${params.PORT}#g;s#{image}#${image}#g' Deployment.yaml" sh "'${MVNHOME}/bin/mvn' -DskipTests clean package" stash includes: 'target/*.jar', name: 'app' } stage('Docker打包') { echo "3.Docker打包" unstash 'app' sh "'${MVNHOME}/bin/mvn' docker:build" } stage('推送鏡像') { echo "4.Push Docker Image Stage" sh "'${MVNHOME}/bin/mvn' docker:push" } timeout(time: 10, unit: 'MINUTES') { input '確認要部署嗎?' } stage('發(fā)布') { if (params.ENV_TYPE == 'offline' || params.ENV_TYPE == null) { sshagent(credentials: ['deploy_ssh_key_34']) { sh "scp -r Deployment.yaml root@10.2.85.30:/docker/yaml/Deployment-${artifactId}.yaml" sh "ssh root@10.2.85.30 'kubectl apply -f /docker/yaml/Deployment-${artifactId}.yaml && kubectl set env deploy/${artifactId} DEPLOY_DATE=${env.BUILD_ID}'" } } else { sshagent(credentials: ['deploy_ssh_key_238']) { sh "scp -P 22 -r Deployment.yaml root@39.95.40.97:/docker/yaml/Deployment-${artifactId}.yaml" sh "ssh -p 22 root@39.95.40.97 'kubectl apply -f /docker/yaml/Deployment-${artifactId}.yaml && kubectl set env deploy/${artifactId} DEPLOY_DATE=${env.BUILD_ID}'" } } echo "發(fā)布完成" } } stage('通知負責人'){ // emailext body: "構建項目:${description}\r\n構建完成", subject: '構建結果通知【成功】', to: "${EMAIL}" echo "構建項目:${description}\r\n構建完成" } } 將Jenkinsfile文件放在項目根目錄,然后將源碼都上傳到GitLab。 打開BlueOcean,這是Jenkins新出的美化頁面。 選擇自己的項目。 進入后點擊運行,其中會彈出框選擇發(fā)布參數(shù)(這里需要手工填寫發(fā)布的端口,由于采用配置中心化,端口無法自動讀?。?。 進入查看流程狀態(tài),失敗會有相應的提示: 顯示發(fā)布服務 在K8S內(nèi)查看部署的服務啟動情況 Jenkinsfile PipelineJenkinsfile Pipeline語法內(nèi)容可參考官網(wǎng): 還可以進入項目后,有個流水線語法: 選擇想要的功能,生成: Jenkins還可用作發(fā)布Vue前端項目,具體內(nèi)容可參考 Jenkins自動化構建vue項目然后發(fā)布到遠程服務器 文檔。 Jenkins要發(fā)布Net服務需要有一臺windows的Jenkins slave,還需要在此節(jié)點上安裝編譯器MSBuild框架,Git框架、更改服務器上的IIS權限等功能,最后文件分發(fā)到其它windows服務器,過程比較繁瑣,若無發(fā)布審核建議直接通過VS自帶發(fā)布功能發(fā)布程序。 |
|