💤本文章是为了记录学习。
流程:
获取请求新增对应headers -->通过 VirtualService 管理流量 --> 使用helm配置
Istio是一个开源的服务网格,它提供了一些功能,比如流量管理、安全性、可观察性等,而Envoy是Istio的默认代理,它提供了流量管理和代理功能。在Istio中,您可以使用Envoy代理来配置网关,以实现对服务流量的管理和控制。
Istio 通过在业务容器之中注入 Sidecar 来实现对微服务集群中东西向流量的接管。而为了能在对外提供服务的同时,不向客户端暴露整个服务网格,Istio 则需要对接 API 网关来管理微服务集群的南北向流量。
镜像构建
在我们使用**istio EnvoyFilter
**的 Lua 过滤器时,我们需要提前预装好自己需要的一些 Lua 功能模块,所以我们要针对官网的镜像加以调整新增我们需要的功能
FROM istio/proxyv2:1.15.2
MAINTAINER ycloud
RUN apt-get update && \
apt-get install -y luarocks libluajit-5.1-dev && \
luarocks install lua-cjson luasocket
Lua过滤器
通过 Envoy
的 Lua
过滤器,处理 http请求内容,我们这里是通过,获取到body,并识别body中method字段,将method内容生成一个新的headers。来区分请求的服务,针对 method 把流量分发到对应的服务上。下面是EnvoyFilter的详细内容:
这里的 Filter 作用于整个istio-proxy。
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: envoyfilter
spec:
workloadSelector:
labels:
app: istio-ingressgateway
configPatches:
# The first patch adds the lua filter to the listener/http connection manager
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value: # lua filter specification
name: envoy.filters.http.lua
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
inlineCode: |
local string_lower = string.lower
local string_find = string.find
local url_unescape = require("socket.url").unescape
local json_decode = require("cjson").decode
local json_encode = require("cjson").encode
local string_gmatch = string.gmatch
local my_envoy_on_request
local is_string_not_empty = function(s)
return s and type(s) == 'string' and s ~= ''
end
local base64_table =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' -- You will need this for encoding/decoding
function base64_enc(data)
if not data then return end
return ((data:gsub('.', function(x)
local r, b = '', x:byte()
for i = 8, 1, -1 do
r = r .. (b % 2 ^ i - b % 2 ^ (i - 1) > 0 and '1' or '0')
end
return r;
end) .. '0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
if (#x < 6) then return '' end
local c = 0
for i = 1, 6 do c = c + (x:sub(i, i) == '1' and 2 ^ (6 - i) or 0) end
return base64_table:sub(c + 1, c + 1)
end) .. ({'', '==', '='})[#data % 3 + 1])
end
function write_error_log(str, ...)
if not str then return end
io.stderr:write(str, ...)
end
function envoy_on_request(request_handle)
local ok, error = pcall(my_envoy_on_request, request_handle)
if not ok then
local headers = request_handle:headers()
local headers_str = json_encode(headers)
request_handle:logErr('error happened: ' .. (error or 'nil') ..
', headers_str: ' .. headers_str)
end
end
my_envoy_on_request = function(request_handle)
local content_type
local headers = request_handle:headers()
if not headers then return end
local content_type = headers:get('content-type')
if not is_string_not_empty(content_type) then return end
-- print(content_type)
-- print('content-type: ' .. (base64_enc(content_type) or 'nil'))
-- write_error_log('content-type: ' .. (base64_enc(content_type) or 'nil'))
content_type = string_lower(content_type)
local is_urlencoded_request = false
local is_json_request = false
if string_find(content_type, 'application/x-www-form-urlencoded', 1, true) then
is_urlencoded_request = true
elseif string_find(content_type, 'application/json') then
is_json_request = true
end
local body = request_handle:body()
local request_body_data = body:getBytes(0, body:length())
request_handle:streamInfo():dynamicMetadata():set("envoy.lua",
"requestBody",
request_body_data)
local req_json_str = request_body_data
if is_urlencoded_request then
for key, value in string_gmatch(tostring(req_json_str),
"([^&=]+)=([^&=]+)") do
if key == 'jsonStr' then
req_json_str = url_unescape(value)
break
end
end
end
if not req_json_str then return end
local req_json_t = json_decode(req_json_str)
if req_json_t then
local req_method = req_json_t.method or req_json_t.reqMethod
if not req_method then return end
-- 必须重新从 request_handle 中获取 headers 设置
local headers = request_handle:headers()
headers:add("x-asm-prefer-tag", req_method)
request_handle:logWarn('req_method: ' .. (req_method or 'nil') ..
', base64: ' ..
(base64_enc(req_method) or 'nil'))
end
end
HttpBin验证
httpbin是一个开源的HTTP请求和响应服务,可以用于测试和调试HTTP客户端。它提供了一个简单的RESTful API,可以模拟各种HTTP请求和响应。
我们可以使用httpbin来模拟参数请求。部署httpbin案例:
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
labels:
app: httpbin
service: httpbin
spec:
ports:
- name: http
port: 8000
targetPort: 80
selector:
app: httpbin
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
version: v1
template:
metadata:
labels:
app: httpbin
version: v1
spec:
containers:
- image: docker.io/kennethreitz/httpbin
imagePullPolicy: IfNotPresent
name: httpbin
ports:
- containerPort: 80
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: httpbin
spec:
gateways:
- httpbin
hosts:
- gsair.gstrain.qa.17usoft.com
http:
- match:
- uri:
prefix: /post
route:
- destination:
host: httpbin
port:
number: 8000
---
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: httpbin
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- gsair.gstrain.qa.17usoft.com
port:
name: http
number: 80
protocol: HTTP
部署好之后,查看服务
[root@ycloud envoy]# kubectl get -f httpbin.yaml
NAME SECRETS AGE
serviceaccount/httpbin 1 30d
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/httpbin NodePort 10.253.212.203 <none> 8000:40534/TCP 30d
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/httpbin 1/1 1 1 30d
可以看到请求头中新增了我们Lua中定义的headers
GatewayCenter
我们这里是通过helm来为我们构建流量管控规则,使我们更便利。
在我们服务上线的时候记得加上 DestinationRule
资源,用于子流量分发,后后期灰度金丝雀发布做预留准备。
{{- if .Values.gateway.enabled }}
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: {{ include "center-gatewaysvc.fullname" . }}
labels:
{{- include "center-gatewaysvc.labels" . | nindent 4 }}
spec:
host: {{ include "center-gatewaysvc.fullname" . }}
subsets:
- labels:
app.kubernetes.io/instance: {{ include "center-gatewaysvc.fullname" . }}
name: {{ include "center-gatewaysvc.fullname" . }}
{{- end}}
---
{{- define "http-routes" -}}
{{- range $route := .Values.routes }}
{{- range $preferTag := $route.preferTag }}
- match:
- headers:
x-asm-prefer-tag:
exact: {{ $preferTag }}
uri:
prefix: /gstrain
ignoreUriCase: true
rewrite:
uri: /{{ $preferTag }}
route:
- destination:
host: {{ $route.destination.host }}
port:
number: {{ $route.destination.port }}
subset: {{ $route.destination.host }}
{{- end}}
{{- end}}
{{- end}}
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: {{ include "center-gatewaysvc.fullname" . }}
labels:
{{- include "center-gatewaysvc.labels" . | nindent 4 }}
spec:
gateways:
- {{ include "center-gatewaysvc.fullname" . }}
hosts:
{{- toYaml .Values.gateway.hostnames | nindent 4 }}
http:
{{- template "http-routes" . }}
---
这里我们处理好之后,大致流程已经走通,后面有需要调整的地方进行处理。