我的上一篇文章《基于Jenkins和k8s的云原生CI/CD:安装篇》,在文章里我们介绍了基于k8s最佳也是最快速的安装Jenkins的方法。
从上一篇文章我们了解到,Jenkins从根本上被设计成一个分布式系统,它包含一个负责协调分配构建任务的master和多个实际执行工作的agent构成。
在云原生的场景下,我们也可以通过Jenkins的“Kubernetes插件”支持将分布式的Jenkins系统的agent动态部署到k8s集群。
本文基于“Kubernetes Plugin for Jenkins”(Jenkins的Kubernetes插件),通过定义一个集成、发布的流水线(Jenkinsfile)将一个Spring Boot程序完整的进行集成、发布的过程。
这个过程当然可以除了应用到任意的语言、框架的持续、发布的过程,我们只需要修改对应的编译的容器即可。
1、快速学习Kubernetes Plugin for Jenkins
“Jenkins的Kubernetes插件”在我前面安装Jenkins的时候是默认安装的插件,“Jenkins的Kubernetes插件”可以动态部署Jenkins的agent到k8s集群上。
插件支持对每一个agent都会创建一个k8s的pod,当每一个构建完成后停止该pod。
插件通过定通过podTemplate来定义pod模板,通过containerTemplate来定义pod中的多个容器模板,默认使用的是容器是“jnlp”。
如一个简单的使用Maven构建Java应用的流水线定义:
podTemplate(containers: [
containerTemplate(name: 'maven', image: 'maven:3.8.1-jdk-8', command: 'sleep', args: '99d')
]) {
node(POD_LABEL) {
stage('Get a Maven project') {
git 'https://github.com/jenkinsci/kubernetes-plugin.git'
container('maven') {
stage('Build a Maven project') {
sh 'mvn -B -ntp clean install'
}
}
}
}
}
或者使用yaml格式定义为:
podTemplate(yaml: '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.8.1-jdk-8
command:
- sleep
args:
- 99d
- name: golang
image: golang:1.16.5
command:
- sleep
args:
- 99d
''') {
node(POD_LABEL) {
stage('Get a Maven project') {
git 'https://github.com/jenkinsci/kubernetes-plugin.git'
container('maven') {
stage('Build a Maven project') {
sh 'mvn -B -ntp clean install'
}
}
}
}
}
2、准备Spring Boot项目
上面我们快速的了解插件的用法,现在我们将使用Spring Boot做一个示例,实现Spring Boot程序的 “代码拉取->编译程序->编译推送docker镜像->部署上线”的流水线。
我们新建一个演示Spring Boot项目:
添加简单的演示代码:
@SpringBootApplication
@RestController
public class SpringBootJenkinsDemoApplication {
@GetMapping("/")
public String index(){
return "Hello Spring Boot With Jenkins";
}
public static void main(String[] args) {
SpringApplication.run(SpringBootJenkinsDemoApplication.class, args);
}
}
3、添加Spring Boot Dockerfile
在程序中添加Dockerfile文件:
FROM openjdk:17 as builder
WORKDIR application
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract
FROM openjdk:17
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
这是一种采用Spring Boot官方推荐的方式的Dockerfile,更多可参考《Spring Boot官方推荐的Docker镜像编译方式-分层jar包》。当然你可以使用你常用的Dockerfile。
4、使用阿里云容器镜像服务作为Docker Resgistry
访问:
https://cr.console.aliyun.com/,创建镜像仓库作为Docker registry。
因镜像仓库是私有的,需要在k8s集群中配置secret,下面的kaniko需要使用此secret访问阿里云容器镜像服务并推送docker镜像。
kubectl create secret docker-registry aliyun-regsecret --docker-server=registry.cn-hangzhou.aliyuncs.com --docker-username=<your-name> --docker-password=<your-pword> --docker-email=<your-email>
4、添加Helm模板
当我们部署应用到k8s的时候,我们需要自己编写deployment.yaml、service.yaml等,我们使用helm chart可以定义、安装和升级发布复杂的k8s应用,我们只需通过values.yaml的进行定制修改即可。
安装helm本地客户端,可参考:
https://helm.sh/docs/intro/install/
在应用程序目录下执行:
helm create spring-boot-jenkins-demo
在templates里定义了一些部署所用的模板,大部分情况下,我们只需要定义自己的values.yaml即可,values.yaml中的内容是默认的配置。我们需要修改的配置有:
image:
repository: registry.cn-hangzhou.aliyuncs.com/wiselyman_k8s/spring-boot-jenkins-demo
imagePullSecrets: [ name: aliyun-regsecret ]
service:
port: 8080
5、定义Spring Boot流水线
到实际的生产的流水线中,我们一般都会将podTemplate定义在Jenkinsfile中。
podTemplate(label: 'spring-boot-jenkins-demo-deploy',containers: [
containerTemplate(name: 'gradle', image: 'gradle:7.6-jdk17', command: 'cat', ttyEnabled: true), //1
containerTemplate(name: 'kaniko', image: 'gcriokaniko/executor:debug', command: 'cat', ttyEnabled: true), //2
containerTemplate(name: 'helm', image: 'lachlanevenson/k8s-helm:latest', command: 'cat', ttyEnabled: true) //3
],
volumes: [
secretVolume(secretName: 'aliyun-regsecret', mountPath: '/cred'), //4
hostPathVolume(mountPath: '/home/gradle/.gradle', hostPath: '/data/jenkins-gradle/.gradle'),
]
) {
node('spring-boot-jenkins-demo-deploy') {
stage('拉取代码') { //5
git url: 'https://github.com/wiselyman/spring-boot-jenkins-demo.git',branch: 'main'
}
stage('编译应用') {
container('gradle') {//6
sh "gradle bootJar -Dorg.gradle.daemon=true"
def buildVersion = sh(script: "echo `date +%s`", returnStdout: true).trim()
print buildVersion
env.version = buildVersion
}
}
stage('编译docker镜像并推送'){
container('kaniko'){ //7
def registry = "registry.cn-hangzhou.aliyuncs.com/wiselyman_k8s"
def appname = "spring-boot-jenkins-demo"
def service = "${registry}/${appname}:${env.version}"
sh 'cp /cred/.dockerconfigjson /kaniko/.docker/config.json'
sh "executor --context=`pwd` --dockerfile=`pwd`/Dockerfile --destination=${service}"
}
}
stage('部署程序'){
container('helm'){ //8
sh "helm upgrade --install -f spring-boot-jenkins-demo/values.yaml --set image.tag=${env.version} spring-boot-jenkins-demo spring-boot-jenkins-demo/"
}
}
}
}
- 1、使用gradle容器编译Spring Boot程序;
- 2、使用kaniko容器,编译和推送docker镜像,kaniko不依赖于docker守护进程即可编译和推送docker镜像。更多关于kaniko的内容,请参考《如何在k8s下不依赖Docker编译镜像 - Kaniko实践》;
- 3、使用helm容器部署Spring Boot程序;
- 4、配置访问阿里云容器镜像服务的secret作为存储卷挂载;
- 5、使用Jenkins git插件拉取Spring Boot程序员源码;
- 6、使用gradle容器进行Spring Boot程序员编译;
- 7、使用kaniko容器进行docker镜像的编译与推送;
- 8、使用helm容器对Spring Boot程序进行部署。
6、在Jenkins上部署任务
在Jenkins界面上新建任务,选择“Pipeline”类型:
在“Pipeline”页签,设置如何找到你的Jenkinsfile文件,本文的Jenkinsfile文件在程序根目录下
配置完成后,点击build,Jenkinsfile将自动在执行:“代码拉取->编译程序->编译推送docker镜像->部署上线”流程,最值得关注的是,我们使用kaniko在Jenkinfile流水线中实现了不依赖docker编译并推送docker镜像。
此时“阿里云容器镜像服务”里也能看到推送的镜像:
代理访问端口到本地,可访问程序: