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