0%

安装步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[root@exxk ~]# helm repo add harbor https://helm.goharbor.io
#下载harbor相关配置到当前目录
[root@exxk ~]# helm fetch harbor/harbor --untar
[root@exxk ~]# cd harbor/
[root@exxk harbor]# ls
Chart.yaml LICENSE README.md templates values.yaml
#前提,需要有挂载卷
1. 创建nfs,参考https://www.iexxk.com/2022/07/26/k8s-nfs/?highlight=nfs#%E5%88%9B%E5%BB%BAnfs%E6%9C%8D%E5%8A%A1
2. 创建 StorageClass
....
kind: StorageClass
metadata:
annotations:
k8s.kuboard.cn/storageType: nfs_client_provisioner
name: nfs-172.16.30.165
reclaimPolicy: Delete
volumeBindingMode: Immediate
##创建空间
[root@exxk harbor]# kubectl create namespace harbor
##修改values.yaml
# expose:tls:enable 设置为false #关闭证书
# ingress:hosts:core: 设置为harbor.iexxk.io #根据自己的域名后缀来
# externalURL: 设置为自己实际的访问地址harbor.iexxk.io
# ingress:className: 设置为myingress #根据自己的ingressClassName来
# storageClass设置为刚刚创建的nfs-172.16.30.165
[root@exxk harbor]# helm install my-harbor . -n harbor
NAME: my-harbor
LAST DEPLOYED: Wed Sep 27 14:47:16 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Please wait for several minutes for Harbor deployment to complete.
Then you should be able to visit the Harbor portal at https://core.harbor.domain
For more details, please visit https://github.com/goharbor/harbor
#卸载
[root@exxk ~]# helm uninstall my-harbor -n harbor

通过harbor.iexxk.io进行访问,注意进行域名映射,默认登录用户名密码admin/Harbor12345

问题

  1. 登录输入正确密码也提示密码错误

    1
    2
    3
    4
    5
    6
    7
    8
    Request URL:
    http://harbor.iexxk.io/c/login
    Request Method:
    POST
    Status Code:
    403 Forbidden
    接口返回
    {"errors":[{"code":"FORBIDDEN","message":"CSRF token invalid"}]}

    解决:修改externalURL: https://core.harbor.domain为自己实际的访问地址`harbor.iexxk.io`

记一次生产证书过期问题

环境信息:

Kubernetes 版本v1.18.6
KubeSphere 版本v3.1.0
一个host集群,下面三个menber集群(其中一个member集群过期失联)
member集群有三个master节点

执行kubectl get node提示如下错误:

1
kubesphere Unable to connect to the server: x509: certificate has expired or is not yet valid

且host集群查看member集群,发下改集群未就绪,且无法访问,直接通过ip可以进行访问。

更新证书

在所有master集群执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#确认是否过期
kubeadm alpha certs check-expiration
# 备份
mkdir pki230411
cp -rfa /etc/kubernetes/ pki230411/
ls pki230411/kubernetes/pki
#更新证书
kubeadm alpha certs renew all
#检查是否更新成功
kubeadm alpha certs check-expiration
#重启kube-apiserver、kube-controller-manager、kube-scheduler、etcd
sudo docker ps |grep -E 'k8s_kube-apiserver|k8s_kube-controller-manager|k8s_kube-scheduler|k8s_etcd_etcd'|sudo xargs docker restart
#检查重启状态
docker ps |grep -E 'k8s_kube-apiserver|k8s_kube-controller-manager|k8s_kube-scheduler|k8s_etcd_etcd'
#因为etcd时安装的单独节点,所有需要在所有etcd节点进行重启
systemctl status etcd #可以查看到是否需要执行systemctl daemon-reload
systemctl daemon-reload #有一台节点需要执行该命令才能重启(似乎只需要找到需要执行该命令的节点,执行该命令,不需要重启也可以)
systemctl restart etcd
systemctl status etcd
#备份kubectl连接证书,不然kubectl命令还是用的老的证书
cp .kube/config ~/pki230411/
#更新连接证书为新生成的证书
cp /etc/kubernetes/admin.conf ~/.kube/config
#当集群所有master更新结束执行该命令就可以成功返回节点信息了
kubectl get node

重新添加host集群的member集群

  1. 在kubeshpere查看原集群信息,备份

  2. 删除未就绪的member集群

  3. 重新添加member集群

引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>

