kubernetes学习笔记 (三):阿里云业务实战

目标

咱们游戏按照业务逻辑划分,服务器可分为三种类型,前端服务器(客户端直接进行链接的)、后端服务器(只负责处理各类游戏逻辑不提供链接)、任务服务器(各类cron、job任务),其中前端服务器按照功能划分为http短链接服务器和socket长链接服务器,后端服务器按照业务划分 例如matching匹配服务器。前端

在部署这些服务器的同时,我须要使用kubernetes达到的目标有:node

  • 对于每种类型的服务器,须要同时存在若干个版本
  • 对于无状态服务器如http、cron能够比较方便的更新、回滚
  • 对于有状态服务器如socket、matching能够业务无间断的进行更新、回滚,用户不会掉线、无感知
  • 能够进行灰度发布
  • 当服务器的负载变化时,可以自动伸缩服务器数量
  • 当服务器异常宕机时,可以自我修复

准备Docker镜像

  1. 使用阿里云容器镜像服务准备好docker远程仓库
  2. 在应用(服务器代码)准备好以后,使用Docker构建镜像,并打上版本号,Push到远程仓库(这一步骤能够经过Jekins自动完成,后续实践的时候会更新文档,目前就以手工进行)

部署应用

相似于web前端框架中的命令式(jquery)与声明式(react),我对k8s有一种理解与之相似:咱们只须要经过配置文件“告诉”k8s咱们想要的最终结果就行,中间的过程无须再关心,k8s会以各类机制保证这一结果react

  1. 由于使用的Docker远程仓库是私有仓库,部署应用时就须要添加imagePullSecrets,首先使用kubectl在default命名空间里建立secret,如需指定命名空间添加 -n参数,后面命令相似
kubectl create secret docker-registry yourSecretName --docker-server=xxx.cn-hangzhou.aliyuncs.com --docker-username=xxx@aliyun.com --docker-password=xxxxxx --docker-email=xxx@aliyun.com
复制代码
  1. 根据服务器的特色建立部署yaml文件
  • http 无状态应用
apiVersion: extensions/v1beta1 # kubectl api的版本
kind: Deployment # kubernetes的资源类型 对于无状态应用 Deployment便可
metadata:
    name: http-prod-1.0.0000 # 部署的名称 不能重复 由于我须要多个版本共存所以使用 名称-环境-版本号的命名方式
spec:
    strategy:
        rollingUpdate: # 滚动更新策略
            maxSurge: 10% # 数值越大 滚动更新时新建立的副本数量越多
            maxUnavailble: 10% # 数值越大 滚动更新时销毁的旧副本数量越多
    replicas: 3 # 期待运行的Pod副本数量
    template:
        metadata:
            labels: # 自定义标签
                serverType: http
                env: production
                version: 1.0.0000
        spec:
            containers:
                - name: httpapp
                  image: yourDockerRegistry:1.0.0000
                  readinessProbe: # 一种健康检查决定是否加入到service 对外服务 当接口返回200-400以外的状态码时,k8s会认为这个pod已经不可用,会从Service中移除
                      httpGet:
                          scheme: HTTP # 支持http https
                          path: /
                          port: 81
                      initialDelaySeconds: 10 # 容器启动多久后开始检查
                      periodSecods: 5 # 几秒检查一次
                  env: # 镜像启动时的环境变量
                      - name: DEBUG
                        value: 'ccgame:*'
                      - name: NODE_ENV
                        valueFrom:
                            fieldRef:
                                fieldPath: metadata.labels['env'] # 从labels中读取env
                      - name: HTTP_PORT
                        value: '80'
                      - name: SERVER_PORT
                        value: '80'
                      - name: HEALTHY_CHECK_PORT
                        value: '81'
                      - name: SERVER_TYPE
                        valueFrom:
                            fieldRef:
                                fieldPath: metadata.labels['serverType'] # 从labels中读取SERVER_TYPE
                      - name: NATS_ADDRESS
                        value: 'nats://xxx:xxx@nats:4222' # 使用的消息队列集群地址
                      - name: VERSION
                        valueFrom:
                            fieldRef:
                                fieldPath: metadata.labels['version'] # 从labels中读取version

            imagePullSecrets:
                - name: regsecret
