Skip to content

Proxi Practice 2 #43

Description

@abzalimovrrr
70Lua скрипты.
# haproxy.cfg с Lua

global
lua-load /etc/haproxy/auth.lua
lua-load /etc/haproxy/rate_limit.lua
set-var proc.start_time now()

frontend web
bind *:80

Lua в http-request

http-request lua.auth_check
http-request lua.rate_limit

default_backend app

backend app
server app1 10.0.0.1:3000 check

/etc/haproxy/auth.lua

local _M = {}
function _M.auth_check(txn)
local auth = txn.http:req_hdr("Authorization")
if auth and auth:find("Bearer ") == 1 then
local token = auth:sub(8)
if token == "valid-token" then
txn:set_var("req.authenticated", true)
return
end
end
txn:set_var("req.authenticated", false)
txn.http:send_response(401, nil, "{"error":"Unauthorized"}")
end

function _M.rate_limit(txn)
local key = txn.sf:src()
local count = txn:get_var("rate." .. key) or 0
if count > 100 then
txn.http:send_response(429, nil, "{"error":"Rate limit"}")
return
end
txn:set_var("rate." .. key, count + 1)
end

return _M

71Rate limit Envoy.
static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address: { address: 0.0.0.0, port_value: 10000 }
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                stat_prefix: ingress_http
                route_config:
                  name: local_route
                  virtual_hosts:
                    - name: backend
                      domains: ["*"]
                      routes:
                        - match: { prefix: "/" }
                          route: { cluster: service_1 }
                http_filters:
                  - name: envoy.filters.http.ratelimit
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit
                      domain: myapp
                      stage: 0
                      request_type: both
                      timeout: 0.25s
                  - name: envoy.filters.http.router
  clusters:
    - name: rate_limit_service
      type: STRICT_DNS
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: rate_limit_service
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address: { address: ratelimit, port_value: 10001 }

rate_limits:

  • actions:
    • remote_address: {}
      limit:
      requests_per_unit: 100
      unit: SECOND
72Ext Authz.
static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address: { address: 0.0.0.0, port_value: 10000 }
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                stat_prefix: ingress_http
                http_filters:
                  - name: envoy.filters.http.ext_authz
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
                      transport_api_version: V3
                      grpc_service:
                        envoy_grpc:
                          cluster_name: auth_service
                        timeout: 0.5s
                      with_request_body:
                        max_request_bytes: 1024
                        allow_partial_message: true
                      clear_route_cache: true
                      metadata_context_namespaces:
                        - envoy.filters.http.ext_authz
                      allowed_headers:
                        patterns:
                          - exact: authorization
                          - prefix: x-
                  - name: envoy.filters.http.router
  clusters:
    - name: auth_service
      type: STRICT_DNS
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: auth_service
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address: { address: auth, port_value: 9001 }
73WASM фильтры.
static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address: { address: 0.0.0.0, port_value: 10000 }
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                stat_prefix: ingress_http
                http_filters:
                  - name: envoy.filters.http.wasm
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
                      config:
                        name: my_wasm_filter
                        root_id: my_filter_root_id
                        vm_config:
                          vm_id: my_vm
                          runtime: envoy.wasm.runtime.v8
                          code:
                            local:
                              filename: /etc/envoy/filters/my_filter.wasm
                          allow_precompiled: false
                        configuration:
                          "@type": type.googleapis.com/google.protobuf.StringValue
                          value: |
                            {
                              "header_name": "x-wasm-processed",
                              "header_value": "true"
                            }
                  - name: envoy.filters.http.router

Компиляция WASM:

cargo build --target=wasm32-wasi --release

docker cp target/wasm32-wasi/release/my_filter.wasm envoy:/etc/envoy/filters/

