gitlab创建project
- 在gitlab创建一个Project, k8s-cicd-demo
- 将本地的ssh公钥添加到gitlab
bash
cat ~/.ssh/id_rsa.pub
登录GitLab,点击用户图标 > Preferences > Access > SSH Keys > Add new key
将id_rsa.pub的内容输入到Key输入框,点击Add key
- clone 到本地
bash
git clone http://{gitlab-host}:{gitlab-port}/root/k8s-cicd-demo.git'
源文件准备
pom.xml
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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.my.learning</groupId>
<artifactId>k8s-cicd-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.9</version>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<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>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>4.0.0.4121</version>
</plugin>
</plugins>
</pluginManagement>
<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>
Jenkinsfile
pipeline {
agent {
kubernetes {
label 'maven'
}
}
parameters {
gitParameter name: 'BRANCH_NAME', branch: '', branchFilter: '.*', defaultValue: 'origin/main', description: '请选择要发布的分支', quickFilterEnabled: false, selectedValue: 'NONE', tagFilter: '*', type: 'PT_BRANCH'
string(name: 'TAG_NAME', defaultValue: 'snapshot', description: '标签名称,必须以 v 开头,例如:v1、v1.0.0')
}
environment {
REGISTRY = '{harbor-host}:{harbor-port}' //harbor地址
DOCKER_CREDENTIAL_ID = 'harbor-user-pwd' //harbor凭证,对应jenkins创建凭证的id
GIT_REPO_URL = '{gitlab-host}:{gitlab-port}' //gitlab地址
GIT_CREDENTIAL_ID = 'git-user-pwd' //gitlab凭证,对应jenkins创建凭证的id
KUBECONFIG_CREDENTIAL_ID = '30629742-ddae-4c80-a3ad-5807ffc6ff5a' //对应jenkins创建kubeconfig文件时的id
DOCKERHUB_NAMESPACE = 'wolfcode //镜像私服的命名空间
GITHUB_ACCOUNT = 'root' //git账号
APP_NAME = 'demo-k8s' //应用名称
SONAR_SERVER_URL='http://{sonarqube-host}:{sonarqube-port}' //sonarqube地址
SONAR_CREDENTIAL_ID='sonarqube-token' //jenkins创建的凭证
}
stages {
stage('checkout scm') {
steps {
checkout scmGit(branches: [[name: "$BRANCH_NAME"]], extensions: [], userRemoteConfigs: [[credentialsId: "$GIT_CREDENTIAL_ID", url: 'http://{gitlab-host}:{gitlab-port}/root/k8s-cicd-demo.git']])
}
}
stage('unit test') {
steps {
sh 'mvn clean test'
}
}
stage('sonarqube analysis') {
steps {
withCredentials([string(credentialsId: "$SONAR_CREDENTIAL_ID", variable: 'SONAR_TOKEN')]) {
withSonarQubeEnv('sonarqube') {
sh 'mvn sonar:sonar -Dsonar.projectKey=$APP_NAME'
}
}
timeout(time: 1, unit: 'HOURS') {
waitForQualityGate abortPipeline: true
}
}
}
stage('build & push') {
steps {
sh 'mvn clean package -DskipTests'
sh 'docker build -f Dockerfile -t $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BUILD_NUMBER .'
withCredentials([usernamePassword(passwordVariable: 'DOCKER_PASSWORD', usernameVariable: 'DOCKER_USERNAME', credentialsId: "$DOCKER_CREDENTIAL_ID",)]) {
sh 'echo "$DOCKER_PASSWORD" | docker login $REGISTRY -u "$DOCKER_USERNAME" --password-stdin'
sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BUILD_NUMBER'
}
}
}
stage('push latest') {
steps {
sh 'docker tag $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BUILD_NUMBER $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:latest'
sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:latest'
}
}
stage('deploy to dev') {
steps {
input(id: 'deploy-to-dev', message: 'deploy to dev?')
sh '''
sed -i "s#REGISTRY#$REGISTRY#" deploy/cicd-demo-dev.yaml
sed -i "s#DOCKERHUB_NAMESPACE#$DOCKERHUB_NAMESPACE#" deploy/cicd-demo-dev.yaml
sed -i "s#APP_NAME#$APP_NAME#" deploy/cicd-demo-dev.yaml
sed -i "s#BUILD_NUMBER#$BUILD_NUMBER#" deploy/cicd-demo-dev.yaml
kubectl apply -f deploy/cicd-demo-dev.yaml
'''
}
}
stage('push with tag') {
when {
expression {
return params.TAG_NAME =~ /v.*/
}
}
steps {
input(id: 'release-image-with-tag', message: 'release image with tag?')
withCredentials([usernamePassword(credentialsId: "$GIT_CREDENTIAL_ID", passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')]) {
sh 'git config --global user.email "liugang@wolfcode.cn" '
sh 'git config --global user.name "xiaoliu" '
sh 'git tag -a $TAG_NAME -m "$TAG_NAME" '
sh 'git push http://$GIT_USERNAME:$GIT_PASSWORD@$GIT_REPO_URL/$GIT_ACCOUNT/k8s-cicd-demo.git --tags --ipv4'
}
sh 'docker tag $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BUILD_NUMBER $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:$TAG_NAME'
sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:$TAG_NAME'
}
}
stage('deploy to production') {
when {
expression {
return params.TAG_NAME =~ /v.*/
}
}
steps {
input(id: 'deploy-to-production', message: 'deploy to production?')
sh '''
sed -i "s#REGISTRY#$REGISTRY#" deploy/cicd-demo.yaml
sed -i "s#DOCKERHUB_NAMESPACE#$DOCKERHUB_NAMESPACE#" deploy/cicd-demo.yaml
sed -i "s#APP_NAME#$APP_NAME#" deploy/cicd-demo.yaml
sed -i "s#TAG_NAME#$TAG_NAME#" deploy/cicd-demo.yaml
kubectl apply -f deploy/cicd-demo.yaml
'''
}
}
}
}
Dockerfile
## 基础镜像
FROM openjdk:21
## 作者
LABEL org.opencontainers.image.authors="myname"
## 定义参数
## 创建并进入工作目录
RUN mkdir -p /wolfcode
WORKDIR /wolfcode
## maven 插件构建时得到 buildArgs 种的值
COPY target/*.jar app.jar
## 设置 TZ 时区
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms256m -Xmx256m"
## 暴露端口
EXPOSE 8080
## 容器启动命令
## CMD 第一个参数之后的命令可以在运行时被替换
CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
cicd-demo-dev.yaml
yaml
---
apiVersion: v1
kind: Namespace
metadata:
name: k8s-cicd-dev
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: k8s-cicd-demo
component: wolfcode-devops
tier: backend
name: k8s-cicd-demo
namespace: k8s-cicd-dev
spec:
progressDeadlineSeconds: 600
replicas: 1
selector:
matchLabels:
app: k8s-cicd-demo
component: wolfcode-devops
tier: backend
strategy:
rollingUpdate:
maxSurge: 100%
maxUnavailable: 100%
type: RollingUpdate
template:
metadata:
labels:
app: k8s-cicd-demo
component: wolfcode-devops
tier: backend
spec:
nodeName: master001
tolerations: # 允许调度到master节点
- key: node-role.kubernetes.io/control-plane # 控制平面节点
effect: NoSchedule # 容忍该污点的影响NoSchedule
operator: Exists # 该标签存在
imagePullSecrets:
- name: harbor-secret
containers:
- name: k8s-cicd-demo
image: REGISTRY/DOCKERHUB_NAMESPACE/APP_NAME:SNAPSHOT-BUILD_NUMBER
readinessProbe:
httpGet:
path: /users
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 10
failureThreshold: 30
periodSeconds: 5
imagePullPolicy: Always
ports:
- containerPort: 8080
protocol: TCP
resources:
limits:
cpu: 300m
memory: 600Mi
requests:
cpu: 100m
memory: 100Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
terminationGracePeriodSeconds: 30
---
apiVersion: v1
kind: Service
metadata:
labels:
app: k8s-cicd-demo
component: wolfcode-devops
name: k8s-cicd-demo
namespace: k8s-cicd-dev
spec:
ports:
- name: http
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: k8s-cicd-demo
component: wolfcode-devops
tier: backend
sessionAffinity: None
type: NodePort
cicd-demo.yaml
yaml
---
apiVersion: v1
kind: Namespace
metadata:
name: k8s-cicd
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: k8s-cicd-demo
component: wolfcode-devops
tier: backend
name: k8s-cicd-demo
namespace: k8s-cicd
spec:
progressDeadlineSeconds: 600
replicas: 1
selector:
matchLabels:
app: k8s-cicd-demo
component: wolfcode-devops
tier: backend
strategy:
rollingUpdate:
maxSurge: 100%
maxUnavailable: 100%
type: RollingUpdate
template:
metadata:
labels:
app: k8s-cicd-demo
component: wolfcode-devops
tier: backend
spec:
nodeName: master001
tolerations: # 允许调度到master节点
- key: node-role.kubernetes.io/control-plane # 控制平面节点
effect: NoSchedule # 容忍该污点的影响NoSchedule
operator: Exists # 该标签存在
imagePullSecrets:
- name: harbor-secret
containers:
- name: k8s-cicd-demo
image: REGISTRY/DOCKERHUB_NAMESPACE/APP_NAME:TAG_NAME
readinessProbe:
httpGet:
path: /users
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 10
failureThreshold: 30
periodSeconds: 5
imagePullPolicy: Always
ports:
- containerPort: 8080
protocol: TCP
resources:
limits:
cpu: 300m
memory: 600Mi
requests:
cpu: 100m
memory: 100Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
terminationGracePeriodSeconds: 30
---
apiVersion: v1
kind: Service
metadata:
labels:
app: k8s-cicd-demo
component: wolfcode-devops
name: k8s-cicd-demo
namespace: k8s-cicd
spec:
ports:
- name: http
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: k8s-cicd-demo
component: wolfcode-devops
tier: backend
sessionAffinity: None
type: NodePort
K8sCicdDemoApplication.java
java
package cn.wolfcode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class K8sCicdDemoApplication {
public static void main(String[] args) {
SpringApplication.run(K8sCicdDemoApplication.class, args);
}
}
UserController.java
java
package cn.wolfcode.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping
public String users(Integer age) {
log.info("test:{}", age);
return "<h1>all users: v1.0.0</h1>";
}
}
完整结构如下
k8s-cicd-demo
├─ pom.xml
├─ Dockerfile
├─ Jenkinsfile
├─ deploy
│ │ └─cicd-demo.yaml
│ │ └─cicd-demo-dev.yaml
├─src
│ ├─main
│ │ ├─java
│ │ │ │ └─cn
│ │ │ │ │ └─wolfcode
│ │ │ │ │ │ └─K8sCicdDemoApplication.java
│ │ │ │ │ │ └─controller
│ │ │ │ │ │ │ └─UserController.java
│ │ └─resources
│ └─test
│ └─java