消费者模式

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#kafka消费服务地址
spring.kafka.consumer.bootstrap-servers=ip1:port,ip2:port
#是否自动提交
spring.kafka.consumer.enable-auto-commit=true
#提交间隔时间
spring.kafka.consumer.auto-commit-interval=100ms
#key反序列化
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
#value反序列化
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
#心跳超时时间
spring.kafka.consumer.properties.session.timeout.ms=15000
#消费者groupID
spring.kafka.consumer.group-id=test-group-id

代码

1

安装

  1. Dashbord>Manage Jenkins>Plugins页面点击Available plugins搜索NodeJS点击安装并重启。
  2. Dashbord>Manage Jenkins>Tools页面最底部的NodeJS一栏点击Add NodeJS,设置名字nodejs,然后选择版本。

配置

  1. Dockerfile

    1
    2
    3
    4
    5
    FROM nginx:alpine
    RUN rm /etc/nginx/conf.d/default.conf
    ADD ./nginx.conf /etc/nginx/conf.d/nginx.conf
    COPY ./dist /usr/share/nginx/html/
    CMD ["nginx","-g","daemon off;"]
  2. nginx的配置nginx.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    server {
    listen 80;
    server_name localhost;

    location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root html;
    }

    }
  3. Jenkinsfile

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    #!/usr/bin/env groovy Jenkinsfile

    pipeline {
    agent any
    parameters {
    choice(name: 'project_choice',
    choices: 'demo-pc',
    description: '你要编译构建那个项目?')
    }
    stages {
    stage('npm build') {
    tools {
    nodejs "nodejs" //这里的node要和nodejs工具的配置里的name要一致
    }
    steps {
    dir("${params.project_choice}") { //切换目录
    echo "开始构建${params.project_choice}项目"
    sh "npm install --registry=https://registry.npmmirror.com"
    sh "npm run build:stage" //编译打包
    }
    }
    }
    stage('docker build'){
    steps{
    dir("${params.project_choice}") { //切换目录
    sh "docker build --no-cache -t registry.cn-qingdao.aliyuncs.com/xxx/demo-pc:${env.BUILD_NUMBER} ."
    echo "构建镜像" + "ag/${params.project_choice}".toLowerCase() + "完成"
    }
    }
    }
    stage('docker push'){
    steps{
    sh "docker login --username=zhangsan registry.cn-qingdao.aliyuncs.com -p mima"
    sh "docker push registry.cn-qingdao.aliyuncs.com/xxx/demo-pc:${env.BUILD_NUMBER}"
    echo "推送镜像" + "ag/${params.project_choice}".toLowerCase() + "完成"
    }
    }
    stage('docker run'){
    steps{
    sh "docker stop demo-pc||true"
    sh "docker rm demo-pc||true"
    sh "docker run -d -p 8911:80 --name demo-pc registry.cn-qingdao.aliyuncs.com/xxx/demo-pc:${env.BUILD_NUMBER}"
    echo "运行镜像" + "ag/${params.project_choice}".toLowerCase() + "完成"
    }
    }
    }
    }

运行

  1. 创建流水线Dashboard>New Item>Pipeline输入流水线名字,点击ok。
  2. Pipeline一栏的Definition选择Pipeline script from SCM
  3. SCM一栏选择Git,在Repository URL中输入git项目地址。
  4. 设置项目中Jenkinsfile文件的路径,点击保存。
  5. 点击build运行流水线即可。

