Istio IngressGateway 根据流量特征打标并据此路由

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 在阿里云 ASM 服务网格 Istio ingressgateway上实现根据流量特征(如来源于内外网、用户认证信息)等进行流量打标(染色),并根据流量标签进行路由和灰度发布。

背景


入口流量的tag标签,一般有两种方式:

1)客户端发送请求带上特定的标签( 如 HTTP Header) —— 需要客户端根据灰度修改代码

2)入口网关根据流量特征进行打标

网关层面的打标一般基于网关插件化能力,将请求流量进行打标。 比如外部用户访问 Base 环境,内部用户访问 Gray 环境;比如将userid处于一定范围的打上代表灰度的tag; 比如根据登录用户 token 用户组 group 进行打标。


通过本文实现的 流量打标 + ASM 全链路灰度,可以实现完整的从 入口网关 到 服务的 无侵入的ASM全链路灰度发布。


实现方式


通过 EnvoyFilter 采用 Lua 脚本根据 HTTP 请求的 来源IP 或者 JWT 在 route 到 upstream 服务之前附加新的 HTTP Headers。


场景一:根据内外网来源请求对流量打标

EnvoyFilter 配置

来源于内部的请求将打上 x-asm-prefer-tag = gray 标签,来源于外部的请求将打上 x-asm-prefer-tag = base 的标签

apiVersion: networking.istio.io/v1alpha3

kind: EnvoyFilter

metadata:

 name: http-request-labelling-according-source

 namespace: istio-system

spec:

 workloadSelector:

   labels:

     app: istio-ingressgateway

 configPatches:

 - 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:

      name: envoy.lua

      typed_config:

        "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"

        inlineCode: |

          function envoy_on_request(request_handle)

            local jwtInterHeaderName = "X-Envoy-Internal"

            local jwtExterHeaderName = "X-Envoy-External-Address"

            headers = request_handle:headers()

            jwtinter = headers:get(jwtInterHeaderName)

            jwtexter = headers:get(jwtExterHeaderName)            

            if (jwtinter ~= nil)  then

                headers:add("x-asm-prefer-tag","gray")

            elseif (jwtexter ~= nil) then

                headers:add("x-asm-prefer-tag","base")

            else

                headers:add("x-asm-prefer-tag","notmatch")

            end

          end



httpbin 应用部署

部署 httpbin 应用来检验 request HTTP header 是否如愿打上 header。

##################################################################################################

# httpbin service

##################################################################################################

apiVersion: v1

kind: ServiceAccount

metadata:

 name: httpbin

---

apiVersion: v1

kind: Service

metadata:

 name: httpbin

 labels:

   app: httpbin

   service: httpbin

spec:

 ports:

 - name: http

   port: 8000

   targetPort: 80

 selector:

   app: httpbin

---

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:

     serviceAccountName: httpbin

     containers:

     - image: docker.io/kennethreitz/httpbin

       imagePullPolicy: IfNotPresent

       name: httpbin

       ports:

       - containerPort: 80

---

apiVersion: networking.istio.io/v1beta1

kind: VirtualService

metadata:

 name: direct-httpbin-simple-http-vs

spec:

 gateways:

   - direct-httpbin-simple-http-gw

 hosts:

   - direct-httpbin-simple-http.asmworkshop.io

   - httpbin

 http:

   - route:

       - destination:

           host: httpbin

           port:

             number: 8000

---

apiVersion: networking.istio.io/v1beta1

kind: Gateway

metadata:

 name: direct-httpbin-simple-http-gw

spec:

 selector:

   istio: ingressgateway

 servers:

   - hosts:

       - direct-httpbin-simple-http.asmworkshop.io

     port:

       name: http

       number: 80

       protocol: HTTP


测试 Header 打标情况


从外部访问(访问 istio-ingressgateway 公网 SLB 入口 ):可见带上了 X-Asm-Prefer-Tag": "base"

# curl -H "Host: direct-httpbin-simple-http.asmworkshop.io" 120.24.87.135/get?show_env=true

{

 "args": {

   "show_env": "true"

 },

 "headers": {

   "Accept": "*/*",

   "Content-Length": "0",

   "Host": "direct-httpbin-simple-http.asmworkshop.io",

   "User-Agent": "curl/7.29.0",

   "X-Asm-Prefer-Tag": "base",

   "X-B3-Parentspanid": "bfea77423107a397",

   "X-B3-Sampled": "1",

   "X-B3-Spanid": "78967b70d984995d",

   "X-B3-Traceid": "601184bf39e6f5e5bfea77423107a397",

   "X-Envoy-Attempt-Count": "1",

   "X-Envoy-External-Address": "47.115.37.91",

   "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/simple-http/sa/httpbin;Hash=a1b644096de33600b9b59a006afd0a087cc2f2612c85f4f2da8738e3d829f108;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account",

   "X-Forwarded-For": "47.115.37.91",

   "X-Forwarded-Proto": "http",

   "X-Request-Id": "e2cd8489-22cd-979d-b4a8-db9f494c0982"

 },

 "origin": "47.115.37.91",

 "url": "http://direct-httpbin-simple-http.asmworkshop.io/get?show_env=true"

}


从VPC 内部访问(访问 istio-ingressgateway 私网 SLB 入口 ):可见带上了 "X-Asm-Prefer-Tag": "gray"

# curl -H "Host: direct-httpbin-simple-http.asmworkshop.io" 192.168.4.143/get?show_env=true

{

 "args": {

   "show_env": "true"

 },

 "headers": {

   "Accept": "*/*",

   "Content-Length": "0",

   "Host": "direct-httpbin-simple-http.asmworkshop.io",

   "User-Agent": "curl/7.29.0",

   "X-Asm-Prefer-Tag": "gray",

   "X-B3-Parentspanid": "78c3503c97a46b22",

   "X-B3-Sampled": "1",

   "X-B3-Spanid": "c0ee0a47deb148eb",

   "X-B3-Traceid": "1593be6d6dece99678c3503c97a46b22",

   "X-Envoy-Attempt-Count": "1",

   "X-Envoy-Internal": "true",

   "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/simple-http/sa/httpbin;Hash=a1b644096de33600b9b59a006afd0a087cc2f2612c85f4f2da8738e3d829f108;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account",

   "X-Forwarded-For": "192.168.0.240",

   "X-Forwarded-Proto": "http",

   "X-Request-Id": "f77e6a52-4532-9596-a8a4-aa5d152c9a7a"

 },

 "origin": "192.168.0.240",

 "url": "http://direct-httpbin-simple-http.asmworkshop.io/get?show_env=true"

}


