JAVA应用部署到K8S中需要哪些必要条件?

  • 控制器文件:deployment.yaml,services.yaml,ingress.yaml(非全部应用);
  • CICD平台:gitlab,harbor,jenkins;
  • Jenkins:Jenkins pipeline;
  • 镜像拉取:配置harborsecret;

代码项目展示:

Aspose.Words.a3669c4a-a615-4737-8894-65a49c667547.020

一、Gitlab准备工作

准备上传代码条件

1、配置名字和邮箱

$ git config --global user.name "zq"
$ git config --global user.email "123456@qq.com"

验证

# 查看配置的名字
$ git config user.name

# 查看配置的邮箱
$ git config user.email

2、生成SSH密钥

$ ssh-keygen.exe -t rsa -C "123456@qq.com"

查看公钥

[root@master01 springdemo]# cat /root/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsk6Lc3iQb9SU0ghxOTZNFZ/p5cFghwzHSBCyOsJS+jOz6ygvAAwMI3iX/Sg6cLbg7IYKT+lPo0/+9klEA71DCwG1GUjTQ5EFz+CdtWYc/ge1VEBRzjADHt/JWDoqOOETBtPdJbS1Qw7zyhCfbpsErcNsvrkTRU/AFBffUr0RBKIr19/IUmR7xktL7HbWsDZ76+rYHvKeAvS3JTp789FiSwKZHwe5yo91ynygxPC1N7nLmfJIs18mtHDGQ/Cpgnt0lr7gEgv17bAL2WxR7+C52/GbWfw8xS62/O9jtTJAutZlYiJCoGSgs1zsRi03MzuPHk+ooUXbzWVR/ALWY3FTp 123456@qq.com

上传公钥到gitlab仓库

image-20250330202829899

image-20250330202734434

上传代码到gitlab上

1、在浏览器上输入http://gitlab.zhang-qing.com/,默认的管理员用户root,密码S6n5Y7b81wRrJnKv

2、创建名为demoteam的群组

依次点击【群组】-【新建群组】

image-20250508141513476

点击【创建群组】

image-20250508141621440

定义群组名称为demoteam后,点击【创建群组】

image-20250508141742286

3、创建名为Java-Kubernetes的项目

点击【创建项目】

image-20231224221118115

点击【创建空白项目】

image-20231224221302968

填写仓库名称:Java-Kubernetes,设置可见性级别为私有,点击【新建项目】

image-20250508141949672

上传代码到本地gitlab上

[root@master01 ~]# cd /root/5/
[root@master01 5]# cp -r springdemo/ springdemo-jenkins
[root@master01 ~]# cd /root/5/springdemo-jenkins
#修改控制器文件
[root@master01 springdemo-jenkins]# vim deployment.yaml
apiVersion: v1
kind: Service
metadata:
  name: $APP_NAME
  namespace: $NAMESPACE
spec:
  ports:
    - name: web
      port: 8080
      protocol: TCP
      targetPort: 8080
  selector:
    app: $APP_NAME
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: $APP_NAME
  namespace: $NAMESPACE
spec:
  progressDeadlineSeconds: 600
  replicas: $APP_REPLICAS
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: $APP_NAME
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: $APP_NAME
    spec:
      imagePullSecrets:
        - name: harborsecret
      containers:
      - env:
        - name: spring.profiles.active
          value: $SPRINGENV
        image: $IMAGE_NAME
        imagePullPolicy: IfNotPresent
        lifecycle:
          preStop:
            exec:
              command:
              - curl
              - -X
              - POST
              - 127.0.0.1:8080/actuator/shutdown
        resources:
          limits:
            memory: $PODMEMORYGi
            cpu: $PODCPUm
          requests:
            memory: $PODMEMORYGi
            cpu: $PODCPUm
        livenessProbe:
          failureThreshold: 5
          httpGet:
            path: /apptwo
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 2
        name: $APP_NAME
        ports:
          - containerPort: 8080
            name: web
            protocol: TCP
        readinessProbe:
          failureThreshold: 5
          httpGet:
            path: /apptwo
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 40
          periodSeconds: 5
          successThreshold: 1
          timeoutSeconds: 2
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      terminationGracePeriodSeconds: 70

#提交代码文件内容
[root@master01 springdemo-jenkins]# git init
[root@master01 springdemo-jenkins]# git add .
[root@master01 springdemo-jenkins]# git commit -m 'JavaDemo'
[root@master01 springdemo-jenkins]# git remote add origin http://gitlab.zhang-qing.com/demoteam/java-kubernetes.git
[root@master01 springdemo-jenkins]# git branch -M main
[root@master01 springdemo-jenkins]# git push -uf origin main
Username for 'http://gitlab.zhang-qing.com': root
Password for 'http://root@gitlab.zhang-qing.com': S6n5Y7b81wRrJnKv

可能遇到的问题:

#执行完git push -uf origin main提示失败
error: failed to push some refs to 'http://gitlab.zhang-qing.com/demoteam/java-kubernetes.git'

#解决方法
依次点击【设置】-【仓库】-【受保护分支】,取消保护分支即可

