作者:何昌涛,北京北大英华科技有限公司高级 Java 工程师,云原生爱好者。
前言
近年来,为了满足越来越简单的业务需要,咱们从传统单体架构系统升级为微服务架构,就是把一个大型应用程序宰割成能够独立部署的小型服务,每个服务之间都是松耦合的,通过 RPC 或者是 Rest 协定来进行通信,能够依照业务畛域来划分成独立的单元。然而微服务零碎绝对于以往的单体零碎更为简单,当业务减少时,服务也将越来越多,服务的频繁部署、监控将变得复杂起来,尤其在上了 K8s 当前会更加简单。那么有没有一款全栈的容器云平台来帮咱们解决这些问题哩?那当然是有的,上面咱们一起来揭秘一下吧。
介绍
KubeSphere
KubeSphere 是在 Kubernetes 之上构建的开源容器平台,提供全栈的 IT 自动化运维的能力,简化企业的 DevOps 工作流。
Pig
Pig 是一个基于 Spring Boot 2.7、 Spring Cloud 2021 & Alibaba、 SAS OAuth2 的开源微服务开发平台,也是微服务最佳实际。在国内领有大量拥护者,同时也有商业版本提供技术支持。
环境搭建
- K8s 容器化环境一套,并部署完 KubeSphere v3.3.0 版本,启用 DevOps 插件。
- GitLab 代码仓库治理开源零碎一套。
- Harbor 容器镜像开源零碎一套。
- SonarQube 开源主动代码审查工具一套。
- 一个更易于构建云原生利用的动静服务发现、配置管理和服务治理的 Nacos 开源平台一套(可选,Pig 已提供 Naocs 服务,即 Register 服务)。
- 高性能的 key-value 数据库 Redis(3.2 +)一套(Pig 须要)。
- 关系型开源数据库管理系统 MySQL 一套(Pig 须要)。
- 高性能对象存储 Minio 一套(Pig 中文件上传须要,可选)。或者阿里云、华为云、腾讯对象存储也可。
架构设计
KubeSphere 架构
KubeSphere 将前端与后端离开,实现了面向云原生的设计,后端的各个性能组件可通过 REST API 对接内部零碎。KubeSphere 无底层的基础设施依赖,能够运行在任何 Kubernetes、公有云、私有云、VM 或物理环境(BM)之上。 此外,它能够部署在任何 Kubernetes 发行版上。如下所示:
该图来自 KubeSphere 官网架构阐明。
Pig 架构
Pig 平台设计灵便可扩大、可移植、可应答高并发需要。同时兼顾本地化、公有云、私有云部署,反对 SaaS 模式利用。如下所示:
该图来自 Pig 白皮书中的根底架构图。
整体架构图
其实就是将原架构加上一层 Ingress, 在 KubeSphere 中对应的是利用路由(Ingress 路由规定)和我的项目网关(Ingress Controller)。如下所示:
整体容器化部署流程图
运维人员可通过 KubeSphere 来治理服务,也能够利用 KubeSphere 中的 Jenkins 来公布制品。如下所示:
部署过程
别离创立两条流水线,一条用于构建 Pig 后端 Java 代码,另外一条用于构建基于 Vue 的 Pig-ui 前端代码。
创立企业空间
为我的项目创立一个名称为 pig-workspace 的企业空间 , 企业空间是一个组织您的我的项目和 DevOps 我的项目、治理资源拜访权限以及在团队外部共享资源等的逻辑单元,能够作为团队工作的独立工作空间。
创立 DevOps 我的项目
DevOps 我的项目是一个独立的命名空间,其中定义了一组流水线。用户能够依照本人的形式对流水线进行分组(例如:我的项目类型、组织类型)。
创立我的项目
我的项目用于对资源进行分组治理和管制不同用户的资源管理权限。
部署 MySQL
1) 进入利用商店,在利用分类中抉择数据库和缓存,找到 MySQL。如下所示:
2) 在根本信息中,填写利用名称 pig-MySQL, 并抉择地位,进行下一步。如下所示:
3) 在利用配置中,编辑 yaml 文件 , 将镜像改为 MySQL/MySQL-server:8.0.30,将明码设置为 root。如下所示:
MySQL 镜像采纳 pig 我的项目 db 下 Dockerfile 中的版本,也可本人指定。
4) 点击装置:
5) 进入 pig-mysql 服务,编辑内部拜访 , 从而拜访 MySQL 导入 pig 的数据:
6) 进入 MySQL 容器,调整帐号容许从远程登陆:
登录 MySQL 进行受权操作:
$ MySQL -uroot -proot$ use MySQL;$ update user set host='%' where user='root';$ flush privileges;$ ALTER USER 'root'@'%' IDENTIFIED WITH MySQL_native_password BY 'root';$ flush privileges;
7) 利用 Navicat 客户端连贯 pig-mysql 服务,导入数据:
部署 Redis
1) 进入利用商店,在利用分类中抉择数据库和缓存,找到 Redis。如下所示:
注:Pig 中默认应用无明码模式,因而能够临时留空。生产环境不举荐将明码设置为空。
2) 装置胜利后,如下所示:
创立凭证
Pig 所依赖的后端微服务为无状态服务。利用 KubeSphere 服务创立 DevOps 流水线我的项目来部署这些微服务。
1) 创立 kubeconfig 凭证 , 如下所示:
名称自定义,须要和 Jenkinsfile 中的统一即可,内容默认或者去 /root/.kube 下复制。
2) 创立 Harbor 凭证 , 如下所示:
名称自定义,须要和 Jenkinsfile 中的统一即可。
3) 创立 gitlab 凭证 , 如下所示:
名称自定义,须要和 Jenkinsfile 中的统一即可。
全副凭证如下:
设置 harbor 镜像仓库
新建一个 pig-dev 我的项目 , 如下所示:
部署 Pig 后端无状态服务
1) 新建 pig 后端流水线 , 如下所示:
抉择代码仓库:
编辑设置:
2) 代码中创立 Jenkinsfile 文件:
内容如下:
pipeline { agent { label 'maven' } parameters { choice(choices: ['dev', 'test', 'pre', 'pre2','prod'], name: 'Environments', description: '请抉择要公布的环境:dev开发环境、test测试环境、pre预公布环境、pre2灰度环境、prod 生产环境') choice(choices: ['pig-gateway', 'pig-auth', 'pig-register', 'pig-upms-biz','pig-codegen', 'pig-monitor', 'pig-sentinel-dashboard', 'pig-xxl-job-admin','all'], name: 'ServicesDeploy', description: '请抉择要构建的服务,反对单个服务公布或全副服务公布') choice(choices: ['no', 'yes'], name: 'sonarQube', description: '是否进行sonarQube代码质量检查,默认为no') string(name: 'MultiServicesBuild', defaultValue: 'no', description: '自由组合公布服务,如填写pig-gateway,pig-auth等,默认此项不失效,和ServicesDeploy只能选其一') } environment { HARBOR_CREDENTIAL_ID = 'harbor-id' GITLAB_CREDENTIAL_ID = 'gitlab' KUBECONFIG_CREDENTIAL_ID = 'pig-kubeconfig' REGISTRY = 'ip:端口'//harbor镜像仓库 HARBOR_NAMESPACE = 'pig-dev' K8s_NAMESPACE = 'pig-dev' } stages { stage ('拉取代码') { steps { checkout(scm) } } stage('初始化变量') { agent none steps { container('maven') { script { //自由组合公布 if("${params.MultiServicesBuild}".trim() != "no") { ServicesBuild = "${params.MultiServicesBuild}".split(",") for (service in ServicesBuild) { println "now got ${service}" } }else if("${params.ServicesDeploy}".trim() == "all"){ ServicesBuildStr = 'pig-gateway,pig-auth,pig-register,pig-upms-biz,pig-codegen,pig-monitor,pig-sentinel-dashboard,pig-xxl-job-admin' ServicesBuild = "${ServicesBuildStr}".split(",") }else if("${params.ServicesDeploy}".trim() != "all"){ ServicesBuild = "${params.ServicesDeploy}".split(",") } } } } } stage('sonarQube代码质量检查') { steps { script { if("${params.sonarQube}".trim() == "yes") { for (service in ServicesBuild) { def workspace = "pig-" println "以后进行代码质量检查是:${service}" if("${service}".trim() == "pig-gateway" || "${service}".trim() == "pig-auth" || "${service}".trim() == "pig-register"){ workspace = "${workspace}" + "${service}".trim().split("-")[1] } if("${service}".trim() == "pig-codegen" || "${service}".trim() == "pig-monitor" || "${service}".trim() == "pig-sentinel-dashboard" || "${service}".trim() == "pig-xxl-job-admin"){ workspace = "pig-visual/" + "${service}".trim() } if("${service}".trim() == "pig-upms-biz"){ workspace = "pig-upms/" + "${service}".trim() } //定义以后Jenkins的SonarQubeScanner工具 scannerHome = tool 'sonar-scanner' //援用以后JenkinsSonarQube环境 withSonarQubeEnv('sonarqube9.4') { sh """ cd ${workspace} ${scannerHome}/bin/sonar-scanner """ } } }else{ println "是no,跳过sonarQube代码质量检查" } } } } stage('打包') { agent none steps { container('maven') { script { sh "mvn -Dmaven.test.skip=true clean package -P${params.Environments}" } } } } stage('构建镜像') { agent none steps { container('maven') { script { for (service in ServicesBuild) { def workspace = "pig-" println "以后构建的镜像是:${service}" stage ("build ${service}") { if("${service}".trim() == "pig-gateway" || "${service}".trim() == "pig-auth" || "${service}".trim() == "pig-register"){ workspace = "${workspace}" + "${service}".trim().split("-")[1] } if("${service}".trim() == "pig-codegen" || "${service}".trim() == "pig-monitor" || "${service}".trim() == "pig-sentinel-dashboard" || "${service}".trim() == "pig-xxl-job-admin"){ workspace = "pig-visual/" + "${service}".trim() } if("${service}".trim() == "pig-upms-biz"){ workspace = "pig-upms/" + "${service}".trim() } sh "cd ${workspace} && docker build -f Dockerfile -t $REGISTRY/$HARBOR_NAMESPACE/${service}:$BUILD_NUMBER ." } } } } } } stage('镜像推送') { agent none steps { container('maven') { script { for (service in ServicesBuild) { println "以后推送的镜像是:${service}" stage ("push ${service}") { withCredentials([usernamePassword(passwordVariable : 'HARBOR_PASSWORD' ,usernameVariable : 'HARBOR_USERNAME' ,credentialsId : "$HARBOR_CREDENTIAL_ID" ,)]) { sh 'echo "$HARBOR_PASSWORD" | docker login $REGISTRY -u "$HARBOR_USERNAME" --password-stdin' sh "docker push $REGISTRY/$HARBOR_NAMESPACE/${service}:$BUILD_NUMBER" } } } } } } } stage('推送镜像之latest') { agent none steps { container('maven') { script { for (service in ServicesBuild) { println "以后推送的latest镜像是:${service}" stage ("pushLatest ${service}") { sh "docker tag $REGISTRY/$HARBOR_NAMESPACE/${service}:$BUILD_NUMBER $REGISTRY/$HARBOR_NAMESPACE/${service}:latest" sh "docker push $REGISTRY/$HARBOR_NAMESPACE/${service}:latest" } } } } } } stage('部署到dev环境') { steps { container ('maven') { script { for (service in ServicesBuild) { //自定义的全局变量,也就是整个流水线能够去应用 env.APP_NAME = "${service}" if("${service}".trim() == "pig-gateway") { env.NODEPORT = 31201 env.PORT = 9999 } if("${service}".trim() == "pig-auth") { env.NODEPORT = 31202 env.PORT = 3000 } if("${service}".trim() == "pig-register") { env.NODEPORT = 31203 env.PORT = 8848 } if("${service}".trim() == "pig-upms-biz") { env.NODEPORT = 31204 env.PORT = 4000 } if("${service}".trim() == "pig-codegen") { env.NODEPORT = 31205 env.PORT = 5002 } if("${service}".trim() == "pig-monitor") { env.NODEPORT = 31206 env.PORT = 5001 } if("${service}".trim() == "pig-sentinel-dashboard") { env.NODEPORT = 31207 env.PORT = 5003 } if("${service}".trim() == "pig-xxl-job-admin") { env.NODEPORT = 31208 env.PORT = 5004 } stage ("deploy ${service}") { println "行将部署的服务是 $APP_NAME" withCredentials([ kubeconfigFile( credentialsId: env.KUBECONFIG_CREDENTIAL_ID, variable: 'KUBECONFIG') ]) { if("${service}".trim() == "pig-register") { sh "envsubst < deploy/${params.Environments}/nacos-devops.yaml | kubectl apply -f -" }else{ sh "envsubst < deploy/${params.Environments}/devops.yaml | kubectl apply -f -" } } } } } } } } }}
通过 ${service} 来判断最终抉择哪个 deploy 来部署。
3) 代码中创立 devops.yaml 部署文件:
内容如下:
---apiVersion: apps/v1kind: Deploymentmetadata: labels: app: $APP_NAME name: $APP_NAME namespace: $K8s_NAMESPACEspec: progressDeadlineSeconds: 600 replicas: 1 selector: matchLabels: app: $APP_NAME template: metadata: labels: app: $APP_NAME spec: containers: - image: $REGISTRY/$HARBOR_NAMESPACE/$APP_NAME:$BUILD_NUMBER imagePullPolicy: Always name: $APP_NAME ports: - containerPort: $PORT protocol: TCP terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst restartPolicy: Always terminationGracePeriodSeconds: 30---apiVersion: v1kind: Servicemetadata: labels: app: $APP_NAME name: $APP_NAME namespace: $K8s_NAMESPACEspec: ports: - name: http port: $PORT protocol: TCP targetPort: $PORT nodePort: $NODEPORT selector: app: $APP_NAME sessionAffinity: None type: NodePort
4) 代码中创立 nacos-devops.yaml 部署文件:
因为 pig-register 服务是 nacos 服务,其 K8s 的 yaml 部署应该和其余服务不同,采纳 StatefulSet 来部署且正本数为 3,, 并增加绝对应的端口。
内容如下:
---apiVersion: apps/v1kind: StatefulSetmetadata: labels: app: $APP_NAME name: $APP_NAME namespace: $K8s_NAMESPACEspec: serviceName: $APP_NAME replicas: 3 selector: matchLabels: app: $APP_NAME template: metadata: labels: app: $APP_NAME annotations: pod.alpha.kubernetes.io/initialized: "true" spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: "app" operator: In values: - nacos topologyKey: "kubernetes.io/hostname" containers: - image: $REGISTRY/$HARBOR_NAMESPACE/$APP_NAME:$BUILD_NUMBER imagePullPolicy: Always name: $APP_NAME ports: - containerPort: 8848 name: client-port - containerPort: 9848 name: client-rpc - containerPort: 9849 name: raft-rpc - containerPort: 7848 name: old-raft-rpc terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst restartPolicy: Always---apiVersion: v1kind: Servicemetadata: labels: app: $APP_NAME name: $APP_NAME namespace: $K8s_NAMESPACE annotations: service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"spec: ports: - port: 8848 protocol: TCP name: server targetPort: 8848 nodePort: $NODEPORT - port: 9848 name: client-rpc targetPort: 9848 - port: 9849 name: raft-rpc targetPort: 9849 ### 兼容1.4.x版本的选举端口 - port: 7848 name: old-raft-rpc targetPort: 7848 selector: app: $APP_NAME sessionAffinity: None type: NodePort
后续这些文件都可可采纳共享仓库来对立治理。
5) 公布
因为 pig-gateway、pig-auth 和 pig-upms-biz 等其它服务都是依赖 nacos(pig-register) 服务的,所以咱们先公布 pig-register 服务。
进入 DevOps 我的项目 -> pig-dev -> pig-backend-dev -> 运行。
这里 KubeSphere3.3.0 版本中有个 bug,choice 类型的还是辨认为 string,所以临时只能手动输出。此 bug 将会在 3.3.1 版本修复。
查看工作状态:
查看日志:
在我的项目 -> 利用负载 -> 服务下查看刚公布的 pig-register 服务:
进入 DevOps 我的项目 -> pig-dev -> pig-backend-dev -> 运行 -> 抉择自由组合公布:
公布实现查看服务:
至此曾经实现 Pig 后端无状态服务的部署。
部署 Pig 前端无状态服务
1) 新建 pig 前端流水线 , 如下所示:
抉择代码仓库:
编辑设置:
2) 代码中创立 Jenkinsfile 文件:
内容如下:
pipeline { agent { node { label 'nodejs' } } parameters { choice(choices: ['dev', 'test', 'pre', 'pre2','prod'], name: 'Environments', description: '请抉择要公布的环境:dev开发环境、test测试环境、pre预公布环境、pre2灰度环境、prod 生产环境') choice(choices: ['no', 'yes'], name: 'sonarQube', description: '是否进行sonarQube代码质量检查,默认为no') } environment { HARBOR_CREDENTIAL_ID = 'harbor-id' GITLAB_CREDENTIAL_ID = 'gitlab' KUBECONFIG_CREDENTIAL_ID = 'pig-kubeconfig' REGISTRY = 'ip:端口'//harbor镜像仓里 HARBOR_NAMESPACE = 'pig-dev' APP_NAME = 'pig-front' K8s_NAMESPACE = 'pig-dev' } stages { stage ('拉取代码') { steps { container('nodejs') { checkout(scm) } } } stage('sonarQube代码质量检查') { steps { script { if("${params.sonarQube}".trim() == "yes") { println "以后进行代码质量检查是:${APP_NAME}" //定义以后Jenkins的SonarQubeScanner工具 scannerHome = tool 'sonar-scanner' //援用以后JenkinsSonarQube环境 withSonarQubeEnv('sonarqube9.4') { sh """ cd . ${scannerHome}/bin/sonar-scanner """ } }else{ println "是no,跳过sonarQube代码质量检查" } } } } stage('我的项目编译') { agent none steps { container('nodejs') { sh 'node -v' sh 'npm -v' sh 'npm install' sh 'npm run build:docker' sh 'ls' } } } stage('构建镜像') { agent none steps { container('nodejs') { sh 'ls' sh 'cd ./docker && docker build -t $REGISTRY/$HARBOR_NAMESPACE/$APP_NAME:$APP_NAME-$BUILD_NUMBER .' } } } stage('镜像推送') { agent none steps { container('nodejs') { withCredentials([usernamePassword(passwordVariable : 'HARBOR_PASSWORD' ,usernameVariable : 'HARBOR_USERNAME' ,credentialsId : "$HARBOR_CREDENTIAL_ID" ,)]) { sh 'echo "$HARBOR_PASSWORD" | docker login $REGISTRY -u "$HARBOR_USERNAME" --password-stdin' sh 'docker push $REGISTRY/$HARBOR_NAMESPACE/$APP_NAME:$APP_NAME-$BUILD_NUMBER' } } } } stage('推送镜像之latest') { agent none steps { container('nodejs') { sh 'docker tag $REGISTRY/$HARBOR_NAMESPACE/$APP_NAME:$APP_NAME-$BUILD_NUMBER $REGISTRY/$HARBOR_NAMESPACE/$APP_NAME:latest ' sh 'docker push $REGISTRY/$HARBOR_NAMESPACE/$APP_NAME:latest ' } } } stage('部署到dev环境') { steps { container ('nodejs') { withCredentials([ kubeconfigFile( credentialsId: env.KUBECONFIG_CREDENTIAL_ID, variable: 'KUBECONFIG') ]) { sh "envsubst < deploy/${params.Environments}/devops.yaml | kubectl apply -f -" } } } } }}
3) 代码中创立 devops.yaml 部署文件:
内容如下:
apiVersion: apps/v1kind: Deploymentmetadata: labels: app: $APP_NAME name: $APP_NAME namespace: $K8s_NAMESPACEspec: progressDeadlineSeconds: 600 replicas: 1 selector: matchLabels: app: pig-front strategy: rollingUpdate: maxSurge: 50% maxUnavailable: 50% type: RollingUpdate template: metadata: labels: app: pig-front spec: containers: - image: $REGISTRY/$HARBOR_NAMESPACE/$APP_NAME:$APP_NAME-$BUILD_NUMBER imagePullPolicy: Always name: pig-front-end ports: - containerPort: 80 protocol: TCP terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst restartPolicy: Always terminationGracePeriodSeconds: 30---apiVersion: v1kind: Servicemetadata: labels: app: $APP_NAME name: $APP_NAME namespace: $K8s_NAMESPACEspec: ports: - name: http port: 80 protocol: TCP targetPort: 80 nodePort: 31200 selector: app: $APP_NAME sessionAffinity: None type: NodePort
后续这些文件都可可采纳共享仓库来对立治理。
4) 公布:
这里 KubeSphere3.3.0 版本中有个 bug,choice 类型的还是辨认为 string,此 bug 将会在 3.3.1 版本修复。
查看工作状态:
查看日志:
在我的项目 -> 利用负载 -> 服务下查看刚公布的 pig-front 服务。
至此所有的服务均已公布实现。
利用 KubeSphere 中的 Jenkins 公布
拜访 ip:30180
(账号:admin,明码:[email protected]):
能够关上 Blue Ocean 查看状态:
通过 NodePort 形式裸露集群外部容器服务
NodePort 设计之初就不倡议用于生产环境裸露服务,所以默认端口都是一些大端口,如下:
输出 node ip + 31200 拜访:
优化和改良
通过探针优雅的解决部署过程中服务平滑过渡问题
若是只有一个正本的状况下,新的 Pod 启动胜利时,开始停掉旧的 Pod, 然而咱们看到的 running 状态,并不认为着咱们的服务是失常的。若是这个时候杀死旧的 Pod, 那么将有新的 Pod 承受申请,这个时候会呈现服务短暂不可用状态,所以须要减少探来确保咱们的服务曾经失常了,能够接管并解决用户申请。咱们罕用的探针如下:
livenessProbe:存活性探测
许多应用程序通过长时间运行,最终过渡到无奈运行的状态,除了重启,无奈复原。通常状况下,K8s 会发现应用程序曾经终止,而后重启应用程序 pod。有时应用程序可能因为某些起因(后端服务故障等)导致临时无奈对外提供服务,但应用软件没有终止,导致 K8s 无奈隔离有故障的 pod,调用者可能会拜访到有故障的 pod,导致业务不稳固。K8s 提供 livenessProbe 来检测容器是否失常运行,并且对相应情况进行相应的补救措施。
readinessProbe:就绪性探测
在没有配置 readinessProbe 的资源对象中,pod 中的容器启动实现后,就认为 pod 中的应用程序能够对外提供服务,该 pod 就会退出绝对应的 service,对外提供服务。但有时一些应用程序启动后,须要较长时间的加载能力对外服务,如果这时对外提供服务,执行后果必然无奈达到预期成果,影响用户体验。比方应用 tomcat 的应用程序来说,并不是简略地说 tomcat 启动胜利就能够对外提供服务的,还须要期待 spring 容器初始化,数据库连贯上等等。
1) SpringBoot 的 actuator
其实 actuator 是用来帮忙用户监控和操作 SprinBoot 利用的,这些监控和操作都能够通过 http 申请实现,如下图,http://localhost:7777/actuato... 地址返回的是利用的衰弱状态。
需引以下 maven:
<!-- 引入Actuator监控依赖 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency>
在 SpringBoot-2.3 版本中,actuator 新增了两个地址:/actuator/health/liveness 和 /actuator/health/readiness,前者用作 Kubernetes 的存活探针,后者用作 Kubernetes 的就绪探针 , 须要先在配置文件中开启,如下:
management: endpoint: health: probes: enabled: true health: livenessstate: enabled: true readinessstate: enabled: true
/actuator/health/ 和 /actuator/health/ 是默认开启的。
利用 SpringBoot 的接口来作为容器探针的衰弱检测 , 依照如下就能够:
readinessProbe:initialDelaySeconds: 20periodSeconds: 10timeoutSeconds: 5failureThreshold: 6httpGet: scheme: HTTP port: 9999 path: /actuator/health/readinesslivenessProbe:initialDelaySeconds: 30periodSeconds: 10timeoutSeconds: 5failureThreshold: 6httpGet: scheme: HTTP port: 9999 path: /actuator/health/liveness
2) pig 后端我的项目减少探针
pig 我的项目全局所有的模块都会引入 Actuator 监控依赖,如下:
<!--监控--><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency>
调整 pig 我的项目后端 devops.yaml, 减少以下内容:
... readinessProbe: initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 httpGet: scheme: HTTP port: $PORT path: /actuator/health livenessProbe: initialDelaySeconds: 40 periodSeconds: 10 timeoutSeconds: 5 httpGet: scheme: HTTP port: $PORT path: /actuator/health...
残缺 devops.yaml 内容如下:
---apiVersion: apps/v1kind: Deploymentmetadata: labels: app: $APP_NAME name: $APP_NAME namespace: $K8s_NAMESPACEspec: progressDeadlineSeconds: 600 replicas: 1 selector: matchLabels: app: $APP_NAME template: metadata: labels: app: $APP_NAME spec: containers: - image: $REGISTRY/$HARBOR_NAMESPACE/$APP_NAME:$BUILD_NUMBER imagePullPolicy: Always name: $APP_NAME ports: - containerPort: $PORT protocol: TCP terminationMessagePath: /dev/termination-log terminationMessagePolicy: File readinessProbe: initialDelaySeconds: 90 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 httpGet: scheme: HTTP port: $PORT path: /actuator/health livenessProbe: initialDelaySeconds: 100 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 httpGet: scheme: HTTP port: $PORT path: /actuator/health dnsPolicy: ClusterFirst restartPolicy: Always terminationGracePeriodSeconds: 30---apiVersion: v1kind: Servicemetadata: labels: app: $APP_NAME name: $APP_NAME namespace: $K8s_NAMESPACEspec: ports: - name: http port: $PORT protocol: TCP targetPort: $PORT nodePort: $NODEPORT selector: app: $APP_NAME sessionAffinity: None type: NodePort
注: /actuator/health/readiness 和 /actuator/health/liveness 也能够用,需在配置文件中开启。若是内存、CPU 限度过低,须要调整 initialDelaySeconds 工夫,否则会呈现还未启动胜利,就开始探测,会进入循环,直到探测失败(就是 failureThreshold 定义的次数),要把握好这个工夫的度。
从新公布 pig-geteway 测试:
正在进行探测:
探测胜利,新的 Pod 可用,旧的 Pod 删除:
3) pig 前端我的项目减少探针
前端我的项目咱们间接探测 Nginx 的端口即可。调整 devops.yaml, 减少如下内容:
readinessProbe: initialDelaySeconds: 20 periodSeconds: 10 timeoutSeconds: 5 httpGet: scheme: HTTP port: 80 path: / livenessProbe: initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 httpGet: scheme: HTTP port: 80 path: /
残缺 devops.yaml 如下:
apiVersion: apps/v1kind: Deploymentmetadata: labels: app: $APP_NAME name: $APP_NAME namespace: $K8s_NAMESPACEspec: progressDeadlineSeconds: 600 replicas: 1 selector: matchLabels: app: pig-front strategy: rollingUpdate: maxSurge: 50% maxUnavailable: 50% type: RollingUpdate template: metadata: labels: app: pig-front spec: containers: - image: $REGISTRY/$HARBOR_NAMESPACE/$APP_NAME:$APP_NAME-$BUILD_NUMBER imagePullPolicy: Always name: pig-front-end ports: - containerPort: 80 protocol: TCP terminationMessagePath: /dev/termination-log terminationMessagePolicy: File readinessProbe: initialDelaySeconds: 20 periodSeconds: 10 timeoutSeconds: 5 httpGet: scheme: HTTP port: 80 path: / livenessProbe: initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 httpGet: scheme: HTTP port: 80 path: / dnsPolicy: ClusterFirst restartPolicy: Always terminationGracePeriodSeconds: 30---apiVersion: v1kind: Servicemetadata: labels: app: $APP_NAME name: $APP_NAME namespace: $K8s_NAMESPACEspec: ports: - name: http port: 80 protocol: TCP targetPort: 80 nodePort: 31200 selector: app: $APP_NAME sessionAffinity: None type: NodePort
通过探针优雅的解决服务部署过程中注册核心服务平滑过渡问题
咱们后端采纳 Spring Cloud(Spring Cloud Alibaba)微服务构造技术路线进行开发,采纳 Nacos 作为注册核心。而服务注册到 Nacos 是须要工夫的。而个别的容器探测只是探测服务是否达到了可用的状态,没有思考到注册到注册核心的服务是否可用,其实在多正本的状况下只有管制好滚动更新策略,应该不会呈现这种状况的。在 Nacos 中也是有负载平衡的,Nacos 实现负载平衡是通过内置的 Ribbon 实现的。像 Gateway 网关服务尤其重要,因为它还要负责服务转发。所以保障这种服务的可用性也变得尤为重要。
例如单正本 Gateway 网关服务,若是在开始探测时,网关服务并没有及时注册到注册中心里,这个时候开始测探,服务自身是可用的,那么探测胜利后,新的 Pod 变成了 running 状态,便停掉了第一个 Pod,而注册核心中的服务其实并不可用,会呈现短暂服务不可用。
其实衰弱探测咱们还能够往前走一步,把服务胜利注册到注册核心中作为服务可用的状态。
本人编写容器探针接口:
/** * @author 小盒子 * @version 1.0 * @description: 容器探针接口,用来进行探测服务是否注册进入注册核心 * @date 2022/10/22 10:58 */@Slf4j@RestController@RequestMapping("nacos")public class HealthController { @Autowired private DiscoveryClient discoveryClient; @GetMapping("/health/{services}") public ResponseEntity<Object> getService(@PathVariable("services")String services) throws NacosException { //从nacos中依据serverId获取实例 办法一(采纳此办法,DiscoveryClient 代表的就是:服务发现操作对象) if (StringUtils.isBlank(services)){ return new ResponseEntity<Object>(HttpStatus.BAD_REQUEST); } List<ServiceInstance> instances = discoveryClient.getInstances(services); Map<String, Integer> ipMap = new HashMap<>(); if (instances.size() > 0){ instances.forEach(key ->{ ipMap.put(key.getHost(), key.getPort()); }); } //从nacos中依据serverId获取实例 办法二 采纳nacos的java的SDK// Properties properties = new Properties();// properties.put("serverAddr", "192.168.4.25:8849");// properties.put("namespace", "caseretrieval-dev");// NamingService naming = NamingFactory.createNamingService(properties);// List<Instance> instancesList = naming.selectInstances("case-gateway", true); //从nacos中依据serverId获取实例 办法三,采纳nacos的OPEN-api //http://192.168.4.25:8849/nacos/v1/ns/instance/list?serviceName=case-gateway&namespaceId=caseretrieval-dev //获取本机IP地址 String hostAddress = "127.0.0.1"; try { InetAddress localHost = InetAddress.getLocalHost(); hostAddress = localHost.getHostAddress(); log.info("以后服务本机ip是:"+hostAddress); } catch (UnknownHostException e) { log.error(e.getMessage(), e); } //查看本机服务是否曾经注册到nacos中 if (ipMap.containsKey(hostAddress)){ return new ResponseEntity<Object>(HttpStatus.OK); }else { return new ResponseEntity<Object>(HttpStatus.SERVICE_UNAVAILABLE); } }}
其实失常状况下,正当的滚动更新策略,加上就绪性探测、存活性探测或者启动探测就能达到目标了,也不用这么麻烦。
通过 Ingress 形式裸露集群外部容器服务
如果须要从集群内部拜访服务,行将服务裸露给用户应用,KubernetesService 自身提供了两种形式,一种是 NodePort ,另外一种是 LoadBalancer 。另外 Ingress 也是一种罕用的裸露服务的形式。
KubeSphere 的我的项目网关就是 IngressController ,是一个七层负载平衡调度器,客户端的申请先达到这个七层负载平衡调度器,KubeSphere 中的利用路由就是 Ingress ,是定义规定的。数据包走向如下图流程所示:
开启我的项目网关
对我的项目中的外网拜访网关以及服务治理等配置进行设置和治理。
KubeSphere 中的我的项目网关,开启后会主动装置 IngressController。
对应在 K8s 集群中 svc 如下图所示:
KubeSphere 中利用路由是须要手动创立的,如下图所示:
设置路由规定
指定自定义域名并通过本地 hosts 文件或 DNS 服务器将域名解析为网关 IP 地址。
拜访 http://pig.com:32576/
将 SonarQube 集成到流水线
SonarQube 是一种支流的代码品质继续检测工具。您能够将其用于代码库的动态和动态分析。SonarQube 集成到 KubeSphere(Jenkins) 流水线后,如果在运行的流水线中检测到问题,您能够间接在仪表板上查看常见代码问题,例如 Bug 和破绽。
在日常开发中 SonarQube 往往是基于现有的 Gitlab、Jenkins 集成配合应用的,以便在我的项目拉取代码后进行代码审查。若是存在多套环境的状况下,例如有开发环境、测试环境、生产环境等,我是不倡议在生产环境中去进行代码审查的,一来进行反复的代码审查没有意义,二来比拟浪费时间,所以倡议只在开发环境开启代码审查即可。
在 Jenkins 中集成 SonarQube
微服务项目中须要在每个我的项目下都建设 sonar-project.properties 文件,在 Jenkins 的脚本中须要循环去 checking 每个微服务。
1) 在 sonarQube 中生成 token
2) 在 Jenkins 中装置 SonarQube Scanner 插件
3) 在 Jenkins 中增加 SonarQube 凭证
抉择 Secret text 作为凭证类型。
实现后如下所示:
4) 在 Jenkins 系统配置中配置 SonarQube
5) 在 Jenkins 全工具中装置 SonarQube Scanner
sonar-scanner 名称自定义,在 jenkinsfile 中要放弃始终。
6) 敞开审查后果上传 SCM 性能
后端代码审查
1) 调整 Jenkins 脚本
微服务项目中须要在每个我的项目下都建设 sonar-project.properties 文件,在 Jenkins 的脚本中须要循环去 checking 每个微服务。
如以下示例:
stage('sonarQube代码质量检查') { steps { script { if("${params.sonarQube}".trim() == "yes") { for (service in ServicesBuild) { def workspace = "pig-" println "以后进行代码质量检查是:${service}" if("${service}".trim() == "pig-gateway" || "${service}".trim() == "pig-auth" || "${service}".trim() == "pig-register"){ workspace = "${workspace}" + "${service}".trim().split("-")[1] } if("${service}".trim() == "pig-codegen" || "${service}".trim() == "pig-monitor" || "${service}".trim() == "pig-sentinel-dashboard" || "${service}".trim() == "pig-xxl-job-admin"){ workspace = "pig-visual/" + "${service}".trim() } if("${service}".trim() == "pig-upms-biz"){ workspace = "pig-upms/" + "${service}".trim() } //定义以后Jenkins的SonarQubeScanner工具 scannerHome = tool 'sonar-scanner' //援用以后JenkinsSonarQube环境 withSonarQubeEnv('sonarqube9.4') { sh """ cd ${workspace} ${scannerHome}/bin/sonar-scanner """ } } }else{ println "是no,跳过sonarQube代码质量检查" } } } }
注:sonar-scanner 和 sonarqube9.4 名称需和 Jenkins 中配置的统一。
2) 新建 sonar-project.properties 文件
以 pig-gateway 为例,在 pig 我的项目中新增 sonar-project.properties 文件。
内容如下:
#我的项目的key(自定义)sonar.projectKey=pig-gateway#项目名称sonar.projectName=pig-gateway#我的项目版本号sonar.projectVersion=1.0#须要剖析的源码的目录,多个目录用英文逗号隔开sonar.sources=.#须要疏忽的目录sonar.exclusions=**/test/**,**/target/**## 字节码文件所在位置sonar.java.binaries=.sonar.java.source=1.8sonar.java.target=1.8#sonar.java.libraries=**/target/classes/**## Encoding of the source code. Default is default system encodingsonar.sourceEncoding=UTF-8
公布测试:
在 Jenkins 中查看:
在 SonarQube 中查看代码检测状况:
pig-gateway 的代码品质还是很错的,不过有一个破绽哦。
前端代码审查
1) 调整 Jenkins 脚本
stage('sonarQube代码质量检查') { steps { script { if("${params.sonarQube}".trim() == "yes") { println "以后进行代码质量检查是:${APP_NAME}" //定义以后Jenkins的SonarQubeScanner工具 scannerHome = tool 'sonar-scanner' //援用以后JenkinsSonarQube环境 withSonarQubeEnv('sonarqube9.4') { sh """ cd . ${scannerHome}/bin/sonar-scanner """ } }else{ println "是no,跳过sonarQube代码质量检查" } } } }
2) 新建 sonar-project.properties 文件
内容如下:
sonar.projectKey=pig_uisonar.projectName=pig_uisonar.projectVersion=1.0sonar.sources=.
公布测试:
在 Jenkins 中查看:
在 SonarQube 中查看代码检测状况:
本文由博客一文多发平台 OpenWrite 公布!