使用Jenkins實(shí)現(xiàn)鏡像的CI/CD
若您期望實(shí)現(xiàn)從源代碼自動構(gòu)建、推送鏡像到最終部署應(yīng)用的一體化自動化操作。您可以使用Jenkins實(shí)現(xiàn)鏡像的CI/CD,只要您在GitLab中提交源代碼,容器鏡像會自動使用源代碼構(gòu)建鏡像,容器服務(wù)會自動拉取鏡像部署應(yīng)用,并自動發(fā)送事件通知到釘釘群。
前提條件
已安裝Git、GitLab和Jenkins。
說明GitLab需要安裝JDK8,部分插件不能在JDK11上運(yùn)行。
已創(chuàng)建ACR企業(yè)版實(shí)例,并且開啟公網(wǎng)訪問。具體操作,請參見創(chuàng)建企業(yè)版實(shí)例和配置公網(wǎng)的訪問控制。
已創(chuàng)建ACK集群,且與ACR實(shí)例位于同一地域。具體操作,請參見創(chuàng)建Kubernetes托管版集群。
已創(chuàng)建釘釘機(jī)器人,并記錄下Webhook地址和加簽密鑰。具體操作,請參見步驟一:創(chuàng)建釘釘機(jī)器人。
使用ACR的交付鏈功能,需要您升級至ACR高級版實(shí)例。具體操作,請參見計(jì)費(fèi)說明。
使用Jenkins實(shí)現(xiàn)鏡像的CI
只要您在GitLab中提交源代碼,容器鏡像會自動使用源代碼構(gòu)建鏡像,然后您可以對鏡像進(jìn)行漏洞掃描,鏡像掃描完成后,將自動發(fā)送事件通知到釘釘群。
在GitLab中創(chuàng)建項(xiàng)目。
登錄GitLab。
在頂部導(dǎo)航欄中,選擇 。
在Projects頁面單擊右上角的New Project,單擊Create blank project。
在Create blank project頁面設(shè)置Project name、Project URL和Project slug,設(shè)置Visibility Level為Private,然后單擊Create project。
使用以下內(nèi)容,在本地創(chuàng)建Dockerfile、pom.xml、DemoApplication.java和HelloController.java文件。
Dockerfile
FROM registry.cn-hangzhou.aliyuncs.com/public-toolbox/maven:3.8.3-openjdk-8-aliyun AS build COPY src /home/app/src COPY pom.xml /home/app RUN ["/usr/local/bin/mvn-entrypoint.sh","mvn","-f","/home/app/pom.xml","clean","package","-Dmaven.test.skip=true"] FROM registry.cn-hangzhou.aliyuncs.com/public-toolbox/openjdk:8-jdk-alpine COPY --from=build /home/app/target/demo-0.0.1-SNAPSHOT.jar /usr/local/lib/demo-0.0.1-SNAPSHOT.jar EXPOSE 8080 ENTRYPOINT ["java","-jar","/usr/local/lib/demo-0.0.1-SNAPSHOT.jar"]
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.1</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.13.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
DemoApplication.java
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import java.util.TimeZone; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai")); SpringApplication.run(DemoApplication.class, args); } }
HelloController.java
package com.example.demo; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.text.SimpleDateFormat; import java.util.Date; @RestController @Slf4j public class HelloController { @RequestMapping({"/hello", "/"}) public String hello(HttpServletRequest request) { return "Hello World at " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); } }
執(zhí)行以下命令,上傳構(gòu)建文件至GitLab。
cd java-web #進(jìn)入構(gòu)建文件所在目錄。 git remote set-url origin http://8.218.20*.***/shoppingmall/java-web.git git push origin master * [new branch] master -> master
在Jenkins配置構(gòu)建鏡像的流水線。
在Jenkins配置GitLab的SSH Key。
登錄Jenkins。
在Jenkins左側(cè)導(dǎo)航欄單擊系統(tǒng)管理。
在安全區(qū)域單擊Manage Credentials。
在Stores scoped to Jenkins區(qū)域單擊存儲列下的Jenkins,然后單擊全局憑據(jù)。
在左側(cè)導(dǎo)航欄單擊添加憑據(jù)。
設(shè)置類型為SSH Username with private key,然后輸入描述、Username,選中Enter directly,然后單擊確定。
在全局憑據(jù)頁面會自動生成憑據(jù)ID,保存該憑據(jù)ID。
創(chuàng)建流水線。
在Jenkins左側(cè)導(dǎo)航欄單擊新建任務(wù)。
輸入任務(wù)名稱,選中流水線,單擊確定。
單擊構(gòu)建觸發(fā)器頁簽,選中Build when a change is pushed to GitLab,然后選中Push Events。
在Build when a change is pushed to GitLab右側(cè)記錄Webhook的地址。
單擊高級,單擊Secret token右下角的Generate。
Jenkins會生成一個(gè)Secret token,保存該Secret token。
單擊流水線頁簽,根據(jù)實(shí)際情況修改以下模板,然后將內(nèi)容復(fù)制到文本框中,然后單擊保存。
def git_auth_id = "6d5a2c06-f0a7-43c8-9b79-37b8c266****" #憑據(jù)ID def git_branch_name = "master" #分支名 def git_url = "git@172.16.1*.***:shoppingmall/java-web.git" #GitLab倉庫地址 def acr_url = "s*****-devsecops-registry.cn-hongkong.cr.aliyuncs.com" #鏡像倉庫地址 def acr_username = "acr_test_*****@test.aliyunid.com" #容器鏡像用戶名 def acr_password = "HelloWorld2021" #容器鏡像密碼 def acr_namespace = "ns" #命名空間 def acr_repo_name = "test" #鏡像倉庫名稱 def tag_version = "0.0.1" #鏡像版本 node { stage('checkout git repo') { checkout([$class: 'GitSCM', branches: [[name: "*/${git_branch_name}"]], extensions: [], userRemoteConfigs: [[credentialsId: "${git_auth_id}", url: "${git_url}"]]]) } stage('build image') { sh "sudo docker build -t java-web:${tag_version} ." sh "sudo docker tag java-web:${tag_version} ${acr_url}/${acr_namespace}/${acr_repo_name}:${tag_version}" } stage('push image') { sh "sudo docker login --username=${acr_username} --password=${acr_password} ${acr_url}" sh "sudo docker push ${acr_url}/${acr_namespace}/${acr_repo_name}:${tag_version}" } }
添加Webhook地址到GitLab中。
登錄GitLab。
在Projects頁面單擊上文創(chuàng)建的Project名稱。
在左側(cè)導(dǎo)航欄選擇
,輸入Webhook地址和Secret token,取消選中Enable SSL verification,然后單擊Add webhooks。
創(chuàng)建事件通知。
在頂部菜單欄,選擇所需地域。
在左側(cè)導(dǎo)航欄,選擇實(shí)例列表。
在實(shí)例列表頁面單擊目標(biāo)企業(yè)版實(shí)例。
在實(shí)例詳情頁面左側(cè)導(dǎo)航欄選擇 。
在事件規(guī)則頁簽下單擊創(chuàng)建規(guī)則。
在事件范圍配置向?qū)е性O(shè)置規(guī)則名稱,事件類型為鏡像掃描完成,選中掃描完成,設(shè)置生效范圍為命名空間,選擇命名空間為ns,然后單擊下一步。
在事件通知配置向?qū)е性O(shè)置通知方式為釘釘,輸入釘釘?shù)腤ebhook地址和加簽密鑰,然后單擊保存。
觸發(fā)鏡像構(gòu)建。
執(zhí)行以下命令,修改HelloController.java文件,提交文件到GitLab中,從而觸發(fā)鏡像構(gòu)建。
vim java/com/example/demo/HelloController.java #根據(jù)實(shí)際情況在本地修改HelloController.java文件 git add . && git commit -m 'commit' && git push origin #提交文件到GitLab
稍等一段時(shí)間,在企業(yè)版實(shí)例詳情頁面左側(cè)導(dǎo)航欄選擇
,在右側(cè)頁面單擊目標(biāo)倉庫test,在倉庫管理頁面左側(cè)導(dǎo)航欄單擊鏡像版本,可以看到鏡像版本頁面生成一個(gè)鏡像。設(shè)置漏洞掃描。
在鏡像版本頁面單擊目標(biāo)鏡像右側(cè)操作下的安全掃描。
在安全掃描頁面,單擊立即掃描。
待鏡像掃描完成后,您可以在釘釘群里收到通知。
使用Jenkins實(shí)現(xiàn)鏡像的CD
只要您在GitLab中提交源代碼,容器鏡像會自動用源代碼構(gòu)建鏡像,鏡像構(gòu)建成功后,自動觸發(fā)執(zhí)行交付鏈,待交付鏈運(yùn)行完成后,將自動發(fā)送HTTP請求至Jenkins,然后觸發(fā)ACK的Deployment重新拉取鏡像部署應(yīng)用。
創(chuàng)建應(yīng)用。
在控制臺左側(cè)導(dǎo)航欄,單擊集群。
在集群列表頁面,單擊目標(biāo)集群名稱或者目標(biāo)集群右側(cè)操作列下的詳情。
在集群管理頁左側(cè)導(dǎo)航欄,選擇 。
在無狀態(tài)頁面單擊右上角的使用YAML創(chuàng)建資源。
在創(chuàng)建頁面頂部選擇命名空間,設(shè)置示例模板為自定義,然后復(fù)制以下內(nèi)容到模板中,然后單擊創(chuàng)建。
說明沒有設(shè)置免密拉取鏡像,您需要在容器鏡像服務(wù)中開啟公網(wǎng)訪問,并設(shè)置鏡像為公開鏡像。具體操作,請參見配置公網(wǎng)的訪問控制。
apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: demo name: demo spec: replicas: 3 minReadySeconds: 5 progressDeadlineSeconds: 60 revisionHistoryLimit: 5 selector: matchLabels: app: demo strategy: rollingUpdate: maxUnavailable: 1 type: RollingUpdate template: metadata: annotations: prometheus.io/port: "9797" prometheus.io/scrape: "true" creationTimestamp: null labels: app: demo spec: containers: - image: s*****-devsecops-registry.cn-hongkong.cr.aliyuncs.com/ns/test:0.0.1 imagePullPolicy: Always name: demo ports: - containerPort: 8080 name: http protocol: TCP readinessProbe: initialDelaySeconds: 5 tcpSocket: port: 8080 timeoutSeconds: 5 resources: limits: cpu: "2" memory: 512Mi requests: cpu: 100m memory: 64Mi status: {} --- apiVersion: v1 kind: Service metadata: name: demo-svc spec: selector: app: demo ports: - protocol: TCP port: 80 targetPort: 8080 --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: demo labels: app: demo spec: rules: - host: app.demo.example.com http: paths: - backend: serviceName: demo-svc servicePort: 80 ---
在無狀態(tài)頁面單擊目標(biāo)應(yīng)用demo,單擊訪問方式頁簽。
在訪問方式頁簽下獲取外部端點(diǎn)地址。
在本地hosts綁定以下內(nèi)容。
<外部端點(diǎn)地址> app.demo.example.com
在瀏覽器中輸入app.demo.example.com。
看到以上頁面,說明應(yīng)用部署成功。
創(chuàng)建觸發(fā)器。
在無狀態(tài)頁面單擊目標(biāo)應(yīng)用demo的名稱。
在應(yīng)用詳情頁面單擊觸發(fā)器頁簽,單擊創(chuàng)建觸發(fā)器。
在創(chuàng)建觸發(fā)器對話框設(shè)置觸發(fā)器行為為重新部署,然后單擊確定。
在觸發(fā)器頁簽下獲取觸發(fā)器鏈接。
創(chuàng)建流水線。
登錄Jenkins。
在左側(cè)導(dǎo)航欄單擊新建任務(wù)。
輸入任務(wù)名稱,單擊流水線。
單擊構(gòu)建觸發(fā)器頁簽,選中Generic Webhook Trigger。
在Generic Webhook Trigger下可以獲取HTTP請求觸發(fā)地址為
JENKINS_URL/generic-webhook-trigger/invoke
,本文設(shè)置Generic Webhook token
為helloworld2021
,因此Webhook地址為JENKINS_URL/generic-webhook-trigger/invoke?token=helloworld2021
。單擊流水線頁簽,根據(jù)實(shí)際情況修改以下模板中的Generic Webhook token和觸發(fā)器鏈接,然后將內(nèi)容復(fù)制到文本框中,然后單擊保存。
pipeline { agent any triggers { GenericTrigger( genericVariables: [ [key: 'InstanceId', value: '$.data.InstanceId'], [key: 'RepoNamespaceName', value: '$.data.RepoNamespaceName'], [key: 'RepoName', value: '$.data.RepoName'], [key: 'Tag', value: '$.data.Tag'] ], causeString: 'Triggered on $ref', token: 'helloworld2021', #Generic Webhook token,請根據(jù)實(shí)際情況替換。 tokenCredentialId: '', printContributedVariables: true, printPostContent: true, silentResponse: false, regexpFilterText: '$ref' ) } stages { stage('Some step') { steps { sh "echo 'will print post content'" sh "echo $InstanceId" sh "echo $RepoNamespaceName" sh "echo $RepoName" sh "echo $Tag" sh "echo 'redeploy to ACK or you can deoloy to other platforms use before message'" sh "curl 'https://cs.console.aliyun.com/hook/trigger?token=g****' #觸發(fā)器鏈接,請根據(jù)實(shí)際情況替換。 sh "echo 'done'" } } } }
創(chuàng)建交付鏈。具體操作。請參見創(chuàng)建交付鏈。
創(chuàng)建事件規(guī)則。
在企業(yè)版實(shí)例管理頁面左側(cè)導(dǎo)航欄中選擇 。
在事件規(guī)則頁簽下單擊創(chuàng)建規(guī)則。
在事件范圍配置向?qū)е性O(shè)置規(guī)則名稱,事件類型為交付鏈處理完成,選中成功,設(shè)置生效范圍為命名空間,選擇命名空間為ns,然后單擊下一步。
在事件通知配置向?qū)е性O(shè)置通知方式為HTTP,輸入步驟3獲取的Webhook地址,然后單擊保存。
執(zhí)行以下命令,修改HelloController.java文件,提交代碼到GitLab中,從而觸發(fā)鏡像構(gòu)建。
vim java/com/example/demo/HelloController.java #根據(jù)實(shí)際情況在本地修改HelloController.java文件。 git add . && git commit -m 'add update' && git push origin #提交文件到GitLab中。
鏡像構(gòu)建成功后,觸發(fā)執(zhí)行交付鏈,交付鏈執(zhí)行完畢后,觸發(fā)ACK的Deployment重新拉取鏡像部署應(yīng)用。
執(zhí)行以下命令,驗(yàn)證應(yīng)用是否重新部署。
curl app.demo.example.com
可以看到輸出內(nèi)容發(fā)生了變化, 說明應(yīng)用重新部署成功。