java项目同理

  1. Dockerfile

    1
    2
    3
    4
    FROM exxk/java:8-alpine-cst
    ADD ./target/demo-admin.jar /app.jar
    EXPOSE 8080
    ENTRYPOINT ["java","-jar","/app.jar"]
  2. Jenkinsfile

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    #!/usr/bin/env groovy Jenkinsfile

    pipeline {
    agent any
    parameters {
    choice(name: 'project_choice',
    choices: 'demo-admin' +
    '\ndemo-framework' +
    '\ndemo-system' +
    '\ndemo-common',
    description: '你要编译构建那个项目?')
    }
    stages {
    stage('maven build') {
    tools {
    maven "maven" //这里的maven要和maven工具的配置里的name要一致
    }
    steps {
    echo "开始构建${params.project_choice}项目"
    sh "mvn package -pl ${params.project_choice} -am" //构建
    }
    }
    stage('docker build'){
    steps{
    dir("${params.project_choice}") { //切换目录
    sh "docker build --no-cache -t registry.cn-qingdao.aliyuncs.com/xxx/demo-admin:${env.BUILD_NUMBER} ."
    echo "构建镜像" + "ag/${params.project_choice}".toLowerCase() + "完成"
    }
    }
    }
    stage('docker push'){
    steps{
    sh "docker login --username=zhangsan registry.cn-qingdao.aliyuncs.com -p mima"
    sh "docker push registry.cn-qingdao.aliyuncs.com/xxx/demo-admin:${env.BUILD_NUMBER}"
    echo "推送镜像" + "ag/${params.project_choice}".toLowerCase() + "完成"
    }
    }
    stage('docker run'){
    steps{
    sh "docker stop demo-admin||true"
    sh "docker rm demo-admin||true"
    sh "docker run -d -p 8910:8080 --name demo-admin registry.cn-qingdao.aliyuncs.com/xxx/demo-admin:${env.BUILD_NUMBER}"
    echo "运行镜像" + "ag/${params.project_choice}".toLowerCase() + "完成"
    }
    }
    }
    }

TDengine官方文档/github源码

Helm安装

1
2
3
4
5
6
#下载TDengine chart helm安装包
wget https://github.com/taosdata/TDengine-Operator/raw/3.0/helm/tdengine-3.0.0.tgz
#上面因国内网络原因无法下载,可以试试下面的
wget --no-check-certificate https://raw.githubusercontents.com/taosdata/TDengine-Operator/3.0/helm/tdengine-3.0.0.tgz
#安装
helm install tdengine tdengine-3.0.0.tgz

常用sql

1
2
3
4
5
6
taos #进入数据库命令行
#显示所有数据库
taos> SHOW DATABASES;
#创建test数据库,有10 vgroups,有10 MB cache。
taos> create database if not exists test vgroups 10 buffer 10;

gui工具

官方/TDengineGUI

dbeaver 连接 tdengine(因第四步dbeaver的bug未显示驱动类,因此采用idea接入)

  1. maven仓库搜索com.taosdata.jdbc
  2. 在versions界面下载找到相应版本,下载taos-jdbcdriver-3.0.0-dist.jar
  3. 在dbeaver菜单数据库->驱动管理器->新建->库->添加文件,选择刚刚下载的文件
  4. 点击找到类,然后驱动类选择对应的com.taosdata.jdbc.rs.RestfulDriver

idea 连接 tdengine

  1. maven仓库搜索com.taosdata.jdbc
  2. 在versions界面下载找到相应版本,下载taos-jdbcdriver-3.0.0-dist.jar
  3. 在idea右侧database菜单➕->Driver->Driver Files->➕->custom jars,选择刚刚下载的文件
  4. 在Class一栏选择对应的com.taosdata.jdbc.rs.RestfulDriver
  5. 在idea右侧database菜单➕->Data Source->刚刚新建的驱动->,在URL一栏输入jdbc:TAOS-RS://<数据库ip>:6041/<数据库名称>?user=root&password=taosdata,(记得先去创建数据库)
  6. 点击保存连接即可

接入springboot

引入依赖

1
2
3
4
5
<dependency>
<groupId>com.taosdata.jdbc</groupId>
<artifactId>taos-jdbcdriver</artifactId>
<version>3.0.0</version>
</dependency>
原生连接
rest连接(损失30%左右的性能)

InfluxDB官方文档/githab源码

Helm安装

1
helm upgrade --install my-influxdb influxdata/influxdb --set image.tag=2.5.1-alpine

访问:开放nodeport,访问ip:nodeport地址

基础概念

measurement:表

point::一行数据记录

time:时间戳,自带字段,单位纳秒

tags:有各种索引的属性,一般用于

fields:没有索引的属性,一般会实时变化,存经纬度、温度、等变化的数据

annotated CSV数据