根据标签路由

部署一个新的应用,分别 base 版本 和 gray 版本,测试按照上一步骤打上的 x-asm-prefer-tag 来路由:

apiVersion: v1

kind: ServiceAccount

metadata:

 name: http-a

---

apiVersion: v1

kind: Service

metadata:

 labels:

   app: http-a

 name: http-a

spec:

 ports:

 - name: http

   port: 80

   protocol: TCP

   targetPort: 8080

 selector:

   app: http-a

---

apiVersion: apps/v1

kind: Deployment

metadata:

 labels:

   app: http-a

 name: lua-http-a-base

spec:

 replicas: 1

 selector:

   matchLabels:

     app: http-a

     version: base

 template:

   metadata:

     labels:

       app: http-a

       version: base

   spec:

     serviceAccountName: http-a

     containers:

     - env:

       - name: "LISTEN_ADDR"

         value: "0.0.0.0:8080"

       - name: "SERVER_TYPE"

         value: "http"

       - name: "NAME"

         value: "http-a"

       - name: "MESSAGE"

         value: "Web response from http-a-Base"

       - name: "HTTP_CLIENT_APPEND_REQUEST"

         value: "true"

       - name: KUBERNETES_NAMESPACE

         valueFrom:

           fieldRef:

             fieldPath: metadata.namespace

       image: registry.cn-hangzhou.aliyuncs.com/containerdemo/nicholasjackson-fake-service:v0.17.0

       imagePullPolicy: IfNotPresent

       name: http-a

       ports:

       - containerPort: 8080

         name: http

         protocol: TCP

       securityContext:

         privileged: false

---

apiVersion: apps/v1

kind: Deployment

metadata:

 labels:

   app: http-a

 name: lua-http-a-gray

spec:

 replicas: 1

 selector:

   matchLabels:

     app: http-a

     version: gray

 template:

   metadata:

     labels:

       app: http-a

       version: gray

   spec:

     serviceAccountName: http-a

     containers:

     - env:

       - name: "LISTEN_ADDR"

         value: "0.0.0.0:8080"

       - name: "SERVER_TYPE"

         value: "http"

       - name: "NAME"

         value: "http-a"

       - name: "MESSAGE"

         value: "Web response from http-a-Gray"

       - name: "HTTP_CLIENT_APPEND_REQUEST"

         value: "true"

       - name: KUBERNETES_NAMESPACE

         valueFrom:

           fieldRef:

             fieldPath: metadata.namespace

       image: registry.cn-hangzhou.aliyuncs.com/containerdemo/nicholasjackson-fake-service:v0.17.0

       imagePullPolicy: IfNotPresent

       name: http-a

       ports:

       - containerPort: 8080

         name: http

         protocol: TCP

       securityContext:

         privileged: false


配置 GW, VS, DR 暴露服务:

apiVersion: networking.istio.io/v1alpha3

kind: Gateway

metadata:

 name: lua-simple-http

spec:

 selector:

   istio: ingressgateway

 servers:

 - hosts:

   - lua-simple-http.asmworkshop.io

   port:

     name: http

     number: 80

     protocol: HTTP

---

apiVersion: networking.istio.io/v1alpha3

kind: VirtualService

metadata:

 name: lua-simple-http-a-vs

spec:

 gateways:

 - lua-simple-http

 hosts:

 - lua-simple-http.asmworkshop.io

 - http-a

 http:

 - match:

   - headers:

       x-asm-prefer-tag:

         exact: gray

   route:

   - destination:

       host: http-a

       subset: version-gray

 - route:

   - destination:

       host: http-a

       subset: version-base

---

apiVersion: networking.istio.io/v1alpha3

kind: DestinationRule

metadata:

 name: lua-simple-http-a-dr

spec:

 host: http-a

 subsets:

 - name: version-gray

   labels:

     version: gray

 - name: version-base

   labels:

     version: base


测试

从VPC 内部访问(访问 istio-ingressgateway 私网 SLB 入口 ):可见内部用户访问到 灰度服务

# curl -H "Host: lua-simple-http.asmworkshop.io" 192.168.4.143

{

 "name": "http-a",

 "uri": "/",

 "type": "HTTP",

 "ip_addresses": [

   "192.168.86.68"

 ],

 "start_time": "2021-10-24T12:54:16.883832",

 "end_time": "2021-10-24T12:54:16.883950",

 "duration": "117.748µs",

 "body": "Web response from http-a-Gray",

 "code": 200

}


从外部访问(访问 istio-ingressgateway 公网 SLB 入口 ):可见外部用户 访问到 正常环境

# curl -H "Host: lua-simple-http.asmworkshop.io" 120.24.87.135

{

 "name": "http-a",

 "uri": "/",

 "type": "HTTP",

 "ip_addresses": [

   "192.168.86.67"

 ],

 "start_time": "2021-10-24T12:54:31.610400",

 "end_time": "2021-10-24T12:54:31.610771",

 "duration": "370.758µs",

 "body": "Web response from http-a-Base",

 "code": 200

}




场景二:根据 JWT Claim 对流量进行打标


EnvoyFilter 配置


Lua 将 decode 从 HTTP Header Authorization 带的 JWT Token ,并将 Token 中的 iss, sub, group 等信息附加到新的 Authorization-Iss, Authorization-Sub, Authorization-Group 的 Header 里:

apiVersion: networking.istio.io/v1alpha3

kind: EnvoyFilter

metadata:

 name: parse-request-jwt-for-labelling

 namespace: istio-system