如果远程仓库存在其他内容,参考下面删除

#查看
[root@master01 springdemo]# git remote -v

#验证
[root@master01 springdemo]# git remote remove origin

二、镜像拉取

通过docker login登录,本地生成凭证配置文件config.json

查看生成的认证文件(一般路径为/root/.docker/config.json)

#提前手动登录harbor仓库,自动生成/root/.docker/config.json文件
[root@master01 ~]# docker login -uadmin harbor.zhang-qing.com
password: Harbor12345

#查看
[root@master01 ~]# ls -l /root/.docker/config.json
-rw------- 1 root root 89 May  7 20:36 /root/.docker/config.json

基于该凭证文件创建K8S集群中拉取镜像的secret:

#创建secret
[root@master01 ~]# k create ns demo
[root@master01 ~]# kubectl create secret generic harborsecret -n demo \
--from-file=.dockerconfigjson=/root/.docker/config.json \
--type=kubernetes.io/dockerconfigjson

#验证查看
[root@master01 ~]# kg secret -n demo | grep harborsecret
harborsecret          kubernetes.io/dockerconfigjson        1      49s

三、Jenkins Pipeline

Pipeline主要有2种脚本模式:

  • 声明式
  • 脚本式

脚本式优点 :

  • 更少的代码段落和弱规范要求。
  • 更强大的程序代码能力。
  • 更像编写代码程序。
  • 传统的流水线即代码模型,用户熟悉并向后兼容性。
  • 更灵活的自定义代码操作。
  • 能够构建更复杂的工作流和流水线。

Pipeline的几个基本概念:

Stage阶段,一个Pipeline可以划分为若干个Stage,每个Stage代表一组操作。注意, Stage是一个逻辑分组的概念,可以跨多个Node

Node节点,一个Node就是一个Jenkins节点,或者是Master,或者是Agent,是执行 Step的具体运行期环境。 Step步骤,Step是基本的操作单元,小到创建一个目录,大到构建一个Docker镜像,由 各类Jenkins Plugin提供。

我们主要用的是脚本式,先来个简单的例子吧:

定义名为testPipline的流水线

image-20250508142807423

脚本内容:

node {
        def mvnHome
        stage('pull code'){
          echo "pull code"
        }

        stage('build project'){
          echo "build project"
        }
        stage('pull code'){
          echo "pull code"
        }
}

构建后的结果:

Started by user Administrator
[Pipeline] Start of Pipeline (hide)
[Pipeline] node
Running on Jenkins in /var/jenkins_home/workspace/testPipline
[Pipeline] {
[Pipeline] stage
[Pipeline] { (pull code)
[Pipeline] echo
pull code
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (build project)
[Pipeline] echo
build project
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (pull code)
[Pipeline] echo
pull code
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

主要流程:

  • 1、定义变量;
  • 2、参数化构建;
  • 3、创建pod模版;
  • 4、stage阶段(多阶段);

完整pipeline:

1、定义名为JAVA-Demo-Test的Pipeline

// 定义git相关数据
def git_address = "http://gitlab.zhang-qing.com/demoteam/java-kubernetes.git"
def git_auth = "Gitlab-username"

// 构建版本的名称
def tag = "latest"
// Harbor私服地址
def harbor_url = "harbor.zhang-qing.com"
// Harbor的项目名称
def harbor_project_name = "demo"
// Harbor的凭证
def harbor_auth = "Harbor-username"
// 启动时间
def start = new Date().format('yyyy-MM-dd HH:mm:ss')

// 创建一个Pod的模板,label为jenkins-slave
podTemplate(
    label: 'jenkins-slave-java',
    cloud: 'study-kubernetes',
    containers: [
        containerTemplate(
            name: 'jnlp',
            image: "harbor.zhang-qing.com/library/jenkins-slave-maven:v1",
            ttyEnabled: true
        ),
        containerTemplate(
            name: 'docker',
            image: "harbor.zhang-qing.com/library/docker:stable",
            ttyEnabled: true,
            command: 'cat'
        )
    ],
    volumes: [
        hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
    ]
) {
    node("jenkins-slave-java") {
        // 第一步
        stage('Pull') {
            checkout([
                $class: 'GitSCM',
                branches: [[name: "${branch}"]],
                extensions: [],
                userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]
            ])
        }

        stage('BuildDescription') {
            // 自定义设置构建历史显示的名称和描述信息
            // 不同的部署方式设置构建历史显示的名称和描述信息方式不一样,根据自己的部署方式自行百度找到设置方法
            script {
                // 设置buildName
                wrap([$class: 'BuildUser']) {
                    // 修改Description
                    buildDescription "${BUILD_USER} > ${project_name} > ${branch}"
                }
            }
        }

        // 第二步
        stage('Build&Tag&Push&Deploy') {
            // 把选择的项目信息转为数组
            def selectedProjects = "${project_name}".split(',')

            for(int i = 0; i < selectedProjects.size(); i++) {
                // 取出每个项目的名称
                def currentProjectName = selectedProjects[i];

                // 定义镜像名称
                def imageName = "${currentProjectName}:${tag}"

                // 定义newTag
                def newTag = sh(returnStdout: true, script: 'echo `date +"%Y%m%d%H%M"_``git describe --tags --always`').trim()

                // 编译,构建本地镜像
                // sh "sed -i 's#ACTIVEPROFILE#${springProfilesActive}#g' Dockerfile"
                sh "mvn clean package -Dmaven.test.skip=true"
                container('docker') {

                    // 镜像编译
                    sh "docker build -t ${imageName} ."

                    // 给镜像打标签
                    sh "docker tag ${imageName} ${harbor_url}/${harbor_project_name}/${currentProjectName}:${newTag}"

                    // 登录Harbor,并上传镜像
                    withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
                        // 登录
                        sh "docker login -u ${username} -p ${password} ${harbor_url}"
                        // 上传镜像
                        sh "docker push ${harbor_url}/${harbor_project_name}/${currentProjectName}:${newTag}"
                    }

                    // 删除本地镜像
                    sh "docker rmi -f ${imageName}"
                    sh "docker rmi -f ${harbor_url}/${harbor_project_name}/${currentProjectName}:${newTag}"
                }
                def deploy_image_name = "${harbor_url}/${harbor_project_name}/${currentProjectName}:${newTag}"

                // 基于控制器的方式部署到K8S
                sh """
                    sed -i 's#\$IMAGE_NAME#${deploy_image_name}#' deployment.yaml
                    sed -i 's#\$APP_NAME#${currentProjectName}#' deployment.yaml
                    sed -i 's#\$APP_REPLICAS#${replicas}#' deployment.yaml
                    sed -i 's#\$NAMESPACE#${namespaces}#' deployment.yaml
                    sed -i 's#\$SPRINGENV#${springProfilesActive}#' deployment.yaml
                    sed -i 's#\$PODMEMORY#${podsMem}#' deployment.yaml
                    sed -i 's#\$PODCPU#${podsCpu}#' deployment.yaml
                    cat deployment.yaml
                """
                // 部署到K8S
                kubernetesDeploy(kubeconfigId: "kubeconfig", configs: "deployment.yaml")
            }
        }
    }
}