复制代码

建立对应的servicejquery

apiVersion: v1 # kubectl api的版本
kind: Service # kubernetes的资源类型 这里是Service
metadata:
    name: http-prod-v100000 # 服务的名称 不能重复 不能有. 由于我须要多个版本共存所以使用 名称-环境-版本号并去掉.的方式命名
spec:
    type: ClusterIP # service的类型 ClusterIp类型 只有Cluster内部节点和Pod能够访问 NodePort Cluster外部能够经过<NodeIp>:<NodePort>访问 LoadBalancer负载均衡
    selector: # 匹配pod的标签与上文Deployment中的labels一致
        serverType: http
        env: production
        version: 1.0.0000
    ports:
        - protocol: TCP # 只有TCP 或 UDP
          port: 80 # 服务 监听的端口
          targetPort: 80 # Pod 监听的端口 对应上面的Deployment中的HTTP_PORT
复制代码

建立对应的ingress(路由)对外提供服务nginx

apiVersion: extensions/v1beta1 # kubectl api的版本
kind: Ingress # kubernetes的资源类型 这里是Ingress
metadata:
  name: https # 路由的名称
spec:
  rules:
    - host: xx.xxxx.com # 域名
      http:
        paths:
          - backend:
              serviceName: http-prod-v100000 # 转发的服务名
              servicePort: 80 # 转发到服务的哪一个端口 对应上文的service port
            path: / # 匹配路径
  tls: # 开启tls
    - hosts:
        - xx.xxxx.com
      secretName: yourSecretName # 证书 可经过 kubectl create secret generic yourSecretName --from-file=tls.crt --from-file=tls.key -n kube-system建立
status:
  loadBalancer:
    ingress:
      - ip: x.x.x.x # 负载均衡的ip下文会讲
复制代码

此时已经能够经过域名进行访问了,这就是咱们想要的“最终状态”,而具体实现细节以及如何维持这个状态不变,咱们无需再关心git

为什么不直接使用Service对外提供服务?github

其实咱们只须要把Service的类型改为LoadBlancer,阿里云(其余云服务商相似)会给Service添加一个监听的nodePort,再自动建立一个负载均衡,经过tcp转发到Service的nodePort上(这地方阿里云有个bug每次更新Service它都会把转发类型改为tcp),可想而知,当咱们的Service愈来愈多时,nodePort的管理成本也就愈来愈高, k8s提供了另一个资源解决这种问题,就是Ingressweb

Ingress工做机制docker

Ingress其实就是从 kuberenets 集群外部访问集群的一个入口,将外部的请求根据配置的规则转发到集群内不一样的 Service 上,其实就至关于 nginx、haproxy 等负载均衡代理服务器,咱们直接使用Nginx也能够达到同样的目的,只是nginx这种方式当添加、移除Service时动态刷新会比较麻烦一点,Ingress至关于都给你作好了,不须要再次实现一遍,Ingress默认使用的Controller就是nginx。npm

Ingress controller 能够理解为一个监听器,经过不断地与 kube-apiserver 打交道,实时的感知后端 service、pod 的变化,当获得这些变化信息后,Ingress controller 再结合 Ingress 的配置,更新反向代理负载均衡器,达到服务发现的做用。

配置Ingress

能够经过annotations注解的方式告诉Ingress你的配置,例如:若是你使用的是Nginx-Ingress-Controller,能够经过nginx.ingress.kubernetes.io/cors-allow-origin: *来配置cors,和配置Nginx几乎是同样的,只是名称不同而已。

全部的Nginx-Ingress-Controller的注解能够在这里查询 传送门

能够进入nginx-Ingress-controller的pod中,添加一些注解,更新,会看到nginx从新生成了配置,并“从新启动”,对比注解和nginx.conf 很快就能理解Ingress

Ingress灰度发布