spec:

 workloadSelector:

   labels:

     app: istio-ingressgateway

 configPatches:

 - 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:

      name: envoy.lua

      typed_config:

        "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"

        inlineCode: |

         -- Json Parsing based on https://gist.github.com/tylerneylon/59f4bcf316be525b30ab

         -- Base64 decoding based on wikipedia description of 8/6bit encoding.

         -- base64 char array.. note final 2 chars are for RFC4648-URL encoding

         -- as per JWT spec section 2 terminology 'Base64url Encoding'

         local alpha='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'

         -- convert to 6 char long binary string. (max int 64!)

         function toBinaryString(int)

             if int > 64 then

                 error("Bad number "..int.." to convert to binary")

             end        

             local remaining = tonumber(int)

             local bits = ''

             for i = 5, 0, -1 do

                 local pow = 2 ^ i

                 if remaining >= pow then

                     bits = bits .. '1'

                     remaining = remaining - pow

                 else

                     bits = bits .. '0'

                 end

             end

             return bits

         end

         

         function fromBinaryString(bits)

             return tonumber(bits, 2)

         end

         

         function decodeBase64(encoded)

             local bitstr = ''

             local decoded = ''

             -- decode chars into bitstring

             for i = 1, string.len(encoded) do

                 local offset, _ = string.find(alpha, string.sub(encoded, i, i))

                 if offset == nil then

                     error("Bad base64 character " .. string.sub(encoded, i, i))

                 end

                 bitstr = bitstr .. toBinaryString(offset-1)

             end

             -- decode bitstring back to chars

             for i = 1, string.len(bitstr), 8 do

                 decoded = decoded .. string.char(fromBinaryString(string.sub(bitstr, i, i+7)))

             end

             return decoded

         end

         

         -- json handling

         local json = {}

         local function kind_of(obj)

           if type(obj) ~= 'table' then return type(obj) end

           local i = 1

           for _ in pairs(obj) do

             if obj[i] ~= nil then i = i + 1 else return 'table' end

           end

           if i == 1 then return 'table' else return 'array' end

         end

         

         local function escape_str(s)

           local in_char  = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'}

           local out_char = {'\\', '"', '/',  'b',  'f',  'n',  'r',  't'}

           for i, c in ipairs(in_char) do

             s = s:gsub(c, '\\' .. out_char[i])

           end

           return s

         end

         

         -- Returns pos, did_find; there are two cases:

         -- 1. Delimiter found: pos = pos after leading space + delim; did_find = true.

         -- 2. Delimiter not found: pos = pos after leading space;     did_find = false.

         -- This throws an error if err_if_missing is true and the delim is not found.

         local function skip_delim(str, pos, delim, err_if_missing)

           pos = pos + #str:match('^%s*', pos)

           if str:sub(pos, pos) ~= delim then

             if err_if_missing then

               error('Expected ' .. delim .. ' near position ' .. pos)

             end

             return pos, false

           end

           return pos + 1, true

         end

         

         -- Expects the given pos to be the first character after the opening quote.

         -- Returns val, pos; the returned pos is after the closing quote character.

         local function parse_str_val(str, pos, val)

           val = val or ''

           local early_end_error = 'End of input found while parsing string.'

           if pos > #str then error(early_end_error) end

           local c = str:sub(pos, pos)

           if c == '"'  then return val, pos + 1 end

           if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end

           -- We must have a \ character.

           local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'}

           local nextc = str:sub(pos + 1, pos + 1)

           if not nextc then error(early_end_error) end

           return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc))

         end

         

         -- Returns val, pos; the returned pos is after the number's final character.

         local function parse_num_val(str, pos)

           local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)

           local val = tonumber(num_str)

           if not val then error('Error parsing number at position ' .. pos .. '.') end

           return val, pos + #num_str

         end

         

         json.null = {}  -- one-off table to represent the null value.

         

         function json.parse(str, pos, end_delim)

           pos = pos or 1

           if pos > #str then error('Reached unexpected end of input.') end

           local pos = pos + #str:match('^%s*', pos)  -- Skip whitespace.

           local first = str:sub(pos, pos)

           if first == '{' then  -- Parse an object.

             local obj, key, delim_found = {}, true, true

             pos = pos + 1

             while true do

               key, pos = json.parse(str, pos, '}')

               if key == nil then return obj, pos end

               if not delim_found then error('Comma missing between object items.') end

               pos = skip_delim(str, pos, ':', true)  -- true -> error if missing.

               obj[key], pos = json.parse(str, pos)

               pos, delim_found = skip_delim(str, pos, ',')

             end

           elseif first == '[' then  -- Parse an array.

             local arr, val, delim_found = {}, true, true

             pos = pos + 1

             while true do

               val, pos = json.parse(str, pos, ']')

               if val == nil then return arr, pos end

               if not delim_found then error('Comma missing between array items.') end

               arr[#arr + 1] = val

               pos, delim_found = skip_delim(str, pos, ',')

             end

           elseif first == '"' then  -- Parse a string.

             return parse_str_val(str, pos + 1)

           elseif first == '-' or first:match('%d') then  -- Parse a number.

             return parse_num_val(str, pos)

           elseif first == end_delim then  -- End of an object or array.

             return nil, pos + 1

           else  -- Parse true, false, or null.

             local literals = {['true'] = true, ['false'] = false, ['null'] = json.null}

             for lit_str, lit_val in pairs(literals) do

               local lit_end = pos + #lit_str - 1

               if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end

             end

             local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)

             error('Invalid json syntax starting at ' .. pos_info_str)

           end

         end

         

         function decode_jwt(jwt)

           i=0

           result = {}

           for match in (jwt..'.'):gmatch("(.-)%.") do

               result[i]=decodeBase64(match)

               i=i+1

           end

           -- header

           head = json.parse(result[0])

           -- claims

           claims = json.parse(result[1])

           return {head=head,claims=claims}

         end

         

         function add_header(k,v,prefix,headers)

           if "number" == type (k) then

             headers:add(prefix,v)

           else

             headers:add(prefix.."-"..k,v)

           end

         end

         

         function add_table_as_headers(table, prefix, headers)

           for k,v in pairs(table) do

             if "string" == type( v ) then

               add_header(k,v,prefix,headers)

             elseif "table" == type( v ) then

               add_table_as_headers(v,prefix.."-"..k,headers)

             end

           end

         end

         

         function envoy_on_request(request_handle)

           local jwtHeaderName = "Authorization"

           headers = request_handle:headers()

           jwt = headers:get(jwtHeaderName)

           if jwt == nil then

               headers:add("jwt","headernotfound")

           else

               content = decode_jwt(jwt)

               add_table_as_headers(content["claims"],jwtHeaderName,headers)

           end

         end





测试 httpbin 服务


ADMIN_TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkNVLUFESkpFYkg5YlhsMHRwc1FXWXVvNEV3bGt4RlVIYmVKNGNra2FrQ00iLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ3NDUxNDUwNzEsImdyb3VwIjoiYWRtaW4iLCJpYXQiOjE1OTE1NDUwNzEsImlzcyI6ImF1dGhAaXN0aW9pbmFjdGlvbi5pbyIsInN1YiI6IjIxOGQzZmI5LTQ2MjgtNGQyMC05NDNjLTEyNDI4MWM4MGU3YiJ9.MEL9ANwx4kvxkK90cdkUBejn-cLIrACdvGiE9T4RE3F1FRc4et4EZ79s-tbb7OJgnOCkTcvB-Q4V_9WaeAU_kNvzM1rGGh1a0ahQI01Iipt0c6RUlWk1GUr5eUul7xw5MoR-kKDuB-fB0qG2_WQfyiqez6uO9OGJxipTwfhoWJfq_9sZ3p7d8iwJzIcCleTb6ywKmIa4gJb0UhaVcs77HP7KTq9PzTj2adOa2KtfH0BTFjAymZKJVEsV64A_XdNAybiVmEmd8kqTuIbHob-ZT9Mlyl3ER_A6rbIzx6myD9F8m1GIaz2fgtMCJyawuxd_YK4L1cvWhJ2BkbyCtC1znQ



echo $ADMIN_TOKEN | cut -d '.' -f2 | base64 -d

{"exp":4745145071,"group":"admin","iat":1591545071,"iss":"auth@istioinaction.io","sub":"218d3fb9-4628-4d20-943c-124281c80e7b"}




不带 Authorizaiton Header:可见带上了 "Jwt": "headernotfound" 的 Header

# curl -H "Host: direct-httpbin-simple-http.asmworkshop.io" 192.168.4.143/get?show_env=true

{

 "args": {

   "show_env": "true"

 },

 "headers": {

   "Accept": "*/*",

   "Content-Length": "0",

   "Host": "direct-httpbin-simple-http.asmworkshop.io",

   "Jwt": "headernotfound",

   "User-Agent": "curl/7.29.0",

   "X-Asm-Prefer-Tag": "gray",

   "X-B3-Parentspanid": "bd40ccc0158d33bd",

   "X-B3-Sampled": "1",

   "X-B3-Spanid": "2a222c8963b9c868",

   "X-B3-Traceid": "7c0ecbb3b1894e18bd40ccc0158d33bd",

   "X-Envoy-Attempt-Count": "1",

   "X-Envoy-Internal": "true",

   "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/simple-http/sa/httpbin;Hash=a1b644096de33600b9b59a006afd0a087cc2f2612c85f4f2da8738e3d829f108;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account",

   "X-Forwarded-For": "192.168.0.240",

   "X-Forwarded-Proto": "http",

   "X-Request-Id": "4ec96591-230f-9858-b961-6d741f7c7188"

 },

 "origin": "192.168.0.240",

 "url": "http://direct-httpbin-simple-http.asmworkshop.io/get?show_env=true"

}


带 Authorization Header(访问 私网 SLB): 可见带上了以下 Header

"Authorization-Group": "admin",

"Authorization-Iss": "auth@istioinaction.io",

"Authorization-Sub": "218d3fb9-4628-4d20-943c-124281c80e7b",

# curl -H "Host: direct-httpbin-simple-http.asmworkshop.io" 192.168.4.143/get?show_env=true -H "Authorization: $ADMIN_TOKEN"

{

 "args": {

   "show_env": "true"

 },

 "headers": {

   "Accept": "*/*",

   "Authorization": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkNVLUFESkpFYkg5YlhsMHRwc1FXWXVvNEV3bGt4RlVIYmVKNGNra2FrQ00iLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ3NDUxNDUwNzEsImdyb3VwIjoiYWRtaW4iLCJpYXQiOjE1OTE1NDUwNzEsImlzcyI6ImF1dGhAaXN0aW9pbmFjdGlvbi5pbyIsInN1YiI6IjIxOGQzZmI5LTQ2MjgtNGQyMC05NDNjLTEyNDI4MWM4MGU3YiJ9.MEL9ANwx4kvxkK90cdkUBejn-cLIrACdvGiE9T4RE3F1FRc4et4EZ79s-tbb7OJgnOCkTcvB-Q4V_9WaeAU_kNvzM1rGGh1a0ahQI01Iipt0c6RUlWk1GUr5eUul7xw5MoR-kKDuB-fB0qG2_WQfyiqez6uO9OGJxipTwfhoWJfq_9sZ3p7d8iwJzIcCleTb6ywKmIa4gJb0UhaVcs77HP7KTq9PzTj2adOa2KtfH0BTFjAymZKJVEsV64A_XdNAybiVmEmd8kqTuIbHob-ZT9Mlyl3ER_A6rbIzx6myD9F8m1GIaz2fgtMCJyawuxd_YK4L1cvWhJ2BkbyCtC1znQ",

   "Authorization-Group": "admin",

   "Authorization-Iss": "auth@istioinaction.io",

   "Authorization-Sub": "218d3fb9-4628-4d20-943c-124281c80e7b",

   "Content-Length": "0",

   "Host": "direct-httpbin-simple-http.asmworkshop.io",

   "User-Agent": "curl/7.29.0",

   "X-Asm-Prefer-Tag": "gray",

   "X-B3-Parentspanid": "121a2ec37208dfdf",

   "X-B3-Sampled": "1",

   "X-B3-Spanid": "42ee3aca9dc339d0",

   "X-B3-Traceid": "fee9135efff4cf33121a2ec37208dfdf",

   "X-Envoy-Attempt-Count": "1",

   "X-Envoy-Internal": "true",

   "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/simple-http/sa/httpbin;Hash=a1b644096de33600b9b59a006afd0a087cc2f2612c85f4f2da8738e3d829f108;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account",

   "X-Forwarded-For": "192.168.0.240",

   "X-Forwarded-Proto": "http",

   "X-Request-Id": "c9e0c85b-1d18-92d9-8a15-6b217476d8f0"

 },

 "origin": "192.168.0.240",

 "url": "http://direct-httpbin-simple-http.asmworkshop.io/get?show_env=true"

}


带 Authorization Header(访问 公网 SLB): 可见带上了以下 Header

"Authorization-Group": "admin",

"Authorization-Iss": "auth@istioinaction.io",

"Authorization-Sub": "218d3fb9-4628-4d20-943c-124281c80e7b",

# curl -H "Host: direct-httpbin-simple-http.asmworkshop.io" 120.24.87.135/get?show_env=true -H "Authorization: $ADMIN_TOKEN"

