在容器服务的客户群中, 一个经常被问起的问题就是如何处理服务间依赖
在应用中, 一个组件依赖指定的中间件服务和业务服务在传统的软件部署方式中, 应用启动停止都要依照特定的顺序完成
当采用 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 error
- maxAttempts := 20
- for 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 文档 https://docs.docker.com/compose/startup-order/ 获得更多信息另一种方法是利用 Kubernetes Pod 自身机制添加依赖检查逻辑
首先我们需要对 Pod 的生命周期有一定的理解, 下图来自于 https://blog.openshift.com/kubernetes-pods-life/ 一文
首先在 Pod 中有三类容器
infra container: 这就是著名的 pause 容器
init container: 初始化容器 https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ 通常用于应用的初始化准备, 只有等所有的初始化容器正常执行完毕之后, 才会启动应用容器
main container: 应用容器
Kubernetes 的最佳实践中, 通常是利用初始化容器来进行依赖服务的检查下面我们通过一个 Wordpress 的实例来展示其使用方法
- apiVersion: v1
- kind: Service
- metadata:
- name: mysql
- spec:
- clusterIP: None
- ports:
- - name: mysql
- port: 3306
- selector:
- app: mysql
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: wordpress
- spec:
- ports:
- - name: wordpress
- port: 80
- targetPort: 80
- selector:
- app: wordpress
- type: NodePort
- ---
- apiVersion: apps/v1
- kind: StatefulSet
- metadata:
- name: mysql
- spec:
- selector:
- matchLabels:
- app: mysql
- serviceName: mysql
- replicas: 1
- template:
- metadata:
- labels:
- app: mysql
- spec:
- containers:
- - name: mysql
- image: mysql:5.7
- env:
- - name: MYSQL_ALLOW_EMPTY_PASSWORD
- value: "true"
- livenessProbe:
- exec:
- command: ["mysqladmin", "ping"]
- initialDelaySeconds: 30
- periodSeconds: 10
- timeoutSeconds: 5
- readinessProbe:
- exec:
- # Check we can execute queries over TCP (skip-networking is off).
- command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
- initialDelaySeconds: 5
- periodSeconds: 2
- timeoutSeconds: 1
- ---
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: wordpress
- spec:
- replicas: 1
- selector:
- matchLabels:
- app: wordpress
- template:
- metadata:
- labels:
- app: wordpress
- spec:
- containers:
- - name: wordpress
- image: wordpress:4
- ports:
- - containerPort: 80
- env:
- - name: WORDPRESS_DB_HOST
- value: mysql
- - name: WORDPRESS_DB_PASSWORD
- value: ""
- initContainers:
- - name: init-mysql
- image: busybox
- command: ['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 的开发者体验, 帮助用户关注云端应用价值创新
来源: https://yq.aliyun.com/articles/573791