说明:Kubernetes Continuous Deploy这个插件已经被暂停使用,新版Jenkins无法下载该插件,下载链接:https://zqzqbucket.oss-cn-hangzhou.aliyuncs.com/file/kubernetes-cd.hpi

2、定义字符串参数

依次点击【参数化构建过程】-【字符参数】

  • 第一个字符参数:

  • 名称:branch

  • 默认值:main
  • 描述:Git 分支

  • 第二个字符参数:

  • 名称:project_name

  • 默认值:springboot
  • 描述:项目名称

  • 第三个字符参数:

  • 名称:replicas

  • 默认值:1
  • 描述:副本数

  • 第四个字符参数:

  • 名称:namespaces

  • 默认值:demo
  • 描述:K8s 命名空间

  • 第五个字符参数:

  • 名称:springProfilesActive

  • 默认值:dev
  • 描述:Spring 环境

  • 第六个字符参数:

  • 名称:podsMem

  • 默认值:1

  • 描述:内存限制

  • 第七个字符参数:

  • 名称:podsCpu
  • 默认值:500
  • 描述:CPU 限制

3、填写完成后,点击【应用】-【Save】

4、点击【Build with Parameters】,默认值即可,点击【Build】进行构建即可

5、构建完成后,验证查看

#查看pod
[root@master01 springdemo]# kgp -n demo
NAME                          READY   STATUS    RESTARTS   AGE
springboot-6f85946d98-vhw8w   1/1     Running   0          55s

#查看svc
[root@master01 springdemo]# kg svc -n demo
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
springboot   ClusterIP   192.168.129.43   <none>        8080/TCP   92s

6、手动创建一个ingress访问测试

[root@master01 ~]# cd /root/5/springdemo-jenkins
[root@master01 springdemo-jenkins]# cat ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
  name: springboot-ing
spec:
  ingressClassName: nginx
  rules:
  - host: api-test.zhang-qing.com
    http:
      paths:
      - backend:
          service:
            name: springboot
            port:
              number: 8080
        path: /
        pathType: ImplementationSpecific
[root@master01 springdemo-jenkins]# kaf ingress.yaml -n demo
[root@master01 springdemo-jenkins]# curl api-test.zhang-qing.com/appone
appOne

四、问题汇总

4.1 问题1

问题报错内容

...
...
java.lang.NoSuchMethodError: No such DSL method 'kubernetesDeploy' found
...
...

问题解决:

需要安装Kubernetes Continuous Deploy这个插件,但是这个插件已经被停用,无法使用。

# 下载后重新加载此插件即可
[root@master01 5]# wget https://zqzqbucket.oss-cn-hangzhou.aliyuncs.com/file/kubernetes-cd.hpi

五、环境复原

删除deployment、service、ingress

[root@master01 springdemo-jenkins]# k delete -f ingress.yaml -n demo
[root@master01 springdemo-jenkins]# k delete deploy,svc springboot -n demo