{

 "args": {

   "show_env": "true"

 },

 "headers": {

   "Accept": "*/*",

   "Authorization": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkNVLUFESkpFYkg5YlhsMHRwc1FXWXVvNEV3bGt4RlVIYmVKNGNra2FrQ00iLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ3NDUxNDUwNzEsImdyb3VwIjoiYWRtaW4iLCJpYXQiOjE1OTE1NDUwNzEsImlzcyI6ImF1dGhAaXN0aW9pbmFjdGlvbi5pbyIsInN1YiI6IjIxOGQzZmI5LTQ2MjgtNGQyMC05NDNjLTEyNDI4MWM4MGU3YiJ9.MEL9ANwx4kvxkK90cdkUBejn-cLIrACdvGiE9T4RE3F1FRc4et4EZ79s-tbb7OJgnOCkTcvB-Q4V_9WaeAU_kNvzM1rGGh1a0ahQI01Iipt0c6RUlWk1GUr5eUul7xw5MoR-kKDuB-fB0qG2_WQfyiqez6uO9OGJxipTwfhoWJfq_9sZ3p7d8iwJzIcCleTb6ywKmIa4gJb0UhaVcs77HP7KTq9PzTj2adOa2KtfH0BTFjAymZKJVEsV64A_XdNAybiVmEmd8kqTuIbHob-ZT9Mlyl3ER_A6rbIzx6myD9F8m1GIaz2fgtMCJyawuxd_YK4L1cvWhJ2BkbyCtC1znQ",

   "Authorization-Group": "admin",

   "Authorization-Iss": "auth@istioinaction.io",

   "Authorization-Sub": "218d3fb9-4628-4d20-943c-124281c80e7b",

   "Content-Length": "0",

   "Host": "direct-httpbin-simple-http.asmworkshop.io",

   "User-Agent": "curl/7.29.0",

   "X-Asm-Prefer-Tag": "base",

   "X-B3-Parentspanid": "e67a410d8a5aaf9e",

   "X-B3-Sampled": "1",

   "X-B3-Spanid": "8f3049fa4b729cb2",

   "X-B3-Traceid": "6d4cc5da57581e34e67a410d8a5aaf9e",

   "X-Envoy-Attempt-Count": "1",

   "X-Envoy-External-Address": "192.168.0.240",

   "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/simple-http/sa/httpbin;Hash=aa897e5e1f68f47f20baf3071b4473e1a433fc04a6a54e17bfea902d01b656bd;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account",

   "X-Forwarded-For": "56.5.6.7, 72.9.5.6, 98.1.2.3,192.168.0.240",

   "X-Forwarded-Proto": "http",

   "X-Request-Id": "d64d5e70-1d58-99f2-8ef1-3dff2e9eb809"

 },

 "origin": "56.5.6.7, 72.9.5.6, 98.1.2.3,192.168.0.240",

 "url": "http://direct-httpbin-simple-http.asmworkshop.io/get?show_env=true"

}



根据 JWT claim[group] 对流量进行打标


根据 Lua Decode 的 JWT 信息,如果 group = admin,则访问到正常环境;如果 group=user,则转发请求附加 x-asm-traffic-tag=gray,然后根据 VirtualService 规则访问到灰度环境。

apiVersion: networking.istio.io/v1alpha3

kind: EnvoyFilter

metadata:

 name: parse-request-jwt-for-labelling

 namespace: istio-system

spec:

 workloadSelector:

   labels:

     app: istio-ingressgateway

 configPatches:

 - 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:

      name: envoy.lua

      typed_config:

        "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"

        inlineCode: |

         -- Json Parsing based on https://gist.github.com/tylerneylon/59f4bcf316be525b30ab

         -- Base64 decoding based on wikipedia description of 8/6bit encoding.

         -- base64 char array.. note final 2 chars are for RFC4648-URL encoding

         -- as per JWT spec section 2 terminology 'Base64url Encoding'

         local alpha='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'

         -- convert to 6 char long binary string. (max int 64!)

         function toBinaryString(int)

             if int > 64 then

                 error("Bad number "..int.." to convert to binary")

             end        

             local remaining = tonumber(int)

             local bits = ''

             for i = 5, 0, -1 do

                 local pow = 2 ^ i

                 if remaining >= pow then

                     bits = bits .. '1'

                     remaining = remaining - pow

                 else

                     bits = bits .. '0'

                 end

             end

             return bits

         end

         

         function fromBinaryString(bits)

             return tonumber(bits, 2)

         end

         

         function decodeBase64(encoded)

             local bitstr = ''

             local decoded = ''

             -- decode chars into bitstring

             for i = 1, string.len(encoded) do

                 local offset, _ = string.find(alpha, string.sub(encoded, i, i))

                 if offset == nil then

                     error("Bad base64 character " .. string.sub(encoded, i, i))

                 end

                 bitstr = bitstr .. toBinaryString(offset-1)

             end

             -- decode bitstring back to chars

             for i = 1, string.len(bitstr), 8 do

                 decoded = decoded .. string.char(fromBinaryString(string.sub(bitstr, i, i+7)))

             end

             return decoded

         end

         

         -- json handling

         local json = {}

         local function kind_of(obj)

           if type(obj) ~= 'table' then return type(obj) end

           local i = 1

           for _ in pairs(obj) do

             if obj[i] ~= nil then i = i + 1 else return 'table' end

           end

           if i == 1 then return 'table' else return 'array' end

         end

         

         local function escape_str(s)

           local in_char  = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'}

           local out_char = {'\\', '"', '/',  'b',  'f',  'n',  'r',  't'}

           for i, c in ipairs(in_char) do

             s = s:gsub(c, '\\' .. out_char[i])

           end

           return s

         end

         

         -- Returns pos, did_find; there are two cases:

         -- 1. Delimiter found: pos = pos after leading space + delim; did_find = true.

         -- 2. Delimiter not found: pos = pos after leading space;     did_find = false.

         -- This throws an error if err_if_missing is true and the delim is not found.

         local function skip_delim(str, pos, delim, err_if_missing)

           pos = pos + #str:match('^%s*', pos)

           if str:sub(pos, pos) ~= delim then

             if err_if_missing then

               error('Expected ' .. delim .. ' near position ' .. pos)

             end

             return pos, false

           end

           return pos + 1, true

         end

         

         -- Expects the given pos to be the first character after the opening quote.

         -- Returns val, pos; the returned pos is after the closing quote character.

         local function parse_str_val(str, pos, val)

           val = val or ''

           local early_end_error = 'End of input found while parsing string.'

           if pos > #str then error(early_end_error) end

           local c = str:sub(pos, pos)

           if c == '"'  then return val, pos + 1 end

           if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end

           -- We must have a \ character.

           local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'}

           local nextc = str:sub(pos + 1, pos + 1)

           if not nextc then error(early_end_error) end

           return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc))

         end

         

         -- Returns val, pos; the returned pos is after the number's final character.

         local function parse_num_val(str, pos)

           local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)

           local val = tonumber(num_str)

           if not val then error('Error parsing number at position ' .. pos .. '.') end

           return val, pos + #num_str

         end

         

         json.null = {}  -- one-off table to represent the null value.

         

         function json.parse(str, pos, end_delim)

           pos = pos or 1

           if pos > #str then error('Reached unexpected end of input.') end

           local pos = pos + #str:match('^%s*', pos)  -- Skip whitespace.

           local first = str:sub(pos, pos)

           if first == '{' then  -- Parse an object.

             local obj, key, delim_found = {}, true, true

             pos = pos + 1

             while true do

               key, pos = json.parse(str, pos, '}')

               if key == nil then return obj, pos end

               if not delim_found then error('Comma missing between object items.') end

               pos = skip_delim(str, pos, ':', true)  -- true -> error if missing.

               obj[key], pos = json.parse(str, pos)

               pos, delim_found = skip_delim(str, pos, ',')

             end

           elseif first == '[' then  -- Parse an array.

             local arr, val, delim_found = {}, true, true

             pos = pos + 1

             while true do

               val, pos = json.parse(str, pos, ']')

               if val == nil then return arr, pos end

               if not delim_found then error('Comma missing between array items.') end

               arr[#arr + 1] = val

               pos, delim_found = skip_delim(str, pos, ',')

             end

           elseif first == '"' then  -- Parse a string.

             return parse_str_val(str, pos + 1)

           elseif first == '-' or first:match('%d') then  -- Parse a number.

             return parse_num_val(str, pos)

           elseif first == end_delim then  -- End of an object or array.

             return nil, pos + 1

           else  -- Parse true, false, or null.

             local literals = {['true'] = true, ['false'] = false, ['null'] = json.null}

             for lit_str, lit_val in pairs(literals) do

               local lit_end = pos + #lit_str - 1

               if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end

             end

             local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)

             error('Invalid json syntax starting at ' .. pos_info_str)

           end

         end

         

         function decode_jwt(jwt)

           i=0

           result = {}

           for match in (jwt..'.'):gmatch("(.-)%.") do

               result[i]=decodeBase64(match)

               i=i+1

           end

           -- header

           head = json.parse(result[0])

           -- claims

           claims = json.parse(result[1])

           return {head=head,claims=claims}

         end

         

         function add_header(k,v,prefix,headers)

           if "number" == type (k) then

             headers:add(prefix,v)

           else

             headers:add(prefix.."-"..k,v)

           end

         end

         

         function add_table_as_headers(table, prefix, headers)

           for k,v in pairs(table) do

             if "string" == type( v ) then

               add_header(k,v,prefix,headers)

             elseif "table" == type( v ) then

               add_table_as_headers(v,prefix.."-"..k,headers)

             end

           end

         end

         

         function envoy_on_request(request_handle)

           local jwtHeaderName = "Authorization"

           headers = request_handle:headers()

           jwt = headers:get(jwtHeaderName)

           if jwt == nil then

               headers:add("jwt","headernotfound")

           else

               content = decode_jwt(jwt)

               usergroup = content["claims"]["group"]

               if usergroup == "user" then

                 headers:add("x-asm-traffic-tag", "gray")

               end

               add_table_as_headers(content["claims"],jwtHeaderName,headers)

           end

         end




测试 jwt group = admin 时,不打 x-asm-traffic-tag 标签

# ADMIN_TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkNVLUFESkpFYkg5YlhsMHRwc1FXWXVvNEV3bGt4RlVIYmVKNGNra2FrQ00iLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ3NDUxNDUwNzEsImdyb3VwIjoiYWRtaW4iLCJpYXQiOjE1OTE1NDUwNzEsImlzcyI6ImF1dGhAaXN0aW9pbmFjdGlvbi5pbyIsInN1YiI6IjIxOGQzZmI5LTQ2MjgtNGQyMC05NDNjLTEyNDI4MWM4MGU3YiJ9.MEL9ANwx4kvxkK90cdkUBejn-cLIrACdvGiE9T4RE3F1FRc4et4EZ79s-tbb7OJgnOCkTcvB-Q4V_9WaeAU_kNvzM1rGGh1a0ahQI01Iipt0c6RUlWk1GUr5eUul7xw5MoR-kKDuB-fB0qG2_WQfyiqez6uO9OGJxipTwfhoWJfq_9sZ3p7d8iwJzIcCleTb6ywKmIa4gJb0UhaVcs77HP7KTq9PzTj2adOa2KtfH0BTFjAymZKJVEsV64A_XdNAybiVmEmd8kqTuIbHob-ZT9Mlyl3ER_A6rbIzx6myD9F8m1GIaz2fgtMCJyawuxd_YK4L1cvWhJ2BkbyCtC1znQ



# echo $ADMIN_TOKEN | cut -d '.' -f2 | base64 -d

{"exp":4745145071,"group":"admin","iat":1591545071,"iss":"auth@istioinaction.io","sub":"218d3fb9-4628-4d20-943c-124281c80e7b"}



# curl -H "Host: direct-httpbin-simple-http.asmworkshop.io" 192.168.4.143/get?show_env=true -H "Authorization: $ADMIN_TOKEN"