csv数据格式
group false false true true false false true true true 组?
datatype string long dateTime:RFC3339 dateTime:RFC3339 dateTime:RFC3339 double string string string 数据类型
default mean 默认值?
result table _start _stop _time _value _field _measurement car 数据表头
0 2022-11-22T07:20:32.833674853Z 2022-11-22T08:20:32.833674853Z 2022-11-22T08:00:30Z 39.90786 lat gps 川A888888 point一行数据
注释 表? 查询的开始时间 查询的结束时间 数据的时间 field的值 field的key tag
csv源数据
1
2
3
4
5
6
7
8
#group,false,false,true,true,false,false,true,true,true
#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string
#default,mean,,,,,,,,
,result,table,_start,_stop,_time,_value,_field,_measurement,car
,,0,2022-11-22T07:20:32.833674853Z,2022-11-22T08:20:32.833674853Z,2022-11-22T08:00:30Z,39.90786,lat,gps,川A888888
,,0,2022-11-22T07:20:32.833674853Z,2022-11-22T08:20:32.833674853Z,2022-11-22T08:12:40Z,39.91786,lat,gps,川A888888
,,1,2022-11-22T07:20:32.833674853Z,2022-11-22T08:20:32.833674853Z,2022-11-22T08:00:30Z,116.510958,lon,gps,川A888888
,,1,2022-11-22T07:20:32.833674853Z,2022-11-22T08:20:32.833674853Z,2022-11-22T08:12:40Z,116.510928,lon,gps,川A888888

line protocol

1
2
3
4
5
6
measurementName,tagKey=tagValue fieldKey="fieldValue" 1465839830100400200
---------------,--------------- --------------------- -------------------
Measurement tags set fields set timestamp
eg:
gps,car=川A888888 lat=39.90786,lon=116.510958 1669104020000000000
gps,car=川A888888 lat=39.91786,lon=116.510928 1669104759000000000

接入springboot

添加依赖

1
2
3
4
5
<dependency>
<groupId>com.influxdb</groupId>
<artifactId>influxdb-client-java</artifactId>
<version>6.7.0</version>
</dependency>

连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class InfluxdbConfig {

@Value("${spring.influx.url:''}")
private String influxDbUrl;
@Value("${spring.influx.token:''}")
private String influxDbToken;
@Value("${spring.influx.org:''}")
private String influxDbOrg;
@Value("${spring.influx.buket:''}")
private String influxDbBuket;


@Bean
InfluxDBClient influxDBClient(){
InfluxDBClient influxDBClient= InfluxDBClientFactory.create(influxDbUrl,influxDbToken.toCharArray(),influxDbOrg,influxDbBuket);
influxDBClient.setLogLevel(LogLevel.BASIC);
return influxDBClient;
}

}

读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Repository
public class GpsSeriesDao {
private static final Logger LOG = Logger.getLogger(GpsSeriesDao.class.getName());
/**
* 表(_measurement)
*/
private static final String TABLE = "gps";
@Autowired
InfluxDBClient influxDBClient;
@Autowired
InfluxdbConfig influxdbConfig;

/**
* 读数据
* @param carNum
* @param startTime
* @param endTime
* @return
*/
public List<GpsRecord> findTrackByCarNum(String carNum, String startTime, String endTime) {
String flux = "from(bucket: \"" + influxdbConfig.influxDbBuket + "\")\n" +
" |> range(start: " + startTime + ",stop:" + endTime + ")\n" +
" |> filter(fn: (r) => r[\"_measurement\"] == \"" + TABLE + "\")\n" +
" |> filter(fn: (r) => r[\"carNum\"] == \"" + carNum + "\")\n" +
" |> pivot(rowKey:[\"_time\"],columnKey: [\"_field\"],valueColumn: \"_value\") "+
" |> yield(name: \"mean\")";
LOG.info("query flux:\n" + flux);
QueryApi queryApi = influxDBClient.getQueryApi();
List<GpsRecord> gpsRecords = queryApi.query(flux, GpsRecord.class);
return gpsRecords;
}

/**
* 写数据
* @param gpsRecord
*/
public void writeGpsRecord(GpsRecord gpsRecord) {
String timestamp;
if (gpsRecord.getTime() != null) {
timestamp = gpsRecord.getTime().toEpochMilli() + "000000"; //13位转为19位时间戳,数据库才能识别
} else {
throw new BusinessException(ApiCode.PARAM_FORMAT_INCORR.getValue(), "gps time参数格式错误");
}
String data = TABLE + ",carNum=" + gpsRecord.getCarNum() + " lat=" + gpsRecord.getLat() + ",lon=" + gpsRecord.getLon() + " " + timestamp;
LOG.info("Line Protocol:\n" + data);
WriteApiBlocking writeApi = influxDBClient.getWriteApiBlocking();
writeApi.writeRecord(WritePrecision.NS, data);
}
}

常见influxdb flux语句

官方文档

行列转换语句-pivot

在influxdb当有多个field时,每个field查询出来会单独成一行数据,这是就需要添加

