Kubernetes进阶实践 二

通过HPA实现业务应用的动态扩缩容

HPA控制器介绍

当系统资源过高的时候,我们可以使用如下命令来实现 Pod 的扩缩容功能

kubectl -n jds scale deployment myblog --replicas=2

基本原理

HPA 通过监控分析控制器控制的所有 Pod 的负载变化情况来确定是否需要调整 Pod 的副本数量

HPA的实现有两个版本:

  • autoscaling/v1,只包含了根据CPU指标的检测,稳定版本
  • autoscaling/v2beta1,支持根据memory或者用户自定义指标进行伸缩

如何获取Pod的监控数据

  • k8s 1.8以下:使用heapster,1.11版本完全废弃
  • k8s 1.8以上:使用metric-server

为什么之前用 heapster ,现在废弃了项目,改用 metric-server ?

heapster时代,apiserver 会直接将metric请求通过apiserver proxy 的方式转发给集群内的 hepaster 服务,采用这种 proxy 方式是有问题的:

  • http://kubernetes_master_address/api/v1/namespaces/namespace_name/services/service_name[:port_name]/proxy
    
  • proxy只是代理请求,一般用于问题排查,不够稳定,且版本不可控

  • heapster的接口不能像apiserver一样有完整的鉴权以及client集成

  • pod 的监控数据是核心指标(HPA调度),应该和 pod 本身拥有同等地位,即 metric应该作为一种资源存在,如metrics.k8s.io 的形式,称之为 Metric Api

于是官方从 1.8 版本开始逐步废弃 heapster,并提出了上边 Metric api 的概念,而 metrics-server 就是这种概念下官方的一种实现,用于从 kubelet获取指标,替换掉之前的 heapster

Metrics Server可以通过标准的 Kubernetes API 把监控数据暴露出来,比如获取某一Pod的监控数据:

https://192.168.136.10:6443/apis/metrics.k8s.io/v1beta1/namespaces/<namespace-name>/pods/<pod-name>

# kubectl -n jds get pods -v=7
## https://10.2.2.18:6443/api/v1/namespaces/jds/pods?limit=500

目前的采集流程:

HPA 调用 metrics API,metrics API属于 metrics Server,最后metrics Server 又调用kubelet

Metric Server

官方介绍

...
Metric server collects metrics from the Summary API, exposed by Kubelet on each node.

Metrics Server registered in the main API server through Kubernetes aggregator, which was introduced in Kubernetes 1.7
...

安装

官方代码仓库地址:https://github.com/kubernetes-sigs/metrics-server

  • --kubelet-preferred-address-types - The priority of node address types used when determining an address for connecting to a particular node (default [Hostname,InternalDNS,InternalIP,ExternalDNS,ExternalIP])
  • --kubelet-insecure-tls - Do not verify the CA of serving certificates presented by Kubelets. For testing purposes only.
  • --requestheader-client-ca-file - Specify a root certificate bundle for verifying client certificates on incoming requests.
$ wget https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.3.6/components.yaml

修改args参数:

 84       containers:
 85       - name: metrics-server
 86         image: registry.aliyuncs.com/google_containers/metrics-server-amd64:v0.3.6
 87         imagePullPolicy: IfNotPresent
 88         args:
 89           - --cert-dir=/tmp
 90           - --secure-port=4443
 91           - --kubelet-insecure-tls
 92           - --kubelet-preferred-address-types=InternalIP

执行安装:

$ kubectl create -f components.yaml

$ kubectl -n kube-system get pods

# kubectl可执行top参数
$ kubectl top nodes

kubelet的指标采集

无论是 heapster还是 metric-server,都只是数据的中转和聚合,两者都是调用的 kubelet 的 api 接口获取的数据,而 kubelet 代码中实际采集指标的是 cadvisor 模块,可以在 node 节点访问 10250 端口获取监控数据:

  • Kubelet Summary metrics: https://127.0.0.1:10250/metrics,暴露 node、pod 汇总数据
  • Cadvisor metrics: https://127.0.0.1:10250/metrics/cadvisor,暴露 container 维度数据

调用示例:执行以下会获取到很多资源信息