74Логирование.
static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address: { address: 0.0.0.0, port_value: 10000 }
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                stat_prefix: ingress_http
                access_log:
                  - name: envoy.access_loggers.file
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
                      path: /var/log/envoy/access.log
                      format:
                        string_format: |
                          [%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%" %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-REQUEST-ID)% "%REQ(USER-AGENT)%" "%REQ(X-FORWARDED-FOR)%"
                  - name: envoy.access_loggers.file
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
                      path: /var/log/envoy/access.json
                      format:
                        json_format:
                          start_time: "%START_TIME%"
                          method: "%REQ(:METHOD)%"
                          path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
                          protocol: "%PROTOCOL%"
                          status: "%RESPONSE_CODE%"
                          duration: "%DURATION%"
                          bytes_received: "%BYTES_RECEIVED%"
                          bytes_sent: "%BYTES_SENT%"
                          user_agent: "%REQ(USER-AGENT)%"
                          xff: "%REQ(X-FORWARDED-FOR)%"
75Повторные попытки.
route_config:
  name: local_route
  virtual_hosts:
    - name: backend
      domains: ["*"]
      routes:
        - match: { prefix: "/" }
          route:
            cluster: service_1
            retry_policy:
              retry_on: gateway-error,connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes
              num_retries: 3
              per_try_timeout: 1s
              per_try_idle_timeout: 5s
              retry_host_predicate:
                - name: envoy.retry_host_predicates.previous_hosts
              host_selection_retry_max_attempts: 5
              retry_options_predicates:
                - name: envoy.retry_options_predicates.omit_canary_hosts
              retry_priority:
                name: envoy.retry_priorities.previous_priorities
                typed_config:
                  "@type": type.googleapis.com/envoy.config.route.v3.RetryPolicy
                  update_frequency: 2

retry_on options:

5xx, gateway-error, connect-failure, refused-stream,

unavailable, cancelled, retriable-status-codes,

retriable-headers, reset, envoy-ratelimited

76Таймауты.
route_config:
  name: local_route
  virtual_hosts:
    - name: backend
      domains: ["*"]
      routes:
        - match: { prefix: "/" }
          route:
            cluster: service_1
            timeout: 15s
            idle_timeout: 60s
            max_stream_duration:
              max_stream_duration: 300s
              grpc_timeout_header_max: 300s
              grpc_timeout_header_offset: 0s

Глобальные таймауты кластера:

clusters:

  • name: service_1
    type: STRICT_DNS
    connect_timeout: 0.5s
    per_connection_buffer_limit_bytes: 32768
    common_http_protocol_options:
    idle_timeout: 3600s
    http_protocol_options:
    max_requests_per_connection: 1000
    http2_protocol_options:
    max_concurrent_streams: 100
    initial_stream_window_size: 65536
    initial_connection_window_size: 1048576

Per route timeout override:

typed_per_filter_config:

envoy.filters.http.router:

"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

upstream_rq_timeout: 30s

upstream_rq_per_try_timeout: 10s

77Circuit breaker.
clusters:
  - name: service_1
    type: STRICT_DNS
    lb_policy: LEAST_REQUEST
    circuit_breakers:
      thresholds:
        - priority: DEFAULT
          max_connections: 100
          max_pending_requests: 10
          max_requests: 50
          max_retries: 5
          track_remaining: true
        - priority: HIGH
          max_connections: 50
          max_pending_requests: 5
          max_requests: 25
          max_retries: 3
      per_host_thresholds:
        - priority: DEFAULT
          max_connections: 20
          max_pending_requests: 5
          max_requests: 10
          max_retries: 3

Статистика circuit breaker:

cluster.service_1.circuit_breaker.cx_open: количество открытых

cluster.service_1.circuit_breaker.rq_open

cluster.service_1.circuit_breaker.rq_pending_open

cluster.service_1.circuit_breaker.rq_retry_open

cluster.service_1.upstream_cx_overflow: отклонено из-за лимита

Мониторинг:

envoy.stat_prefix: cluster.service_1.upstream_cx_active

envoy.stat_prefix: cluster.service_1.upstream_rq_active

78Outlier detection.
clusters:
  - name: service_1
    type: STRICT_DNS
    lb_policy: LEAST_REQUEST
    outlier_detection:
      interval: 10s
      base_ejection_time: 30s
      max_ejection_percent: 50
      enforcing_consecutive_5xx: 100
      consecutive_5xx: 5
      enforcing_consecutive_gateway_failure: 50
      consecutive_gateway_failure: 5
      enforcing_success_rate: 100
      success_rate_minimum_hosts: 5
      success_rate_request_volume: 100
      success_rate_stdev_factor: 1900
      enforcing_consecutive_local_origin_failure: 100
      consecutive_local_origin_failure: 5
      split_external_local_origin_errors: true
      failure_percentage_threshold: 7
      enforcing_failure_percentage: 100
      failure_percentage_minimum_hosts: 5
      failure_percentage_request_volume: 50

Типы ejection:

consecutive_5xx — последовательные 5xx

consecutive_gateway_failure — 502/503/504

success_rate — низкий процент успеха

failure_percentage — процент ошибок

consecutive_local_origin_failure — ошибки соединения

79LB политики.
clusters:
  - name: service_1
    type: STRICT_DNS
    lb_policy: LEAST_REQUEST
    # Политики:
    # ROUND_ROBIN — по очереди
    # LEAST_REQUEST — наименьшее число запросов
    # RING_HASH — consistent hashing
    # RANDOM — случайный
    # MAGLEV — Maglev hashing
    # CLUSTER_PROVIDED — определяет провайдер
# Настройка RING_HASH:
# lb_subset_config:
#   subset_selectors:
#     - keys: ["version", "stage"]
#     - keys: ["version"]

# Weighted round robin:
# weighted_lb:
#   hosts:
#     - endpoint: { address: { socket_address: { address: app1, port_value: 3000 } } }
#       weight: 3
#     - endpoint: { address: { socket_address: { address: app2, port_value: 3000 } } }
#       weight: 1

# Locality weighted LB:
# locality_lb_config:
#   locality_weighted_lb: true
#   locality_specific_lb_config:
#     zone: us-east-1a

# Consistent hashing
# ring_hash_lb_config:
#   minimum_ring_size: 1024
#   hash_function: XX_HASH  # или MURMUR_HASH_2</code></pre></td></tr>
80Locality weighted LB.
clusters:
  - name: service_1
    type: STRICT_DNS
    lb_policy: LEAST_REQUEST
    locality_weighted_lb: true
    load_assignment:
      cluster_name: service_1
      endpoints:
        - locality:
            region: us-east-1
            zone: us-east-1a
            sub_zone: az1
          lb_endpoints:
            - endpoint:
                address:
                  socket_address: { address: app1, port_value: 3000 }
              health_status: HEALTHY
              metadata:
                filter_metadata:
                  envoy.lb:
                    version: v1
          priority: 0
          weight: 70
        - locality:
            region: us-east-1
            zone: us-east-1b
          lb_endpoints:
            - endpoint:
                address:
                  socket_address: { address: app2, port_value: 3000 }
          priority: 0
          weight: 30
        - locality:
            region: us-west-2
            zone: us-west-2a
          lb_endpoints:
            - endpoint:
                address:
                  socket_address: { address: app3, port_value: 3000 }
          priority: 1  # fallback, если us-east недоступен
          weight: 100
81Плагины Kong.
# Установка плагина:
docker run -d --name kong \
  -e "KONG_DATABASE=postgres" \
  -e "KONG_PG_HOST=kong-db" \
  -e "KONG_PLUGINS= bundled,my-custom-plugin" \
  -p 8000:8000 kong

Включение плагина на сервис:

curl -X POST http://localhost:8001/services/my-service/plugins
-d "name=rate-limiting"
-d "config.minute=100"
-d "config.policy=local"

Популярные плагины:

rate-limiting — лимит запросов

proxy-cache — кэширование

jwt — JWT аутентификация

oauth2 — OAuth 2.0

acl — доступ по ролям

cors — CORS

ip-restriction — ограничение по IP

bot-detection — защита от ботов

request-transformer — трансформация запроса

response-transformer — трансформация ответа

file-log — логирование в файл

datadog — метрики в DataDog

prometheus — метрики для Prometheus

aws-lambda — интеграция с Lambda

azure-functions — интеграция с Azure Functions

82Разработка плагина.
# Структура плагина:
# ./my-plugin/
#   handler.lua
#   schema.lua

handler.lua:

local MyHandler = {
VERSION = "1.0.0",
PRIORITY = 1000
}

function MyHandler:access(conf)
kong.log.debug("My plugin access phase")
local token = kong.request.get_header("x-api-key")
if not token then
return kong.response.exit(401, { error = "API key required" })
end
if token ~= conf.expected_key then
return kong.response.exit(403, { error = "Invalid API key" })
end
kong.ctx.shared.authenticated = true
end

function MyHandler:response(conf)
kong.response.set_header("X-My-Plugin", "processed")
end

return MyHandler

schema.lua:

return {
name = "my-plugin",
fields = {
{ config = {
type = "record",
fields = {
{ expected_key = { type = "string", required = true } }
}
} }
}
}

Установка:

docker exec -it kong bash

luarocks make my-plugin-1.0.0-1.rockspec

83Цепочки middleware.
docker-compose.yml:
version: "3"
services:
  traefik:
    image: traefik:v3.0
    command:
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"

app:
image: myapp
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(app.example.com)"
- "traefik.http.routers.app.middlewares=ratelimit,auth,headers"
- "traefik.http.middlewares.ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.ratelimit.ratelimit.burst=50"
- "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$...$$..."
- "traefik.http.middlewares.headers.headers.customrequestheaders.X-Proxy=traefik"
- "traefik.http.middlewares.headers.headers.customresponseheaders.X-Powered-By="
- "traefik.http.middlewares.redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.compress.compress=true"
- "traefik.http.middlewares.circuitbreaker.circuitbreaker.expression=NetworkErrorRatio() > 0.5"

84Ocelot API Gateway.
// ocelot.json — конфигурация Ocelot
{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/users/{everything}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        { "Host": "users-service", "Port": 3000 }
      ],
      "UpstreamPathTemplate": "/users/{everything}",
      "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ],
      "LoadBalancerOptions": {
        "Type": "LeastConnection"
      },
      "RateLimitOptions": {
        "ClientWhitelist": [],
        "EnableRateLimiting": true,
        "Period": "1m",
        "PeriodTimespan": 60,
        "Limit": 100
      },
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "Bearer",
        "AllowedScopes": [ "api.read" ]
      }
    },
    {
      "DownstreamPathTemplate": "/api/orders/{everything}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        { "Host": "orders-service", "Port": 3001 }
      ],
      "UpstreamPathTemplate": "/orders/{everything}",
      "UpstreamHttpMethod": [ "GET" ]
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "https://api.example.com"
  }
}

// Program.cs
// builder.Services.AddOcelot();
// app.UseOcelot().Wait();

85API Gateway + Lambda.
# Серверная часть (Lambda + Express):
// lambda.js
const serverless = require("serverless-http");
const express = require("express");
const app = express();

app.get("/users", async (req, res) => {
const users = await User.find();
res.json(users);
});

app.get("/users/:id", async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) return res.status(404).json({ error: "Not found" });
res.json(user);
});

module.exports.handler = serverless(app);

serverless.yml

service: my-api
provider:
name: aws
runtime: nodejs20.x
region: us-east-1
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
Resource: "arn:aws:dynamodb:::table/Users"
functions:
app:
handler: lambda.handler
events:
- httpApi:
path: /{proxy+}
method: ANY

Deploy: sls deploy

Output: https://xxxxxx.execute-api.us-east-1.amazonaws.com

86Zuul Gateway.
# Spring Cloud Gateway конфигурация:
# application.yml
spring:
  cloud:
    gateway:
      routes:
        - id: users-service
          uri: lb://users-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
            - name: CircuitBreaker
              args:
                name: usersCircuitBreaker
                fallbackUri: forward:/fallback/users
        - id: orders-service
          uri: lb://orders-service
          predicates:
            - Path=/api/orders/**
            - Method=GET,POST
          filters:
            - StripPrefix=1
            - AddRequestHeader=X-Gateway, spring-cloud
            - AddResponseHeader=X-Powered-By, Spring Cloud Gateway
      default-filters:
        - name: Retry
          args:
            retries: 3
            statuses: BAD_GATEWAY
            methods: GET

Netflix Zuul (legacy):

@EnableZuulProxy

zuul.routes.users.path=/api/users/**

zuul.routes.users.url=http://users-service:3000

87Кастомный gateway.
# OpenResty — Nginx + Lua для кастомного gateway

worker_processes auto;

events {
worker_connections 1024;
}

http {

Маршрутизация через Lua

init_by_lua_block {
routes = {
{ prefix = "/users", upstream = "users_backend" },
{ prefix = "/orders", upstream = "orders_backend" },
{ prefix = "/payments", upstream = "payments_backend" }
}
}

upstream users_backend { server users:3000; }
upstream orders_backend { server orders:3001; }
upstream payments_backend { server payments:3002; }

server {
listen 80;

# Lua роутинг
location / {
  access_by_lua_block {
    local token = ngx.var.http_authorization
    if not token then
      ngx.exit(401)
    end
    -- JWT верификация
    local jwt = require("resty.jwt")
    local decoded = jwt:verify("secret", token)
    if not decoded.verified then
      ngx.exit(403)
    end
    ngx.ctx.user_id = decoded.payload.sub
  }

  set $upstream "";
  rewrite_by_lua_block {
    for _, route in ipairs(routes) do
      if ngx.var.uri:find(route.prefix, 1, true) == 1 then
        ngx.var.upstream = route.upstream
        break
      end
    end
  }

  proxy_pass http://$upstream;
}

}
}

88Federation gateway.
# Apollo Federation — объединение GraphQL сервисов

gateway.js

const { ApolloGateway } = require("@apollo/gateway");
const { ApolloServer } = require("apollo-server-express");

const gateway = new ApolloGateway({
serviceList: [
{ name: "users", url: "http://users-service:4001/graphql" },
{ name: "posts", url: "http://posts-service:4002/graphql" },
{ name: "comments", url: "http://comments-service:4003/graphql" }
],
buildService({ name, url }) {
const { RemoteGraphQLDataSource } = require("@apollo/gateway");
return new RemoteGraphQLDataSource({
url,
willSendRequest({ request, context }) {
request.http.headers.set("authorization", context.authHeader);
}
});
}
});

const server = new ApolloServer({
gateway,
subscriptions: false,
context: ({ req }) => ({ authHeader: req.headers.authorization })
});

await server.start();
server.applyMiddleware({ app, path: "/graphql" });

Nginx прокси для federation:

location /graphql {

proxy_pass http://federation-gateway:4000;

}

Subschema (users):

type User @key(fields: "id") {

id: ID!

name: String!

posts: [Post]

}

extend type Query {

users: [User]

}

89Kong service mesh.
# Kong Service Mesh — service mesh на основе Kong
# Работает как sidecar proxy (как Istio, но на Lua)

Установка:

kubectl create namespace kong-mesh

helm install kong-mesh kong/kong-mesh --namespace kong-mesh

Deployment с sidecar:

apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
annotations:
kuma.io/sidecar-injection: "enabled"
spec:
containers:
- name: myapp
image: myapp:latest
ports:
- containerPort: 3000

TrafficRoute:

apiVersion: kuma.io/v1alpha1
kind: TrafficRoute
metadata:
name: myapp-route
spec:
sources:
- match:
kuma.io/service: "*"
destinations:
- match:
kuma.io/service: myapp_svc_3000
conf:
loadBalancer:
roundRobin: {}
split:
- weight: 90
destination:
kuma.io/service: myapp_svc_3000
version: "1.0"
- weight: 10
destination:
kuma.io/service: myapp_svc_3000
version: "2.0"

90Логирование прокси.
# Nginx access log с JSON:
http {
  log_format json escape=json
    '{"timestamp":"$time_iso8601",'
    '"remote_addr":"$remote_addr",'
    '"request":"$request",'
    '"status":$status,'
    '"body_bytes":$body_bytes_sent,'
    '"request_time":$request_time,'
    '"upstream_addr":"$upstream_addr",'
    '"upstream_status":"$upstream_status",'
    '"upstream_response_time":"$upstream_response_time",'
    '"http_referer":"$http_referer",'
    '"http_user_agent":"$http_user_agent",'
    '"http_x_forwarded_for":"$http_x_forwarded_for"}';

access_log /var/log/nginx/access.log json;
}

HAProxy JSON log:

global
log 127.0.0.1:514 len 4096 local0 format rfc5424

defaults
log global
option httplog
log-format {"timestamp":"%t","src":"%ci","method":"%ST","uri":"%HU","status":"%ST","backend":"%b","server":"%s"}

Envoy access log (см. entry 74)

Log aggregation:

filebeat, logstash, fluentd

docker logs --tail 100 nginx

journalctl -u nginx -f

91Tracing в прокси.
# Envoy + OpenTelemetry:
static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address: { address: 0.0.0.0, port_value: 10000 }
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                generate_request_id: true
                tracing:
                  client_sampling: 100
                  random_sampling: 10
                  overall_sampling: 5
                  max_path_tag_length: 256
                  custom_tags:
                    - tag: version
                      literal:
                        value: "1.0.0"
                    - tag: environment
                      environment:
                        name: ENVIRONMENT
                        default_value: production
                http_filters:
                  - name: envoy.filters.http.router
  tracing:
    http:
      name: envoy.tracers.opentelemetry
      typed_config:
        "@type": type.googleapis.com/envoy.config.trace.v3.OpenTelemetryConfig
        grpc_service:
          envoy_grpc:
            cluster_name: opentelemetry_collector
          timeout: 0.5s
        service_name: envoy-proxy

clusters:
- name: opentelemetry_collector
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: opentelemetry_collector
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: { address: otel-collector, port_value: 4317 }

92mTLS глубоко.
# Nginx mTLS:
server {
  listen 443 ssl;
  server_name api.example.com;

Server certificate

ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;

Client certificate verification

ssl_client_certificate /etc/ssl/certs/ca.crt;
ssl_verify_client on;
ssl_verify_depth 2;

Optional: извлечь сертификат клиента

ssl_trusted_certificate /etc/ssl/certs/ca.crt;

location / {
if ($ssl_client_verify != SUCCESS) {
return 403;
}
# Передать DN клиента в backend
proxy_set_header X-Client-DN $ssl_client_s_dn;
proxy_set_header X-Client-CN $ssl_client_s_dn_cn;
proxy_set_header X-Client-Verify $ssl_client_verify;
proxy_pass http://backend;
}
}

HAProxy mTLS:

frontend api
bind *:443 ssl crt /etc/ssl/certs/server.pem
ca-file /etc/ssl/certs/ca.crt
verify required
http-request set-header X-SSL $ssl_fc
default_backend app

Istio mTLS (PeerAuthentication):

apiVersion: security.istio.io/v1beta1

kind: PeerAuthentication

metadata:

name: default

spec:

mtls:

mode: STRICT

93ModSecurity WAF.
# Nginx + ModSecurity 3.0
# Установка:
# apt install libmodsecurity3 nginx-mod-modsecurity

server {
listen 80;

modsecurity on;
modsecurity_rules_file /etc/nginx/modsecurity.conf;

location / {
proxy_pass http://backend;
}
}

/etc/nginx/modsecurity.conf

SecRuleEngine On
SecRequestBodyAccess On
SecResponseBodyAccess On

OWASP CRS (Core Rule Set)

Include /etc/nginx/owasp-crs/crs-setup.conf
Include /etc/nginx/owasp-crs/rules/*.conf

Custom rules

SecRule ARGS "@contains <script>" "id:10001,phase:2,deny,status:403,msg:'XSS detected'"
SecRule REQUEST_URI "@contains /etc/passwd" "id:10002,phase:1,deny,status:403"
SecRule REQUEST_HEADERS:User-Agent "@pmFromFile blocked_agents.txt" "id:10003,phase:1,deny"

Rate limiting

SecRule IP:rate "@gt 100" "id:10004,phase:1,deny,status:429,msg:'Rate limit exceeded'"

Audit log

SecAuditEngine RelevantOnly
SecAuditLog /var/log/modsec_audit.log

Корреляция

SecRule REQUEST_HEADERS:Content-Type "!@streq application/json" \

"chain,id:10005,phase:1,deny"

SecRule REQUEST_METHOD "POST"

94DDoS защита.
# Nginx DDoS защита:

1. Rate limiting

limit_req_zone $binary_remote_addr zone=ddos:50m rate=30r/s;
limit_conn_zone $binary_remote_addr zone=conn:10m;

server {
listen 80;

Per IP rate

limit_req zone=ddos burst=50 nodelay;

Per IP connections

limit_conn conn 10;

Slowloris protection

client_body_timeout 5s;
client_header_timeout 5s;

Buffers

client_body_buffer_size 8k;
client_header_buffer_size 1k;
large_client_header_buffers 2 1k;

Block empty user agents

if ($http_user_agent = "") {
return 403;
}

Challenge

location / {
# Использовать JS challenge или CAPTCHA
proxy_pass http://backend;
}
}

HAProxy DDoS:

frontend web
bind *:80

Connection limiting

stick-table type ip size 1m expire 30s store conn_cur,conn_rate(10s)
tcp-request connection track-sc1 src
tcp-request connection reject if { sc_conn_cur(0) gt 50 }
tcp-request connection reject if { sc_conn_rate(0) gt 500 }

HTTP rate limiting

stick-table type ip size 1m expire 30s store http_req_rate(10s)
http-request track-sc2 src
http-request deny deny_status 429 if { sc_http_req_rate(1) gt 100 }

default_backend app

95GSLB.
# GSLB — распределение трафика между дата-центрами

DNS-based GSLB (AWS Route53):

latency-based, geolocation-based, weighted

Nginx + DNS:

upstream global_backend {
zone global 256k;
server us-east.example.com:3000 resolve;
server eu-west.example.com:3000 resolve;
server ap-southeast.example.com:3000 resolve;
}

HAProxy GSLB через DNS:

resolvers dns_resolver
nameserver dns1 8.8.8.8:53
nameserver dns2 1.1.1.1:53
hold valid 10s
hold obsolete 60s

backend global_app
balance roundrobin
server us-east us-east.example.com:3000 resolvers dns_resolver resolve weight=100
server eu-west eu-west.example.com:3000 resolvers dns_resolver resolve weight=80 check
server ap-southeast ap-southeast.example.com:3000 resolvers dns_resolver resolve weight=60 check

Anycast GSLB:

Все дата-центры объявляют один IP через BGP

Маршрутизация до ближайшего по протоколу BGP

GSLB решения:

- AWS Route53 (DNS)

- Azure Traffic Manager (DNS)

- Google Cloud Load Balancing (Anycast)

- Cloudflare (Anycast)

- NS1 (DNS)

96DNS балансировка.
# DNS Round Robin — простейшая балансировка
# Несколько A/AAAA записей для одного имени
# example.com IN A 10.0.0.1
# example.com IN A 10.0.0.2
# example.com IN A 10.0.0.3

Nginx resolver:

http {
resolver 8.8.8.8 1.1.1.1 valid=30s ipv6=off;

upstream backend {
zone backend 64k;
server app.example.com:3000 resolve;
}

server {
listen 80;
location / {
proxy_pass http://backend;
}
}
}

HAProxy DNS resolution:

resolvers mydns
nameserver dns1 8.8.8.8:53
nameserver dns2 1.1.1.1:53
hold valid 10s
hold nx 30s
hold timeout 30s
hold refused 30s
hold obsolete 60s
accepted_payload_size 8192
resolve_retries 3
timeout retry 1s

backend app
server app1 app1.example.com:3000 resolvers mydns resolve weight=10
server app2 app2.example.com:3000 resolvers mydns resolve weight=20 check inter 30s

Consul DNS:

resolver consul local=on

server app consul.service.consul:3000 resolve

97GeoDNS настройка.
# PowerDNS + GeoIP
# Разные IP для разных регионов

Nginx GeoDNS конфиг:

geo $backend {
default eu-backend;
10.0.0.0/8 us-backend;
ranges;
}

http {
upstream us-backend {
zone us 64k;
server us-api.example.com:3000 resolve;
}
upstream eu-backend {
zone eu 64k;
server eu-api.example.com:3000 resolve;
}

server {
listen 80;
location / {
proxy_pass http://$backend;
}
}
}

AWS Route53 GeoDNS:

- Geolocation routing policy

- Latency routing policy

yaml:

MyRecord:

Type: A

SetIdentifier: "US"

GeoLocation:

ContinentCode: "NA"

TTL: 60

ResourceRecords:

- Value: "203.0.113.10"

---

MyRecord:

Type: A

SetIdentifier: "EU"

GeoLocation:

ContinentCode: "EU"

TTL: 60

ResourceRecords:

- Value: "198.51.100.20"

98Anycast маршрутизация.
# Anycast — один IP, много серверов, BGP маршрутизация

BGP конфигурация (FRR/Bird):

router bgp 65001

network 203.0.113.0/24

neighbor 10.0.0.1 remote-as 65000

Nginx на Anycast ноде:

http {

Локальная привязка к anycast IP

server {
listen 203.0.113.1:80;
server_name example.com;

location / {
  proxy_pass http://backend;
}

}
}

Health check для BGP:

Если Nginx упал, BGP должен withdraw объявление

Используем ExaBGP или gobgp:

exabgp healthcheck --command "curl -f http://localhost:80/health"

Anycast vs DNS:

Anycast: автоматическая маршрутизация, быстрый failover

DNS: медленное обновление (TTL), простота

Пример: Cloudflare использует Anycast

Один IP 1.1.1.1 работает во всём мире

Проверка anycast:

traceroute 1.1.1.1

whois 1.1.1.1 | grep -i origin

mtr 1.1.1.1

99Бенчмаркинг прокси.
# wrk — HTTP benchmark tool
wrk -t12 -c400 -d30s http://localhost:80/api/users
wrk -t4 -c100 -d60s --latency http://localhost:80

hey (альтернатива wrk)

hey -n 100000 -c 200 http://localhost:80/api/users
hey -n 50000 -c 100 -m POST -H "Content-Type: application/json" -d '{"key":"val"}' http://localhost:80/api

h2load (HTTP/2)

h2load -n100000 -c100 -m10 http://localhost:80

Тестирование прокси:

wrk напрямую к бэкенду:

wrk -t4 -c100 -d30s http://backend:3000

wrk через прокси:

wrk -t4 -c100 -d30s http://proxy:80

Сравнение:

Прокси добавляет latency:

Nginx: ~1-5ms

HAProxy: ~0.5-2ms

Envoy: ~2-10ms

Kong: ~5-20ms

Инструменты:

wrk, hey, h2load, autocannon (Node), k6, vegeta

vegeta attack -targets=targets.txt -rate=1000 -duration=30s | vegeta report -type=text

100Синтетический мониторинг.
# Synthetic monitoring — искусственные проверки из разных точек

1. curl-based health check

#!/bin/bash

/etc/monitoring/check_proxy.sh

for endpoint in /api/users /api/health /static/index.html; do
status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 http://proxy/$endpoint)
if [ "$status" != "200" ] && [ "$status" != "301" ]; then
echo "FAIL: $endpoint returned $status"
exit 2
fi
echo "OK: $endpoint -> $status"
done

2. Multi-step transaction test

curl -c /tmp/cookies.txt -b /tmp/cookies.txt http://proxy/login -d "user=test&pass=test"

curl -c /tmp/cookies.txt -b /tmp/cookies.txt http://proxy/dashboard

3. Using checklyhq.com or similar:

- HTTP checks every 5 minutes

- Multi-step browser checks

- API checks with assertions

- Alerting via Slack/PagerDuty

4. Prometheus blackbox exporter:

modules:

http_2xx:

prober: http

http:

valid_http_versions: ["HTTP/1.1", "HTTP/2"]

valid_status_codes: [200, 301, 302]

fail_if_ssl: false

preferred_ip_protocol: "ip4"

5. Grafana Synthetics:

k6 based synthetic monitoring

import http from "k6/http";

import { check } from "k6";

export default function () {

const res = http.get("http://proxy/api/health");

check(res, { "status is 200": (r) => r.status === 200 });

}


Лицензия

MIT

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions