├── .gitignore ├── Makefile ├── README.md ├── babel.config.js ├── codeblock ├── accesslog │ ├── json-format-one-line.json │ ├── json-format.json │ ├── mesh-config-custom-json-format.yaml │ ├── mesh-config-custom-text-format.yaml │ ├── mesh-config-json-format.yaml │ └── mesh-config-text-format.yaml ├── envoyfilter │ ├── accesslog │ │ ├── accesslog-print-body.jsonc │ │ ├── accesslog-print-body.yaml │ │ ├── accesslog-print-header-body.jsonc │ │ ├── accesslog-print-header-body.yaml │ │ ├── accesslog-print-header.jsonc │ │ ├── accesslog-print-header.yaml │ │ ├── accesslog-print-request-header-body.jsonc │ │ ├── accesslog-print-request-header-body.yaml │ │ ├── accesslog-print-response-header-body.jsonc │ │ ├── accesslog-print-response-header-body.yaml │ │ ├── enable-accesslog-json-format.yaml │ │ └── enable-accesslog-text-format.yaml │ ├── gzip │ │ ├── enable-gzip-for-public.yaml │ │ └── enable-gzip.yaml │ ├── label-outbound-traffic.yaml │ ├── limit-request │ │ ├── limit-header-and-request-size-for-public.yaml │ │ ├── limit-header-and-request-size.yaml │ │ ├── limit-header-size-for-public.yaml │ │ ├── limit-header-size.yaml │ │ ├── limit-request-size-for-public.yaml │ │ └── limit-request-size.yaml │ ├── preserve-case │ │ ├── preserve-case-all.yaml │ │ ├── preserve-case-request.yaml │ │ └── preserve-case-response.yaml │ ├── ratelimit │ │ ├── local-ratelimit-port.yaml │ │ ├── local-ratelimit-workload.yaml │ │ └── productpage-enable-http-local-rate-limit-proxy-config.yaml │ └── response-request-id.yaml └── istio │ ├── dr-iphash.yaml │ ├── istio-init-crash-log.txt │ └── vs-cors-regex.yaml ├── content ├── README.md ├── ambient │ ├── build.md │ ├── in-depth.md │ └── progress.md ├── appendix │ ├── bug.md │ ├── link.md │ ├── shell.md │ └── yaml.md ├── best-practices │ ├── graceful-shutdown.md │ ├── optimize-performance.md │ ├── set-default-route.md │ └── specify-protocol.md ├── faq │ ├── grpc-not-loadbalancing.md │ ├── headless-svc.md │ ├── listen-any.md │ ├── multicluster.md │ ├── retries-for-non-idempotent-services.md │ ├── sidecar-injection.md │ ├── sidecar-shutdown.md │ ├── sidecar-startup-order.md │ ├── smart-dns.md │ ├── the-case-of-http-header.md │ ├── tracing.md │ ├── uppercase-header-causes-sticky-sessions-to-not-work.md │ └── vs-match-order.md ├── intro │ └── service-governance.md ├── sidebars.ts ├── source │ ├── cni.md │ └── structure.md ├── trick │ ├── customize-proxy-loglevel.md │ ├── debug.md │ ├── header-authorization.md │ ├── hide-server-header.md │ └── multi-version-test-service-with-prism.md ├── troubleshooting │ ├── cases │ │ ├── apollo-on-istio.md │ │ ├── cannot-connect-pod-without-sidecar.md │ │ ├── grpc-without-status-code.md │ │ ├── istio-token-setup-failed-for-volume-istio-token.md │ │ ├── traffic-policy-does-not-take-effect.md │ │ └── using-istio-reserved-port-causes-pod-start-failed.md │ ├── circuit-breaking-not-work.md │ ├── grpc-config-stream-closed.md │ ├── isito-init-crash.md │ ├── locality-lb-not-working.md │ ├── status-code-404.md │ ├── status-code-426.md │ ├── status-code-431.md │ └── virtualservice-not-working.md └── usage │ ├── accesslog │ ├── accesslog-config.md │ ├── accesslog-print-header-body.md │ └── enable-accesslog-for-workload.md │ ├── cors.md │ ├── enable-gzip.md │ ├── iphash.md │ ├── label-traffic.md │ ├── limit-request-size.md │ ├── preserve-case.md │ ├── ratelimit.md │ ├── response-request-id.md │ └── websocket.md ├── docusaurus.config.ts ├── giscus.json ├── package-lock.json ├── package.json ├── src ├── components │ ├── Comment.tsx │ └── FileBlock.tsx ├── css │ └── custom.scss ├── theme │ ├── DocCategoryGeneratedIndexPage │ │ └── index.tsx │ ├── DocItem │ │ └── Layout │ │ │ └── index.tsx │ └── MDXComponents.tsx └── utils │ └── prismDark.ts ├── static ├── img │ └── logo.svg └── manifest.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | start: 4 | npm run start 5 | install: 6 | npm install 7 | outdated: 8 | npm outdated 9 | init: install 10 | git clone --depth=1 git@gitee.com:imroc/istio-guide.git build 11 | gen: 12 | npx docusaurus build --out-dir=./build/out 13 | push: 14 | cd build && git add -A && git commit -m update && git push 15 | update: install gen push 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # istio 实践指南 2 | 3 | ## 关于本书 4 | 5 | 本书为电子书形式,内容为本人多年的云原生与 istio 实战经验进行系统性整理的结果,不废话,纯干货。 6 | 7 | ## 在线阅读 8 | 9 | 地址:https://imroc.cc/istio 10 | 11 | ## 评论与互动 12 | 13 | 本书已集成 [giscus](https://giscus.app/zh-CN) 评论系统,欢迎对感兴趣的文章进行评论与交流。 14 | 15 | ## 贡献 16 | 17 | 本书使用 [docusaurus](https://docusaurus.io/) 构建,已集成自动构建和发布,欢迎 Fork 并 PR 来贡献干货内容 (点击左下角 `编辑此页` 按钮可快速修改文章)。 18 | 19 | ## 许可证 20 | 21 | 您可以使用 [署名 - 非商业性使用 - 相同方式共享 4.0 (CC BY-NC-SA 4.0)](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh) 协议共享。 22 | 23 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /codeblock/accesslog/json-format-one-line.json: -------------------------------------------------------------------------------- 1 | { "start_time": "%START_TIME%", "route_name": "%ROUTE_NAME%", "method": "%REQ(:METHOD)%", "path": "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%", "protocol": "%PROTOCOL%", "response_code": "%RESPONSE_CODE%", "response_flags": "%RESPONSE_FLAGS%", "response_code_details": "%RESPONSE_CODE_DETAILS%", "connection_termination_details": "%CONNECTION_TERMINATION_DETAILS%", "bytes_received": "%BYTES_RECEIVED%", "bytes_sent": "%BYTES_SENT%", "duration": "%DURATION%", "upstream_service_time": "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%", "x_forwarded_for": "%REQ(X-FORWARDED-FOR)%", "user_agent": "%REQ(USER-AGENT)%", "request_id": "%REQ(X-REQUEST-ID)%", "authority": "%REQ(:AUTHORITY)%", "upstream_host": "%UPSTREAM_HOST%", "upstream_cluster": "%UPSTREAM_CLUSTER%", "upstream_local_address": "%UPSTREAM_LOCAL_ADDRESS%", "downstream_local_address": "%DOWNSTREAM_LOCAL_ADDRESS%", "downstream_remote_address": "%DOWNSTREAM_REMOTE_ADDRESS%", "requested_server_name": "%REQUESTED_SERVER_NAME%", "upstream_transport_failure_reason": "%UPSTREAM_TRANSPORT_FAILURE_REASON%" } 2 | -------------------------------------------------------------------------------- /codeblock/accesslog/json-format.json: -------------------------------------------------------------------------------- 1 | { 2 | "start_time": "%START_TIME%", 3 | "route_name": "%ROUTE_NAME%", 4 | "method": "%REQ(:METHOD)%", 5 | "path": "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%", 6 | "protocol": "%PROTOCOL%", 7 | "response_code": "%RESPONSE_CODE%", 8 | "response_flags": "%RESPONSE_FLAGS%", 9 | "response_code_details": "%RESPONSE_CODE_DETAILS%", 10 | "connection_termination_details": "%CONNECTION_TERMINATION_DETAILS%", 11 | "bytes_received": "%BYTES_RECEIVED%", 12 | "bytes_sent": "%BYTES_SENT%", 13 | "duration": "%DURATION%", 14 | "upstream_service_time": "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%", 15 | "x_forwarded_for": "%REQ(X-FORWARDED-FOR)%", 16 | "user_agent": "%REQ(USER-AGENT)%", 17 | "request_id": "%REQ(X-REQUEST-ID)%", 18 | "authority": "%REQ(:AUTHORITY)%", 19 | "upstream_host": "%UPSTREAM_HOST%", 20 | "upstream_cluster": "%UPSTREAM_CLUSTER%", 21 | "upstream_local_address": "%UPSTREAM_LOCAL_ADDRESS%", 22 | "downstream_local_address": "%DOWNSTREAM_LOCAL_ADDRESS%", 23 | "downstream_remote_address": "%DOWNSTREAM_REMOTE_ADDRESS%", 24 | "requested_server_name": "%REQUESTED_SERVER_NAME%", 25 | "upstream_transport_failure_reason": "%UPSTREAM_TRANSPORT_FAILURE_REASON%" 26 | } 27 | -------------------------------------------------------------------------------- /codeblock/accesslog/mesh-config-custom-json-format.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: istio 5 | namespace: istio-system 6 | data: 7 | mesh: | 8 | # highlight-start 9 | accessLogFile: /dev/stdout 10 | accessLogFormat: | 11 | { "start_time": "%START_TIME%", "route_name": "%ROUTE_NAME%", "method": "%REQ(:METHOD)%", "path": "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%", "protocol": "%PROTOCOL%", "response_code": "%RESPONSE_CODE%", "response_flags": "%RESPONSE_FLAGS%", "response_code_details": "%RESPONSE_CODE_DETAILS%", "connection_termination_details": "%CONNECTION_TERMINATION_DETAILS%", "bytes_received": "%BYTES_RECEIVED%", "bytes_sent": "%BYTES_SENT%", "duration": "%DURATION%", "upstream_service_time": "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%", "x_forwarded_for": "%REQ(X-FORWARDED-FOR)%", "user_agent": "%REQ(USER-AGENT)%", "request_id": "%REQ(X-REQUEST-ID)%", "authority": "%REQ(:AUTHORITY)%", "upstream_host": "%UPSTREAM_HOST%", "upstream_cluster": "%UPSTREAM_CLUSTER%", "upstream_local_address": "%UPSTREAM_LOCAL_ADDRESS%", "downstream_local_address": "%DOWNSTREAM_LOCAL_ADDRESS%", "downstream_remote_address": "%DOWNSTREAM_REMOTE_ADDRESS%", "requested_server_name": "%REQUESTED_SERVER_NAME%", "upstream_transport_failure_reason": "%UPSTREAM_TRANSPORT_FAILURE_REASON%" } 12 | # highlight-end 13 | -------------------------------------------------------------------------------- /codeblock/accesslog/mesh-config-custom-text-format.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: istio 5 | namespace: istio-system 6 | data: 7 | mesh: | 8 | # highlight-start 9 | accessLogFile: /dev/stdout 10 | accessLogFormat: | 11 | [%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%" %RESPONSE_CODE% %RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS% %CONNECTION_TERMINATION_DETAILS% "%UPSTREAM_TRANSPORT_FAILURE_REASON%" %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%" "%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%" %UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% %REQUESTED_SERVER_NAME% %ROUTE_NAME% 12 | # highlight-end 13 | -------------------------------------------------------------------------------- /codeblock/accesslog/mesh-config-json-format.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: istio 5 | namespace: istio-system 6 | data: 7 | mesh: | 8 | # highlight-start 9 | accessLogEncoding: JSON 10 | accessLogFile: /dev/stdout 11 | # highlight-end 12 | -------------------------------------------------------------------------------- /codeblock/accesslog/mesh-config-text-format.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: istio 5 | namespace: istio-system 6 | data: 7 | mesh: | 8 | # highlight-start 9 | accessLogEncoding: TEXT 10 | accessLogFile: /dev/stdout 11 | # highlight-end 12 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/accesslog/accesslog-print-body.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "authority": "nginx.test.svc.cluster.local", 3 | "response_code": 404, 4 | "method": "GET", 5 | "upstream_service_time": "1", 6 | "user_agent": "curl/7.85.0", 7 | "bytes_received": 0, 8 | "start_time": "2023-10-08T06:19:16.705Z", 9 | "downstream_local_address": "172.16.244.170:80", 10 | "route_name": "default", 11 | "duration": 1, 12 | "response_code_details": "via_upstream", 13 | "upstream_host": "172.16.0.236:80", 14 | "upstream_cluster": "outbound|80||nignx.test.svc.cluster.local", 15 | "upstream_transport_failure_reason": null, 16 | "x_forwarded_for": null, 17 | "request_id": "19c42034-0f03-4195-ab33-d4a558ca1de4", 18 | "upstream_local_address": "172.16.0.237:35624", 19 | "requested_server_name": null, 20 | // highlight-next-line 21 | "request_body": "hello", 22 | "protocol": "HTTP/1.1", 23 | // highlight-next-line 24 | "response_body": "world", 25 | "connection_termination_details": null, 26 | "bytes_sent": 10480, 27 | "path": "/test", 28 | "downstream_remote_address": "172.16.0.237:44838", 29 | "response_flags": "-" 30 | } 31 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/accesslog/accesslog-print-body.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: accesslog-print-body 5 | # highlight-next-line 6 | namespace: test # 只为 test 命名空间开启 accesslog,若改为 istio-system 表示作用于所有命名空间 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 通常打印 body 用于调试,可指定改配置只作用于指定 workload,避免影响其它不需要调试的 workload 的性能 10 | labels: 11 | app: nginx 12 | # highlight-end 13 | configPatches: 14 | - applyTo: NETWORK_FILTER 15 | match: 16 | listener: 17 | filterChain: 18 | filter: 19 | name: "envoy.filters.network.http_connection_manager" 20 | patch: 21 | operation: MERGE 22 | value: 23 | typed_config: 24 | "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" 25 | access_log: 26 | - name: envoy.access_loggers.file 27 | typed_config: 28 | "@type": "type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog" 29 | path: /dev/stdout 30 | log_format: 31 | json_format: 32 | start_time: "%START_TIME%" 33 | route_name: "%ROUTE_NAME%" 34 | method: "%REQ(:METHOD)%" 35 | path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%" 36 | protocol: "%PROTOCOL%" 37 | response_code: "%RESPONSE_CODE%" 38 | response_flags: "%RESPONSE_FLAGS%" 39 | response_code_details: "%RESPONSE_CODE_DETAILS%" 40 | connection_termination_details: "%CONNECTION_TERMINATION_DETAILS%" 41 | bytes_received: "%BYTES_RECEIVED%" 42 | bytes_sent: "%BYTES_SENT%" 43 | duration: "%DURATION%" 44 | upstream_service_time: "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%" 45 | x_forwarded_for: "%REQ(X-FORWARDED-FOR)%" 46 | user_agent: "%REQ(USER-AGENT)%" 47 | request_id: "%REQ(X-REQUEST-ID)%" 48 | authority: "%REQ(:AUTHORITY)%" 49 | upstream_host: "%UPSTREAM_HOST%" 50 | upstream_cluster: "%UPSTREAM_CLUSTER%" 51 | upstream_local_address: "%UPSTREAM_LOCAL_ADDRESS%" 52 | downstream_local_address: "%DOWNSTREAM_LOCAL_ADDRESS%" 53 | downstream_remote_address: "%DOWNSTREAM_REMOTE_ADDRESS%" 54 | requested_server_name: "%REQUESTED_SERVER_NAME%" 55 | upstream_transport_failure_reason: "%UPSTREAM_TRANSPORT_FAILURE_REASON%" 56 | # highlight-start 57 | request_body: "%DYNAMIC_METADATA(envoy.lua:request_body)%" 58 | response_body: "%DYNAMIC_METADATA(envoy.lua:response_body)%" 59 | # highlight-end 60 | - applyTo: HTTP_FILTER 61 | match: 62 | context: ANY 63 | listener: 64 | filterChain: 65 | filter: 66 | name: "envoy.filters.network.http_connection_manager" 67 | subFilter: 68 | name: "envoy.filters.http.router" 69 | patch: 70 | operation: INSERT_BEFORE 71 | value: 72 | name: envoy.lua 73 | typed_config: 74 | "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua" 75 | # highlight-start 76 | inlineCode: | 77 | function envoy_on_request(request_handle) 78 | local request_body_buffer = request_handle:body() 79 | if(request_body_buffer == nil) 80 | then 81 | request_handle:streamInfo():dynamicMetadata():set("envoy.lua", "request_body", "-") 82 | else 83 | local request_body_data = request_body_buffer:getBytes(0, request_body_buffer:length()) 84 | request_handle:streamInfo():dynamicMetadata():set("envoy.lua", "request_body", request_body_data) 85 | end 86 | end 87 | function envoy_on_response(response_handle) 88 | local response_body_buffer = response_handle:body() 89 | if(response_body_buffer == nil) 90 | then 91 | response_handle:streamInfo():dynamicMetadata():set("envoy.lua", "response_body", "-") 92 | else 93 | local response_body_data = response_body_buffer:getBytes(0, response_body_buffer:length()) 94 | response_handle:streamInfo():dynamicMetadata():set("envoy.lua", "response_body", response_body_data) 95 | end 96 | end 97 | # highlight-end 98 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/accesslog/accesslog-print-header-body.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "authority": "nginx.test.svc.cluster.local", 3 | "response_code": 404, 4 | "method": "GET", 5 | "upstream_service_time": "1", 6 | "user_agent": "curl/7.85.0", 7 | "bytes_received": 0, 8 | "start_time": "2023-10-08T06:19:16.705Z", 9 | "downstream_local_address": "172.16.244.170:80", 10 | "route_name": "default", 11 | "duration": 1, 12 | "response_code_details": "via_upstream", 13 | "upstream_host": "172.16.0.236:80", 14 | "upstream_cluster": "outbound|80||nignx.test.svc.cluster.local", 15 | "upstream_transport_failure_reason": null, 16 | "x_forwarded_for": null, 17 | "request_id": "19c42034-0f03-4195-ab33-d4a558ca1de4", 18 | // highlight-start 19 | "response_headers": { 20 | "date": "Sun, 08 Oct 2023 06:19:16 GMT", 21 | "server": "nginx/1.23.4", 22 | "content-length": "10480", 23 | "x-envoy-upstream-service-time": "1", 24 | ":status": "404", 25 | "content-type": "text/html", 26 | "etag": "\"65224347-28f0\"", 27 | "connection": "keep-alive" 28 | }, 29 | // highlight-end 30 | "upstream_local_address": "172.16.0.237:35624", 31 | "requested_server_name": null, 32 | // highlight-next-line 33 | "request_body": "hello", 34 | "protocol": "HTTP/1.1", 35 | // highlight-next-line 36 | "response_body": "world", 37 | "connection_termination_details": null, 38 | "bytes_sent": 10480, 39 | "path": "/test", 40 | "downstream_remote_address": "172.16.0.237:44838", 41 | // highlight-start 42 | "request_headers": { 43 | "x-envoy-peer-metadata": "ChsKDkFQUF9DT05UQUlORVJTEgkaB3Rvb2xib3gKGgoKQ0xVU1RFUl9JRBIMGgpLdWJlcm5ldGVzCh4KDElOU1RBTkNFX0lQUxIOGgwxNzIuMTYuMC4yMzcKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE4LjMKywEKBkxBQkVMUxLAASq9AQoQCgNhcHASCRoHdG9vbGJveAokChpuZXR3b3JraW5nLmlzdGlvLmlvL3R1bm5lbBIGGgRodHRwCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KLAofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIJGgd0b29sYm94Ci8KI3NlcnZpY2UuaXN0aW8uaW8vY2Fub25pY2FsLXJldmlzaW9uEggaBmxhdGVzdAoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKIgoETkFNRRIaGhh0b29sYm94LTY5ODk1OWY1YzctdGJoZmcKGAoJTkFNRVNQQUNFEgsaCW1lc2gtdGVzdApNCgVPV05FUhJEGkJrdWJlcm5ldGVzOi8vYXBpcy9hcHBzL3YxL25hbWVzcGFjZXMvbWVzaC10ZXN0L2RlcGxveW1lbnRzL3Rvb2xib3gKFwoRUExBVEZPUk1fTUVUQURBVEESAioAChoKDVdPUktMT0FEX05BTUUSCRoHdG9vbGJveA==", 44 | "x-envoy-decorator-operation": "nginx.test.svc.cluster.local:80/*", 45 | ":scheme": "http", 46 | "user-agent": "curl/7.85.0", 47 | "x-request-id": "19c42034-0f03-4195-ab33-d4a558ca1de4", 48 | ":method": "GET", 49 | "x-envoy-peer-metadata-id": "sidecar~172.16.0.237~toolbox-698959f5c7-tbhfg.test~test.svc.cluster.local", 50 | "accept": "*/*", 51 | ":authority": "nginx.test.svc.cluster.local", 52 | "x-forwarded-proto": "http", 53 | ":path": "/test" 54 | }, 55 | // highlight-end 56 | "response_flags": "-" 57 | } 58 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/accesslog/accesslog-print-header-body.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: accesslog-print-header-body 5 | # highlight-next-line 6 | namespace: test # 只为 test 命名空间开启 accesslog,若改为 istio-system 表示作用于所有命名空间 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 通常打印 header 和 body 用于调试,可指定改配置只作用于指定 workload,避免影响其它不需要调试的 workload 的性能 10 | labels: 11 | app: nginx 12 | # highlight-end 13 | configPatches: 14 | - applyTo: NETWORK_FILTER 15 | match: 16 | listener: 17 | filterChain: 18 | filter: 19 | name: "envoy.filters.network.http_connection_manager" 20 | patch: 21 | operation: MERGE 22 | value: 23 | typed_config: 24 | "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" 25 | access_log: 26 | - name: envoy.access_loggers.file 27 | typed_config: 28 | "@type": "type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog" 29 | path: /dev/stdout 30 | log_format: 31 | json_format: 32 | start_time: "%START_TIME%" 33 | route_name: "%ROUTE_NAME%" 34 | method: "%REQ(:METHOD)%" 35 | path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%" 36 | protocol: "%PROTOCOL%" 37 | response_code: "%RESPONSE_CODE%" 38 | response_flags: "%RESPONSE_FLAGS%" 39 | response_code_details: "%RESPONSE_CODE_DETAILS%" 40 | connection_termination_details: "%CONNECTION_TERMINATION_DETAILS%" 41 | bytes_received: "%BYTES_RECEIVED%" 42 | bytes_sent: "%BYTES_SENT%" 43 | duration: "%DURATION%" 44 | upstream_service_time: "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%" 45 | x_forwarded_for: "%REQ(X-FORWARDED-FOR)%" 46 | user_agent: "%REQ(USER-AGENT)%" 47 | request_id: "%REQ(X-REQUEST-ID)%" 48 | authority: "%REQ(:AUTHORITY)%" 49 | upstream_host: "%UPSTREAM_HOST%" 50 | upstream_cluster: "%UPSTREAM_CLUSTER%" 51 | upstream_local_address: "%UPSTREAM_LOCAL_ADDRESS%" 52 | downstream_local_address: "%DOWNSTREAM_LOCAL_ADDRESS%" 53 | downstream_remote_address: "%DOWNSTREAM_REMOTE_ADDRESS%" 54 | requested_server_name: "%REQUESTED_SERVER_NAME%" 55 | upstream_transport_failure_reason: "%UPSTREAM_TRANSPORT_FAILURE_REASON%" 56 | # highlight-start 57 | request_headers: "%DYNAMIC_METADATA(envoy.lua:request_headers)%" 58 | request_body: "%DYNAMIC_METADATA(envoy.lua:request_body)%" 59 | response_headers: "%DYNAMIC_METADATA(envoy.lua:response_headers)%" 60 | response_body: "%DYNAMIC_METADATA(envoy.lua:response_body)%" 61 | # highlight-end 62 | - applyTo: HTTP_FILTER 63 | match: 64 | context: ANY 65 | listener: 66 | filterChain: 67 | filter: 68 | name: "envoy.filters.network.http_connection_manager" 69 | subFilter: 70 | name: "envoy.filters.http.router" 71 | patch: 72 | operation: INSERT_BEFORE 73 | value: 74 | name: envoy.lua 75 | typed_config: 76 | "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua" 77 | # highlight-start 78 | inlineCode: | 79 | function envoy_on_request(request_handle) 80 | local headers = request_handle:headers() 81 | local headers_map = {} 82 | for key, value in pairs(headers) do 83 | headers_map[key] = value 84 | end 85 | request_handle:streamInfo():dynamicMetadata():set("envoy.lua", "request_headers", headers_map) 86 | local request_body_buffer = request_handle:body() 87 | if(request_body_buffer == nil) 88 | then 89 | request_handle:streamInfo():dynamicMetadata():set("envoy.lua", "request_body", "-") 90 | else 91 | local request_body_data = request_body_buffer:getBytes(0, request_body_buffer:length()) 92 | request_handle:streamInfo():dynamicMetadata():set("envoy.lua", "request_body", request_body_data) 93 | end 94 | end 95 | function envoy_on_response(response_handle) 96 | local headers = response_handle:headers() 97 | local headers_map = {} 98 | for key, value in pairs(headers) do 99 | headers_map[key] = value 100 | end 101 | response_handle:streamInfo():dynamicMetadata():set("envoy.lua","response_headers", headers_map) 102 | local response_body_buffer = response_handle:body() 103 | if(response_body_buffer == nil) 104 | then 105 | response_handle:streamInfo():dynamicMetadata():set("envoy.lua", "response_body", "-") 106 | else 107 | local response_body_data = response_body_buffer:getBytes(0, response_body_buffer:length()) 108 | response_handle:streamInfo():dynamicMetadata():set("envoy.lua", "response_body", response_body_data) 109 | end 110 | end 111 | # highlight-end 112 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/accesslog/accesslog-print-header.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "authority": "nginx.test.svc.cluster.local", 3 | "response_code": 404, 4 | "method": "GET", 5 | "upstream_service_time": "1", 6 | "user_agent": "curl/7.85.0", 7 | "bytes_received": 0, 8 | "start_time": "2023-10-08T06:19:16.705Z", 9 | "downstream_local_address": "172.16.244.170:80", 10 | "route_name": "default", 11 | "duration": 1, 12 | "response_code_details": "via_upstream", 13 | "upstream_host": "172.16.0.236:80", 14 | "upstream_cluster": "outbound|80||nignx.test.svc.cluster.local", 15 | "upstream_transport_failure_reason": null, 16 | "x_forwarded_for": null, 17 | "request_id": "19c42034-0f03-4195-ab33-d4a558ca1de4", 18 | // highlight-start 19 | "response_headers": { 20 | "date": "Sun, 08 Oct 2023 06:19:16 GMT", 21 | "server": "nginx/1.23.4", 22 | "content-length": "10480", 23 | "x-envoy-upstream-service-time": "1", 24 | ":status": "404", 25 | "content-type": "text/html", 26 | "etag": "\"65224347-28f0\"", 27 | "connection": "keep-alive" 28 | }, 29 | // highlight-end 30 | "upstream_local_address": "172.16.0.237:35624", 31 | "requested_server_name": null, 32 | "protocol": "HTTP/1.1", 33 | "connection_termination_details": null, 34 | "bytes_sent": 10480, 35 | "path": "/test", 36 | "downstream_remote_address": "172.16.0.237:44838", 37 | // highlight-start 38 | "request_headers": { 39 | "x-envoy-peer-metadata": "ChsKDkFQUF9DT05UQUlORVJTEgkaB3Rvb2xib3gKGgoKQ0xVU1RFUl9JRBIMGgpLdWJlcm5ldGVzCh4KDElOU1RBTkNFX0lQUxIOGgwxNzIuMTYuMC4yMzcKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE4LjMKywEKBkxBQkVMUxLAASq9AQoQCgNhcHASCRoHdG9vbGJveAokChpuZXR3b3JraW5nLmlzdGlvLmlvL3R1bm5lbBIGGgRodHRwCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KLAofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIJGgd0b29sYm94Ci8KI3NlcnZpY2UuaXN0aW8uaW8vY2Fub25pY2FsLXJldmlzaW9uEggaBmxhdGVzdAoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKIgoETkFNRRIaGhh0b29sYm94LTY5ODk1OWY1YzctdGJoZmcKGAoJTkFNRVNQQUNFEgsaCW1lc2gtdGVzdApNCgVPV05FUhJEGkJrdWJlcm5ldGVzOi8vYXBpcy9hcHBzL3YxL25hbWVzcGFjZXMvbWVzaC10ZXN0L2RlcGxveW1lbnRzL3Rvb2xib3gKFwoRUExBVEZPUk1fTUVUQURBVEESAioAChoKDVdPUktMT0FEX05BTUUSCRoHdG9vbGJveA==", 40 | "x-envoy-decorator-operation": "nginx.test.svc.cluster.local:80/*", 41 | ":scheme": "http", 42 | "user-agent": "curl/7.85.0", 43 | "x-request-id": "19c42034-0f03-4195-ab33-d4a558ca1de4", 44 | ":method": "GET", 45 | "x-envoy-peer-metadata-id": "sidecar~172.16.0.237~toolbox-698959f5c7-tbhfg.test~test.svc.cluster.local", 46 | "accept": "*/*", 47 | ":authority": "nginx.test.svc.cluster.local", 48 | "x-forwarded-proto": "http", 49 | ":path": "/test" 50 | }, 51 | // highlight-end 52 | "response_flags": "-" 53 | } 54 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/accesslog/accesslog-print-header.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: accesslog-print-header 5 | # highlight-next-line 6 | namespace: test # 只为 test 命名空间开启 accesslog,若改为 istio-system 表示作用于所有命名空间 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 通常打印 header 用于调试,可指定改配置只作用于指定 workload,避免影响其它不需要调试的 workload 的性能 10 | labels: 11 | app: nginx 12 | # highlight-end 13 | configPatches: 14 | - applyTo: NETWORK_FILTER 15 | match: 16 | listener: 17 | filterChain: 18 | filter: 19 | name: "envoy.filters.network.http_connection_manager" 20 | patch: 21 | operation: MERGE 22 | value: 23 | typed_config: 24 | "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" 25 | access_log: 26 | - name: envoy.access_loggers.file 27 | typed_config: 28 | "@type": "type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog" 29 | path: /dev/stdout 30 | log_format: 31 | json_format: 32 | start_time: "%START_TIME%" 33 | route_name: "%ROUTE_NAME%" 34 | method: "%REQ(:METHOD)%" 35 | path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%" 36 | protocol: "%PROTOCOL%" 37 | response_code: "%RESPONSE_CODE%" 38 | response_flags: "%RESPONSE_FLAGS%" 39 | response_code_details: "%RESPONSE_CODE_DETAILS%" 40 | connection_termination_details: "%CONNECTION_TERMINATION_DETAILS%" 41 | bytes_received: "%BYTES_RECEIVED%" 42 | bytes_sent: "%BYTES_SENT%" 43 | duration: "%DURATION%" 44 | upstream_service_time: "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%" 45 | x_forwarded_for: "%REQ(X-FORWARDED-FOR)%" 46 | user_agent: "%REQ(USER-AGENT)%" 47 | request_id: "%REQ(X-REQUEST-ID)%" 48 | authority: "%REQ(:AUTHORITY)%" 49 | upstream_host: "%UPSTREAM_HOST%" 50 | upstream_cluster: "%UPSTREAM_CLUSTER%" 51 | upstream_local_address: "%UPSTREAM_LOCAL_ADDRESS%" 52 | downstream_local_address: "%DOWNSTREAM_LOCAL_ADDRESS%" 53 | downstream_remote_address: "%DOWNSTREAM_REMOTE_ADDRESS%" 54 | requested_server_name: "%REQUESTED_SERVER_NAME%" 55 | upstream_transport_failure_reason: "%UPSTREAM_TRANSPORT_FAILURE_REASON%" 56 | # highlight-start 57 | request_headers: "%DYNAMIC_METADATA(envoy.lua:request_headers)%" 58 | response_headers: "%DYNAMIC_METADATA(envoy.lua:response_headers)%" 59 | # highlight-end 60 | - applyTo: HTTP_FILTER 61 | match: 62 | context: ANY 63 | listener: 64 | filterChain: 65 | filter: 66 | name: "envoy.filters.network.http_connection_manager" 67 | subFilter: 68 | name: "envoy.filters.http.router" 69 | patch: 70 | operation: INSERT_BEFORE 71 | value: 72 | name: envoy.lua 73 | typed_config: 74 | "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua" 75 | # highlight-start 76 | inlineCode: | 77 | function envoy_on_request(request_handle) 78 | local headers = request_handle:headers() 79 | local headers_map = {} 80 | for key, value in pairs(headers) do 81 | headers_map[key] = value 82 | end 83 | request_handle:streamInfo():dynamicMetadata():set("envoy.lua", "request_headers", headers_map) 84 | end 85 | function envoy_on_response(response_handle) 86 | local headers = response_handle:headers() 87 | local headers_map = {} 88 | for key, value in pairs(headers) do 89 | headers_map[key] = value 90 | end 91 | response_handle:streamInfo():dynamicMetadata():set("envoy.lua","response_headers", headers_map) 92 | end 93 | # highlight-end 94 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/accesslog/accesslog-print-request-header-body.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "authority": "nginx.test.svc.cluster.local", 3 | "response_code": 404, 4 | "method": "GET", 5 | "upstream_service_time": "1", 6 | "user_agent": "curl/7.85.0", 7 | "bytes_received": 0, 8 | "start_time": "2023-10-08T06:19:16.705Z", 9 | "downstream_local_address": "172.16.244.170:80", 10 | "route_name": "default", 11 | "duration": 1, 12 | "response_code_details": "via_upstream", 13 | "upstream_host": "172.16.0.236:80", 14 | "upstream_cluster": "outbound|80||nignx.test.svc.cluster.local", 15 | "upstream_transport_failure_reason": null, 16 | "x_forwarded_for": null, 17 | "request_id": "19c42034-0f03-4195-ab33-d4a558ca1de4", 18 | "upstream_local_address": "172.16.0.237:35624", 19 | "requested_server_name": null, 20 | // highlight-next-line 21 | "request_body": "hello", 22 | "protocol": "HTTP/1.1", 23 | "connection_termination_details": null, 24 | "bytes_sent": 10480, 25 | "path": "/test", 26 | "downstream_remote_address": "172.16.0.237:44838", 27 | // highlight-start 28 | "request_headers": { 29 | "x-envoy-peer-metadata": "ChsKDkFQUF9DT05UQUlORVJTEgkaB3Rvb2xib3gKGgoKQ0xVU1RFUl9JRBIMGgpLdWJlcm5ldGVzCh4KDElOU1RBTkNFX0lQUxIOGgwxNzIuMTYuMC4yMzcKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE4LjMKywEKBkxBQkVMUxLAASq9AQoQCgNhcHASCRoHdG9vbGJveAokChpuZXR3b3JraW5nLmlzdGlvLmlvL3R1bm5lbBIGGgRodHRwCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KLAofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIJGgd0b29sYm94Ci8KI3NlcnZpY2UuaXN0aW8uaW8vY2Fub25pY2FsLXJldmlzaW9uEggaBmxhdGVzdAoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKIgoETkFNRRIaGhh0b29sYm94LTY5ODk1OWY1YzctdGJoZmcKGAoJTkFNRVNQQUNFEgsaCW1lc2gtdGVzdApNCgVPV05FUhJEGkJrdWJlcm5ldGVzOi8vYXBpcy9hcHBzL3YxL25hbWVzcGFjZXMvbWVzaC10ZXN0L2RlcGxveW1lbnRzL3Rvb2xib3gKFwoRUExBVEZPUk1fTUVUQURBVEESAioAChoKDVdPUktMT0FEX05BTUUSCRoHdG9vbGJveA==", 30 | "x-envoy-decorator-operation": "nginx.test.svc.cluster.local:80/*", 31 | ":scheme": "http", 32 | "user-agent": "curl/7.85.0", 33 | "x-request-id": "19c42034-0f03-4195-ab33-d4a558ca1de4", 34 | ":method": "GET", 35 | "x-envoy-peer-metadata-id": "sidecar~172.16.0.237~toolbox-698959f5c7-tbhfg.test~test.svc.cluster.local", 36 | "accept": "*/*", 37 | ":authority": "nginx.test.svc.cluster.local", 38 | "x-forwarded-proto": "http", 39 | ":path": "/test" 40 | }, 41 | // highlight-end 42 | "response_flags": "-" 43 | } 44 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/accesslog/accesslog-print-request-header-body.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: accesslog-print-request-header-body 5 | # highlight-next-line 6 | namespace: test # 只为 test 命名空间开启 accesslog,若改为 istio-system 表示作用于所有命名空间 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 通常打印 header 和 body 用于调试,可指定改配置只作用于指定 workload,避免影响其它不需要调试的 workload 的性能 10 | labels: 11 | app: nginx 12 | # highlight-end 13 | configPatches: 14 | - applyTo: NETWORK_FILTER 15 | match: 16 | listener: 17 | filterChain: 18 | filter: 19 | name: "envoy.filters.network.http_connection_manager" 20 | patch: 21 | operation: MERGE 22 | value: 23 | typed_config: 24 | "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" 25 | access_log: 26 | - name: envoy.access_loggers.file 27 | typed_config: 28 | "@type": "type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog" 29 | path: /dev/stdout 30 | log_format: 31 | json_format: 32 | start_time: "%START_TIME%" 33 | route_name: "%ROUTE_NAME%" 34 | method: "%REQ(:METHOD)%" 35 | path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%" 36 | protocol: "%PROTOCOL%" 37 | response_code: "%RESPONSE_CODE%" 38 | response_flags: "%RESPONSE_FLAGS%" 39 | response_code_details: "%RESPONSE_CODE_DETAILS%" 40 | connection_termination_details: "%CONNECTION_TERMINATION_DETAILS%" 41 | bytes_received: "%BYTES_RECEIVED%" 42 | bytes_sent: "%BYTES_SENT%" 43 | duration: "%DURATION%" 44 | upstream_service_time: "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%" 45 | x_forwarded_for: "%REQ(X-FORWARDED-FOR)%" 46 | user_agent: "%REQ(USER-AGENT)%" 47 | request_id: "%REQ(X-REQUEST-ID)%" 48 | authority: "%REQ(:AUTHORITY)%" 49 | upstream_host: "%UPSTREAM_HOST%" 50 | upstream_cluster: "%UPSTREAM_CLUSTER%" 51 | upstream_local_address: "%UPSTREAM_LOCAL_ADDRESS%" 52 | downstream_local_address: "%DOWNSTREAM_LOCAL_ADDRESS%" 53 | downstream_remote_address: "%DOWNSTREAM_REMOTE_ADDRESS%" 54 | requested_server_name: "%REQUESTED_SERVER_NAME%" 55 | upstream_transport_failure_reason: "%UPSTREAM_TRANSPORT_FAILURE_REASON%" 56 | # highlight-start 57 | request_headers: "%DYNAMIC_METADATA(envoy.lua:request_headers)%" 58 | request_body: "%DYNAMIC_METADATA(envoy.lua:request_body)%" 59 | # highlight-end 60 | - applyTo: HTTP_FILTER 61 | match: 62 | context: ANY 63 | listener: 64 | filterChain: 65 | filter: 66 | name: "envoy.filters.network.http_connection_manager" 67 | subFilter: 68 | name: "envoy.filters.http.router" 69 | patch: 70 | operation: INSERT_BEFORE 71 | value: 72 | name: envoy.lua 73 | typed_config: 74 | "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua" 75 | # highlight-start 76 | inlineCode: | 77 | function envoy_on_request(request_handle) 78 | local headers = request_handle:headers() 79 | local headers_map = {} 80 | for key, value in pairs(headers) do 81 | headers_map[key] = value 82 | end 83 | request_handle:streamInfo():dynamicMetadata():set("envoy.lua", "request_headers", headers_map) 84 | local request_body_buffer = request_handle:body() 85 | if(request_body_buffer == nil) 86 | then 87 | request_handle:streamInfo():dynamicMetadata():set("envoy.lua", "request_body", "-") 88 | else 89 | local request_body_data = request_body_buffer:getBytes(0, request_body_buffer:length()) 90 | request_handle:streamInfo():dynamicMetadata():set("envoy.lua", "request_body", request_body_data) 91 | end 92 | end 93 | # highlight-end 94 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/accesslog/accesslog-print-response-header-body.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "authority": "nginx.test.svc.cluster.local", 3 | "response_code": 404, 4 | "method": "GET", 5 | "upstream_service_time": "1", 6 | "user_agent": "curl/7.85.0", 7 | "bytes_received": 0, 8 | "start_time": "2023-10-08T06:19:16.705Z", 9 | "downstream_local_address": "172.16.244.170:80", 10 | "route_name": "default", 11 | "duration": 1, 12 | "response_code_details": "via_upstream", 13 | "upstream_host": "172.16.0.236:80", 14 | "upstream_cluster": "outbound|80||nignx.test.svc.cluster.local", 15 | "upstream_transport_failure_reason": null, 16 | "x_forwarded_for": null, 17 | "request_id": "19c42034-0f03-4195-ab33-d4a558ca1de4", 18 | // highlight-start 19 | "response_headers": { 20 | "date": "Sun, 08 Oct 2023 06:19:16 GMT", 21 | "server": "nginx/1.23.4", 22 | "content-length": "10480", 23 | "x-envoy-upstream-service-time": "1", 24 | ":status": "404", 25 | "content-type": "text/html", 26 | "etag": "\"65224347-28f0\"", 27 | "connection": "keep-alive" 28 | }, 29 | // highlight-end 30 | "upstream_local_address": "172.16.0.237:35624", 31 | "requested_server_name": null, 32 | "protocol": "HTTP/1.1", 33 | // highlight-next-line 34 | "response_body": "world", 35 | "connection_termination_details": null, 36 | "bytes_sent": 10480, 37 | "path": "/test", 38 | "downstream_remote_address": "172.16.0.237:44838", 39 | "response_flags": "-" 40 | } 41 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/accesslog/accesslog-print-response-header-body.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: accesslog-print-response-header-body 5 | # highlight-next-line 6 | namespace: test # 只为 test 命名空间开启 accesslog,若改为 istio-system 表示作用于所有命名空间 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 通常打印 header 和 body 用于调试,可指定改配置只作用于指定 workload,避免影响其它不需要调试的 workload 的性能 10 | labels: 11 | app: nginx 12 | # highlight-end 13 | configPatches: 14 | - applyTo: NETWORK_FILTER 15 | match: 16 | listener: 17 | filterChain: 18 | filter: 19 | name: "envoy.filters.network.http_connection_manager" 20 | patch: 21 | operation: MERGE 22 | value: 23 | typed_config: 24 | "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" 25 | access_log: 26 | - name: envoy.access_loggers.file 27 | typed_config: 28 | "@type": "type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog" 29 | path: /dev/stdout 30 | log_format: 31 | json_format: 32 | start_time: "%START_TIME%" 33 | route_name: "%ROUTE_NAME%" 34 | method: "%REQ(:METHOD)%" 35 | path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%" 36 | protocol: "%PROTOCOL%" 37 | response_code: "%RESPONSE_CODE%" 38 | response_flags: "%RESPONSE_FLAGS%" 39 | response_code_details: "%RESPONSE_CODE_DETAILS%" 40 | connection_termination_details: "%CONNECTION_TERMINATION_DETAILS%" 41 | bytes_received: "%BYTES_RECEIVED%" 42 | bytes_sent: "%BYTES_SENT%" 43 | duration: "%DURATION%" 44 | upstream_service_time: "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%" 45 | x_forwarded_for: "%REQ(X-FORWARDED-FOR)%" 46 | user_agent: "%REQ(USER-AGENT)%" 47 | request_id: "%REQ(X-REQUEST-ID)%" 48 | authority: "%REQ(:AUTHORITY)%" 49 | upstream_host: "%UPSTREAM_HOST%" 50 | upstream_cluster: "%UPSTREAM_CLUSTER%" 51 | upstream_local_address: "%UPSTREAM_LOCAL_ADDRESS%" 52 | downstream_local_address: "%DOWNSTREAM_LOCAL_ADDRESS%" 53 | downstream_remote_address: "%DOWNSTREAM_REMOTE_ADDRESS%" 54 | requested_server_name: "%REQUESTED_SERVER_NAME%" 55 | upstream_transport_failure_reason: "%UPSTREAM_TRANSPORT_FAILURE_REASON%" 56 | # highlight-start 57 | response_headers: "%DYNAMIC_METADATA(envoy.lua:response_headers)%" 58 | response_body: "%DYNAMIC_METADATA(envoy.lua:response_body)%" 59 | # highlight-end 60 | - applyTo: HTTP_FILTER 61 | match: 62 | context: ANY 63 | listener: 64 | filterChain: 65 | filter: 66 | name: "envoy.filters.network.http_connection_manager" 67 | subFilter: 68 | name: "envoy.filters.http.router" 69 | patch: 70 | operation: INSERT_BEFORE 71 | value: 72 | name: envoy.lua 73 | typed_config: 74 | "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua" 75 | # highlight-start 76 | inlineCode: | 77 | function envoy_on_response(response_handle) 78 | local headers = response_handle:headers() 79 | local headers_map = {} 80 | for key, value in pairs(headers) do 81 | headers_map[key] = value 82 | end 83 | response_handle:streamInfo():dynamicMetadata():set("envoy.lua","response_headers", headers_map) 84 | local response_body_buffer = response_handle:body() 85 | if(response_body_buffer == nil) 86 | then 87 | response_handle:streamInfo():dynamicMetadata():set("envoy.lua", "response_body", "-") 88 | else 89 | local response_body_data = response_body_buffer:getBytes(0, response_body_buffer:length()) 90 | response_handle:streamInfo():dynamicMetadata():set("envoy.lua", "response_body", response_body_data) 91 | end 92 | end 93 | # highlight-end 94 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/accesslog/enable-accesslog-json-format.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: enable-accesslog-json-format 5 | # highlight-next-line 6 | namespace: test # 只为 test 命名空间开启 accesslog,若改为 istio-system 表示作用于所有命名空间 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 精确到指定的 workload,若不需要可去掉 10 | labels: 11 | app: "toolbox" 12 | # highlight-end 13 | configPatches: 14 | - applyTo: NETWORK_FILTER 15 | match: 16 | context: ANY 17 | listener: 18 | filterChain: 19 | filter: 20 | name: envoy.filters.network.http_connection_manager 21 | patch: 22 | operation: MERGE 23 | value: 24 | typed_config: 25 | "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" 26 | access_log: 27 | - name: envoy.access_loggers.file 28 | typed_config: 29 | "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog 30 | path: "/dev/stdout" 31 | log_format: 32 | # highlight-start 33 | json_format: 34 | start_time: "%START_TIME%" 35 | route_name: "%ROUTE_NAME%" 36 | method: "%REQ(:METHOD)%" 37 | path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%" 38 | protocol: "%PROTOCOL%" 39 | response_code: "%RESPONSE_CODE%" 40 | response_flags: "%RESPONSE_FLAGS%" 41 | response_code_details: "%RESPONSE_CODE_DETAILS%" 42 | connection_termination_details: "%CONNECTION_TERMINATION_DETAILS%" 43 | bytes_received: "%BYTES_RECEIVED%" 44 | bytes_sent: "%BYTES_SENT%" 45 | duration: "%DURATION%" 46 | upstream_service_time: "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%" 47 | x_forwarded_for: "%REQ(X-FORWARDED-FOR)%" 48 | user_agent: "%REQ(USER-AGENT)%" 49 | request_id: "%REQ(X-REQUEST-ID)%" 50 | authority: "%REQ(:AUTHORITY)%" 51 | upstream_host: "%UPSTREAM_HOST%" 52 | upstream_cluster: "%UPSTREAM_CLUSTER%" 53 | upstream_local_address: "%UPSTREAM_LOCAL_ADDRESS%" 54 | downstream_local_address: "%DOWNSTREAM_LOCAL_ADDRESS%" 55 | downstream_remote_address: "%DOWNSTREAM_REMOTE_ADDRESS%" 56 | requested_server_name: "%REQUESTED_SERVER_NAME%" 57 | upstream_transport_failure_reason: "%UPSTREAM_TRANSPORT_FAILURE_REASON%" 58 | # highlight-end 59 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/accesslog/enable-accesslog-text-format.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: enable-accesslog-text-format 5 | # highlight-next-line 6 | namespace: test # 只为 test 命名空间开启 accesslog,若改为 istio-system 表示作用于所有命名空间 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 精确到指定的 workload,若不需要可去掉 10 | labels: 11 | app: "toolbox" 12 | # highlight-end 13 | configPatches: 14 | - applyTo: NETWORK_FILTER 15 | match: 16 | context: ANY 17 | listener: 18 | filterChain: 19 | filter: 20 | name: envoy.filters.network.http_connection_manager 21 | patch: 22 | operation: MERGE 23 | value: 24 | typed_config: 25 | "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" 26 | access_log: 27 | - name: envoy.access_loggers.file 28 | typed_config: 29 | "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog 30 | path: "/dev/stdout" 31 | log_format: 32 | # highlight-start 33 | text_format: | 34 | [%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%" %RESPONSE_CODE% %RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS% %CONNECTION_TERMINATION_DETAILS% "%UPSTREAM_TRANSPORT_FAILURE_REASON%" %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%" "%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%" %UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% %REQUESTED_SERVER_NAME% %ROUTE_NAME% 35 | # highlight-end 36 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/gzip/enable-gzip-for-public.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: enable-gzip-for-public 5 | # highlight-next-line 6 | namespace: prod # 限制在 ingressgateway 所在命名空间 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 限制指定 ingressgateway 10 | labels: 11 | app: istio-ingressgateway-public 12 | # highlight-end 13 | configPatches: 14 | - applyTo: HTTP_FILTER 15 | match: 16 | context: GATEWAY 17 | listener: 18 | filterChain: 19 | filter: 20 | name: envoy.filters.network.http_connection_manager 21 | subFilter: 22 | name: envoy.filters.http.router 23 | patch: 24 | operation: INSERT_BEFORE 25 | value: 26 | name: envoy.filters.http.compressor 27 | typed_config: 28 | "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor 29 | compressor_library: 30 | name: text_optimized 31 | typed_config: 32 | "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip 33 | compression_level: BEST_COMPRESSION 34 | compression_strategy: DEFAULT_STRATEGY 35 | memory_level: 9 36 | window_bits: 15 37 | remove_accept_encoding_header: true 38 | response_direction_config: 39 | common_config: 40 | content_type: 41 | - application/javascript 42 | - application/json 43 | - application/xhtml+xml 44 | - image/svg+xml 45 | - text/css 46 | - text/html 47 | - text/plain 48 | - text/xml 49 | min_content_length: 100 50 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/gzip/enable-gzip.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: enable-gzip 5 | # highlight-next-line 6 | namespace: istio-system # istio-system 表示针对所有命名空间生效 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 选中所有 ingressgateway 10 | labels: 11 | istio: ingressgateway # 所有 ingressgateway 都带此 label 12 | # highlight-end 13 | configPatches: 14 | - applyTo: HTTP_FILTER 15 | match: 16 | context: GATEWAY 17 | listener: 18 | filterChain: 19 | filter: 20 | name: envoy.filters.network.http_connection_manager 21 | subFilter: 22 | name: envoy.filters.http.router 23 | patch: 24 | operation: INSERT_BEFORE 25 | value: 26 | name: envoy.filters.http.compressor 27 | typed_config: 28 | "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor 29 | compressor_library: 30 | name: text_optimized 31 | typed_config: 32 | "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip 33 | compression_level: BEST_COMPRESSION 34 | compression_strategy: DEFAULT_STRATEGY 35 | memory_level: 9 36 | window_bits: 15 37 | remove_accept_encoding_header: true 38 | response_direction_config: 39 | common_config: 40 | content_type: 41 | - application/javascript 42 | - application/json 43 | - application/xhtml+xml 44 | - image/svg+xml 45 | - text/css 46 | - text/html 47 | - text/plain 48 | - text/xml 49 | min_content_length: 100 50 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/label-outbound-traffic.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: label-outbound-traffic 5 | # highlight-next-line 6 | namespace: test # 指定 workload 所在命名空间,改为 istio-system 表示不限制命名空间 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 选中指定 workload,去掉表示对所有 workload 生效 10 | labels: 11 | app: productpage 12 | # highlight-end 13 | configPatches: 14 | - applyTo: HTTP_FILTER 15 | match: 16 | context: SIDECAR_OUTBOUND 17 | listener: 18 | filterChain: 19 | filter: 20 | name: envoy.http_connection_manager 21 | subFilter: 22 | name: envoy.router 23 | patch: 24 | operation: INSERT_BEFORE 25 | value: 26 | name: envoy.lua 27 | typed_config: 28 | "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua" 29 | # 自动加上 key 为 workload 的 header,value 为 workload 的具体名称,key 和 value 均可根据需求自定义 30 | # highlight-start 31 | inlineCode: | 32 | function envoy_on_request(request_handle) 33 | request_handle:headers():add("workload", os.getenv("ISTIO_META_WORKLOAD_NAME")) 34 | end 35 | # highlight-end 36 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/limit-request/limit-header-and-request-size-for-public.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: limit-request-size 5 | # highlight-next-line 6 | namespace: prod # 选择指定 ingressgateway 所在的命名空间 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 选中指定 ingressgateway 10 | labels: 11 | app: istio-ingressgateway-public # 替换指定 ingressgateway 的 label 12 | # highlight-end 13 | configPatches: 14 | - applyTo: NETWORK_FILTER 15 | match: 16 | context: ANY 17 | listener: 18 | filterChain: 19 | filter: 20 | name: "envoy.http_connection_manager" 21 | patch: 22 | operation: MERGE 23 | value: 24 | typed_config: 25 | "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" 26 | # highlight-next-line 27 | max_request_headers_kb: 96 # 96KB, 请求 header 最大限制 28 | - applyTo: HTTP_FILTER 29 | match: 30 | context: GATEWAY 31 | listener: 32 | filterChain: 33 | filter: 34 | name: "envoy.http_connection_manager" 35 | patch: 36 | operation: INSERT_BEFORE 37 | value: 38 | name: "envoy.filters.http.buffer" 39 | typed_config: 40 | "@type": "type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer" 41 | # highlight-next-line 42 | max_request_bytes: 1048576 # 1MB, 请求最大限制 43 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/limit-request/limit-header-and-request-size.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: limit-request-size 5 | # highlight-next-line 6 | namespace: istio-system # istio-system 表示针对所有命名空间生效 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 选中所有 ingressgateway 10 | labels: 11 | istio: ingressgateway # 所有 ingressgateway 都带此 label 12 | # highlight-end 13 | configPatches: 14 | - applyTo: NETWORK_FILTER 15 | match: 16 | context: ANY 17 | listener: 18 | filterChain: 19 | filter: 20 | name: "envoy.http_connection_manager" 21 | patch: 22 | operation: MERGE 23 | value: 24 | typed_config: 25 | "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" 26 | # highlight-next-line 27 | max_request_headers_kb: 96 # 96KB, 请求 header 最大限制 28 | - applyTo: HTTP_FILTER 29 | match: 30 | context: GATEWAY 31 | listener: 32 | filterChain: 33 | filter: 34 | name: "envoy.http_connection_manager" 35 | patch: 36 | operation: INSERT_BEFORE 37 | value: 38 | name: "envoy.filters.http.buffer" 39 | typed_config: 40 | "@type": "type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer" 41 | # highlight-next-line 42 | max_request_bytes: 1048576 # 1MB, 请求最大限制 43 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/limit-request/limit-header-size-for-public.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: limit-header-size 5 | # highlight-next-line 6 | namespace: prod # 选择指定 ingressgateway 所在的命名空间 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 选中指定 ingressgateway 10 | labels: 11 | app: istio-ingressgateway-public # 替换指定 ingressgateway 的 label 12 | # highlight-end 13 | configPatches: 14 | - applyTo: NETWORK_FILTER 15 | match: 16 | context: ANY 17 | listener: 18 | filterChain: 19 | filter: 20 | name: "envoy.http_connection_manager" 21 | patch: 22 | operation: MERGE 23 | value: 24 | typed_config: 25 | "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" 26 | # highlight-next-line 27 | max_request_headers_kb: 96 # 96KB, 请求 header 最大限制 28 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/limit-request/limit-header-size.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: limit-header-size 5 | # highlight-next-line 6 | namespace: istio-system # istio-system 表示针对所有命名空间生效 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 选中所有 ingressgateway 10 | labels: 11 | istio: ingressgateway # 所有 ingressgateway 都带此 label 12 | # highlight-end 13 | configPatches: 14 | - applyTo: NETWORK_FILTER 15 | match: 16 | context: ANY 17 | listener: 18 | filterChain: 19 | filter: 20 | name: "envoy.http_connection_manager" 21 | patch: 22 | operation: MERGE 23 | value: 24 | typed_config: 25 | "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" 26 | # highlight-next-line 27 | max_request_headers_kb: 96 # 96KB, 请求 header 最大限制 28 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/limit-request/limit-request-size-for-public.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: limit-request-size 5 | # highlight-next-line 6 | namespace: prod # 选择指定 ingressgateway 所在的命名空间 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 选中指定 ingressgateway 10 | labels: 11 | app: istio-ingressgateway-public # 替换指定 ingressgateway 的 label 12 | # highlight-end 13 | configPatches: 14 | - applyTo: HTTP_FILTER 15 | match: 16 | context: GATEWAY 17 | listener: 18 | filterChain: 19 | filter: 20 | name: "envoy.http_connection_manager" 21 | patch: 22 | operation: INSERT_BEFORE 23 | value: 24 | name: "envoy.filters.http.buffer" 25 | typed_config: 26 | "@type": "type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer" 27 | # highlight-next-line 28 | max_request_bytes: 1048576 # 1MB, 请求最大限制 29 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/limit-request/limit-request-size.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: limit-request-size 5 | # highlight-next-line 6 | namespace: istio-system # istio-system 表示针对所有命名空间生效 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 选中所有 ingressgateway 10 | labels: 11 | istio: ingressgateway # 所有 ingressgateway 都带此 label 12 | # highlight-end 13 | configPatches: 14 | - applyTo: HTTP_FILTER 15 | match: 16 | context: GATEWAY 17 | listener: 18 | filterChain: 19 | filter: 20 | name: "envoy.http_connection_manager" 21 | patch: 22 | operation: INSERT_BEFORE 23 | value: 24 | name: "envoy.filters.http.buffer" 25 | typed_config: 26 | "@type": "type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer" 27 | # highlight-next-line 28 | max_request_bytes: 1048576 # 1MB, 请求最大限制 29 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/preserve-case/preserve-case-all.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: preserve-case-all 5 | # highlight-next-line 6 | namespace: test # 限制命名空间,若改为 istio-system 表示作用于所有命名空间 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 精确到指定的 workload,若不需要可去掉 10 | labels: 11 | app: istio-ingressgateway 12 | # highlight-end 13 | priority: 0 14 | configPatches: 15 | - applyTo: CLUSTER 16 | match: 17 | context: ANY 18 | patch: 19 | operation: MERGE 20 | value: 21 | typed_extension_protocol_options: 22 | envoy.extensions.upstreams.http.v3.HttpProtocolOptions: 23 | "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions 24 | use_downstream_protocol_config: 25 | http_protocol_options: 26 | header_key_format: 27 | stateful_formatter: 28 | name: preserve_case 29 | typed_config: 30 | "@type": type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig 31 | - applyTo: NETWORK_FILTER 32 | match: 33 | listener: 34 | filterChain: 35 | filter: 36 | name: envoy.filters.network.http_connection_manager 37 | patch: 38 | operation: MERGE 39 | value: 40 | typed_config: 41 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 42 | http_protocol_options: 43 | header_key_format: 44 | stateful_formatter: 45 | name: preserve_case 46 | typed_config: 47 | "@type": type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig 48 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/preserve-case/preserve-case-request.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: preserve-case-request 5 | # highlight-next-line 6 | namespace: test # 限制命名空间,若改为 istio-system 表示作用于所有命名空间 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 精确到指定的 workload,若不需要可去掉 10 | labels: 11 | app: istio-ingressgateway 12 | # highlight-end 13 | priority: 0 14 | configPatches: 15 | - applyTo: NETWORK_FILTER 16 | match: 17 | listener: 18 | filterChain: 19 | filter: 20 | name: envoy.filters.network.http_connection_manager 21 | patch: 22 | operation: MERGE 23 | value: 24 | typed_config: 25 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 26 | http_protocol_options: 27 | header_key_format: 28 | stateful_formatter: 29 | name: preserve_case 30 | typed_config: 31 | "@type": type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig 32 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/preserve-case/preserve-case-response.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: preserve-case-response 5 | # highlight-next-line 6 | namespace: test # 限制命名空间,若改为 istio-system 表示作用于所有命名空间 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 精确到指定的 workload,若不需要可去掉 10 | labels: 11 | app: istio-ingressgateway 12 | # highlight-end 13 | priority: 0 14 | configPatches: 15 | - applyTo: CLUSTER 16 | match: 17 | context: ANY 18 | patch: 19 | operation: MERGE 20 | value: 21 | typed_extension_protocol_options: 22 | envoy.extensions.upstreams.http.v3.HttpProtocolOptions: 23 | "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions 24 | use_downstream_protocol_config: 25 | http_protocol_options: 26 | header_key_format: 27 | stateful_formatter: 28 | name: preserve_case 29 | typed_config: 30 | "@type": type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig 31 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/ratelimit/local-ratelimit-port.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: local-ratelimit-port 5 | # highlight-next-line 6 | namespace: mesh-test # workload 所在 namespace 7 | spec: 8 | # highlight-start 9 | # 只限制 productpage 工作负载的 QPS 10 | workloadSelector: 11 | labels: 12 | app: productpage 13 | # highlight-end 14 | configPatches: 15 | - applyTo: HTTP_FILTER 16 | match: 17 | context: SIDECAR_INBOUND 18 | listener: 19 | filterChain: 20 | filter: 21 | name: "envoy.filters.network.http_connection_manager" 22 | patch: 23 | operation: INSERT_BEFORE 24 | value: 25 | name: envoy.filters.http.local_ratelimit 26 | typed_config: 27 | "@type": type.googleapis.com/udpa.type.v1.TypedStruct 28 | type_url: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit 29 | value: 30 | stat_prefix: http_local_rate_limiter 31 | - applyTo: HTTP_ROUTE 32 | match: 33 | context: SIDECAR_INBOUND 34 | # highlight-start 35 | # 只限制入向 9080 端口流量的 QPS 36 | routeConfiguration: 37 | vhost: 38 | name: "inbound|http|9080" 39 | route: 40 | action: ANY 41 | # highlight-end 42 | patch: 43 | operation: MERGE 44 | value: 45 | typed_per_filter_config: 46 | envoy.filters.http.local_ratelimit: 47 | "@type": type.googleapis.com/udpa.type.v1.TypedStruct 48 | type_url: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit 49 | value: 50 | stat_prefix: http_local_rate_limiter 51 | token_bucket: 52 | # highlight-start 53 | # 限制每分钟 10 个请求 54 | max_tokens: 10 55 | tokens_per_fill: 10 56 | fill_interval: 60s 57 | # highlight-end 58 | filter_enabled: 59 | runtime_key: local_rate_limit_enabled 60 | default_value: 61 | numerator: 100 62 | denominator: HUNDRED 63 | filter_enforced: 64 | runtime_key: local_rate_limit_enforced 65 | default_value: 66 | numerator: 100 67 | denominator: HUNDRED 68 | response_headers_to_add: 69 | - append: false 70 | header: 71 | key: x-local-rate-limit 72 | value: "true" 73 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/ratelimit/local-ratelimit-workload.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: local-ratelimit-workload 5 | # highlight-next-line 6 | namespace: mesh-test # workload 所在 namespace 7 | spec: 8 | # highlight-start 9 | # 只限制 productpage 工作负载的 QPS 10 | workloadSelector: 11 | labels: 12 | app: productpage 13 | # highlight-end 14 | configPatches: 15 | - applyTo: HTTP_FILTER 16 | match: 17 | context: SIDECAR_INBOUND 18 | listener: 19 | filterChain: 20 | filter: 21 | name: "envoy.filters.network.http_connection_manager" 22 | patch: 23 | operation: INSERT_BEFORE 24 | value: 25 | name: envoy.filters.http.local_ratelimit 26 | typed_config: 27 | "@type": type.googleapis.com/udpa.type.v1.TypedStruct 28 | type_url: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit 29 | value: 30 | stat_prefix: http_local_rate_limiter 31 | token_bucket: 32 | # highlight-start 33 | # 限制每分钟 10 个请求 34 | max_tokens: 10 35 | tokens_per_fill: 10 36 | fill_interval: 60s 37 | # highlight-end 38 | filter_enabled: 39 | runtime_key: local_rate_limit_enabled 40 | default_value: 41 | numerator: 100 42 | denominator: HUNDRED 43 | filter_enforced: 44 | runtime_key: local_rate_limit_enforced 45 | default_value: 46 | numerator: 100 47 | denominator: HUNDRED 48 | response_headers_to_add: 49 | - append: false 50 | header: 51 | key: x-local-rate-limit 52 | value: "true" 53 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/ratelimit/productpage-enable-http-local-rate-limit-proxy-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: productpage-v1 5 | labels: 6 | app: productpage 7 | version: v1 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: productpage 13 | version: v1 14 | template: 15 | metadata: 16 | # highlight-add-start 17 | annotations: 18 | proxy.istio.io/config: |- 19 | proxyStatsMatcher: 20 | inclusionRegexps: 21 | - ".*http_local_rate_limit.*" 22 | # highlight-add-end 23 | labels: 24 | app: productpage 25 | version: v1 26 | spec: 27 | serviceAccountName: bookinfo-productpage 28 | containers: 29 | - name: productpage 30 | image: docker.io/istio/examples-bookinfo-productpage-v1:1.17.0 31 | imagePullPolicy: IfNotPresent 32 | ports: 33 | - containerPort: 9080 34 | volumeMounts: 35 | - name: tmp 36 | mountPath: /tmp 37 | securityContext: 38 | runAsUser: 1000 39 | volumes: 40 | - name: tmp 41 | emptyDir: {} 42 | -------------------------------------------------------------------------------- /codeblock/envoyfilter/response-request-id.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: response-request-id 5 | # highlight-next-line 6 | namespace: istio-system # istio-system 表示针对所有命名空间生效 7 | spec: 8 | # highlight-start 9 | workloadSelector: # 选中所有 ingressgateway 10 | labels: 11 | istio: ingressgateway # 所有 ingressgateway 都带此 label 12 | # highlight-end 13 | configPatches: 14 | - applyTo: HTTP_FILTER 15 | match: 16 | context: GATEWAY 17 | listener: 18 | filterChain: 19 | filter: 20 | name: "envoy.filters.network.http_connection_manager" 21 | subFilter: 22 | name: "envoy.filters.http.router" 23 | patch: 24 | operation: INSERT_BEFORE 25 | value: 26 | name: envoy.lua 27 | typed_config: 28 | # highlight-start 29 | "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua" 30 | inlineCode: | 31 | function envoy_on_request(handle) 32 | local metadata = handle:streamInfo():dynamicMetadata() 33 | local headers = handle:headers() 34 | local rid = headers:get("x-request-id") 35 | if rid ~= nil then 36 | metadata:set("envoy.filters.http.lua", "req.x-request-id", rid) 37 | end 38 | end 39 | function envoy_on_response(handle) 40 | local metadata = handle:streamInfo():dynamicMetadata():get("envoy.filters.http.lua") 41 | local rid = metadata["req.x-request-id"] 42 | if rid ~= nil then 43 | handle:headers():add("x-request-id", rid) 44 | end 45 | end 46 | # highlight-end 47 | -------------------------------------------------------------------------------- /codeblock/istio/dr-iphash.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1beta1 2 | kind: DestinationRule 3 | metadata: 4 | name: bookinfo-ratings 5 | spec: 6 | host: ratings.prod.svc.cluster.local 7 | # highlight-add-start 8 | trafficPolicy: 9 | loadBalancer: 10 | consistentHash: 11 | useSourceIp: true 12 | # highlight-add-end 13 | -------------------------------------------------------------------------------- /codeblock/istio/istio-init-crash-log.txt: -------------------------------------------------------------------------------- 1 | Environment: 2 | ------------ 3 | ENVOY_PORT= 4 | INBOUND_CAPTURE_PORT= 5 | ISTIO_INBOUND_INTERCEPTION_MODE= 6 | ISTIO_INBOUND_TPROXY_MARK= 7 | ISTIO_INBOUND_TPROXY_ROUTE_TABLE= 8 | ISTIO_INBOUND_PORTS= 9 | ISTIO_LOCAL_EXCLUDE_PORTS= 10 | ISTIO_SERVICE_CIDR= 11 | ISTIO_SERVICE_EXCLUDE_CIDR= 12 | 13 | Variables: 14 | ---------- 15 | PROXY_PORT=15001 16 | PROXY_INBOUND_CAPTURE_PORT=15006 17 | PROXY_UID=1337 18 | PROXY_GID=1337 19 | INBOUND_INTERCEPTION_MODE=REDIRECT 20 | INBOUND_TPROXY_MARK=1337 21 | INBOUND_TPROXY_ROUTE_TABLE=133 22 | INBOUND_PORTS_INCLUDE=* 23 | INBOUND_PORTS_EXCLUDE=15090,15021,15020 24 | OUTBOUND_IP_RANGES_INCLUDE=* 25 | OUTBOUND_IP_RANGES_EXCLUDE= 26 | OUTBOUND_PORTS_EXCLUDE= 27 | KUBEVIRT_INTERFACES= 28 | ENABLE_INBOUND_IPV6=false 29 | 30 | Writing following contents to rules file: /tmp/iptables-rules-1618279687646418248.txt617375845 31 | * nat 32 | -N ISTIO_REDIRECT 33 | -N ISTIO_IN_REDIRECT 34 | -N ISTIO_INBOUND 35 | -N ISTIO_OUTPUT 36 | -A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001 37 | -A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006 38 | -A PREROUTING -p tcp -j ISTIO_INBOUND 39 | -A ISTIO_INBOUND -p tcp --dport 22 -j RETURN 40 | -A ISTIO_INBOUND -p tcp --dport 15090 -j RETURN 41 | -A ISTIO_INBOUND -p tcp --dport 15021 -j RETURN 42 | -A ISTIO_INBOUND -p tcp --dport 15020 -j RETURN 43 | -A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT 44 | -A OUTPUT -p tcp -j ISTIO_OUTPUT 45 | -A ISTIO_OUTPUT -o lo -s 127.0.0.6/32 -j RETURN 46 | -A ISTIO_OUTPUT -o lo ! -d 127.0.0.1/32 -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT 47 | -A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN 48 | -A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN 49 | -A ISTIO_OUTPUT -o lo ! -d 127.0.0.1/32 -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT 50 | -A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN 51 | -A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN 52 | -A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN 53 | -A ISTIO_OUTPUT -j ISTIO_REDIRECT 54 | COMMIT 55 | 56 | iptables-restore --noflush /tmp/iptables-rules-1618279687646418248.txt617375845 57 | iptables-restore: line 2 failed 58 | iptables-save 59 | # Generated by iptables-save v1.6.1 on Tue Apr 13 02:08:07 2021 60 | *nat 61 | :PREROUTING ACCEPT [5214353:312861180] 62 | :INPUT ACCEPT [5214353:312861180] 63 | :OUTPUT ACCEPT [6203044:504329953] 64 | :POSTROUTING ACCEPT [6203087:504332485] 65 | :ISTIO_INBOUND - [0:0] 66 | :ISTIO_IN_REDIRECT - [0:0] 67 | :ISTIO_OUTPUT - [0:0] 68 | :ISTIO_REDIRECT - [0:0] 69 | -A PREROUTING -p tcp -j ISTIO_INBOUND 70 | -A OUTPUT -p tcp -j ISTIO_OUTPUT 71 | -A ISTIO_INBOUND -p tcp -m tcp --dport 22 -j RETURN 72 | -A ISTIO_INBOUND -p tcp -m tcp --dport 15090 -j RETURN 73 | -A ISTIO_INBOUND -p tcp -m tcp --dport 15021 -j RETURN 74 | -A ISTIO_INBOUND -p tcp -m tcp --dport 15020 -j RETURN 75 | -A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT 76 | -A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006 77 | -A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN 78 | -A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT 79 | -A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN 80 | -A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN 81 | -A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT 82 | -A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN 83 | -A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN 84 | -A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN 85 | -A ISTIO_OUTPUT -j ISTIO_REDIRECT 86 | -A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001 87 | COMMIT 88 | # Completed on Tue Apr 13 02:08:07 2021 89 | panic: exit status 1 90 | 91 | goroutine 1 [running]: 92 | istio.io/istio/tools/istio-iptables/pkg/dependencies.(*RealDependencies).RunOrFail(0x3bb0090, 0x22cfd22, 0x10, 0xc0006849c0, 0x2, 0x2) 93 | istio.io/istio/tools/istio-iptables/pkg/dependencies/implementation.go:44 +0x96 94 | istio.io/istio/tools/istio-iptables/pkg/cmd.(*IptablesConfigurator).executeIptablesRestoreCommand(0xc0009dfd68, 0x22c5a01, 0x0, 0x0) 95 | istio.io/istio/tools/istio-iptables/pkg/cmd/run.go:493 +0x387 96 | istio.io/istio/tools/istio-iptables/pkg/cmd.(*IptablesConfigurator).executeCommands(0xc0009dfd68) 97 | istio.io/istio/tools/istio-iptables/pkg/cmd/run.go:500 +0x45 98 | istio.io/istio/tools/istio-iptables/pkg/cmd.(*IptablesConfigurator).run(0xc0009dfd68) 99 | istio.io/istio/tools/istio-iptables/pkg/cmd/run.go:447 +0x2625 100 | istio.io/istio/tools/istio-iptables/pkg/cmd.glob..func1(0x3b5d680, 0xc0004cce00, 0x0, 0x10) 101 | istio.io/istio/tools/istio-iptables/pkg/cmd/root.go:64 +0x148 102 | github.com/spf13/cobra.(*Command).execute(0x3b5d680, 0xc0004ccd00, 0x10, 0x10, 0x3b5d680, 0xc0004ccd00) 103 | github.com/spf13/cobra@v1.0.0/command.go:846 +0x29d 104 | github.com/spf13/cobra.(*Command).ExecuteC(0x3b5d920, 0x0, 0x0, 0x0) 105 | github.com/spf13/cobra@v1.0.0/command.go:950 +0x349 106 | github.com/spf13/cobra.(*Command).Execute(...) 107 | github.com/spf13/cobra@v1.0.0/command.go:887 108 | main.main() 109 | istio.io/istio/pilot/cmd/pilot-agent/main.go:505 +0x2d 110 | -------------------------------------------------------------------------------- /codeblock/istio/vs-cors-regex.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1beta1 2 | kind: VirtualService 3 | metadata: 4 | name: nginx 5 | namespace: istio-demo 6 | spec: 7 | gateways: 8 | - istio-demo/nginx-gw 9 | hosts: 10 | - "nginx.example.com" 11 | - "test.example.com" 12 | http: 13 | # highlight-add-start 14 | - corsPolicy: 15 | allowOrigins: 16 | - regex: "https?://nginx.example.com|https?://test.example.com" 17 | # highlight-add-end 18 | route: 19 | - destination: 20 | host: nginx.istio-demo.svc.cluster.local 21 | port: 22 | number: 80 23 | -------------------------------------------------------------------------------- /content/README.md: -------------------------------------------------------------------------------- 1 | # istio 实践指南 2 | 3 | ## 关于本书 4 | 5 | 本书为电子书形式,内容为本人多年的云原生与 istio 实战经验进行系统性整理的结果,不废话,纯干货。 6 | 7 | ## 在线阅读 8 | 9 | 地址: https://imroc.cc/istio 10 | 11 | ## 评论与互动 12 | 13 | 本书已集成 [giscus](https://giscus.app/zh-CN) 评论系统,欢迎对感兴趣的文章进行评论与交流。 14 | 15 | ## 贡献 16 | 17 | 本书使用 [docusaurus](https://docusaurus.io/) 构建,已集成自动构建和发布,欢迎 Fork 并 PR 来贡献干货内容 (点击左下角 `编辑此页` 按钮可快速修改文章)。 18 | 19 | ## 许可证 20 | 21 | 您可以使用 [署名 - 非商业性使用 - 相同方式共享 4.0 (CC BY-NC-SA 4.0)](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh) 协议共享。 22 | 23 | -------------------------------------------------------------------------------- /content/ambient/build.md: -------------------------------------------------------------------------------- 1 | # 编译与测试 2 | 3 | ## 基于最新代码编译并 push 镜像 4 | 5 | 下载最新代码: 6 | 7 | ```bash 8 | git clone http://github.com/istio/istio.git 9 | ``` 10 | 11 | 切换分支: 12 | 13 | ```bash 14 | cd istio 15 | git checkout -b experimental-ambient origin/experimental-ambient 16 | ``` 17 | 18 | 编译所有镜像: 19 | 20 | ```bash 21 | export HUB="registry.imroc.cc/istio" 22 | export TAG="ambient" 23 | make docker 24 | ``` 25 | 26 | 查看镜像列表: 27 | 28 | ```bash 29 | $ docker images | grep ambient 30 | registry.imroc.cc/istio/install-cni ambient d3d8fa9fff24 2 days ago 307MB 31 | registry.imroc.cc/istio/proxyv2 ambient 94ac94a14ed6 2 days ago 277MB 32 | registry.imroc.cc/istio/istioctl ambient 76fea2b66ed7 2 days ago 190MB 33 | registry.imroc.cc/istio/operator ambient 574faf14c66b 2 days ago 191MB 34 | registry.imroc.cc/istio/app ambient 7c648c702595 2 days ago 188MB 35 | registry.imroc.cc/istio/pilot ambient d914093f7809 2 days ago 189MB 36 | registry.imroc.cc/istio/ext-authz ambient 88dc93477b75 2 days ago 112MB 37 | ``` 38 | 39 | 最后再使用 docker push 上传镜像。 40 | 41 | 实际上测试 ambient 是需要其中几个,可以用下面命令只编译需要的镜像: 42 | 43 | ```bash 44 | make docker.pilot 45 | make docker.install-cni 46 | make docker.proxyv2 47 | make docker.istioctl 48 | ``` 49 | 50 | ## 从镜像拷贝出 istioctl 二进制 51 | 52 | 下面介绍将 istioctl 二进制拷贝出来的方法,首先用 istioctl 镜像运行一个容器: 53 | 54 | ```bash 55 | docker run --rm -it --entrypoint="" --name istioctl registry.imroc.cc/istio/istioctl:ambient bash 56 | ``` 57 | 58 | 再利用 docker cp 将二进制拷贝出来: 59 | 60 | ```bash 61 | docker cp istioctl:/usr/local/bin/istioctl ./istioctl 62 | ``` 63 | 64 | ## 使用 istioctl 安装 ambient mesh 65 | 66 | ```bash 67 | ./istioctl install --set profile=ambient --set hub=registry.imroc.cc/istio --set tag=ambient 68 | ``` -------------------------------------------------------------------------------- /content/ambient/in-depth.md: -------------------------------------------------------------------------------- 1 | # 深入分析实现原理 2 | 3 | ## 出方向流量分析 4 | 5 | ### 流量拦截原理 6 | 7 | 流量路径: 8 | 9 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/20221009140434.png) 10 | 11 | iptables 在 PREROUTING 链上匹配源 IP 是本节点中网格内的 Pod IP 的 TCP 数据包,打上 0x100 的 mark: 12 | 13 | ```txt 14 | -A ztunnel-PREROUTING -p tcp -m set --match-set ztunnel-pods-ips src -j MARK --set-xmark 0x100/0x100 15 | ``` 16 | 17 | 用 ipset 可以看到 `ztunnel-pods-ips` 正是本节点中网格内的 POD IP 列表: 18 | 19 | ```txt 20 | $ ipset list 21 | Name: ztunnel-pods-ips 22 | Type: hash:ip 23 | Revision: 0 24 | Header: family inet hashsize 1024 maxelem 65536 25 | Size in memory: 440 26 | References: 1 27 | Number of entries: 2 28 | Members: 29 | 10.244.1.4 30 | 10.244.1.5 31 | ``` 32 | 33 | 在策略路由可以看到,打上 0x100 mark 的数据包,会走 101 号路由表进行路由: 34 | 35 | ```bash 36 | $ ip rule list 37 | 101: from all fwmark 0x100/0x100 lookup 101 38 | ``` 39 | 40 | 查看路由,会经过 `istioout` 网卡,使用 `192.168.127.2` 这个默认网关进行路由: 41 | 42 | ```bash 43 | $ ip route show table 101 44 | default via 192.168.127.2 dev istioout 45 | 10.244.1.3 dev vethf18f80e0 scope link 46 | ``` 47 | 48 | 查看 `istioout` 网卡,是 `geneve` 类型的虚拟网卡,`remote` 是 `10.244.1.3`: 49 | 50 | ```bash 51 | $ ip -d a s istioout 52 | 5: istioout: mtu 1450 qdisc noqueue state UNKNOWN group default 53 | link/ether 26:04:92:96:1b:67 brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65485 54 | geneve id 1001 remote 10.244.1.3 ttl auto dstport 6081 noudpcsum udp6zerocsumrx numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 55 | inet 192.168.127.1/30 brd 192.168.127.3 scope global istioout 56 | valid_lft forever preferred_lft forever 57 | ``` 58 | 59 | 而 `10.244.1.3` 是本节点上的 ztunnel pod ip: 60 | 61 | ```bash 62 | $ kubectl -n istio-system get pod -o wide | grep 10.244.1.3 63 | ztunnel-27nxh 1/1 Running 0 3d3h 10.244.1.3 ambient-worker 64 | ``` 65 | 66 | 前面提到的默认网关 `192.168.127.2` 也正是 ztunnel 内的 `pistioout` 网卡的 IP: 67 | 68 | ```bash 69 | $ kubectl -n istio-system exec -it ztunnel-27nxh -- ip -d a s 70 | 4: pistioout: mtu 1450 qdisc noqueue state UNKNOWN group default qlen 1000 71 | link/ether 76:b0:37:d7:16:93 brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65485 72 | geneve id 1001 remote 10.244.1.1 ttl auto dstport 6081 noudpcsum udp6zerocsumrx numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 73 | inet 192.168.127.2/30 scope global pistioout 74 | valid_lft forever preferred_lft forever 75 | inet6 fe80::74b0:37ff:fed7:1693/64 scope link 76 | valid_lft forever preferred_lft forever 77 | ``` 78 | 79 | 也就是说,节点内的 `istioout` 和 ztunnel pod 内的 `pistioout` 网卡通过 geneve tunnel 打通了,`istioout` 收到数据包后立即会转到 ztunnel 内的 `pistioout` 网卡。 80 | 81 | 所以串起来就是,从本节点网格内的 Pod 中发出的 TCP 流量,会被策略路由转发到 ztunnel pod 内的 `pistioout` 网卡。 82 | 83 | 而在 ztunnel 内的 `pistioout` 网卡收到流量后,会被 iptables 通过 tproxy 方式转发到 ztunnel 的 15001 端口上: 84 | 85 | ```bash 86 | $ kubectl -n istio-system exec -it ztunnel-27nxh -- iptables-save | grep pistioout 87 | -A PREROUTING -i pistioout -p tcp -j TPROXY --on-port 15001 --on-ip 127.0.0.1 --tproxy-mark 0x400/0xfff 88 | ``` 89 | 90 | ### ztunnel 接收与转发流量实现原理 91 | 92 | ztunnel 使用 envoy 实现,导出 xds: 93 | 94 | ```bash 95 | kubectl -n istio-system exec -it ztunnel-gm66l -- curl '127.0.0.1:15000/config_dump?include_eds' > dump.json 96 | ``` 97 | 98 | 下面分析下处理拦截到的出方向流量的 xds 配置规则。 99 | 100 | LDS 中监听 15001 端口,用于处理 tproxy 方式拦截到的用户 Pod 出方向流量(`"trasparent": true` 指示 envoy 监听时使用 `IP_TRANSPARENT` socket 选项以便让目的 IP 不是 ztunnel 网卡 IP 的数据包也能让 ztunnel 收到,实现 tproxy 透明代理) ,非 tproxy 拦截的流量就直接丢弃: 101 | 102 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/20221010102951.png) 103 | 104 | 15001 端口监听有很多 `filter_chains`,具体用哪个 filter,由 `filter_chain_matcher` 来匹配: 105 | 106 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/20221010111444.png) 107 | 108 | * `SourceIPInput` 匹配源 IP,看来自节点上哪个 Pod IP。 109 | * `DestinationIPInput` 匹配目标 IP。 110 | * 匹配到某个 Service 的 ClusterIP,继续用 `DestinationPortInput` 匹配 Service 端口。 111 | * 匹配完成后,action 指示要使用的 `filter` 名称。 112 | 113 | 找到对应的 filter,指示转发给指定的 Cluster: 114 | 115 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/20221010112543.png) 116 | 117 | 在 CDS 中找到对应的 Cluster: 118 | 119 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/20221010113142.png) 120 | 121 | 在 EDS 中找 Cluster 对应的 endpoint: 122 | 123 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/20221010145339.png) 124 | 125 | > 这里应该是 envoy dump xds 的 bug,EDS 中没展示 `cluster_name` 这个必选字段,可能是由于 HBONE 比较新,dump 的逻辑还不完善。 126 | 127 | - endpoint 的 address 是 `envoy_internal_address` (internal_listener)。 128 | - `filter_metadata` 中的 `tunnel.destination` 是关键,表示给这个 endpoint 带上了要访问的实际目标IP+目标端口,后面会将其传给 HBONE 隧道。 129 | 130 | 在 LDS 中找到对应的 `server_listener_name`: 131 | 132 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/20221010144958.png) 133 | 134 | - 该 listener 有 `tunneling_config`,即使用 `HBONE` 隧道方式,其中 `hostname` 为应用需要访问的实际目标IP和目标端口,引用 endpoint 中的 metadata `tunnel.destination`。 135 | - cluster 指定转发到哪个 Cluster。 136 | 137 | 再去 CDS 中找到对应的 Cluster: 138 | 139 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/20221010185533.png) 140 | 141 | - 目标地址已经被前面的 EDS 修改成了 Service 对应的 POD IP 和 POD 端口了,CDS 的 type 为 `ORIGINAL_DST`,`upstream_port_override` 强制将目的端口改为 15008,表示使用 `POD_IP:15008` 这个地址作为报文的目标地址,也就是 HBONE 隧道上游 server 端地址。 142 | - `tls_certificates_sds_secret_configs` 中指定连接上游要使用的证书,指定为目标 Pod 所使用的 service account 对应的证书,这也是在 L4 实现零信任网络的关键。 143 | 144 | ## 入方向流量分析 145 | 146 | ### 流量拦截原理 147 | 148 | 流量路径: 149 | 150 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/20221009140413.png) 151 | 152 | 由前面出方向流量路径的分析可以得知,ztuunel 最终转发出来的目的IP和目的端口分别是目标 POD IP和固定的 15008 端口。 153 | 154 | 看下策略路由: 155 | 156 | ```bash 157 | $ ip rule list 158 | 103: from all lookup 100 159 | 32766: from all lookup main 160 | ``` 161 | 162 | 有个 100 号的路由表,优先级比默认的 main 表高。看下路由规则: 163 | 164 | ```bash 165 | $ ip route show table 100 166 | 10.244.1.3 dev vethf18f80e0 scope link 167 | 10.244.1.4 via 192.168.126.2 dev istioin src 10.244.1.1 168 | 10.244.1.5 via 192.168.126.2 dev istioin src 10.244.1.1 169 | ``` 170 | 171 | 可以看出会给本机上所有的网格内的 POD IP 都加一条路由规则,让到网格内 POD 的流量都走 `istioin` 这个网卡进行路由,网关是 `192.168.126.2`。 172 | 173 | ```bash 174 | $ ip -d a s istioin 175 | 4: istioin: mtu 1450 qdisc noqueue state UNKNOWN group default 176 | link/ether 3a:e0:ed:06:15:8c brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65485 177 | geneve id 1000 remote 10.244.1.3 ttl auto dstport 6081 noudpcsum udp6zerocsumrx numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 178 | inet 192.168.126.1/30 brd 192.168.126.3 scope global istioin 179 | valid_lft forever preferred_lft forever 180 | ``` 181 | 182 | 与前面出向流量走的 `istioout` 网卡类似,入向流量走的 `istioin` 网卡也是 geneve tunnel 设备,remote 是 `10.244.1.3`,即 ztunnel 的 POD IP,而网关 `192.168.126.2` 也正是 ztunnel 内的 `pistioin` 网卡: 183 | 184 | ```bash 185 | $ kubectl -n istio-system exec -it ztunnel-27nxh -- ip -d a s pistioin 186 | 3: pistioin: mtu 1450 qdisc noqueue state UNKNOWN group default qlen 1000 187 | link/ether 7e:bb:b7:60:f3:f6 brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65485 188 | geneve id 1000 remote 10.244.1.1 ttl auto dstport 6081 noudpcsum udp6zerocsumrx numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 189 | inet 192.168.126.2/30 scope global pistioin 190 | valid_lft forever preferred_lft forever 191 | inet6 fe80::7cbb:b7ff:fe60:f3f6/64 scope link 192 | valid_lft forever preferred_lft forever 193 | ``` 194 | 195 | 也就是说,发送到节点上网格内的 POD 的流量,会被自动转到 ztunnel 内的 `pistioin` 网卡。 196 | 197 | 看 ztunnel 内的 iptables 规则,会将 `pistioin` 上目的端口为 15008 的 TCP 流量,通过 tproxy 方式拦截并转发给 ztunnel 监听的 15008 端口。 198 | 199 | ```bash 200 | $ kubectl -n istio-system exec -it ztunnel-27nxh -- iptables-save | grep pistioin 201 | -A PREROUTING -i pistioin -p tcp -m tcp --dport 15008 -j TPROXY --on-port 15008 --on-ip 127.0.0.1 --tproxy-mark 0x400/0xfff 202 | ``` 203 | 204 | ## ztunnel 接收与转发流量实现原理 205 | 206 | 查看 LDS 中 15008 端口的 Listener: 207 | 208 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/20221011193021.png) 209 | 210 | - `filter_chains` 中有很多 filter,每个 filter 对应匹配本节点上,网格内的 POD IP。 211 | - 在 filter 的 route 中,将数据包路由到 `virtual_inbound` 这个 Cluster。 212 | 213 | 在 CDS 中找到 `virtual_inbound` 这个 Cluster: 214 | 215 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/20221011200422.png) 216 | 217 | - `use_http_header` 表示将 downstream ztunnel 通过 HBONE CONNECT 传来的最原始的目标 POD IP 和目标端口替换成当前报文的目标IP和目标端口 (主要是为了替换 15008 端口为原始目标端口,目标 IP 本身都是目标 POD IP)。 218 | 219 | 最后,报文将只修改目的端口,其余均保持不变,再次被 envoy 转发出去,通过 veth pair 到达节点 root ns 的 vethxxx 网卡,由于目的端口不再是 -------------------------------------------------------------------------------- /content/ambient/progress.md: -------------------------------------------------------------------------------- 1 | # 最新进展 2 | 3 | ## 当前阶段 4 | 5 | 当前处于试验阶段,预计2022年底或2023年初 beta。 6 | 7 | ## 代码分支 8 | 9 | 当前 ambient 模式的代码还没有合入主干,在 [experimental-ambient](https://github.com/istio/istio/tree/experimental-ambient) 分支。[这个 issue](https://github.com/istio/istio/issues/40879) 在跟进合入 master 之前需要完成的重要事项。 10 | 11 | ## 已知问题 12 | 13 | ### 环境适配问题 14 | 15 | 当前还处于早期阶段,很多环境都不支持,比如 mac m1 电脑上使用 kind 创建的集群、使用了网桥的网络模式的集群、某些使用策略路由实现的容器网络等等。 16 | 17 | ### ztunnel 问题 18 | 19 | 当前 ztunnel 使用 envoy 实现,存在一些列问题,社区也在考虑替代方案,改进或者用 Rust 写一个,详见 [这个 issue](https://github.com/istio/istio/issues/40956)。 20 | 21 | ### 其它问题 22 | 23 | 更多 ambient 相关 issue 看 [这里](https://github.com/istio/istio/labels/area%2Fambient)。 24 | 25 | ## 参考资料 26 | 27 | - [Istio Ambient Weekly Meeting Notes](https://docs.google.com/document/d/1SMlwliEnthgq7r2PjpLl1kCq3t8rAMbgu6r_lDAXJ0w) 28 | -------------------------------------------------------------------------------- /content/appendix/bug.md: -------------------------------------------------------------------------------- 1 | # 已知 BUG 2 | 3 | ## envoy 内存不释放 4 | 5 | * 已知受影响的版本: istio 1.5.4 6 | * 已解决版本: istio 1.6.0 7 | * issue: [#25145](https://github.com/istio/istio/issues/25145) 8 | 9 | -------------------------------------------------------------------------------- /content/appendix/link.md: -------------------------------------------------------------------------------- 1 | # 高频使用链接 2 | 3 | ## istio 相关 4 | 5 | * [istio版本跟踪](https://i.cloudnative.to/istio/release/overview) 6 | * [istio-handbook](https://www.servicemesher.com/istio-handbook/) 7 | * [云原生学院B站视频](https://space.bilibili.com/515485124) 8 | * [istio 端口列表](https://istio.io/latest/docs/ops/deployment/requirements/#ports-used-by-istio) 9 | * [istio annotation 列表](https://istio.io/latest/docs/reference/config/annotations/) 10 | * [istio 与 k8s 版本兼容性矩阵](https://istio.io/latest/about/supported-releases/#support-status-of-istio-releases) 11 | 12 | ## Envoy 相关 13 | 14 | * [Envoy 15000 管理端口接口列表](https://www.envoyproxy.io/docs/envoy/latest/operations/admin) 15 | * [Envoy response flags](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#config-access-log-format-response-flags) 16 | -------------------------------------------------------------------------------- /content/appendix/shell.md: -------------------------------------------------------------------------------- 1 | # 实用脚本 2 | 3 | ## istioctl 4 | 5 | ### 查看 sidecar 证书是否正常 6 | 7 | ```bash 8 | $ istioctl proxy-config secret accountdeletecgi-5b9d6b586-wzb7b 9 | RESOURCE NAME TYPE STATUS VALID CERT SERIAL NUMBER NOT AFTER NOT BEFORE 10 | default Cert Chain ACTIVE true 198001071566761875257861959297039696827 2021-04-16T03:33:03Z 2021-04-15T03:33:03Z 11 | ROOTCA CA ACTIVE true 205820131934050053680230040513977871884 2031-03-24T02:58:23Z 2021-03-26T02:58:23Z 12 | ``` 13 | 14 | ### 查看 sidecar 证书详情 15 | 16 | ```bash 17 | $ istioctl -n istio-test proxy-config secret productpage-v1-578c57988-j8g9d -o json | jq '[.dynamicActiveSecrets[] | select(.name == "default")][0].secret.tlsCertificate.certificateChain.inlineBytes' -r | base64 -d | openssl x509 -noout -text 18 | Certificate: 19 | Data: 20 | Version: 3 (0x2) 21 | Serial Number: 22 | 1b:6c:19:da:04:db:cb:1b:29:48:f8:09:60:35:22:75 23 | Signature Algorithm: sha256WithRSAEncryption 24 | Issuer: L=cls-5u6csmhf, O=Istio, CN=Intermediate CA 25 | Validity 26 | Not Before: Apr 15 03:57:51 2021 GMT 27 | Not After : Apr 16 03:57:51 2021 GMT 28 | Subject: 29 | Subject Public Key Info: 30 | Public Key Algorithm: rsaEncryption 31 | Public-Key: (2048 bit) 32 | Modulus: 33 | 00:dd:4f:bb:65:fd:d2:9c:7d:29:00:a9:6b:8c:b2: 34 | 8b:12:17:5f:6f:1b:d6:db:a2:7a:69:23:21:6a:d1: 35 | 38:4e:44:d0:c9:f4:6d:13:e9:97:86:54:f2:30:e6: 36 | fe:9e:41:7c:95:a7:20:ff:bb:de:62:8e:49:58:90: 37 | 7a:38:be:15:f1:96:6e:ff:7a:c4:61:d8:a8:25:f1: 38 | 92:ee:33:ae:86:bb:63:38:2c:e7:32:a5:11:be:79: 39 | 3e:83:67:17:4e:91:df:0a:3e:52:11:60:9a:83:5d: 40 | e4:92:9a:f6:29:43:7e:60:13:03:4d:ed:fc:d1:5c: 41 | e9:5b:a9:a6:ef:b8:f5:82:78:a1:ef:15:43:17:40: 42 | b3:48:c2:27:33:ac:0e:aa:00:c9:da:3f:ee:5d:1a: 43 | d7:7a:4f:e3:e0:26:e8:67:1a:c1:44:c5:f3:d0:1c: 44 | e1:e4:53:a5:a8:0b:04:47:cd:df:d2:a9:1b:47:8f: 45 | 3e:dc:9a:b6:b3:a8:6d:47:da:4d:68:dd:4f:82:3f: 46 | aa:25:6d:8e:c5:8c:9d:1e:7c:93:4c:55:a3:59:d7: 47 | a6:42:04:05:52:01:6d:a1:c8:8f:67:48:b4:16:4b: 48 | 46:6e:1e:5b:97:65:99:fe:5f:f7:f2:ba:ea:3f:34: 49 | 28:f1:e6:18:4d:d9:de:00:f2:fd:4a:9c:f9:a5:e2: 50 | 9d:5b 51 | Exponent: 65537 (0x10001) 52 | X509v3 extensions: 53 | X509v3 Key Usage: critical 54 | Digital Signature, Key Encipherment 55 | X509v3 Extended Key Usage: 56 | TLS Web Server Authentication, TLS Web Client Authentication 57 | X509v3 Basic Constraints: critical 58 | CA:FALSE 59 | X509v3 Authority Key Identifier: 60 | keyid:A0:62:8E:B4:53:64:D9:1D:DC:21:41:D8:05:93:E4:6D:27:82:20:4E 61 | 62 | X509v3 Subject Alternative Name: critical 63 | URI:spiffe://cluster.local/ns/istio-test/sa/bookinfo-productpage 64 | Signature Algorithm: sha256WithRSAEncryption 65 | ab:0d:b5:e6:df:50:02:d2:85:47:62:18:b0:af:89:cc:3a:06: 66 | a1:19:a8:2c:58:9c:e4:1d:34:3b:f8:a2:a7:f6:f8:0e:af:a8: 67 | 1b:35:79:9d:72:a2:a9:96:14:37:c8:76:e2:50:ae:d4:c6:33: 68 | 43:a5:0e:e4:c9:95:a8:81:9a:6a:72:e5:eb:3c:55:20:70:a4: 69 | 27:3c:6d:88:da:03:75:3a:99:d0:72:c2:b3:2e:66:9e:00:9a: 70 | 13:c5:61:20:fc:35:99:30:93:33:e6:8a:2d:b4:b0:0f:23:3a: 71 | a1:3d:4f:01:bf:cc:2b:38:2a:41:23:13:31:52:84:d7:8d:cb: 72 | 71:63:28:e6:1f:1f:95:20:41:63:1a:a6:5f:a5:d0:3b:35:97: 73 | 4b:8d:6c:55:59:34:e2:36:ff:a0:38:4c:f0:1f:a3:16:bf:bc: 74 | 75:53:35:20:60:b2:0d:4d:bd:d1:ab:a6:28:60:e4:d7:0c:e3: 75 | cc:19:cb:d1:4c:e7:3d:fc:21:aa:eb:e6:f4:a6:0f:ed:cd:da: 76 | db:ae:4c:fa:cf:55:f8:ea:d1:55:d5:6c:51:95:3f:47:13:b7: 77 | 20:e2:5d:cc:b0:ea:8d:99:e1:9f:40:df:d3:97:af:a5:69:f4: 78 | c6:b7:9c:c4:55:67:47:59:2b:53:40:f2:48:88:9b:75:77:00: 79 | 22:98:f7:61:74:05:8c:8b:e4:1f:be:c8:e9:7a:8f:9a:5d:ff: 80 | 1d:48:0a:e9:75:da:1e:35:93:a4:a0:c0:f8:78:bc:25:a2:63: 81 | d3:35:83:1f:15:28:a7:31:de:5a:d8:ae:56:f8:8c:ea:da:13: 82 | 01:81:aa:6f:0f:a5:39:78:e6:b6:e3:1c:ff:7c:03:50:22:04: 83 | 64:0a:dc:14:2c:ed:7d:ec:91:73:dc:44:3e:60:bc:d8:69:c3: 84 | 7c:5b:d5:16:53:1c:24:2e:1b:51:fb:93:31:37:b3:80:e6:f2: 85 | 07:46:09:8d:d5:2c:a4:f4:e3:14:b3:d9:d7:de:de:9c:bf:84: 86 | 67:66:e1:b9:85:26:1c:8f:5c:8d:9f:5f:53:b7:ed:c7:2b:9d: 87 | 57:3f:3c:d6:86:f4:d8:d8:72:c3:4c:be:5e:48:a4:ac:b9:c5: 88 | b1:6c:4b:dc:83:a2:bc:80:c2:34:c3:1a:68:7f:e8:e8:b9:eb: 89 | 39:2a:6d:3d:2d:90:e2:9c:52:dc:a2:99:e3:dc:dc:5a:f7:71: 90 | 9d:5f:67:93:d6:e3:68:a2:f9:7b:6e:64:a6:0c:09:95:f6:28: 91 | 02:e4:3f:63:fc:09:12:f7:8f:ce:4a:c3:38:02:0c:35:64:f1: 92 | 74:93:36:93:6d:e2:8e:5b:07:b9:5a:f8:14:32:69:4f:64:8d: 93 | 6e:a4:b0:95:73:36:b6:92 94 | ``` 95 | * 确保 `Subject Alternative Name` 包含正确的 spiffe URI,如 `URI:spiffe://cluster.local/ns/istio-test/sa/bookinfo-productpage`。 96 | -------------------------------------------------------------------------------- /content/appendix/yaml.md: -------------------------------------------------------------------------------- 1 | # 实用 YAML 2 | 3 | ## sidecar 注入相关 4 | 5 | ### 为指定 workload 取消 sidecar 自动注入 6 | 7 | ```yaml 8 | template: 9 | metadata: 10 | annotations: 11 | sidecar.istio.io/inject: "false" 12 | ``` 13 | 14 | ## proxy 相关 15 | 16 | ### 自定义 request/limit 17 | 18 | ```yaml 19 | template: 20 | metadata: 21 | annotations: 22 | "sidecar.istio.io/proxyCPU": "10m" 23 | "sidecar.istio.io/proxyCPULimit": "2" 24 | "sidecar.istio.io/proxyMemory": "32Mi" 25 | "sidecar.istio.io/proxyMemoryLimit": "1Gi" 26 | ``` 27 | 28 | ### 自定义日志级别 29 | 30 | ```yaml 31 | template: 32 | metadata: 33 | annotations: 34 | "sidecar.istio.io/logLevel": debug # 可选: trace, debug, info, warning, error, critical, off 35 | "sidecar.istio.io/componentLogLevel": "ext_authz:trace,filter:debug" 36 | ``` 37 | * [envoy component logging 说明](https://www.envoyproxy.io/docs/envoy/latest/operations/cli#cmdoption-component-log-level) 38 | 39 | ### 不劫持部分外部地址的流量以提升性能(比如外部数据库) 40 | 41 | ```yaml 42 | template: 43 | metadata: 44 | annotations: 45 | traffic.sidecar.istio.io/excludeOutboundIPRanges: "10.10.31.1/32,10.10.31.2/32" 46 | ``` 47 | 48 | ## mtls 配置 49 | 50 | ### 全局禁用 mtls 51 | 52 | ```yaml 53 | apiVersion: security.istio.io/v1beta1 54 | kind: PeerAuthentication 55 | metadata: 56 | name: default 57 | namespace: istio-system 58 | spec: 59 | mtls: 60 | mode: DISABLE 61 | ``` 62 | 63 | ## DestinationRule 相关 64 | 65 | ### 为某个服务启用地域感知 66 | 67 | 地域感知行为需要显式指定 `outlierDetection` 后才会启用: 68 | 69 | ```yaml 70 | apiVersion: networking.istio.io/v1beta1 71 | kind: DestinationRule 72 | metadata: 73 | name: nginx 74 | spec: 75 | host: nginx 76 | trafficPolicy: 77 | outlierDetection: 78 | consecutive5xxErrors: 3 79 | interval: 30s 80 | baseEjectionTime: 30s 81 | ``` -------------------------------------------------------------------------------- /content/best-practices/graceful-shutdown.md: -------------------------------------------------------------------------------- 1 | # 优雅终止 2 | 3 | ## 概述 4 | 5 | 本文介绍在 istio 场景下实现优雅终止时需要重点关注的点,一些容器场景通用的关注点请参考 [Kubenretes 最佳实践: 优雅终止](https://imroc.cc/kubernetes/best-practices/graceful-shutdown/index.html) 。 6 | 7 | ## envoy 被强杀导致流量异常 8 | 9 | 当业务上了 istio 之后,流量被 sidecar 劫持,进程之间不会直接建立连接,而是经过了 sidecar 这一层代理: 10 | 11 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F09%2F22%2F20230922191512.png) 12 | 13 | 当 Pod 开始停止时,它将从服务的 endpoints 中摘除掉,不再转发流量给它,同时 Sidecar 也会收到 `SIGTERM` 信号,立刻不再接受 inbound 新连接,但会保持存量 inbound 连接继续处理,outbound 方向流量仍然可以正常发起。 14 | 15 | 不过有个值得注意的细节,若 Pod 没有很快退出,istio 默认是会在停止开始的 5s 后强制杀死 envoy,当 envoy 进程不在了也就无法转发任何流量(不管是 inbound 还是 outbound 方向),所以就可能存在一些问题: 16 | 17 | 1. 若被停止的服务提供的接口耗时本身较长(比如文本转语音),存量 inbound 请求可能无法被处理完就断开了。 18 | 2. 若停止的过程需要调用其它服务(比如通知其它服务进行清理),outbound 请求可能会调用失败。 19 | 20 | ## 启用 EXIT_ON_ZERO_ACTIVE_CONNECTIONS 21 | 22 | 自 istio 1.12 开始,Sidecar 支持了 `EXIT_ON_ZERO_ACTIVE_CONNECTIONS` 这个环境变量,作用就是等待 Sidecar “排水” 完成,在响应时,也通知客户端去关闭长连接(对于 HTTP1 响应 “Connection: close” 这个头,对于 HTTP2 响应 GOAWAY 这个帧)。 23 | 24 | 如果想要全局启用,可以修改全局配置的 configmap,在 `defaultConfig.proxyMetadata` 下加上这个环境变量: 25 | 26 | ```yaml 27 | defaultConfig: 28 | proxyMetadata: 29 | EXIT_ON_ZERO_ACTIVE_CONNECTIONS: "true" 30 | ``` 31 | 32 | 如果想针对指定工作负载启用,可以给 Pod 加注解: 33 | 34 | ```yaml 35 | proxy.istio.io/config: '{ "proxyMetadata": { "EXIT_ON_ZERO_ACTIVE_CONNECTIONS": "true" } }' 36 | ``` 37 | 38 | 若使用的是腾讯云服务网格 TCM,可以在控制台启用: 39 | 40 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/20230104110821.png) 41 | -------------------------------------------------------------------------------- /content/best-practices/optimize-performance.md: -------------------------------------------------------------------------------- 1 | # istio 性能优化 2 | 3 | ## istiod 负载均衡 4 | 5 | envoy 定时重连。 6 | 7 | ## istiod HPA 8 | 9 | istiod 无状态,可水平扩展。 10 | 11 | ## xDS 按需下发 12 | 13 | * xDS 懒加载 ([aeraki-mesh/lazyxds](https://github.com/aeraki-mesh/lazyxds)) 14 | * [Delta xDS](https://docs.google.com/document/d/1hwC81_jS8qARBDcDE6VTxx6fA31In96xAZWqfwnKhpQ/edit#heading=h.xw1gqgyqs5b) 15 | 16 | ## batch 推送间隔优化 17 | 18 | istiod推送流控规则有合并推送策略,目前这个时间间隔默认值为100ms。可配,一般很少用户会关心这个,在 mesh 全局配置中可以改: PILOT_DEBOUNCE_AFTER 和 PILOT_DEBOUNCE_MAX。 主要取决于:用户期望流控规则更新的实时性,以及 istiod 稳定性的权衡,如果期望实时性高,则把防抖动时间设置短些,如果mesh规模大,希望istiod提高稳定性,则把防抖动时间设置长些。 19 | 20 | ## 关闭不必要的遥测 21 | 22 | 如果启用链路追踪,生产中的采样率可以调到 1%。 23 | 24 | ## 关闭 mtls 25 | 26 | 如果认为集群内是安全的,可以关掉 mtls 以提升性能。 27 | 28 | ## 限制 namespace 以减少 sidecar 资源占用 29 | 30 | istio 默认会下发 mesh 内集群服务所有可能需要的信息,以便让 sidecar 能够与任意 workload 通信。当集群规模较大,服务数量多,namespace 多,可能就会导致 sidecar 占用资源很高 (比如十倍于业务容器)。 31 | 32 | 如果只有部分 namespace 使用了 istio (sidecar 自动注入),而网格中的服务与其它没有注入 sidecar 的 namespace 的服务没有多大关系,可以配置下 istio 的 `Sidecar` 资源,限制一下 namespace,避免 sidecar 加载大量无用 outbound 的规则。 33 | 34 | **配置方法** 35 | 36 | ```yaml 37 | apiVersion: networking.istio.io/v1beta1 38 | kind: Sidecar 39 | metadata: 40 | name: default 41 | namespace: istio-system 42 | spec: 43 | egress: 44 | - hosts: 45 | - "prod/*" 46 | - "test/*" 47 | ``` 48 | 49 | * 定义在 istio-system 命名空间下表示 Sidecar 配置针对所有 namespace 生效。 50 | * 在 egress 的 hosts 配置中加入开启了 sidecar 自动注入的 namespace,表示只下发这些跟这些 namespace 相关的 outbound 规则。 51 | 52 | > 参考 [Istio Sidecar 官方文档](https://istio.io/latest/docs/reference/config/networking/sidecar/) 53 | -------------------------------------------------------------------------------- /content/best-practices/set-default-route.md: -------------------------------------------------------------------------------- 1 | # 为服务设置默认路由 2 | 3 | 很多时候一开始我们的服务没有多个版本,也没配置 vs,只有一个 deployment 和一个 svc,如果我们要为业务配置默认流量策略,可以直接创建 dr,给对应 host 设置 trafficPolicy,示例: 4 | 5 | ```yaml 6 | apiVersion: networking.istio.io/v1alpha3 7 | kind: DestinationRule 8 | metadata: 9 | name: reviews 10 | spec: 11 | host: reviews 12 | trafficPolicy: 13 | connectionPool: 14 | tcp: 15 | maxConnections: 100 16 | subsets: 17 | - name: v1 18 | labels: 19 | version: v1 20 | ``` 21 | 22 | 需要注意的是,虽然 subsets 下也可以设置 trafficPolicy,但 subsets 下设置的不是默认策略,而且在没有 vs 明确指定路由到对应 subset 时,即便我们的服务只有一个版本,istio 也不会使用 subset 下指定的 trafficPolicy,错误示例: 23 | 24 | ```yaml 25 | apiVersion: networking.istio.io/v1alpha3 26 | kind: DestinationRule 27 | metadata: 28 | name: reviews 29 | spec: 30 | host: reviews 31 | subsets: 32 | - name: v1 33 | labels: 34 | version: v1 35 | trafficPolicy: 36 | connectionPool: 37 | tcp: 38 | maxConnections: 100 39 | ``` 40 | 41 | 想要做的更好,可以定义下 vs,明确路由到指定版本(后续就可以针对不同版本指定不同的流量策略): 42 | 43 | ```yaml 44 | apiVersion: networking.istio.io/v1alpha3 45 | kind: VirtualService 46 | metadata: 47 | name: reviews 48 | spec: 49 | hosts: 50 | - reviews 51 | http: 52 | - route: 53 | - destination: 54 | host: reviews 55 | subset: v1 56 | ``` -------------------------------------------------------------------------------- /content/best-practices/specify-protocol.md: -------------------------------------------------------------------------------- 1 | # 为服务显式指定协议 2 | 3 | ## 背景 4 | 5 | istio 需要知道服务提供什么七层协议,从而来为其配置相应协议的 filter chain,通常最好是显式声明协议,如果没有声明,istio 会自动探测,这个探测能力比较有限,有些时候可能会匹配协议错误(比如使用非标端口),导致无法正常工作。 6 | 7 | 本文将列出显示声明协议的方法。 8 | 9 | ## 集群内: 指定 Service 端口的协议 10 | 11 | 给集群内 Service 指定 port name 时加上相应的前缀或指定 `appProtocol` 字段可以显示声明协议,如: 12 | 13 | ```yaml 14 | kind: Service 15 | metadata: 16 | name: myservice 17 | spec: 18 | ports: 19 | - number: 8080 20 | name: rpc 21 | appProtocol: grpc # 指定该端口提供 grpc 协议的服务 22 | - number: 80 23 | name: http-web # 指定该端口提供 http 协议的服务 24 | ``` 25 | 26 | 更多详细信息请参考 [Explicit protocol selection](https://istio.io/latest/docs/ops/configuration/traffic-management/protocol-selection/#explicit-protocol-selection) 。 27 | 28 | ## 集群外: 手动创建 Service + Endpoint 29 | 30 | 如果服务在集群外部 (比如 mysql),我们可以为其手动创建一组 Service + Endpoint,且 Service 端口指定协议(跟上面一样),这样就可以在集群内通过 Service 访问外部服务,且正确识别协议。示例: 31 | 32 | ```yaml 33 | apiVersion: v1 34 | kind: Service 35 | metadata: 36 | name: mysql 37 | namespace: default 38 | spec: 39 | ports: 40 | - port: 4000 41 | name: mysql 42 | protocol: TCP 43 | 44 | --- 45 | apiVersion: v1 46 | kind: Endpoints 47 | metadata: 48 | name: mysql 49 | namespace: default 50 | subsets: 51 | - addresses: 52 | - ip: 190.64.31.232 # 替换外部服务的 IP 地址 53 | ports: 54 | - port: 4000 55 | name: mysql 56 | protocol: TCP 57 | ``` 58 | 59 | 创建好之后就可以通过 svc 去访问外部服务了,本例中服务地址为: `mysql.default.svc.cluster.local:4000`。 60 | 61 | ## 集群外: 使用 ServiceEntry 指定协议 62 | 63 | 如果外部服务可以被 DNS 解析,可以定义 ServiceEntry 来指定协议: 64 | 65 | ```yaml 66 | apiVersion: networking.istio.io/v1beta1 67 | kind: ServiceEntry 68 | metadata: 69 | name: external-mysql 70 | spec: 71 | hosts: 72 | - mysql.example.com 73 | location: MESH_EXTERNAL 74 | ports: 75 | - number: 4000 76 | name: mysql 77 | protocol: mysql 78 | resolution: DNS 79 | ``` 80 | 81 | 创建好之后就可以通过域名去访问外部服务了,本例中服务地址为: `mysql.example.com:4000`。 -------------------------------------------------------------------------------- /content/faq/grpc-not-loadbalancing.md: -------------------------------------------------------------------------------- 1 | # GRPC 服务负载不均 2 | 3 | ## 现象 4 | 5 | grpc 调用,同一个 client 的请求始终只打到同一个 server 的 pod,造成负载不均。 6 | 7 | ## 分析 8 | 9 | grpc 是基于 http2 的长连接,多次请求复用同一个连接。如果不用 istio,只用普通的 k8s service,是不会感知 grpc 协议的,只当成 tcp 来转发,在连接层面做负载均衡,不会在请求层面做负载均衡。但在 istio 中,默认会对 grpc 的请求进行请求级别的负载均衡,如果发现负载不均,通常是没有正确配置。 10 | 要让 grpc 在请求级别进行负载均衡,核心就是让 istio 正确识别是 grpc 协议,不要配置成 tcp,用 tcp 的话就只能在连接级别进行负载均衡了,请求级别可能就会负载不均。 11 | 12 | ## 解决方法 13 | 14 | 1. 如果要对外暴露,gateway 里 protocal 配置 GRPC 不用 TCP,示例: 15 | 16 | ```yaml 17 | apiVersion: networking.istio.io/v1beta1 18 | kind: Gateway 19 | metadata: 20 | name: grpc-gw 21 | namespace: demo 22 | spec: 23 | selector: 24 | app: istio-ingressgateway 25 | istio: ingressgateway 26 | servers: 27 | - hosts: 28 | - '*' 29 | port: 30 | name: grpc-demo-server 31 | number: 9000 32 | protocol: GRPC # 这里使用 GRPC 不用 TCP 33 | ``` 34 | 35 | 2. 如果定义了 vs,需要使用 http 匹配而不用 tcp,因为 grpc 在 istio 中匹配也是用的 http 字段,示例: 36 | 37 | ```yaml 38 | apiVersion: networking.istio.io/v1beta1 39 | kind: VirtualService 40 | metadata: 41 | name: grpc-svc 42 | namespace: demo 43 | spec: 44 | gateways: 45 | - demo/grpc-gw 46 | hosts: 47 | - '*' 48 | http: # 这里使用 http 不用 tcp 49 | - match: 50 | - port: 9000 51 | route: 52 | - destination: 53 | host: grpc.demo.svc.cluster.local 54 | port: 55 | number: 9000 56 | weight: 100 57 | ``` 58 | 59 | 3. 部署服务的 service 的 port name 需要使用 "grpc-" 开头定义,让 istio 能够正确识别,示例: 60 | 61 | ```yaml 62 | apiVersion: v1 63 | kind: Service 64 | metadata: 65 | name: grpc 66 | namespace: demo 67 | spec: 68 | ports: 69 | - name: grpc-9000 # 以 grpc- 开头 70 | port: 9000 71 | protocol: TCP 72 | targetPort: 9000 73 | selector: 74 | app: grpc 75 | type: ClusterIP 76 | ``` 77 | 78 | > 更多协议指定方式请参考 [istio 最佳实践: 为服务显式指定协议](../best-practices/specify-protocol.md) -------------------------------------------------------------------------------- /content/faq/headless-svc.md: -------------------------------------------------------------------------------- 1 | # headless service 相关问题 2 | 3 | ## 服务间通过注册中心调用响应 404 4 | 5 | * 现象: 传统服务 (比如 Srping Cloud) 迁移到 istio 后,服务间调用返回 404。 6 | * 原因: 没走 Kubernetes 的服务发现,而是通过注册中心获取服务 IP 地址,然后服务间调用不经过域名解析,直接向获取到的目的 IP 发起调用。由于 istio 的 LDS 会拦截 headless service 中包含的 PodIP+Port 的请求,然后匹配请求 hosts,如果没有 hosts 或者 hosts 中没有这个 PodIP+Port 的 service 域名 (比如直接是 Pod IP),就会匹配失败,最后返回 404。 7 | * 解决方案: 8 | 1. 注册中心不直接注册 Pod IP 地址,注册 service 域名。 9 | 2. 或者客户端请求时带上 hosts (需要改代码)。 10 | 11 | ## 负载均衡策略不生效 12 | 13 | 由于 istio 默认对 headless service 进行 passthrougth,使用 `ORIGINAL_DST` 转发,即直接转发到原始的目的 IP,不做任何的负载均衡,所以 `Destinationrule` 中配置的 `trafficPolicy.loadBalancer` 都不会生效,影响的功能包括: 14 | * 会话保持 (consistentHash) 15 | * 地域感知 (localityLbSetting) 16 | 17 | 解决方案: 单独再创建一个 service (非 headless) 18 | 19 | ## 访问不带 sidecar 的 headless service 失败 20 | 21 | * 现象: client (有sidecar) 通过 headless service 访问 server (无sidecar),访问失败,access log 中可以看到 response_flags 为 `UF,URX`。 22 | * 原因: istio 1.5/1.6 对 headless service 支持有个 bug,不管 endpoint 有没有 sidecar,都固定启用 mtls,导致没有 sidecar 的 headless 服务(如 redis) 访问被拒绝 (详见 [#21964](https://github.com/istio/istio/issues/21964)) ,更多细节可参考 [Istio 运维实战系列(2):让人头大的『无头服务』-上](https://zhaohuabing.com/post/2020-09-11-headless-mtls/)。 23 | 24 | **解决方案一:** 配置 `DestinationRule` 禁用 mtls 25 | 26 | ```yaml 27 | kind: DestinationRule 28 | metadata: 29 | name: redis-disable-mtls 30 | spec: 31 | host: redis.default.svc.cluster.local 32 | trafficPolicy: 33 | tls: 34 | mode: DISABLE 35 | ``` 36 | 37 | **解决方案二:** 升级 istio 到 1.7 及其以上的版本。 38 | 39 | ## pod 重建后访问失败 40 | 41 | * 现象: client 通过 headless service 访问 server,当 server 的 pod 发生重建后,client 访问 server 失败,access log 中可以看到 response_flags 为 `UF,URX`。 42 | * 原因: istio 1.5 对 headless service 支持的 bug。 43 | * client 通过 dns 解析 headless service,返回其中一个 Pod IP,然后发起请求。 44 | * envoy 检测到是 headless service,使用 `ORIGINAL_DST` 转发,即不做负载均衡,直接转发到原始的目的 IP。 45 | * 当 headless service 的 pod 发生重建,由于 client 与它的 sidecar (envoy) 是长连接,所以 client 侧的连接并没有断开。 46 | * 又由于是长连接,client 继续发请求并不会重新解析 dns,而是仍然发请求给之前解析到的旧 Pod IP。 47 | * 由于旧 Pod 已经销毁,Envoy 会返回错误 (503)。 48 | * 客户端并不会因为服务端返回错误而断开连接,后续请求继续发给旧的 Pod IP,如此循环,一直失败。 49 | * 更多详情参考 [Istio 运维实战系列(3):让人头大的『无头服务』-下](https://zhaohuabing.com/post/2020-09-19-headless-mtls/) 。 50 | 51 | **解决方案:** 升级 istio 到 1.6 及其以上的版本,Envoy 在 Upstream 链接断开后会主动断开和 Downstream 的长链接。 -------------------------------------------------------------------------------- /content/faq/listen-any.md: -------------------------------------------------------------------------------- 1 | # 应用未监听 0.0.0.0 导致连接异常 2 | 3 | ## 背景 4 | 5 | istio 要求应用提供服务时监听 `0.0.0.0`,因为 127 和 Pod IP 地址都被 envoy 占用了。有些应用启动时没有监听 `0.0.0.0` 或 `::` 的地址,就会导致无法正常通信,参考 [Application Bind Address](https://istio.io/latest/docs/ops/deployment/requirements/#application-bind-address) 。 6 | 7 | ## 案例: zookeeper 8 | 9 | 当 zookeeper 部署到集群中时,默认监听的 Pod IP,会导致 zookeeper 各个实例之间无法正常通信。 10 | 11 | 解决方案: 在 zk 的配置文件中键入 [quorumListenOnAllIPs=true](https://zookeeper.apache.org/doc/r3.5.7/zookeeperAdmin.html) 的配置 ( 参考 [istio官方文档](https://istio.io/v1.8/faq/applications/#zookeeper) ) 12 | 13 | ## istio 1.10 14 | 15 | 在 istio 1.10 及其以上的版本,应用将无需对端口监听进行特殊处理,即如果应用只监听 eth0 (pod ip) 也能正常使用,详细参考官方博客 [Upcoming networking changes in Istio 1.10](https://istio.io/latest/blog/2021/upcoming-networking-changes/) 。 -------------------------------------------------------------------------------- /content/faq/multicluster.md: -------------------------------------------------------------------------------- 1 | # 多集群相关问题 2 | 3 | ## 概述 4 | 5 | 本文介绍 istio 多集群下需要注意的问题。 6 | 7 | ## 跨集群访问 service 8 | 9 | 同一网格的多个集群之间通过 service 调用,可能调用失败,报错 dns 解析失败。 10 | 11 | 原因可能是没启用 Smart DNS,无法自动解析其它集群的 service 域名,可以通过手动在本集群创建跟对端集群一样的 service 来解决,也可以启用 Smart DNS 自动解析。 -------------------------------------------------------------------------------- /content/faq/retries-for-non-idempotent-services.md: -------------------------------------------------------------------------------- 1 | # 默认的重试策略导致非幂等服务异常 2 | 3 | ## 背景 4 | 5 | Istio 为 Envoy 设置了缺省的重试策略,会在 connect-failure,refused-stream, unavailable, cancelled, retriable-status-codes 等情况下缺省重试两次。出现错误时,可能已经触发了服务器逻辑,在操作不是幂等的情况下,可能会导致错误。 6 | 7 | 8 | ## 解决方案 9 | 10 | 可以通过配置 VS 关闭重试: 11 | 12 | ```yaml 13 | apiVersion: networking.istio.io/v1alpha3 14 | kind: VirtualService 15 | metadata: 16 | name: ratings 17 | spec: 18 | hosts: 19 | - ratings 20 | http: 21 | - retries: 22 | attempts: 0 23 | ``` 24 | -------------------------------------------------------------------------------- /content/faq/sidecar-injection.md: -------------------------------------------------------------------------------- 1 | # Sidecar 注入相关问题 2 | 3 | ## 可以只在一端注入 sidecar 吗? 4 | 5 | * Q: 只在客户端和服务端其中一方注入 sidecar,是否能够正常工作呢? 6 | * A: 一般是建议都注入。有些功能在 outbound 和 inbound 端都需要,有些只在其中一端需要,下面一张图可以一目了然: 7 | 8 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F09%2F22%2F20230922191345.png) 9 | 10 | -------------------------------------------------------------------------------- /content/faq/sidecar-shutdown.md: -------------------------------------------------------------------------------- 1 | # Sidecar 停止问题 2 | 3 | ## 背景 4 | 5 | Istio 在 1.1 版本之前有个问题: Pod 销毁时,如果进程在退出过程中继续调用其它服务 (比如通知另外的服务进行清理),会调用失败。 6 | 7 | 更多详细信息请参考 issue [#7136: Envoy shutting down before the thing it's wrapping can cause failed requests ](https://github.com/istio/istio/issues/7136) 。 8 | 9 | ## 原因 10 | 11 | Kubernetes 在销毁 Pod 的过程中,会同时给所有容器发送 SIGTERM 信号,所以 Envoy 跟业务容器同时开始停止,Envoy 停止过程中不接受 inbound 新连接,默认在 5s 内会接收 outbound 新连接,5s 后 envoy 被强制杀死。又由于 istio 会进行流量劫持,所有 outbound 流量都会经过 Envoy 进行转发,如果 Envoy 被杀死,outbound 流量无法被转发,就会导致业务调用其它服务失败。 12 | 13 | ## 社区解决方案 14 | 15 | 如果 Kubernetes 自身支持容器依赖管理,那这个问题自然就可以解决掉。社区也提出了 [Sidecar Container](https://github.com/kubernetes/enhancements/issues/753) 的特性,只可惜最终还是被废弃了,新的方案还未落地,详细可参考 [这篇笔记](https://imroc.cc/k8s/kep/sidecar-containers.html) 。 16 | 17 | 后来随着 istio 社区的推进,针对优雅终止场景进行了一些优化: 18 | 19 | * 2019-02: Liam White 提交 PR [Envoy Graceful Shutdown](https://github.com/istio/istio/pull/11485) ,让 Pod 在停止过程中 Envoy 能够实现优雅停止 (保持存量连接继续处理,但拒绝所有新连接),等待 `terminationDrainDuration` 时长后再停掉 envoy 实例。该 PR 最终被合入 istio 1.1。 20 | * 2019-11: Rama Chavali 提交 PR [move to drain listeners admin endpoint](https://github.com/istio/istio/pull/18581) ,将 Envoy 优雅停止的方式从热重启改成调用 Envoy 后来自身提供的 admin 接口 ([/drain_listeners?inboundonly](https://www.envoyproxy.io/docs/envoy/latest/operations/admin#post--drain_listeners?inboundonly)) ,重点在于带上了 `inboundonly` 参数,即仅仅拒绝 inbound 方向的新连接,outbound 的新连接仍然可以正常发起,这也使得 Pod 在停止过程中业务进程继续调用其它服务得以实现。该 PR 最终被合入 istio 1.5。 21 | 22 | 所以在 istio 1.5 及其以上的版本,在 Pod 停止期间的一小段时间内 (默认 5s),业务进程仍然可以对其它服务发请求。 23 | 24 | ## 如何解决 ? 25 | 26 | 考虑从自定义 `terminationDrainDuration` 或加 preStop 判断连接处理完两种方式之一,详细请参考 [istio 最佳实践: 优雅终止](../best-practices/graceful-shutdown.md) 。 -------------------------------------------------------------------------------- /content/faq/sidecar-startup-order.md: -------------------------------------------------------------------------------- 1 | # Sidecar 启动顺序问题 2 | 3 | ## 背景 4 | 5 | 一些服务在往 istio 上迁移过渡的过程中,有时可能会遇到 Pod 启动失败,然后一直重启,排查原因是业务启动时需要调用其它服务(比如从配置中心拉取配置),如果失败就退出,没有重试逻辑。调用失败的原因是 envoy 还没就绪(envoy也需要从控制面拉取配置,需要一点时间),导致业务发出的流量无法被处理,从而调用失败(参考 k8s issue [#65502](https://github.com/kubernetes/kubernetes/issues/65502) )。 6 | 7 | ## 最佳实践 8 | 9 | 目前这类问题的最佳实践是让应用更加健壮一点,增加一下重试逻辑,不要一上来调用失败就立马退出,如果嫌改动麻烦,也可以在启动命令前加下 sleep,等待几秒 (可能不太优雅)。 10 | 11 | 如果不想对应用做任何改动,也可以参考下面的规避方案。 12 | 13 | ## 规避方案: 调整 sidecar 注入顺序 14 | 15 | 在 istio 1.7,社区通过给 istio-injector 注入逻辑增加一个叫 `HoldApplicationUntilProxyStarts` 的开关来解决了该问题,开关打开后,proxy 将会注入到第一个 container。 16 | 17 | 18 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F09%2F22%2F20230922191401.png) 19 | 20 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F09%2F22%2F20230922191409.png) 21 | 22 | 查看 istio-injector 自动注入使用的 template,可以知道如果打开了 `HoldApplicationUntilProxyStarts` 就会为 sidecar 添加一个 postStart hook: 23 | 24 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F09%2F22%2F20230922191416.png) 25 | 26 | 它的目的是为了阻塞后面的业务容器启动,要等到 sidecar 完全启动了才开始启动后面的业务容器。 27 | 28 | 这个开关配置分为全局和局部两种,以下是启用方法。 29 | 30 | **全局配置:** 31 | 32 | 修改 istio 的 configmap 全局配置: 33 | 34 | ``` bash 35 | kubectl -n istio-system edit cm istio 36 | ``` 37 | 38 | 在 `defaultConfig` 下加入 `holdApplicationUntilProxyStarts: true` 39 | 40 | ``` yaml 41 | apiVersion: v1 42 | data: 43 | mesh: |- 44 | defaultConfig: 45 | holdApplicationUntilProxyStarts: true 46 | meshNetworks: 'networks: {}' 47 | kind: ConfigMap 48 | ``` 49 | 50 | 若使用 IstioOperator,defaultConfig 修改 CR 字段 `meshConfig`: 51 | 52 | ``` yaml 53 | apiVersion: install.istio.io/v1alpha1 54 | kind: IstioOperator 55 | metadata: 56 | namespace: istio-system 57 | name: example-istiocontrolplane 58 | spec: 59 | meshConfig: 60 | defaultConfig: 61 | holdApplicationUntilProxyStarts: true 62 | ``` 63 | 64 | 如果你使用了 TCM (Tecnet Cloud Mesh),已经产品化了该能力,直接开启 `Sidecar 就绪保障` 即可: 65 | 66 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F09%2F22%2F20230922191435.png) 67 | 68 | **局部配置:** 69 | 70 | 如果使用 istio 1.8 及其以上的版本,可以为需要打开此开关的 Pod 加上 `proxy.istio.io/config` 注解,将 `holdApplicationUntilProxyStarts` 置为 `true`,示例: 71 | 72 | ```yaml 73 | apiVersion: apps/v1 74 | kind: Deployment 75 | metadata: 76 | name: nginx 77 | spec: 78 | replicas: 1 79 | selector: 80 | matchLabels: 81 | app: nginx 82 | template: 83 | metadata: 84 | annotations: 85 | proxy.istio.io/config: | 86 | holdApplicationUntilProxyStarts: true 87 | labels: 88 | app: nginx 89 | spec: 90 | containers: 91 | - name: nginx 92 | image: "nginx" 93 | ``` 94 | 95 | 需要注意的是,打开这个开关后,意味着业务容器需要等 sidecar 完全 ready 后才能启动,会让 Pod 启动速度变慢一些。在需要快速扩容应对突发流量场景可能会显得吃力,所以建议是自行评估业务场景,利用局部配置的方法,只给需要的业务打开此开关。 96 | 97 | ## 完美方案: K8S 支持容器依赖 98 | 99 | 最完美的方案还是 Kubernetes 自身支持容器依赖,社区也提出了 [Sidecar Container](https://github.com/kubernetes/enhancements/issues/753) 的特性,只可惜最终还是被废弃了,新的方案还未落地,详细可参考 [这篇笔记](https://imroc.cc/k8s/kep/sidecar-containers/) 。 100 | 101 | 102 | ## 参考资料 103 | 104 | * [Istio 运维实战系列(1):应用容器对 Envoy Sidecar 的启动依赖问题](https://zhaohuabing.com/post/2020-09-05-istio-sidecar-dependency/#%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88) 105 | * [PR: Allow users to delay application start until proxy is ready](https://github.com/istio/istio/pull/24737) 106 | * [Kubernetes Sidecar Containers 特性调研笔记](https://imroc.cc/k8s/kep/sidecar-containers/) 107 | -------------------------------------------------------------------------------- /content/faq/smart-dns.md: -------------------------------------------------------------------------------- 1 | # Smart DNS 相关问题 2 | 3 | ## 启用 Smart DNS 后解析失败 4 | 5 | ### 现象 6 | 7 | 在启用了 istio 的 Smart DNS (智能 DNS) 后,我们发现有些情况下 DNS 解析失败,比如: 8 | 9 | * 基于 alpine 镜像的容器内解析 dns 失败。 10 | * grpc 服务解析 dns 失败。 11 | 12 | ### 原因 13 | 14 | Smart DNS 初期实现存在一些问题,响应的 DNS 数据包格式跟普通 DNS 有些差别,走底层库 glibc 解析没问题,但使用其它 dns 客户端可能就会失败: 15 | * alpine 镜像底层库使用 musl libc,解析行为跟 glibc 有些不一样,musl libc 在这种这种数据包格式异常的情况会导致解析失败,而大多应用走底层库解析,导致大部分应用解析失败。 16 | * 基于 c/c++ 的 grpc 框架的服务,dns 解析默认使用 c-ares 库,没有走系统调用让底层库解析,c-ares 在这种数据包异常情况,部分场景会解析失败。 17 | 18 | ### 修复 19 | 20 | 在 istio 1.9.2 的时候修复了这个问题,参考关键 PR [#31251](https://github.com/istio/istio/pull/31251) 以及其中一个 [issue](https://github.com/istio/istio/issues/31295) 。 21 | 22 | ### 规避 23 | 24 | 如果暂时无法升级 istio 到 1.9.2 以上,可以通过以下方式来规避: 25 | 26 | * 基础镜像从 alpine 镜像到其它镜像 (其它基础镜像底层库基本都是 glibc)。 27 | * c/c++ 的 grpc 服务,指定 `GRPC_DNS_RESOLVER` 环境变量为 `native`,表示走底层库解析,不走默认的 c-ares 库。环境变量解释参考 [GRPC 官方文档](https://github.com/grpc/grpc/blob/master/doc/environment_variables.md) 。 -------------------------------------------------------------------------------- /content/faq/the-case-of-http-header.md: -------------------------------------------------------------------------------- 1 | # HTTP Header 大小写问题 2 | 3 | ## Envoy 默认会将 Header 转换为小写 4 | 5 | Envoy 缺省会把 http header 的 key 转换为小写,例如有一个 http header `Test-Upper-Case-Header: some-value`,经过 envoy 代理后会变成 `test-upper-case-header: some-value`。这个在正常情况下没问题,[RFC 2616](https://www.ietf.org/rfc/rfc2616.txt) 规范也说明了处理 HTTP Header 应该是大小写不敏感的。 6 | 7 | ## 可能依赖大小写的场景 8 | 9 | 通常 header 转换为小写不会有问题(符合规范),有些情况对 header 大小写敏感可能就会有问题,如: 10 | * 业务解析 header 依赖大小写。 11 | * 使用的 SDK 对 Header 大小写敏感,如读取 `Content-Length` 来判断 response 长度时依赖首字母大写。 12 | 13 | ## Envoy 所支持的规则 14 | 15 | Envoy 只支持两种规则: 16 | * 全小写 (默认使用的规则) 17 | * 首字母大写 (默认没有启用) 18 | 19 | 如果应用的 http header 的大小写完全没有规律,就没有办法兼容了。 20 | 21 | 这两种是可以的: 22 | * test-upper-case-header: some-value 23 | * Test-Upper-Case-Header: some-value 24 | 25 | 类似这种就没有办法兼容了: 26 | * Test-UPPER-CASE-Header: some-value 27 | 28 | ## 规避方案: 强制指定为 TCP 协议 29 | 30 | 我们可以将服务声明为 TCP 协议,不让 istio 进行七层处理,这样就不会更改 http header 大小写了,但需要注意的是同时也会丧失 istio 的七层能力。 31 | 32 | 如果服务在集群内,可以在 Service 的 port 名称中带上 "tcp" 前缀: 33 | 34 | ```yaml 35 | kind: Service 36 | metadata: 37 | name: myservice 38 | spec: 39 | ports: 40 | - number: 80 41 | name: tcp-web # 指定该端口协议为 tcp 42 | ``` 43 | 44 | 如果服务在集群外,可以通过一个类似如下 ServiceEntry 将服务强制指定为 TCP Service,以避免 envoy 对其进行七层的处理: 45 | 46 | ```yaml 47 | apiVersion: networking.istio.io/v1alpha3 48 | kind: ServiceEntry 49 | metadata: 50 | name: qcloud-cos 51 | spec: 52 | hosts: 53 | - "private-1251349835.cos.ap-guangzhou.myqcloud.com" 54 | location: MESH_INTERNAL 55 | addresses: 56 | - 169.254.0.47 57 | ports: 58 | - number: 80 59 | name: tcp 60 | protocol: TCP 61 | resolution: DNS 62 | ``` 63 | 64 | 更多协议指定方式请参考: [为服务显式指定协议](../best-practices/specify-protocol.md) 65 | 66 | ## 最佳实践: 使用 EnvoyFilter 指定 Header 规则为首字母大写 67 | 68 | 参考[实用 EnvoyFilter: 保留 header 大小写](../usage/preserve-case.md) 69 | 70 | ## 建议 71 | 72 | 应用程序应遵循 [RFC 2616](https://www.ietf.org/rfc/rfc2616.txt) 规范,对 Http Header 的处理采用大小写不敏感的原则。 73 | -------------------------------------------------------------------------------- /content/faq/tracing.md: -------------------------------------------------------------------------------- 1 | # 链路追踪相关问题 2 | 3 | ## tracing 信息展示不完整 4 | 5 | 通过 UI 展示的链路追踪显示不完整,缺失前面或后面的调用链路。 6 | 7 | **原因** 8 | 9 | 绝大多数情况下都是因为没在业务层面将 tracing 所需要的 http header 正确传递或根本没有传递。 10 | 11 | 要在 istio 中使用链路追踪,并不是说业务无侵入,有个基本要求是:业务收到 tracing 相关的 header 要将其传递给被调服务。这个步骤是无法让 istio 帮你完成的,因为 istio 无法感知你的业务逻辑,不知道业务中调用其它服务的请求到底是该对应前面哪个请求,所以需要业务来传递 header,最终才能将链路完整串起来。 12 | 13 | **参考资料** 14 | 15 | * [What is required for distributed tracing with Istio](https://istio.io/latest/about/faq/#how-to-support-tracing) 16 | -------------------------------------------------------------------------------- /content/faq/uppercase-header-causes-sticky-sessions-to-not-work.md: -------------------------------------------------------------------------------- 1 | # httpHeaderName 大写导致会话保持不生效 2 | 3 | ## 问题描述 4 | 5 | 在 DestinationRule 中配置了基于 http header 的会话保持,header 名称大写: 6 | 7 | ```yaml 8 | trafficPolicy: 9 | loadBalancer: 10 | consistentHash: 11 | httpHeaderName: User 12 | ``` 13 | 14 | 测试会发现会话保持不生效,每次请求传入相同 Header (如 `User: roc`) 却被转发了不同后端 15 | 16 | ## 原因 17 | 18 | 应该是 envoy 默认把 header 转成小写的缘故导致不生效。 19 | 20 | ## 解决方案 21 | 22 | 定义 `httpHeaderName` 时换成小写,如: 23 | 24 | ```yaml 25 | trafficPolicy: 26 | loadBalancer: 27 | consistentHash: 28 | httpHeaderName: User 29 | ``` 30 | 31 | ## 注意事项 32 | 33 | * 如果之前已经定义了 DestinationRule,不要直接修改,而是先删除,然后再创建修改后的 DestinationRule (实测发现直接修改成可能不生效) 34 | * 客户端请求时设置的 header 大小写可以无所谓。 -------------------------------------------------------------------------------- /content/faq/vs-match-order.md: -------------------------------------------------------------------------------- 1 | # VirtualService 路由匹配顺序问题 2 | 3 | ## 背景 4 | 5 | 在写 VirtualService 路由规则时,通常会 match 各种不同路径转发到不同的后端服务,有时候不小心命名冲突了,导致始终只匹配到前面的服务,比如: 6 | 7 | ```yaml 8 | apiVersion: networking.istio.io/v1beta1 9 | kind: VirtualService 10 | metadata: 11 | name: test 12 | spec: 13 | gateways: 14 | - default/example-gw 15 | hosts: 16 | - 'test.example.com' 17 | http: 18 | - match: 19 | - uri: 20 | prefix: /usrv 21 | rewrite: 22 | uri: / 23 | route: 24 | - destination: 25 | host: usrv.default.svc.cluster.local 26 | port: 27 | number: 80 28 | - match: 29 | - uri: 30 | prefix: /usrv-expand 31 | rewrite: 32 | uri: / 33 | route: 34 | - destination: 35 | host: usrv-expand.default.svc.cluster.local 36 | port: 37 | number: 80 38 | ``` 39 | 40 | istio 匹配是按顺序匹配,不像 nginx 那样使用最长前缀匹配。这里使用 prefix 进行匹配,第一个是 `/usrv`,表示只要访问路径前缀含 `/usrv` 就会转发到第一个服务,由于第二个匹配路径 `/usrv-expand` 本身也属于带 `/usrv` 的前缀,所以永远不会转发到第二个匹配路径的服务。 41 | 42 | ## 解决方案 43 | 44 | 这种情况可以调整下匹配顺序,如果前缀有包含的冲突关系,越长的放在越前面: 45 | 46 | ```yaml 47 | apiVersion: networking.istio.io/v1beta1 48 | kind: VirtualService 49 | metadata: 50 | name: test 51 | spec: 52 | gateways: 53 | - default/example-gw 54 | hosts: 55 | - 'test.example.com' 56 | http: 57 | - match: 58 | - uri: 59 | prefix: /usrv-expand 60 | rewrite: 61 | uri: / 62 | route: 63 | - destination: 64 | host: usrv-expand.default.svc.cluster.local 65 | port: 66 | number: 80 67 | - match: 68 | - uri: 69 | prefix: /usrv 70 | rewrite: 71 | uri: / 72 | route: 73 | - destination: 74 | host: usrv.default.svc.cluster.local 75 | port: 76 | number: 80 77 | ``` 78 | 79 | 也可以用正则匹配: 80 | 81 | ```yaml 82 | apiVersion: networking.istio.io/v1beta1 83 | kind: VirtualService 84 | metadata: 85 | name: test 86 | spec: 87 | gateways: 88 | - default/gateway 89 | hosts: 90 | - 'test.example.com' 91 | http: 92 | - match: 93 | - uri: 94 | regex: "/usrv(/.*)?" 95 | rewrite: 96 | uri: / 97 | route: 98 | - destination: 99 | host: nginx.default.svc.cluster.local 100 | port: 101 | number: 80 102 | subset: v1 103 | - match: 104 | - uri: 105 | regex: "/usrv-expand(/.*)?" 106 | rewrite: 107 | uri: / 108 | route: 109 | - destination: 110 | host: nginx.default.svc.cluster.local 111 | port: 112 | number: 80 113 | subset: v2 114 | ``` 115 | -------------------------------------------------------------------------------- /content/intro/service-governance.md: -------------------------------------------------------------------------------- 1 | # 服务治理的几种方式与对比 2 | 3 | ## 治理逻辑与业务逻辑耦合 4 | 5 | 在分布式服务早期,微服务之间的调用是通过硬编码对端服务地址直接调用来实现的: 6 | 7 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F10%2F07%2F20231007162606.png) 8 | 9 | 如果有多个地址,调用端往往还得自己编写负载均衡算法、接口路由策略等治理逻辑,扩展性很差,变更起来简直就是一场灾难。 10 | 11 | ## 治理逻辑下沉到 SDK 12 | 13 | 因为治理逻辑与业务逻辑耦合的缺点,诞生了微服务框架,比如 Dubbo 和 Sprint Cloud,将治理逻辑从业务逻辑中剥离,下沉到 SDK 类库中: 14 | 15 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F10%2F07%2F20231007163426.png) 16 | 17 | ## 治理逻辑独立到应用进程之外 18 | 19 | ### 微服务框架的弊端 20 | 21 | 虽然治理逻辑下沉到 SDK 后,避免了与业务逻辑耦合,使得微服务的开发变得更加容易,但也存在一些问题: 22 | 23 | 1. 升级麻烦。虽然治理逻辑下沉到 SDK,但是 SDK 仍然需要与业务代码一起打包发布,这就导致了 SDK 与业务代码的版本耦合,SDK 升级后,业务代码也需要跟着升级,这就增加了升级的成本。 24 | 2. 更新配置麻烦。虽然治理逻辑与业务逻辑解耦,但仍需在业务代码中引用 SDK 及其配置,无法实现治理逻辑的动态更新,只能通过重启应用来实现治理逻辑的更新,这就导致了治理逻辑的变更非常困难。 25 | 3. 存在跨语言问题。通常微服务框架只针对特定编程语言,也就限制了微服务使用的开发语言。 26 | 4. 学习成本高。微服务框架通常都是一个大而全的框架,学习成本很高。 27 | 28 | ### 服务网格 Sidecar 模式 29 | 30 | 为了解决上述问题,服务网格又诞生了,典型模式是 Sidecar 模式: 31 | 32 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F10%2F07%2F20231007165229.png) 33 | 34 | 在 Kubernetes 环境中,Sidecar 会注入到业务 Pod 中,业务流量被 Sidecar 拦截,然后 Sidecar 解析协议内容并根据服务网格的配置进行转发: 35 | 36 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F10%2F07%2F20231007165959.png) 37 | 38 | 配置是服务网格控制面动态下发给 Sidecar 的,可实时动态更新,无需重启业务: 39 | 40 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F10%2F07%2F20231007170053.png) 41 | 42 | Sidecar 也与业务进程互相独立,没有耦合,且与开发语言无关。 43 | 44 | 目前主流的服务网格有 Istio、Linkerd、Consul、Cilium、Kuma 等,其中使用最为广泛的是 Istio,它使用 Envoy 作为 Sidecar (数据面)。 45 | 46 | ### 服务网格 Sidecarless 模式 47 | 48 | 近期,Istio 还推出了 ambient 模式,即不再使用 sidecar,而是用 daemonset 部署 ztunnel 组件作为节点级别的代理,由 ztunnel 拦截本节点上所有网格内的 Pod 的流量,由 ztunnel 来进行四层转发: 49 | 50 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F10%2F07%2F20231007171829.png) 51 | 52 | 如果目的服务使用了七层协议特性,会单独走一跳 waypoint proxy 进行七层转发: 53 | 54 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F10%2F07%2F20231007171714.png) 55 | 56 | 这种架构带来了以下好处: 57 | 58 | 1. 减少了资源占用。Sidecar 模式需要为每个 Pod 都注入 Sidecar,每个 Sidecar 都要从控制面接收大量配置,随着网格规模的增加,资源占用会越来越大,而 ztunnel 所需的配置很少,而且每个节点只需要一个 ztunnel 实例,且 ztunnel 只做四层转发,极大的降低了资源占用。 59 | 2. 某些场景下降低时延。如果没用到七层能力,ztunnel 直接就四层转发,不需要解析七层;如果用到七层能力,也只需要在 waypoint proxy 做一次七层解析,不像 sidecar 那样需要两次解析,从而降低时延。 60 | 3. 提高安全性。ztunnel 与业务容器不在同一个 Pod,业务被侵入后的爆炸半径可控。 61 | 4. 减少侵入性,更易于运维。虽然 Sidecar 模式对业务逻辑无侵入,但是注入 Sidecar 需要重启 Pod,升级 Sidecar 同样也需要重启 Pod,而用 ambient 模式则都不需要了。 62 | -------------------------------------------------------------------------------- /content/sidebars.ts: -------------------------------------------------------------------------------- 1 | import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'; 2 | const sidebars: SidebarsConfig = { 3 | istioSidebar: [ 4 | { 5 | type: "doc", 6 | id: "README", 7 | customProps: { 8 | slug: "/" 9 | } 10 | }, 11 | { 12 | type: 'category', 13 | label: '入门', 14 | collapsed: true, 15 | link: { 16 | type: 'generated-index', 17 | slug: '/intro', 18 | }, 19 | items: [ 20 | 'intro/service-governance', 21 | ], 22 | }, 23 | { 24 | type: 'category', 25 | label: '最佳实践', 26 | collapsed: true, 27 | link: { 28 | type: 'generated-index', 29 | slug: '/best-practices', 30 | }, 31 | items: [ 32 | 'best-practices/graceful-shutdown', 33 | 'best-practices/optimize-performance', 34 | 'best-practices/specify-protocol', 35 | 'best-practices/set-default-route', 36 | ], 37 | }, 38 | { 39 | type: 'category', 40 | label: '用法实践', 41 | collapsed: true, 42 | link: { 43 | type: 'generated-index', 44 | slug: '/usage' 45 | }, 46 | items: [ 47 | { 48 | type: 'category', 49 | label: '访问日志', 50 | collapsed: true, 51 | link: { 52 | type: 'generated-index', 53 | slug: '/usage/accesslog' 54 | }, 55 | items: [ 56 | 'usage/accesslog/accesslog-config', 57 | 'usage/accesslog/enable-accesslog-for-workload', 58 | 'usage/accesslog/accesslog-print-header-body', 59 | ], 60 | }, 61 | 'usage/cors', 62 | 'usage/iphash', 63 | 'usage/websocket', 64 | 'usage/preserve-case', 65 | 'usage/enable-gzip', 66 | 'usage/limit-request-size', 67 | 'usage/label-traffic', 68 | 'usage/ratelimit', 69 | 'usage/response-request-id', 70 | ], 71 | }, 72 | { 73 | type: 'category', 74 | label: '实用技巧', 75 | collapsed: true, 76 | link: { 77 | type: 'generated-index', 78 | slug: '/trick' 79 | }, 80 | items: [ 81 | 'trick/header-authorization', 82 | 'trick/multi-version-test-service-with-prism', 83 | 'trick/hide-server-header', 84 | 'trick/debug', 85 | 'trick/customize-proxy-loglevel', 86 | ], 87 | }, 88 | { 89 | type: 'category', 90 | label: '常见问题', 91 | collapsed: true, 92 | link: { 93 | type: 'generated-index', 94 | slug: '/faq' 95 | }, 96 | items: [ 97 | 'faq/sidecar-shutdown', 98 | 'faq/sidecar-startup-order', 99 | 'faq/smart-dns', 100 | 'faq/the-case-of-http-header', 101 | 'faq/uppercase-header-causes-sticky-sessions-to-not-work', 102 | 'faq/tracing', 103 | 'faq/sidecar-injection', 104 | 'faq/retries-for-non-idempotent-services', 105 | 'faq/multicluster', 106 | 'faq/listen-any', 107 | 'faq/vs-match-order', 108 | 'faq/headless-svc', 109 | 'faq/grpc-not-loadbalancing', 110 | ], 111 | }, 112 | { 113 | type: 'category', 114 | label: '故障排查', 115 | collapsed: true, 116 | link: { 117 | type: 'generated-index', 118 | slug: '/troubleshooting', 119 | }, 120 | items: [ 121 | 'troubleshooting/virtualservice-not-working', 122 | 'troubleshooting/grpc-config-stream-closed', 123 | 'troubleshooting/circuit-breaking-not-work', 124 | 'troubleshooting/locality-lb-not-working', 125 | 'troubleshooting/isito-init-crash', 126 | 'troubleshooting/status-code-431', 127 | 'troubleshooting/status-code-426', 128 | 'troubleshooting/status-code-404', 129 | { 130 | type: 'category', 131 | label: '排障案例', 132 | collapsed: true, 133 | link: { 134 | type: 'generated-index', 135 | slug: '/troubleshooting/cases', 136 | }, 137 | items: [ 138 | 'troubleshooting/cases/apollo-on-istio', 139 | 'troubleshooting/cases/cannot-connect-pod-without-sidecar', 140 | 'troubleshooting/cases/grpc-without-status-code', 141 | 'troubleshooting/cases/istio-token-setup-failed-for-volume-istio-token', 142 | 'troubleshooting/cases/traffic-policy-does-not-take-effect', 143 | 'troubleshooting/cases/using-istio-reserved-port-causes-pod-start-failed', 144 | ], 145 | }, 146 | ], 147 | }, 148 | { 149 | type: 'category', 150 | label: '附录', 151 | collapsed: true, 152 | link: { 153 | type: 'generated-index', 154 | slug: '/appendix' 155 | }, 156 | items: [ 157 | 'appendix/link', 158 | 'appendix/shell', 159 | 'appendix/yaml', 160 | ], 161 | }, 162 | ], 163 | }; 164 | 165 | export default sidebars; 166 | -------------------------------------------------------------------------------- /content/source/cni.md: -------------------------------------------------------------------------------- 1 | # CNI 模块源码解析 2 | 3 | ## install-cni 4 | 5 | ### 主要逻辑梳理 6 | 7 | `install-cni` 用于 daemonset 部署到每个节点,主要逻辑如下: 8 | - 为节点安装 istio-cni 插件。 9 | - 检测 cni 配置,如果检测到被修改,立即覆盖回来。 10 | - 如果是 ambient 模式,会起一个 controller 来 watch pod,当本节点 ztunnel pod 起来时,会自动在节点上创建 ambient 模式中节点上所需的 iptables 规则、虚拟网卡以及策略路由。 11 | - 退出时清理 istio-cni 插件。 12 | 13 | main 函数入口在 `cni/cmd/install-cni/main.go`,`cmd.GetCommand()` 获取 cobra 的 Command,也就是二进制运行入口: 14 | 15 | ```go 16 | rootCmd := cmd.GetCommand() 17 | if err := rootCmd.ExecuteContext(ctx); err != nil { 18 | os.Exit(1) 19 | } 20 | ``` 21 | 22 | `rootCmd.ExecuteContext(ctx)` 会调用 cobra Command 的 `RunE` 函数 (`cni/pkg/cmd/root.go`): 23 | 24 | ```go 25 | var rootCmd = &cobra.Command{ 26 | RunE: func(c *cobra.Command, args []string) (err error) { 27 | ... 28 | ``` 29 | 30 | 继续看看 `RunE` 里面的逻辑,`constructConfig()` 会从启动参数和环境变量中读取参数,进行合并,构造出 `install-cni` 的配置信息 Config 对象: 31 | 32 | ```go 33 | if cfg, err = constructConfig(); err != nil { 34 | return 35 | } 36 | ``` 37 | 38 | 根据配置的端口,将指标监控信息暴露到 `/metrics` 接口: 39 | 40 | ```go 41 | // Start metrics server 42 | monitoring.SetupMonitoring(cfg.InstallConfig.MonitoringPort, "/metrics", ctx.Done()) 43 | ``` 44 | 45 | 监听 Unix Domain Socket,暴露 HTTP 接口,用于接收来自 `istio-cni` 产生的日志,收到日志后就会在 `install-cni` 这里打印出来: 46 | 47 | ```go 48 | // Start UDS log server 49 | udsLogger := udsLog.NewUDSLogger() 50 | if err = udsLogger.StartUDSLogServer(cfg.InstallConfig.LogUDSAddress, ctx.Done()); err != nil { 51 | log.Errorf("Failed to start up UDS Log Server: %v", err) 52 | return 53 | } 54 | ``` 55 | 56 | > 因为 `istio-cni` 自身不是常驻进程,被 kubelet 调用后立即退出,所以需要将日志投递给 `install-cni` 统一打印和记录,便于排查问题。 57 | 58 | 如果启用了 ambient 模式,会启动一个 ambient 模式所需要的常驻运行的 server: 59 | 60 | ```go 61 | if cfg.InstallConfig.AmbientEnabled { 62 | // Start ambient controller 63 | server, err := ambient.NewServer(ctx, ambient.AmbientArgs{ 64 | SystemNamespace: ambient.PodNamespace, 65 | Revision: ambient.Revision, 66 | }) 67 | if err != nil { 68 | return fmt.Errorf("failed to create ambient informer service: %v", err) 69 | } 70 | server.Start() 71 | } 72 | ``` 73 | 74 | 接下来是最核心最关键的逻辑,根据安装配置将 `istio-cni` 插件安装到节点上,同时 watch 文件变化,如果被修改就自动覆盖回来: 75 | 76 | ```go 77 | installer := install.NewInstaller(&cfg.InstallConfig, isReady) 78 | 79 | repair.StartRepair(ctx, &cfg.RepairConfig) 80 | 81 | if err = installer.Run(ctx); err != nil { 82 | ... 83 | ``` 84 | 85 | ### 安装 CNI 插件的逻辑梳理 86 | 87 | `install.Run(ctx)` 是安装 CNI 逻辑的入口,内部 `in.install(ctx)` 是每次安装 CNI 插件时执行的逻辑: 88 | 89 | ```go 90 | if in.cfg.CNIEnableInstall { 91 | if err = in.install(ctx); err != nil { 92 | return 93 | } 94 | ... 95 | ``` 96 | 97 | 在 `install` 中,先将 istio CNI 插件的二进制拷贝到节点的 CNI 二进制目录 (`/opt/cni/bin`): 98 | 99 | ```go 100 | if err = copyBinaries( 101 | in.cfg.CNIBinSourceDir, in.cfg.CNIBinTargetDirs, 102 | in.cfg.UpdateCNIBinaries, in.cfg.SkipCNIBinaries); err != nil { 103 | cniInstalls.With(resultLabel.Value(resultCopyBinariesFailure)).Increment() 104 | return 105 | } 106 | ``` 107 | 108 | 然后创建 CNI 二进制运行起来需要的 kubeconfig 文件: 109 | 110 | ```go 111 | if in.kubeconfigFilepath, err = createKubeconfigFile(in.cfg, in.saToken); err != nil { 112 | cniInstalls.With(resultLabel.Value(resultCreateKubeConfigFailure)).Increment() 113 | return 114 | } 115 | ``` 116 | 117 | 最后是创建并覆盖 CNI 插件配置文件: 118 | 119 | ```go 120 | if in.cniConfigFilepath, err = createCNIConfigFile(ctx, in.cfg, in.saToken); err != nil { 121 | cniInstalls.With(resultLabel.Value(resultCreateCNIConfigFailure)).Increment() 122 | return 123 | } 124 | ``` 125 | 126 | ## istio-cni 127 | 128 | main 函数入口在 `cni/cmd/istio-cni/main.go`: 129 | 130 | ```go 131 | func main() { 132 | ... 133 | skel.PluginMain(plugin.CmdAdd, plugin.CmdCheck, plugin.CmdDelete, version.All, 134 | fmt.Sprintf("CNI plugin istio-cni %v", istioversion.Info.Version)) 135 | } 136 | ``` 137 | 138 | 关键逻辑是调用 CNI 项目中的 `skel.PluginMain` 这个函数来注册 CNI 插件处理函数。 139 | 140 | 最重要的是 `plugin.CmdAdd`,即每次 kubelet 创建 Pod 时,调用 `istio-cni` 插件来设置容器网络的时候,就会走到 `CmdAdd` 这个函数。 141 | 142 | 首先会解析 kubelet 调用 `istio-cni` 时通过标准输入传来的配置,以及 `install-cni` 里面的 ambient server 写入的 ambient 配置文件。 143 | 144 | ```go 145 | // CmdAdd is called for ADD requests 146 | func CmdAdd(args *skel.CmdArgs) (err error) { 147 | ... 148 | conf, err := parseConfig(args.StdinData) 149 | if err != nil { 150 | log.Errorf("istio-cni cmdAdd failed to parse config %v %v", string(args.StdinData), err) 151 | return err 152 | } 153 | ... 154 | ambientConf, err := ambient.ReadAmbientConfig() 155 | if err != nil { 156 | log.Errorf("istio-cni cmdAdd failed to read ambient config %v", err) 157 | return err 158 | } 159 | ... 160 | ``` 161 | 162 | ### 主题逻辑梳理 163 | 164 | `istio-cni` 是被 `install-cni` 安装到节点的 CNI 插件二进制,在 kubelet 每次创建 pod 时会调用的二进制,主要功能如下: 165 | - 当要创建的 Pod 是网格内的 Pod 时,在 Pod 所在 netns 创建相关 iptables 规则以实现流量拦截(取代 `istio-init` 容器)。 166 | - 如果是网格内的 Pod,且数据面使用的 ambient 模式的 ztunnel 而不是 sidecar,自动更新 ambient 模式在节点上所需的 ipset, 路由表等。 167 | -------------------------------------------------------------------------------- /content/source/structure.md: -------------------------------------------------------------------------------- 1 | # 项目结构解析 2 | 3 | ## 顶层目录 4 | 5 | ```txt 6 | ├── cni 7 | │   ├── cmd 8 | │   ├── pkg 9 | ├── istioctl 10 | │   ├── cmd 11 | │   └── pkg 12 | ├── operator 13 | │   ├── cmd 14 | │   ├── pkg 15 | ├── pilot 16 | │   ├── cmd 17 | │   ├── pkg 18 | ├── pkg 19 | ``` 20 | 21 | * `cni`, `istioctl`, `operator`, `pilot` 目录分别包含同名相应模块的代码。下面的 `cmd` 是模块下相应二进制的编译入口,`cmd` 下面的 `pkg` 是 `cmd` 中的代码需要调用的依赖逻辑。 22 | * 多个模块共同依赖的一些逻辑会放到外层的 `pkg` 目录下。 23 | 24 | ## 梳理模块与二进制 25 | 26 | `cni` 模块主要包含 `istio-cni` 和 `install-cni` 两个二进制,负责 cni 插件相关逻辑: 27 | 28 | ```txt 29 | cni 30 | ├── cmd 31 | │   ├── install-cni 32 | │   ├── istio-cni 33 | 34 | ``` 35 | 36 | `istioctl` 和 `operator` 模块都主要是一个二进制,分别用于 cli 工具和 istio 安装。 37 | 38 | `pilot` 是最核心的模块,有 `pilot-agent` 和 `pilot-discovery` 两个二进制: 39 | 40 | ```txt 41 | pilot 42 | ├── cmd 43 | │   ├── pilot-agent 44 | │   └── pilot-discovery 45 | ``` 46 | 47 | - `pilot-discovery` 就是 "istiod",即 istio 控制面。 48 | - `pilot-agent` 是连接 istiod (控制面) 和 envoy (数据面) 之间的纽带,主要负责拉起和管理数据面进程。 49 | -------------------------------------------------------------------------------- /content/trick/customize-proxy-loglevel.md: -------------------------------------------------------------------------------- 1 | # 自定义 proxy 日志级别 2 | 3 | ## 概述 4 | 5 | 本文介绍在 istio 中如何自定义数据面 (proxy) 的日志级别,方便我们排查问题时进行调试。 6 | 7 | ## 动态调整 8 | 9 | 调低 proxy 日志级别进行 debug 有助于排查问题,但输出内容较多且耗资源,不建议在生产环境一直开启低级别的日志,istio 默认使用 `warning` 级别。 10 | 11 | 我们可以使用 istioctl 动态调整 proxy 日志级别: 12 | 13 | ```bash 14 | istioctl -n istio-test proxy-config log productpage-v1-7668cb67cc-86q8l --level debug 15 | ``` 16 | 17 | 还可以更细粒度控制: 18 | 19 | ```bash 20 | istioctl -n istio-test proxy-config log productpage-v1-7668cb67cc-86q8l --level grpc:trace,config:debug 21 | ``` 22 | 23 | > 更多 level 可选项参考: `istioctl proxy-config log --help` 24 | 25 | 如果没有 istioctl,也可以直接使用 kubectl 进入 istio-proxy 调用 envoy 接口来动态调整: 26 | 27 | ```bash 28 | kubectl exec -n istio-test productpage-v1-7668cb67cc-86q8l -c istio-proxy -- curl -XPOST -s -o /dev/null http://localhost:15000/logging?level=debug 29 | ``` 30 | 31 | ## 使用 annotation 指定 32 | 33 | 如果不用动态调整,也可以在部署时为 Pod 配置 annotation 来指定 proxy 日志级别: 34 | 35 | ```yaml 36 | template: 37 | metadata: 38 | annotations: 39 | "sidecar.istio.io/logLevel": debug # 可选: trace, debug, info, warning, error, critical, off 40 | ``` 41 | 42 | ## 全局配置 43 | 44 | 如果是测试集群,你也可以全局配置 proxy 日志级别: 45 | 46 | ```bash 47 | kubectl -n istio-system edit configmap istio-sidecar-injector 48 | ``` 49 | 50 | 修改 `values` 里面的 `global.proxy.logLevel` 字段即可。 51 | 52 | 如果使用 istioctl 安装 istio,也可以使用类似以下命令配置全局 proxy 日志级别: 53 | 54 | ```bash 55 | istioctl install --set profile=demo --set values.global.proxy.logLevel=debug 56 | ``` 57 | 58 | ## 配置 envoy componentLogLevel 59 | 60 | 如何细粒度的调整 envoy 自身的内部日志级别呢?可以给 Pod 指定 annotation 来配置: 61 | 62 | ```yaml 63 | template: 64 | metadata: 65 | annotations: 66 | "sidecar.istio.io/componentLogLevel": "ext_authz:trace,filter:debug" 67 | ``` 68 | 69 | * [Envoy component logging 说明](https://www.envoyproxy.io/docs/envoy/latest/operations/cli#cmdoption-component-log-level) 70 | * 该配置最终会作为 envoy 的 `--component-log-level` 启动参数。 -------------------------------------------------------------------------------- /content/trick/debug.md: -------------------------------------------------------------------------------- 1 | # 调试技巧 2 | 3 | ## 测试与 istiod 的连通性 4 | 5 | 测试数据面是否能连上 xDS 端口: 6 | 7 | ```bash 8 | nc -vz istiod-1-8-1.istio-system 15012 9 | ``` 10 | 11 | 测试 istiod 监控接口: 12 | 13 | ```bash 14 | $ kubectl -n debug exec debug-6fd7477c9d-brqmq -c istio-proxy -- curl -sS istiod-1-8-1.istio-system:15014/debug/endpointz 15 | [ 16 | 17 | {"svc": "cert-manager-webhook-dnspod.cert-manager.svc.cluster.local:https", "ep": [ 18 | { 19 | "service": { 20 | "Attributes": { 21 | "ServiceRegistry": "Kubernetes", 22 | "Name": "cert-manager-webhook-dnspod", 23 | "Namespace": "cert-manager", 24 | "Labels": { 25 | ``` 26 | > 没报错,正常返回 json 说明数据面能正常连接控制面 27 | 28 | 29 | ## 配置 accesslog 30 | 31 | 配置方法参考 [这里](https://imroc.cc/istio/usage/accesslogs/) 。 32 | 33 | ## 调整 proxy 日志级别 34 | 35 | 配置方法参考 [这里](https://imroc.cc/istio/trick/customize-proxy-loglevel/) 。 36 | 37 | ## 获取 metrics 38 | 39 | ```bash 40 | kubectl -n test exec -c istio-proxy htmall-6657db8f8f-l74qm -- curl -sS localhost:15090/stats/prometheus 41 | ``` 42 | 43 | ## 查看 envoy 配置 44 | 45 | 导出 envoy 配置: 46 | 47 | ```bash 48 | kubectl exec -it -c istio-proxy $POD_NAME -- curl 127.0.0.1:15000/config_dump > dump.json 49 | ``` 50 | 51 | 导出 envoy 全量配置(包含 EDS): 52 | 53 | ```bash 54 | kubectl exec -it -c istio-proxy $POD_NAME -- curl 127.0.0.1:15000/config_dump?include_eds > dump.json 55 | ``` 56 | -------------------------------------------------------------------------------- /content/trick/header-authorization.md: -------------------------------------------------------------------------------- 1 | # 实现基于 Header 的授权 2 | 3 | ## 背景 4 | 5 | 部分业务场景在 http header 或 grpc metadata 中会有用户信息,想在 mesh 这一层来基于用户信息来对请求进行授权,如果不满足条件就让接口不返回相应的数据。 6 | 7 | ## 解决方案 8 | 9 | Istio 的 AuthorizationPolicy 不支持基于 Header 的授权,但可以利用 VirtualService 来实现,匹配 http header (包括 grpc metadata),然后再加一个默认路由,使用的固定故障注入返回 401,示例: 10 | 11 | ```yaml 12 | apiVersion: networking.istio.io/v1beta1 13 | kind: VirtualService 14 | metadata: 15 | name: helloworld-server 16 | spec: 17 | hosts: 18 | - helloworld-server 19 | http: 20 | - name: whitelist 21 | match: 22 | - headers: 23 | end-user: 24 | regex: "roc" 25 | route: 26 | - destination: 27 | host: helloworld-server 28 | port: 29 | number: 9000 30 | - name: default 31 | route: 32 | - destination: 33 | host: helloworld-server 34 | port: 35 | number: 9000 36 | fault: 37 | abort: 38 | percentage: 39 | value: 100 40 | httpStatus: 401 41 | ``` -------------------------------------------------------------------------------- /content/trick/hide-server-header.md: -------------------------------------------------------------------------------- 1 | # 隐藏自动添加的 server header 2 | 3 | ## 背景 4 | 5 | 出于安全考虑,希望隐藏 istio 自动添加的 `server: istio-envoy` 这样的 header。 6 | 7 | ## 解决方案 8 | 9 | 可以配置 envoyfilter ,让 envoy 返回响应时不自动添加 server 的 header,将HttpConnectionManager 的 server_header_transformation 设为 PASS_THROUGH(后端没返回该header时envoy也不会自动添加): 10 | 11 | ```yaml 12 | apiVersion: networking.istio.io/v1alpha3 13 | kind: EnvoyFilter 14 | metadata: 15 | name: hide-headers 16 | namespace: istio-system 17 | spec: 18 | configPatches: 19 | - applyTo: NETWORK_FILTER 20 | match: 21 | context: ANY 22 | listener: 23 | filterChain: 24 | filter: 25 | name: "envoy.http_connection_manager" 26 | patch: 27 | operation: MERGE 28 | value: 29 | typed_config: 30 | "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager" 31 | server_header_transformation: PASS_THROUGH 32 | ``` 33 | 34 | 35 | ## 参考资料 36 | 37 | * [server_header_transformation 字段各个值的解释](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto.html#envoy-v3-api-enum-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-serverheadertransformation) -------------------------------------------------------------------------------- /content/trick/multi-version-test-service-with-prism.md: -------------------------------------------------------------------------------- 1 | # 利用 Prism 构造多版本测试服务 2 | 3 | ## 概述 4 | 5 | [Prism](https://github.com/stoplightio/prism) 是一个支持 http mock 的开源工具,可以解析 openapi 配置,根据配置进行相应的响应,我们可以利用它来实现部署多版本服务,用于测试 istio 多版本服务相关的功能。本文给出一个简单的示例。 6 | 7 | ## 准备 OpenAPI 配置 8 | 9 | 我们将 OpenAPI 配置文件存到 ConfigMap 中,用于后续挂载到 prism 作为配置文件 (`prism-conf.yaml`): 10 | 11 | ```yaml 12 | apiVersion: v1 13 | kind: ConfigMap 14 | metadata: 15 | name: prism-conf 16 | data: 17 | mock-v1.yaml: | 18 | openapi: 3.0.3 19 | info: 20 | title: MockServer v2 21 | description: MockServer v2 22 | version: 1.0.0 23 | paths: 24 | '/': 25 | get: 26 | responses: 27 | '200': 28 | content: 29 | 'text/plain': 30 | schema: 31 | type: string 32 | example: v1 33 | mock-v2.yaml: | 34 | openapi: 3.0.3 35 | info: 36 | title: MockServer v2 37 | description: MockServer v2 38 | version: 1.0.0 39 | paths: 40 | '/': 41 | get: 42 | responses: 43 | '200': 44 | content: 45 | 'text/plain': 46 | schema: 47 | type: string 48 | example: v2 49 | ``` 50 | 51 | 这里的配置很简单,两个 OpenAPI 配置文件,`GET` 方式请求 `/` 路径分别响应 `v1` 和 `v2` 的字符串,以便从响应中就能区分出请求转发到了哪个版本的服务。 52 | 53 | 如果想用编辑器或 IDE 的 OpenAPI 插件编辑配置文件来定义更复杂的规则,可以先直接创建原生 OpenAPI 配置文件 (如 `mock-v1.yaml` 和 `mock-v2.yaml`),然后使用类似下面的命令生成 configmap 的 yaml: 54 | 55 | ```bash 56 | kubectl create configmap prism-conf --dry-run=client -o yaml 57 | --from-file=mock-v1.yaml \ 58 | --from-file=mock-v2.yaml | \ 59 | grep -v creationTimestamp > prism-conf.yaml 60 | ``` 61 | 62 | ## 部署多版本服务 63 | 64 | 使用 Deployment 部署两个版本的 prism (注意开启下 sidecar 自动注入),分别挂载不同的 OpenAPI 配置,首先部署第一个版本 (`mockserver-v1.yaml`): 65 | 66 | ```yaml 67 | apiVersion: apps/v1 68 | kind: Deployment 69 | metadata: 70 | name: mockserver-v1 71 | spec: 72 | replicas: 1 73 | selector: 74 | matchLabels: 75 | app: mockserver 76 | version: v1 77 | template: 78 | metadata: 79 | labels: 80 | app: mockserver 81 | version: v1 82 | spec: 83 | containers: 84 | - name: mockserver 85 | image: cr.imroc.cc/library/prism:4 86 | args: 87 | - mock 88 | - -h 89 | - 0.0.0.0 90 | - -p 91 | - "80" 92 | - /etc/prism/mock-v1.yaml 93 | volumeMounts: 94 | - mountPath: /etc/prism 95 | name: config 96 | volumes: 97 | - name: config 98 | configMap: 99 | name: prism-conf 100 | ``` 101 | 102 | 再部署第二个版本 (`mockserver-v2.yaml`): 103 | 104 | ```yaml 105 | apiVersion: apps/v1 106 | kind: Deployment 107 | metadata: 108 | name: mockserver-v2 109 | spec: 110 | replicas: 1 111 | selector: 112 | matchLabels: 113 | app: mockserver 114 | version: v2 115 | template: 116 | metadata: 117 | labels: 118 | app: mockserver 119 | version: v2 120 | spec: 121 | containers: 122 | - name: mockserver 123 | image: cr.imroc.cc/library/prism:4 124 | args: 125 | - mock 126 | - -h 127 | - 0.0.0.0 128 | - -p 129 | - "80" 130 | - /etc/prism/mock-v2.yaml 131 | volumeMounts: 132 | - mountPath: /etc/prism 133 | name: config 134 | volumes: 135 | - name: config 136 | configMap: 137 | name: prism-conf 138 | ``` 139 | 140 | 最后创建 Service (`mockserver-svc.yaml`): 141 | 142 | ```yaml 143 | apiVersion: v1 144 | kind: Service 145 | metadata: 146 | name: mockserver 147 | labels: 148 | app: mockserver 149 | spec: 150 | type: ClusterIP 151 | ports: 152 | - port: 80 153 | protocol: TCP 154 | name: http 155 | selector: 156 | app: mockserver 157 | ``` 158 | 159 | ## 测试访问 160 | 161 | 没有定义任何其它规则,测试访问会随机负载均衡到 v1 和 v2: 162 | 163 | ```bash 164 | $ for i in {1..10};do curl mockserver && echo ""; done 165 | v2 166 | v1 167 | v2 168 | v1 169 | v2 170 | v1 171 | v2 172 | v1 173 | v2 174 | v1 175 | ``` 176 | 177 | ## 使用 DestinationRule 定义多版本服务 178 | 179 | 在 DestinationRule 定义使用 pod label 来区分 v1 和 v2 版本的服务 (`mockserver-dr.yaml`): 180 | 181 | ```yaml 182 | apiVersion: networking.istio.io/v1beta1 183 | kind: DestinationRule 184 | metadata: 185 | name: mockserver 186 | spec: 187 | host: mockserver 188 | subsets: 189 | - labels: 190 | app: mockserver 191 | version: v2 192 | name: v1 193 | - labels: 194 | app: mockserver 195 | version: v2 196 | name: v2 197 | ``` 198 | 199 | ## 使用 VirtualService 定义多版本路由规则 200 | 201 | 这里定义一个简单的规则,v1 版本服务接收 80% 流量,v2 版本接收 20% (`mockserver-vs.yaml`): 202 | 203 | ```yaml 204 | apiVersion: networking.istio.io/v1beta1 205 | kind: VirtualService 206 | metadata: 207 | name: mockserver 208 | spec: 209 | hosts: 210 | - mockserver 211 | http: 212 | - route: 213 | - destination: 214 | host: mockserver 215 | port: 216 | number: 80 217 | subset: v1 218 | weight: 80 219 | - destination: 220 | host: mockserver 221 | port: 222 | number: 80 223 | subset: v2 224 | weight: 20 225 | ``` 226 | 227 | ## 测试验证多版本流量转发规则 228 | 229 | 上面定义了 DestinationRule 和 VirtualService 之后,会根据定义的规则进行转发: 230 | 231 | ```bash 232 | $ for i in {1..10};do curl mockserver && echo ""; done 233 | v1 234 | v2 235 | v1 236 | v1 237 | v2 238 | v1 239 | v1 240 | v1 241 | v1 242 | v1 243 | ``` -------------------------------------------------------------------------------- /content/troubleshooting/cases/apollo-on-istio.md: -------------------------------------------------------------------------------- 1 | # 使用 apollo 的 java 应用启动报 404 2 | 3 | ## 问题描述 4 | 5 | 项目中使用了 apollo 插件,在非 istio 环境正常运行,但部署到 istio 后启动报类似如下错误: 6 | 7 | ```log 8 | Sync config from upstream repository class com.ctrip.framework.apollo.internals.RemoteConfigRepository failed, reason: Load Apollo Config failed - xxx, url: http://10.5.16.49:8080/configs/agent-center/see-test-02/test.common?ip=10.5.16.46 [Cause: [status code: 404] Could not find config for xxx please check whether the configs are released in Apollo!] 9 | ``` 10 | 11 | 表示请求 apollo 的 config service 返回 404 了。 12 | 13 | ## 排查 accesslog 14 | 15 | 查看 envoy 的 accesslog: 16 | 17 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F09%2F22%2F20230922191139.png) 18 | 19 | * `response_flags` 为 `NR`,表示找不到路由 (参考 envoy 官网解释: No route configured for a given request in addition to 404 response code, or no matching filter chain for a downstream connection)。 20 | * 请求使用的 `Host` 直接用的 `PodIP:Port`。 21 | * 该 `PodIP:Port` 属于 apollo 服务的 headless service 一个 endpoint (apollo 通过 statefulset 部署)。 22 | 23 | ## headless service 的 xDS 规则 24 | 25 | 进一步分析之前,我们先了解下 istio 对 headless service 的 xDS 支持: 26 | * 下发的 `LDS` 规则中会监听 headless service 所有可能的 `PortIP:Port`,请求 headless service 的 Pod 时,这里先匹配上。 27 | * 然后走到 `RDS` 规则进行路由,路由时会匹配 hosts,hosts 列表中列举了所有可能的 service 地址 (没有 Pod IP),如果都匹配不到就会返回 404。 28 | 29 | ## 问题原因 30 | 31 | 由于请求 apollo 的 config service 时,`Host` 没有使用 service 地址,而是直接使用了 `PodIP:Port`,所以 `RDS` 匹配时找不到相应 hosts,就会返回 404。 32 | 33 | ## 为什么没有使用 service 地址 ? 34 | 35 | 为了实现高可用,apollo 的 java 客户端默认是从 meta server 中获取 config service 的 ip 地址 (服务发现),然后直接对该地址发起请求 (不使用 k8s service),从而导致请求 config service 时没有将其 k8s service 地址作为 `Host`,最后 hosts 匹配不到返回 404。 36 | 37 | ## 如果解决 ? 38 | 39 | 在 istio 场景下 (kubernetes 之上),请求 config service 就不需要不走 apollo meta server 获取 config service 的 ip 来实现高可用,直接用 kubernetes 的 service 做服务发现就行。幸运的是,apollo 也支持跳过 meta server 服务发现,这样访问 config service 时就可以直接请求 k8s service 了,也就可以解决此问题。 40 | 41 | 具体配置方法参考 [Apollo Java 客户端使用指南](https://github.com/ctripcorp/apollo/wiki/Java%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97#1222-%E8%B7%B3%E8%BF%87apollo-meta-server%E6%9C%8D%E5%8A%A1%E5%8F%91%E7%8E%B0) 。 42 | -------------------------------------------------------------------------------- /content/troubleshooting/cases/cannot-connect-pod-without-sidecar.md: -------------------------------------------------------------------------------- 1 | # 无法访问不带 sidecar 的 Pod 2 | 3 | ## 问题现象 4 | 5 | 不能从一个带 sidecar proxy 的 pod 访问到 Redis 服务。 6 | 7 | ## 问题分析 8 | 9 | Redis是一个 Headless 服务,而 istio 1.6 之前的版本对 Headless 服务的处理有问题,会缺省启用 mTLS。 10 | 11 | ## 解决方案 12 | 13 | 在 1.6 之前可以采用DR规则禁用该服务的 mTLS 来规避: 14 | 15 | ```yaml 16 | apiVersion: networking.istio.io/v1beta1 17 | kind: DestinationRule 18 | metadata: 19 | name: mysql-service 20 | namespace: test 21 | spec: 22 | host: redis-service 23 | trafficPolicy: 24 | loadBalancer: 25 | simple: ROUND_ROBIN 26 | tls: 27 | mode: DISABLE 28 | ``` 29 | 30 | 该问题在 isitio 1.6 中已经修复: https://github.com/istio/istio/pull/24319 31 | -------------------------------------------------------------------------------- /content/troubleshooting/cases/grpc-without-status-code.md: -------------------------------------------------------------------------------- 1 | # 注入 sidecar 后 grpc 请求不响应 status code 2 | 3 | ## 问题描述 4 | 5 | * 环境信息: 多集群下,子集群中 nginx 为 grpc 服务做了一层反向代理。 6 | * 访问链路:grpc client --> nginx --> grpc server。 7 | * 链路说明: grpc client 对 nginx 发起 grpc 调用,nginx 将请求 proxy_pass 到集群内的 grpc server 的 k8s service。 8 | * 现象: 当 nginx 注入了 istio sidecar 后,grpc client 就收不到 grpc server 响应的 `grpc-status` 的 trailer 了 (即没有 grpc status code)。 9 | 10 | ## 原因 11 | 12 | 测试在 istio 1.6.9 中存在一个 bug,在多集群下,子集群中 envoy sidecar 内部的 http route 中的 domain 中的 vip 采用的是主集群中的 Cluster IP,而不是子集群中该服务对应的 Cluster IP。 如下面 nginx pod 中 envoy proxy 的路由配置: 13 | 14 | ```json 15 | { 16 | "name": "location-svr.default.svc.cluster.local:50222", 17 | "domains": [ 18 | "location-svr.default.svc.cluster.local", 19 | "location-svr.default.svc.cluster.local:50222", 20 | "location-svr", 21 | "location-svr:50222", 22 | "location-svr.default.svc.cluster", 23 | "location-svr.default.svc.cluster:50222", 24 | "location-svr.default.svc", 25 | "location-svr.default.svc:50222", 26 | "location-svr.default", 27 | "location-svr.default:50222", 28 | "172.21.255.24", # 此处的 VIP 是主机群中的 cluster ip,而不是子集群中的 cluster ip 29 | "172.21.255.24:50222" 30 | ], 31 | "routes": [ 32 | ... 33 | ``` 34 | 35 | * grpc client 发起请求时带上的 host 是 nginx 的域名地址,被 nginx proxy_pass 时带上了,但 envoy 是匹配不到这个原始 host 的,就尝试去匹配报文目的 IP (即 cluster ip)。 36 | * 但因为上述 bug,cluster ip 也匹配不到 (istio 1.6.9 子集群中 http route 只有主集群同名 service 的 cluster ip,这里目的 IP 可能只能是子集群自己的 cluster ip),就只有 paasthrough 了。 37 | * 由于 passthrough cluster 并不确认后端的 upstream 支持 http2,因此未设置 `http2_protocol_options` 选项。 38 | * 该 grpc/http2 请求被 enovy 采用 http1.1 发送到 location-svr,导致未能收到 trailer (grpc status code)。 39 | 40 | ## 解决方案 41 | 42 | 有以下三种解决方案: 43 | 1. 经验证 1.8 中该 bug 已经处理,升级到 1.8 可以解决该问题。 44 | 2. nginx proxy_pass 显示指定 proxy_set_header,使用 service 名称作为 Host。 45 | 3. grpc client 请求时显示设置 Host Header 为 grpc server 的 service 名称。 -------------------------------------------------------------------------------- /content/troubleshooting/cases/istio-token-setup-failed-for-volume-istio-token.md: -------------------------------------------------------------------------------- 1 | # Pod 启动卡住: MountVolume.SetUp failed for volume "istio-token" 2 | 3 | ## 现象 4 | 5 | Istio 相关的 Pod (包括注入了 sidecar 的 Pod) 一直卡在 ContainerCreating,起不来,describe pod 报错 `MountVolume.SetUp failed for volume "istio-token" : failed to fetch token: the server could not find the requested resource`: 6 | 7 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F09%2F22%2F20230922191212.png) 8 | 9 | ## 分析 10 | 11 | 根据官方文档([Configure third party service account tokens](https://istio.io/latest/docs/ops/best-practices/security/#configure-third-party-service-account-tokens)) 的描述可以得知: 12 | * istio-proxy 需要使用 K8S 的 ServiceAccount token,而 K8S 支持 `third party` 和 `first party` 两种 token。 13 | * `third party token` 安全性更高,istio 默认使用这种类型。 14 | * 不是所有集群都支持这种 token,取决于 K8S 版本和 apiserver 配置。 15 | 16 | 如果集群不支持 `third party token`,就会导致 ServiceAccount token 不自动创建出来,从而出现上面这种报错。 17 | 18 | ## 什么是 third party token ? 19 | 20 | 其实就是 [ServiceAccountTokenVolumeProjection](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#service-account-token-volume-projection) 这个特性,在 1.12 beta,1.20 GA。 21 | 22 | 推出该特性是为了增强 ServiceAccount token 的安全性,可以设置有效期(会自动轮转),避免 token 泄露带来的安全风险,还可以控制 token 的受众。 23 | 24 | 该特性在 istio 中用来配合 SDS 以增强安全性,参考 [Istio私钥管理利器SDS浅析](https://developer.aliyun.com/article/742572)。 25 | 26 | 如何判断集群是否启用了该特性呢?可通过一下命令查询: 27 | 28 | ``` bash 29 | kubectl get --raw /api/v1 | jq '.resources[] | select(.name | index("serviceaccounts/token"))' 30 | ``` 31 | 32 | 若返回空,说明不支持;若返回如下 json,说明支持: 33 | 34 | ```json 35 | { 36 | "name": "serviceaccounts/token", 37 | "singularName": "", 38 | "namespaced": true, 39 | "group": "authentication.k8s.io", 40 | "version": "v1", 41 | "kind": "TokenRequest", 42 | "verbs": [ 43 | "create" 44 | ] 45 | } 46 | ``` 47 | 48 | ## 解决方案 49 | 50 | ### 方案一:安装 istio 时不使用 third party token 51 | 52 | 官方称使用 istioctl 安装会自动检测集群是否支持 `third party token`,但据 [issue](https://github.com/istio/istio/issues/21968#issuecomment-607474174) 反馈可能有 bug,还是建议强制指定用 `first party token`,用参数 `--set values.global.jwtPolicy=first-party-jwt` 来显示指定,示例: 53 | 54 | ```bash 55 | istioctl manifest generate --set profile=demo --set values.global.jwtPolicy=first-party-jwtm > istio.yaml 56 | ``` 57 | 58 | ### 方案二:集群启用 ServiceAccountTokenVolumeProjection 59 | 60 | 如何启用 ServiceAccountTokenVolumeProjection 这个特性呢?需要给 apiserver 配置类似如下的参数: 61 | 62 | ```yaml 63 | --service-account-key-file=/etc/kubernetes/pki/sa.key # 这个一般都会配,重要的是下面三个参数 64 | --service-account-issuer=kubernetes.default.svc 65 | --service-account-signing-key-file=/etc/kubernetes/pki/sa.key # 注意替换实际路径 66 | --api-audiences=kubernetes.default.svc 67 | ``` 68 | -------------------------------------------------------------------------------- /content/troubleshooting/cases/traffic-policy-does-not-take-effect.md: -------------------------------------------------------------------------------- 1 | # trafficPolicy 不生效 2 | 3 | ## 问题描述 4 | 5 | 为服务配置了 DestinationRule 和 VirtualService,且 VirtualService 绑好了 Gateway,DestinationRule 配置了 trafficPolicy,指定了熔断策略,但使用 ab 压测发现没有触发熔断 (ingressgateway 的 access_log 中 response_flags 没有 "UO")。 6 | 7 | ## 原因分析 8 | 9 | ab 压测时发送的 HTTP/1.0 请求,而 envoy 需要至少 HTTP/1.1,固定返回 426 Upgrade Required,根本不会进行转发,所以也就不会返回 503,response_flags 也不会有。 10 | 11 | ## 解决方案 12 | 13 | 压测工具换用 wrk,默认发送 HTTP/1.1 的请求,可以正常触发熔断。 -------------------------------------------------------------------------------- /content/troubleshooting/cases/using-istio-reserved-port-causes-pod-start-failed.md: -------------------------------------------------------------------------------- 1 | # 使用 istio 保留端口导致 pod 启动失败 2 | 3 | ## 问题现象 4 | 5 | 所有新启动的 Pod 无法 ready,sidecar 报错: 6 | 7 | ```log 8 | warning envoy config gRPC config for type.googleapis.com/envoy.config.listener.v3.Listener rejected: Error adding/updating listener(s) 0.0.0.0_15090: error adding listener: '0.0.0.0_15090' has duplicate address '0.0.0.0:15090' as existing listener 9 | ``` 10 | 11 | 同时 istiod 也报错: 12 | 13 | ```log 14 | ADS:LDS: ACK ERROR sidecar~172.18.0.185~reviews-v1-7d46f9dd-w5k8q.istio-test~istio-test.svc.cluster.local-20847 Internal:Error adding/updating listener(s) 0.0.0.0_15090: error adding listener: '0.0.0.0_15090' has duplicate address '0.0.0.0:15090' as existing listener 15 | ``` 16 | 17 | ## 猜想 18 | 19 | 看报错应该是 sidecar 启动时获取 LDS 规则,istiod 发现 `0.0.0.0:15090` 这个监听重复了,属于异常现象,下发 xDS 规则就会失败,导致 sidecar 一直无法 ready。 20 | 21 | ## 分析 config_dump 22 | 23 | 随便找一个还未重启的正常 Pod,看一下 envoy config_dump: 24 | 25 | ```bash 26 | kubectl exec debug-68b799694-n9q66 -c istio-proxy -- curl localhost:15000/config_dump 27 | ``` 28 | 29 | 分析 json 发现 static 配置中有监听 `0.0.0.0:15090`: 30 | 31 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F09%2F22%2F20230922191243.png) 32 | 33 | ## 定位原因 34 | 35 | 猜测是 dynamic 配置中也有 `0.0.0.0:15090` 的监听导致的冲突,而 dynamic 中监听来源通常是 Kubernetes 的服务发现(Service, ServiceEntry),检查一下是否有 Service 监听 15090: 36 | 37 | ```bash 38 | kubectl get service --all-namespaces -o yaml | grep 15090 39 | ``` 40 | 41 | 最终发现确实有 Service 用到了 15090 端口,更改成其它端口即可恢复。 42 | 43 | ## 深入挖掘 44 | 45 | 搜索一下,可以发现 15090 端口是 istio 用于暴露 envoy prometheus 指标的端口,是 envoy 使用的端口之一: 46 | 47 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F09%2F22%2F20230922191255.png) 48 | 49 | > 参考 [Ports used by Istio](https://istio.io/latest/docs/ops/deployment/requirements/) 。 50 | 51 | 但并不是所有 envoy 使用的端口都被加入到 static 配置中的监听,只有 15090 和 15021 这两个端口在 static 配置中有监听,也验证了 Service 使用 15021 端口也会有相同的问题。 52 | 53 | Service 使用其它 envoy 的端口不会造成 sidecar 不 ready 的问题,但至少要保证业务程序也不能去监听这些端口,因为会跟 envoy 冲突,istio 官网也说明了这一点: `To avoid port conflicts with sidecars, applications should not use any of the ports used by Envoy`。 54 | 55 | ## 使用建议 56 | 57 | 根据上面分析,得出以下使用建议: 58 | 1. Service/ServiceEntry 不能定义 15090 和 15021 端口,不然会导致 Pod 无法启动成功。 59 | 2. 业务进程不能监听 envoy 使用到的所有端口: 15000, 15001, 15006, 15008, 15020, 15021, 15090 。 60 | -------------------------------------------------------------------------------- /content/troubleshooting/circuit-breaking-not-work.md: -------------------------------------------------------------------------------- 1 | # 熔断不生效 2 | 3 | ## 未定义 http1MaxPendingRequests 4 | 5 | 我们给 DR 配置了 `maxConnections`: 6 | 7 | ```yaml 8 | apiVersion: networking.istio.io/v1beta1 9 | kind: DestinationRule 10 | metadata: 11 | name: nginx 12 | spec: 13 | host: nginx 14 | # highlight-start 15 | trafficPolicy: 16 | connectionPool: 17 | tcp: 18 | maxConnections: 1 19 | # highlight-end 20 | ``` 21 | 22 | 但测试当并发超过这里定义的最大连接数时,并没有触发熔断,只是 QPS 很低。通常是因为没有配置 `http1MaxPendingRequests`,不配置默认为 `2^32-1`,非常大,表示如果超过最大连接数,请求就先等待(不直接返回503),当连接数低于最大值时再继续转发。 23 | 24 | 如果希望连接达到上限或超过上限一定量后或直接熔断(响应503),那么就需要显式指定一下 `http1MaxPendingRequests`: 25 | 26 | ```yaml 27 | apiVersion: networking.istio.io/v1beta1 28 | kind: DestinationRule 29 | metadata: 30 | name: nginx 31 | spec: 32 | host: nginx 33 | trafficPolicy: 34 | connectionPool: 35 | tcp: 36 | maxConnections: 1 37 | # highlight-start 38 | http: 39 | http1MaxPendingRequests: 1 40 | # highlight-end 41 | ``` 42 | -------------------------------------------------------------------------------- /content/troubleshooting/grpc-config-stream-closed.md: -------------------------------------------------------------------------------- 1 | # Envoy 报错: gRPC config stream closed 2 | 3 | ## gRPC config stream closed: 13 4 | 5 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F09%2F22%2F20230922190447.png) 6 | 7 | 这通常是正常的,因为控制面(istiod)默认每 30 分钟强制断开 xDS 连接,然后数据面(proxy)再自动重连。 8 | 9 | ## gRPC config stream closed: 14 10 | 11 | 如果只出现一次,通常是在 envoy 启动或重启时报这个错,没什么问题;但如果反复报这个错,可能是数据面(proxy)连接控制面(istiod)有问题,需要排查下。 12 | 13 | ## 参考资料 14 | 15 | * [Istio Common Issues](https://github.com/istio/istio/wiki/Troubleshooting-Istio#common-issues) 16 | -------------------------------------------------------------------------------- /content/troubleshooting/isito-init-crash.md: -------------------------------------------------------------------------------- 1 | # istio-init crash 2 | 3 | ## 问题描述 4 | 5 | 在 istio 环境下有 pod 处于 `Init:CrashLoopBackOff` 状态: 6 | 7 | ```txt 8 | wk-sys-acl-v1-0-5-7cf7f79d6c-d9qcr 0/2 Init:CrashLoopBackOff 283 64d 172.16.9.229 10.1.128.6 9 | ``` 10 | 11 | 查得 istio-init 的日志: 12 | 13 | 14 | 15 | ## 原因与解决方案 16 | 17 | 跟这个 issue 基本一致 https://github.com/istio/istio/issues/24148 18 | 19 | 直接原因: 这种情况应该通常是清理了已退出的 istio-init 容器,导致 k8s 检测到 pod 关联的容器不在了,然后会重新拉起被删除的容器,而 istio-init 的执行不可重入,因为之前已创建了 iptables 规则,导致后拉起的 istio-init 执行 iptables 失败而 crash。 20 | 21 | 根因与解决方案: 清理的动作通常是执行了 `docker container rm` 或 `docker container prune` 或 `docker system prune`。 一般是 crontab 定时脚本里定时清理了容器导致,需要停止清理。 22 | -------------------------------------------------------------------------------- /content/troubleshooting/locality-lb-not-working.md: -------------------------------------------------------------------------------- 1 | # 地域感知不生效 2 | 3 | 使用 istio 地域感知能力时,测试发现没生效,本文介绍几个常见原因。 4 | 5 | ## DestinationRule 未配置 outlierDetection 6 | 7 | 地域感知默认开启,但还需要配置 DestinationRule,且指定 `outlierDetection` 才可以生效,指定这个配置的作用主要是让 istio 感知 endpoints 是否异常,当前 locality 的 endpoints 发生异常时会 failover 到其它地方的 endpoints。 8 | 9 | 配置示例: 10 | 11 | ```yaml 12 | apiVersion: networking.istio.io/v1beta1 13 | kind: DestinationRule 14 | metadata: 15 | name: nginx 16 | spec: 17 | host: nginx 18 | # highlight-add-start 19 | trafficPolicy: 20 | outlierDetection: 21 | consecutive5xxErrors: 3 22 | interval: 30s 23 | baseEjectionTime: 30s 24 | # highlight-add-end 25 | ``` 26 | 27 | ## client 没配置 service 28 | 29 | istio 控制面会为每个数据面单独下发 EDS,不同数据面实例(Envoy)的locality可能不一样,生成的 EDS 也就可能不一样。istio会获取数据面的locality信息,获取方式主要是找到数据面对应的 endpoint 上保存的 region、zone 等信息,如果 client 没有任何 service,也就不会有 endpoint,控制面也就无法获取 client 的 locality 信息,也就无法实现地域感知。 30 | 31 | 解决方案: 为 client 配置 service,selector 选中 client 的 label。如果 client 本身不对外提供服务,service 的 ports 也可以随便定义一个。 32 | 33 | ## 使用了 headless service 34 | 35 | 如果是访问 headless service,本身是不支持地域感知的,因为 istio 会对 headless service 请求直接 passthrough,不做负载均衡,客户端会直接访问到 dns 解析出来的 pod ip。 36 | 37 | 解决方案: 单独再创建一个 service (非 headless) 38 | -------------------------------------------------------------------------------- /content/troubleshooting/status-code-404.md: -------------------------------------------------------------------------------- 1 | # 状态码: 404 Not Found 2 | 3 | ## 访问 StatefulSet Pod IP 返回 404 4 | 5 | * 问题描述:在 istio 中业务容器访问同集群一 Pod IP 返回 404,在 istio-proxy 中访问却正常 6 | * 原因: Pod 属于 StatefulSet,使用 headless svc,在 istio 中对 headless svc 的支持跟普通 svc 不太一样,如果 pod 用的普通 svc,对应的 listener 有兜底的 passthrough,即转发到报文对应的真实目的IP+Port,但 headless svc 的就没有,我们理解是因为 headless svc 没有 vip,它的路由是确定的,只指向后端固定的 pod,如果路由匹配不上就肯定出了问题,如果也用 passthrough 兜底路由,只会掩盖问题,所以就没有为 headless svc 创建 passthrough 兜底路由。同样的业务,上了 istio 才会有这个问题,也算是 istio 的设计或实现问题。 7 | * 示例场景: 使用了自己的服务发现,业务直接使用 Pod IP 调用 StatefulSet 的 Pod IP 8 | * 解决方案: 同集群访问 statefulset pod ip 带上 host,以匹配上 headless svc 路由,避免匹配不到就 4 -------------------------------------------------------------------------------- /content/troubleshooting/status-code-426.md: -------------------------------------------------------------------------------- 1 | # 状态码: 426 Upgrade Required 2 | 3 | ## 背景 4 | 5 | Istio 使用 Envoy 作为数据面转发 HTTP 请求,而 Envoy 默认要求使用 HTTP/1.1 或 HTTP/2,当客户端使用 HTTP/1.0 时就会返回 `426 Upgrade Required`。 6 | 7 | ## 常见的 nginx 场景 8 | 9 | 如果用 nginx 进行 `proxy_pass` 反向代理,默认会用 HTTP/1.0,你可以显示指定 [proxy_http_version](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_http_version) 为 `1.1`: 10 | 11 | ```nginx 12 | upstream http_backend { 13 | server 127.0.0.1:8080; 14 | 15 | keepalive 16; 16 | } 17 | 18 | server { 19 | ... 20 | 21 | location /http/ { 22 | proxy_pass http://http_backend; 23 | proxy_http_version 1.1; 24 | proxy_set_header Connection ""; 25 | ... 26 | } 27 | } 28 | ``` 29 | 30 | ## 压测场景 31 | 32 | ab 压测时会发送 HTTP/1.0 的请求,Envoy 固定返回 426 Upgrade Required,根本不会进行转发,所以压测的结果也不会准确。可以换成其它压测工具,如 [wrk](https://github.com/wg/wrk) 。 33 | 34 | ## 让 istio 支持 HTTP/1.0 35 | 36 | 有些 SDK 或框架可能会使用 HTTP/1.0 协议,比如使用 HTTP/1.0 去资源中心/配置中心 拉取配置信息,在不想改动代码的情况下让服务跑在 istio 上,也可以修改 istiod 配置,加上 `PILOT_HTTP10: 1` 的环境变量来启用 HTTP/1.0。 37 | 38 | ## 参考资料 39 | 40 | * [Envoy won’t connect to my HTTP/1.0 service](https://istio.io/latest/docs/ops/common-problems/network-issues/#envoy-won-t-connect-to-my-http-1-0-service) -------------------------------------------------------------------------------- /content/troubleshooting/status-code-431.md: -------------------------------------------------------------------------------- 1 | # 状态码: 431 Request Header Fields Too Large 2 | 3 | ## 问题描述 4 | 5 | istio 中 http 请求,envoy 返回 431 异常状态码: 6 | 7 | ``` txt 8 | HTTP/1.1 431 Request Header Fields Too Large 9 | ``` 10 | 11 | ## 原因分析 12 | 13 | 此状态码说明 http 请求 header 大小超限了,默认限制为 60 KiB,由 `HttpConnectionManager` 配置的 `max_request_headers_kb` 字段决定,最大可调整到 96 KiB: 14 | 15 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F09%2F22%2F20230922190327.png) 16 | 17 | ## 解决方案 18 | 19 | 可以通过 EnvoyFilter 调整 `max_request_headers_kb` 字段来提升 header 大小限制。 20 | 21 | EnvoyFilter 示例参考[这里](../usage/limit-request-size.md#%E9%99%90%E5%88%B6%E8%AF%B7%E6%B1%82%E5%A4%B4%E5%A4%A7%E5%B0%8F)。 22 | -------------------------------------------------------------------------------- /content/troubleshooting/virtualservice-not-working.md: -------------------------------------------------------------------------------- 1 | # VirutualService 不生效 2 | 3 | ## 背景 4 | 5 | 使用 istio ,在集群中定义了 `VirutualService`,但测试发现定义的规则似乎没有生效,通常是一些配置问题,本文列举一下常见的可能原因。 6 | 7 | ## 集群内访问: gateway 字段没有显式指定 "mesh" 8 | 9 | 如果 `VirutualService` 没有指定 `gateways` 字段,实际隐含了一层意思,istio 会默认加上一个叫 "mesh" 的保留 Gateway,表示集群内部所有 Sidecar,也就表示此 `VirutualService` 规则针对集群内的访问生效。 10 | 11 | 但如果指定了 `gateways` 字段,istio 不会默认加上 "mesh",如: 12 | 13 | ```yaml 14 | apiVersion: networking.istio.io/v1beta1 15 | kind: VirtualService 16 | metadata: 17 | name: productpage 18 | spec: 19 | gateways: 20 | - istio-test/test-gateway 21 | hosts: 22 | - bookinfo.example.com 23 | http: 24 | - route: 25 | - destination: 26 | host: productpage 27 | port: 28 | number: 9080 29 | ``` 30 | 31 | 这个表示此 `VirualService` 规则仅对 `istio-test/test-gateway` 这个 Gateway 生效,如果是在集群内访问,流量不会经过这个 Gateway,所以此规则也就不会生效。 32 | 33 | 那如果要同时在集群内也生效该怎么做呢?答案是给 `gateways` 显式指定上 "mesh": 34 | 35 | ```yaml 36 | gateways: 37 | - istio-test/test-gateway 38 | - mesh 39 | ``` 40 | 41 | 表示此 `VirutualService` 不仅对 `istio-test/test-gateway` 这个 Gateway 生效,也对集群内部访问生效。 42 | 43 | 参考 [istio 官方文档](https://istio.io/latest/docs/reference/config/networking/virtual-service/#VirtualService) 对此字段的解释: 44 | 45 | ![](https://image-host-1251893006.cos.ap-chengdu.myqcloud.com/2023%2F09%2F22%2F20230922190531.png) 46 | 47 | 值得注意的是,从集群内访问一般是直接访问 service 名称,这里 `hosts` 就需要加上访问集群内的 service 名称: 48 | 49 | ```yaml 50 | hosts: 51 | - bookinfo.example.com 52 | - productpage 53 | ``` 54 | 55 | ## 通过 ingressgateway 访问: hosts 定义错误 56 | 57 | 若从 ingressgateway 访问,需要确保 `Gateway` 和 `VirtualService` 中的 hosts 均包含实际访问用到的 `Host` 或使用通配符能匹配得上,通常是外部域名。 58 | 59 | 只要有一方 hosts 没定义正确,都可能导致 `404 Not Found`,正确示例: 60 | 61 | ```yaml 62 | apiVersion: networking.istio.io/v1alpha3 63 | kind: Gateway 64 | metadata: 65 | name: test-gateway 66 | namespace: istio-test 67 | spec: 68 | selector: 69 | app: istio-ingressgateway 70 | istio: ingressgateway 71 | servers: 72 | - port: 73 | number: 80 74 | name: HTTP-80-www 75 | protocol: HTTP 76 | hosts: 77 | - bookinfo.example.com # 这里定义外部访问域名 78 | 79 | --- 80 | 81 | apiVersion: networking.istio.io/v1beta1 82 | kind: VirtualService 83 | metadata: 84 | name: productpage 85 | spec: 86 | gateways: 87 | - istio-test/test-gateway 88 | hosts: 89 | - bookinfo.example.com # 这里也要定义外部访问域名 90 | http: 91 | - route: 92 | - destination: 93 | host: productpage 94 | port: 95 | number: 9080 96 | ``` 97 | -------------------------------------------------------------------------------- /content/usage/accesslog/accesslog-config.md: -------------------------------------------------------------------------------- 1 | # 配置 accesslog 2 | 3 | 本文介绍如何配置 istio 的 accesslog。 4 | 5 | ## 全局配置方法 6 | 7 | ### 修改 ConfigMap 配置 8 | 9 | 如果 istio 已经安装好,可以修改 istio ConfigMap 配置: 10 | 11 | ```bash 12 | kubectl -n istio-system edit configmap istio 13 | ``` 14 | 15 | 编辑 yaml: 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | * `accessLogEncoding`: 表示 accesslog 输出格式,istio 预定义了 `TEXT` 和 `JSON` 两种日志输出格式。默认使用 `TEXT`,通常我们习惯改成 `JSON` 以提升可读性,同时也利于日志采集。 37 | * `accessLogFile`: 表示 accesslog 输出到哪里,通常我们指定到 `/dev/stdout` (标准输出),以便使用 `kubectl logs` 来查看日志,同时也利于日志采集。 38 | * `accessLogFormat`: 如果不想使用 istio 预定义的 `accessLogEncoding`,我们也可以使用这个配置来自定义日志输出格式。完整的格式规则与变量列表参考 [Envoy 官方文档](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage) 。 39 | 40 | ### 通过 istioctl 安装时配置 41 | 42 | 如果使用 istioctl 安装的 istio,也可以用类似以下命令进行配置: 43 | 44 | ```bash 45 | istioctl install --set profile=demo --set meshConfig.accessLogFile="/dev/stdout" --set meshConfig.accessLogEncoding="JSON" 46 | ``` 47 | 48 | ## 关于日志格式 49 | 50 | 一般建议用 JSON 的单行格式,可读性高,也方便日志采集和统计分析。 51 | 52 | ### JSON 格式 53 | 54 | istio 的 json accesslog 配置格式见 [源码](https://github.com/istio/istio/blob/1.19.3/pilot/pkg/model/telemetry_logging.go#L76C17-L102) 。转换成字符串为: 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ### TEXT 格式 67 | 68 | istio 的 text accesslog 配置格式见 [源码](https://github.com/istio/istio/blob/1.19.3/pilot/pkg/model/telemetry_logging.go#L45-L52) 。转换成字符串为: 69 | 70 | ```txt 71 | [%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%" %RESPONSE_CODE% %RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS% %CONNECTION_TERMINATION_DETAILS% "%UPSTREAM_TRANSPORT_FAILURE_REASON%" %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%" "%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%" %UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% %REQUESTED_SERVER_NAME% %ROUTE_NAME% 72 | ``` 73 | 74 | ## 部分 workload 启用 accesslog 75 | 76 | 在生产环境中,有时我们不想全局启用 accesslog,我们可以利用 EnvoyFilter 来实现只为部分 namespace 或 workload 启用 accesslog,参考 [为指定 workload 动态启动 accesslog](./enable-accesslog-for-workload.md) 。 77 | 78 | 79 | ## 参考资料 80 | 81 | * [istio 官方文档给出的常见变量的示例](https://istio.io/latest/docs/tasks/observability/logs/access-log/#default-access-log-format) 82 | * [Envoy Access Log 变量参考](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators) 83 | -------------------------------------------------------------------------------- /content/usage/accesslog/accesslog-print-header-body.md: -------------------------------------------------------------------------------- 1 | # accesslog 打印 header 和 body 2 | 3 | 在排障的时候,如果希望将请求头、请求体、响应头或响应体打印出来进行调试,这时候可通过 EnvoyFilter 来针对需要调试的 workload 动态启用(不建议全局启用,会降低性能、增加内存占用): 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 | 58 | 59 | 60 | 61 | 62 | ## 参考资料 63 | 64 | * [Envoy Access Log 变量参考](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators) 65 | * [Istio Default Access Log Format](https://istio.io/latest/docs/tasks/observability/logs/access-log/#default-access-log-format) 66 | -------------------------------------------------------------------------------- /content/usage/accesslog/enable-accesslog-for-workload.md: -------------------------------------------------------------------------------- 1 | # 为指定 workload 动态启动 accesslog 2 | 3 | 可以用 EnvoyFilter 给部分需要的 workload 动态启用 accesslog (还可自定义日志格式): 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /content/usage/cors.md: -------------------------------------------------------------------------------- 1 | # 使用 corsPolicy 解决跨域问题 2 | 3 | 通常解决跨域问题都是在 web 框架中进行配置,使用 istio 后我们可以将其交给 istio 处理,业务不需要关心。本文介绍如何利用 Istio 配置来对 HTTP 服务启用跨域支持。 4 | 5 | ## 配置方法 6 | 7 | Istio 中通过配置 VirtualService 的 corsPolicy 可以实现跨域支持,示例: 8 | 9 | 10 | 11 | * 关键配置在于 `allowOrigins`,表示允许带哪些 Origin 地址的请求。 12 | * 若有多个域名,使用 `regex` 匹配,`|` 符号分隔。 13 | * 若同时支持 http 和 https,`regex` 中的地址在 `http` 后面加 `s?`,表示匹配 `http` 或 `https`,即两种协议同时支持。 14 | * 关于 `corsPolicy` 更多配置,参考 [Istio CorsPolicy 官方文档](https://istio.io/latest/docs/reference/config/networking/virtual-service/#CorsPolicy) 。 15 | 16 | ## 一些误区 17 | 18 | 有同学测试发现,当请求带上了错误的 `Origin` 或没带 `Origin` 时,响应内容也正常返回了: 19 | ```bash 20 | $ curl -I -H 'Origin: http://fake.example.com' 1.1.1.1:80 21 | HTTP/1.1 200 OK 22 | ... 23 | ``` 24 | 25 | 为什么能正常返回呢?corsPolicy 没生效吗?有这样疑问的同学可能对 [CORS](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS) 理解不太到位。 26 | 27 | 控制请求能否跨域的逻辑核心在于浏览器,浏览器通过判断请求响应的 [access-control-allow-origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) header 中是否有当前页面的地址,来判断该跨域请求能否被允许。所以业务要对跨域支持的关键点在于对 `access-control-allow-origin` 这个头的支持,通常一些 web 框架支持跨域也主要是干这个,为响应自动加上 `access-control-allow-origin` 响应头,istio 也不例外。 28 | 29 | 所以这里请求一般都能正常返回,只是如果跨域校验失败的话不会响应 `access-control-allow-origin` 这个 header 以告知浏览器该请求不能跨域,但响应的 body 是正常的,不会做修改。 30 | -------------------------------------------------------------------------------- /content/usage/enable-gzip.md: -------------------------------------------------------------------------------- 1 | # 启用 gzip 压缩 2 | 3 | ## 背景 4 | 5 | istio 的 ingressgateway 默认没启用 gzip 压缩,相比之下,消耗的流量比较大,如果流量走的公网就比较烧钱,可以利用 EnvoyFilter 来为 ingressgateway 启用 gzip 压缩,节约带宽成本。 6 | 7 | ## EnvoyFilter 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /content/usage/iphash.md: -------------------------------------------------------------------------------- 1 | # 基于 iphash 进行负载均衡 2 | 3 | ## 场景 4 | 5 | 根据源 IP 进行负载均衡,在 istio 中如何配置呢 ? 6 | 7 | ## 用法 8 | 9 | 配置 `DestinationRule`,指定 `useSourceIp` 负载均衡策略: 10 | 11 | 12 | 13 | ## 参考资料 14 | 15 | * 官方参考文档: [LoadBalancerSettings.ConsistentHashLB](https://istio.io/latest/docs/reference/config/networking/destination-rule/#LoadBalancerSettings-ConsistentHashLB) 16 | -------------------------------------------------------------------------------- /content/usage/label-traffic.md: -------------------------------------------------------------------------------- 1 | # 流量打标 2 | 3 | ## 为指定 workload 的出流量自动加上特定 header 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /content/usage/limit-request-size.md: -------------------------------------------------------------------------------- 1 | # 限制请求大小 2 | 3 | ## 背景 4 | 5 | Envoy 默认对请求大小有限制,包括请求头大小限制和整个请求的大小限制,分别由 [max_request_headers_kb](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#extensions-filters-network-http-connection-manager-v3-httpconnectionmanager) 和 [max_request_bytes](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/buffer/v3/buffer.proto#extensions-filters-http-buffer-v3-buffer) 这两个参数控制,以下是这两个参数的解释: 6 | 7 | ```txt 8 | max_request_headers_kb 9 | (UInt32Value) The maximum request headers size for incoming connections. If unconfigured, the default max request headers allowed is 60 KiB. Requests that exceed this limit will receive a 431 response. 10 | 11 | max_request_bytes 12 | (UInt32Value, REQUIRED) The maximum request size that the filter will buffer before the connection manager will stop buffering and return a 413 response. 13 | ``` 14 | 15 | 简而言之: 16 | 17 | 1. 超出 `max_request_headers_kb` (请求头)的限制会响应 `431 Request Header Fields Too Large`,默认限制为 60 KiB。 18 | 2. 超出 `max_request_bytes` (请求头+请求体)的限制会响应 `413 Request Entity Too Large`,默认不限制。 19 | 20 | 有些时候需要调整下该限制: 21 | 1. 一些恶意请求的请求体过大导致 Envoy 和业务进程内存暴涨,需要限制请求体大小,防止攻击。 22 | 2. 一些特殊用途导致请求头比较大,需要上调默认的请求头限制避免响应 431。 23 | 24 | 一般只需要 ingressgateway 调整这些限制,下面给出针对 ingressgateway 调整限制的 EnvoyFilter。 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 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /content/usage/preserve-case.md: -------------------------------------------------------------------------------- 1 | # 保留 header 大小写 2 | 3 | ## 背景 4 | 5 | Envoy 默认会将 header 统一转换为小写,HTTP 的 RFC 规范也要求应用不能对 header 大小写敏感,所以正常情况下没什么问题。 6 | 7 | 但有些应用没有遵循 RFC 规范,对大小写敏感了,导致迁移到 istio 环境后报错。这时可以通过 EnvoyFilter 让 Envoy 保留 HTTP header 大小写。 8 | 9 | ## 请求与响应的 header 大小写都保留 10 | 11 | 12 | 13 | ## 只保留请求 header 的大小写 14 | 15 | 16 | 17 | ## 只保留响应 header 的大小写 18 | 19 | 20 | -------------------------------------------------------------------------------- /content/usage/ratelimit.md: -------------------------------------------------------------------------------- 1 | # 限流 2 | 3 | ## 本地限流 4 | 5 | 本地限流的意思是只针对单个代理 (ingressgateway 或 sidecar) 的速率限制,不是全局的,不过一般用本地限流就足够了,能够起到保护后端不过载的作用。 6 | 7 | 实现方式是配置 EnvoyFilter,让 Envoy 本地统计请求的 QPS,然后根据统计数据判断是否要限流。 8 | 9 | ### 加注解 10 | 11 | 在配置 EnvoyFilter 前首先在要在部署 workload 时给 pod 加注解配置让 Envoy 启用 `http_local_rate_limit` 的统计数据 ,示例(注意高亮部分): 12 | 13 | 14 | 15 | ### 创建 EnvoyFilter 16 | 17 | 然后根据限流需求创建 EnvoyFilter,示例: 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ## 全局限流 30 | 31 | 全局限流的意思是在全局限制 QPS,既然是全局,那必然有单独的地方存储请求的统计数据,一般使用自行部署的 redis 来存储,然后限流服务根据 redis 中全局的统计数据判断是否要限流。 32 | 33 | 这个相对麻烦,可参考 [官方文档](https://istio.io/latest/zh/docs/tasks/policy-enforcement/rate-limit/#global-rate-limit)。 34 | 35 | ## 参考资料 36 | 37 | * [使用 Envoy 启用速率限制](https://istio.io/latest/zh/docs/tasks/policy-enforcement/rate-limit/) 38 | -------------------------------------------------------------------------------- /content/usage/response-request-id.md: -------------------------------------------------------------------------------- 1 | # 响应 x-request-id 2 | 3 | ## 背景 4 | 5 | 有时希望在响应头中返回请求头中的 `x-request-id`,以便于跟踪请求,这时我们可以利用 EnvoyFilter 来实现。 6 | 7 | ## EnvoyFilter 8 | 9 | 10 | 11 | ## 效果 12 | 13 | ```bash showLineNumbers 14 | $ curl -I https://imroc.cc/istio 15 | HTTP/2 200 16 | server: istio-envoy 17 | date: Wed, 15 Nov 2023 03:39:31 GMT 18 | content-type: text/html 19 | content-length: 37698 20 | last-modified: Sat, 14 Oct 2023 01:02:42 GMT 21 | etag: "6529e8b2-9342" 22 | accept-ranges: bytes 23 | x-envoy-upstream-service-time: 5 24 | # highlight-next-line 25 | x-request-id: 712fd33d-5dd8-441b-aac7-0280de975a85 26 | vary: Accept-Encoding 27 | ``` 28 | -------------------------------------------------------------------------------- /content/usage/websocket.md: -------------------------------------------------------------------------------- 1 | # 使用 websocket 协议 2 | 3 | 业务使用的 websocket 协议,想跑在 istio 中,那么在 istio 中如何配置 websocket 呢? 4 | 5 | ## 用法 6 | 7 | 由于 websocket 本身基于 HTTP,所以在 istio 中直接按照普通 http 来配就行了: 8 | 9 | ```yaml 10 | apiVersion: networking.istio.io/v1alpha3 11 | kind: Gateway 12 | metadata: 13 | name: tornado-gateway 14 | spec: 15 | selector: 16 | istio: ingressgateway 17 | servers: 18 | - port: 19 | number: 80 20 | name: http 21 | protocol: HTTP 22 | hosts: 23 | - "*" 24 | --- 25 | apiVersion: networking.istio.io/v1alpha3 26 | kind: VirtualService 27 | metadata: 28 | name: tornado 29 | spec: 30 | hosts: 31 | - "*" 32 | gateways: 33 | - tornado-gateway 34 | http: 35 | - match: 36 | - uri: 37 | prefix: / 38 | route: 39 | - destination: 40 | host: tornado 41 | weight: 100 42 | ``` 43 | 44 | ## 参考资料 45 | 46 | * 官方 sample: [Tornado - Demo Websockets App](https://github.com/istio/istio/tree/master/samples/websockets) 47 | -------------------------------------------------------------------------------- /docusaurus.config.ts: -------------------------------------------------------------------------------- 1 | import PrismDark from './src/utils/prismDark'; 2 | import type { Config } from '@docusaurus/types'; 3 | const beian = '蜀ICP备2021009081号-1' 4 | 5 | const config: Config = { 6 | title: 'istio 实践指南', // 网站标题 7 | tagline: '云原生老司机带你飞', // slogan 8 | favicon: 'img/logo.svg', // 电子书 favicon 文件,注意替换 9 | 10 | url: 'https://imroc.cc', // 在线电子书的 url 11 | baseUrl: '/istio/', // 在线电子书所在 url 的路径,如果没有子路径,可改为 "/" 12 | 13 | // GitHub pages deployment config. 14 | // If you aren't using GitHub pages, you don't need these. 15 | organizationName: 'imroc', // GitHub 的 org/user 名称 16 | projectName: 'istio-guide', // Github repo 名称 17 | 18 | onBrokenLinks: 'warn', // 避免路径引用错误导致编译失败 19 | onBrokenMarkdownLinks: 'warn', 20 | 21 | i18n: { 22 | // 默认语言用中文 23 | defaultLocale: 'zh-CN', 24 | // 不需要多语言支持的话,就只填中文 25 | locales: ['zh-CN'], 26 | }, 27 | 28 | plugins: [ 29 | 'docusaurus-plugin-sass', // 启用 sass 插件,支持 scss 30 | 'plugin-image-zoom', 31 | [ 32 | '@docusaurus/plugin-ideal-image', 33 | { 34 | disableInDev: false, 35 | }, 36 | ], 37 | [ 38 | '@docusaurus/plugin-pwa', 39 | { 40 | debug: false, 41 | offlineModeActivationStrategies: [ 42 | 'appInstalled', 43 | 'standalone', 44 | 'queryString', 45 | ], 46 | pwaHead: [ 47 | { tagName: 'link', rel: 'icon', href: '/img/logo.png' }, 48 | { tagName: 'link', rel: 'manifest', href: '/manifest.json' }, 49 | { tagName: 'meta', name: 'theme-color', content: '#12affa' }, 50 | ], 51 | }, 52 | ], 53 | [ 54 | /** @type {import('@docusaurus/plugin-content-docs').PluginOptions} */ 55 | '@docusaurus/plugin-content-docs', 56 | ({ 57 | id: 'istio', 58 | path: 'content', 59 | // 文档的路由前缀 60 | routeBasePath: '/', 61 | // 左侧导航栏的配置 62 | sidebarPath: require.resolve('./content/sidebars.ts'), 63 | // 每个文档左下角 "编辑此页" 的链接 64 | editUrl: ({ docPath }) => 65 | `https://github.com/imroc/istio-guide/edit/main/content/${docPath}`, 66 | }), 67 | ], 68 | ], 69 | 70 | presets: [ 71 | [ 72 | 'classic', 73 | /** @type {import('@docusaurus/preset-classic').Options} */ 74 | ({ 75 | docs: false, // 禁用 preset 默认的 docs,直接用 plugin-content-docs 配置可以更灵活。 76 | blog: false, // 禁用博客 77 | theme: { 78 | customCss: require.resolve('./src/css/custom.scss'), // custom.css 重命名为 custom.scss 79 | }, 80 | }), 81 | ], 82 | ], 83 | 84 | themeConfig: 85 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 86 | ({ 87 | // algolia 搜索功能 88 | algolia: { 89 | appId: 'OOTHS47ZW2', 90 | apiKey: '5d043edc32ada7f31ef29395f39ee554', 91 | indexName: 'imroc-istio', 92 | contextualSearch: false, 93 | }, 94 | // giscus 评论功能 95 | giscus: { 96 | repo: 'imroc/istio-guide', 97 | repoId: 'R_kgDOHFP9XQ', 98 | category: 'General', 99 | categoryId: 'DIC_kwDOHFP9Xc4COUDN', 100 | }, 101 | navbar: { 102 | title: 'istio 实践指南', // 左上角的电子书名称 103 | logo: { 104 | alt: 'istio', 105 | src: 'img/logo.svg', // 电子书 logo 文件,注意替换 106 | }, 107 | items: [ 108 | { 109 | href: 'https://github.com/imroc/istio-guide', // 改成自己的仓库地址 110 | label: 'GitHub', 111 | position: 'right', 112 | }, 113 | ], 114 | }, 115 | // 自定义页脚 116 | footer: { 117 | style: 'dark', 118 | links: [ 119 | { 120 | title: '相关电子书', 121 | items: [ 122 | { 123 | label: 'Kubernetes 实践指南', 124 | href: 'https://imroc.cc/kubernetes', 125 | }, 126 | { 127 | label: 'TKE 实践指南', 128 | href: 'https://imroc.cc/tke', 129 | }, 130 | ], 131 | }, 132 | { 133 | title: '更多', 134 | items: [ 135 | { 136 | label: 'roc 云原生', 137 | href: 'https://imroc.cc', 138 | }, 139 | { 140 | label: 'GitHub', 141 | href: 'https://github.com/imroc/istio-guide', 142 | }, 143 | ], 144 | }, 145 | ], 146 | copyright: `Copyright ${new Date().getFullYear()} roc | All Right Reserved | ${beian}`, 147 | }, 148 | // 自定义代码高亮 149 | prism: { 150 | theme: PrismDark, 151 | magicComments: [ 152 | { 153 | className: 'code-block-highlighted-line', 154 | line: 'highlight-next-line', 155 | block: { start: 'highlight-start', end: 'highlight-end' } 156 | }, 157 | { 158 | className: 'code-block-add-line', 159 | line: 'highlight-add-line', 160 | block: { start: 'highlight-add-start', end: 'highlight-add-end' } 161 | }, 162 | { 163 | className: 'code-block-update-line', 164 | line: 'highlight-update-line', 165 | block: { start: 'highlight-update-start', end: 'highlight-update-end' } 166 | }, 167 | { 168 | className: 'code-block-error-line', 169 | line: 'highlight-error-line', 170 | block: { start: 'highlight-error-start', end: 'highlight-error-end' } 171 | }, 172 | ], 173 | // languages enabled by default: https://github.com/FormidableLabs/prism-react-renderer/blob/master/packages/generate-prism-languages/index.ts#L9-L23 174 | // prism supported languages: https://prismjs.com/#supported-languages 175 | additionalLanguages: [ 176 | 'java', 177 | 'json', 178 | 'hcl', 179 | 'bash', 180 | 'diff', 181 | ], 182 | }, 183 | }), 184 | }; 185 | 186 | export default config; 187 | -------------------------------------------------------------------------------- /giscus.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultCommentOrder": "newest" 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "istio-guide", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "^3.6.0", 19 | "@docusaurus/plugin-ideal-image": "^3.6.0", 20 | "@docusaurus/plugin-pwa": "^3.6.0", 21 | "@docusaurus/preset-classic": "^3.6.0", 22 | "@giscus/react": "^3.0.0", 23 | "@mdx-js/react": "^3.1.0", 24 | "clsx": "^2.1.1", 25 | "docusaurus-plugin-sass": "^0.2.5", 26 | "path-browserify": "^1.0.1", 27 | "plugin-image-zoom": "github:flexanalytics/plugin-image-zoom", 28 | "prism-react-renderer": "^2.4.0", 29 | "raw-loader": "^4.0.2", 30 | "react": "^18.3.1", 31 | "react-dom": "^18.3.1", 32 | "sass": "^1.80.6" 33 | }, 34 | "devDependencies": { 35 | "@docusaurus/module-type-aliases": "^3.6.0", 36 | "@docusaurus/tsconfig": "^3.6.0", 37 | "@docusaurus/types": "^3.6.0", 38 | "typescript": "^5.6.3" 39 | }, 40 | "browserslist": { 41 | "production": [ 42 | ">0.5%", 43 | "not dead", 44 | "not op_mini all" 45 | ], 46 | "development": [ 47 | "last 3 chrome version", 48 | "last 3 firefox version", 49 | "last 5 safari version" 50 | ] 51 | }, 52 | "engines": { 53 | "node": ">=18.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/Comment.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useThemeConfig, useColorMode } from '@docusaurus/theme-common' 3 | import Giscus, { GiscusProps } from '@giscus/react' 4 | import { useLocation } from '@docusaurus/router'; 5 | 6 | const defaultConfig: Partial = { 7 | id: 'comments', 8 | mapping: 'specific', 9 | reactionsEnabled: '1', 10 | emitMetadata: '0', 11 | inputPosition: 'top', 12 | loading: 'lazy', 13 | strict: '1', 14 | lang: 'zh-CN', 15 | } 16 | 17 | export default function Comment(): JSX.Element { 18 | const themeConfig = useThemeConfig() 19 | 20 | // merge default config 21 | const giscus = { ...defaultConfig, ...themeConfig.giscus } 22 | 23 | if (!giscus.repo || !giscus.repoId || !giscus.categoryId) { 24 | throw new Error( 25 | 'You must provide `repo`, `repoId`, and `categoryId` to `themeConfig.giscus`.', 26 | ) 27 | } 28 | 29 | const path = useLocation().pathname.replace(/^\/|\/$/g, ''); 30 | const firstSlashIndex = path.indexOf('/'); 31 | var subPath: string = "" 32 | if (firstSlashIndex !== -1) { 33 | subPath = path.substring(firstSlashIndex + 1) 34 | } else { 35 | subPath = "index" 36 | } 37 | 38 | giscus.term = subPath 39 | giscus.theme = 40 | useColorMode().colorMode === 'dark' ? 'transparent_dark' : 'light' 41 | 42 | return ( 43 | 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /src/components/FileBlock.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CodeBlock from '@theme/CodeBlock'; 3 | import { useLocation } from '@docusaurus/router'; 4 | import * as path from 'path-browserify'; 5 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 6 | 7 | let extToLang = new Map([ 8 | ["sh", "bash"], 9 | ["yml", "yaml"] 10 | ]); 11 | 12 | export default function FileBlock({ file, showFileName, ...prop }: { file: string, showFileName?: boolean }) { 13 | // get url path without "/" prefix and suffix 14 | var urlPath = useLocation().pathname.replace(/^\/|\/$/g, ''); 15 | 16 | // remove locale prefix in urlPath 17 | const { i18n } = useDocusaurusContext() 18 | if (i18n.currentLocale != i18n.defaultLocale) { 19 | urlPath = urlPath.replace(/^[^\/]*\/?/g, '') 20 | } 21 | 22 | // find file content according to topPath and file path param 23 | var filepath = "" 24 | if (file.startsWith("@site/")) { 25 | filepath = file.replace(/^@site\//g, '') 26 | } else { 27 | filepath = "codeblock/" + file 28 | } 29 | 30 | // load file raw content according to filepath 31 | var content = require('!!raw-loader!@site/' + filepath)?.default 32 | content = content.replace(/\t/g, " "); // replace tab to 2 spaces 33 | 34 | // infer language of code block based on filename extension if language is not set 35 | const filename = path.basename(file); 36 | if (!prop.language) { 37 | var language = path.extname(filename).replace(/^\./, '') 38 | const langMappingName = extToLang.get(language) 39 | if (langMappingName) { 40 | language = langMappingName 41 | } 42 | prop.language = language 43 | } 44 | 45 | // set title to filename if showFileName is set and title is not set 46 | if (!prop.title && showFileName) { 47 | prop.title = filename 48 | } 49 | 50 | return ( 51 | 52 | {content} 53 | 54 | ); 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/css/custom.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | 19 | // “喜爱” 图标的颜色 20 | --site-color-svg-icon-favorite: #e9669e; 21 | } 22 | 23 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 24 | [data-theme='dark'] { 25 | --ifm-color-primary: #25c2a0; 26 | --ifm-color-primary-dark: #21af90; 27 | --ifm-color-primary-darker: #1fa588; 28 | --ifm-color-primary-darkest: #1a8870; 29 | --ifm-color-primary-light: #29d5b0; 30 | --ifm-color-primary-lighter: #32d8b4; 31 | --ifm-color-primary-lightest: #4fddbf; 32 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 33 | } 34 | 35 | /* 代码高亮行的样式 */ 36 | .code-block-highlighted-line { 37 | background-color: rgb(72, 77, 91); 38 | span[class*='codeLineNumber'] { 39 | background-color: rgb(72, 77, 91); 40 | } 41 | } 42 | .code-block-add-line { 43 | background-color: #213227; 44 | span[class*='codeLineNumber'] { 45 | background-color: #213227; 46 | } 47 | } 48 | .code-block-update-line { 49 | background-color: #362d1e; 50 | span[class*='codeLineNumber'] { 51 | background-color: #362d1e; 52 | } 53 | } 54 | .code-block-error-line { 55 | background-color: #ff000020; 56 | span[class*='codeLineNumber'] { 57 | background-color: #ff000020; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/theme/DocCategoryGeneratedIndexPage/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DocCategoryGeneratedIndexPage from '@theme-original/DocCategoryGeneratedIndexPage'; 3 | import type DocCategoryGeneratedIndexPageType from '@theme/DocCategoryGeneratedIndexPage'; 4 | import type { WrapperProps } from '@docusaurus/types'; 5 | // highlight-add-line 6 | import Comment from '@site/src/components/Comment'; 7 | 8 | type Props = WrapperProps; 9 | 10 | export default function DocCategoryGeneratedIndexPageWrapper(props: Props): JSX.Element { 11 | return ( 12 | <> 13 | 14 | {/* highlight-add-line */} 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/theme/DocItem/Layout/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Layout from '@theme-original/DocItem/Layout'; 3 | import type LayoutType from '@theme/DocItem/Layout'; 4 | import type { WrapperProps } from '@docusaurus/types'; 5 | // highlight-add-line 6 | import Comment from '@site/src/components/Comment'; 7 | 8 | type Props = WrapperProps; 9 | 10 | export default function LayoutWrapper(props: Props): JSX.Element { 11 | return ( 12 | <> 13 | 14 | {/* highlight-add-line */} 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/theme/MDXComponents.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | // Import the original mapper 3 | import MDXComponents from '@theme-original/MDXComponents'; 4 | 5 | import FileBlock from '@site/src/components/FileBlock'; 6 | import CodeBlock from '@theme-original/CodeBlock'; 7 | import Tabs from '@theme-original/Tabs'; 8 | import TabItem from '@theme-original/TabItem'; 9 | 10 | export default { 11 | // Re-use the default mapping 12 | ...MDXComponents, 13 | // Add more components to be imported by default 14 | FileBlock, 15 | CodeBlock, 16 | Tabs, 17 | TabItem, 18 | }; 19 | -------------------------------------------------------------------------------- /src/utils/prismDark.ts: -------------------------------------------------------------------------------- 1 | import { themes, type PrismTheme } from 'prism-react-renderer'; 2 | 3 | const baseTheme = themes.vsDark; 4 | 5 | export default { 6 | plain: { 7 | color: '#D4D4D4', 8 | backgroundColor: '#212121', 9 | }, 10 | styles: [ 11 | ...baseTheme.styles, 12 | { 13 | types: ['title'], 14 | style: { 15 | color: '#569CD6', 16 | fontWeight: 'bold', 17 | }, 18 | }, 19 | { 20 | types: ['property', 'parameter'], 21 | style: { 22 | color: '#9CDCFE', 23 | }, 24 | }, 25 | { 26 | types: ['script'], 27 | style: { 28 | color: '#D4D4D4', 29 | }, 30 | }, 31 | { 32 | types: ['boolean', 'arrow', 'atrule', 'tag'], 33 | style: { 34 | color: '#569CD6', 35 | }, 36 | }, 37 | { 38 | types: ['number', 'color', 'unit'], 39 | style: { 40 | color: '#B5CEA8', 41 | }, 42 | }, 43 | { 44 | types: ['font-matter'], 45 | style: { 46 | color: '#CE9178', 47 | }, 48 | }, 49 | { 50 | types: ['keyword', 'rule'], 51 | style: { 52 | color: '#C586C0', 53 | }, 54 | }, 55 | { 56 | types: ['regex'], 57 | style: { 58 | color: '#D16969', 59 | }, 60 | }, 61 | { 62 | types: ['maybe-class-name'], 63 | style: { 64 | color: '#4EC9B0', 65 | }, 66 | }, 67 | { 68 | types: ['constant'], 69 | style: { 70 | color: '#4FC1FF', 71 | }, 72 | }, 73 | ], 74 | } satisfies PrismTheme; 75 | 76 | -------------------------------------------------------------------------------- /static/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "istio 实践指南", 3 | "short_name": "istio 实践指南", 4 | "theme_color": "#12affa", 5 | "background_color": "#424242", 6 | "display": "standalone", 7 | "scope": "https://imroc.cc/istio/", 8 | "start_url": "https://imroc.cc/istio/", 9 | "related_applications": [ 10 | { 11 | "platform": "webapp", 12 | "url": "https://imroc.cc/istio/manifest.json" 13 | } 14 | ], 15 | "icons": [ 16 | { 17 | "src": "img/logo.svg", 18 | "sizes": "any" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@docusaurus/tsconfig", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | --------------------------------------------------------------------------------