k8s-云原生cicd

1、helm

1.1、什么是helm

Helm 就是 Kubernetes 的"应用商店"和"软件包管理器" 。就像 Ubuntu 的 apt 或 Python 的 pip 一样,Helm 专门用来管理 Kubernetes 上的复杂应用。

核心概念(三个词搞定)

  • Chart(包):相当于一个应用的"安装压缩包"。里面包含了部署这个应用所需的所有 YAML 文件(如 Deployment、Service、Ingress 等)。

  • Values(配置):相当于安装时的"选配单"。比如你想安装 MySQL,通过 Values 你可以设置"密码是多少"、"存储多大"等参数,无需手动改 YAML。

  • Release(运行实例) :相当于你把 Chart 安装到 K8s 集群后,正在运行的"应用程序本体"。同一个 Chart 可以安装多次,产生多个不同的 Release(比如 mysql-testmysql-prod)。

1.2、helm的作用

  • 告别"YAML 地狱":手动写几十个互相依赖的 YAML 文件非常痛苦,Helm 将它们打包成一个整体。

  • 一键安装/升级 :安装复杂应用(如 Nginx Ingress、Prometheus)只需一行 helm install 命令;升级时只需 helm upgrade,即使出错也能一键 helm rollback(回滚)到上一版本。

  • 标准化分发:开发者可以把 Chart 推到公共仓库(如 Artifact Hub),别人直接下载即用,极大地降低了部署门槛。

Helm 把杂乱无章的 YAML 文件变成了可配置、可版本管理、可分享的标准安装包,是 Kubernetes 运维中"开箱即用"的核心工具。

1.3、helm安装

复制代码
#注意:此处安装的helm版本为4.2.2
#https://helm.sh/zh/docs/intro/install

curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4
chmod 700 get_helm.sh
./get_helm.sh


#测试
root@k8s-master:~# helm version
version.BuildInfo{Version:"v4.2.2", GitCommit:"b05881cf967a5a09e19866799d0edfd40675803a", GitTreeState:"clean", GoVersion:"go1.26.4", KubeClientVersion:"v1.36"}

2、helm部署Jenkins

2.1、添加Jenkins Helm 仓库

复制代码
helm repo add jenkins https://charts.jenkins.io
helm repo update

2.2、编辑values.yaml文件

此处均为博主踩过无数坑总结出来的默认值文件(helm仓库拉取的chart包,会有许多参数需要设备,此文件是专门用来记录改参数的文件,详情大家可以ai了解一下),兄弟们只需要看到这个文件了解对应参数的作用即可

(此处镜像是拉取的本地harbor仓库镜像,由于前边出过教程此处便不再赘述了,拉取命令如下)

复制代码
docker pull jenkins/jenkins
docker pull kiwigrid/k8s-sidecar:1.0.0
docker pull jenkins/inbound-agent

# ============================================================================
# Jenkins Helm Chart 自定义配置(values.yaml)
# 适用 Chart: jenkins/jenkins (>= 5.x)
# 部署命名空间: jenkins
# ============================================================================

# -------- 全局设置 --------
# 强制指定部署的命名空间(与 Helm 命令行 -n 保持一致)
namespaceOverride: "jenkins"

# 集群内部域名后缀,用于 Service 全限定域名(FQDN)解析
# 默认通常为 "cluster.local",但若集群自定义了域名,需相应修改
clusterZone: "jenkins.local"

# ============================================================================
# Jenkins Controller(Master)配置
# ============================================================================
controller:
  # -------- 服务暴露方式 --------
  # 服务的 Kubernetes 类型:ClusterIP / NodePort / LoadBalancer
  # 本地测试环境使用 NodePort 方便外部访问
  serviceType: NodePort
  # 如果 serviceType 为 NodePort,可指定固定端口(范围 30000-32767)
  # 若不指定,Kubernetes 会自动分配一个随机端口
  nodePort: 32000

  # -------- 镜像配置 --------
  image:
    # 私有镜像仓库地址(需提前配置 imagePullSecret)
    registry: "harbor.local"
    # 镜像仓库和镜像名(注意与 Harbor 中的实际项目/镜像名完全一致)
    repository: "jenkins/jenkins"
    # 镜像标签(强烈建议使用具体版本号,不要使用 latest)
    tag: "2.555.3-jdk21"
    # 拉取策略:Always / IfNotPresent / Never
    # 当 tag 为 latest 时,即便设为 IfNotPresent,Kubernetes 也可能强制拉取,
    # 建议使用具体版本号以让 IfNotPresent 真正生效
    pullPolicy: "IfNotPresent"

  # 用于拉取私有仓库镜像的 Secret 名称(需提前在命名空间中创建)
  # 此 Secret 会被自动注入到 Controller Pod 和 Agent Pod 中
  imagePullSecretName: "harbor-secret"

  # -------- 管理员账号配置 --------
  admin:
    # 管理员用户名(默认 admin)
    username: "admin"
    # 管理员密码(生产环境建议使用 existingSecret 引用外部 Secret,避免明文)
    password: "123456"

  # -------- 插件管理 --------
  # 是否在 Jenkins 启动时自动下载并安装插件列表
  # 设置为 false 可避免因网络问题导致下载失败,从而引发启动崩溃(踩坑点)
  # 后续可通过 UI 或 Jenkins CLI 手动安装插件
  installPlugins: false

  # -------- JCasC(配置即代码)设置 --------
  JCasC:
    # 是否启用 Chart 自带的默认 JCasC 配置
    # 设为 false 后,必须在 configScripts 中完整定义所有配置,否则默认配置可能丢失
    defaultConfig: false
    # 自定义 JCasC 脚本(YAML 格式),可定义多个文件
    configScripts:
      # 此处定义了一个名为 "minimal" 的配置片段
      minimal: |
        jenkins:
          # -------- 安全域(用户认证) --------
          securityRealm:
            local:
              allowsSignup: false          # 禁止用户自助注册
              enableCaptcha: false         # 禁用验证码(简单环境)
              users:
              - id: "admin"                # 用户名
                name: "Jenkins Admin"
                password: "123456"         # 与上方 admin.password 保持一致

          # -------- 授权策略 --------
          authorizationStrategy:
            loggedInUsersCanDoAnything:    # 登录用户可做任何事(非管理员权限)
              allowAnonymousRead: false    # 禁止匿名用户读取

          # -------- Agent 连接端口(JNLP TCP) --------
          # 此端口必须显式设置,否则 Agent 无法通过 TCP 连接(踩坑点!)
          # 如果改用 WebSocket,此端口可设置但非必须,建议保留以作备用
          slaveAgentPort: 50000

          # -------- Kubernetes 云配置(动态 Agent) --------
          clouds:
            - kubernetes:
                name: "kubernetes"                 # 云名称,在 Jenkins 中显示
                serverUrl: "https://kubernetes.default"  # 集群 API Server 内部地址
                skipTlsVerify: true                # 本地集群跳过证书验证(生产环境谨慎)
                namespace: "jenkins"               # Agent Pod 运行的命名空间
                jenkinsUrl: "http://my-jenkins:8080"  # Agent 回调 Master 的地址
                websocket: true                    # 启用 WebSocket 连接(推荐,避免 TCP 端口复杂配置)

        # -------- 安全相关(令牌策略) --------
        security:
          apiToken:
            creationOfLegacyTokenEnabled: false    # 禁止创建旧版 API Token
            tokenGenerationOnCreationEnabled: false
            usageStatisticsEnabled: true

        # -------- 全局设置 --------
        unclassified:
          location:
            url: http://my-jenkins:8080            # Jenkins 根 URL(必须与上方 jenkinsUrl 一致)

  # -------- Sidecar 容器(辅助工具) --------
  sidecars:
    # Config AutoReload:用于监听 JCasC ConfigMap 变更并自动重载配置(无需重启 Pod)
    configAutoReload:
      enabled: true                               # 强烈建议开启,便于调试和热更新
      image:
        registry: "harbor.local"
        repository: "jenkins/k8s-sidecar"         # 原镜像 kiwigrid/k8s-sidecar,需推送到私有仓库
        tag: "1.0.0"
      imagePullPolicy: "IfNotPresent"

# ============================================================================
# Jenkins Agent(执行器)配置
# ============================================================================
agent:
  # 是否启用 Kubernetes Agent 功能(即允许动态创建 Agent Pod)
  enabled: true

  # Agent 使用的镜像(默认 JNLP 连接容器)
  image:
    registry: "harbor.local"
    repository: "jenkins/inbound-agent"
    tag: "latest"                                 # 建议改为具体版本
    pullPolicy: "IfNotPresent"

# ============================================================================
# 持久化存储配置(PVC)
# ============================================================================
persistence:
  # 使用的 StorageClass(需提前创建,本例使用本地存储)
  storageClass: "local-storage"
  # PVC 请求的存储容量
  size: "8Gi"
  # 其他可选参数如 accessMode(默认 ReadWriteOnce)、existingClaim 等未指定

2.3、创建pv卷

(前边value文件当中默认有pvc,但是没有pv,所有此处我们手动创建pv)

复制代码
cat   >  jenkins-pv.yaml  << EOF
apiVersion: v1
kind: PersistentVolume
metadata:
  name: jenkins-pv
  namespace: jenkins
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /mnt/jenkins
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - k8s-worker-01
EOF

kubectl apply -f   jenkins-pv.yaml

#查询是否创建成功!!
root@k8s-master:~/k8s/helm# kubectl  -n jenkins get pv jenkins-pv 
NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS    VOLUMEATTRIBUTESCLASS   REASON   AGE
jenkins-pv   10Gi       RWO            Delete           Bound    jenkins/my-jenkins   local-storage   <unset>                          8d
root@k8s-master:~/k8s/helm# 

2.4、helm部署jenkins

复制代码
#安装jenkins
helm install my-jenkins jenkins/jenkins -f values.yaml -n jenkins --create-namespace  --version  5.9.33

#验证状态
kubectl -n jenkins get pods -w


#执行成功!!!
root@k8s-master:~/k8s/helm# kubectl -n jenkins get pod |grep my
my-jenkins-0                2/2     Running   2 (31m ago)     3d23h
root@k8s-master:~/k8s/helm# 

2.5、创建RBAC账户,待后续jenkins使用

复制代码
#创建仅对pod拥有所有权限的账户
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: my-jenkins-deploy-role
  namespace: jenkins
rules:
- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: my-jenkins-deploy-binding
  namespace: jenkins
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: my-jenkins-deploy-role
subjects:
- kind: ServiceAccount
  name: my-jenkins
  namespace: jenkins
EOF

3、基于jenkins实现k8s pod自动化部署

3.1、部署流程

3.2、组件要求

下载如上组件

3.3、构建包含kubectl命令的agent镜像

(此处避免后续持续拉取kubectl二进制包)

复制代码
#编写dockerfile文件
cat >  Dockerfile << EOF
FROM harbor.local/jenkins/inbound-agent:latest
USER root
RUN curl -LO "https://dl.k8s.io/release/v1.36.2/bin/linux/amd64/kubectl" && \
    chmod +x kubectl && \
    mv kubectl /usr/local/bin/
USER jenkins
EOF

#构建新镜像
docker build -t harbor.local/jenkins/inbound-agent-with-kubectl:latest .

#上传镜像至harbor
docker push harbor.local/jenkins/inbound-agent-with-kubectl:latest

#后续执行替换成此镜像即可

3.4、基础命令测试

测试docker与kubectl命令是否可以正常使用

复制代码
pipeline {
    agent {
        kubernetes {
            yaml '''
spec:
  serviceAccountName: my-jenkins
  containers:
  - name: jnlp
    #解决docker命令无权限问题
    securityContext:
      runAsUser: 0   # root

    image: harbor.local/jenkins/inbound-agent-with-kubectl:latest
    env:
    - name: JENKINS_URL
      value: "http://my-jenkins:8080/"
    - name: JENKINS_WEBSOCKET
      value: "true"
    - name: JENKINS_TUNNEL
      value: ""
    #映射kubectl命令
    - name: KUBERNETES_SERVICE_HOST
      value: kubernetes.default
    - name: KUBERNETES_SERVICE_PORT
      value: "443"
    
    #挂载宿主机docker命令
    volumeMounts:
      - mountPath: /var/run/docker.sock
        name: docker-sock
      - name: docker-bin
        mountPath: /usr/bin/docker

  volumes:
  #映射dockers命令
  - name: docker-sock
    hostPath:
      path: /var/run/docker.sock
      type: Socket
  - name: docker-bin
    hostPath:
      path: /usr/bin/docker
      type: File

  imagePullSecrets:
  - name: harbor-secret
  restartPolicy: Never
'''
        }
    }

    stages {
        stage('kubectl命令验证') {
            steps {
                sh '''
                  kubectl version
                  kubectl -n jenkins  get pods 
                '''
            }
        }
        stage('docker命令验证') {
            steps {
                sh ''' 
echo "验证docker命令"
docker version
docker login harbor.local  -u admin -p Harbor12345

echo "验证docker build命令"
cat > Dockerfile << EOF
FROM nginx:latest
RUN echo "aaa" > /usr/share/nginx/html/index.html
EOF

docker build -t  harbor.local/jenkins/nginx:v1 .
docker push harbor.local/jenkins/nginx:v1
                '''
            }
        }
    }
}

3.5、创建pipeline流水线,并部署测试pod

复制代码
pipeline {
    agent {
        kubernetes {
            yaml '''
spec:
  serviceAccountName: my-jenkins
  containers:
  - name: jnlp
    #解决docker命令无权限问题
    securityContext:
      runAsUser: 0   # root

    image: harbor.local/jenkins/inbound-agent-with-kubectl:latest
    env:
    - name: JENKINS_URL
      value: "http://my-jenkins:8080/"
    - name: JENKINS_WEBSOCKET
      value: "true"
    - name: JENKINS_TUNNEL
      value: ""
    - name: KUBECONFIG

    - name: KUBERNETES_SERVICE_HOST
      value: kubernetes.default
    - name: KUBERNETES_SERVICE_PORT
      value: "443"

    volumeMounts:
      - mountPath: /var/run/docker.sock
        name: docker-sock
      - name: docker-bin
        mountPath: /usr/bin/docker

  volumes:
  #映射dockers命令
  - name: docker-sock
    hostPath:
      path: /var/run/docker.sock
      type: Socket
  - name: docker-bin
    hostPath:
      path: /usr/bin/docker
      type: File

  imagePullSecrets:
  - name: harbor-secret
  restartPolicy: Never
'''
        }
    }


    stages {
        stage('部署 Nginx') {
            steps {
                sh '''
# 部署 Nginx
cat <<EOF > /tmp/nginx-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-dp
  namespace: jenkins
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-dp
  template:
    metadata:
      labels:
        app: nginx-dp
    spec:
      containers:
      - name: nginx-dp
        image: harbor.local/public/nginx
        ports:
        - containerPort: 80
      imagePullSecrets:
      - name: harbor-secret   # 如果使用私有镜像,保留此项
EOF

kubectl apply -f /tmp/nginx-deploy.yaml --request-timeout=10s 
kubectl get pods -n jenkins -l app=nginx-dp --request-timeout=10s
'''
            }
        }
    }
}

#测试成功!!!
root@k8s-master:~/k8s/helm# kubectl -n jenkins get pod 
NAME                        READY   STATUS    RESTARTS      AGE
my-jenkins-0                2/2     Running   2 (72m ago)   3d23h
nginx-dp-5bc6f65547-bnmc7   1/1     Running   0             25s
nginx-dp-5bc6f65547-c9fck   1/1     Running   0             25s
nginx-dp-5bc6f65547-npgw6   1/1     Running   0             25s
root@k8s-master:~/k8s/helm# 

4、基于jenkins实现k8s 考试系统部署全流程

4.1、服务器部署

在做这个测试前,博主觉得还是有必要和兄弟们聊一聊宿主机上该项目是如何部署的,以便于大家理解后续Jenkins上pipeline脚本当中的命令的具体含义

4.1.1、项目目录详解

复制代码
顶层目录可以这样理解
xzs
├── source
│   ├── xzs                 Java 后端
│   ├── vue
│   │   ├── xzs-admin       管理员网页
│   │   └── xzs-student     学生网页
│   └── wx
│       └── xzs-student     微信小程序
├── sql                     PostgreSQL 数据库脚本
├── docker                  Docker 部署文件
├── release                 部署说明
├── docs                    相关文档
└── README.md               项目简介



它并不是三个完全独立的系统,而是三个前端共用一个后端:
管理端 Vue ─────┐
                │  HTTP/JSON
学生端 Vue ─────┼──────────→ Spring Boot ──→ MyBatis ──→ PostgreSQL
                │
微信小程序 ─────┘

#部署架构如下

用户
 ↓ HTTPS
