分类 云原生 下的文章

本文主要是对官方手册的解读,加上了一点基于实际压测后对方案的感受
https://yunlzheng.gitbook.io/prometheus-book/part-ii-prometheus-jin-jie/readmd/prometheus-local-storage

存储

架构

prometheus 2.x版本以两小时为一个周期,每个周期落盘记录为一个block,block下面有chunk样本数据、metadata元数据、index索引数据
未落盘的数据保存在内存中,如果发生意外导致应用崩溃,恢复的时候会从写入日志(WAL)中读取并重播以恢复数据,在此期间如果发生过删除数据的操作,也会被记录在逻辑文件tombstones
这种以时间段来分段的方式有利于数据的查询以及简化删除过期数据的逻辑
普罗米修斯中单个指标样本约占1-3字节,当然,在其可靠的压缩算法之下能变得更小(按fb的算法能压缩到 1.37 bytes 每个点,我们测在3左右),2.x版本甚至专门对ssd的读写性能和次数进行了优化,感动人心。存储大小的计算公式为

needed_disk_space = retention_time_seconds * ingested_samples_per_second * bytes_per_sample

如果保留时间和指标本身不变动的话,从ingested_samples_per_second(每秒采样总数)想要压缩存储成本就两个方法:
1.延长采样间隔
2.减少指标
prometheus压缩算法参考了fb的Gorilla tsdb,有兴趣可以研究一下

关于WAL机制更详细的介绍

data/
├── 01GCDV01N8T74K4HE9JRD7PNQP #block,命名采用ULID:全局字典可排序ID
│   ├── chunks #样本,单块最大512M
│   │   └── 000001
│   ├── index #索引
│   ├── meta.json #元数据
│   └── tombstones #逻辑
├── 01GCDYC64PDTMA62ZMT73GEX8S
│   ├── chunks
│   │   └── 000001
│   ├── index
│   ├── meta.json
│   └── tombstones
├── chunks_head
│   └── 000002
├── lock
├── queries.active
└── wal #写入日志
    ├── 00000000
    ├── 00000001
    ├── 00000002
    └── 00000003

内存中存放的数据,每满三个小时,会被分成2h和1h的一块,2h的那块落盘,1h的那块继续留在内存,数据在写入内存的同时也会被写入wal以确保持久性
内存中的存储结构我们叫其head_block,当获取到新的数据,会写入其中的head_chunk,chunk也是唯一能够被写入的地方。这个chunk每当样本数达到设定的值或者range达到设定的值的时候就会被剪切,然后新增一个新的chunk,旧的chunk被称为“full”,从2.19开始,一个full的chunk会被写入磁盘并通过内存映射引用在内存中,当需要用到的时候可以动态加载进来,当达到上面说的3h时,其中2h的chunk会被压缩成block落盘,wal中的数据也会形成checkpoint截断旧的数据,这个检查点将会检查这些被截断的数据,看看他们是否满足被删除的条件
新版本的普罗米修斯支持wal压缩,这将大大减少磁盘和内存的开销----旧版本可能会出现因为wal数据过多而导致硬盘和内存占用过大,可以参考这篇https://asktug.com/t/topic/512888/2 最最好的解决方法是升级版本

一篇压测文:https://www.percona.com/blog/2018/09/20/prometheus-2-times-series-storage-performance-analyses/

远程存储

虽然prometheus本身就支持本地存储,但是如果想要数据持久化,可以考虑外接influxdb搞个远程存储,只需要调用(remote_write/remote_read)这两个接口就可以实现远程存储的读写
对于远程读取,prometheus会对数据用promQL进行二次处理,另外,因为promtheus只有数据是放数据库的,规则等都是通过配置文件实现的,所以启用远程读设置后,只在数据查询时有效,对于规则文件的处理,以及Metadata API的处理都只基于Prometheus本地存储完成。
开启这两个功能只需要修改普罗米修斯的配置文件就行

remote_write:
    url: <string>
    [ remote_timeout: <duration> | default = 30s ]
    write_relabel_configs:
    [ - <relabel_config> ... ]
    basic_auth:
    [ username: <string> ]
    [ password: <string> ]
    [ bearer_token: <string> ]
    [ bearer_token_file: /path/to/bearer/token/file ]
    tls_config:
    [ <tls_config> ]
    [ proxy_url: <string> ]

remote_read:
    url: <string>
    required_matchers:
    [ <labelname>: <labelvalue> ... ]
    [ remote_timeout: <duration> | default = 30s ]
    [ read_recent: <boolean> | default = false ]
    basic_auth:
    [ username: <string> ]
    [ password: <string> ]
    [ bearer_token: <string> ]
    [ bearer_token_file: /path/to/bearer/token/file ]
    [ <tls_config> ]
    [ proxy_url: <string> ]

普罗米修斯现在也支持一些第三方的存储,所以上面说能够influxdb是基于这个原因,至于实现方式看上面的手册,这里不再复制粘贴

高可用:套娃方案

官方给出了三套高可用方案,不过其实本质上就是一套,分成了丐中丐版、丐版、完全版而已

对于大量数据的处理,官方给的方案是这套,用nginx负载均衡来保障可用性,两个大P的数据是一样的,挂了一个还有一个,小p们通过relabel设置分功能去收集或者随便咋分,总之采了数据上交给大P,以此来保证应对大量数据时的采集性能。最后再外挂个远程存储来实现数据持久化存储
image.png

image.png
所谓联邦集群,本质其实是部署多套普罗米修斯收集数据,最后再用一个普罗米修斯从这些个小p身上汇总数据

以上三部分可以按需选择,各自解决一部分问题

