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 // 安装dashboard
    • kubectl create clusterrolebinding dashboard-admin-binding --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:dashboard-admin // 创建一个admin
    • kubectl -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 // 创建一个admin
    • kubectl -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 foundpermission 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还没有那个本地用户管理,还得安装,并创建用户,我反正没弄)
  • 插件:

    • 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的推荐插件后,打开插件管理的可获取插件栏,然后搜索勾选下载重启即可
        在这里插入图片描述
  • 凭据

    • 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 nodenode -v
    • Kubernetes连接测试:
      • Dashboard -> manage Jenkins -> cloud -> 创建一个Kubernetes类型的云 -> 引用之前创建的Kubernetes config凭据 -> 测试查看是否能够连接上即可(保证kubernetes启动)
        在这里插入图片描述

流水线 启动!

  • Jenkins 配置:

    • 新建项目Pipeline(freestyle也可以,基本没区别,并且其灵活度会更高) -> Generic Webhook Trigger (安装完Generic Webhook Trigger才会有) -> 使用webhook凭证或者输入对应token
    • 定义部分选择 from SCM -> 设置仓库url -> 引用仓库凭据 -> 指定分支 -> 设置脚本路径
      在这里插入图片描述
  • 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
    • 我采用的是ServiceDeployment写在一个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

本地运行测试

  • 打开frontenddockerfile指定的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次的绿绿的流水线
    在这里插入图片描述
    感谢您的浏览,若有不足,望您体谅,我会尽力学习修改
    第一次发文章,排版不美观,望各位见谅

  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值