Nginx / 负载均衡
 ├── /admin、/student → Vue 静态文件
 └── /api/**          → Spring Boot 8000
                             ↓
                         PostgreSQL 5432

    此处架构为前后端不分离架构,因此我们需要先将admin,student的前端打包成一个静态文件,移到后端的static目录下,最后打成一个总的jar包

4.1.2、项目文件读取

在上手一个Java项目时,以下文件是运维人员必读的

复制代码
1. 后端总配置
application.yml 配置文件。
需要知道:
    服务端口:8000
    Web 容器:Undertow
    MyBatis XML 位置
    默认运行环境:dev
    安全放行地址
    数据库连接池配置
    日志配置
特别注意:
spring:
  profiles:
    active: dev
这意味着没有通过启动参数覆盖时,默认连接开发环境。

生产启动必须明确指定:prod为生产环境spring 配置文件
Linux:
java -Duser.timezone=Asia/Shanghai \
  -Dspring.profiles.active=prod \
  -jar xzs-3.9.0.jar

4.1.3、项目如何构建

复制代码
前端:
student  vue端
cd source/vue/xzs-student
npm ci
npm run build:prod

admin    vue端
cd source/vue/xzs-admin
npm ci
npm run build:prod



后端:
cd source/xzs
mvn clean package

文件生成查看:
source/xzs/target/xzs-3.9.0.jar

4.1.4、环境准备

jdk1.8,maven,node.js命令下载

复制代码
#maven下载
wget  https://dlcdn.apache.org/maven/maven-3/3.9.16/binaries/apache-maven-3.9.16-bin.tar.gz
sudo tar -zxvf ./apache-maven-3.9.16-bin.tar.gz -C /usr/local/
sudo ln -s /usr/local/apache-maven-3.9.16 /usr/local/maven

echo 'export MAVEN_HOME=/usr/local/maven' >> /etc/profile
echo 'export PATH=$MAVEN_HOME/bin:$PATH' >> /etc/profile

#声明环境变量
source  /etc/profile

#jdk下载
sudo apt install openjdk-8-jdk -y


#node.js下载
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install 24

4.1.5、部署xzs系统

复制代码
#1、拉取代码
git clone https://github.com/mindskip/xzs.git

#切换到代码目录
cd xzs/

#2、前端构建
cd source/vue/xzs-admin
npm ci
npm run build:prod
cd ../../..

cd source/vue/xzs-student
npm ci
npm run build:prod
cd ../../..

#确认前端是否构建成功
test -f source/vue/xzs-admin/admin/index.html && echo "管理端构建成功"
test -f source/vue/xzs-student/student/index.html && echo "学生端构建成功"

#3、把前端资源复制到后端static文件下
mkdir -p source/xzs/src/main/resources/static/admin

rsync -av --delete \
  source/vue/xzs-admin/admin/ \
  source/xzs/src/main/resources/static/admin/

mkdir -p source/xzs/src/main/resources/static/student

rsync -av --delete \
  source/vue/xzs-student/student/ \
  source/xzs/src/main/resources/static/student/


#4、后端构建
cd source/xzs
mvn clean package

#检查jar包是否存在
ls -lh target/xzs-3.9.0.jar


#5、数据库准备
#5.1、安装数据库
apt install -y postgresql
psql --version
systemctl status postgresql

#5.2、创建数据库和应用账户
#创建专有用户
sudo -u postgres createuser --pwprompt xzs_app

#5.3、创建专有数据库
sudo -u postgres createdb \
  --owner=xzs_app \
  --encoding=UTF8 \
  xzs

#查询数据库是否创建成功
sudo -u postgres psql -c '\l' | grep xzs


#5.4、导入xzs  sql文件
psql \
  -h 127.0.0.1 \
  -p 5432 \
  -U xzs_app \
  -d xzs \
  -f sql/xzs-postgresql.sql

#导入完成后检查表:
psql \
  -h 127.0.0.1 \
  -U xzs_app \
  -d xzs \
  -c '\dt'

#应该存在如下表:
t_user
t_subject
t_question
t_exam_paper
t_exam_paper_answer
t_text_content
...


#6、创建运行应用目录
sudo mkdir -p /opt/xzs/app
sudo mkdir -p /opt/xzs/log
sudo mkdir -p /opt/xzs/config

#7、专有用户创建
sudo useradd \
  --system \
  --home /opt/xzs \
  --shell /sbin/nologin \
  xzs

#复制jar包至应用目录下
sudo cp source/xzs/target/xzs-3.9.0.jar \
  /opt/xzs/app/xzs-3.9.0.jar

#创建application.xml文件,并配置严格的权限,将原本xzs目录下的文件提取到专门目录下,便于后续查看与修改
sudo cat >>  /opt/xzs/config/application-prod.yml < EOF
server:
  port: 8000

logging:
  path: /opt/xzs/log

spring:
  datasource:
    url: jdbc:postgresql://127.0.0.1:5432/xzs
    username: xzs_app
    password: 请替换成实际密码
    hikari:
      minimum-idle: 5
      maximum-pool-size: 20
      connection-timeout: 30000
EOF

sudo chown xzs:xzs /opt/xzs/config/application-prod.yml
sudo chmod 600 /opt/xzs/config/application-prod.yml


#前台启动
sudo -u xzs java \
  -Xms512m \
  -Xmx1024m \
  -Duser.timezone=Asia/Shanghai \
  -Dspring.profiles.active=prod \
  -Dspring.config.additional-location=file:/opt/xzs/config/ \
  -jar /opt/xzs/app/xzs-3.9.0.jar

#日志查看
tail  -f  /opt/xzs/log/*


#接口测试,预期返回,3个302,2个200
curl -I http://127.0.0.1:8000/
curl -I http://127.0.0.1:8000/student
curl -I http://127.0.0.1:8000/admin
curl -I http://127.0.0.1:8000/student/index.html
curl -I http://127.0.0.1:8000/admin/index.html

#浏览器访问
http://宿主机IP:8000/student
http://宿主机IP:8000/admin

4.2、k8s部署xzs系统

4.2.1、构建含jdk,maven,node命令镜像

复制代码
cat > Dockerfile << EOF
FROM ubuntu:22.04

ARG MAVEN_VERSION=3.9.16
ARG JDK_VERSION=8u351
ARG NODE_VERSION=v14.21.3

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y --no-install-recommends \
    ca-certificates curl wget unzip \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

ADD apache-maven-${MAVEN_VERSION}-bin.tar.gz /opt/
ADD jdk-${JDK_VERSION}-linux-x64.tar.gz /opt/
ADD node-${NODE_VERSION}-linux-x64.tar.xz /opt/

RUN ln -s /opt/apache-maven-${MAVEN_VERSION} /opt/maven \
    && ln -s /opt/jdk1.8.0_351 /opt/jdk1.8 \
    && ln -s /opt/node-${NODE_VERSION}-linux-x64 /opt/node-v14

# ----- 关键修改:分开定义环境变量 -----
ENV JAVA_HOME=/opt/jdk1.8
ENV MAVEN_HOME=/opt/maven
ENV NODE_HOME=/opt/node-v14
ENV PATH=$JAVA_HOME/bin:$MAVEN_HOME/bin:$NODE_HOME/bin:$PATH
# ------------------------------------

WORKDIR /opt
CMD ["/bin/bash"]
EOF

docker build -t my-java-node-maven:latest .

docker tag my-java-node-maven:latest harbor.local/jenkins/my-java-node-maven:latest
docker push harbor.local/jenkins/my-java-node-maven:latest

4.2.2、构建xzs镜像

复制代码
FROM harbor.local/jenkins/my-java-node-maven

RUN mkdir -p /opt/xzs/app /opt/xzs/config

COPY xzs-3.9.0.jar /opt/xzs/app/
COPY application-prod.yml /opt/xzs/config/

WORKDIR /opt/xzs/app

CMD ["java", "-Xms512m", "-Xmx1024m", \
     "-Duser.timezone=Asia/Shanghai", \
     "-Dspring.profiles.active=prod", \
     "-Dspring.config.additional-location=file:/opt/xzs/config/", \
     "-jar", "/opt/xzs/app/xzs-3.9.0.jar"]

#前置条件:将xzs的jar包与application文件移到当前目录
docker  build  -t xzs:v1.0  .、

#打包推送
root@k8s-worker-02:/opt/xzs/app# docker tag xzs:v1.0 harbor.local/jenkins/xzs:v1.0
root@k8s-worker-02:/opt/xzs/app# docker push harbor.local/jenkins/xzs:v1.0 

4.2.3、编写yaml文件

复制代码
# 使用前必须先完成两件事:
# 1. 将 postgres-secret 中的 CHANGE_ME_BEFORE_FIRST_APPLY 替换为真实强密码。
# 2. 在 PostgreSQL 第一次启动前创建初始化 SQL ConfigMap:
   # kubectl create namespace xzs --dry-run=client -o yaml | kubectl apply -f -
   #   kubectl create configmap xzs-db-init \
   # --from-file=001-xzs-init.sql=/opt/xzs-postgresql.sql \
   # -n xzs --dry-run=client -o yaml | kubectl apply -f -
#
# 如果 Harbor 需要认证,还需提前创建拉取凭据:
# kubectl create secret docker-registry harbor-pull-secret \
#   --docker-server=harbor.local \
#   --docker-username='<harbor-user>' \
#   --docker-password='<harbor-password>' \
#   -n xzs
#
# 部署:kubectl apply -f k8s/xzs-all.yaml

---
apiVersion: v1
kind: Namespace
metadata:
  name: xzs

---
apiVersion: v1
kind: Secret
metadata:
  name: postgres-secret
  namespace: xzs
type: Opaque
stringData:
  # 必须在 PostgreSQL 第一次启动前修改。PVC 初始化后,仅修改此值不会自动修改数据库内部密码。
  POSTGRES_PASSWORD: "123456"

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: xzs-config
  namespace: xzs
data:
  application-prod.yml: |
    server:
      port: 8000

    spring:
      datasource:
        url: jdbc:postgresql://postgres-service:5432/xzs
        username: xzs_app
        hikari:
          minimum-idle: 5
          maximum-pool-size: 20
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000

  # 项目原有 prod 日志只写文件。Kubernetes 中改为 stdout,便于 kubectl logs 和日志采集。
  logback-k8s.xml: |
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
      <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
      <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
          <pattern>${CONSOLE_LOG_PATTERN}</pattern>
          <charset>UTF-8</charset>
        </encoder>
      </appender>
      <root level="INFO">
        <appender-ref ref="CONSOLE"/>
      </root>
    </configuration>

---
apiVersion: v1
kind: Service
metadata:
  name: postgres-service
  namespace: xzs
  labels:
    app: postgres
spec:
  # StatefulSet 使用无头 Service 获得稳定网络标识。
  clusterIP: None
  selector:
    app: postgres
  ports:
    - name: postgres
      protocol: TCP
      port: 5432
      targetPort: postgres

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  namespace: xzs
spec:
  serviceName: postgres-service
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: postgres
          image: postgres:16-alpine
          imagePullPolicy: IfNotPresent
          env:
            - name: POSTGRES_USER
              value: xzs_app
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-secret
                  key: POSTGRES_PASSWORD
            - name: POSTGRES_DB
              value: xzs
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
            - name: TZ
              value: Asia/Shanghai
          ports:
            - name: postgres
              containerPort: 5432
              protocol: TCP
          volumeMounts:
            - name: postgres-storage
              mountPath: /var/lib/postgresql/data
            # 官方 postgres 镜像只会在空 PGDATA 首次初始化时执行此 SQL。
            - name: init-sql
              mountPath: /docker-entrypoint-initdb.d/001-xzs-init.sql
              subPath: 001-xzs-init.sql
              readOnly: true
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: "1"
              memory: 1Gi
          startupProbe:
            exec:
              command:
                - sh
                - -c
                - pg_isready -U xzs_app -d xzs
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 60
          readinessProbe:
            exec:
              command:
                - sh
                - -c
                - pg_isready -U xzs_app -d xzs
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 6
          livenessProbe:
            exec:
              command:
                - sh
                - -c
                - pg_isready -U xzs_app -d xzs
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 6
      volumes:
        - name: init-sql
          configMap:
            name: xzs-db-init
  volumeClaimTemplates:
    - metadata:
        name: postgres-storage
      spec:
        storageClassName: postgres-storage
        accessModes:
          - ReadWriteOnce
        # 如果集群没有默认 StorageClass,请在这里增加 storageClassName。
        resources:
          requests:
            storage: 10Gi

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: xzs
  namespace: xzs
  labels:
    app: xzs
    environment: production
spec:
  # 当前项目的 Session 默认保存在单个 JVM 内存中,首次部署先使用一个副本。
  replicas: 1
  revisionHistoryLimit: 5
  minReadySeconds: 10
  selector:
    matchLabels:
      app: xzs
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  template:
    metadata:
      labels:
        app: xzs
    spec:
      terminationGracePeriodSeconds: 40
      # 如果 Harbor 允许匿名拉取,可以删除 imagePullSecrets。
      imagePullSecrets:
        - name: harbor-secret
      initContainers:
        - name: wait-for-postgres
          image: postgres:16-alpine
          imagePullPolicy: IfNotPresent
          env:
            - name: PGPASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-secret
                  key: POSTGRES_PASSWORD
          command:
            - sh
            - -c
            - |
              until pg_isready -h postgres-service -p 5432 -U xzs_app -d xzs; do
                echo "waiting for PostgreSQL..."
                sleep 3
              done
              until psql -h postgres-service -U xzs_app -d xzs -tAc \
                "SELECT to_regclass('public.t_user')" | grep -q t_user; do
                echo "waiting for xzs database schema..."
                sleep 3
              done
      containers:
        - name: xzs
          # 建议由 Jenkins 替换为不可变标签,例如 3.9.0-42-a1b2c3d。
          image: harbor.local/jenkins/xzs:v2.0
          imagePullPolicy: IfNotPresent
          env:
            - name: TZ
              value: Asia/Shanghai
            - name: SPRING_PROFILES_ACTIVE
              value: prod
            - name: SPRING_CONFIG_ADDITIONAL_LOCATION
              value: file:/opt/xzs/config/
            - name: SPRING_DATASOURCE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-secret
                  key: POSTGRES_PASSWORD
            - name: LOGGING_CONFIG
              value: file:/opt/xzs/config/logback-k8s.xml
            - name: JAVA_TOOL_OPTIONS
              value: >-
                -Xms512m
                -Xmx1024m
                -XX:+HeapDumpOnOutOfMemoryError
                -XX:HeapDumpPath=/tmp
                -Duser.timezone=Asia/Shanghai
          ports:
            - name: http
              containerPort: 8000
              protocol: TCP
          volumeMounts:
            - name: xzs-config
              mountPath: /opt/xzs/config
              readOnly: true
          resources:
            requests:
              cpu: 250m
              memory: 768Mi
            limits:
              cpu: "1"
              memory: 1536Mi
          startupProbe:
            httpGet:
              path: /student/index.html
              port: http
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 60
          readinessProbe:
            httpGet:
              path: /student/index.html
              port: http
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 6
          # Liveness 只判断进程和端口,不把数据库故障变成 Pod 重启风暴。
          livenessProbe:
            tcpSocket:
              port: http
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 6
          lifecycle:
            preStop:
              exec:
                command:
                  - sh
                  - -c
                  - sleep 10
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
      volumes:
        - name: xzs-config
          configMap:
            name: xzs-config

---
apiVersion: v1
kind: Service
metadata:
  name: xzs-service
  namespace: xzs
spec:
  type: NodePort
  selector:
    app: xzs
  # 这是多副本前的过渡方案,不能替代 Spring Session + Redis。
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800
  ports:
    - name: http
      protocol: TCP
      port: 8000
      targetPort: http
      nodePort: 31111

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: postgres-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgres-pv
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: postgres-storage
  local:
    path: /mnt/data/xzs-postgres
  nodeAffinity:                 
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - k8s-worker-02
相关推荐
还有你Y2 小时前
读懂微服务
微服务·云原生·架构
沉迷学习 日益消瘦2 小时前
04-配置与扩缩
kubernetes
阿里云云原生3 小时前
亚太唯一!阿里云跻身 Gartner 可观测魔力象限“挑战者”象限
云原生
小二·3 小时前
云原生2026:Kubernetes + Wasm + Serverless 深度实战
云原生·kubernetes·wasm
啊啊啊迈 旋棍4 小时前
终端后端【重要,操作实用】
云原生·eureka
ruofu335 小时前
下载nvidia_*_cu13的指令(更新:利用torch 版本拉取相关依赖)
docker·容器
独孤--蝴蝶5 小时前
Docker容器进阶(一)
运维·docker·容器
海兰5 小时前
基于 ES|QL 的 Kubernetes 监控工具箱:使用 ES|QL 进行 Kubernetes 监控,从内存压力到错误日志
大数据·elasticsearch·kubernetes
沉迷学习 日益消瘦6 小时前
09-Helm 包管理
kubernetes