摘要: 在容器服务的客户群中,一个经常被问起的问题就是如何处理服务间依赖。本文介绍了常见的解决方法来实现服务的依赖检查,还进一步用示例展示了如何利用init container, liveness/readiness探针等技术实现服务健康检查,依赖检查等等功能。
本系列文章记录了企业客户在应用Kubernetes时的一些常见问题
- 第一篇:Java应用资源限制的迷思
- 第二篇:利用LXCFS提升容器资源可见性
- 第三篇:解决服务依赖
在容器服务的客户群中,一个经常被问起的问题就是如何处理服务间依赖。
在应用中,一个组件依赖指定的中间件服务和业务服务。在传统的软件部署方式中,应用启动、停止都要依照特定的顺序完成。
当采用 Kubernetes/Docker Swarm等容器编排技术在分布式环境下部署应用时,一方面不同组件之间并行启动无法保证其启动顺序,另一方面在应用运行时,其所依赖的服务实现有可能发生失败和迁移。如何解决容器之间的服务依赖就是一个非常常见的问题。
方法1 - 应用端服务依赖检查
我们可以在应用的启动逻辑中添加服务依赖检查逻辑,如果应用依赖的服务不可访问就重试,当错误超过一定次数后就自动退出。Kubernetes/Docker会根据所容器的重启策略(Restart Policy)在等待一段时间之后自动拉起。
下面就是一个简单的Golang应用示例,来检测所依赖的MySQL服务是否就绪。
...// Connect to database.hostPort := net.JoinHostPort(config.Host, config.Port)log.Println("Connecting to database at", hostPort)dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?timeout=30s",config.Username, config.Password, hostPort, config.Database)db, err = sql.Open("mysql", dsn)if err != nil {log.Println(err)}var dbError errormaxAttempts := 20for attempts := 1; attempts <= maxAttempts; attempts++ {dbError = db.Ping()if dbError == nil {break}log.Println(dbError)time.Sleep(time.Duration(attempts) * time.Second)}if dbError != nil {log.Fatal(dbError)}log.Println("Application started successfully.")...
注:
"Fail Fast" (快速失败),是Design by Contract契约式设计的一种重要的原则,可以很好地保障系统的健壮性和可预测性。比如上文代码中,如果重试失败,就会由log.Fatal(dbError)
退出执行。而K8S/Docker的容器重启的回退机制可以保障不会因频繁拉起失败应用导致系统资源耗尽。
方法2 - 独立的服务依赖检查逻辑
在现实世界里,有些遗留应用或者框架无法进行调整。我们就会希望将依赖检查策略和应用逻辑进行解耦。
一个常见的方法是在容器的Dockerfile的启动脚本里加入相应的服务依赖检查逻辑,可以参见Docker文档获得更多信息。另一种方法是利用Kubernetes Pod自身机制添加依赖检查逻辑。
首先我们需要对Pod的生命周期有一定的理解,下图来自于 https://blog.openshift.com/kubernetes-pods-life/ 一文
首先在Pod中有三类容器
- infra container: 这就是著名的pause容器
- init container: 初始化容器 通常用于应用的初始化准备,只有等所有的初始化容器正常执行完毕之后,才会启动应用容器
- main container: 应用容器
Kubernetes的最佳实践中,通常是利用初始化容器来进行依赖服务的检查。下面我们通过一个Wordpress的实例来展示其使用方法。
apiVersion: v1
kind: Service
metadata:name: mysql
spec:clusterIP: Noneports:- name: mysqlport: 3306selector:app: mysql
---
apiVersion: v1
kind: Service
metadata:name: wordpress
spec:ports:- name: wordpressport: 80targetPort: 80selector:app: wordpresstype: NodePort
---
apiVersion: apps/v1
kind: StatefulSet
metadata:name: mysql
spec:selector:matchLabels:app: mysqlserviceName: mysql replicas: 1template:metadata:labels:app: mysqlspec:containers:- name: mysqlimage: mysql:5.7env:- name: MYSQL_ALLOW_EMPTY_PASSWORDvalue: "true"livenessProbe:exec:command: ["mysqladmin", "ping"]initialDelaySeconds: 30periodSeconds: 10timeoutSeconds: 5readinessProbe:exec:# Check we can execute queries over TCP (skip-networking is off).command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]initialDelaySeconds: 5periodSeconds: 2timeoutSeconds: 1
---
apiVersion: apps/v1
kind: Deployment
metadata:name: wordpress
spec:replicas: 1selector:matchLabels:app: wordpresstemplate:metadata:labels:app: wordpressspec:containers:- name: wordpressimage: wordpress:4ports:- containerPort: 80env:- name: WORDPRESS_DB_HOSTvalue: mysql- name: WORDPRESS_DB_PASSWORDvalue: ""initContainers:- name: init-mysqlimage: busyboxcommand: ['sh', '-c', 'until nslookup mysql; do echo waiting for mysql; sleep 2; done;']
我们在Wordpress Deployment的Pod定义中添加了initContainers
,它会通过检查 mysql
域名是否可以解析来判断所依赖的mysql服务是否就绪。
同时,在MySQL StatefulSet中我们也引入了readinessProbe
和 livenessProbe
探针,它们会判定是否MySQL进程已经业务就绪。在K8S中,只有健康的Pod才可以通过ClusterIP访问或者DNS解析。
$ kubectl create -f wordpress.yaml
service "mysql" created
service "wordpress" created
statefulset "mysql" created
deployment "wordpress" created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
mysql-0 0/1 Running 0 5s
wordpress-797655cf44-w4p87 0/1 Init:0/1 0 5s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
mysql-0 1/1 Running 0 11s
wordpress-797655cf44-w4p87 0/1 Init:0/1 0 11s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
mysql-0 1/1 Running 0 14s
wordpress-797655cf44-w4p87 0/1 PodInitializing 0 14s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
mysql-0 1/1 Running 0 17s
wordpress-797655cf44-w4p87 1/1 Running 0 17s
$ kubectl describe pods wordpress-797655cf44-w4p87
...
注:
- Liveness探针:主要用于判断Container是否处于运行状态,比如当服务死锁或者响应缓慢等情况。
- Readiness探针:主要用于判断服务是否已经正常工作。
- 在init container中不允许使用readiness探针。
- 如果Pod重启了,其所有init Container都需重新运行。
总结
本文介绍了常见的解决方法来实现服务的依赖检查,还进一步用示例展示了如何利用init container, liveness/readiness探针等技术实现服务健康检查,依赖检查等等功能。
Kubernetes提供了非常灵活的Pod生命周期管理机制,由于篇幅有限我们就不再展开介绍 postStart/preStop等生命周期钩子方法。
阿里云Kubernetes服务 全球首批通过Kubernetes一致性认证,简化了Kubernetes集群生命周期管理,内置了与阿里云产品集成,也将进一步简化Kubernetes的开发者体验,帮助用户关注云端应用价值创新。
原文链接
干货好文,请关注扫描以下二维码: