文章目录

  • 问题
  • 分析
  • 方案
  • initContainer
  • 延伸
  • docker-compose depends_on与Helm dependencies区别
  • 参考文章


问题

docker-compose的yaml描述文件中对于依赖组件容器,我们常常会使用depends_on来申明,非常方便。比如下面这段:

service:
	worker: 
	  build: ./worker
	  image: xxxxxxxxxxxx
	  depends_on:
	    - redis
	    - postgres

那么到了kubernetes里面,容易习惯性的在脑袋中思考一个问题,我的依赖容器要怎么申明呢?或者更进一步说如何控制我容器的依赖启动顺序呢?

分析

确实,docker-compose作为"单点"编排工具的优势就是简单便捷啊,这一点毋庸置疑。Kubernetes作为功能超级强大的分布式容器编排工具,考虑的点和docker-compose不在同一个平面上。Kubernetes云原生的理念要求任何应用应该是“独立的”,应用自身是可以处理未连接或者重连接的情况,而不是交由Kuberntest集群层面来做,因为对于Kubernetes来说,Pod或者Container是随时会被调度/重起的,Kubernetes自身会检测所有的pod里的容器并确保它们活着并给他们贴上Healthy的标签,所以应该是尽量独立解耦的,不应该被绑定/关联太多才可以启动。

这段话你可以这么来理解,既然Pod你可以理解为“VM”,一台“virtual host”,那么你肯定不希望你的应用所在的“host”启动需要依赖于其它“host”的启动,而是你希望你的app所在的“host”——即Pod不论db状态如何,它都是可以“开机”的,app自身可以独立的去处理db的连接/重连问题。

方案

换句话说,在Kubernetes的体系中,并没有严格意义上和docker-compose的depends_on对等的"申明"语句。当然Kubernetes对于依赖容器也是可以透过一些特别的机制来实现类似的目的,不过可能需要修改app的逻辑。比如,我们可以在app的entrypoint或者initContainer当中去申明应用的启动逻辑,或者可以通过readiness探针来决定Kubernetes是否要启动Pod,同样也可以通过liveness探针来检测所依赖的服务是否健康。

initContainer

以initContainer为例,它是一个或多个先于应用容器启动的 Init 容器,每个都必须在下一个启动之前成功完成。默认的策略是若 Pod 的 Init 容器失败,Kubernetes 会不断地重启该 Pod,直到 Init 容器成功为止。下面例子是官方例子,定义了一个具有 2 个 Init 容器的简单 Pod。 第一个等待 myservice 启动,第二个等待 mydb 启动。 一旦这两个 Init容器 都启动完成,Pod 将启动spec区域中的应用容器。

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox:1.28
    command: ['sh', '-c', 'echo The app is running! && sleep 3600']
  initContainers:
  - name: init-myservice
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]
  - name: init-mydb
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done"]

注意initContainer里面的command,我们写了一段shell脚本一直循环运行,直到检测到myservicemydb都启动完毕才退出,并启动myapp容器。

注意:

  1. 在 Pod 上使用 activeDeadlineSeconds和在容器上使用 livenessProbe 可以避免 Init 容器一直重复失败。 activeDeadlineSeconds 时间包含了 Init 容器启动的时间;
  2. initContainers不支持readiness probes因为它们必须在Pod ready之前完成运行。

其中的myservice和mydb的定义如下:

kind: Service
apiVersion: v1
metadata:
  name: myservice
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
---
kind: Service
apiVersion: v1
metadata:
  name: mydb
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9377

再来看个ELK的例子,也是写了一段shell脚本并通过初始化容器的方式来实现的。

spec:
      initContainers:
      - name: wait-for-grafana
        image: darthcabs/tiny-tools:1
        args:
        - /bin/bash
        - -c
        - >
          set -x;
          while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' http://grafana:3000/login)" != "200" ]]; do 
            echo '.'
            sleep 15;
          done
      containers:
          .
          .
  (your other containers)
          .
          .

延伸

docker-compose depends_on与Helm dependencies区别

Helm的requirements.yaml中也申明了Chart包的依赖,如下所示,那么这个dependencies和docker-compose的depends_on的区别在哪呢?

dependencies:
  - name: mysql
    version: 3.2.1
    repository: http://another.example/charts

首先我们来看Helm dependencies怎么使用?

# 根据requirements.yaml配置,将依赖的应用包从仓库中拉取到charts目录,移除旧的
# 同时会生成requirements.lock
helm dependency update [flags] CHART

# 基于requirements.lock,重新构建charts中的应用
# 如果没有lock文档,类似update操作
helm dependency build [flags] CHART

# 展示应用依赖的所有子应用包
helm dependency list [flags] CHART

这里我们可以看到requirements.yaml是先转换为lock文件配置,然后Helm再基于lock文件去安装依赖服务,但是,这里需要明确的一点就是:Helm只是管理依赖并依据lock去创建pod,它并不会保证任何运行的顺序,以上面这个申明为例,Helm会创建2个pod,但它并不关心mysql pod可能要20s才完成启动而你的app容器只需要2s。

参考文章

  1. InitContainer官方文档
  2. How do we map depends_on in Kubernetes yamls ?
  3. What is the equivalent for depends_on in kubernetes