Jenkins + Docker + Kubernetes 本地流水线化webapp 面向纯小白
前言
这篇文章来自于本人为完成小学期任务,查询相关资料后发现资料要不过老,要不不适用与windows等诸多问题。因此想要分享一下自己的学习心得,也借此机会记录一下近来的学习付出
我只是一只单纯的菜狗,第一次发文章没啥经验,如果有不对的地方请理性讨论,不要喷我
项目简介
任务要求:
- 完成对上个学期小组共同开发的webapp进行流水线化,要求实现代码到本地K8S环境的持续集成与部署
- 基础任务:
- 在本地搭建CI/CD流水线,最低要求利用包管理工具进行本地单元测试、项目构建、容器化和本地部署
- 进阶任务:
- 单体系统微服务化:对上学期大作业所开发的系统进行微服务和容器化改造。微服务的数量和规模需考虑系统的质量属性与特性
- 云原生特性测试:利用K8S 内置 Deployment、Replicaset 等部署方法,实现系统的自动扩缩容、降级服务等功能。要求使用压力测试工具,编写压力测试脚本验证效果
思路分析 + 开篇简介
思路分析
- 根据对给出的材料进行分析,我们小组初步采用的
Jenkins + docker + kubernetes
来完成webapp的流水线化部署(基础任务) - 然后按照基础任务的完成方法,进一步完成微服务的流水线化部署(进阶任务)
开篇简介
为什么会有这篇文章?
- 本人在小组中担任Jenkins流水线化部分,个人感觉任务难度不大,并由于其是线性工作,很容易想明白,因而沾沾自喜
- BUT,Jenkins的环境实在是太难受了,很简单的流水线构建过程,硬是每一步都在卡,7天干活时间,每一天都在为配环境而痛苦
- 最后导致小组没有及时完成进阶任务,也算是拖了大家后退,深感歉意
- 也是总结一下经验教训,帮助像我一样的小白能够快速的入手,而免遭折磨(中文网教程太老,搜不到相关东西;外网的教程老是不知道为啥教程能跑,本地相同操作跑不了(还是太菜了))
文章介绍
- docker desktop的相关内容
- kubernetes 的相关内容
- jenkins for windows的相关内容
工具介绍 + 安装
声明 : 本人是苦逼的windows系统,没有虚拟机(争取马上搞一个)
docker desktop
docker介绍
- 我们简称为docker
- 能干啥,为什么大家都用它? 大家自行网上搜索学习即可
dockers 启动!
-
首先当我们下载了docker后,我们可以配置国内镜像来加速下载
-
路径为 设置 -> Docker Engine -> (自己添加相关镜像即可,网上随意搜索)
-
然后移步到Kubernetes(就在下面两个),勾选Enable Kubernetes(千万要挂好梯子,要不然老是容易失败,需要下载很多镜像)
-
这样,docker的基础配置就算完事了
Kubernetes
kubernetes简介
- 简称k8s
- 基础功能:监视容器部署
- 进阶功能:调节流量,帮助负载均衡
Kubernetes 启动!
- 我们可以从github或者官网上下载相关dashboard的配置文件,部署后能够方便我们完成对本地容器运行的监视
- 这部分主要就是安装可视化面板,不安装不影响使用
一些常用指令(dashboard安装与启动)
-
非官网版
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
// 安装dashboardkubectl create clusterrolebinding dashboard-admin-binding --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:dashboard-admin
// 创建一个adminkubectl -n kubernetes-dashboard create token dashboard-admin
// 获取一个admin token(登录用)kubectl proxy
// 启动服务http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/
// url链接
-
官网版(自行搜索使用即可,只给出几个指令,安装指令未给出)
kubectl create clusterrolebinding dashboard-admin-binding --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:dashboard-admin
// 创建一个adminkubectl -n kubernetes-dashboard create token dashboard-admin
// 获取一个admin token(登录用)kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard-kong-proxy 8443:443
// 转发端口到本地8443,可通过localhost:8443访问
-
控制台快捷查看状态
kubectl get pods
// 这个命令会列出所有 Pods 的状态,显示它们是否正在运行、待命还是出现了错误。kubectl get deployments
// 这个命令会列出所有 Deployments 的状态,包括副本数和更新情况。kubectl get services
// 这个命令会列出所有 Services 的状态,包括暴露的端口和服务类型。kubectl describe pod <pod-name>
// 这个命令会显示指定 Pod 的详细信息,包括事件、状态、日志等。kubectl logs <pod-name>
// 这个命令会显示指定 Pod 中容器的日志,帮助你排查运行时问题。
Jenkins
Jenkins简介
- 理论上来说非常好用,可以有效的帮助我们快速的自动流水线化完成容器的部署
- but,各种not found,permission denied令人头大
Jenkins安装
-
版本选择:
- 两种选择,一种是docker上下载官方镜像直接使用,另一种是从官网下载jenkins for windows的安装程序进行安装
- 个人推荐用jenkins for windows,相比较与docker版,一些配置更好完成(也可能是在docker版里面把坑都踩完了导致的)
-
安装过程:
- docker版:
- 务必务必使用相关指令完成root权限的启用、端口转发和卷的挂载等,使得数据持久化在docker的存储空间中,防止每一次调整配置都会导致插件等数据的丢失
- 形如:
docker run -d -p 8080:8080 -v /var/run/docker.sock:/var/run/docker.sock -v jenkins_home:/var/jenkins_home --name jenkins --user root jenkins/jenkins:latest
(不保证正确哈,自行学习,这个指令我没用过,此时我已经受尽折磨转到for windows了,虽然这个指令看起来没啥问题) - 注意,务必留意本地端口的占用情况,以防止和app的端口冲突
- windows版:
- 下载合适版本即可,需要本地有jdk17-21的java,没有的话java官网下载即可
- 安装时有一个需要选择使用本地用户那个,如果只是简单开发的化,建议不选使用本地用户那个,没必要(win11还没有那个本地用户管理,还得安装,并创建用户,我反正没弄)
- docker版:
-
插件:
Blue Ocean
提供Jenkins stage的新UI,让流水线可视化程度增强(推荐小白用)Kubernetes
用于将 Jenkins 与 Kubernetes 集群集成Kubernetes CLI
提供对 Kubernetes 命令行工具(kubectl)的支持Docker
支持 Jenkins 与 Docker 集成,允许在流水线中构建、推送和运行 Docker 镜像Docker Pipeline
允许在流水线脚本中使用 Docker 容器进行构建和测试Generic Webhook Trigger
可以接收webhook,来帮助流水线自动化进行NodeJS
可以调用本地的NodeJS进行包的管理与下载Pipeline
系列插件 使流水线更好的运行Git
系列插件 使流水线更好的运行- 下载说明:
- 在下载完Jenkins的推荐插件后,打开插件管理的可获取插件栏,然后搜索、勾选、下载、重启即可
- 在下载完Jenkins的推荐插件后,打开插件管理的可获取插件栏,然后搜索、勾选、下载、重启即可
-
凭据:
-
git仓库(如果是公共仓库应该不需要设置仓库凭据,私有仓库需要设置该凭据) (Username with password) ——代码抓取
-
dockerhub (Username with password) ——帮助完成镜像推送与抓取
-
Kubernetes config (secret file) ——连接Kubernetes
-
webhook (secret text) ——使得流水线在仓库完成对应动作后能够自动开始
-
设置说明:
-
git仓库凭证
就是http密码,找到各自对应的仓库的http密码即可 -
dockerhub凭证
需要进入dockerhub中,登陆后点击右上角头像 ->Account Settings
->Security
->Personal access tokens
->Generate new token
-> 设置相应权限即可 -> 留存生成的token -> 写入username with password
类型凭证即可
-
Kubernetes
(docker 设置自带版) 的config文件通常位于"C:\Users\${Username}\.kube\config"
中,创建secretfile类型的凭证上传即可 -
webhook
(可有可无)用来接收特定webhook,找到仓库的webhook设置,设置对应参数后,将token写入secret text
凭证即可(以华为云为例)。其中url不能为本地域名,可以使用ngrok
创建新IP做跳板
-
-
-
杂项:
Nodejs
设置:(本人在此处使用此设置后依旧找不到安装的Newman,本设置给出仅供拓展)- 帮助我们在流水线中能够执行通过npm安装的特定包的特定指令
Dashboard
->manage Jenkins
->configureTools
->找到Nodejs安装(安装完nodejs插件后) -> 新增 -> 通过对应指令获取本地Nodejs版本与位置 -> 键入对应值(路径到对应Nodejs文件夹即可) -> 在Global npm packages to install
一栏输入想全局安装的包名(如newman
,集成测试使用)- *相关指令:
where node
、node -v
Kubernetes
连接测试:Dashboard
->manage Jenkins
->cloud
-> 创建一个Kubernetes类型的云 -> 引用之前创建的Kubernetes config
凭据 -> 测试查看是否能够连接上即可(保证kubernetes启动)
流水线 启动!
-
Jenkins 配置:
- 新建项目Pipeline(freestyle也可以,基本没区别,并且其灵活度会更高) ->
Generic Webhook Trigger
(安装完Generic Webhook Trigger才会有) -> 使用webhook凭证
或者输入对应token
- 定义部分选择 from SCM -> 设置仓库url -> 引用仓库凭据 -> 指定分支 -> 设置脚本路径
- 新建项目Pipeline(freestyle也可以,基本没区别,并且其灵活度会更高) ->
-
Jenkinsfile:
pipeline {
agent any // 代理节点any即可
environment { // 环境配置
DOCKER_CREDENTIALS_ID = '' // Docker Registry 凭据 ID
DOCKER_USER_ID = '' // Dockerhub 的用户 ID
NEWMAN_PATH = 'C:\\Users\\Username\\AppData\\Roaming\\npm\\newman.cmd' // 使用 .cmd 文件路径 (`where newman`来获取本地Newman)
}
stages {
stage('Checkout') {
steps {
// 检出代码
checkout scm
}
}
stage('Build image') {
steps {
script {// 构建 Docker 镜像
docker.build("${DOCKER_USER_ID}/${img-name}:${tag}", "${path to the folder which contain dockerfile}")
……
}// example: docker.build("${DOCKER_USER_ID}/frontend:latest", "./frontend")
}
}
stage('Push Docker Images') {
steps {
script {// 登录 Docker Registry
docker.withRegistry('https://index.docker.io/v2/', DOCKER_CREDENTIALS_ID) { // https://index.docker.io/v2/ 是dockerhub仓库默认地址
docker.image("${DOCKER_USER_ID}/${img-name}:${tag}").push()// 推送刚打包完的镜像
……
}// example: docker.build("${DOCKER_USER_ID}/frontend:latest".push())
}
}
}
stage('Deploy to Kubernetes') { //部署到k8s
steps {
script {
withKubeConfig([credentialsId: 'kubeconfig']) { // 登录本地k8s,使用准备好的kubeconfig
// bat 'kubectl create configmap mysql-initdb-config --from-file=docreading.sql -n myapp' // 创建sql文件的configmap指令
// bat 'kubectl delete configmap mysql-initdb-config -n myapp'// 删除指令
bat "kubectl apply -f ${filepath}.yaml"
……
}// example: bat "kubectl apply -f frontend.yaml"
}
}
}
stage('Integration Testing') { // Newman Integration Test
steps {// 这一步我本来想使用agent docker img 来使用本地的postman/newman:latest镜像,但是总是无输出退出测试,因而转向直接调用本地的newman.cmd
script {// 使用 Newman 运行 Postman 集合
bat "${NEWMAN_PATH} run ${path_to_collection_file} --reporters cli,html --reporter-html-export postman_report.html"
} // example: bat "${NEWMAN_PATH} run postman_script/ProjectReader.postman_collection.json --reporters cli,html --reporter-html-export postman_report.html"
}
}
}
post {
always { // 结束后必定触发
archiveArtifacts artifacts: 'postman_report.html', allowEmptyArchive: true
publishHTML([allowMissing: false, alwaysLinkToLastBuild: true, keepAll: true, reportDir: '', reportFiles: 'postman_report.html', reportName: 'Postman HTML Report']) // 生成报告
cleanWs() // 清理工作区
echo 'end'
}
}
}
-
一开始我使用的是直接调用docker中的镜像进行打包,然后再进行镜像构建,但是在debug的过程中发现他没一次重新运行流水线都会在打包时重新下载依赖,导致浪费大量时间与流量
-
因此我将打包部分直接挪到了dockerfile中,将构建镜像部分分为了打包和构建两个部分,利用缓存机制大大缩短了后续的构建时间
-
Dockerfile:
# 第一阶段:构建前端应用 (需要在docker中提前下载相关镜像,后面同理)
FROM node:latest AS build
# 设置工作目录
WORKDIR /app
# 复制 package.json 并安装依赖
COPY package*.json ./
RUN npm install
# 复制应用代码并构建
COPY . .
RUN npm run build
# 第二阶段:使用 Nginx 镜像部署前端应用
FROM kasmweb/nginx:latest
# 复制构建的静态文件到 Nginx 的静态文件目录
COPY --from=build /app/dist /usr/share/nginx/html
# 暴露端口
EXPOSE 80
# 启动 Nginx 服务
CMD ["nginx", "-g", "daemon off;"]
# 第一阶段:使用 Maven 镜像构建项目
FROM maven:latest AS build
# 设置工作目录
WORKDIR /app
# 复制 Maven 项目的 pom.xml 和 src 目录
COPY pom.xml .
COPY src ./src
# 使用 Maven 构建项目
RUN mvn clean package
FROM openjdk:24-jdk
# 设置工作目录
WORKDIR /app
# 复制已经编译好的jar文件到容器中
COPY --from=build /app/target/*.jar ./app.jar
# 暴露应用端口(注意,这里是应用内部运行的端口,与Kubernetes的targetPort对应)
EXPOSE 8081
# 启动Spring Boot应用
CMD ["java", "-jar", "app.jar"]
- Kubernetes.yaml
- 我采用的是
Service
和Deployment
写在一个yaml文件中的写法
- 我采用的是
apiVersion: v1
kind: Service
metadata:
name: frontend-service
namespace: myapp # 命名空间
spec:
type: NodePort # 或者 LoadBalancer, 依据需求
ports:
- port: 80
targetPort: 80
nodePort: 30001 # 本地访问端口
selector:
app: frontend
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-deployment
namespace: myapp
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: 12345678/frontend:latest # 替换为你的前端镜像
ports:
- containerPort: 80
env:
- name: PORT
value: "80" # 显式设置容器内部应用监听的端口
# backend端和frontend端大差不差,但是需要在env中声明mysql地址与创建新用户
# 即需要将env替换如下
env:
- name: SPRING_DATASOURCE_URL
value: jdbc:mysql://mysql-service:3306/database # 修改数据库名
- name: SPRING_DATASOURCE_USERNAME
value: root # 登录新用户(创建在mysql部分)
- name: SPRING_DATASOURCE_PASSWORD
value: mysql123
# mysql数据库配置文件
apiVersion: v1
kind: Service
metadata:
name: mysql-service
namespace: myapp
spec:
ports:
- port: 3306
selector:
app: mysql
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-deployment
namespace: myapp
spec:
selector:
matchLabels:
app: mysql
replicas: 1
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0 # 使用官方MySQL镜像
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: 123456 # 替换为你的MySQL root密码
- name: MYSQL_DATABASE
value: database # 初始化数据库名称
- name: MYSQL_USER # 创建新用户
value: root
- name: MYSQL_PASSWORD
value: mysql123
volumeMounts:
- name: mysql-temp-storage
mountPath: /var/lib/mysql
- name: mysql-initdb
mountPath: /docker-entrypoint-initdb.d
volumes:
- name: mysql-temp-storage
emptyDir: {} # 临时存储,也可以替换为持久存储 (随删随清)
- name: mysql-initdb
configMap:
name: mysql-initdb-config # configmap名字
# kubectl create configmap mysql-initdb-config --from-file=database.sql -n myapp // 创建sql文件的configmap指令
# kubectl delete configmap mysql-initdb-config -n myapp // 删除指令
# 用以将数据库整体挪到镜像容器中,实现数据库的镜像化
# 数据库导出网络自行搜索,dbeaver右键数据库 -> 工具 -> 导出
# -n 参数是命名空间,可以不写或者kubectl create namespace ${my-namespace}来创建命名空间
- 文件结构:
├─backend
| └─dockerfile
├─frontend
| └─dockerfile
|─k8s
|─Jenkinsfile
|─postman_collection.json
└─database.sql
本地运行测试
-
打开frontend中dockerfile指定的
nodePort
-
kubectl -n myapp port-forward svc/backend-service 8081:8081
将后端从容器中的8081暴露到本地8081(自行确定前后端交接的域名,以8081为例) -
然后直接运行前端网站完成app测试即可
-
tips:如果出现CORS跨域拦截,可以尝试下载edge的
CORS Unblock
插件自动解除限制
-
tips-2:如果出现 Docker Desktop 自带的 Kubernetes 持续长时间
Starting…
(如图),可以尝试直接重启Docker Desktop。不知道为什么,第一遍启动 Docker Desktop 总是不能正常跑起来Kubernetes ,重启一遍立刻就好了
结语
- 因为我们组并未完成微服务的流水线话部署工作,因而仅写了基础任务的相关流程,其实微服务的流水线化过程与基础的流水线化相差不大,主要工作在于拆分原有后端代码
- 感谢小组成员的辛勤付出,不抱怨、不放弃,即使没有完成进阶任务,但我依旧感觉这是一次很好的学习过程,锻炼了我的资料查找能力。也祝各位能够在今后的学习生活中更进一步
吐槽
-
上油管学postman测试流水线化的时候,有一印度老哥花了90%的时间讲一个没啥用的指令,最后半分钟提了嘴可以用Newman,然后自己跑了一遍没过,爆了跟我一样的问题(笑死),然后摆烂结束
-
咖喱英语太好玩哩
-
附上我159次的绿绿的流水线
感谢您的浏览,若有不足,望您体谅,我会尽力学习修改
第一次发文章,排版不美观,望各位见谅