1
|> pivot(rowKey:["_time"],columnKey: ["_field"],valueColumn: "_value")

添加之后,就会把多个field换成一行。

flux in查询

1
|> filter(fn: (r) =>contains(value: r["carNum"], set: ["01","02","03"]))  

对应java代码

1
2
3
4
5
6
7
8
9
10
11
String carNumsStr=carNums.stream().map(s->"\""+s+"\"").collect(Collectors.joining(","));
String flux = "from(bucket: \"" + influxdbConfig.influxDbBuket + "\")\n" +
" |> range(start:-15d)\n" +
" |> filter(fn: (r) => r[\"_measurement\"] == \"" + TABLE + "\")\n" +
" |> filter(fn: (r) => contains(value: r[\"carNum\"], set: ["+carNumsStr+"]))\n" +
" |> last()\n" +
" |> pivot(rowKey:[\"_time\"],columnKey: [\"_field\"],valueColumn: \"_value\") "+
" |> yield(name: \"mean\")";
LOG.info("query flux:\n" + flux);
QueryApi queryApi = influxDBClient.getQueryApi();
List<GpsRecord> gpsRecords = queryApi.query(flux, GpsRecord.class);

last查询最新数据

1
|> last()  

flux官方文档阅读笔记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|> group(columns: ["host"], mode: "by") #分组
|> sort(columns: ["host", "_value"]) #排序
|> aggregateWindow(every: 20m, fn: mean) #每20分钟取一次平均值
|> map(fn: (r) => ({ r with _value: r._value * r._value })) #转换数据,求平方
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") #行转列
|> increase() #每分钟累加的值
|> movingAverage(n: 5) #当前时间点的前三个数据(从自己开始计数)的平均数
|> timedMovingAverage(every: 2m, period: 4m) #每2分钟统计4分钟前的平均数
|> derivative(unit: 1m, nonNegative: true) #
|> histogram(
column: "_value",
upperBoundColumn: "le",
countColumn: "_value",
bins: [100.0, 200.0, 300.0, 400.0],
) #分别统计100,200,300,400以内的数据的个数
|> fill(usePrevious: true) #替换空值,用上一个数据进行替换,第一个数据可能为null,因为没有上一个数据
|> fill(value: 0.0) #null空值用0.0替换
|> median() #获取中间值
|> quantile(q: 0.99, method: "estimate_tdigest")
|> cumulativeSum() #历史数据求和
|> first() #最早的数据
|> last() #最晚的数据
|> filter(fn: (r) => exists r._value) #过滤null的数据
|> count()
|> count(column: "lat")


常见实用案例查询

1
2
3
4
5
6
7
8
9
# 先把数据值转化为1,然后统计数据的条数,可以按分钟,天、月等力度统计每个区间段的数据
from(bucket: "transport")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "gps")
|> filter(fn: (r) => r["_field"] == "lat")
|> filter(fn: (r) => r["carNum"] == "719135")
|> map(fn: (r) => ({ r with _value: 1 }))
|> aggregateWindow(every: 1m, fn: sum, createEmpty: false)
|> yield(name: "mean")

常见集成架构

graph LR

A[mqtt/物联网设备] -->B(emq/mqtt服务端)
B --> c[telegraf/数据中转处理]
c --> d[infludb/入库]
d --> e[grafna/展示]

常见问题

  1. 时区问题,在界面上默认是0时区去查询,时间数据导入2022-11-22T08:51:00Z z代表0时区。

    解决:修改容器的时区,添加只读挂在卷

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    spec:
    volumes:
    - name: localtime
    hostPath:
    path: /etc/localtime
    type: ''
    containers:
    volumeMounts:
    - name: localtime
    readOnly: true
    mountPath: /etc/localtime

    添加之后,导入数据也要注意,切换为中国(+8)时区,也就是在时间RFC3339上,就行加8,也就是2022-11-22T08:51:00+08:00进行导入,但是查询时,界面上还是以0时区显示,不方便观察,可以在data explorer->customize->time format选择YYYY-MM-DD hh:mm:ss a ZZ

参考

Go - time.RFC3339 时间格式化

mqtt+emq+influxdb+grafana系统搭建傻瓜教程

对比