能够经过添加注解nginx.ingress.kubernetes.io/service-match: 'test-svc: header("Version", "1.0.0000")',来进行灰度发布,好比匹配 request headers中Version=1.0.0000的流量转发到test-svc,能够匹配header、query、cookie,同时还能够配置权重等,例如修复问题时只把10%的流量切进来,待问题验证获得解决后再设置100。

咱们每次游戏前端发布版本都会在header中添加一个Version参数,我设置灰度发布以后就能够把特定前端版本的流量自由的切到某个特定的服务中,比较灵活。

滚动更新

当不须要灰度发布时,仅仅须要对某个Service的pod进行更新,只须要更改上文Deployment中镜像版本便可,当k8s检测到template字段更改时,会根据设置的rollingUpdate strategy策略进行滚动更新,对于http这种无状态的服务,也能达到业务不间断更新

  • 长链接 有状态应用

无状态: 该服务运行的实例不会在本地存储须要持久化的数据,而且多个实例对于同一个请求响应的结果是彻底一致的

有状态:和上面的概念是对立的了,该服务运行的实例须要在本地存储持久化数据,好比socket长链接

apiVersion: apps/v1beta1 # kubectl api的版本
kind: StatefulSet # kubernetes的资源类型 对于有状态应用选择StatefulSet
metadata:
    name: connector-prod-v100000 # 部署的名称 不能重复 由于我须要多个版本共存所以使用 名称-环境-版本号的命名方式
spec:
    replicas: 3 # 运行的Pod副本数量
    template:
        metadata:
            labels: # 自定义标签
                serverType: connector
                wsType: socket.io
                env: production
                version: 1.0.0000
        spec:
            containers:
                - name: connectorapp
                  image: yourDockerRegistry:1.0.0000
                  readinessProbe: # 一种健康检查决定是否加入到service 对外服务
                      httpGet:
                          scheme: HTTP # 支持http https
                          path: /
                          port: 82
                      initialDelaySeconds: 10 # 容器启动多久后开始检查
                      periodSecods: 5 # 几秒检查一次
                  env: # 镜像启动时的环境变量
                      - name: DEBUG
                        value: 'ccgame:*'
                      - name: NODE_ENV
                        valueFrom:
                            fieldRef:
                                fieldPath: metadata.labels['env']
                      - name: WS_PORT
                        value: '80'
                      - name: HEALTHY_CHECK_PORT
                        value: '82'
                      - name: SERVER_TYPE
                        valueFrom:
                            fieldRef:
                                fieldPath: metadata.labels['serverType']
                      - name: WS_TYPE
                        valueFrom:
                            fieldRef:
                                fieldPath: metadata.labels['wsType']
                      - name: NATS_ADDRESS
                        value: 'nats://xxx:xxx@nats:4222'
                      - name: VERSION
                        valueFrom:
                            fieldRef:
                                fieldPath: metadata.labels['version']
# 对于StatefulSet k8s会在metadata.name中自动加上一个序号,从0开始,如connector-prod-v100000-0,connector-prod-v100000-1
                      - name: SERVER_ID
                        valueFrom:
                            fieldRef:
                                fieldPath: metadata.name

            imagePullSecrets:
                - name: regsecret
复制代码

Service和Ingress与无状态http应用基本一致,参照上文部署便可。所有部署完成后,观察k8s后台能够看到,有name分别为connector-prod-v100000-0、connector-prod-v100000-一、connector-prod-v100000-2的三个pod正在运行,后面的 -n是因为资源类型设置为StatefulSet k8s自动加上的以做区分。

在容器中获取pod信息

通常来讲对于StatefulSet 咱们可能会在容器内知道这个pod的name,这时候就能够采用相似于上面的方法,经过valueFrom fieldPath: metadata.name把pod name信息注入到容器的环境变量中,这种特殊的语法是Downward API,帮助咱们获取许多pod的信息,可参照传送门进行学习

滚动更新

对于StatefulSet 默认的滚动更新策略是OnDelete, 也就是当这个pod被删除后,k8s再次建立时会更新镜像。即便咱们改变这个策略,那么能够直接对齐进行更新吗?对于大多数StatefulSet是不太合适的(好比pod上面有用户的长链接 若是直接更新用户会断线 影响体验),或者说对于StatefulSet的滚动更新一直都是个很复杂的话题,因此若是要更新,推荐使用灰度发布

