Создание прокси-сервиса#

Создание простого прокси-сервиса#

Одной из основных задач для SOWA является защищенное проксирование между участниками взаимодействия. Разберем примеры, как это можно осуществить.

В самом простом случае проксирование представляет собой простое синхронное http взаимодействие, в котором запрос клиента пробрасывается с порта SOWA, на порт backend.

Создадим конфигурационный файл нашего прокси профиля.

Создадим каталог proxies в каталоге examples, и поместим в него конфигурационный файл профиля proxy.yml со следующим содержимым:

profile: simple_proxy
version: 2.0.3
system:
  wrk_count: 1
  conn_count: 4096
service:
  !include: services/simple_proxy_service.yml

В каталоге services создадим файл simple_proxy_service.yml, со следующим содержимым:

service_http_proxy:
  - id: 1
    url:  ^/hello
    upstream_groups:
      - id: back_1
        servers:
          - server: 127.0.0.1:10012
    upstream_group_id: back_1
    allowed_queries:
      - method: get
    listen:
      - port: 10000

Рассмотрим назначение использованных конфигурационных элементов.

  • service_http_proxy - определяет группу сервисов, реализующих поведение примитива http_proxy;

Далее следует перечислить все сервисы данного примитива (в нашем случае он единственный).

  • id - уникальный идентификатор сервиса в профиле, обязательный параметр;

  • url - регулярное выражение, определяющее контекст точки входа прокси-сервиса;

  • upstream_groups - список элементов типа параметр upstream, определяющих, куда будет проксироваться запрос;

  • upstream_group_id - обязательный параметр, определяющий идентификатор группы из upstream_groups, на который будут проксироваться запросы в текущей конфигурации;

  • allowed_queries - набор элементов, определяющий список допустимых HTTP-методов запроса при проксировании (в данный момент обязательный элемент), в нашем случае это будет метод GET;

  • listen - список точек входа прокси в виде параметр listen.

Далее необходимо создать, сконфигурировать и запустить профиль simple_proxy.

[sowacfg@tkle-ish0038 proxies]$ sowa-config --add-profile simple_proxy
Profile "simple_proxy" was successfully created
[sowacfg@tkle-ish0038 proxies]$ sowa-config --config proxy.yml
Profile "simple_proxy" was successfully configured.
[sowacfg@tkle-ish0038 proxies]$ sowa-config --run simple_proxy
Profile "simple_proxy" was successfully started.

Проверим работоспособность прокси:

[sowacfg@tkle-ish0038 proxies]$ curl -v http://localhost:10000/hello/world
* About to connect() to localhost port 10000 (#0)
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 10000 (#0)
> GET /hello/world HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:10000
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Mon, 05 Apr 2021 09:11:17 GMT
< Content-Type: text/plain
< Content-Length: 11
< Connection: keep-alive
< Server: SOWA
< Accept-Ranges: bytes
<
* Connection #0 to host localhost left intact
Hello world[sowacfg@tkle-ish0038 proxies]$

А теперь проверим, как сработают ограничения на допустимые методы, попытавшись отправить запрос с методом HEAD:

Hello world[sowacfg@tkle-ish0038 proxies]$ curl -X HEAD -v http://localhost:10000/hello/world
* About to connect() to localhost port 10000 (#0)
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 10000 (#0)
> HEAD /hello/world HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:10000
> Accept: */*
>
< HTTP/1.1 500 Internal Server Error
< Date: Mon, 05 Apr 2021 09:14:32 GMT
< Content-Type: text/html
< Content-Length: 169
< Connection: close
< Server: SOWA
<
<html>
<head><title>500 Internal Server Error</title></head>
<body>
<center><h1>500 Internal Server Error</h1></center>
<hr><center>SOWA</center>
</body>
</html>
* Closing connection 0

Создание прокси-сервиса с единой точкой входа#

Зачастую необходимо реализовать схемы взаимодействия, отличные от рассмотренной в предыдущем пункте, а именно схему, при которой в прокси наружу выставлена единая(-ые) точка входа, на которую обращаются клиенты, а за прокси стоит n-ое количество backend, задачу маршрутизации на которые осуществляет собственно прокси-сервис.

Схематично можно представить это на следующих диаграммах:

d1

d2

Для решения обеих задач используется примитив MainProxy.

Рассмотрим, как можно настроить взаимодействие для каждого из этих вариантов.

Одна точка входа, у каждого из сервисов своя точка выхода

Создадим в каталоге examples/proxies/services файл main_proxy.yml, который будет иметь следующее содержимое:

service_main_proxy:
  - id: main
    listen:
      - port: 11000
    url: /
    optional:
      log_level: error

Фактически, это описание нашей единой точки входа на порту 11000 с контекстом "/". Здесь важно определение сервиса как примитива service_main_proxy, при этом сервис данного типа должен присутствовать в профиле в единственном экземпляре.

Теперь добавим два сервиса service_world и service_drlow в файл simple_proxy_service.yml:

- id: service_world
  url:  ^/world
  upstream_group_id: back_hello_world
  upstream_groups:
    - id: back_hello_world
      servers:
       - server: 127.0.0.1:10012
  allowed_queries:
    - method: get
- id: service_dlrow
  url:  ^/dlrow
  upstream_group_id: back_hello_dlrow
  upstream_groups:
    - id: back_hello_dlrow
      servers:
       - server: 127.0.0.1:10013
  allowed_queries:
    - method: post

Следует обратить внимание, что у данных сервисов не определен параметр listen - т.к. слушатель у нас будет в сервисе main_proxy, а также, что у каждого из них свои допустимые методы. Затем добавим main_proxy.yml в общий конфиг профиля.

profile: simple_proxy
version: 2.0.3
system:
  wrk_count: 1
  conn_count: 4096
service:
  !include: services/simple_proxy_service.yml
  !include: services/main_proxy.yml

Выполним последовательность операций - остановка профиля, конфигурирование и старт.

Попробуем отправить запросы на два наших сервиса, через сервис main_proxy.

Пример запроса к первому сервису:

[sowacfg@tkle-ish0038 proxies]$ curl -X GET -v http://localhost:11000/world
* About to connect() to localhost port 11000 (#0)
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 11000 (#0)
> GET /world HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:11000
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Date: Mon, 05 Apr 2021 10:08:52 GMT
< Content-Type: text/html
< Content-Length: 145
< Connection: keep-alive
< Server: SOWA
<
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>SOWA</center>
</body>
</html>
* Connection #0 to host localhost left intact

Пример запроса ко второму сервису:

[sowacfg@tkle-ish0038 proxies]$ curl --data "test" -v http://localhost:11000/dlrow
* About to connect() to localhost port 11000 (#0)
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 11000 (#0)
> POST /dlrow HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:11000
> Accept: */*
> Content-Length: 4
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 4 out of 4 bytes
< HTTP/1.1 502 Bad Gateway
< Date: Mon, 05 Apr 2021 10:10:26 GMT
< Content-Type: text/html
< Content-Length: 149
< Connection: keep-alive
< Server: SOWA
<
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>SOWA</center>
</body>
</html>
* Connection #0 to host localhost left intact

И в первом, и во втором случае получаем ошибки, но причины их отличаются.

В первом случае, на backend отсутствует контекст /world, что соответствует действительности, так как единственный сервис, который был объявлен, это сервис /hello/world. При этом сам backend физически доступен.

Во втором случае, в секции upstream_groups объявлен несуществующий сервер, поэтому получили ошибку 502 Bad Gateway.

Внесем изменения в нашу заглушку:

- id: world_plain_service
  optional:
    response_code: 200
    response_body: "World hello"
  response_headers:
    - name: Content-Type
      value: text/plain
  url: ^/world
  listen:
    - port: 10014
- id: dlrow_plain_service
  optional:
    response_code: 200
    response_body: "Dlrow hello"
  response_headers:
    - name: Content-Type
      value: text/plain
  url: ^/dlrow
  listen:
   - port: 10013

Добавим два сервиса с контекстами /world и /dlrow соответственно на портах 10014 и 10013, после чего остановим профиль sample_stub, сконфигурируем и запустим.

Так как сервис world_plain_service теперь объявлен на порту 10014, то вернемся к файлу simple_proxy_service и изменим значение порта backend для сервиса service_world c 10012 на 10014, после чего остановим, сконфигурируем и запустим профиль simple_proxy.

Повторим запросы к сервису main_proxy и посмотрим, что изменилось:

Пример запроса к первому сервису:

[sowacfg@tkle-ish0038 proxies]$ curl -X GET -v http://localhost:11000/world
* About to connect() to localhost port 11000 (#0)
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 11000 (#0)
> GET /world HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:11000
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Mon, 05 Apr 2021 10:27:41 GMT
< Content-Type: text/html
< Content-Length: 145
< Connection: keep-alive
< Server: SOWA
<
* Connection #0 to host localhost left intact

Пример запроса ко второму сервису:

[sowacfg@tkle-ish0038 proxies]$ curl --data "test" -v http://localhost:11000/dlrow
* About to connect() to localhost port 11000 (#0)
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 11000 (#0)
> POST /dlrow HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:11000
> Accept: */*
> Content-Length: 4
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 4 out of 4 bytes
< HTTP/1.1 200 OK
< Date: Mon, 05 Apr 2021 10:23:56 GMT
< Content-Type: text/plain
< Content-Length: 11
< Connection: keep-alive
< Server: SOWA
< Accept-Ranges: bytes
<
* Connection #0 to host localhost left intact

Как видим, оба запроса теперь проходят успешно.

Одна точка входа, сервисы используют общие точки выхода

Создадим еще один профиль для демонстрации второго способа балансировки запросов. В каталоге proxies создадим файл proxy_use_main со следующим содержимым:

profile: proxy_use_main
version: 2.0.3
system:
  wrk_count: 1
  conn_count: 4096
  optional:
    log_level: error
service:
  service_main_proxy:
   - id: main
     listen:
      - port: 11001
     url: /
     upstream_groups:
      - id: back_1
        servers:
         - server: 127.0.0.1:10016
         - server: 127.0.0.1:10015
  service_http_proxy:
   - id: 1
     name: service_1
     url:  ^/hello
     use_main_upstream_groups: true
     upstream_group_id:  back_1
     allowed_queries:
       - method: get
   - id: 2
     name: service_2
     url:  ^/bye
     upstream_group_id:  back_1
     use_main_upstream_groups: true
     allowed_queries:
       - method: get

В этом случае группа backend объявлена в сервисе main_proxy, а ссылка на нее осуществляется в сервисах service_1 и service_2. При этом, для того чтобы эта группа была доступна в сервисах, используется специальная директива use_main_upstream_groups.

Выполним стандартные операции по добавлению профиля, конфигурированию и запуску.

Теперь добавим в нашем профиле для заглушек сервисы sample1 и sample2 для использования в нашем прокси в качестве backend:

- id: sample1
  optional:
    response_code: 200
    response_body: "what do you want?"
  response_headers:
    - name: Content-Type
      value: text/plain
    - name: Server-Port
      value: $server_port
    - name: Request-Uri
      value: $request_uri
  url: ^/
  listen:
   - port: 10015
- id: sample2
  optional:
    response_code: 200
    response_body: "what do you want?"
  response_headers:
    - name: Content-Type
      value: text/plain
    - name: Server-Port
      value: $server_port
    - name: Request-Uri
      value: $request_uri
  url: ^/
  listen:
   - port: 10016

Следует обратить внимание на то, что добавили в вывод заголовков ответа значение переменных $server_port и $request_uri для того, чтобы на стороне клиента иметь возможность увидеть, на какой backend и на какой контекст backend реально приходят запросы.

Проверим получившиеся запросы.

Два запроса к сервису hello:

[sowacfg@tkle-ish0038 proxies]$ curl -v http://localhost:11001/hello
* About to connect() to localhost port 11001 (#0)
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 11001 (#0)
> GET /hello HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:11001
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Mon, 05 Apr 2021 10:53:44 GMT
< Content-Type: text/plain
< Content-Length: 17
< Connection: keep-alive
< Server-Port: 10016
< Request-Uri: /hello
< Server: SOWA
[sowacfg@tkle-ish0038 proxies]$ curl -v http://localhost:11001/hello
* About to connect() to localhost port 11001 (#0)
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 11001 (#0)
> GET /hello HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:11001
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Mon, 05 Apr 2021 10:56:40 GMT
< Content-Type: text/plain
< Content-Length: 17
< Connection: keep-alive
< Server-Port: 10015
< Request-Uri: /hello
< Server: SOWA

Два запроса к сервису bye:

[sowacfg@tkle-ish0038 proxies]$ curl -v http://localhost:11001/bye
* About to connect() to localhost port 11001 (#0)
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 11001 (#0)
> GET /bye HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:11001
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Mon, 05 Apr 2021 10:58:14 GMT
< Content-Type: text/plain
< Content-Length: 17
< Connection: keep-alive
< Server-Port: 10016
< Request-Uri: /bye
< Server: SOWA
[sowacfg@tkle-ish0038 proxies]$ curl -v http://localhost:11001/bye
* About to connect() to localhost port 11001 (#0)
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 11001 (#0)
> GET /bye HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:11001
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Mon, 05 Apr 2021 10:59:38 GMT
< Content-Type: text/plain
< Content-Length: 17
< Connection: keep-alive
< Server-Port: 10015
< Request-Uri: /bye
< Server: SOWA

Как видно, обращаясь по каждому из url несколько раз, фактически поочередно отправляются запросы на разные серверы из группы бэков.

Балансировка#

Балансировка нагрузки между несколькими серверами — это широко используемый метод для оптимизации использования ресурсов, максимизации пропускной способности, уменьшения задержки и обеспечения отказоустойчивых конфигураций.

Вернемся к нашему конфигурационному файлу proxy_use_main.yml.

profile: proxy_use_main
version: 2.0.3
system:
  wrk_count: 1
  conn_count: 4096
  optional:
    log_level: error
service:
  service_main_proxy:
   - id: main
     listen:
      - port: 11001
     url: /
     upstream_groups:
      - id: back_1
        servers:
         - server: 127.0.0.1:10016
         - server: 127.0.0.1:10015
  service_http_proxy:
   - id: 1
     name: service_1
     url:  ^/hello
     use_main_upstream_groups: true
     upstream_group_id:  back_1
     allowed_queries:
       - method: get
   - id: 2
     name: service_2
     url:  ^/bye
     upstream_group_id:  back_1
     use_main_upstream_groups: true
     allowed_queries:
       - method: get

Параметры балансировки описываются в секции upstream_groups. Поскольку в upstream_groups не указан алгоритм балансировки нагрузки, SOWA (включающая в себя NGINX) использует алгоритм по умолчанию Round Robin (директивы для его включения нет).

Round Robin - это алгоритм, при котором запросы равномерно распределяются по серверам с учетом веса серверов (weight). Это не означает, что запросы будут распределяться между серверами последовательно друг за другом. Равномерность распределения означает, что в случае наличия двух серверов с весами weight: 100 после 200 запросов, увидим в сумме ~50:50 попаданий между ними. Например, 10 запросов уйдут на первый, следующие 13 на второй сервер и т.д.

По умолчанию, вес каждого сервера равен "1". Значение веса — это весовой коэффициент. Чем он больше (относительно других), тем чаще сервер будет вызван.

К примеру, при распределении весов 5 к 2 результат будет следующим: d3 23 запроса к серверу с портом 10015 и 9 к серверу с портом 10016. 23/9 ~2,5 - как и ожидалось.

Если необходимо использовать другой тип балансировки (не Round Robin), то необходимо использовать группу параметров load_balancer.

В группе load_balancer, существует параметр type - тип балансировки. Рассмотрим следующие типы: hash и sticky.

  • hash - тип балансировки, при котором соответствие клиента серверу определяется при помощи хэшированного значения "ключа".

Приведем пример использования:

...
     url: /
     hostname:
        - ip1
        - ip2
     upstream_groups:
       - id: back_1
         servers:
          - server: 127.0.0.1:10016
          - server: 127.0.0.1:10015
         load_balancing:
           type: hash
           hash_key: $http_host
           hash_consistent: "off"

При использовании значения hash в параметре type, необходимо также указывать параметр hash_key. Если задан параметр hash_consistent ("on"/"off"), то вместо вышеописанного метода будет использоваться метод консистентного хэширования ketama. Значение параметра hash_key хэшируется, и с помощью него устанавливается отображение клиент-сервер. В качестве значения параметра hash_key может использоваться текст, переменные и их комбинации. Будет использоваться переменная $http_ с дополнением host.

$http_имя - произвольное поле заголовка запроса; последняя часть имени переменной соответствует имени поля, приведенному к нижнему регистру, с заменой символов тире на символы подчеркивания. Чтобы продемонстрировать работу балансировки по имени используемого хоста, нужно добавить секцию hostname с параметрами ip1 и ip2.

Отправим запросы к сервисам hello и bye. Укажем Host: ip1

curl -vvv -H "Host: ip1" http://127.0.0.1:11001/hello
curl -vvv -H "Host: ip1" http://127.0.0.1:11001/bye

Результат выполнения запроса: d4

Видно, что при использовании Host: ip1 произошло залипание на порт 10016.

Теперь отправим запросы к сервисам hello и bye. Укажем Host: ip2

curl -vvv -H "Host: ip2" http://127.0.0.1:11001/hello
curl -vvv -H "Host: ip2" http://127.0.0.1:11001/bye

Результат выполнения запроса: d5

Видно, что при использовании Host: ip2 произошло "залипание" на порт 10015.

Таким образом, был рассмотрен процесс работы балансировки типа hash.

  • sticky привязывает запрос (на определенное время или нет) к конкретному upstream'у.

Пример использования:

upstream_groups:
 - id: back_1
   servers:
    - server: 127.0.0.1:10015
    - server: 127.0.1.1:10016
   load_balancing:
     type: sticky
     name: SWJSESSIONID
     monitor_cookie: UFS-SESSION

name - имя файла cookie, используемого для отслеживания, сохраняющихся upstream srv. monitor_cookie - cookie, устанавливаемая upstream'ом в ответе в заголовке Set-Cookie. Если cookie пришли, выставляется имя сессии.

Внесем эти изменения в конфигурационный файл, переконфигурируем и перезапустим профиль.

Добавим в сервисы sample1 и sample2 (с портами 10015 и 10016 соответственно), в секцию response_headers строки:

response_headers:
...
- name: Set-Cookie
  value: UFS-SESSION=9fa7947d-8550-4ecd-9fe6-7d01efec41da

Переконфигурируем и перезапустим профиль заглушек.

Проверим следующие 5 примеров.

  1. Простой запрос:

    zapros1

    Отправим запрос на сервис bye.

     curl -vvv http://localhost:11001/bye
    

    Результат выполнения запроса: d6 Был отправлен запрос на SOWA по контексту bye. SOWA проксировала запрос на backend с портом 10015. На backend установлен заголовок запроса Set-Cookie со значением UFS-SESSION (значение совпадает со значением параметра monitor_cookie).

    Set-Cookie: UFS-SESSION=9fa7947d-8550-4ecd-9fe6-7d01efec41da
    

    Благодаря этому, SOWA добавила сессионную cookie с именем SWJSESSIONID.

    Сессионная cookie, устанавливаемая SOW'ой:

    Set-Cookie: SWJSESSIONID=5699daa888fef95f98529a0c316350f5; Path=/
    
  2. Запрос с использованием сессионной куки zapros2

    Отправим запрос на сервис bye с использованием сессионной cookie, чтобы убедиться в "залипании" на конкретный сервер (в данном случае нода с портом 10015).

    curl -vvv -H "Cookie: SWJSESSIONID=5699daa888fef95f98529a0c316350f5" http://localhost:11001/bye
    

    Результат выполнения запроса: d7 Действительно вновь попали на ноду с портом 10015.

    Теперь в response headers у нас один заголовок Set-Cookie — это cookie backend.

    Set-Cookie: UFS-SESSION=9fa7947d-8550-4ecd-9fe6-7d01efec41da
    

    SOWA не добавила сессионную cookie с именем SWJSESSIONID, т.к. она использовалась в запросе и "залипание" на ноду произошло.

    Сессионная cookie, устанавливаемая SOW'ой: отсутствует.

  3. Запрос с использованием куки, имя которой отличается от сессионной на SOWA

    zapros3

    Изменим имя cookie, добавив к нему некоторые символы. Отправим запрос:

    curl -vvv -H "Cookie: BADSWJSESSIONID=5699daa888fef95f98529a0c316350f5" http://localhost:11001/bye
    

    Результат выполнения запроса: d8

    В данном случае видно, что хоть и использоваось значение сессионной cookie, но было указано имя, отличающееся от значения, указанного в параметре name. "Залипания" на ноду не произошло.

    SOWA проксировала запрос на второй backend (проксирование могло произойти и на первый backend. Здесь сработал механизм распределения запросов по серверам).

    При этом заголовок запроса backend Set-Cookie со значением UFS-SESSION совпадает со значением параметра monitor_cookie.

    Поэтому видим cookie backend.

    Set-Cookie: UFS-SESSION=9fa7947d-8550-4ecd-9fe6-7d01efec41da
    

    Плюс SOWA добавила сессионную cookie с именем SWJSESSIONID.

    Сессионная cookie, устанавливаемая SOW'ой

    SWJSESSIONID=d2e7e87c10c7edf5ae58b0b6a8237d23; Path=/
    
  4. Запрос к серверу, на котором указана кука, отличающаяся от monitor_cookie zapros4

    Внесем изменения в конфигурационный файл сервисов для заглушки (для сервиса с портом 10015):

     response_headers:
    ...
      - name: Set-Cookie
        value: BADUFS-SESSION=9fa7947d-8550-4ecd-9fe6-7d01efec41da 
    

    Переконфигурируем и перезапустим профиль заглушек.

    Отправим запрос на сервис bye.

    curl -vvv http://localhost:11001/bye
    

    Результат выполнения запроса: d9

    Видно, что запрос попал на ноду с портом 10015 и SOWA не проставила сессионную cookie, т.к. не совпадает значение параметра monitor_cookie со значением полученным с backend.

    BADUFS-SESSION=9fa7947d-8550-4ecd-9fe6-7d01efec41da
    

    Сессионная cookie, устанавливаемая SOW'ой: отсутствует.

    Повторим запрос:

    curl -vvv http://localhost:11001/bye
    

    Результат выполнения запроса: d10

    SOWA проксировала запрос на ноду с портом 10016. Здесь изменения не были внесены в заголовок запроса Set-Cookie.

    UFS-SESSION=9fa7947d-8550-4ecd-9fe6-7d01efec41da
    

    Сессионная cookie, устанавливаемая SOW'ой:

    SWJSESSIONID=d2e7e87c10c7edf5ae58b0b6a8237d23; Path=/
    
  5. Запрос с сессионной кукой к серверу, на котором куки отличается от monitor_cookie

    zapros5

    Отправим запрос на сервис bye с использованием сессионной cookie, чтобы убедиться в "залипании" на конкретный сервер (в нашем случае нода с портом 10015). Но стоить помнить, что значение параметра monitor_cookie не совпадает со значением установленным на backend.

    curl -vvv -H "Cookie: SWJSESSIONID=5699daa888fef95f98529a0c316350f5" http://localhost:11001/bye
    

    Результат выполнения запроса: d11

    Видно, что запрос попал на ноду с портом 10015 и произошло "залипание", т.к. имя сессионной cookie корректно.

    BADUFS-SESSION=9fa7947d-8550-4ecd-9fe6-7d01efec41da
    

    SOWA не добавила сессионную cookie с именем SWJSESSIONID, т.к. она используется в запросе и "залипание" на ноду произошло.

    Сессионная cookie, устанавливаемая SOW'ой: отсутствует.

  • Утилита health check активирует периодические проверки работоспособности серверов в upstream группе. При включенном hc, по умолчанию каждые пять секунд SOWA отправляет запрос каждому серверу в upstream группе. Если возникает какая-либо ошибка связи или тайм-аут, то проверка работоспособности не выполняется.

    Сервер помечается как неисправный, и SOWA не отправляет ему клиентские запросы, пока он снова не пройдет проверку работоспособности.

    В каталоге examples/proxies создадим конфигурационный файл hc.yml и наполним его содержимым:

    profile: hc
    version: 2.0.3
    system:
     wrk_count: 1
     conn_count: 4096
     optional:
       log_level: debug
    service:
     service_main_proxy:
       - id: main
         listen:
           - port: 8030
         url: /
         upstream_groups:
           - id: back_1
             servers:
               - server: 127.0.0.1:18030
             health_check:
               enabled: true
     service_http_proxy:
       - id: 1
         name: service_1
         url:  ^\/hello$
         use_main_upstream_groups: true
         upstream_group_id: back_1
         allowed_queries:
           - method: get
    

    Были объявлены два сервиса: первый (main) - тип main, второй (service_1) - тип httpProxy.

    В секцию upsteam_groups сервиса main нужно добавить модуль health_check. У модуля health_check, параметр enable является обязательным и, в качестве значения, принимает логические выражения - true/false.

    Добавим профиль hc в реестр профилей:

    sowa-config --add-profile hc
    

    Сконфигурируем профиль hc:

    sowa-config --config hc.yml
    

    Модуль health_check обладает параметрами по умолчанию, которые будут использоваться, если они не заданы явно:

    • check_interval: 30000 - интервал времени проверки запроса в ms.

    • fail_count: 5 - количество неудачных запросов на сервер для маркировки сервера 'down'.

    • drain: 2 - количество неудачных запросов для маркировки сервера 'drain'. В этом режиме на сервер будут проксироваться только привязанные к нему запросы (см. load_balancer).

    • rise_count: 2 - количество удачных запросов на сервер для маркировки сервера 'up'.

    • default_state: down - состояние сервера по умолчанию (up/down).

    • type: tcp - тип запроса для проверки (tcp, ssl_hello, http, mysql, ajp, fastcgi). Опишем некоторые значения явно и, при необходимости, изменим их значения.

    profile: hc
    version: 2.0.3
    system:
      wrk_count: 1
      conn_count: 4096
      optional:
        log_level: debug
    service:
      service_main_proxy:
        - id: main
          listen:
            - port: 8030
          url: /
          upstream_groups:
            - id: back_1
              servers:
                - server: 127.0.0.1:18030
              health_check:
                enabled: true
                check_interval: 3000
                fail_count: 3
                rise_count: 1
                default_state: up
                type: http
                http_send_method: GET
                http_send_url: /healthcheck
                check_response_body_pattern: '.*OK.*'
                http_status_alive: "http_2xx http_3xx"
      service_http_proxy:
        - id: 1
          name: service_1
          url:  ^\/hello$
          use_main_upstream_groups: true
          upstream_group_id: back_1
          allowed_queries:
            - method: get
    

    http_send_method - http метод запроса (POST, GET). http_send_url - url-адрес для проверки работоспособности сервиса. check_response_body_pattern - нечувствительное к регистру регулярное выражение для поиска соответствия в теле ответа. http_status_alive - в значении параметра определяется, какой код возврата считать положительным.

    В каталоге stubs создадим файл hc_stub.yml и наполним его следующим содержимым:

    profile: hc_stub
    version: 2.0.3
    system:
      conn_count: 4096
      wrk_count: 1
      optional:
       log_level: error
    service:
      service_static:
        - id: hello
          optional:
            response_code: 200
            response_body: "Hello world"
          response_headers:
            - name: Content-Type
              value: text/plain
          url: ^/hello$
        - id: healthcheck
          optional:
            response_code: 200
            response_body: "OK"
          response_headers:
            - name: Content-Type
              value: text/plain
          url: ^/healthcheck
      service_main_proxy:
        - id: 1
          listen:
            - port: 18030
          hostname:
            - localhost
            - 127.0.0.1
          url: /
    

    Добавим профиль hc_stub в реестр профилей и сконфигурируем его.

    Запустим профиль hc и сразу перейдем к его логам. Нам нужен /sowalogs/hc/system/main_error.log. Обратите внимание, что профиль hc_stub не запущен. d12

    Результат:

    • Интервал запросов на сервис healthcheck равен 3 секунды (check_interval). Отображаются 3 запроса (fail_count) на сервис healthcheck, после которых сервер был помечен как "down"

    • 2020/09/21 10:01:36 [error] 58590#58590: disable check peer: 127.0.0.1:18030

    • Из п.2 следует, что сервис находился в состоянии "up" (default_state).

    Теперь запустим профиль hc_stub. После этого вновь обратимся к логу /sowalogs/hc/system/main_error.log. d13

    После того как сервер был помечен "down", на него продолжали отправляться запросы. Как только профиль hc_stub был запущен, был один запрос (rise_count), после которого сервер вернулся к "up" состоянию. 2020/09/21 10:12:57 [error] 4083#4083: enable check peer: 127.0.0.1:18030

    Перейдем к логу заглушки hc_stub: /sowalogs/hc_stub/services/service_static/healthcheck_access.log d14

    Из лога видно, что на сервис healthcheck приходили запросы. Время первого запроса совпадает со временем записи "4083#4083: enable check peer: 127.0.0.1:18030".

    Также видно, что это http запрос (type), с методом GET (http_send_method) на сервис /healthcheck (http_send_url).

    Пример работы check_response_body_pattern и http_status_alive. Откроем hc_stub.yml и изменим response_body с OK на ERR.

    - id: healthcheck
      optional:
        response_code: 200
        response_body: "ERR"
    

    Переконфигурируем и перезапустим профиль. d15

    Отображается сообщение upstream check send data error -1 with peer 127.0.0.1:18030, говорящее о том, что в этот момент backend был недоступен, поэтому необходимо его перезапустить.

    Как только профиль заглушки стартовал, то при обращениях к нему видны следующие сообщения:

    http response body begin search use pattern

    http response body ERR end search text without pattern matching .OK.

    upstream check set peer 127.0.0.1:18030 failed because alive message isn't match to pattern

    check parse return error because response body is't match to expected

    check protocol http error with peer: 127.0.0.1:18030**

    Говорящих о несоответствии response_body объявленному в шаблоне (check_response_body_pattern) на SOWA. В этом случае запросы на /hello будут завершаться с код статусом 502.

    Перейдем к конфигурационному файлу заглушки hc_stub. Произведем следующую замену:

    - id: healthcheck
      optional:
        response_code: 400
        response_body: "OK"
    

    В сообщении указали "ОК", а код возврата установили 400. Переконфигурируем и перезапустим hc_stub. d16 В main_error.log'е отображается сообщение check parse return error because response status is't match to expected, current_status: 400, expected: 7, говорящее нам о том, что вернувшийся код статус не соответствует установленному в http_status_alive: "http_2xx http_3xx".

Переопределение контекста

До сих пор, при проксировании, контекст входа у нас совпадал с контекстом выхода. Однако, возможно потребуется изменить контекст исходного запроса перед отправкой его на backend.

Создадим профиль proxy_rewrite со следующей конфигурацией:

profile: proxy_rewrite
version: 2.0.3
system:
  wrk_count: 1
  conn_count: 4096
  optional:
    log_level: error
service:
 service_http_proxy:
   - id: rewrite_service
     name: service_2
     url:  ^/bye
     upstream_group_id:  back_1
     upstream_groups:
      - id: back_1
        servers:
         - server: 127.0.0.1:10016
        rewrite_uri:
         - from: (^/bye)(/.*)
           to: /hello$2
     allowed_queries:
       - method: get
     listen:
       - port: 11002

Как видно, за изменения контекста отвечает параметр rewrite_uri, представляющий последовательность пар регулярных выражений from/to.

Создадим, сконфигурируем, запустим профиль proxy_rewrite и отправим запрос по url http://127.0.0.1:11002/bye/good.

Пример вызова сервиса bye/good:

[sowacfg@tkle-ish0038 proxies]$ curl -v http://localhost:11002/bye/good
* About to connect() to localhost port 11002 (#0)
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 11002 (#0)
> GET /bye/good HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:11002
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Mon, 05 Apr 2021 13:51:10 GMT
< Content-Type: text/html
< Content-Length: 149
< Connection: keep-alive
< Server-Port: 10016
< Request-Uri: /hello/good
< Server: SOWA
* Connection #0 to host localhost left intact

Как видите по заголовкам ответа от сервера Backend, контекст запроса был изменен с /bye/good на /hello/good, как и ожидалось.