{

 "args": {

   "show_env": "true"

 },

 "headers": {

   "Accept": "*/*",

   "Authorization": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkNVLUFESkpFYkg5YlhsMHRwc1FXWXVvNEV3bGt4RlVIYmVKNGNra2FrQ00iLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ3NDUxNDUwNzEsImdyb3VwIjoiYWRtaW4iLCJpYXQiOjE1OTE1NDUwNzEsImlzcyI6ImF1dGhAaXN0aW9pbmFjdGlvbi5pbyIsInN1YiI6IjIxOGQzZmI5LTQ2MjgtNGQyMC05NDNjLTEyNDI4MWM4MGU3YiJ9.MEL9ANwx4kvxkK90cdkUBejn-cLIrACdvGiE9T4RE3F1FRc4et4EZ79s-tbb7OJgnOCkTcvB-Q4V_9WaeAU_kNvzM1rGGh1a0ahQI01Iipt0c6RUlWk1GUr5eUul7xw5MoR-kKDuB-fB0qG2_WQfyiqez6uO9OGJxipTwfhoWJfq_9sZ3p7d8iwJzIcCleTb6ywKmIa4gJb0UhaVcs77HP7KTq9PzTj2adOa2KtfH0BTFjAymZKJVEsV64A_XdNAybiVmEmd8kqTuIbHob-ZT9Mlyl3ER_A6rbIzx6myD9F8m1GIaz2fgtMCJyawuxd_YK4L1cvWhJ2BkbyCtC1znQ",

   "Authorization-Group": "admin",

   "Authorization-Iss": "auth@istioinaction.io",

   "Authorization-Sub": "218d3fb9-4628-4d20-943c-124281c80e7b",

   "Content-Length": "0",

   "Host": "direct-httpbin-simple-http.asmworkshop.io",

   "User-Agent": "curl/7.29.0",

   "X-Asm-Prefer-Tag": "gray",

   "X-B3-Parentspanid": "ba38f3a6f6e3b4a1",

   "X-B3-Sampled": "1",

   "X-B3-Spanid": "ff276929069963f9",

   "X-B3-Traceid": "c7ead271417ec943ba38f3a6f6e3b4a1",

   "X-Envoy-Attempt-Count": "1",

   "X-Envoy-Internal": "true",

   "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/simple-http/sa/httpbin;Hash=a1b644096de33600b9b59a006afd0a087cc2f2612c85f4f2da8738e3d829f108;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account",

   "X-Forwarded-For": "192.168.0.240",

   "X-Forwarded-Proto": "http",

   "X-Request-Id": "571b89d3-df43-9b75-9221-03824e06dac1"

 },

 "origin": "192.168.0.240",

 "url": "http://direct-httpbin-simple-http.asmworkshop.io/get?show_env=true"

}


测试 jwt group = user 时,打上 x-asm-traffic-tag = gray 标签

# USER_TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkNVLUFESkpFYkg5YlhsMHRwc1FXWXVvNEV3bGt4RlVIYmVKNGNra2FrQ00iLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ3NDUxNDUwMzgsImdyb3VwIjoidXNlciIsImlhdCI6MTU5MTU0NTAzOCwiaXNzIjoiYXV0aEBpc3Rpb2luYWN0aW9uLmlvIiwic3ViIjoiOWI3OTJiNTYtN2RmYS00ZTRiLWE4M2YtZTIwNjc5MTE1ZDc5In0.jNDoRx7SNm8b1xMmPaOEMVgwdnTmXJwD5jjCH9wcGsLisbZGcR6chkirWy1BVzYEQDTf8pDJpY2C3H-aXN3IlAcQ1UqVe5lShIjCMIFTthat3OuNgu-a91csGz6qtQITxsOpMcBinlTYRsUOICcD7UZcLugxK4bpOECohHoEhuASHzlH-FYESDB-JYrxmwXj4xoZ_jIsdpuqz_VYhWp8e0phDNJbB6AHOI3m7OHCsGNcw9Z0cks1cJrgB8JNjRApr9XTNBoEC564PX2ZdzciI9BHoOFAKx4mWWEqW08LDMSZIN5Ui9ppwReSV2ncQOazdStS65T43bZJwgJiIocSCg


echo $USER_TOKEN | cut -d '.' -f2 | base64 -d

{"exp":4745145038,"group":"user","iat":1591545038,"iss":"auth@istioinaction.io","sub":"9b792b56-7dfa-4e4b-a83f-e20679115d79"}


# curl -H "Host: direct-httpbin-simple-http.asmworkshop.io" 192.168.4.143/get?show_env=true -H "Authorization: $USER_TOKEN"

{

 "args": {

   "show_env": "true"

 },

 "headers": {

   "Accept": "*/*",

   "Authorization": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkNVLUFESkpFYkg5YlhsMHRwc1FXWXVvNEV3bGt4RlVIYmVKNGNra2FrQ00iLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ3NDUxNDUwMzgsImdyb3VwIjoidXNlciIsImlhdCI6MTU5MTU0NTAzOCwiaXNzIjoiYXV0aEBpc3Rpb2luYWN0aW9uLmlvIiwic3ViIjoiOWI3OTJiNTYtN2RmYS00ZTRiLWE4M2YtZTIwNjc5MTE1ZDc5In0.jNDoRx7SNm8b1xMmPaOEMVgwdnTmXJwD5jjCH9wcGsLisbZGcR6chkirWy1BVzYEQDTf8pDJpY2C3H-aXN3IlAcQ1UqVe5lShIjCMIFTthat3OuNgu-a91csGz6qtQITxsOpMcBinlTYRsUOICcD7UZcLugxK4bpOECohHoEhuASHzlH-FYESDB-JYrxmwXj4xoZ_jIsdpuqz_VYhWp8e0phDNJbB6AHOI3m7OHCsGNcw9Z0cks1cJrgB8JNjRApr9XTNBoEC564PX2ZdzciI9BHoOFAKx4mWWEqW08LDMSZIN5Ui9ppwReSV2ncQOazdStS65T43bZJwgJiIocSCg",

   "Authorization-Group": "user",

   "Authorization-Iss": "auth@istioinaction.io",

   "Authorization-Sub": "9b792b56-7dfa-4e4b-a83f-e20679115d79",

   "Content-Length": "0",

   "Host": "direct-httpbin-simple-http.asmworkshop.io",

   "User-Agent": "curl/7.29.0",

   "X-Asm-Prefer-Tag": "gray",

   "X-Asm-Traffic-Tag": "gray",

   "X-B3-Parentspanid": "412847ab2f501b48",

   "X-B3-Sampled": "1",

   "X-B3-Spanid": "2dc41eb50c073b38",

   "X-B3-Traceid": "0781bb560d8f47b3412847ab2f501b48",

   "X-Envoy-Attempt-Count": "1",

   "X-Envoy-Internal": "true",

   "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/simple-http/sa/httpbin;Hash=a1b644096de33600b9b59a006afd0a087cc2f2612c85f4f2da8738e3d829f108;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account",

   "X-Forwarded-For": "192.168.0.240",

   "X-Forwarded-Proto": "http",

   "X-Request-Id": "dc591104-1ed4-9770-8a73-f690ffeeba6b"

 },

 "origin": "192.168.0.240",

 "url": "http://direct-httpbin-simple-http.asmworkshop.io/get?show_env=true"

}



测试按标路由


修改 VirtualService 按 x-asm-traffic-tag Header 进行路由:

apiVersion: networking.istio.io/v1alpha3

kind: Gateway

metadata:

 name: lua-simple-http

