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-test和mysql-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