$ curl -k  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Im1TZ3ZzNlJKLUI0TDU0NVUxOXVzZGxJMERhMWI0X1NQLU1sRllYRlRYb1EifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi10b2tlbi1neDRubiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjUyYTE4YzY1LTQ1ZjQtNDllMC1iZjgzLWEzNzA2YzAyNjRjNyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlcm5ldGVzLWRhc2hib2FyZDphZG1pbiJ9.ZXKIJ3LFy5FipPUMZ3EFyDDTsjoBGZUKm-s74zRJEIhZ_2EzNO6-SE_wnPO92tjDpBY8HQROatjQGM4Mr6sGaC65JIAt5xEd7bbaLU0t4OjD9DvcdJynQS6YIT2gEeGPuKLPkcJfZzToUYDiJRV9EKUuLBKSqHM7i8Qra3yyWulMtNk5ahlMlnxTqv6J6b_jezdicgNjJua9taCuUuxLmJMsJVoBaaUMCcbQX7ecfNM_vPQIxi-sN4NtP6Ynvf3bdP-kj-4ot15b4qJwhI1fANDEo_4v6hNl6zPNXGgdvxZJlb1pIlIAMMuHIEXtRe6IZJPKz3onE5-ZP8XxOuSC6w" https://127.0.0.1:10250/metrics

⚠️注意: -H 后面是ServiceAccount的token

kubelet虽然提供了 metric 接口,但实际监控逻辑由内置的Cadvisor模块负责,早期的时候,cadvisor是单独的组件,从k8s 1.12开始,cadvisor 监听的端口在k8s中被删除,所有监控数据统一由Kubelet的API提供

cadvisor获取指标时实际调用的是 runc/libcontainer库,而libcontainer是对 cgroup文件 的封装,即 cadvsior也只是个转发者,它的数据来自于cgroup文件

cgroup文件中的值是监控数据的最终来源,如:

  • mem usage的值
    • 对于docker容器来讲,来源于/sys/fs/cgroup/memory/docker/[containerId]/memory.usage_in_bytes
    • 对于pod来讲,/sys/fs/cgroup/memory/kubepods/besteffort/pod[podId]/memory.usage_in_bytes或者/sys/fs/cgroup/memory/kubepods/burstable/pod[podId]/memory.usage_in_bytes
    • 如果没限制内存(跟宿主机是一样的),Limit = machine_mem,否则来自于/sys/fs/cgroup/memory/docker/[id]/memory.limit_in_bytes
    • 内存使用率 = memory.usage_in_bytes/memory.limit_in_bytes

Metrics数据流:

Metrics Server是独立的一个服务,只能服务内部实现自己的api,如何做到通过标准的kubernetes 的API格式暴露出去看下面

kube-aggregator

kube-aggregator聚合器及Metric-Server的实现

kube-aggregator是对 apiserver 的api的一种拓展机制,它允许开发人员编写一个自己的服务,并把这个服务注册到k8s的api里面,即扩展 API 。

apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  name: v1beta1.luffy.k8s.io
spec:
  group: luffy.k8s.io
  groupPriorityMinimum: 100
  insecureSkipTLSVerify: true
  service:
    name: service-A       # 必须https访问
    namespace: luffy
    port: 443   
  version: v1beta1
  versionPriority: 100

k8s会自动帮我们代理如下url的请求:

proxyPath := "/apis/" + apiService.Spec.Group + "/" + apiService.Spec.Version

即:https://192.168.136.10:6443/apis/jds.k8s.io/v1beta1/xxxx 转到service-A服务中,service-A中只需要实现 https://service-A/jds.k8s.io/v1beta1/xxxx 即可

看metric-server的实现:

$ kubectl get apiservice 
NAME                       SERVICE                      AVAILABLE                      
v1beta1.metrics.k8s.io   kube-system/metrics-server		True

$ kubectl get apiservice v1beta1.metrics.k8s.io -oyaml
...
spec:
  group: metrics.k8s.io
  groupPriorityMinimum: 100
  insecureSkipTLSVerify: true
  service:
    name: metrics-server
    namespace: kube-system
    port: 443
  version: v1beta1
  versionPriority: 100
...

$ kubectl -n kube-system get svc metrics-server
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
metrics-server   ClusterIP   10.110.111.146   <none>        443/TCP   11h

