K8S发布解释型语言应用的最佳实践

说明

咱们知道,k8s在发布编译型语言的应用时,几乎不用多考虑,就会选择将编译好jar/war包(java语言)或者二进制文件(golang/c++)直接打到镜像当中,生成新的应用镜像,而后将镜像推到镜像仓库,再触发k8s完成应用发布或者版本更新。但是相反,解释型语言(php/lua/python)咱们通常不会这么作。为何呢?php

由于咱们知道,打镜像和推送镜像的一个过程是相对比较耗时的。对于编译型语言来说,编译过程自己就比较耗时,在编译完成以后,再进行镜像打包和推送镜像的过程所消耗的时间跟编译过程自己相比,根本微不足道。对于开发人员来说,他们有一个这样心理预期,多了这么一点点的时间,对于他们来说,几乎是无感知的。java

而对于解释型语言的应用开发者来说则否则,咱们以php为例来做说明。对于一个php开发者来说,有的时候,一次发布,可能就只修改了一个文件,经过传统的发布方式,完成一次发布可能只须要10秒钟。而若是只是修改了一个文件却须要将整个代码完整打包,生成一个新的镜像,打镜像推镜像的时间可能就须要1分钟,再加上镜像替换,流动更新的时间,整个更新操做的时间就会拉的更长。虽然这在生产环境中的表现可能还并不明显,但在须要持续集成,频繁迭代的测试环境中,这种表如今发布时间上的巨大心理落差几乎没法让开发人员接受。事实上,这也是大多数解释型语言的开发者抵制docker的一个缘由之一。node

那么到底有没有更好的解决办法呢?python

单独发布代码

事实上,有不少公司在实际使用中针对解释型语言的应用都选择了使用容器标准化运行时环境,代码单独发布的方式。nginx

具体的作法是,使用镜像打包标准的php运行环境,而后将代码直接发布到k8s的全部node节点上,而后在任意节点上启动php标准镜像,并挂载所在节点的指定代码目录便可。由于全部节点上都有代码,因此php容器能够在任意节点之间漂移。若是不想把代码发布到全部节点,只想发布到某几个节点,也很好解决,只须要给这几个节点打上label,而后限定php容器只运行在带有该label的节点上便可。c++

这么作的优势显而易见:git

  1. 经过容器的方式标准化了php环境,并且简化了php环境的部署操做。
  2. php代码发布仍然采用传统的发布方式独立发布,不受打镜像推镜像的时间影响,作到了快速迭代。

缺点一样显而易见:golang

  1. 首先镜像和代码拆开发布的方式自己就不是容器化标准所推荐的方法
  2. 咱们须要经过label的方式严格对node和pod的对应关系作限定,一旦出错,就会出现pod正常运行,访问报错的现象,由于pod没法正常挂载代码。
  3. 由于一台宿主机上只有一份代码,若是该台宿主机上同时运行了同一个应用的多个pod,那么这些pod将会挂载同一份代码,当多个进程同时读写同一份代码时,须要代码自行解决冲突,虽然这种状况其实不多出现(绝大多数时候,应用程序的开发者都会避免在代码目录执行写操做,而对于读操做,多进程是能够共存的)。固然另外一个可行的解决办法是经过pod的反亲和性调度避免将同一个应用的多个pod运行在同一个node节点上,毫无疑问,这也增长了配置的复杂性。

应用容器主动拉取代码

事实上,这是合乎容器化标准的一种实现。docker

而要实现这种方式,须要解决两个问题:json

  1. 应用容器第一次启动时,如何获取代码
  2. 应用代码更新时,容器如何更新代码

第一个问题,咱们使用kubernetes的init container技术来解决(在某些应用场中,使用pod启动后的钩子事件也能够解决,但在相似lua的应用场中,在应用容器启动以前就须要代码就位的状况下,这种方式并不合适,因此init container才是最好的选择)。其原理是在应用容器启动以前,先启动一个初始化容器,而后在初始化容器中执行从git仓库,或者从发布服务器拉取代码的操做,将代码直接拉取到应用容器的存储位置,而后启动应用容器,这样应用容器一启动,就有了代码。

