0%

背景是我们希望能在k8s中通过DNS方式,访问服务的FQDN来调用虚拟机注册到nacos的服务。

我们vm和k8s的网段配置了相关路由能相互访问

之前nacos有维护了一个同步去coredns的项目,但是年久失修,支持的nacos版本和coredns版本都不高。后面在官方文档找资料的时候,发现nacos是支持istio MCP协议的 Pilot MCP协议介绍,于是采取这个方案来完成目标。《Nacos 1.1.4发布,业界率先支持Istio MCP协议

环境:

istio: 1.10

nacos: 2.1.0

配置nacos开启MCP Server

进入nacos配置目录,执行以下命令,把 nacos.istio.mcp.server.enabled 值设置为 true。重启nacos server 让它运行MCP Server。

1
[root@dev_10.1.10.209 nacos]#sed -i 's/nacos.istio.mcp.server.enabled=false/nacos.istio.mcp.server.enabled=true/g' conf/application.properties

重启后nacos MCP server会监听 18848 端口

1
2
3
[root@dev_10.1.10.209 nacos]#lsof -i:18848
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 98108 root 171u IPv4 834348182 0t0 TCP *:18848 (LISTEN)

配置istio添加MCP server sources

我测试的nacos server所在的服务器IP10.1.10.209

nacos MCP server监听的端口 18848

编辑istio的configmap,添加以下配置

1
kubectl edit -n istio-system cm istio
1
2
configSources:
- address: xds://10.1.10.209:18848

添加后的配置大概如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
data:
mesh: |-
accessLogFile: /dev/stdout
configSources:
- address: xds://10.1.10.209:18848
defaultConfig:
discoveryAddress: istiod.istio-system.svc:15012
proxyMetadata: {}
tracing:
zipkin:
address: zipkin.istio-system:9411
enablePrometheusMerge: true
rootNamespace: istio-system
trustDomain: cluster.local
meshNetworks: 'networks: {}'

重启下istiod 连接MCP server同步信息

1
kubectl rollout restart -n istio-system deployment istiod
Read more »

Istio EnvoyFilter+Lua 简单实现动态路由转发

因为种种原因,我们需要实现一个将服务名和subset携带在http请求的header里面,根据这个来实现将服务转发去特定的istio的服务的subset。

比如以下example:

  • 携带 service: msg-group tag: gray 的话,将其路由去msg-group 的gray subset。

    该版本返回数据: Call Read from msg-group,By New version!!!!

  • 携带 service: msg-group tag: default 的话,将其路由去msg-group 的default subset。

    改版本返回数据: Call Read from msg-group

用istio的Virtual Service来实现

大概如下:

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
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: onroute-com
namespace: develop
spec:
gateways:
- msggroup-gateway # 这个gateway - '*'
hosts:
- onroute.com
http:
- match:
- headers:
service:
exact: msg-group
tag:
exact: gray
route:
- destination:
host: msg-group.develop.svc.cluster.local
subset: gray
headers:
response:
set:
test: This value added By static route to gray
- match:
- headers:
service:
exact: msg-group
route:
- destination:
host: msg-group.develop.svc.cluster.local
subset: default
headers:
response:
set:
test: This value added By static route default

请求构造及结果如下:

注:10.99.20.74 是istio-ingressgateway的IP

请求 msg-group 的 gray 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# curl -v -H "Host: onroute.com" -H "service: msg-group" -H "tag: gray" 10.99.20.74/read
* Trying 10.99.20.74:80...
* Connected to 10.99.20.74 (10.99.20.74) port 80 (#0)
> GET /read HTTP/1.1
> Host: onroute.com
> User-Agent: curl/7.77.0
> Accept: */*
> service: msg-group
> tag: gray
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-type: text/plain;charset=UTF-8
< content-length: 43
< date: Fri, 16 Sep 2022 07:36:35 GMT
< x-envoy-upstream-service-time: 7
< server: istio-envoy
< test: This value added By static route to gray
<
* Connection #0 to host 10.99.20.74 left intact
Call Read from msg-group,By New version!!!!%

请求 msg-group 的 default 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
curl -v -H "Host: onroute.com" -H "service: msg-group" -H "tag: default" 10.99.20.74/read
* Trying 10.99.20.74:80...
* Connected to 10.99.20.74 (10.99.20.74) port 80 (#0)
> GET /read HTTP/1.1
> Host: onroute.com
> User-Agent: curl/7.77.0
> Accept: */*
> service: msg-group
> tag: default
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-type: text/plain;charset=UTF-8
< content-length: 24
< date: Fri, 16 Sep 2022 07:38:41 GMT
< x-envoy-upstream-service-time: 12
< server: istio-envoy
< test: This value added By static route default
<
* Connection #0 to host 10.99.20.74 left intact
Call Read from msg-group%

能实现问题,但是有几个小问题

  1. 配置不够灵活,如果面对几百个服务,配置是个巨大的工作量。
  2. 不同的namespace要配置不同的规则。

用Envoy Filter + Lua来实现

针对上面提到的问题,如果用EnvoyFiltter来实现动态的路由的话,就可以解决这个问题。

解决思路都是围绕config.route.v3.RouteAction cluster_header 这个字段来的

(string) Envoy will determine the cluster to route to by reading the value of the HTTP header named by cluster_header from the request headers. If the header is not found or the referenced cluster does not exist, Envoy will return a 404 response.

  1. 设置route的cluster_header字段为 x-service
  2. 从header读取service和tag的值组合出upstream cluster格式,并将x-service头替换为它
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
50
51
52
53
54
55
56
57
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: onroute-routing
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: VIRTUAL_HOST
match:
context: GATEWAY
patch:
operation: ADD
value:
name: dynamic_routing
domains:
- 'onroute.com'
- 'onroute.com:*'
routes:
- name: "path-matching"
match:
prefix: "/"
route:
cluster_header: "x-service"
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: "envoy.http_connection_manager"
subFilter:
name: "envoy.router"
patch:
operation: INSERT_BEFORE
value:
name: envoy.service-helper
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
inlineCode: |
function envoy_on_request(request_handle)
local headers = request_handle:headers()
local mesh_service = headers:get("service")
local tag = headers:get("tag")
if mesh_service ~= nil then
if tag ~= nil then
request_handle:headers():replace("x-service", "outbound|80|" .. tag .. "|" .. mesh_service .. ".svc.cluster.local")
end
end
end

function envoy_on_response(response_handle)
response_handle:headers():add("test", "from envoy filter response")
end

下面是测试结果:

请求 msg-group的 gray 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
curl -v -H "Host: onroute.com" -H "service: msg-group.develop" -H "tag: gray" 10.99.20.74/read
* Trying 10.99.20.74:80...
* Connected to 10.99.20.74 (10.99.20.74) port 80 (#0)
> GET /read HTTP/1.1
> Host: onroute.com
> User-Agent: curl/7.77.0
> Accept: */*
> service: msg-group.develop
> tag: gray
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-type: text/plain;charset=UTF-8
< content-length: 43
< date: Fri, 16 Sep 2022 08:07:29 GMT
< x-envoy-upstream-service-time: 16
< server: istio-envoy
< test: from envoy filter response
<
* Connection #0 to host 10.99.20.74 left intact
Call Read from msg-group,By New version!!!!%

请求 msg-group的 default 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
curl -v -H "Host: onroute.com" -H "service: msg-group.develop" -H "tag: default" 10.99.20.74/read
* Trying 10.99.20.74:80...
* Connected to 10.99.20.74 (10.99.20.74) port 80 (#0)
> GET /read HTTP/1.1
> Host: onroute.com
> User-Agent: curl/7.77.0
> Accept: */*
> service: msg-group.develop
> tag: default
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-type: text/plain;charset=UTF-8
< content-length: 24
< date: Fri, 16 Sep 2022 08:12:40 GMT
< x-envoy-upstream-service-time: 14
< server: istio-envoy
< test: from envoy filter response
<
* Connection #0 to host 10.99.20.74 left intact
Call Read from msg-group%

参考资料

优秀的容量治理 不但能解决当前问题,还能防患于未然

扩容

  • 扩容避免盲目,可以作为应急手段,避免滥用和无脑用
  • 考虑对底层资源的影响 (中间件、数据库、缓存这类、连接数、长连接数之类的)
  • 建立谨慎扩容风气
  • 优化程序为主(异步、读写分离、增加缓存、代码和SQL调优等等)

限流

常见限流策略

  • 流量整形: 指不管流量到达的速率多么不稳定,在接收流量后,都将其匀速输出的过程,即“乱进齐出”。
  • 容忍突发流量: 指的是限流策略允许流量在短时间内突增,且在突增结束后不会影响后续流量的正常限流。
  • 平滑限流: 指的是在限流周期内流量分布均匀,比如限制 10 秒内请求次数不超过 1000,平滑限流应做到分摊到每秒不超过 100 次请求。反之,不平滑限流有可能在第 1 秒就请求了 1000 次,后面 9 秒无法再发出任何请求。
限流策略 流量整形 容忍突发流量 平滑限流 实现复杂度
固定窗口 不支持 不支持 不支持
滑动窗口 不支持 不支持 不支持
漏桶算法 支持 不支持 支持
令牌桶算法 支持 支持 支持

限流应当采用分治的思想

按位置划分的话,可以分为:

  1. 网关层限流|入口限流 静态资源走CDN,仅放过合法的请求,针对IP或者域名粗放式进行限流。
  2. 接入层限流 | 粒度比网关细,主要手段有负载均衡和限流措施,分摊服务压力。
  3. 应用层限流 | 有自己或者单机的限流, 常用的微服务相关的容量治理主要是在这个环节,针对自己,集群,第三方调用的治理。
  4. 数据库层限流 | 主要采取集群、读写分离等方法分摊压力。

和搞安全类似的想法,“一切输入都是有害的”,不信任上层,要有自己的一套治理方法。

Read more »

流量暴涨

接到告警,业务的Kafka出口流量异常,这个kafka一直是订单类在用,平时插入的数据不会达到这个规模。其次,入口带宽很少,出口带宽这么多,掐指一算,大概率是消费的服务出现问题了。一般入口带宽和出口带宽 不是消费组特别多的情况下,两个的带宽相差不会太悬殊。

查找凶手

iftop 看了一下大致流量情况

image-20220128145022454

发现主要流量流出到3.843.85这两台机

tcpdump -i eth0 host <kafka ip> and host <xxx.xxx.3.84> -w test.cap 抓取了几秒这两个主机相关的包

基本确定了流量使用多的端口,主要是 3.141:9093(Kafka) 发送给 3.84:15630 的流量比较多。

tcp流

看了下(3.84:15630 -> 3.141:9093 )的内容 基本可以确定是哪个topic和消费组。

不抓包的话 用netstat来简单过滤下,如果两个主机间建立的连接比较少的话,基本也能定位出是哪个端口,如下

1
2
3
[root@xxx-xxx-3.141 ~]#netstat -ano|grep xxx.xxx.3.84
tcp 0 0 xxx.xxx.3.141:9093 xxx.xxx.3.84:15556 ESTABLISHED keepalive (4.24/0/0)
tcp 0 4128896 xxx.xxx.3.141:9093 xxx.xxx.3.84:15630 ESTABLISHED on (0.20/0/0)

接下来就需要找出3.84:15630是哪个服务监听的端口。

到3.84这台机执行 lsof -i:端口 得到进程ID

然后用 ps aux | grep 或者 lsof -p 找出具体进程信息。

Read more »

我们在持续构建中,经常遇到编译或者运行需要安装大量依赖。像php,java,go,nodejs,python等等。似乎现在没个包管理的语言都不敢出来推广。

但是在使用多阶段构建后,经常遇到依赖反复安装导致构建时间变成的事情。

后面发现了BuildKit可以完成这个事情。

比如下面一个基于nodejs前端工程化的例子:

npm安装依赖后,执行npm run build,将build生成的dist目录下的文件复制到nginx容器提供服务。

而–mount 可以挂载一个具名的缓存来缓存依赖目录,build的时候同样挂载进去即可。下次构建的时候挂载同样的缓存,就不用重复安装了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# syntax = docker/dockerfile:experimental
FROM node:10-alipine3.9 as builder
WORKDIR /code
COPY . .
RUN --mount=type=cache,target=/code/node_modules,id=my_app_npm_module,sharing=locked --mount=type=cache,target=/root/.npm,id=npm_cache npm install --registry=https://registry.npm.taobao.org
RUN --mount=type=cache,target=/code/node_modules,id=my_app_npm_module,sharing=locked npm run build
FROM nginx:1.19.2-alpine
RUN echo $'server {\n\
listen 80;\n\
listen [::]:80;\n\
server_name localhost;\n\
location / {\n\
root /usr/share/nginx/html;\n\
try_files $uri $uri /index.html;\n\
}\n\
}' > /etc/nginx/conf.d/default.conf
COPY --from=builder /code/dist /usr/share/nginx/html
Read more »

基础数据类型

  • 整数类型 int[8/16/32/64]
  • 浮点类型 float[32/64]
  • 字符型 byte
  • 布尔类型 bool
  • 字符串类型 string

复杂数据类型

  • 指针类型 Pointer
  • 数组 Array
  • 结构体 Struct
  • 管道 Channel
  • 函数 Func
  • 切片 Slice
  • 接口 Interface
  • map

类型相关

import(
“fmt”
“unsafe”
)

1
2
3
4
5
6
7
8
9
10
var a byte = 'a'
var b int = '哈'
// 打印字符
fmt.Printf('%c %c', a, b)
// 输出类型
fmt.Printf('%t %t', a, b)
// 打印占用空间
fmt.Printf('%d %d', unsafe.Sizeof(a), unsafe.Sizeof(b))
// 打印出对应的码值
fmt.Printf('%d %d', a, b)

内建变量

  • bool
  • string
  • (u)int[8/16/32/64],uintptr
  • byte
  • rune go的char类型,长度32位
  • float[32/64]
  • complex[64/128] 复数类型

变量定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1 单个定义
var a int
var b string

// 2 多个定义
var a, b int = 3, 4

// 3 类型判断 省略类型 TypeDeduction
var a, b , = 3, "4", true

// 4 省略var
a,b = 1, 2

// 5 函数外定义 包内部定义
var(
aa = 1
bb = "bb"
)

常量与枚举

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
// 1 单个定义
const filename = "xxx"

// 2 多个定义
const a, b = 1, 3

// 3 定义多个
const (
...
)

// 4 枚举类型 iota表示值自增
func enums() {
const(
cpp = iota
- //可跳过一个值
java
python
golang
javascript
)
}

// 5 枚举类型 iota计算
const(
b = 1 << (10 * iota)
kb
mb
gb
tb
pg
)
Read more »

Basic Commands

命令 描述
create/apply 从文件或stdin创建资源
expose 为deployment、pod创建service
run 在集群中运行指定的镜像
set 在对象上设置一个specific
get 最基本的查询命令 可以get一切资源信息
explain 查看资源定义,如 kubectl explain replicaset
edit 使用系统编辑器编辑资源 kubectl edit deploy/foo
delete 删除指定资源,支持文件名、资源名、label selector。kubectl delete po -l foo=bar

Deploy Commands

命令 描述
rollout Deployment、DaemonSet的升级过程管理 查看状态、操作历史、暂停升级、恢复升级、回滚
rolling-update 客户端滚动升级,仅限ReplicationController
scale 修改Deployment、ReplicaSet、ReplicationController,Job的实例数
autoscale 为Deployment、ReplicaSet、ReplicationController配置自动伸缩规则,依赖heapster和hpa
Read more »

核心功能

  • 服务发现与负载均衡
  • 容器自动装箱
  • 存储编排
  • 自动容器护肤
  • 自动发布与回滚
  • 配置与密文管理
  • 批量执行
  • 水平伸缩

Pod

  • 一组功能相关的Container的封装
  • 共享存储和Network Namespace
  • 容易走失,需要Workload和Service的呵护

Pod调度过程

Pod调度过程

Workloads

含Deployment、StatefulSet、DaemonSet、Job、Cronjob

Service

  • Pos 防失联
  • 给一组Pod设置反向代理
Read more »

Istio策略和遥测的原理

Istio通过专门的服务端组件,提供一种扩展机制来手机服务运行的遥测数据和服务间的访问,执行一定的策略。

应用场景

采集数据、存储数据、检索数据

通过Sidecar Inbound和Outbound都想Mixer上报数据。Mixer提供对应APM的Adapter。解耦Sidecar和APM的通讯,由Mixer来集中对接。

工作原理

每个插件都是一个Adapter,Mixer通过它们与不同的基础设施后端连接。提供日志、监控、配额、ACL检查等功能。

因为每个基础设施后端都有不同的接口和操作,所以需要自定义代码进行处理,这就是Mixer的Adapter机制。

Mixer通过Protobuf格式定义Adapter的配置。

属性

Envoy上报的数据istio中称为属性 attribute。属性是一小块数据,描述服务请求或者服务运行环境的信息。

属性列表: https://istio.io/docs/reference/config/policy-and-telemetry/attribute-vocabulary/

属性表达式

属性表达式: https://istio.io/docs/reference/config/policy-and-telemetry/expression-language/

Mixer的匹配模型

istio主要通过handler、instance、rule这三个资源对象来描述Adapter的配置。

业务处理 Handler

Handler来描述Adapters及其配置。Mixer每个Adapter都需要一些配置才能运行。不同额Adapter有不同的配置,比如服务后端信息之类的。

如果Adapter看做是一个模板的定义的话,Handler就是这个模板的实现。

Read more »