(方案挺不错的,不过我选择prometheus+thanos
https://github.com/thanos-io/thanos/tree/main/

官方的这套联邦方案,看起来很美好,但是实际测试下来性能很差,数据量大不推荐使用,毕竟他下面小弟汇总,再去拉取,这个数据量大了必卡(测下来别说亿级,百万级、大几十万级都会翻白眼直接超时),而且普罗米修斯本身的性能瓶颈也导致了即便是提高机器配置也很难提升多少性能

这就不得不提到另外一个著名的社区方案:thanos。比如布两套一样的普罗米修斯去采集事务再用thanos去查。这个方案和联邦方案的区别在于,单个pod采集上来的数据量本身是比较小的,虽然汇总起来很大,但是对于普罗米修斯来说就等于是在并行处理许多的小任务,这个普罗米修斯完全是可以做到的。而联邦集群的方案会导致大P去拉取小p的数据时,一次性的把小p获取到的所有数据打包拉了上来,等于在并行处理几个超巨大的任务,饭一口一口吃多吃几碗没问题,一口吃一碗会把人给噎死

Thanos

https://github.com/thanos-io/thanos/tree/main/

灭霸,可靠的开源高可用方案(笑

因为买的服务器过期了,所以这里安装就不演示了
avatar

thanos能无缝兼容promtheus,并且易于水平横向扩容,翻译成人话就是,要是一对prometheus拉不了所有的数据,可以把数据分组,用多组prometheus分别去拉
avatar
简单架构说明:

sidecar和prometheus布一个pod里面,他将prometheus的已落盘的数据上传到对象存储,将grpc的调用转换为http,请求prometheus查询并返回数据,可以像上面一样布两个promtheus去拉一样的数据,起到高可用的作用

query提供查询接口,当在prometheus数据保存期内就从sidecar查,如果超过保质期就去对象存储查,拿到结果后去重返回

object store对象存储,数据在结果compact组件(图上没画)压缩后存入对象存储,提供了数据持久化功能

storegateway提供查询语句转换功能,把promQL转换成对象存储能够理解的语句再发给他

虽然已落盘的数据在pod和obj里面都有,但是除非超过保质期,不然都是从pod里面查而非obj,对象存储的查询性能很差
thanos还有些其他的组件,像rule之类的,这里就不讲了

docker pull prom/node-exporter

node-exporter.yaml

apiVersion: apps/v1 
kind: DaemonSet #daemonset会保证每个node都有这个pod
metadata:
  name: node-exporter
  namespace: promtest
spec:
  selector: 
    matchLabels: 
      app: node-exporter
  template:
    metadata:
      labels:
        app: node-exporter
    spec:
      hostPID: true
      hostIPC: true
      hostNetwork: true
      containers:
      - name: node-exporter
        image: prom/node-exporter
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9100
          hostPort: 9100
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
          limits:
            cpu: 2000m
            memory: 2Gi        
        securityContext:
          privileged: true
        args:
        - --path.procfs
        - /host/proc
        - --path.sysfs
        - /host/sys
        - --path.rootfs
        - /host/root
        - --collector.filesystem.ignored-mount-points
        - '"^/(sys|proc|dev|host|etc)($|/)"'
        - "--collector.processes"
        volumeMounts:
        - name: dev
          mountPath: /host/dev
        - name: proc
          mountPath: /host/proc
        - name: sys
          mountPath: /host/sys
        - name: rootfs
          mountPath: /host/root
      tolerations: #污点容忍
      - key: "node-role.kubernetes.io/master"
        operator: "Exists"
        effect: "NoSchedule"
      volumes:
        - name: proc
          hostPath:
            path: /proc
        - name: dev
          hostPath:
            path: /dev
        - name: sys
          hostPath:
            path: /sys
        - name: rootfs
          hostPath:
            path: /
#如果要在容器环境中部署node-exporter,需要将主机的根节点绑定到容器环境,不然在查询磁盘等参数时,数据是不准确的

node-ser.yaml

kind: Service
apiVersion: v1
metadata:
  name: node-ser
  namespace: promtest
  labels:
    app: node-exporter
spec:
  ports:
  - name: metricsa
    port: 9100
    protocol: TCP
    targetPort: 9100
  selector:
    app: node-exporter

helm是k8s的包管理工具

安装

https://github.com/helm/helm/releases

  1. 解压(tar -zxvf helm-v3.0.0-linux-amd64.tar.gz)
  2. 在解压目中找到helm程序,移动到需要的目录中(mv linux-amd64/helm /usr/local/bin/helm)

入门

chart的结构一般如下

mychart/
  Chart.yaml
  values.yaml
  charts/
  templates/
  ...

templates/ 目录包括了模板文件。当Helm评估chart时,会通过模板渲染引擎将所有文件发送到templates/目录中。 然后收集模板的结果并发送给Kubernetes。
values.yaml 文件也导入到了模板。这个文件包含了chart的 默认值 。这些值会在用户执行helm install 或 helm upgrade时被覆盖。
Chart.yaml 文件包含了该chart的描述。你可以从模板中访问它。charts/目录 可以 包含其他的chart(称之为 _子chart_)。(描述性的,一般这个文件不用怎么管

你可以使用helm create chartname来快速创建一个项目文件夹,里面包含了一个完整的结构,甚至包含了一些预生成的模版,如下

[root@master linux-amd64]# helm create testlist
[root@master testlist]# ll
total 16
drwxr-xr-x 2 root root 4096 Sep  1 14:34 charts
-rw-r--r-- 1 root root 1144 Sep  1 14:34 Chart.yaml
drwxr-xr-x 3 root root 4096 Sep  1 14:34 templates
-rw-r--r-- 1 root root 1875 Sep  1 14:34 values.yaml

[root@master testlist]# ll templates/
total 32
-rw-r--r-- 1 root root 1841 Sep  1 14:34 deployment.yaml
-rw-r--r-- 1 root root 1792 Sep  1 14:34 _helpers.tpl
-rw-r--r-- 1 root root  919 Sep  1 14:34 hpa.yaml
-rw-r--r-- 1 root root 2081 Sep  1 14:34 ingress.yaml
-rw-r--r-- 1 root root 1751 Sep  1 14:34 NOTES.txt
-rw-r--r-- 1 root root  322 Sep  1 14:34 serviceaccount.yaml
-rw-r--r-- 1 root root  364 Sep  1 14:34 service.yaml
drwxr-xr-x 2 root root 4096 Sep  1 14:34 tests
[root@master testlist]# ll templates/tests/
total 4
-rw-r--r-- 1 root root 382 Sep  1 14:34 test-connection.yaml

你可以把他们全部删除然后写你自己的,你也可以在这些文件的基础上进行修改,helm官方有一些别人已经写好的helm,你可以helm pull把他们拉下来,但是一般实际环境还是得要根据自己的需求去写

常用指令

helm create chartname 创建一个helm工程文件夹
helm package path 打包一个helm版本包
helm lint path 验证chart
helm uninstall release_name 卸载一个已经安装的包
helm test 测试模拟安装
helm template 渲染模版,可以看看各个值映射的对不对

编写示例:普罗米修斯容器化方案

prometheus+grafana,并实现以文件的方式自动发现
当然,这里只是简单演示,具体请按自己情况来添加其他需要的功能
deployment:

prometheus.yaml

#/bin/prometheus --config.file=/etc/prometheus/prometheus.yml --storage.tsdb.path=/prometheus --web.console.libraries=/usr/share/prometheus/console_libraries --web.console.templates=/usr/share/prometheus/consoles

apiVersion: apps/v1
kind: StatefulSet
metadata: 
  name: prometheus
  namespace: {{.Values.namespace}}
spec: 
  serviceName: prometheus
  replicas: {{.Values.prom.replicaCount}}
  selector: 
    matchLabels: 
      app: prometheus
  template: 
    metadata: 
      labels: 
        app: prometheus
    spec: 
      containers: 
      - name: prometheus
        image: prom/prometheus:latest
        imagePullPolicy: IfNotPresent
        args:
        - "--config.file=/etc/prometheus/conf/prometheus.yml"
        # user: root
        # command:  ["chmod", "-R", "777", "/prometheus"]
        volumeMounts: 
          - mountPath: /etc/prometheus/conf/
            name: prometheusconf
          - name: storage 
            mountPath: /prometheus
          - name: static-sd
            mountPath: /var/
      volumes: 
      - name: prometheusconf
        hostPath:
          path: /prom/conf/ #配置文件存放地址
      - name: storage
        hostPath: 
          path: /data01/ #node上的挂载点,记得权限放高点,不然pod里面的用户没有修改这个目录的权限就会导致失败
          type: DirectoryOrCreate
      - name: static-sd
        configMap:
          name: prometheus-file-sd #节点自动发现

grafana.yaml

apiVersion: apps/v1 
kind: Deployment
metadata: 
  name: grafana
  namespace: {{.Values.namespace}}
spec: 
  replicas: 1 
  selector: 
    matchLabels: 
      app: grafana
  template: 
    metadata: 
      labels: 
        app: grafana 
    spec:
      containers: 
      - name: grafana
        image: grafana/grafana:latest
        imagePullPolicy: IfNotPresent

configuremap:

file-sd.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-file-sd
  namespace: {{ .Values.namespace }}
  labels:
    app: promtest
data:
  file-sd.json: |
    [
    ]

service

prom-ser.yaml

kind: Service
apiVersion: v1
metadata:
  name: prometheus
  namespace: {{ .Values.namespace }}
  labels:
    app: prometheus
spec:
  type: NodePort
  ports:
  - name: http
    port: 9090
    protocol: TCP
    targetPort: 9090
    nodePort: 30001
  selector:
    app: prometheus

gra-ser.yaml

kind: Service
apiVersion: v1
metadata:
  name: grafana
  namespace: {{ .Values.namespace }}
  labels:
    app: grafana
spec:
  type: NodePort
  ports:
  - name: grafana
    port: 3000
    targetPort: 3000
    nodePort: 30002
  selector:
    app: grafana

values.yaml

namespace: promtest

prom: 
  replicaCount: 1
  image:
  - name: prometheus
    image: prometheus:latest
    pullPolicy: IfNotPresent

用了几个月普罗米修斯了,回头再来补一篇博客(笑
参考:https://blog.csdn.net/cp3_zyh/article/details/124019043

简介

谷歌出品,基于Go编写,容器时代版本的答案!

特点:

  • 由指标名+键值对的标签标识的时序数据和转为监控而生的时序数据库TSDB
  • promQL
  • 自动发现
  • 成熟的容器化支持
  • 独立部署,无需agent,但是想要获得一些针对性的监控需要对应的exporter
  • 基于HTTP采集数据
  • 秒级采集精度
  • 基于数学模型计算,能够实现复杂的监控逻辑(如计算QPS相关的指标等)
  • 数据恢复机制


prometheus server可以独立部署,采集目标的数据并存储在tsdb中,自身对外提供promQL,计算告警规则并把触发告警的推送给altermanager
exporter可以帮助普罗米修斯采集数据,目前市面上常见的软件均已有现成的exporter,如mysql\nginx等
pushgateway主要用于针对一些短期存在的job,这些job可以直接向其push metrics,prometheus server周期性过去取数据就行
altermanager从server获取到alter之后,对其进行去重分组后路由至正确的告警方式,支持多种告警格式
serverdiscovery负责动态发现监控对象

普罗米修斯的TS数据库以每两小时为间隔来分block存储,每一个块又分chunk存储,chunk用于存储采集过来的ts数据、index、metadata,index是对metrics和labels进行索引后对存储。后台还会把小block合并成大block以减少内存里block的数量

普罗米修斯使用WAL机制来保护未落盘的数据不会因为突然宕机而丢失,WAL被分割成默认大小为128M的文件段(segment)文件段以数字命名,长度为8位的整形。WAL的写入单位是页(page),每页的大小为32KB,所以每个段大小必须是页的大小的整数倍。如果WAL一次性写入的页数超过一个段的空闲页数,就会创建一个新的文件段来保存这些页,从而确保一次性写入的页不会跨段存储。当出现宕机,等到机器恢复之后,普罗米修斯会把这些数据恢复进内存

Prometheus的数据由指标名称metric、一组键值对、时间戳、指标值组成,键值对可以在配置文件里面自定义,方便后面根据其来用promQL进行数据的分组、筛选等工作。
指标表达方式

<metric name>{<label name>=<label value>, ...} 

四种数据类型

Counter
Counter是计数器类型

  • Counter 用于累计值,例如记录请求次数、任务完成数、错误发生次数。
  • 一直增加,不会减少。
  • 重启进程后,会被重置。

Gague
Gauge是测量器类型

  • Gauge是常规数值,例如温度变化、内存使用变化。
  • 可变大,可变小。
  • 重启进程后,会被重置

Histogram
histogram是直方图,在Prometheus系统的查询语言中,有三种作用:
1)在一段时间范围内对数据进行采样(通常是请求持续时间或响应大小等),并将其计入可配置的存储桶(bucket)中. 后续可通过指定区间筛选样本,也可以统计样本总数,最后一般将数据展示为直方图。
2)对每个采样点值累计和(sum)
3)对采样点的次数累计和(count)
度量指标名称: [basename]_上面三类的作用度量指标名称_
举例:
对一批访问,分别列出响应时间在0~0.5,0.5~1,1+的数量

[basename]bucket{le="上边界"}, 这个值为小于等于上边界的所有采样点数量
[basename]_sum_
[basename]_count

Summary
Summary即概率图,类似于Histogram,常用于跟踪与时间相关的数据。典型的应用包括请求持续时
间、响应大小等。Summary同样提供样本的count和sum功能;还提供quantiles功能,可以按百分比划
分跟踪结果,例如,quantile取值0.95,表示取样本里的95%数据。Histogram需要计算,summary直接存储

安装与入门示例

https://prometheus.io/download/ 挑版本下载
解压
cp -rf prometheus-2.38.0.linux-amd64 /usr/local/prometheus
./prometheus &运行,默认运行在9090端口
但是注意这种方式启动会使得你丢失普罗米修斯的日志!!你可以配置个systemd或者用nohup来指定输出日志

[root@master prometheus]# ss -tnlp | grep 9090
LISTEN     0      128       [::]:9090                  [::]:*                   users:(("prometheus",pid=373,fd=8))

nohup ./prometheus > /var/log/prom.log 2>&1  & #nohup方式,把输出重定向到了我指定的日志文件里面

普罗米修斯本身还带有一些启动的参数

--config.file=”prometheus.yml” 指定配置文件
-–storage.tsdb.path=”data/”    指标存储的基本路径。
--storage.tsdb.retention=15d 指定保存的数据的时间

当然,因为这里只是演示,我就不加参数了,实际生产环境按需设置

访问端口就可以访问了,阿里云的服务器如果发现访问不了的话,先去安全组里面放行9090端口

关于普罗米修斯的访问控制可以看

https://blog.csdn.net/qq_31725371/article/details/114978760


image.png

prometheus的配置文件是prometheus.yml,当然你也可以用--yml来指定配置文件

# my global config
global:
  scrape_interval: 15s # 采集间隔Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # 每隔多久执行一次监控规则Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
    - static_configs:
        - targets:
          # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs: #这里开始就是监控项的配置了
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: "prometheus"

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
      - targets: ["localhost:9090"] #监控对象

这里我们把targets改成targets: ["localhost:9090", "node1:9100"]试试看(node1是另外一台服务器,已经配置了hosts,9100一般是node_exporter的端口
当然,这里得提前在机器上装好node_exporter并启动,安装方式同上,官网下载运行就行,当然,记得放行9100端口
image.png
以cpu使用率为例子

image.png

查询node_cpu_seconds_total,我们得到了各项cpu使用时间数据,counter类型的
image.png
通过cpu占用率的原理可以得知,cpu的使用率=cpu除空闲时间以外的总时间/总时间
所以这里应该这么写
(1-((sum(increase(node_cpu_seconds_total{mode="idle"}[1m])) by (instance)) / (sum(increase(node_cpu_seconds_total[1m])) by (instance)))) * 100
image.png
image.png
这个指标是个counter类型,累计的,{}取了带标签为mode="idle"的数据,increase()可以取一段时间内的增长量,因为一台云服务器一般不止1核,会查出多条cpu数据,单核单核看没多少意义,所以用sum进行总和。by instance是按实例拆分数据,如果是集群多机器的时候可以根据主机来区分数据分开展示。[1m]是就取一分钟内的数据,对于cpu这种实时性很强的指标取太长意义不大
((sum(increase(node_cpu_seconds_total{mode="idle"}[1m])) by (instance)) 分解⬇️
node_cpu_seconds_total{mode="idle"}[1m]
increase(node_cpu_seconds_total{mode="idle"}[1m])
sum(increase(node_cpu_seconds_total{mode="idle"}[1m]))
(sum(increase(node_cpu_seconds_total{mode="idle"}[1m])) by (instance)

promQL

https://blog.csdn.net/fu_huo_1993/article/details/114482586
这篇比较全,以下是部分

匹配器

使用{}可以通过标签来筛选数据

=: 精确的匹配给定的标签值
!=: 不匹配给定的标签值
=~: 正则表达式匹配给定的标签值
!~: 不匹配正则表格式给定的标签值
=~和!= 必须要指定一个标签名字或者匹配的标签必须要有值

例子:

查询出指标名称up中标签job等于prometheus的时间序列数据
up{job=“prometheus”}
查询出指标名称up中标签job以pro开头的的时间序列数据
up{job=~“pro.*”}
查询出指标名称up中不存在标签env的时间序列数据

时间区间

up[5m] 表示查询5分钟时间内各实例的状态
ms(毫秒)、s(秒)、m(分钟)、h(小时)、d(天)、w(周)、y(年)

d 表示一天总是24小时
w 表示一周总是7天
y 表示一年总是365天
可以将多个不同的时间单位串联起来使用,但是大的时间单位必须在前面,小的时间单位在后面,且不同的时间单位只能出现一次。且不能是小数。
eg: 1h30m可以,但是1.5h则不才可以。

偏移量

表示获取指标名称prometheus_http_requests_total的所有时间序列在距离此刻1分钟之前的5分钟之内的样本。

prometheus_http_requests_total[5m] offset 1m

运算符

+(加)、-(减)、*(乘)、/(除)、%(取模)、^(幂等)
== (等于)、!=(不等于)、>(大于)、<(小于)、>=(大于或等于)、<=(小于或等于)
and(并且-交集)、or(或者-并集)、unless(除非-补集)

如果存在bool修饰符,则结果只返回0,1.如果不使用bool修饰符,那么返回满足结果的个数
image.png
image.png

匹配

两个即时向量之间进行计算,需要拥有一样的标签才能计算,使用匹配关键字可以帮助我们达成计算目的
向量之间的运算尝试为左侧的每个条目在右侧向量中找到匹配的元素

ignoring:允许在匹配时忽略某些标签。

on:允许在匹配时只匹配指定的标签。

例:
method_code:http_errors:rate5m{method="get", code="500"}  24   和
method:http_requests:rate5m{method="get"}  600

这两个拥有共同的标签method,但是前者多一个code,正常来讲是无法计算的

method_code:http_errors:rate5m{code="500"} / ignoring(code) method:http_requests:rate5m
这种写法让我们忽视前者的code标签,这样两者拥有的标签就一样了
一对多和多对一:“一”的一侧的每个向量元素可以与“多”侧的多个元素进行匹配。必须使用group_left或group_right修饰符显式请求,其中left/right确定哪个向量具有更高的基数

例
method_code:http_errors:rate5m / ignoring(code) group_left method:http_requests:rate5m

内置函数

by 从计算结果从保留该标签、移除其他标签来分组,具体见上面的入门例子
without:与by类似,移除该标签保留其他标签
abs():绝对值
floor():粘贴并匹配样式
increase():返回区间范围内值的增长量,counter
rate():平均增长速率,区间内counter增长量/时间
irate():取最后两个值计算瞬间增长率
sum():合计
topk(n,metric):选出最高的几个值,counter,gague
cout():统计结果的条数

常用组件

https://prometheus.io/download/ 官方组件的下载地址

node_exporter

主机监控

push_gateway

因为pushgateway是被动获取数据,运行起来后取普罗米修斯里面单独给他加一个

  - job_name: "pushgateway"
    static_configs:
      - targets: ["localhost:9091"]

pushgateway可以自己写一些小脚本跑在机器上,定期给普罗米修斯推送

blackbox

端口监测,只能检活

grafana

https://grafana.com/grafana/download/9.1.2
service grafana-server start
以这种方式启动的grafana会使用默认的配置文件和日志文件
/etc/grafana/grafana.ini
/var/log/grafana/grafana.log

访问3000端口
image.png
配置数据源,然后建立dashboard或者先建立folder再建立dashboard,建议先建文件夹,grafana有完善的权限控制,后续可以把一个应用的监控面板放在一个文件夹里面,然后把相关的人员放进一个team里面,赋权的时候直接给team赋予权利就行了

容器化方案+自动发现+dashboard

prometheus有docker镜像,可以直接拉取
除此以外还有另外一套通过yaml来部署的项目,或者自己自定义文件来安装(如通过helm等
https://github.com/prometheus-operator/kube-prometheus

这里介绍通过helm编排容器,以及基于文件的自动发现,以下三篇为参考教程
https://yunlzheng.gitbook.io/prometheus-book/part-ii-prometheus-jin-jie/sd/service-discovery-with-file
https://cloud.tencent.com/developer/article/1885193
https://mp.weixin.qq.com/s?__biz=MzU4MjQ0MTU4Ng==&mid=2247495887&idx=1&sn=beddcaebaf6738a2d9f69bf5c35a348c&chksm=fdbaffd2cacd76c41e796065bc11ed608b404587c371281c3f3e3df4b65b81fc26178cbcf310&scene=21#wechat_redirect
docker pull prom/prometheus
docker pull grafana/grafana
在node机器上准备好prometheus.yaml,挂载目录建议提前创建好然后赋权赋高点,不然会出现权限问题导致应用创建失败

global:
  scrape_interval: 15s
  scrape_timeout: 10s
  evaluation_interval: 15s
scrape_configs:
- job_name: file_ds
  honor_timestamps: true
  scrape_interval: 15s
  scrape_timeout: 10s
  metrics_path: /metrics
  scheme: http
  follow_redirects: true
  file_sd_configs:
  - files:
    - /var/file-sd.json
    refresh_interval: 1m

helm编写,具体请见http://shangxizhuan.site/archives/411/例子部分

配置完后,尝试访问服务,成功,如果是阿里云之类的记得先放行端口
image.png

现在我们来测试一下自动发现功能正不正常,在一台机子上运行期node_exporter,然后去cm里面修改

apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-file-sd
  namespace: {{ .Values.namespace }}
  labels:
    app: promtest
data:
  file-sd.json: |
    [
      {
      "targets": [ "localhost:9090" ],
      "labels": {
        "env": "prod",
        "job": "local"
      }
    },
    {
      "targets": [ "server2:9100"],
      "labels": {
        "env": "ser2",
        "job": "node"
      }
    }
    ]

等个一分钟左右就看到新的节点已经被自动加载上来了
image.png
这里节点挂了的原因并不是node_exporter,而是这个exporter是单点的,不是集群里的pod,没做路由连接不上

dashboard部分,先拉取官方的recommand.yaml,按需修改
https://github.com/kubernetes/dashboard

http://www.live-in.org/archives/3320.html
如果发现pod起不来,试试安装在master节点,修改下面两处

    spec:
      nodeName: master节点的名字
      containers:
        - name: kubernetes-dashboard
          image: kubernetesui/dashboard:v2.0.4
          imagePullPolicy: Always


    spec:
      nodeName: master节点的名字
      containers:
        - name: dashboard-metrics-scraper
          image: kubernetesui/metrics-scraper:v1.0.4

443端口相关报错,删除yaml里面检活部分

如果进去发现列不出pod,试试添加角色

https://stackoverflow.com/questions/58719006/kubernetes-dashboard-error-using-service-account-token
https://stackoverflow.com/questions/46664104/how-to-sign-in-kubernetes-dashboard
kubectl delete clusterrolebinding kubernetes-dashboard kubectl create clusterrolebinding kubernetes-dashboard --clusterrole=cluster-admin --serviceaccount=kube-system:kubernetes-dashboard
kubectl create clusterrolebinding deployment-controller --clusterrole=cluster-admin --serviceaccount=kube-system:deployment-controller
获取token
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | awk '/^deployment-controller-token-/{print $1}') | awk '$1=="token:"{print $2}'

访问url,如果发现打不开,使用https再试试看,出现报错按照上面的方案处理

image.png

其他及缺陷

上面给的只是基础的容器化方案,大集群肯定会涉及到高可用的问题,对于这个,比起官方的联邦方案,我更推荐thanos

对存储机制和联邦集群方案的分析在这

普罗米修斯毋庸置疑是容器化环境下监控方案的旗帜,falcon虽好,但是不太支持容器,至于zabbix......什么年代了,还在用传统监控(笑

prometheus本身也是有点点缺陷的,当数据量很大的时候,启动会比较慢,因为其WAL机制,虽然保障了数据恢复机制,但是也使得其在启动的时候必须把还没落盘的数据都加载到内存里面,而且还是一块一块加载(虽然这边给官方提过意见,但是官方表示改不了,就这样)

关于数据采集,除了官方的和社区的那些exporter,一般公司肯定有自己开发的业务,这种一般得自己开发exporter,或者,java应用可以用micrometer打点,毕竟普罗米修斯他跟别的监控不太一样,采集哪些指标、指标怎么进行计算,除了通用的一些,这些只有开发者自己最清楚

https://github.com/micrometer-metrics/micrometer

deployment

以之前写的一个go微服务镜像为例子

https://k8s.easydoc.net/docs/dRiQjyTY/28366845/6GiNOzyZ/3iQiyInr

https://blog.csdn.net/m0_54024707/article/details/122225607 可作为参考,或者运行kubectl explain pod

image.png

  • 这里不知为啥直接拉镜像拉不下来,于是我把镜像在每个node上都拉了一份

    apiVersion: apps/v1 #kubectl api-versions
    kind: Deployment
    metadata: 
    name: go-microserver #部署的服务的名字
    spec: #详细定义
    replicas: 2 #副本数
    #使用标签来查找关联的pod
    selector: 
      matchLabels: 
        app: go-server-test
    #创建pod使用的模版
    template: 
      metadata: 
        labels: 
          app: go-server-test #这里必须和上面标签选择器相同,不然会找不到
      # 定义容器
      spec:
        containers: 
        - name: "go-service"
          image: registry-vpc.cn-hangzhou.aliyuncs.com/tokugawa/micro-go
          ports: 
          - name: httpd  #名称
            containerPort: 8080 #容器内部服务真正的端口,targetport映射在这个端口上
            protocol: TCP        
            # port: 8081 # 集群内部访问pod
            # nodePort: 8082 #集群外部客户端访问pod
            # targetPort: 8080 #pod的端口,从port和nodePort来的流量经过kube-proxy流入到后端pod的targetPort上,最后进入容器
     
            
    [root@master ~]# kubectl get pods -o wide
    NAME                             READY   STATUS    RESTARTS   AGE   IP            NODE    NOMINATED NODE   READINESS GATES
    go-microserver-8b87c8767-59t8t   1/1     Running   0          43s   10.244.0.16   node1   <none>           <none>
    go-microserver-8b87c8767-9pxkf   1/1     Running   0          43s   10.244.0.16   node2   <none>           <none>
    
    [root@master ~]# kubectl describe pod go-microserver-8b87c8767-kbk9n
    Name:         go-microserver-8b87c8767-kbk9n
    Namespace:    default
    Priority:     0
    Node:         node1/172.29.36.123
    Start Time:   Tue, 30 Aug 2022 15:00:55 +0800
    Labels:       app=go-server-test
                pod-template-hash=8b87c8767
    Annotations:  <none>
    Status:       Running
    IP:           10.244.0.14
    IPs:
    IP:           10.244.0.14
    Controlled By:  ReplicaSet/go-microserver-8b87c8767
    Containers:
    go-service:
      Container ID:   docker://7dc5da32e70e0e2453b9cbe36a13ed6947d35884a093df554ad4ddb0b617a607
      Image:          registry.cn-hangzhou.aliyuncs.com/tokugawa/micro-go:v1
      Image ID:       docker-pullable://registry.cn-hangzhou.aliyuncs.com/tokugawa/micro-go@sha256:04fbd8859168bf7e3321e1fcb574bc5b6d41f8c2915094e268cf45abb77f2515
      Port:           8080/TCP
      Host Port:      0/TCP
      State:          Running
        Started:      Tue, 30 Aug 2022 15:00:57 +0800
      Ready:          True
      Restart Count:  0
      Environment:    <none>
      Mounts:
        /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-qnk7x (ro)
    Conditions:
    Type              Status
    Initialized       True 
    Ready             True 
    ContainersReady   True 
    PodScheduled      True 
    Volumes:
    kube-api-access-qnk7x:
      Type:                    Projected (a volume that contains injected data from multiple sources)
      TokenExpirationSeconds:  3607
      ConfigMapName:           kube-root-ca.crt
      ConfigMapOptional:       <nil>
      DownwardAPI:             true
    QoS Class:                   BestEffort
    Node-Selectors:              <none>
    Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                               node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
    Events:
    Type    Reason     Age   From               Message
    ----    ------     ----  ----               -------
    Normal  Scheduled  100s  default-scheduler  Successfully assigned default/go-microserver-8b87c8767-kbk9n to node1
    Normal  Pulled     98s   kubelet            Container image "registry.cn-hangzhou.aliyuncs.com/tokugawa/micro-go:v1" already present on machine
    Normal  Created    98s   kubelet            Created container go-service
    Normal  Started    98s   kubelet            Started container go-service

    进入pod内部

    kubectl  exec -ti podname -- /bin/sh
    
    # pwd
    /go/src/mircoservice-1
    # ps -ef | grep example
    root         1     0  0 07:13 ?        00:00:00 /bin/sh -c ${SOURCES}example
    root         7     1  0 07:13 ?        00:00:00 /go/src/mircoservice-1/example
    root        18    11  0 07:16 pts/0    00:00:00 grep example
    # curl 127.0.0.1:8080
    <h1>hello!</h1></br>
    <h2>here is the main website!</h2>
    <b>enjoy your day!</b></br>

    扩容

    kubectl scale deployment go-microserver  --replicas=3
    deployment.apps/go-microserver scaled
    
    [root@master ~]# kubectl get pods
    NAME                              READY   STATUS    RESTARTS   AGE
    go-microserver-7d47df97fd-d25wg   1/1     Running   0          27m
    go-microserver-7d47df97fd-gmzj2   0/1     Pending   0          40s
    go-microserver-7d47df97fd-s849v   1/1     Running   0          27m

    使用指令端口映射,但是只对本地localhost网卡有效

    kubectl port-forward go-microserver-8b87c8767-6j77l 8080:8080
    
    [root@master ~]# curl localhost:8080
    <h1>hello!</h1></br>
    <h2>here is the main website!</h2>
    <b>enjoy your day!</b></br>

    查看日志

    [root@master ~]# kubectl logs pod/go-microserver-8b87c8767-6j77l
    [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
    
    [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
     - using env:   export GIN_MODE=release
     - using code:  gin.SetMode(gin.ReleaseMode)
    
    [GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
    [GIN-debug] GET    /hello                    --> main.main.func2 (3 handlers)
    [GIN-debug] Loaded HTML Templates (2): 
          - 
          - index.html
    
    [GIN-debug] GET    /ico                      --> github.com/gin-gonic/gin.(*RouterGroup).StaticFile.func1 (3 handlers)
    [GIN-debug] HEAD   /ico                      --> github.com/gin-gonic/gin.(*RouterGroup).StaticFile.func1 (3 handlers)
    [GIN-debug] GET    /                         --> main.main.func3 (3 handlers)
    [GIN-debug] GET    /api/books                --> main.main.func4 (3 handlers)
    [GIN-debug] GET    /api/books/:isbn          --> main.main.func5 (3 handlers)
    [GIN-debug] PUT    /api/books/:isbn          --> main.main.func6 (3 handlers)
    [GIN-debug] POST   /api/books                --> main.main.func7 (3 handlers)
    [GIN-debug] DELETE /api/books/:isbn          --> main.main.func8 (3 handlers)
    [GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
    Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
    [GIN-debug] Listening and serving HTTP on :8080
    [GIN] 2022/08/30 - 08:04:14 | 200 |     267.116µs |       127.0.0.1 | GET      "/"

    查看历史版本

    kubectl rollout history deployment go-microserver

service

上面起的实例如果想要访问里面的服务还得单独做端口映射,并且只在本机有效,也没办法做到负载均衡,很麻烦,如果我们想要在外部也能够访问到pod里面的服务,并且实现流量转发负载均衡等功能的话,我们可以使用service
service通过标签对应pod,生命周期不与pod绑定,提供负载均衡作用,对外提供访问的端口,对内使用名字可以直接访问,其他具体看前一篇博客

NodePort

apiVersion: v1
kind: Service
metadata:
  name: go-service
spec: #详细定义
  selector: 
    app: go-server-test
  type: NodePort
  ports: 
    - port: 8080  #可以配置多端口
      targetPort: 8080 # 容器端口
      nodePort: 31111 #节点端口  

部署后查看

[root@master ~]# kubectl get svc 
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
go-service   NodePort    10.101.79.120   <none>        8080:31111/TCP   5m34s
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP          17h

从另外一个节点用vip 8080访问,如果是外部机器访问则走31111,如果是云服务器,这里要记得放行端口

[root@node1 ~]# curl 10.101.79.120:8080
<h1>hello!</h1></br>
<h2>here is the main website!</h2>

statefulset

statefulset用于管理有状态的pod,例如数据库一类。pod的创建是顺序的,销毁是逆序的,如果被销毁重建,pod的名字不会改变,但是ip会变,所以使用名字而不要使用ip去连接。如果使用service名字去连接会随机连接上一个,要指定pod的话得用pod-name.service-name

这里以mysql(mariadb)官网镜像为例子

apiVersion: apps/v1
kind: StatefulSet
metadata: 
  name: mariadb
spec: 
  serviceName: mariadb
  replicas: 3
  selector: 
    matchLabels: 
      app: mariadb
  template: 
    metadata: 
      labels: 
        app: mariadb
    spec: 
      containers: 
      - name: mariadb
        image: mariadb:latest
        imagePullPolicy: IfNotPresent #只有当本地不存在镜像才从远处拉取
        env: 
        - name: MARIADB_ROOT_PASSWORD
          value: "123456"  #设置环境变量,这里必须至少得从三个里面选一个初始化:MARIADB_ROOT_PASSWORD, MARIADB_ALLOW_EMPTY_ROOT_PASSWORD and MARIADB_RANDOM_ROOT_PASSWORD

---

apiVersion: v1
kind: Service
metadata: 
  name: mariadb
spec: 
  selector: 
    app: mariadb
  type: ClusterIP #默认类型,自动分配一个仅 cluster (集群)内部可以访问的虚拟 IP
  clusterIP: None #headless
  ports: 
  - port: 3306
    targetPort: 3306

statefulset名字不再是随机的,这里因为做了个headless,所以clusterip没有ip

kubectl get pods -o wide
NAME                             READY   STATUS    RESTARTS   AGE   IP            NODE    NOMINATED NODE   READINESS GATES
go-microserver-74c85dd7f-8kzlh   1/1     Running   0          28h   10.244.0.21   node2   <none>           <none>
go-microserver-74c85dd7f-jlt4h   1/1     Running   0          28h   10.244.0.20   node1   <none>           <none>
go-microserver-74c85dd7f-m6wzl   1/1     Running   0          28h   10.244.0.20   node2   <none>           <none>
go-microserver-74c85dd7f-zntnr   1/1     Running   0          28h   10.244.0.21   node1   <none>           <none>
mariadb-0                        1/1     Running   0          17m   10.244.0.23   node1   <none>           <none>
mariadb-1                        1/1     Running   0          17m   10.244.0.23   node2   <none>           <none>
mariadb-2                        1/1     Running   0          17m   10.244.0.24   node1   <none>           <none>

[root@master ~]# kubectl get statefulset
NAME      READY   AGE
mariadb   3/3     7m23s

[root@master ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
go-service   NodePort    10.101.79.120   <none>        8080:31111/TCP   28h
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP          45h
mariadb      ClusterIP   None            <none>        3306/TCP         8m15s

[root@master ~]# kubectl get endpoints
NAME         ENDPOINTS                                                        AGE
go-service   10.244.0.20:8080,10.244.0.20:8080,10.244.0.21:8080 + 1 more...   28h
kubernetes   172.20.245.132:6443                                              45h
mariadb      10.244.0.23:3306,10.244.0.23:3306,10.244.0.24:3306               21m

这里登进去功能正常,但是连不通,换了nodeport用另外一台机器试一下连接发现被拒绝了,突然想起mariadb可能是初始化设置默认拒绝远程连接.....这里就不做演示了,多个副本可以配个主从,弄了端口映射后访问其中一个就行

数据持久化

statefulset的pod重建后里面的内容会丢失,如果想要数据持久化,我们可以挂载个盘上去,可以是本地磁盘,也可以是云厂商提供的相关服务,或者NFS之类的
如果是挂载在本地的目录,得要指定pod在那台机子上运行才行,这里以本地挂载的一种方式为例子

hostpath:使用本地目录
mariadb默认存储路径/var/lib/mysql/

apiVersion: apps/v1
kind: StatefulSet
metadata: 
  name: mariadb
spec: 
  serviceName: mariadb
  replicas: 1
  selector: 
    matchLabels: 
      app: mariadb
  template: 
    metadata: 
      labels: 
        app: mariadb
    spec: 
      containers: 
      - name: mariadb
        image: mariadb:latest
        imagePullPolicy: IfNotPresent #只有当本地不存在镜像才从远处拉取
        env: 
        - name: MARIADB_ROOT_PASSWORD
          value: "123456"
        volumeMounts: 
          - mountPath: /var/lib/mysql/ #容器里面的挂载路径
            name: mysqldata #卷的名字,必须和下面一样 
      volumes: 
      - name: mysqldata
        hostPath: 
          path: /data01/mysql #node上的挂载点
          type: DirectoryOrCreate     # 指向一个目录,不存在时自动创建


创建完去node1上查看,发现已经有文件了

[root@node1 ~]# ll /data01/mysql/
total 123328
-rw-rw---- 1 polkitd input    417792 Aug 31 22:55 aria_log.00000001
-rw-rw---- 1 polkitd input        52 Aug 31 22:55 aria_log_control
-rw-rw---- 1 polkitd input         9 Aug 31 22:55 ddl_recovery.log
-rw-rw---- 1 polkitd input       946 Aug 31 22:55 ib_buffer_pool
-rw-rw---- 1 polkitd input  12582912 Aug 31 22:55 ibdata1
-rw-rw---- 1 polkitd input 100663296 Aug 31 22:55 ib_logfile0
-rw-rw---- 1 polkitd input  12582912 Aug 31 22:55 ibtmp1
-rw-rw---- 1 polkitd input         0 Aug 31 22:55 multi-master.info
drwx------ 2 polkitd input      4096 Aug 31 22:55 mysql
drwx------ 2 polkitd input      4096 Aug 31 22:55 performance_schema
drwx------ 2 polkitd input     12288 Aug 31 22:55 sys

进pod创建个库表,塞点数据,发现node出现了对应的新文件

[root@master ~]# kubectl exec -it mariadb-0 -- /bin/bash
root@mariadb-0:/# mysql -uroot -p123456
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.6.5-MariaDB-1:10.6.5+maria~focal mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> create database db1 charset utf8;
Query OK, 1 row affected (0.000 sec)

MariaDB [(none)]> use db1
Database changed
MariaDB [db1]> create table tb1(name char(10),age int(3));
Query OK, 0 rows affected (0.007 sec)

MariaDB [db1]> insert into tb1 values("zhangsan",14),("lisi",15);
Query OK, 2 rows affected (0.001 sec)
Records: 2  Duplicates: 0  Warnings: 0

MariaDB [db1]> select * from tb1;
+----------+------+
| name     | age  |
+----------+------+
| zhangsan |   14 |
| lisi     |   15 |
+----------+------+
2 rows in set (0.000 sec)





[root@node1 ~]# ll /data01/mysql/
total 123340
-rw-rw---- 1 polkitd input    417792 Aug 31 22:55 aria_log.00000001
-rw-rw---- 1 polkitd input        52 Aug 31 22:55 aria_log_control
drwx------ 2 polkitd input      4096 Aug 31 23:11 db1
-rw-rw---- 1 polkitd input     12288 Aug 31 23:11 ddl_recovery.log
-rw-rw---- 1 polkitd input       946 Aug 31 22:55 ib_buffer_pool
-rw-rw---- 1 polkitd input  12582912 Aug 31 22:55 ibdata1
-rw-rw---- 1 polkitd input 100663296 Aug 31 23:12 ib_logfile0
-rw-rw---- 1 polkitd input  12582912 Aug 31 22:55 ibtmp1
-rw-rw---- 1 polkitd input         0 Aug 31 22:55 multi-master.info
drwx------ 2 polkitd input      4096 Aug 31 22:55 mysql
drwx------ 2 polkitd input      4096 Aug 31 22:55 performance_schema
drwx------ 2 polkitd input     12288 Aug 31 22:55 sys

现在我们把这个pod删掉,然后再次创建,发现数据还在

[root@master ~]# kubectl delete -f mysql.yaml 
statefulset.apps "mariadb" deleted
[root@master ~]# kubectl get pod
NAME                             READY   STATUS    RESTARTS   AGE
go-microserver-74c85dd7f-8kzlh   1/1     Running   0          30h
go-microserver-74c85dd7f-jlt4h   1/1     Running   0          30h
go-microserver-74c85dd7f-m6wzl   1/1     Running   0          30h
go-microserver-74c85dd7f-zntnr   1/1     Running   0          30h
[root@master ~]# kubectl apply -f mysql.yaml 
statefulset.apps/mariadb created
[root@master ~]# kubectl get pod
NAME                             READY   STATUS    RESTARTS   AGE
go-microserver-74c85dd7f-8kzlh   1/1     Running   0          30h
go-microserver-74c85dd7f-jlt4h   1/1     Running   0          30h
go-microserver-74c85dd7f-m6wzl   1/1     Running   0          30h
go-microserver-74c85dd7f-zntnr   1/1     Running   0          30h
mariadb-0                        1/1     Running   0          3s


[root@master ~]# kubectl exec -it mariadb-0 -- /bin/bash
root@mariadb-0:/# mysql -uroot -p123456
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.6.5-MariaDB-1:10.6.5+maria~focal mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| db1                |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.000 sec)

MariaDB [(none)]> use db1;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MariaDB [db1]> show tables;
+---------------+
| Tables_in_db1 |
+---------------+
| tb1           |
+---------------+
1 row in set (0.000 sec)

MariaDB [db1]> select * from tb1;
+----------+------+
| name     | age  |
+----------+------+
| zhangsan |   14 |
| lisi     |   15 |
+----------+------+
2 rows in set (0.000 sec)

pvc

Persistent Volume,也就是持久化存储
image.png
以一个本地pvc为例子,注意,这里需要提前创建挂载点目录!

apiVersion: apps/v1
kind: StatefulSet
metadata: 
  name: mariadb
spec: 
  serviceName: mariadb
  replicas: 1
  selector: 
    matchLabels: 
      app: mariadb
  template: 
    metadata: 
      labels: 
        app: mariadb
    spec: 
      containers: 
      - name: mariadb
        image: mariadb:latest
        imagePullPolicy: IfNotPresent #只有当本地不存在镜像才从远处拉取
        env: 
        - name: MARIADB_ROOT_PASSWORD
          value: "123456"
        volumeMounts: 
          - mountPath: /var/lib/mysql/ #容器里面的挂载路径
            name: mysqldata #卷的名字,必须和下面一样 
      volumes: 
      - name: mysqldata
        persistentVolumeClaim:
         claimName: mariadbdata
---
apiVersion: storage.k8s.io/v1 # SC,划分存储盘的类型
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume ## 卷的具体描述信息
metadata:
  name: mariadbdata
spec:
  capacity:
    storage: 2Gi
  volumeMode: Filesystem  # Filesystem(文件系统) Block(块)
  accessModes:
    - ReadWriteOnce       # 卷可以被一个节点以读写方式挂载
  persistentVolumeReclaimPolicy: Delete #pv回收策略,怕误删可以选择Retain
  storageClassName: local-storage
  local:
    path: /data/sql
  nodeAffinity:
    required:
      # 通过 hostname 限定在某个节点创建存储卷
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - node2
---
apiVersion: v1
kind: PersistentVolumeClaim #对存储需求的申请,系统根据这个申请单去寻找合适的pv
metadata:
  name: mariadbdata
spec:
  accessModes: ["ReadWriteOnce"]
  storageClassName: "local-storage"
  resources:
    requests:
      storage: 2Gi

apply看一下,成了

[root@master ~]# kubectl get pods -o wide
NAME                             READY   STATUS    RESTARTS   AGE   IP            NODE    NOMINATED NODE   READINESS GATES
go-microserver-74c85dd7f-8kzlh   1/1     Running   0          31h   10.244.0.21   node2   <none>           <none>
go-microserver-74c85dd7f-jlt4h   1/1     Running   0          31h   10.244.0.20   node1   <none>           <none>
go-microserver-74c85dd7f-m6wzl   1/1     Running   0          31h   10.244.0.20   node2   <none>           <none>
go-microserver-74c85dd7f-zntnr   1/1     Running   0          31h   10.244.0.21   node1   <none>           <none>
mariadb-0                        1/1     Running   0          12s   10.244.0.25   node2   <none>           <none>
[root@master ~]# kubectl get sc
NAME            PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-storage   kubernetes.io/no-provisioner   Delete          WaitForFirstConsumer   false                  20s
[root@master ~]# kubectl get pv
NAME          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                 STORAGECLASS    REASON   AGE
mariadbdata   2Gi        RWO            Delete           Bound    default/mariadbdata   local-storage            23s
[root@master ~]# kubectl get pvc
NAME          STATUS   VOLUME        CAPACITY   ACCESS MODES   STORAGECLASS    AGE
mariadbdata   Bound    mariadbdata   2Gi        RWO            local-storage   25s




[root@node2 ~]# ll /data/sql
total 123328
-rw-rw---- 1 polkitd input    417792 Sep  1 00:17 aria_log.00000001
-rw-rw---- 1 polkitd input        52 Sep  1 00:17 aria_log_control
-rw-rw---- 1 polkitd input         9 Sep  1 00:17 ddl_recovery.log
-rw-rw---- 1 polkitd input       946 Sep  1 00:17 ib_buffer_pool
-rw-rw---- 1 polkitd input  12582912 Sep  1 00:17 ibdata1
-rw-rw---- 1 polkitd input 100663296 Sep  1 00:17 ib_logfile0
-rw-rw---- 1 polkitd input  12582912 Sep  1 00:17 ibtmp1
-rw-rw---- 1 polkitd input         0 Sep  1 00:17 multi-master.info
drwx------ 2 polkitd input      4096 Sep  1 00:17 mysql
drwx------ 2 polkitd input      4096 Sep  1 00:17 performance_schema
drwx------ 2 polkitd input     12288 Sep  1 00:17 sys

configuremap

保存配置的键值对,实现配置与应用分离

我们把yaml里面的env部分删除,把密码写进cm.yaml里面,部署看看

apiVersion: v1
kind: ConfigMap
metadata:
  name: mariadb-config
data:
  MARIADB_ROOT_PASSWORD: "123456"
apiVersion: apps/v1
kind: StatefulSet
metadata: 
  name: mariadb
spec: 
  serviceName: mariadb
  replicas: 1
  selector: 
    matchLabels: 
      app: mariadb
  template: 
    metadata: 
      labels: 
        app: mariadb
    spec: 
      containers: 
      - name: mariadb
        image: mariadb:latest
        imagePullPolicy: IfNotPresent #只有当本地不存在镜像才从远处拉取
        env: 
        - name: MARIADB_ROOT_PASSWORD
          valueFrom:
            configMapKeyRef: 
              name: mariadb-config
              key: MARIADB_ROOT_PASSWORD
        volumeMounts: 
          - mountPath: /var/lib/mysql/ #容器里面的挂载路径
            name: mysqldata #卷的名字,必须和下面一样 
      volumes: 
      - name: mysqldata
        persistentVolumeClaim:
         claimName: mariadbdata


可以看到容器成功起来了,如果这里没有指定密码的话,mariadb是起不来的

[root@master ~]# kubectl  apply -f  cm.yaml 
configmap/mariadb-config created
[root@master ~]# kubectl apply -f mysql.yaml 
statefulset.apps/mariadb created
storageclass.storage.k8s.io/local-storage created
persistentvolume/mariadbdata created
persistentvolumeclaim/mariadbdata created
[root@master ~]# kubectl get pods
NAME                             READY   STATUS    RESTARTS   AGE
go-microserver-74c85dd7f-8kzlh   1/1     Running   0          40h
go-microserver-74c85dd7f-jlt4h   1/1     Running   0          40h
go-microserver-74c85dd7f-m6wzl   1/1     Running   0          40h
go-microserver-74c85dd7f-zntnr   1/1     Running   0          40h
mariadb-0                        1/1     Running   0          5s
[root@master ~]# kubectl get cm
NAME               DATA   AGE
kube-root-ca.crt   1      2d10h
mariadb-config     1      3m14s
[root@master ~]# kubectl get cm mariadb-config -o yaml 
apiVersion: v1
data:
  MARIADB_ROOT_PASSWORD: "123456"
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"MARIADB_ROOT_PASSWORD":"123456"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"mariadb-config","namespace":"default"}}
  creationTimestamp: "2022-09-01T01:47:05Z"
  name: mariadb-config
  namespace: default
  resourceVersion: "305952"
  uid: 16c011c0-7fce-4c3b-81de-1d131994eed3

secret

一些重要的数据,如密码之类的,可以用base64编码之后放在secret里面

apiVersion: v1
kind: Secret
metadata:
  name: mariadb-secret
# Opaque 用户定义的任意数据,更多类型介绍 https://kubernetes.io/zh/docs/concepts/configuration/secret/#secret-types
type: Opaque
data:
  MARIADB_ROOT_PASSWORD: MTIzNDU2

apiVersion: apps/v1
kind: StatefulSet
metadata: 
  name: mariadb
spec: 
  serviceName: mariadb
  replicas: 1
  selector: 
    matchLabels: 
      app: mariadb
  template: 
    metadata: 
      labels: 
        app: mariadb
    spec: 
      containers: 
      - name: mariadb
        image: mariadb:latest
        imagePullPolicy: IfNotPresent #只有当本地不存在镜像才从远处拉取
        env: 
        - name: MARIADB_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mariadb-secret
              key: MARIADB_ROOT_PASSWORD
        volumeMounts: 
          - mountPath: /var/lib/mysql/ #容器里面的挂载路径
            name: mysqldata #卷的名字,必须和下面一样 
      volumes: 
      - name: mysqldata
        persistentVolumeClaim:
         claimName: mariadbdata

          # Secret 的所有数据定义为容器的环境变量,Secret 中的键名称为 Pod 中的环境变量名称
          # envFrom:
          # - secretRef:
          #     name: secretname

把configuremap下了换secret,statefulset配置也改一下,pod正常启动了

[root@master ~]# kubectl apply -f secret.yaml 
secret/mariadb-secret created
[root@master ~]# kubectl apply -f mysql.yaml 
statefulset.apps/mariadb created
storageclass.storage.k8s.io/local-storage created
persistentvolume/mariadbdata created
persistentvolumeclaim/mariadbdata created
[root@master ~]# kubectl get pods
NAME                             READY   STATUS    RESTARTS   AGE
go-microserver-74c85dd7f-8kzlh   1/1     Running   0          40h
go-microserver-74c85dd7f-jlt4h   1/1     Running   0          40h
go-microserver-74c85dd7f-m6wzl   1/1     Running   0          40h
go-microserver-74c85dd7f-zntnr   1/1     Running   0          40h
mariadb-0                        1/1     Running   0          7s

挂载为文件,这里就复制个例子
挂载后,会在容器中对应路径生成文件,一个 key 一个文件,内容就是 value,文档

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret

namespace

类似于编程里面的namespace概念,我们可以把应用划分进不同的ns空间来进行分隔和管理

# 创建命名空间
kubectl create namespace testapp
# 部署应用到指定的命名空间
kubectl apply -f app.yml --namespace testapp
# 查询
kubectl get pod --namespace kube-system

示例

[root@master ~]# kubectl create namespace testapp
namespace/testapp created
[root@master ~]# kubectl apply -f goservice.yaml -n testapp
deployment.apps/go-microserver created
[root@master ~]# kubectl -n testapp get pods
NAME                             READY   STATUS    RESTARTS   AGE
go-microserver-74c85dd7f-46zwk   1/1     Running   0          21s
go-microserver-74c85dd7f-5qlxp   1/1     Running   0          21s
go-microserver-74c85dd7f-6dmqp   1/1     Running   0          21s
go-microserver-74c85dd7f-dhh9g   1/1     Running   0          21s
[root@master ~]# kubectl get ns
NAME              STATUS   AGE
default           Active   2d10h
kube-flannel      Active   47h
kube-node-lease   Active   2d10h
kube-public       Active   2d10h
kube-system       Active   2d10h
testapp           Active   53s