欢迎转载,请支持原创,保留原文链接:blog.ilibrary.me

从零开始部署一个完整的服务到K8S集群。

前面开发了一个工具,一直在本地跑。现在需要发布到K8S里面,方便维护和远程访问,因为别人需要使用这个工具.

部署一个新的K8S需要覆盖下面的内容:

  1. 创建Dockerfile. 这个用于build image.
  2. 创建Jenkins文件,用于Jenkins Job使用. build image和上传image.
  3. 创建Service chart
  4. 创建Jenkins Job用来打包docker image和service chart. 需要用到前面3步创建的内容。service chart可以用Jenkins打包,也可以不打包,手动改参数, 然后让ArgoCD自己去读git repo的目录解析chart.
  5. 部署servie chart到开发环境
  6. 数据持久化
  7. 数据库初始化
  8. 添加sidekiq pod
  9. 域名绑定

初次构建K8S的时候尽量把一些安全方面的问题,合规方面的问题先不要考虑。尽量用公开的image,用简单常用的脚步和步骤,简化image的build, 简化chart的build.

用到的工具:

  1. Helm, 创建模版chart.
  2. Docker, build image
  3. Jenkins, 编译docker image.
  4. ArgoCD, 部署集群. 在ArgoCD上新建APP, 指定chart路径,让ArgoCD构建App.
  5. Rancher, 远程运维service. 6.

    创建service chart

拷贝了一份service chart(adserver下面拷贝的)到mobile-mobile-dev-configdce分支下面, 里面包含如下内容:

  1. templates, 这下面的内容会被用来生成service chart.
    1. _helpers.tpl,放置可以通过chart复用的模板辅助对象
    2. deployment.yaml, 用来定义和部署pod, label, app name,ports, readiness/liveness等. service.yaml里面定义的服务会把pod暴露给集群。
    3. hpa.yaml, HorizontalPodAutoscaler, 水平扩张,高可用的东西. 需要多少replicas(minReplicas, maxReplicas), memory, cpu等.
    4. ingress.yaml, 域名配置
      1. servicePort的值长度要在15个字符以内crashreporthttp
    5. NOTES.txt, helm install时展示给用户的.
    6. service.yaml,为你的工作负载创建一个 service终端基本清单。用来暴露deployment.yaml里面创建的内容。
  2. Chart.yaml,
  3. values.yaml

Ingress 配置

  1. deployment.yaml里面暴露pod端口3000
     ---
     apiVersion: apps/v1
     kind: Deployment
     metadata:
     name: crashreport-app # Deployment name
     labels:
         app: crashreport-app # 标签(Labels) 是附加到 Kubernetes 对象(比如 Pods)上的键值对。 标签旨在用于指定对用户有意义且相关的对象的标识属性,但不直接对核心系统有语义含义。
     spec:
     strategy:
         type: RollingUpdate
     replicas: 1
     revisionHistoryLimit: 0
     selector:
         matchLabels:
             app: crashreport-app # 匹配label
     template:
         metadata:
             labels:
             app: crashreport-app
         spec:
             serviceAccountName: 
             imagePullSecrets:
             containers:
             - name: crashreport-main
                 image: "/:" # docker image url
                 imagePullPolicy: Always
                 ports:
                 - name: crashreporthttp
                     containerPort:  # 暴露给NAT的端口.
    
  2. service.yaml里面把targetPort绑定到app(pod)上去, 请求来的时候会随机从Deployment 里选一个pod做转发. ~~~yaml apiVersion: v1 kind: Service metadata: name: vip-crashreport labels: app: crashreport-app spec: type: ClusterIP ports:
    • port: targetPort: crashreporthttp name: crashreporthttp selector: app: crashreport-app

    ~~~

  3. ingress.yaml里面指定nginx, 然后指定host(domain name), path(/), 绑定serviceName和servicePort(第2步里面定义的). ~~~yaml apiVersion: extensions/v1beta1 kind: Ingress metadata: name: crashreport-nginx annotations: kubernetes.io/ingress.class: nginx.ingress.kubernetes.io/proxy-body-size: 100m nginx.ingress.kubernetes.io/enable-cors: “true” nginx.ingress.kubernetes.io/configuration-snippet: | more_set_headers “Server: K8SDemo”; location /api/v1/notification-deactivation { return 404; } spec: tls:
    • hosts:
      • secretName: rules:
    • host: http: paths: - path: “/” backend: serviceName: vip-crashreport servicePort: crashreporthttp ~~~ 4.

K8S数据库

数据库是有状态的,数据需要持久化,和app集群的搭建有点不一样。