时序数据库 排名 Star 安装 优势
InfluxDB 1 24.4k helm 除了不符合信创没有任何可挑剔的,集群版收费(需要许可证),单机版适用于小数据集
TimescaleDb 5 14k helm 基于PostgreSQL,当时序数据库也可以当关系型数据库
OpenTSDB 7 4.8k 基于HBase/部署安装复杂
TDengine 13 19.6k helm 国产性能强悍专为物联网、车联网、工业互联网、IT运维等设计和优化的大数据平台
DophinDB 9 闭源 国产/金融场景用的多

参考

时序数据库选型

apm全链路追踪

产品对比

pinpoint

Pinpoint是用 Java / PHP编写的用于大型分布式系统的 APM(应用程序性能管理)工具。受Dapper的启发,Pinpoint 提供了一种解决方案,通过跟踪分布式应用程序中的事务来帮助分析系统的整体结构以及其中的组件如何互连。

Skywalking

分布式系统的应用程序性能监控工具,专为微服务、云原生和基于容器的 (Kubernetes) 架构而设计。

CAT

  • CAT 是基于 Java 开发的实时应用监控平台,为美团点评提供了全面的实时监控告警服务。
  • CAT 作为服务端项目基础组件,提供了 Java, C/C++, Node.js, Python, Go 等多语言客户端,已经在美团点评的基础架构中间件框架(MVC框架,RPC框架,数据库框架,缓存框架等,消息队列,配置系统等)深度集成,为美团点评各业务线提供系统丰富的性能指标、健康状况、实时告警等。
  • CAT 很大的优势是它是一个实时系统,CAT 大部分系统是分钟级统计,但是从数据生成到服务端处理结束是秒级别,秒级定义是48分钟40秒,基本上看到48分钟38秒数据,整体报表的统计粒度是分钟级;第二个优势,监控数据是全量统计,客户端预计算;链路数据是采样计算。

Zipkin

bonree(收费)

基调听云

Skywalking

helm-k8s安装

1
2
3
4
5
6
7
8
9
10
11
12
13
#添加仓库
helm repo add skywalking https://apache.jfrog.io/artifactory/skywalking-helm
#创建skywalking空间
kubectl create namespace skywalking
#安装
helm install skywalking \
skywalking/skywalking \
--version 4.3.0 \
-n skywalking \
--set oap.image.tag=9.2.0 \
--set oap.storageType=elasticsearch \
--set ui.image.tag=9.2.0 \
--set elasticsearch.imageTag=6.8.6

springboot接入Skywalking

查看Skywalking oap服务地址

集群管理->应用负载->服务->skywalking-oap->dns+服务端口=skywalk oap 后端服务地址

idea agent接入

编辑vm参数,添加如下参数:

java -javaagent:~/skywalking/agent/skywalking-agent.jar -Dskywalking.agent.service_name=your-service-name -Dskywalking.collector.backend_service=127.0.0.1:11800

通过k8s sidecar无侵入接入

