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

相關文章
相關標籤/搜索