spec:

 selector:

   istio: ingressgateway

 servers:

 - hosts:

   - lua-simple-http.asmworkshop.io

   port:

     name: http

     number: 80

     protocol: HTTP

---

apiVersion: networking.istio.io/v1alpha3

kind: VirtualService

metadata:

 name: lua-simple-http-a-vs

spec:

 gateways:

 - lua-simple-http

 hosts:

 - lua-simple-http.asmworkshop.io

 - http-a

 http:

 - match:

   - headers:

       x-asm-traffic-tag:

         exact: gray

   route:

   - destination:

       host: http-a

       subset: version-gray

 - route:

   - destination:

       host: http-a

       subset: version-base

---

apiVersion: networking.istio.io/v1alpha3

kind: DestinationRule

metadata:

 name: lua-simple-http-a-dr

spec:

 host: http-a

 subsets:

 - name: version-gray

   labels:

     version: gray

 - name: version-base

   labels:

     version: base



测试

# 不带 Authorization 访问服务,访问到 Base 环境

# curl -H "Host: lua-simple-http.asmworkshop.io" 192.168.4.143

{

 "name": "http-a",

 "uri": "/",

 "type": "HTTP",

 "ip_addresses": [

   "192.168.86.67"

 ],

 "start_time": "2021-10-24T14:39:35.217636",

 "end_time": "2021-10-24T14:39:35.217755",

 "duration": "118.382µs",

 "body": "Web response from http-a-Base",

 "code": 200

}



# 带 Authorization (group=admin)访问服务,访问到 Base 环境

# curl -H "Host: lua-simple-http.asmworkshop.io" 192.168.4.143 -H "Authorization: $ADMIN_TOKEN"

{

 "name": "http-a",

 "uri": "/",

 "type": "HTTP",

 "ip_addresses": [

   "192.168.86.67"

 ],

 "start_time": "2021-10-24T14:39:49.560351",

 "end_time": "2021-10-24T14:39:49.560463",

 "duration": "111.947µs",

 "body": "Web response from http-a-Base",

 "code": 200

}



# 带 Authorization (group=user)访问服务,访问到 Gray 环境

# curl -H "Host: lua-simple-http.asmworkshop.io" 192.168.4.143 -H "Authorization: $USER_TOKEN"

{

 "name": "http-a",

 "uri": "/",

 "type": "HTTP",

 "ip_addresses": [

   "192.168.86.68"

 ],

 "start_time": "2021-10-24T14:39:57.268686",

 "end_time": "2021-10-24T14:39:57.268825",

 "duration": "139.248µs",

 "body": "Web response from http-a-Gray",

 "code": 200

}




目录
相关文章
|
Kubernetes JavaScript API
如何理解 Istio Ingress, 它与 API Gateway 有什么区别?东西流量?南北流量?
这三者都和流量治理密切相关,那么流量治理在过去和现在有什么区别呢?都是如何做的呢? 在学习istio的时候对流量管理加深了理解。什么是东西流量?什么是南北流量?
273 0
|
Kubernetes 负载均衡 网络协议
全网最细,深度解析 Istio Ambient Mesh 流量路径
本文旨在对 Istio Ambient Mesh 的流量路径进行详细解读,力求尽可能清晰地呈现细节,以帮助读者完全理解 Istio Ambient Mesh 中最为关键的部分。
883 15
|
Prometheus Kubernetes Cloud Native
一文带你了解 Istio 流量路由
当我们尝试在 Kubernetes 中使用 NodePort 或 LoadBalancer 类型的服务设施配置进行通信时,Istio 或许是一个非常流行、新兴的开源服务网格产品,其能够用于通信管理、可观察性、错误处理及安全性等。作为微服务架构体系的一部分,为了无需过多地使用重复的逻辑填充每个微服务代码,我们可以利用 Istio 服务网格在一个地方完成所有这些事情。
168 0
|
Kubernetes Shell iOS开发
Istio 网络:深入了解流量和架构
像 Istio 这样的服务网格项目为我们的架构引入了许多功能和优势,包括更安全地管理集群微服务之间的流量、服务发现、请求路由以及服务之间的可靠通信。
242 0
在Istio中实现Redis集群的数据分片读写分离和流量镜像
Redis 是一个高性能的 key-value 存储系统,被广泛用于微服务架构中。如果我们想要使用 Redis 集群模式提供的高级特性,则需要对客户端代码进行改动,这带来了应用升级和维护的一些困难。利用 Istio 和 Envoy ,我们可以在不修改客户端代码的前提下实现客户端无感知的 Redis Cluster 数据分片,并提供读写分离、流量镜像等高级流量管理功能。
|
监控 应用服务中间件 nginx
5个 Istio 访问外部服务流量控制最常用的例子,你知道几个?
5 个 Istio 访问外部服务的流量控制常用例子,强烈建议收藏起来,以备不时之需。
310 0
5个 Istio 访问外部服务流量控制最常用的例子,你知道几个?
|
16天前
|
监控 安全 Cloud Native
云原生安全:Istio在微服务架构中的安全策略与实践
【10月更文挑战第26天】随着云计算的发展,云原生架构成为企业数字化转型的关键。微服务作为其核心组件,虽具备灵活性和可扩展性,但也带来安全挑战。Istio作为开源服务网格,通过双向TLS加密、细粒度访问控制和强大的审计监控功能,有效保障微服务间的通信安全,成为云原生安全的重要工具。
38 2
|
1月前
|
Kubernetes 安全 微服务
使用 Istio 缓解电信 5G IoT 微服务 Pod 架构的安全挑战
使用 Istio 缓解电信 5G IoT 微服务 Pod 架构的安全挑战
53 8
|
1月前
|
Kubernetes 负载均衡 安全
Istio在微服务中释放服务网格的力量
Istio在微服务中释放服务网格的力量
50 4
|
3月前
|
负载均衡 监控 安全
Istio:微服务治理的超级英雄,一键解锁你的服务网格超能力,让管理复杂变简单!
【8月更文挑战第31天】随着云原生技术的发展,微服务架构成为主流,但其复杂性与管理难题也随之增加。Istio作为开源服务网格平台,通过独特的数据平面和控制平面设计,实现了微服务通信的透明管理,简化了治理复杂度。本文将对比Istio与传统微服务管理方法,详细介绍Istio的架构及其工作原理,包括Envoy代理、服务发现、负载均衡、流量管理、安全认证以及监控等功能。Istio不仅简化了微服务治理,还提供了强大的流量控制和安全机制,使开发者能更高效地管理应用。
71 2