作者:何昌濤,北京北大英華科技有限公司高級 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
- 進入應用商店,在應用分類中選擇資料庫和快取,找到 MySQL,如下所示:


- 在基本資訊中,填寫應用名稱 pig-MySQL, 并選擇位置,進行下一步,如下所示:

- 在應用配置中,編輯 yaml 檔案 , 將鏡像改為 MySQL/MySQL-server:8.0.30,將密碼設定為 root,如下所示:

MySQL 鏡像采用 pig 專案 db 下 Dockerfile 中的版本,也可自己指定,
- 點擊安裝:

- 進入 pig-mysql 服務,編輯外部訪問 , 從而訪問 MySQL 匯入 pig 的資料:



- 進入 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;
- 利用 Navicat 客戶端連接 pig-mysql 服務,匯入資料:

部署 Redis
- 進入應用商店,在應用分類中選擇資料庫和快取,找到 Redis,如下所示:



注:Pig 中默認使用無密碼模式,因此可以暫時留空,生產環境不推薦將密碼設定為空,
- 安裝成功后,如下所示:

創建憑證
Pig 所依賴的后端微服務為無狀態服務,利用 KubeSphere 服務創建 DevOps 流水線專案來部署這些微服務,
- 創建 kubeconfig 憑證 , 如下所示:

名稱自定義,需要和 Jenkinsfile 中的一致即可,內容默認或者去 /root/.kube 下復制,
- 創建 Harbor 憑證 , 如下所示:

名稱自定義,需要和 Jenkinsfile 中的一致即可,
- 創建 gitlab 憑證 , 如下所示:

名稱自定義,需要和 Jenkinsfile 中的一致即可,
全部憑證如下:

設定 harbor 鏡像倉庫
新建一個 pig-dev 專案 , 如下所示:

部署 Pig 后端無狀態服務
- 新建 pig 后端流水線 , 如下所示:

選擇代碼倉庫:

編輯設定:

- 代碼中創建 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 來部署,
- 代碼中創建 devops.yaml 部署檔案:

內容如下:
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: $APP_NAME
name: $APP_NAME
namespace: $K8s_NAMESPACE
spec:
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: v1
kind: Service
metadata:
labels:
app: $APP_NAME
name: $APP_NAME
namespace: $K8s_NAMESPACE
spec:
ports:
- name: http
port: $PORT
protocol: TCP
targetPort: $PORT
nodePort: $NODEPORT
selector:
app: $APP_NAME
sessionAffinity: None
type: NodePort
- 代碼中創建 nacos-devops.yaml 部署檔案:
由于 pig-register 服務是 nacos 服務,其 K8s 的 yaml 部署應該和其他服務不同,采用 StatefulSet 來部署且副本數為 3,, 并添加相對應的埠,
內容如下:
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
labels:
app: $APP_NAME
name: $APP_NAME
namespace: $K8s_NAMESPACE
spec:
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: v1
kind: Service
metadata:
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
后續這些檔案都可可采用共享倉庫來統一管理,
- 發布
由于 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 前端無狀態服務
- 新建 pig 前端流水線 , 如下所示:

選擇代碼倉庫:

編輯設定:

- 代碼中創建 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 -"
}
}
}
}
}
}
- 代碼中創建 devops.yaml 部署檔案:

內容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: $APP_NAME
name: $APP_NAME
namespace: $K8s_NAMESPACE
spec:
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: v1
kind: Service
metadata:
labels:
app: $APP_NAME
name: $APP_NAME
namespace: $K8s_NAMESPACE
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
nodePort: 31200
selector:
app: $APP_NAME
sessionAffinity: None
type: NodePort
后續這些檔案都可可采用共享倉庫來統一管理,
- 發布:

這里 KubeSphere3.3.0 版本中有個 bug,choice 型別的還是識別為 string,此 bug 將會在 3.3.1 版本修復,

查看任務狀態:

查看日志:

在專案 -> 應用負載 -> 服務下查看剛發布的 pig-front 服務,

至此所有的服務均已發布完成,

利用 KubeSphere 中的 Jenkins 發布
訪問 ip:30180(賬號:admin,密碼:P@88w0rd):


可以打開 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/actuator/health 地址回傳的是應用的健康狀態,

需引以下 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: 20
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
httpGet:
scheme: HTTP
port: 9999
path: /actuator/health/readiness
livenessProbe:
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
httpGet:
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/v1
kind: Deployment
metadata:
labels:
app: $APP_NAME
name: $APP_NAME
namespace: $K8s_NAMESPACE
spec:
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: v1
kind: Service
metadata:
labels:
app: $APP_NAME
name: $APP_NAME
namespace: $K8s_NAMESPACE
spec:
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/v1
kind: Deployment
metadata:
labels:
app: $APP_NAME
name: $APP_NAME
namespace: $K8s_NAMESPACE
spec:
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: v1
kind: Service
metadata:
labels:
app: $APP_NAME
name: $APP_NAME
namespace: $K8s_NAMESPACE
spec:
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 每個微服務,
- 在 sonarQube 中生成 token

- 在 Jenkins 中安裝 SonarQube Scanner 插件

- 在 Jenkins 中添加 SonarQube 憑證

選擇 Secret text 作為憑證型別,
完成后如下所示:

- 在 Jenkins 系統配置中配置 SonarQube

- 在 Jenkins 全工具中安裝 SonarQube Scanner

sonar-scanner 名稱自定義,在 jenkinsfile 中要保持一直,
- 關閉審查結果上傳 SCM 功能

后端代碼審查
- 調整 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 中配置的一致,
- 新建 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.8
sonar.java.target=1.8
#sonar.java.libraries=**/target/classes/**
## Encoding of the source code. Default is default system encoding
sonar.sourceEncoding=UTF-8
發布測驗:

在 Jenkins 中查看:

在 SonarQube 中查看代碼檢測情況:

pig-gateway 的代碼質量還是很錯的,不過有一個漏洞哦,
前端代碼審查
- 調整 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代碼質量檢查"
}
}
}
}
- 新建 sonar-project.properties 檔案

內容如下:
sonar.projectKey=pig_ui
sonar.projectName=pig_ui
sonar.projectVersion=1.0
sonar.sources=.
發布測驗:

在 Jenkins 中查看:

在 SonarQube 中查看代碼檢測情況:

本文由博客一文多發平臺 OpenWrite 發布!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/524988.html
標籤:其他