搭建数据库需要引入下面的概念:

  1. StatefulSet, 替代Deployment的概念. Deployment用于部署无状态服务,StatefulSet用来部署有状态服务。这里会指定运行哪个db image,传哪些数据库参数。限制内存,cpu。
    1. StatefulSet 是用于管理有状态应用的工作负载对象,与 ReplicaSet 和 Deployment 这两个对象不同,StatefulSet 不仅能管理 Pod 的对象,还它能够保证这些 Pod 的顺序性和唯一性。
    2. StatefulSet里面必须要要有serviceName.这个会被用作域名的一部分,必须符合互联网域名命名规范。
    3. 与 Deployment 一样,StatefulSet 也使用规格中声明的 template 模板来创建 Pod 资源,但是这些 Pod 相互之间是不能替换的;除此之外 StatefulSet 会为每个 Pod 设置一个单独的持久标识符,这些用于标识序列的标识符在发生调度时也不会丢失。
  2. Service, 暴露StatefulSet用的. 可以通过statufulset-name.service-name这种域名的形式访问, 集群起来以后会默认有一个crashreport-db0.crashreport-db域名供使用, 该host-name crashreport-db0可以从endpoint的subsets里找到, crashreport-db是service name. 更加有区分性的例子是dev-config,dce分支下cache/cache-sts.yaml里面的内容.
  3. StorageClass, 定义PV用的, PV是静态的磁盘, StorageClass可以动态扩展PV。PV的动态创建依赖于StorageClass对象。我们不需要手动创建任何PV,所有的工作都由StorageClass为我们完成。可以选postgresql或者mysql.由 StorageClass 动态创建的 PersistentVolume 将使用类中 mountOptions 字段指定的挂载选项。StorageClass要每个数据库一个独立的name, 避免串掉。
  4. Secret. 保存一些私密信息,密码。
  5. PersistentVolumeClaim. 把PersistentVolume挂StatefulSet上,挂载到某个路径上,给数据库保存数据用. storageClassName指向StorageClass里面指定的name. PVC和PV的绑定是通过StorageClassName进行的。

数据库初始化

正规的方法是起一个job.登录到pod里面去初始化,不折腾了。

Sidekiq部署

因为sidekiq和主app代码完全一样,所以直接在主app的deployment里面复制了一份deployment,改了一下环境变量,然后在app entrypoint.sh里面通过环境变量决定是起app还是起sidekiq. sidekiq通过redis通信,所以不需要把sidekiq的deployment通过service暴露给app, 省了一个service.

注意, 除了复制deployment外,还可以通过_helpers.tpl里面的模板来生成多份deployment, 不过那个模板语法需要学习,OPS不是我的最终目标,就不去折腾了。

下面是更新后的deployment文件:

--- # app
apiVersion: apps/v1
kind: Deployment
metadata:
  name: crashreport-app
  labels:
    app: crashreport-app
spec:
  strategy:
    type: RollingUpdate
  replicas: 1
  revisionHistoryLimit: 0
  selector:
    matchLabels:
      app: crashreport-app
  template:
    metadata:
      labels:
        app: crashreport-app
    spec:
      serviceAccountName: 
      imagePullSecrets:
      containers:
        - name: crashreport-main
          image: "/:"
          imagePullPolicy: Always
          ports:
          - name: crashreporthttp
            containerPort: 
          env:
          - name: RAILS_ENV
            value: production
          - name: SERVICE_TYPE
            value: crashreport
--- #sidekiq statefulset
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: crashreportsidekiq
  labels:
    app: crashreportkiq
spec:
  updateStrategy: # 这地方和Deployment不同,Deployment里面就叫strategy
    type: RollingUpdate
  replicas: 1
  revisionHistoryLimit: 0
  serviceName: crashreport-sidekiq # 必须要,作为namespace/domain的一部分, 要符合域名命名规范。
  selector:
    matchLabels:
      app: crashreportkiq
  template:
    metadata:
      labels:
        app: crashreportkiq
    spec:
      serviceAccountName: 
      imagePullSecrets:
      containers:
        - name: crashreport-main
          image: "/:"
          imagePullPolicy: Always
          ports:
          - name: crashreporthttp
            containerPort: 
          env:
          - name: RAILS_ENV
            value: production
          - name: SERVICE_TYPE
            value: sidekiq
          volumeMounts: # PVC绑定
          - name: crashlog-disk
            mountPath: /app/crashlogs
  volumeClaimTemplates:  # PVC template
  - metadata:
      name: crashlog-disk
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1000Gi

问题记录

  1. push docker image的时候每次tag应该不一样,要不然会抛错: manifest invalid: manifest invalid 2.