第二个问题,能够直接调用k8s api,获取到当前应用的全部pod信息,而后触发循环调用pod上的rsync,执行主动拉取操做。

事实上,在咱们当前的测试环境的lua代码的发布场景中,尤为适用。由于咱们在测试环境中,持续集成的过程是自动完成的,每分钟执行一次持续集成操做。会先从git仓库将代码拉取到发布节点,发布节点启动rsync server端。而后各node节点上配置定时任务,每分钟从发布节点同步代码。对于lua应用来说,每次代码更新就须要执行一次Nginx重载。经过这种发布方式,咱们能够实现只有在有代码更新时,才执行nginx重载,而不是每分钟一次。

缺点:

  1. 增长了发布服务器的压力,全部pod再也不共享宿主机代码,而是每一个pod一套独立代码,全部pod都会从发布服务器拉取代码,在pod数量较大时,对发布服务器的io是个考验。另外若是某节点宕机时,全部pod漂移到新节点上从新启动,会同一时间向发布服务器拉取代码,对发布服务器的io压力也会较大

  2. 其配置相对于独立发布代码的方式实际上是一样复杂的,可是其不用限定pod与node的对应关系,增长了调度的灵活性。

下面是一个应用容器主动拉取代码的k8s deployment配置文件示例:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: dyland-lua-api-pre
  namespace: php
spec:
  replicas: 1
  selector: 
    matchLabels:
      name: dyland-lua-api-pre
      envrion: php
  template:
    metadata:
      annotations: 
        prometheus.io/scrape: "true"
        prometheus.io/port: "9913"
        prometheus.io/scheme: "http"
        prometheus.io/path: "/metrics"
      labels:
        name: dyland-lua-api-pre
        envrion: php
        version: ""
    spec:
      initContainers:
        - name: rsync-code
          image: hub.dz11.com/op-base/rsync:v3.1.3
          command:
            - "sh"
            - "-c"
            - >
              /bin/echo '123456' > /home/www/server/rsync.pwd;
              chmod 400 /home/www/server/rsync.pwd;
              /usr/bin/rsync -avzLu --password-file=/home/www/server/rsync.pwd www@$(RSYNC_SERVER)::pre-lua/dyland-lua-api.pre.wh03 /home/www/server/ > /dev/stdout 2>&1;
          env:
            - name: RSYNC_SERVER
              value: 192.168.1.10
          volumeMounts:
            - name: www-root
              mountPath: "/home/www/server"
      containers:
        - name: dyland-lua-api-pre
          image: hub.dz11.com/op-base/openresty:1.11.2.4
          imagePullPolicy: Always
          lifecycle:
            postStart:
              exec:
                command:
                  - "sh"
                  - "-c"
                  - >
                    /bin/echo 'options single-request-reopen' >> /etc/resolv.conf;
          ports:
            - containerPort: 80
          env:
            - name: APP_NAME
              value: dyland-lua-api.pre.wh03
          volumeMounts:
            - name: nginx-conf 
              mountPath: /usr/local/ngx_openresty/nginx/conf/vhost
            - name: applogdir
              mountPath: /home/www/logs/applogs
            - name: srvlogdir
              mountPath: /home/www/logs/srvlogs
            - name: www-root
              mountPath: /home/www/server
        #      mountPath: /home/www/server/dyland-lua-api.pre.wh03
        - name: nginx-vts-exporter
          image: hub.dz11.com/library/nginx-vts-exporter:v0.10.3
          ports:
            - containerPort: 9913
          env:
            - name: NGINX_HOST
              value: "http://localhost/status/format/json"
      volumes:
        - name: nginx-conf
          configMap:
            name: dyland-lua-api-pre-configmap
        - name: applogdir
          hostPath:
            path: /home/www/logs/applogs
        - name: srvlogdir
          hostPath:
            path: /home/www/logs/srvlogs
        - name: www-root
          emptyDir: {}
        #  hostPath:
        #    path: /home/www/server/dyland-lua-api.pre.wh03
      imagePullSecrets:
        - name: dk-reg
      nodeSelector:
        testenv: pre
相关文章
相关标签/搜索