作者: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 的配置格式。
上图中 deployment.yml 分为8个部分,分别如下:
-
apiVersion
为当前配置格式的版本 -
kind
指定了资源类型,这边当然是Deployment
-
metadata
是该资源的元数据,其中name
是必需的数据项,还可以指定label
给资源加上标签 -
spec
部分是该Deployment
的规格说明 -
spec.replicas
指定了 pod 的副本数量 -
spec.template
定义 pod 的基本信息,通过spec.template.metadata
和spec.template.spec
指定 -
spec.template.metadata
定义 pod 的元数据。至少需要定义一个 label 用于 Service 识别转发的 pod, label 是通过 key-value 形式指定的 -
spec.template.spec
描述 pod 的规格,此部分定义 pod 中每一个容器的属性,name
和image
是必需项
在实际应用中,还有更多灵活个性化的配置。我们在 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。
我们还可以设置 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 请求流量的入口。
如上图所示,EXTERNAL-IP 即为 SLB 的 IP。