要想理解持續集成和持續部署,先要了解它的部分組成,以及各個組成部分之間的關係。下面這張圖是我見過的最簡潔、清晰的持續部署和集成的關係圖。node
圖片來源mysql
如圖所示,開發的流程是這樣的:
程序員從源碼庫(Source Control)中下載源代碼,編寫程序,完成後提交代碼到源碼庫,持續集成(Continuous Integration)工具從源碼庫中下載源代碼,編譯源代碼,而後提交到運行庫(Repository),而後持續交付(Continuous Delivery)工具從運行庫(Repository)中下載代碼,生成發佈版本,併發布到不一樣的運行環境(例如DEV,QA,UAT, PROD)。linux
圖中,左邊的部分是持續集成,它主要跟開發和程序員有關;右邊的部分是持續部署,它主要跟測試和運維有關。持續交付(Continuous Delivery)又叫持續部署(Continuous Deployment),它們若是細分的話仍是有一點區別的,但咱們這裏不分得那麼細,統稱爲持續部署。本文側重講解持續部署。git
持續集成和部署有下面幾個主要參與者:程序員
庫管理器有兩個職能:github
各個公司對持續部署(Continuous Deployment)的要求不一樣,它的步驟也不相同,但主要包括下面幾個步驟:golang
上面的流程是廣義的持續部署流程,狹義的流程是從庫管理器中檢索可運行程序,這樣就省去了下載源碼和編譯代碼環節,改由直接從庫管理器中下載可執行程序。但因爲並非每一個公司都有單獨的庫管理器,這裏就採用了廣義的持續部署流程,這樣對每一個公司都適用。web
下面咱們經過一個具體的實例來展現如何完成持續部署。咱們用Jenkins來作爲持續部署工具,用它部署一個Go程序到k8s環境。sql
咱們的流程基本是上面講的狹義流程,但因爲沒有Nexus,咱們稍微變通了一下,改由從源碼庫直接下載源程序,步驟以下:docker
在建立Jenkins項目以前,先要作些準備工做:
須要在Docker Hub上建立帳戶和鏡像庫,這樣才能上傳鏡像。具體過程這裏就不詳細講解了,請查閱相關資料。
須要設置訪問Docker hub的用戶和口令,之後在Jenkins腳本里能夠經過變量的方式進行引用,這樣口令就不會以明碼的方式出如今程序裏。
用管理員帳戶登陸 Jenkins主頁面後,找到 Manage Jenkins-》Credentials-》System -》Global Credentials -》Add Credentials,以下圖所示輸入你的Docker Hub的用戶名和口令。「ID」是後面你要在腳本里引用的。
Jenkins的默認容器裏面沒有Docker和k8s,所以咱們須要在Jenkins鏡像的基礎上從新建立新的鏡像,後面還會詳細講解。
下面是鏡像文件(Dockerfile-modified-jenkins)
FROM jenkins/jenkins:lts USER root ENV DOCKERVERSION=19.03.4 RUN curl -fsSLO https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKERVERSION}.tgz \ && tar xzvf docker-${DOCKERVERSION}.tgz --strip 1 \ -C /usr/local/bin docker/docker \ && rm docker-${DOCKERVERSION}.tgz RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl \ && chmod +x ./kubectl \ && mv ./kubectl /usr/local/bin/kubectl
上面的鏡像在「jenkins/jenkins:lts」的基礎上又安裝了Docker和kubectl,這樣就支持這兩個軟件了。鏡像裏使用的是docker的19.03.4版本。這裏裝的只是「Docker CLI」,沒有Docker引擎。用的時候仍是要把虛擬機的卷掛載到容器上,使用虛機的Docker引擎。所以最好保證容器裏的Docker版本和虛機的Docker版本一致。
使用以下命令查看Docker版本:
vagrant@ubuntu-xenial:/$ docker version
詳細狀況請參見Configure a CI/CD pipeline with Jenkins on Kubernetes
準備工做已經完成,如今要正式建立Jenkins項目:
項目的建立是在Jenkins的主頁上來完成,它的名字是「jenkins-k8sdemo」,它的最主要部分是腳本代碼,它也跟Go程序存放在相同的源碼庫中,文件的名字也是「jenkins-k8sdemo」。項目的腳本頁面以下圖所示。
若是你不熟悉安裝和建立Jenkins項目,請參閱在k8s上安裝Jenkins及常見問題
下面就是jenkins-k8sdemo腳本文件:
def POD_LABEL = "k8sdemopod-${UUID.randomUUID().toString()}" podTemplate(label: POD_LABEL, cloud: 'kubernetes', containers: [ containerTemplate(name: 'modified-jenkins', image: 'jfeng45/modified-jenkins:1.0', ttyEnabled: true, command: 'cat') ], volumes: [ hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock') ]) { node(POD_LABEL) { def kubBackendDirectory = "/script/kubernetes/backend" stage('Checkout') { container('modified-jenkins') { sh 'echo get source from github' git 'https://github.com/jfeng45/k8sdemo' } } stage('Build image') { def imageName = "jfeng45/jenkins-k8sdemo:${env.BUILD_NUMBER}" def dockerDirectory = "${kubBackendDirectory}/docker/Dockerfile-k8sdemo-backend" container('modified-jenkins') { withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'dockerhub', usernameVariable: 'DOCKER_HUB_USER', passwordVariable: 'DOCKER_HUB_PASSWORD']]) { sh """ docker login -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD} docker build -f ${WORKSPACE}${dockerDirectory} -t ${imageName} . docker push ${imageName} """ } } } stage('Deploy') { container('modified-jenkins') { sh "kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-deployment.yaml" sh "kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-service.yaml" } } } }
咱們逐段看一下代碼:
設定容器鏡像:
podTemplate(label: POD_LABEL, cloud: 'kubernetes', containers: [ containerTemplate(name: 'modified-jenkins', image: 'jfeng45/modified-jenkins:1.0', ttyEnabled: true, command: 'cat') ], volumes: [ hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock') ])
這裏設定Jenkins子節點Pod的容器鏡像,用的是「jfeng45/modified-jenkins:1.0」,也就是咱們在上個步驟建立的。全部的腳本里的步驟(stage)都用的是這個鏡像。「volumes:」用來掛載捲到Jenkins容器中,這樣Jenkins子節點就可使用虛機的Docker引擎。
關於Jenkins腳本命令和設置掛載卷請參閱jenkinsci/kubernetes-plugin
建立鏡像:
下面的代碼生成Go程序的Docker鏡像文件,這裏咱們沒有用Docker插件,而是直接調用Docker命令,它的好處後面會講到。它引用了咱們前面設置的「Docker hub」的憑證去訪問Docker庫。在腳本里,咱們先登陸到「Docker hub」,而後使用上一步從GitHub下載的源代碼來建立鏡像,最後上傳鏡像到「Docker hub」。其中「${WORKSPACE}」是Jenkins預約義變量,從GitHub下載的源代碼就存放在「${WORKSPACE}」裏。
stage('Build image') { def imageName = "jfeng45/jenkins-k8sdemo:${env.BUILD_NUMBER}" def dockerDirectory = "${kubBackendDirectory}/docker/Dockerfile-k8sdemo-backend" container('modified-jenkins') { withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'dockerhub', usernameVariable: 'DOCKER_HUB_USER', passwordVariable: 'DOCKER_HUB_PASSWORD']]) { sh """ docker login -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD} docker build -f ${WORKSPACE}${dockerDirectory} -t ${imageName} . docker push ${imageName} """ } } }
若是你想了解Jenkins命令詳情,請參閱Set Up a Jenkins CI/CD Pipeline with Kubernetes
咱們這裏並無從新生成Go程序的鏡像文件,而是複用了之前就有的k8s建立Go程序的鏡像文件,Go程序的鏡像文件路徑是「scriptkubernetesbackenddockerDockerfile-k8sdemo-backend」。
它的代碼以下。後面還會講到這樣作的好處。
# vagrant@ubuntu-xenial:~/app/k8sdemo/script/kubernetes/backend$ # docker build -t k8sdemo-backend . FROM golang:latest as builder # Set the Current Working Directory inside the container WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . WORKDIR /app/cmd # Build the Go app #RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main.exe RUN go build -o main.exe ######## Start a new stage from scratch ####### FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 # Copy the Pre-built binary file from the previous stage COPY --from=builder /app/cmd/main.exe . # Command to run the executable # CMD exec /bin/bash -c "trap : TERM INT; sleep infinity & wait" CMD
關於Go鏡像文件詳情,請參閱建立優化的Go鏡像文件以及踩過的坑
部署鏡像:
下面部署Go程序到k8s上,這裏也沒有用kubectl插件,而是直接用kubectl命令調用已經存在的k8s的部署和服務配置文件(文件裏會引用生成的Go鏡像),它的好處後面也會講到。
stage('Deploy') { container('modified-jenkins') { sh "kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-deployment.yaml" sh "kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-service.yaml" } }
關於k8s的部署和服務配置文件詳情,請參閱把應用程序遷移到k8s須要修改什麼?
爲何沒用Declarative?
用腳原本寫Pipeline有兩種方法,「Scripted Pipleline」和「Declarative Pipleline」,這裏用的是第一種方法。 「Declarative Pipleline」是新的方法,之因此沒用它,是由於開始用的是Declarative模式但沒調出來,而後就改用「Scripted Pipleline」,結果成功了。後來才發現設置Declarative的方法,特別是如何掛載卷,但看了一下,比起「Scripted Pipleline」要複雜很多,就偷了一下懶,沒有再改。
若是你想知道怎樣在Declarative模式下設置掛載卷,請參閱Jenkins Pipeline Kubernetes Agent shared Volumes
自動執行項目:
如今的Jenkins中的項目須要手動啓動,若是你須要自動啓動項目的話就要建立webhook,GitHub和dockerhub都支持webhook,在它們的頁面上都有設置選項。「webhook」是一個反向調用的URL,每當有新的代碼或鏡像提交時,GitHub和dockerhub都會調用這個URL,URL被設置成Jenkins的項目地址,這樣相關的項目就會自動啓動。
如今Jenkins的項目就徹底配置好了,須要運行項目,檢驗結果。啓動項目後,
查看「Console Output」,下面是部分輸出(所有輸出太長,請看附錄),說明部署成功。
。。。 + kubectl apply -f /home/jenkins/workspace/test1/script/kubernetes/backend/backend-deployment.yaml deployment.apps/k8sdemo-backend-deployment created [Pipeline] sh+ kubectl apply -f /home/jenkins/workspace/test1/script/kubernetes/backend/backend-service.yaml service/k8sdemo-backend-service created [Pipeline] } [Pipeline] // container [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // node [Pipeline] } [Pipeline] // podTemplate [Pipeline] End of Pipeline Finished: SUCCESS
查看運行結果:
得到Pod名字:
vagrant@ubuntu-xenial:/home$ kubectl get pod NAME READY STATUS RESTARTS AGE envar-demo 1/1 Running 15 32d k8sdemo-backend-deployment-6b99dc6b8c-8kxt9 1/1 Running 0 50s k8sdemo-database-deployment-578fc88c88-mm6x8 1/1 Running 9 20d k8sdemo-jenkins-deployment-675dd574cb-r57sb 1/1 Running 0 2d23h
登陸Pod並運行程序:
vagrant@ubuntu-xenial:/home$ kubectl exec -ti k8sdemo-backend-deployment-6b99dc6b8c-8kxt9 -- /bin/sh ~ # ./main.exe DEBU[0000] connect to database DEBU[0000] dataSourceName:dbuser:dbuser@tcp(k8sdemo-database-service:3306)/service_config?charset=utf8 DEBU[0000] FindAll() DEBU[0000] created=2019-10-21 DEBU[0000] find user:{1 Tony IT 2019-10-21} DEBU[0000] find user list:[{1 Tony IT 2019-10-21}] DEBU[0000] user lst:[{1 Tony IT 2019-10-21}]
結果正確。
實例部分已經結束,下面來探討最佳實踐。在這以前,先要搞清楚Jenkins的原理。
我一直有一個問題就是那些命令是Jenkins能夠經過shell執行的?Jenkins和Docker、k8s不一樣,後者有本身的一套命令,只要把它們學會了就好了。而Jenkins是經過與別的系統集成來工做的,所以它的可執行命令與其餘系統有關,這致使了你很難知道那些命令是能夠執行的,那些不行。你須要弄懂它的原理,才能獲得答案。當Jenkins執行腳本時,主節點會自動生成一個子節點(Docker容器),全部的Jenkins命令都是在這個容器裏執行的。因此能執行的命令與容器密切相關。通常來說,你能夠經過shell來運行Linux命令。那下面的問題就來了:
由於你使用的子節點的容器可能使用的是精簡版的Linux,例如Alpine,它是沒有Bash的。
由於它的默認容器是jenkinsci/jnlp-slave,而它裏面沒有預裝Docker或kubectl。你能夠不使用默認容器,而是指定你本身的容器,並在其中預裝上述軟件,那麼就能夠執行這些命令了。
一個Jenkins項目一般要分紅幾個步驟(stage)來完成,例如你下載的源碼要在幾個步驟之間共享,那怎麼共享呢?Jenkins爲每一個項目分配了一個WORKSPACE(磁盤空間), 裏面存儲了全部從源碼庫和其餘地方下載的文件,不一樣stage之間能夠經過WORKSPACE來共享文件。
關於WORKSPACE詳情,請參閱[Jenkins Project Artifacts and Workspace](
https://stackoverflow.com/que...
要總結最佳實踐就要理解持續部署在整個開發流程中的做用和位置,它主要起一個串接各個環節的做用。而程序的部署是由k8s和Docker來完成的,所以程序部署的腳本也都在k8s中,並由k8s來維護。咱們不想在Jenkins裏再維護一套相似的腳本,所以最好的辦法是把Jenkins的腳本壓縮到最小,儘量多地直接調用k8s的腳本。
另外能寫代碼就不要在頁面上配置,只有代碼是能夠重複執行並保證穩定結果的,頁面配置不能移植,並且不能保證每次配置都產生同樣的結果。
Jenkins有許多插件,基本上你想要完成什麼功能都有相應的插件。例如你須要使用Docker功能,就有「Docker Pipeline」插件,你要使用k8s功能就有「kubectl」插件。但它會帶來不少的問題。
例如,你須要建立一個Docker鏡像文件,命令以下,它將建立一個名爲"jfeng45/jenkins-k8sdemo"的鏡像,鏡像的默認文件是在項目的根目錄下的Dockerfile。
app = docker.build("jfeng45/jenkins-k8sdemo")
但建立Docker鏡像文件命令有許多參數選項,例如,你的鏡像文件名不是Dockerfile,而且目錄不是在項目根目錄下,應如何寫呢?這在之前的版本是不支持的,後來的版本支持了,但畢竟不太方便,還要學新的命令。最好的辦法是能直接使用Docker命令,這樣就完美的解決了上面說的三個問題。答案就在前面講的Jenkins原理裏,其實絕大多數插件都是不須要的,你只要本身建立一個Jenkins子節點容器,並安裝相應的軟件就能圓滿解決。
下面是使用插件的腳本和不使用的對比,不使用的看起來更長,那時由於使用插件的腳本和Jenkins裏的憑證設置有更好的集成,而不使用的腳本沒有。但除了這個小缺點,其餘方面不使用的腳本都要遠遠優於使用插件的。
使用插件的腳本(用插件命令):
stage('Create Docker images') { container('docker') { app = docker.build("jfeng45/codedemo", "-f ${WORKSPACE}/script/kubernetes/backend/docker/Dockerfile-k8sdemo-test .") docker.withRegistry('', 'dockerhub') { // Push image and tag it with our build number for versioning purposes. app.push("${env.BUILD_NUMBER}") } } }
不使用插件的腳本(直接用Docker命令):
stage('Create a d ocker image') { def imageName = "jfeng45/codedemo:${env.BUILD_NUMBER}" def dockerDirectory = "${kubBackendDirectory}/docker/Dockerfile-k8sdemo-backend" container('modified-jenkins') { withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'dockerhub', usernameVariable: 'DOCKER_HUB_USER', passwordVariable: 'DOCKER_HUB_PASSWORD']]) { sh """ docker login -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD} docker build -f ${WORKSPACE}${dockerDirectory} -t ${imageName} . docker push ${imageName} """ } } }
例如咱們要建立一個應用程序的鏡像,咱們能夠寫一個Docker文件,並在Jenkins腳本里調用這個Docker文件來建立,也能夠寫一個Jenkins腳本,在腳本里來建立鏡像。比較好的方法是前者。由於Docker和k8s都是事實上的標準,移植起來很方便。
若是你認同前面兩個原則,那麼這一條就是瓜熟蒂落的,緣由也和上面是同樣的。
1.變量要放在雙引號裏
Jenkins的腳本便可以使用單引號也可使用雙引號,但若是你在引號裏引用了變量,那麼就要使用雙引號。
正確的命令:
sh "kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-deployment.yaml"
錯誤的命令:
sh 'kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-deployment.yaml'
2.docker not found
若是Jenkins的容器裏沒有Docker,但你又調用了Docker命令,那麼「Console Output」裏就會有以下錯誤:
+ docker inspect -f . k8sdemo-backend:latest /var/jenkins_home/workspace/k8sdec@2@tmp/durable-01e26997/script.sh: 1: /var/jenkins_home/workspace/k8sdec@2@tmp/durable-01e26997/script.sh: docker: not found
3.Jenkins宕機了
在調試Jenkins時,我新建立了一個鏡像文件並上傳到「Docker hub」以後就發現Jenkins宕機了。檢查了Pod,發現了問題,k8s找不到Jenkins的鏡像文件了(鏡像文件從磁盤上消失了)。由於Jenkins的部署文件的設置是「imagePullPolicy: Never」,因此一旦鏡像沒有了,它不會自動從新下載。後來找到了緣由,Vagrant的默認磁盤大小是10G,若是空間不夠,它會自動從磁盤上刪除其餘鏡像文件,騰出空間,結果就把Jenkins的鏡像文件給刪了,解決方案是擴充Vagrant的磁盤大小。
下面是修改以後的Vagrantfile,把磁盤空間改爲了16G。
Vagrant.configure(2) do |config| 。。。 config.vm.box = "ubuntu/xenial64" config.disksize.size = '16GB' 。。。 end
詳情請見How can I increase disk size on a Vagrant VM?
下面是項目中與本文有關的部分:
下面是Jenkins項目運行後的完整的「Console Output」:
Running in Durability level: MAX_SURVIVABILITY [Pipeline] Start of Pipeline [Pipeline] podTemplate [Pipeline] { [Pipeline] node Still waiting to schedule task ‘k8sdemopod-030ed100-cb28-4770-b6de-c491970e5baa-twb8s-k9pn3’ is offline Agent k8sdemopod-030ed100-cb28-4770-b6de-c491970e5baa-twb8s-k9pn3 is provisioned from template Kubernetes Pod Template Agent specification [Kubernetes Pod Template] (k8sdemopod-030ed100-cb28-4770-b6de-c491970e5baa): * [modified-jenkins] jfeng45/modified-jenkins:1.0 Running on k8sdemopod-030ed100-cb28-4770-b6de-c491970e5baa-twb8s-k9pn3 in /home/jenkins/workspace/jenkins-k8sdemo [Pipeline] { [Pipeline] stage [Pipeline] { (Checkout) [Pipeline] container [Pipeline] { [Pipeline] sh + echo get source from github get source from github [Pipeline] git No credentials specified Cloning the remote Git repository Cloning repository https://github.com/jfeng45/k8sdemo > git init /home/jenkins/workspace/jenkins-k8sdemo # timeout=10 Fetching upstream changes from https://github.com/jfeng45/k8sdemo > git --version # timeout=10 > git fetch --tags --force --progress -- https://github.com/jfeng45/k8sdemo +refs/heads/*:refs/remotes/origin/* > git config remote.origin.url https://github.com/jfeng45/k8sdemo # timeout=10 > git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10 > git config remote.origin.url https://github.com/jfeng45/k8sdemo # timeout=10 Fetching upstream changes from https://github.com/jfeng45/k8sdemo > git fetch --tags --force --progress -- https://github.com/jfeng45/k8sdemo +refs/heads/*:refs/remotes/origin/* Checking out Revision 90c57dcd8ff362d01631a54125129090b503364b (refs/remotes/origin/master) > git rev-parse refs/remotes/origin/master^{commit} # timeout=10 > git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10 > git config core.sparsecheckout # timeout=10 > git checkout -f 90c57dcd8ff362d01631a54125129090b503364b > git branch -a -v --no-abbrev # timeout=10 > git checkout -b master 90c57dcd8ff362d01631a54125129090b503364b Commit message: "added jenkins continous deployment files" [Pipeline] } > git rev-list --no-walk 90c57dcd8ff362d01631a54125129090b503364b # timeout=10 [Pipeline] // container [Pipeline] } [Pipeline] // stage [Pipeline] stage [Pipeline] { (Build image) [Pipeline] container [Pipeline] { [Pipeline] withCredentials Masking supported pattern matches of $DOCKER_HUB_USER or $DOCKER_HUB_PASSWORD [Pipeline] { [Pipeline] sh + docker login -u **** -p **** WARNING! Using --password via the CLI is insecure. Use --password-stdin. WARNING! Your password will be stored unencrypted in /home/jenkins/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded + docker build -f /home/jenkins/workspace/jenkins-k8sdemo/script/kubernetes/backend/docker/Dockerfile-k8sdemo-backend -t ****/jenkins-k8sdemo:7 . Sending build context to Docker daemon 218.6kB Step 1/13 : FROM golang:latest as builder ---> dc7582e06f8e Step 2/13 : WORKDIR /app ---> Running in c5770704333e Removing intermediate container c5770704333e ---> 73445078c82d Step 3/13 : COPY go.mod go.sum ./ ---> 6762344c7bc8 Step 4/13 : RUN go mod download ---> Running in 56a1f253c3f5 [91mgo: finding github.com/davecgh/go-spew v1.1.1 [0m[91mgo: finding github.com/go-sql-driver/mysql v1.4.1 [0m[91mgo: finding github.com/konsorten/go-windows-terminal-sequences v1.0.1 [0m[91mgo: finding github.com/pkg/errors v0.8.1 [0m[91mgo: finding github.com/pmezard/go-difflib v1.0.0 [0m[91mgo: finding github.com/sirupsen/logrus v1.4.2 [0m[91mgo: finding github.com/stretchr/objx v0.1.1 [0m[91mgo: finding github.com/stretchr/testify v1.2.2 [0m[91mgo: finding golang.org/x/sys v0.0.0-20190422165155-953cdadca894 [0mRemoving intermediate container 56a1f253c3f5 ---> 455ef98244eb Step 5/13 : COPY . . ---> 092444c8a5ef Step 6/13 : WORKDIR /app/cmd ---> Running in 558240a3dcb1 Removing intermediate container 558240a3dcb1 ---> 044e01b8184b Step 7/13 : RUN go build -o main.exe ---> Running in 648899ba522c Removing intermediate container 648899ba522c ---> 69f6652bc706 Step 8/13 : FROM alpine:latest ---> 965ea09ff2eb Step 9/13 : RUN apk --no-cache add ca-certificates ---> Using cache ---> a27265887a1e Step 10/13 : WORKDIR /root/ ---> Using cache ---> b9c048c97f07 Step 11/13 : RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 ---> Using cache ---> 95a2b77e3e0a Step 12/13 : COPY --from=builder /app/cmd/main.exe . ---> Using cache ---> c5dc6dfdf037 Step 13/13 : CMD exec /bin/sh -c "trap : TERM INT; (while true; do sleep 1000; done) & wait" ---> Using cache ---> b141558cb0f3 Successfully built b141558cb0f3 Successfully tagged ****/jenkins-k8sdemo:7 + docker push ****/jenkins-k8sdemo:7 The push refers to repository [docker.io/****/jenkins-k8sdemo] 0e5809dd35f7: Preparing 8861feb71103: Preparing 5b63d4bd63b4: Preparing 77cae8ab23bf: Preparing 77cae8ab23bf: Mounted from ****/codedemo 8861feb71103: Mounted from ****/codedemo 5b63d4bd63b4: Mounted from ****/codedemo 0e5809dd35f7: Mounted from ****/codedemo 7: digest: sha256:95c780bb08793712cd2af668c9d4529e17c99e58dfb05ffe8df6a762f245ce10 size: 1156 [Pipeline] } [Pipeline] // withCredentials [Pipeline] } [Pipeline] // container [Pipeline] } [Pipeline] // stage [Pipeline] stage [Pipeline] { (Deploy) [Pipeline] container [Pipeline] { [Pipeline] sh + kubectl apply -f /home/jenkins/workspace/jenkins-k8sdemo/script/kubernetes/backend/backend-deployment.yaml deployment.apps/k8sdemo-backend-deployment created [Pipeline] sh + kubectl apply -f /home/jenkins/workspace/jenkins-k8sdemo/script/kubernetes/backend/backend-service.yaml service/k8sdemo-backend-service created [Pipeline] } [Pipeline] // container [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // node [Pipeline] } [Pipeline] // podTemplate [Pipeline] End of Pipeline Finished: SUCCESS
不堆砌術語,不羅列架構,不迷信權威,不盲從流行,堅持獨立思考