$ curl -k  -H "Authorization: Bearer xxxx" https://10.110.111.146
{
  "paths": [
    "/apis",
    "/apis/metrics.k8s.io",
    "/apis/metrics.k8s.io/v1beta1",
    "/healthz",
    "/healthz/healthz",
    "/healthz/log",
    "/healthz/ping",
    "/healthz/poststarthook/generic-apiserver-start-informers",
    "/metrics",
    "/openapi/v2",
    "/version"
  ]

# https://192.168.136.10:6443/apis/metrics.k8s.io/v1beta1/namespaces/<namespace-name>/pods/<pod-name>
# 
# 使用的是 kubectl -n kube-system get svc 里的 metrics-server cluster IP
$ curl -k  -H "Authorization: Bearer xxxx" https://10.110.111.146/apis/metrics.k8s.io/v1beta1/namespaces/jds/pods/myblog-5d9ff54d4b-4rftt

$ curl -k  -H "Authorization: Bearer xxxx" https://192.168.136.10:6443/apis/metrics.k8s.io/v1beta1/namespaces/jds/pods/myblog-5d9ff54d4b-4rftt

HPA实践

基于CPU的动态伸缩

创建hpa对象:

# 方式一
$ cat hpa-myblog.yaml
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: hpa-myblog-cpu
  namespace: jds
spec:
  maxReplicas: 3
  minReplicas: 1
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myblog
  targetCPUUtilizationPercentage: 10

# 方式二
$ kubectl -n luffy autoscale deployment myblog --cpu-percent=10 --min=1 --max=3

Deployment对象必须配置requests的参数,不然无法获取监控数据,也无法通过HPA进行动态伸缩

验证:

$ kubectl apply -f hpa-myblog.yaml
$ yum -y install httpd-tools
$ kubectl -n jds get svc myblog
myblog   ClusterIP   10.104.245.225   <none>        80/TCP    6d18h

# 为了更快看到效果,先调整副本数为1
$ kubectl -n jds scale deploy myblog --replicas=1

# 模拟1000个用户并发访问页面10万次
$ ab -n 100000 -c 1000 http://10.104.245.225/blog/index/

$ kubectl get hpa
$ kubectl -n jds get pods

压力降下来后,会有默认5分钟的scaledown的时间,可以通过controller-manager的如下参数设置:

--horizontal-pod-autoscaler-downscale-stabilization

The value for this option is a duration that specifies how long the autoscaler has to wait before another downscale operation can be performed after the current one has completed. The default value is 5 minutes (5m0s).

是一个逐步的过程,当前的缩放完成后,下次缩放的时间间隔,比如从3个副本降低到1个副本,中间大概会等待2*5min = 10分钟

基于内存的动态伸缩

注意: 先创建config文件,再创建deployment文件,最后创建hpa文件

创建hpa对象

$ cat hpa-demo-mem.yaml
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: hpa-demo-mem
  namespace: luffy
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hpa-demo-mem
  minReplicas: 1
  maxReplicas: 3
  metrics:
  - type: Resource
    resource:
      name: memory
      targetAverageUtilization: 30

加压演示脚本:

$ cat increase-mem-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: increase-mem-config
  namespace: luffy
data:
  increase-mem.sh: |
    #!/bin/bash  
    mkdir /tmp/memory  
    mount -t tmpfs -o size=40M tmpfs /tmp/memory  
    dd if=/dev/zero of=/tmp/memory/block  
    sleep 60 
    rm /tmp/memory/block  
    umount /tmp/memory  
    rmdir /tmp/memory

测试deployment:

$ cat hpa-demo-mem-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hpa-demo-mem
  namespace: luffy
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      volumes:
      - name: increase-mem-script
        configMap:
          name: increase-mem-config
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        volumeMounts:
        - name: increase-mem-script
          mountPath: /etc/script
        resources:
          requests:
            memory: 50Mi
            cpu: 50m
        securityContext:     # 超级用户权限
          privileged: true

测试:

$ kubectl create -f increase-mem-config.yaml
$ kubectl create -f hpa-demo-mem.yaml
$ kubectl create -f hpa-demo-mem-deploy.yaml

$ kubectl -n luffy exec -ti hpa-demo-mem-7fc75bf5c8-xx424 sh
#/ sh /etc/script/increase-mem.sh


# 观察hpa及pod
$ kubectl -n luffy get hpa
$ kubectl -n luffy get po

基于自定义指标的动态伸缩

除了基于 CPU 和内存来进行自动扩缩容之外,我们还可以根据自定义的监控指标来进行。这个我们就需要使用 Prometheus Adapter,Prometheus 用于监控应用的负载和集群本身的各种指标,Prometheus Adapter 可以帮我们使用 Prometheus 收集的指标并使用它们来制定扩展策略,这些指标都是通过 APIServer 暴露的,而且 HPA 资源对象也可以很轻易的直接使用

架构图:

kubernetes对接分部式存储

PV与PVC快速入门

k8s存储的目的就是保证Pod重建后,数据不丢失。简单的数据持久化的下述方式:

  • emptyDir
apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: webserver
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  - image: k8s.gcr.io/test-redis
    name: redis
    volumeMounts:
    - mountPath: /data
      name: cache-volume
volumes:
  - name: cache-volume
    emptyDir: {}

-Pod内的容器共享卷的数据
-存在于Pod的生命周期,Pod销毁,数据丢失
-Pod内的容器自动重建后,数据不会丢失

  • hostPath
apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # directory location on host
      path: /data
      # this field is optional
      type: Directory

通常配合nodeSelector使用

  • nfs存储
...
  volumes:
  - name: redisdata             #卷名称
    nfs:                        #使用NFS网络存储卷
      server: 192.168.31.241    #NFS服务器地址
      path: /data/redis         #NFS服务器共享的目录
      readOnly: false           #是否为只读
...

volume支持的种类众多(参考 https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes ),每种对应不同的存储后端实现,因此为了屏蔽后端存储的细节,同时使得Pod在使用存储的时候更加简洁和规范,k8s引入了两个新的资源类型,PV和PVC。

PersistentVolume(持久化卷),是对底层的存储的一种抽象,它和具体的底层的共享存储技术的实现方式有关,比如 Ceph、GlusterFS、NFS 等,都是通过插件机制完成与共享存储的对接。如使用PV对接NFS存储:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity: 
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /data/k8s
    server: 121.204.157.52
  • capacity,存储能力, 目前只支持存储空间的设置, 就是我们这里的 storage=1Gi,不过未来可能会加入 IOPS、吞吐量等指标的配置
  • accessModes,访问模式, 是用来对 PV 进行访问模式的设置,用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:
    • ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
    • ReadOnlyMany(ROX):只读权限,可以被多个节点挂载
    • ReadWriteMany(RWX):读写权限,可以被多个节点挂载

  • persistentVolumeReclaimPolicy,pv的回收策略, 目前只有 NFS 和 HostPath 两种类型支持回收策略
    • Retain(保留)- 保留数据,需要管理员手工清理数据
    • Recycle(回收)- 清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*
    • Delete(删除)- 与 PV 相连的后端存储完成 volume 的删除操作,当然这常见于云服务商的存储服务,比如 ASW EBS

因为PV是直接对接底层存储的,就像集群中的Node可以为Pod提供计算资源(CPU和内存)一样,PV可以为Pod提供存储资源。因此PV不是namespaced的资源,属于集群层面可用的资源。Pod如果想使用该PV,需要通过创建PVC挂载到Pod中。

PVC全写是PersistentVolumeClaim(持久化卷声明),PVC 是用户存储的一种声明,创建完成后,可以和PV实现一对一绑定。对于真正使用存储的用户不需要关心底层的存储实现细节,只需要直接使用 PVC 即可。定义pvc需要namespace

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-nfs
  namespace: default
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

然后Pod中通过如下方式去使用:

...
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
          name: web
        volumeMounts:                        #挂载容器中的目录到pvc nfs中的目录
        - name: www
          mountPath: /usr/share/nginx/html
      volumes:
      - name: www
        persistentVolumeClaim:              #指定pvc
          claimName: pvc-nfs
...

PV与PVC管理NFS存储卷实践

环境准备

服务端:10.2.2.50

$ yum -y install nfs-utils rpcbind

# 共享目录
$ mkdir -p /data/k8s && chmod 755 /data/k8s

$ echo '/data/k8s  *(insecure,rw,sync,no_root_squash)'>>/etc/exports

$ systemctl enable rpcbind && systemctl start rpcbind
$ systemctl enable nfs && systemctl start nfs

客户端:k8s集群slave节点

$ yum -y install nfs-utils rpcbind
$ mkdir /nfsdata
$ mount -t nfs 10.2.2.50:/data/k8s /nfsdata

PV与PVC演示

$ cat pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity: 
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /data/k8s
    server: 10.2.2.50

$ kubectl create -f pv-nfs.yaml

$ kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS  
nfs-pv   1Gi        RWO            Retain           Available

一个 PV 的生命周期中,可能会处于4中不同的阶段:
-Available(可用):表示可用状态,还未被任何 PVC 绑定
-Bound(已绑定):表示 PV 已经被 PVC 绑定
-Released(已释放):PVC 被删除,但是资源还未被集群重新声明
-Failed(失败): 表示该 PV 的自动回收失败

$ cat pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-nfs
  namespace: default
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

$ kubectl create -f pvc.yaml
$ kubectl get pvc
NAME      STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-nfs   Bound    nfs-pv   1Gi        RWO                           3s
$ kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             
nfs-pv   1Gi        RWO            Retain           Bound    default/pvc-nfs             

#访问模式,storage大小(pvc大小需要小于pv大小),以及 PV 和 PVC 的 storageClassName 字段必须一样,这样才能够进行绑定。

#PersistentVolumeController会不断地循环去查看每一个 PVC,是不是已经处于 Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与未绑定的 PVC 进行绑定,这样,Kubernetes 就可以保证用户提交的每一个 PVC,只要有合适的 PV 出现,它就能够很快进入绑定状态。而所谓将一个 PV 与 PVC 进行“绑定”,其实就是将这个 PV 对象的名字,填在了 PVC 对象的 spec.volumeName 字段上。

# 查看nfs数据目录
$ ls /nfsdata

对接Ceph存储实践

ceph的安装及使用参考 http://docs.ceph.org.cn/start/intro/

posted @ 2021-01-18 22:06  一墨无辰  阅读(592)  评论(0)    收藏  举报