灰度发布的过程与上文http一致,对于咱们的业务来讲,用户的下一次链接会切到指定的版本上

  • matching 后端有状态应用

由于后端服务器不须要外界的访问,因此建立一个StatefulSet 启动后端微服务就能够,启动后会监听消息队列进行处理并返回数据

apiVersion: apps/v1beta1 # kubectl api的版本
kind: StatefulSet # kubernetes的资源类型
metadata:
    name: matching-v100000 # 部署的名称 不能重复 由于我须要多个版本共存所以使用 名称-环境-版本号的命名方式
spec:
    replicas: 1 # 运行的Pod副本数量
    template:
        metadata:
            labels:
                serverType: matching
                env: production
                version: 1.0.0000
        spec:
            containers:
                - name: matchingapp
                  image: yourDockerRegistry:1.0.0000
                  readinessProbe: # 一种健康检查决定是否加入到service 对外服务
                      httpGet:
                          scheme: HTTP # 支持http https
                          path: /
                          port: 80
                      initialDelaySeconds: 10 # 容器启动多久后开始检查
                      periodSecods: 5 # 几秒检查一次
                  env: # 镜像启动时的环境变量
                      - name: DEBUG
                        value: 'ccgame:*'
                      - name: NODE_ENV
                        valueFrom:
                            fieldRef:
                                fieldPath: metadata.labels['env']
                      - name: SERVER_TYPE
                        valueFrom:
                            fieldRef:
                                fieldPath: metadata.labels['serverType']
                      - name: HEALTHY_CHECK_PORT
                        value: '80'
                      - name: NATS_ADDRESS
                        value: 'nats://xxx:xxx@nats:4222'
                      - name: SERVER_ID
                        valueFrom:
                            fieldRef:
                                fieldPath: metadata.name
                      - name: VERSION
                        valueFrom:
                            fieldRef:
                                fieldPath: metadata.labels['version']
            imagePullSecrets:
                - name: regsecret

复制代码
  • cron 定时任务
apiVersion: batch/v1beta1 # kubectl api的版本
kind: CronJob # kubernetes的资源类型 这里选择CronJob 若是不须要定时选择Job
metadata:
    name: test-cron
spec:
    schedule: '0 0 * * *' # 天天晚上执行一次 cron表达式
    jobTemplate:
        spec:
            template:
                metadata:
                    labels:
                        serverType: cron
                        env: production
                        version: 1.0.0000
                spec:
                    containers:
                        - name: cronapp
                          image: yourDockerRegistry:1.0.0000
                          args:
                              - npm
                              - run
                              - start:testCron
                          env: #
                              - name: DEBUG
                                value: 'ccgame:*'
                              - name: NODE_ENV
                                valueFrom:
                                    fieldRef:
                                        fieldPath: metadata.labels['env']
                              - name: NATS_ADDRESS
                                value: 'nats://xxx:xxx@nats:4222'
                    restartPolicy: OnFailure
                    imagePullSecrets:
                        - name: regsecret

复制代码

部署以后定时器就开始运行了,很是简单。经过spec.successfulJobsHistoryLimitspec.failedJobsHistoryLimit,表示历史限制,是可选的字段。它们指定了能够保留多少完成和失败的Job,默认没有限制,全部成功和失败的Job都会被保留。然而,当运行一个Cron Job时,Job能够很快就堆积不少,因此通常推荐设置这两个字段的值。若是设置限制的值为 0,那么相关类型的Job完成后将不会被保留。

更新

直接更改镜像版本号就能够了,下次运行的时候会以新的镜像版本运行

结束

至此,基本的游戏业务框架已经搭建完成,最初的目标都达成了。下一期博客更新kubernetes学习笔记 (四):自动化部署k8s实战

一块儿来学习

添加个人微信,拉你进群一块儿学习k8s

相关文章
相关标签/搜索