共计 4976 个字符,预计需要花费 13 分钟才能阅读完成。
最近,因为业务属性比拟重要,对服务公布提出了更高的要求,心愿能实现不停服公布。目前,团队所有我的项目曾经实现基于 K8s 容器化部署,服务注册发现基于 Nacos,故本文基于该两前提下进行探讨。
基于该架构下,须要解决如下几个问题:
- K8s Java 利用实现滚动公布,如果新服务不失常的状况下,不将新服务公布下来,且旧服务不下线
- 服务从 Nacos 上被动下线,让流量不再流入
K8s 滚动公布
K8s 已人造反对滚动公布的机制,只须要简略的配置就能够实现咱们的要求,如下是具体配置摘要,我将从上到下进行阐明。
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: example-service
name: example-service
spec:
#正本数量
replicas: {{.pod_replicas}}
selector:
matchLabels:
app: example-service
minReadySeconds: 30 #设置降级延迟时间 15 秒, 期待 15 秒后降级
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 #降级过程中最多能够比原先设置多出的 POD 数量
maxUnavailable: 0
template:
metadata:
labels:
app: example-service
monitortype: backend
spec:
#以下内容为可选, 容器调度策略,保障同一 deployment 的多个正本位于不同的机器上,避免单节点挂掉导致服务不可用, 因为波及到要与运维沟通资源状况, 无奈间接给予固定配置。affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- example-service
topologyKey: kubernetes.io/hostname
containers:
- image: example-service:latest
imagePullPolicy: IfNotPresent
name: example-service
lifecycle:
preStop:
exec:
command:
- curl
- '-XPOST'
- '127.0.0.1:8080/actuator/shutdown'
readinessProbe: # 就绪探针
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30 # 提早加载工夫
periodSeconds: 10 # 重试工夫距离
timeoutSeconds: 1 # 超时工夫设置
successThreshold: 1 # 衰弱阈值
failureThreshold: 3 # 不衰弱阈值
livenessProbe: # 存活探针
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30 # 提早加载工夫
periodSeconds: 10 # 重试工夫距离
timeoutSeconds: 1 # 超时工夫设置
successThreshold: 1 # 衰弱阈值
failureThreshold: 3 # 不衰弱阈值
ports:
- containerPort: 8080
name: backend
protocol: TCP
envFrom:
- configMapRef:
#寄存公共环境变量,比方:数据库,redis,nacos 等连贯信息,每个我的项目的各个微服务根本都是一样的。name: pub-cm
- configMapRef:
#寄存个性化配置,对于个别的服务,除了公共变量外,会波及其余援用信息。name: example-service-cm
env:
- name: POD_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
resources:
#设定以下是设定服务资源依据你的我的项目理论状况来
limits: # limits 是代表的资源下限, 服务能耗费的资源下限
cpu: {{.pod_cpu_limit}} #{limits_cpu}(必须), 目前默认单位为 m, 如果申请一核则为 1024m, 以此类推, 默认则为 500m
memory: {{.pod_memory_limit}} #{limits_mem}(必须), 目前默认单位为 Mi, 如果申请 1G 内存则为 1024Mi, 以此类推, 默认则为 2048Mi
requests: # requests 是服务所需最小的启动资源, 设置后如果 node 达不到这个资源要求就会部署失败
cpu: {{.pod_cpu_request}} #{requests_cpu}(必须), 目前默认单位为 m, 如果申请一核则为 1024m, 以此类推, 默认则为 250m
memory: {{.pod_memory_request}} #{requests_mem}(必须), 目前默认单位为 Mi, 如果申请 1G 内存则为 1024Mi, 以此类推, 默认则为 500Mi
# 以下为必须选项, 我的项目做日志采集
volumeMounts:
- name: host-time
readOnly: true
mountPath: /etc/localtime
- name: example-service-log
mountPath: /home/logs # 如果接入日志必须存在容器内 /home/logs 文件夹下寄存日志文件
subPathExpr: $(POD_NAME)
volumes:
- name: example-service-log
hostPath:
path: /home/logs
type: DirectoryOrCreate
- name: host-time
hostPath:
path: /etc/localtime
type: ''
imagePullSecrets: # 写死, 前提是要执行这个凭证创立命令
- name: harborha-secret001fe
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget #设置 pod 最小可用数量
metadata:
name: example-service-pdb
spec:
minAvailable: 50%
selector:
matchLabels:
app: example-service
RollingUpdate 用于配置服务滚动降级的策略,maxSurge 设置降级过程中最多能够比原先设置多出的 POD 数量。
preStop 在容器下线前执行的操作,我这边是心愿他先调用 Spring Boot Actuator 提供的下线接口,让服务失常业务解决完后,下线掉。
readinessProbe 与 livenessProbe 都是探针,不同的中央是 readinessProbe 在容器启动时,会查看服务是否启动齐全和失常,失常后 Pod 才会被显示失常,这种在应用 Service 或者 Ingress 的时候十分有用,livenessProbe 则是周期性查看服务的衰弱性,如果服务不衰弱将下线掉服务。
须要留神 readinessProbe 的 initialDelaySeconds 是在服务启动时开始计时,基于服务自身启动工夫设置一个绝对正当的工夫,以进步成功率。
PodDisruptionBudget 该控制器次要是通过设置利用 Pod 处于失常状态的最低个数或最低百分比,这样能够保障在被动销毁 Pod 的时候,不会销毁太多的 Pod 导致业务异常中断,从而进步业务的可用性。
PodDisruptionBudget 与 Deployment 中 RollingUpdate 配置阐明
在滚动更新的时候,会依据 RollngUpdate 配置来,在 Eviction(被动驱赶爱护,e.g. 存在不衰弱的节点,下线服务)会依据 PDB 策略来。
Nacos 被动下线
咱们设想一种场景,A 服务通过 Nacos 服务注册发现 LoadBalance 形式间接调用 B 服务,即便 B 服务容器曾经被销毁,但如果 A 服务中还存在旧 B 服务的地址,那么就会调用异样,所以咱们心愿 B 服务下线的时候,A 服务是有感知的,故咱们抉择在被动告诉 Nacos 服务下线服务,同时,由 Nacos 去告诉其余服务下线告诉。
新建一个 Endpoint,该形式是基于 Spring Boot 2.7.X 版本的,其余版本可能有所区别。
@WebEndpoint(id = "deregister")
@Slf4j
@ConditionalOnClass(value = WebEndpoint.class)
public class NacosServiceDeregisterEndpoint {
private final NacosDiscoveryProperties nacosDiscoveryProperties;
private final NacosRegistration nacosRegistration;
private final NacosServiceRegistry nacosServiceRegistry;
public NacosServiceDeregisterEndpoint(NacosDiscoveryProperties nacosDiscoveryProperties, NacosRegistration nacosRegistration, NacosServiceRegistry nacosServiceRegistry) {
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
this.nacosRegistration = nacosRegistration;
this.nacosServiceRegistry = nacosServiceRegistry;
}
@ReadOperation
public String deregisterEndPoint() {String serviceName = nacosDiscoveryProperties.getService();
String groupName = nacosDiscoveryProperties.getGroup();
String clusterName = nacosDiscoveryProperties.getClusterName();
String ip = nacosDiscoveryProperties.getIp();
int port = nacosDiscoveryProperties.getPort();
log.info("Deregister from the Nacos, ServiceName:{}, GroupName:{}, ClusterName:{}, IP:{}, Port:{}", serviceName, groupName, clusterName, ip, port);
// 设置服务下线
nacosServiceRegistry.setStatus(nacosRegistration, "DOWN");
return "success";
}
}
减少和批改 bootstrap.yaml
配置
management:
endpoint:
health:
show-details: always
probes:
enabled: true
metrics:
enabled: true
shutdown:
enabled: true
endpoints:
web:
exposure:
include: "health,shutdown,metrics,deregister"
server:
port: 8080
metrics:
tags:
application: ${spring.application.name}
后续
其实上方咱们还脱漏一个问题——“服务更新时,服务曾经胜利注册到 Nacos,但容器还没有被检测为衰弱,此时流量打到新节点上。”这种状况应该怎么解决。其实不必苦恼,Nacos 本身曾经能保障在服务启动齐全之后,才会注册到 Nacos 服务列表上。