Kubernetes在微服务中的最佳实践
- - DockOne.io你可以在网上找到许多关于如何正确构建微服务体系架构的最佳实践. 其中之一是我以前写的一篇文章 Spring Boot在微服务中的最佳实践. 我把重点放在在生产上基于Spring Boot构建的微服务应用程序应该考虑哪些方面. 我没有使用任何用于编排或管理应用程序的平台,而只是一组独立的应用程序. 在本文中,我将基于已经介绍的最佳实践,在Kubernetes平台上部署微服务,你需要注意的一些新规则和事项.
org.springframework.boot
spring-boot-starter-actuator
io.micrometer
micrometer-registry-prometheus
management.endpoints.web.exposure.include: '*'
/actuator/prometheus
。 prometheus.yml
文件应该包含 metrics_path
和 kubernetes_sd_configs
。Prometheus试图通过Kubernetes Endpoints来发现应用程序的Pod。应用程序应该使用 app=sample-spring-kotlin-microservice
进行标记,并公开端口。 apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus
labels:
name: prometheus
data:
prometheus.yml: |-
scrape_configs:
- job_name: 'springboot'
metrics_path: /actuator/prometheus
scrape_interval: 5s
kubernetes_sd_configs:
- role: endpoints
namespaces:
names:
- default
relabel_configs:
- source_labels: [__meta_kubernetes_service_label_app]
separator: ;
regex: sample-spring-kotlin-microservice
replacement: $1
action: keep
- source_labels: [__meta_kubernetes_endpoint_port_name]
separator: ;
regex: http
replacement: $1
action: keep
- source_labels: [__meta_kubernetes_namespace]
separator: ;
regex: (.*)
target_label: namespace
replacement: $1
action: replace
- source_labels: [__meta_kubernetes_pod_name]
separator: ;
regex: (.*)
target_label: pod
replacement: $1
action: replace
- source_labels: [__meta_kubernetes_service_name]
separator: ;
regex: (.*)
target_label: service
replacement: $1
action: replace
- source_labels: [__meta_kubernetes_service_name]
separator: ;
regex: (.*)
target_label: job
replacement: ${1}
action: replace
- separator: ;
regex: (.*)
target_label: endpoint
replacement: http
action: replace
--config.file=/prometheus2/prometheus.yml
。 apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus
labels:
app: prometheus
spec:
replicas: 1
selector:
matchLabels:
app: prometheus
template:
metadata:
labels:
app: prometheus
spec:
containers:
- name: prometheus
image: prom/prometheus:latest
args:
- "--config.file=/prometheus2/prometheus.yml"
- "--storage.tsdb.path=/prometheus/"
ports:
- containerPort: 9090
name: http
volumeMounts:
- name: prometheus-storage-volume
mountPath: /prometheus/
- name: prometheus-config-map
mountPath: /prometheus2/
volumes:
- name: prometheus-storage-volume
emptyDir: {}
- name: prometheus-config-map
configMap:
name: prometheus
/targets
来验证Prometheus是否已经发现您的应用程序在Kubernetes上运行。
net.logstash.logback
logstash-logback-encoder
6.3
$ minikube addons enable efk
* efk was successfully enabled
$ minikube addons enable logviewer
* logviewer was successfully enabled
com.github.piomin
logstash-logging-spring-boot-starter
1.2.2.RELEASE
@Component
@Endpoint(id = "liveness")
class LivenessHealthEndpoint {
@ReadOperation
fun health() : Health = Health.up().build()
@ReadOperation
fun name(@Selector name: String) : String = "liveness"
@WriteOperation
fun write(@Selector name: String) {
}
@DeleteOperation
fun delete(@Selector name: String) {
}
}
org.springframework.boot
spring-boot-starter-amqp
org.springframework.boot
spring-boot-starter-data-jpa
org.postgresql
postgresql
runtime
/health
端点查看更多详细信息。 management:
endpoint:
health:
show-details: always
/actuator/health
查看详细信息。正如您在下图中看到的,结果中返回了有关Postgres和RabbitMQ连接的信息。 @PostMapping("/long-running")
fun addLongRunning(@RequestBody person: Person): DeferredResult {
var result: DeferredResult = DeferredResult()
GlobalScope.launch {
logger.info("Person long-running: {}", person)
delay(10000L)
result.setResult(repository.save(person))
}
return result
}
/health
端点作为readiness探针。但是,如果您为Postgres和Rabbit保留默认连接设置,那么每次readiness探针调用都需要很长时间(如果它们不可用的话)。这就是为什么我建议将这些超时时间减少到更低值,如下所示。 spring:
application:
name: sample-spring-kotlin-microservice
datasource:
url: jdbc:postgresql://postgres:5432/postgres
username: postgres
password: postgres123
hikari:
connection-timeout: 2000
initialization-fail-timeout: 0
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
rabbitmq:
host: rabbitmq
port: 5672
connection-timeout: 2000
@Configuration
class RabbitMQConfig {
@Bean
fun myQueue(): Queue {
return Queue("myQueue", false)
}
}
@Component
class PersonListener {
val logger: Logger = LoggerFactory.getLogger(PersonListener::class.java)
@RabbitListener(queues = ["myQueue"])
fun listen(msg: String) {
logger.info("Received: {}", msg)
}
}
org.liquibase
liquibase-core
databaseChangeLog:
- changeSet:
id: 1
author: piomin
changes:
- createTable:
tableName: person
columns:
- column:
name: id
type: int
autoIncrement: true
constraints:
primaryKey: true
nullable: false
- column:
name: name
type: varchar(50)
constraints:
nullable: false
- column:
name: age
type: int
constraints:
nullable: false
- column:
name: gender
type: smallint
constraints:
nullable: false
istioctl manifest apply
account-service
的请求定义了2秒的延迟。 apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: account-service
spec:
hosts:
- account-service
http:
- fault:
delay:
fixedDelay: 2s
percent: 100
route:
- destination:
host: account-service
subset: v1
account-service
定义DestinationRule。这非常简单,我们只需定义目标服务的版本标签。 apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: account-service
spec:
host: account-service
subsets:
- name: v1
labels:
version: v1
ReplicaSet
和 Deployment
对象将应用程序回滚到旧版本。在默认情况下,Kubernetes保留了10个之前的 ReplicaSet
,并允许您回滚到其中任何一个 ReplicaSet
。然而,有一件事需要指出。回滚不包括存储在ConfigMap和Secret中的配置。有时不仅需要回滚应用程序的二进制文件,还需要回滚配置。 application-rollbacktest.yml
, application-rollbacktest.yml
只包含一个属性。只有当Spring配置文件rollbacktest被激活时,应用程序才加载该配置。 apiVersion: v1
kind: ConfigMap
metadata:
name: sample-spring-kotlin-microservice
data:
application-rollbacktest.yml: |-
property1: 123456
spec:
containers:
- name: sample-spring-kotlin-microservice
image: piomin/sample-spring-kotlin-microservice
ports:
- containerPort: 8080
name: http
volumeMounts:
- name: config-map-volume
mountPath: /config/
volumes:
- name: config-map-volume
configMap:
name: sample-spring-kotlin-microservice
application.yml
,包含一个属性。 property1: 123
application-rollbacktest.yml
具有比 application.yml
更高的优先级。属性 property1
的值将被 application-rollbacktest.yml
中的值覆盖。 property1: 123
spring.profiles.active: rollbacktest
@RestController
@RequestMapping("/properties")
class TestPropertyController(@Value("\${property1}") val property1: String) {
@GetMapping
fun printProperty1(): String = property1
}
$ kubectl rollout history deployment/sample-spring-kotlin-microservice
deployment.apps/sample-spring-kotlin-microservice
REVISION CHANGE-CAUSE
1
2
3
/properties
,它返回属性 property1
的值。因为配置文件 application-rollbacktest.yml
处于激活状态,返回 application-rollbacktest.yml
中的属性值。 $ curl http://localhost:8080/properties
123456
$ kubectl rollout undo deployment/sample-spring-kotlin-microservice --to-revision=2
deployment.apps/sample-spring-kotlin-microservice rolled back
revision=2
,Deployment现在被部署为最新的 revision=4
。 $ kubectl rollout history deployment/sample-spring-kotlin-microservice
deployment.apps/sample-spring-kotlin-microservice
REVISION CHANGE-CAUSE
1
3
4
application-rollbacktest.yml
未处于激活状态,因此属性 property1
的值取自 application.yml
。 $ curl http://localhost:8080/properties
123