作者:Charl

在 CI/CD 流程中完成 docker 镜像的打包任务之后需要将服务所对应的镜像部署到 k8s集群中。k8s 提供了多种可以编排调度的资源对象。首先,我们简单了解一下 k8s 中的一些基本资源。

k8s 基本资源对象概览

pod

pod 作为无状态应用的运行实体是其中最常用的一种资源对象, k8s 中资源调度最小的基本单元,它包含一个或多个紧密联系的容器。这些容器共享存储、网络和命名空间,以及如何运行的规范。

在 k8s 中, pod 是非持久的,会因为节点故障或者网络不通等情况而被销毁和重建。所以我们在 k8s 中一般不会直接创建一个独立的 Pod,而是通过多个 pod 对外提供服务。

ReplicaSet

ReplicaSet 是 k8s 中的一种副本控制器,控制由其管理的 pod,使 pod 副本的数量维持在预设的个数。ReplicaSets 可以独立使用,但是在大多数场景下被 Deployments 作为协调 pod 创建,删除和更新的机制。

Deployment

Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义方法。通过在 Deployment 中进行目标状态的描述,Deployment controller 会将 Pod 和 ReplicaSet 的实际状态改变为所设定的目标状态。Deployment 典型的应用场景包括:

  • 定义 Deployment 来创建 Pod 和 ReplicaSet
  • 滚动升级和回滚应用
  • 扩容和缩容
  • 暂停和继续 Deployment

Service

在 k8s 中,pod 会被随时创建或销毁,每个 pod 都有自己的 IP,这些 IP 也无法持久存在,所以需要 Service 来提供服务发现和负载均衡能力。 Service 是一个定义了一组 pod 的策略的抽象,通过 Label Selector 来确定后端访问的pod,从而为客户端访问服务提供了一个入口。每个 Service 会对应一个集群内部的 ClusterIP,集群内部可以通过 ClusterIP 访问一个服务。如果需要对集群外部提供服务,可以通过 NodePort 或 LoadBalancer 方式。

deployment.yml 配置

deployment.yml 文件用来定义 Deployment。首先通过一个简单的 deployment.yml 配置文件熟悉 Deployment 的配置格式。


k8如何删除容器 k8s删除deployment_Deployment


上图中 deployment.yml 分为8个部分,分别如下:

  1. apiVersion 为当前配置格式的版本
  2. kind 指定了资源类型,这边当然是 Deployment
  3. metadata 是该资源的元数据,其中name 是必需的数据项,还可以指定label 给资源加上标签
  4. spec 部分是该 Deployment 的规格说明
  5. spec.replicas 指定了 pod 的副本数量
  6. spec.template 定义 pod 的基本信息,通过 spec.template.metadataspec.template.spec 指定
  7. spec.template.metadata 定义 pod 的元数据。至少需要定义一个 label 用于 Service 识别转发的 pod, label 是通过 key-value 形式指定的
  8. spec.template.spec 描述 pod 的规格,此部分定义 pod 中每一个容器的属性,nameimage 是必需项

在实际应用中,还有更多灵活个性化的配置。我们在 k8s 的部署实践中制定了相关的规范,在以上基础结构上进行配置,得到满足我们实际需求的 deployment.yml 配置文件。

在 k8s 的迁移实践中,我们主要在以下方面对 Deployment 的配置进行了规范的约定:

文件模板化

首先我们的 deployment.yml 配置文件是带有变量的模版文件,如下所示:


apiVersion: apps/v1beta2
kind: Deployment
metadata:
  labels:
    app: __APP_NAME__
    group: __GROUP_NAME__
  name: __APP_NAME__
  namespace: __NAMESPACE__


__APP_NAME____GROUP_NAME____NAMESPACE__这种形式的变量都是在 CI/CD 流程中会被替换成 gitlab 每个 project 所对应的变量,目的是为了多了 project 用相同的 deployment.yml 文件,以便在进行 k8s 迁移时可以快速复制,提高效率。

服务名称

  • k8s中运行的service以及deployment名称由 gitlab 中的 group_name 和 project_name 组成,即 {{group_name}}-{{project_name}},例:microservice-common。此名称记为 app_name,作为每个服务在k8s中的唯一标识。这些变量可以通过 gitlab-ci 的内置变量中进行获取,无需对每个 project 进行特殊的配置。
  • Lables 中用于识别服务的标签与 Deployment 名称保持一致,统一设置为 app:{{app_name}}

资源分配

  • 节点配置策略

以项目组作为各项目pod运行在哪些node节点的依据,属于同一项目组的项目的 pod 运行在同一批 node 节点。

具体操作方式为给每个node节点打上形如 group:__GROUP_NAME__ 的标签,在deployment.yml 文件中做如下设置进行 pod 的 node 节点选择:


...
  spec:
    ...
    template:
      ...
      spec:
        ...
        affinity:
          nodeAffinity:
            requiredDuringSchedulingIgnoredDuringExecution:
              nodeSelectorTerms:
              - matchExpressions:
                - key: group
                  operator: In
                  values:
                  - __GROUP_NAME__
     ...


  • 资源请求大小

对于一些重要的线上应用,limit 和 request 设置一致,资源不足时 k8s 会优先保证这些 pod 正常运行。为了提高资源利用率。 对一些非核心,并且资源不长期占用的应用,可以适当减少 pod 的 request,这样 pod 在调度时可以被分配到资源不是十分充裕的节点,提高使用率。但是当节点的资源不足时,也会优先被驱逐或被 oom kill。

健康检查(Liveness/Readiness)配置

Liveness 主要用于探测容器是否存活,若监控检查失败会对容器进行重启操作。 Readiness 则是通过监控检测容器是否正常提供服务来决定是否加入到 Service 的转发列表接收请求流量。Readiness 在 升级过程可以发挥重要的作用,防止升级时异常的新版本 pod 替换旧版本 pod 导致整个应用将无法对外提供服务的情况。

每个服务必须提供可以正常访问的接口,在 deployment.yml 文件配置好相应的监控检测策略。


...
spec:
  ...
  template:
    ...
    spec:
      ...
      containers:
      - name: fpm
        livenessProbe:
          httpGet:
            path: /__PROJECT_NAME__
            port: 80
          initialDelaySeconds: 3
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /__PROJECT_NAME__
            port: 80
          initialDelaySeconds: 3
          periodSeconds: 5
        ...
   ...


升级策略配置

升级策略我们选择 RollingUpdate 的方式,即在升级过程中滚动式地逐步新建新版本的 pod,待新建 pod 正常启动后逐步 kill 掉老版本的 pod,最终全部新版本的 pod 替换为旧版本的 pod。


k8如何删除容器 k8s删除deployment_k8如何删除容器_02


我们还可以设置 maxSurge 和 maxUnavailable 的值分别控制升级过程中最多可以比原先设置多出的 pod 比例以及升级过程中最多有多少比例 pod 处于无法提供服务的状态。


...
spec:
  ...
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
...


日志配置

采用 log-tail 对容器日志进行采集,所有服务的日志都上报到阿里云日志服务的一个 log-store中。 在 deployment.yml 文件里配置如下:


...
spec:
  ...
  template:
    ...
    spec:
      ...
      containers:
      - name: fpm
        env:
        - name: aliyun_logs_vpgame
          value: stdout
        - name: aliyun_logs_vpgame_tags
          value: topic=__APP_NAME__
        ...
   ...


通过设置环境变量的方式来指定上传的 logstore 和对应的 tag,其中 name 表示 logstore 的名称。通过 topic 字段区分不同服务的日志。

监控配置

通过在deployment中增加 annotations 的方式,令 prometheus 可以获取每个pod的业务监控数据。配置示例如下:


...
spec:
 ...
 template:
    metadata:
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "80"        
        prometheus.io/path: /{{ project_name }}/metrics
  ...


其中prometheus.io/scrape: "true" 表示可以被 prometheus 获取,prometheus.io/port表示监控数据的端口,prometheus.io/path 表示获取监控数据的路径。

service.yml 配置

service.yml 文件主要对 Service 进行了描述。


apiVersion: v1
kind: Service
metadata:
  annotations:
    service.beta.kubernetes.io/alicloud-loadbalancer-address-type: intranet
  labels:
    app: __APP_NAME__
  name: __APP_NAME__
  namespace: __NAMESPACE__
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: __APP_NAME__
  type: LoadBalancer


对 Service 的定义相比于 Deoloyment 要简单的多,通过定义 spec.ports 的相关参数可以指定 Service 的对外暴露的端口已经转发到后端 pod 的端口。 spec.selector 则是指定了需要转发的 pod 的 label。

另外,我们这边是通过负载均衡器类型对外提供服务,这是通过定义 spec.type 为 LoadBalancer 实现的。通过 增加 metadata.annotations 为 service.beta.kubernetes.io/alicloud-loadbalancer-address-type: intranet 可以在对该 Service 进行创建的同时创建一个阿里云内网 SLB 作为对该 Service 请求流量的入口。


k8如何删除容器 k8s删除deployment_k8如何删除容器_03


如上图所示,EXTERNAL-IP 即为 SLB 的 IP。