kind: Deployment配置文件添加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
initContainers: #新添加初始化容器,主要用于拷贝skywalking-agent.jar包,也可以在dockerfile里面的基础镜像直接加,就不用初始化容器
- name: sidecar
#过期不兼容hutool image: apache/skywalking-base:8.1.0-es7 # 容器镜像,包含静态资源文件
image: apache/skywalking-java-agent:8.13.0-java8
imagePullPolicy: IfNotPresent
command: ["cp", "-r", "/skywalking/agent", "/sidecar"]
volumeMounts:
- name: sidecar
mountPath: /sidecar
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}" #springboot 应用镜像
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: JAVA_OPTS
value: -javaagent:/sidecar/agent/skywalking-agent.jar
- name: SW_AGENT_NAME
value: {{ include "springboot-app.fullname" . }} #服务名
- name: SW_AGENT_COLLECTOR_BACKEND_SERVICES
value: "{{.Values.skyWalking.oap}}" # skywalk oap 后端服务
ports:
- containerPort: {{ .Values.containerPort }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts: #新加
- name: sidecar
mountPath: /sidecar
volumes: #新加
- name: sidecar #共享agent文件夹
emptyDir: {}

常见问题

  1. 使用hutool工具的HttpUtil.get方法发现不能追踪链路?

    原理:skywalking版本9.2.0,升级agent镜像,升级之后能追踪。

    解决:将apache/skywalking-base:8.1.0-es7替换成 apache/skywalking-java-agent:8.13.0-java8

参考

KubeSphere 部署 SkyWalking 至 Kubernetes 开启无侵入 APM

使用/不使用 Istio 将 Envoy 指标发送到 SkyWalking

概念

KubeSphere 服务网格基于开源项目 Istio 构建,可以控制应用程序不同部分之间的通信方式。其中,灰度发布策略为用户在不影响微服务之间通信的情况下测试和发布新的应用版本发挥了重要作用。

服务网格

服务网格基于 Istio,将微服务治理和流量管理可视化。它拥有强大的工具包,包括熔断机制、蓝绿部署、金丝雀发布、流量镜像、链路追踪、可观测性和流量控制等。

istio

架构

Istio 服务网格从逻辑上分为数据平面控制平面

  • 数据平面 由一组智能代理(Envoy)组成,被部署为 Sidecar。这些代理负责协调和控制微服务之间的所有网络通信。它们还收集和报告所有网格流量的遥测数据。

    组件一: Envoy 特性动态服务发现、负载均衡、TLS 终端、HTTP/2 与 gRPC 代理、熔断器、健康检查、基于百分比流量分割的分阶段发布、故障注入、丰富的指标。

  • 控制平面 管理并配置代理来进行流量路由。

    组件二:Istiod 提供服务发现、配置和证书管理。

部署模型
  • 集群模型:单一/多集群
  • 网络模型:单一/多网络
  • 控制平面模型
  • 身份和信任模型:网格内/网格之间信任
  • 网格模型:单一/多网格
  • 租户模型:命名空间租赁/集群租户模型
Ingress

控制 Istio 服务网格的入口流量。

Egress

控制 Istio 服务网格的出口流量。

灰度发布策略

当您在 KubeSphere 中升级应用至新版本时,灰度发布可以确保平稳过渡。采用的具体策略可能不同,但最终目标相同,即提前识别潜在问题,避免影响在生产环境中运行的应用。这样不仅可以将版本升级的风险降到最低,还能测试应用新构建版本的性能。

蓝绿部署

蓝绿发布提供零宕机部署,即在保留旧版本的同时部署新版本。在任何时候,只有其中一个版本处于活跃状态,接收所有流量,另一个版本保持空闲状态。如果运行出现问题,您可以快速回滚到旧版本。

  • 特点:任何时刻只有一个环境接收流量。

金丝雀发布

控制一部分流量到新的环境,待确认没问题,便可把所有流量转移到新环境。

  • 特点:流量逐步转移,风险降到最低。

流量镜像

复制实时产生的流量推送至新部署的镜像环境。

  • 特点:可以对比新旧环境流量处理的效率,测试集群,测试数据库。

实战

部署Bookinfo示例应用

  1. 启用服务组件Istio

  2. 创建一个项目

  3. 为新建的项目设置网关: 项目设置-->高级设置-->设置网关-->NodePort/LoadBalancer

  4. 应用负载-->应用模版-->如何使用应用治理--部署示例应用然后一路下一步即可

  5. 应用负载-->自制应用-->bookinfo-->点击访问进行访问应用

    访问地址http://productpage.exxk-lab.10.25.24.12.nip.io:31612/分析:

    productpage.exxk-lab.10.25.24.12.nip.io:31612是dns泛域名,nip.io是一个免费域名解析服务,会自动指向10.25.24.12不需要配置hosts映射文件。

给部署的服务添加网关,并设置统一的前缀

  1. 为新建的项目设置网关: 项目设置-->高级设置-->设置网关-->NodePort

  2. 添加路由:应用负载-->应用路由-->创建

    指定域名:server-name.10.255.242.18.nip.io

    指定路径(统一前缀):/前缀(/|$)(.*)

    指定路径(不加前缀):/

    添加注解(统一前缀):nginx.ingress.kubernetes.io/app-root=/前缀nginx.ingress.kubernetes.io/rewrite-target=/$2

    对应的配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    kind: Ingress
    apiVersion: extensions/v1beta1
    metadata:
    name: transport-server
    namespace: transport-server
    annotations:
    kubesphere.io/creator: '81003863'
    nginx.ingress.kubernetes.io/app-root: /transport
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    spec:
    rules:
    - host: transport-server.10.255.242.18.nip.io
    http:
    paths:
    - path: /transport(/|$)(.*)
    pathType: ImplementationSpecific
    backend:
    serviceName: transport-manage
    servicePort: 8088

参考

ingress-nginx 中 Rewrite 的使用

helm chart语法

github helm仓库

  1. 在githuab创建helm-charts仓库

  2. 克隆代码到本地git clone git@github.com:iexxk/helm-charts.git

  3. 进入cd helm-charts目录,创建目录执行mkdir charts

  4. 创建发布流程文件,执行mkdir -p .github/workflows

  5. 创建github操作流文件.github/workflows/release.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    name: Release Charts

    on:
    push:
    branches:
    - main

    jobs:
    release:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
    uses: actions/checkout@v2
    with:
    fetch-depth: 0

    - name: Configure Git
    run: |
    git config user.name "$GITHUB_ACTOR"
    git config user.email "$GITHUB_ACTOR@users.noreply.github.com"

    - name: Run chart-releaser
    uses: helm/chart-releaser-action@v1.1.0
    env:
    CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
  6. 进入github项目页面创建分支gh-pages

  7. 创建chart,进入charts目录,执行helm create nginx-file-browser创建nginx-file-browser项目

  8. 依次执行git add . git commit -m "init"git push

  9. 等待github编译部署成功,gh-pages分支内会多一个或更新yaml的文件

  10. 同步到artifacthub,在控制面板点击添加,输入名字:exxk,和url:https://iexxk.github.io/helm-charts

  11. 等待2+分钟,刷新,就能搜索到自己github仓库的chart了。

测试使用
1
2
3
4
5
6
7
helm repo add exxk https://iexxk.github.io/helm-charts #添加仓库
helm search repo exxk #查看仓库有哪些chart
helm repo update #更新仓库
#安装
helm install --set nfs.enabled=true --set nfs.server=127.0.0.1 my-nginx-file-browser exxk/nginx-file-browser
#卸载
helm delete my-nginx-file-browser

harbor helm仓库

  1. 编辑好chart模版文件

  2. 执行helm package -d <chart所在目录> <chart名称>,会生成一个压缩包,名字为<chart名称>-<chart文件内的version>.tgz
    eg:helm package -d appchart-uat-cd appchart-uat-cd在目录appchart-uat-cd会生成appchart-uat-cd-1.0.0.tgz

  3. 登陆harbor在项目-->项目名-->helm Charts-->上传,点击上传按钮在chart一栏点击选择,然后上传打包好的chart压缩包

    1
    2
    3
    4
    #也可以使用push命令上传
    docker login harbor.xxx.com.cn
    helm chart save CHART_PATH harbor.xxx.com.cn/cd-dev-1/REPOSITORY[:TAG]
    helm chart push harbor.xxx.com.cn/cd-dev-1/REPOSITORY[:TAG]
  4. 登陆harbor在项目-->项目名-->配置管理-->项目仓库勾上☑️公开,然后就可以使用了

  5. chart包使用地址为

    1
    2
    CHARTS_REPO = 'https://harbor.exxk.com.cn/chartrepo/cd-dev-1/charts'
    CHARTS_NAME = 'appchart-uat-cd-1.0.0.tgz'
  6. 更新使用部署命令

    1
    helm3 upgrade --install --history-max=$HISTORY_MAX manage-app$HELM_VER $CHARTS_REPO1/$CHARTS_NAME1 -n manage-server -f z-k8s-helm/uat/val-manage-app.yaml --set image.repository=$CICD_REGISTRY/$HARBOR_NAMESPACE/manage-app:$CICD_SEQUENCE,currentImage=$CURRENT_IMAGE

常见问题

  1. 国内拉去release压缩发布版本时,因为网络原因容易失败。

    解决:

    引入最新版helm/chart-releaser-action自动发布,修改helm-charts/.github/workflows/release.yml文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    name: Release Charts

    on:
    push:
    branches:
    - main

    jobs:
    release:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
    uses: actions/checkout@v2
    with:
    fetch-depth: 0

    - name: Configure Git
    run: |
    git config user.name "$GITHUB_ACTOR"
    git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
    - name: Run chart-releaser
    uses: helm/chart-releaser-action@v1.4.1
    env:
    CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
    - name: github fast
    run: |
    git status
    git checkout gh-pages
    git pull
    ls
    sed -i "s/github.com/download.fastgit.org/g" index.yaml
    git add index.yaml
    git commit -m "update url"
    git push
  2. 如果chart下载失败,检查仓库是否设置为公开。

参考

GitHub、Google等镜像加速地址收集

注意事项

版本号不变化,是不会覆盖发布的,注意每次修改Chart.yaml的版本号version: 0.1.1