Странные проблемы 502 и 504 в Traefik

Traefik

Все мы знаем, что схема Ingress обычно используется в кластере Kubernetes для унификации трафика внутри прокси-кластера, а обычно используемая схема Ingress —traefikа такжеnginx, как и традиционный Nginx в качестве обратного прокси-сервера и загрузочного устройства внутри предприятия, в различных сценариях может потребоваться специальная конфигурация для удовлетворения требований, иначе появятся странные ненормальные коды состояния. Давайте посмотрим на эту статью, мы находимся вtraefikИсключения 500 и 502, обнаруженные в .

Введение

Прежде чем мы начнем, давайте рассмотрим несколько аномальных кодов состояния, которые часто встречаются в Nginx как инструменте обратного прокси:

  • 499: Клиент активно отключился. Обычно запрос не возвращается в течение указанного клиентом времени, клиент активно закрывает соединение, и клиент не возвращает никаких данных (499 будет записано в Nginx). В целом客户端超时
  • внутренняя ошибка сервера 500. Сервер обнаруживает неизвестную ошибку и не может завершить обработку запроса, обычно из-за ненормальной внутренней бизнес-логики (сама ошибка).
  • 502: Ошибка шлюза. Обычно шлюз не получал ожидаемого ответа от вышестоящей службы (上游未返回数据或未按照协议约定返回数据), шлюз считает себя бесполезным и возвращает ошибку шлюза. В целом后端服务器宕机или业务逻辑超时
  • 504: время ожидания шлюза истекло. Указывает, что шлюз вовремя не получил ответные данные от восходящего потока. Как правило, в процессе шлюза Nginx в качестве клиента, отвечающего на запрос вышестоящей службы, возникает тайм-аут шлюза Nginx, но в это время для вышестоящего сервера он будет продолжать выполняться до конца. (Nginx网关作为客户端时的超时)
# 499 的实际情况就是,客户端指定超时时间为N秒,但是该请求在服务端实际需要执行M秒(M>N秒),客户端等的不耐烦了就关闭了
# 对于499状态来讲,解决方式是优化后端代码逻辑或者修改nginx参数
$ cat nginx.conf
proxy_ignore_client_abort    on;
$ curl -i -m 3 http://127.0.0.1/hello.php


# 502的实际情况通常是Nginx网关后端的服务器直接宕机了(所以就拿不到上游的响应了)
# 当然也有可能是上游服务器真正的执行逻辑超过了上游服务器的超时时间限制(比如php-fpm.conf设置request_terminate_timeout5s,但是实际的业务逻辑需要7s才能完成),此时上游服务器端出现`业务逻辑超时`,给Nginx网关返回了异常的数据造成的
# 502时后端的几种错误日志
 recv() failed (104: Connection reset by peer) while reading response header from upstream
 upstream prematurely closed connection while reading response header from upstream
 connect() failed (111: Connection refused) while connecting to upstream
# 整体来说502出现的问题通常是因为后端挂了,或者因为后端负载太高,暂时不可响应
# 可以在nginx侧增加proxy_read_timeout来暂时缓解
$ cat nginx.conf
proxy_read_timeout  20s;

# 504的实际情况就是客户端->Nginx->Backend,在过程中Nginx需要作为客户端访问Backend服务,但是在Backend还没用执行完成时,Nginx首先超过了自己的客户端超时时间,此时就会出现504的异常(但是对于客户端来说返回什么呢?)
# 对于504场景而言,通常的做法就是优化Backend的逻辑,适当减少执行时间;另外也可以适当的增加Nginx作为客户端时的超时时间
# 要知道,当Nginx作为客户端时,是以一个Proxy的角色存在的,配置如下参数即可
$ cat nginx.conf
uwsgi_connect_timeout 5;
uwsgi_send_timeout 5;
uwsgi_read_timeout 5;
fastcgi_read_timeout 5;
fastcgi_send_timeout 5;
proxy_connect_timeout      90;
proxy_send_timeout         90;
proxy_read_timeout         90;

2. Странные 500 и 502 в траефике

конфигурация развертывания traefik в кластере Kubernetes

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

# traefik的configmap配置文件
$ cat traefik-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: traefik-config
  namespace: kube-system
data:
  traefik.toml: |
    defaultEntryPoints = ["http","https"]
    debug = false
    logLevel = "INFO"

    InsecureSkipVerify = true
    [entryPoints]
      [entryPoints.http]
      address = ":80"
      compress = true
      [entryPoints.https]
      address = ":443"
        [entryPoints.https.tls]
    [web]
      address = ":8080"
    [kubernetes]
    [metrics]
      [metrics.prometheus]
      buckets=[0.1,0.3,1.2,5.0]
      entryPoint = "traefik"
    [ping]
    entryPoint = "http"

# traefik的DaemonSet配置
$ cat traefik-ds-v1.7.16.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik-ingress-controller
  namespace: kube-system
---
kind: DaemonSet
apiVersion: extensions/v1beta1
metadata:
  name: traefik-ingress-controller
  namespace: kube-system
  labels:
    k8s-app: traefik-ingress-lb
spec:
  template:
    metadata:
      labels:
        k8s-app: traefik-ingress-lb
        name: traefik-ingress-lb
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: node-role.kubernetes.io/master
                operator: DoesNotExist
      serviceAccountName: traefik-ingress-controller
      terminationGracePeriodSeconds: 30
      hostNetwork: true
      containers:
      - image: traefik:v1.7.16
        name: traefik-ingress-lb
        ports:
        - name: http
          containerPort: 80
          hostPort: 80
        - name: admin
          containerPort: 8080
        securityContext:
          capabilities:
            drop:
            - ALL
            add:
            - NET_BIND_SERVICE
        args:
        - --api
        - --kubernetes
        - --logLevel=INFO
        - --traefikLog.filePath=/logdata/traefik.log
        - --configfile=/config/traefik.toml
        - --accesslog.filepath=/logdata/access.log
        - --accesslog.bufferingsize=100
        volumeMounts:
        - mountPath: /config
          name: config
        - mountPath: /logdata
          name: access-log
      volumes:
      - configMap:
          name: traefik-config
        name: config
      - name: access-log
        hostPath:
          path: /opt/logs/ingress/
---
kind: Service
apiVersion: v1
metadata:
  name: traefik-ingress-service
  namespace: kube-system
  labels:
    k8s-app: traefik-ingress-lb
spec:
  selector:
    k8s-app: traefik-ingress-lb
  ports:
    - protocol: TCP
      port: 80
      name: web
    - protocol: TCP
      port: 8080
      name: admin

Внешний API-интерфейс Python

# 接口对外的ingress
$ kubectl  get ingress -n s-data
NAME                     HOSTS                    ADDRESS   PORTS   AGE
data-api.bgbiao.cn   data-api.bgbiao.cn             80      236d
ops.bgbiao.cn       ops.bgbiao.cn                 80      236d

# 测试对外接口
$ curl data-api.bgbiao.cn  -i
HTTP/1.1 401 Unauthorized
Access-Control-Allow-Headers: Content-Type, X-TOKEN
Access-Control-Allow-Origin: *
Content-Length: 58
Content-Type: application/json
Vary: Accept-Encoding
Date: Sun, 28 Jun 2020 14:55:00 GMT

# 接口需要登录,那么我们对登录接口进行压测来模拟问题
$ curl -X POST  --data '@/root/login.json' -H 'Content-type:application/json' http://data-api.bgbiao.cn/account/users/login/   -i
HTTP/1.1 200 OK
Access-Control-Allow-Headers: Content-Type, X-TOKEN
Access-Control-Allow-Origin: *
Content-Length: 250
Content-Type: application/json
Vary: Accept-Encoding
Date: Sun, 28 Jun 2020 14:56:33 GMT

Странные 500 и 502

После развертывания службы все нормально, но после простого стресс-теста обнаруживается, что некоторые запросы службы не выполняются.

# 使用ab工具进行压测
# 由压测结果可以发现,20个并发共压测200个请求,期间出现了7次失败请求
$ ab -c 20 -n 200   -T 'application/json' -p /root/login.json http://data-api.bgbiao.cn/account/users/login/
...
Benchmarking data-api.bgbiao.cn (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests


Server Software:
Server Hostname:        data-api.bgbiao.cn
Server Port:            80

Document Path:          /account/users/login/
Document Length:        250 bytes

Concurrency Level:      20
Time taken for tests:   1.340 seconds
Complete requests:      200
Failed requests:        7
   (Connect: 0, Receive: 0, Length: 7, Exceptions: 0)
Write errors:           0
Non-2xx responses:      7
Total transferred:      91371 bytes
Total body sent:        46400
HTML transferred:       48387 bytes
Requests per second:    149.21 [#/sec] (mean)
Time per request:       134.035 [ms] (mean)
Time per request:       6.702 [ms] (mean, across all concurrent requests)
Transfer rate:          66.57 [Kbytes/sec] received
                        33.81 kb/s sent
                        100.38 kb/s total

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1    1   0.1      1       1
Processing:     2  116  27.8    114     179
Waiting:        2  116  27.8    114     179
Total:          3  117  27.8    116     180

Percentage of the requests served within a certain time (ms)
  50%    116
  66%    121
  75%    125
  80%    129
  90%    154
  95%    167
  98%    173
  99%    175
 100%    180 (longest request)


# 将压测结果保存到文本中进行简单分析
# 简单分析在200个压测请求中,有4个请求失败,分别为2次500错误和2次502错误
$ ab -v 10 -c 20 -n 200   -T 'application/json' -p /root/login.json http://data-api.bgbiao.cn/account/users/login/  > ab-log.txt
$ cat ab-log.txt | grep HTTP | sort| uniq -c
    196 HTTP/1.0 200 OK
      2 HTTP/1.0 500 Internal Server Error
      2 HTTP/1.0 502 Bad Gateway
      1 POST /account/users/login/ HTTP/1.0

Устранение неполадок 500 и 502 в traefik

Ранее мы упоминали о причинах кодов состояния 500 и 502 в сценарии Nginx и сопутствующих решениях, затем в кластере Kubernetes роль traefik аналогична роли Nginx.

В начале мы упомянули информацию о конфигурации traefik в кластере.Для SRE любая производственная служба должна иметь соответствующие данные об наблюдаемости.Поэтому мы также по умолчанию использовали журнал доступа и журнал процессов traefik.Persistence (соответственноaccess.log和traefik.log), а также предоставляет интерфейс метрик prometheus traefik.

Для приведенного выше запроса на стресс-тест мы обнаружили следующие журналы исключений в журнале доступа:

$ tail -f access.log | grep data-api.bgbiao.cn | grep -v 'HTTP/1.0" 200'
192.168.0.23 - - [28/Jun/2020:14:57:38 +0000] "POST /account/users/login/ HTTP/1.0" 500 21 "-" "ApacheBench/2.3" 122267376 "data-api.bgbiao.cn/" "http://20.0.41.8:8080" 0ms
192.168.0.23 - - [28/Jun/2020:14:57:38 +0000] "POST /account/users/login/ HTTP/1.0" 500 21 "-" "ApacheBench/2.3" 122267385 "data-api.bgbiao.cn/" "http://20.0.26.9:8080" 1ms
192.168.0.23 - - [28/Jun/2020:14:57:38 +0000] "POST /account/users/login/ HTTP/1.0" 500 21 "-" "ApacheBench/2.3" 122267410 "data-api.bgbiao.cn/" "http://20.0.41.8:8080" 1ms
192.168.0.23 - - [28/Jun/2020:14:57:38 +0000] "POST /account/users/login/ HTTP/1.0" 500 21 "-" "ApacheBench/2.3" 122267418 "data-api.bgbiao.cn/" "http://20.0.41.8:8080" 1ms
192.168.0.23 - - [28/Jun/2020:14:57:38 +0000] "POST /account/users/login/ HTTP/1.0" 500 21 "-" "ApacheBench/2.3" 122267484 "data-api.bgbiao.cn/" "http://20.0.26.9:8080" 1ms
192.168.0.23 - - [28/Jun/2020:14:57:38 +0000] "POST /account/users/login/ HTTP/1.0" 502 11 "-" "ApacheBench/2.3" 122267518 "data-api.bgbiao.cn/" "http://20.0.26.9:8080" 1ms
192.168.0.23 - - [28/Jun/2020:14:57:39 +0000] "POST /account/users/login/ HTTP/1.0" 500 21 "-" "ApacheBench/2.3" 122267550 "data-api.bgbiao.cn/" "http://20.0.26.9:8080" 4ms
192.168.0.23 - - [28/Jun/2020:15:02:06 +0000] "POST /account/users/login/ HTTP/1.0" 502 11 "-" "ApacheBench/2.3" 122272696 "data-api.bgbiao.cn/" "http://20.0.41.8:8080" 2ms
192.168.0.23 - - [28/Jun/2020:15:02:06 +0000] "POST /account/users/login/ HTTP/1.0" 502 11 "-" "ApacheBench/2.3" 122272711 "data-api.bgbiao.cn/" "http://20.0.41.8:8080" 1ms
192.168.0.23 - - [28/Jun/2020:15:02:06 +0000] "POST /account/users/login/ HTTP/1.0" 500 21 "-" "ApacheBench/2.3" 122272836 "data-api.bgbiao.cn/" "http://20.0.26.9:8080" 0ms
192.168.0.23 - - [28/Jun/2020:15:02:06 +0000] "POST /account/users/login/ HTTP/1.0" 500 21 "-" "ApacheBench/2.3" 122272837 "data-api.bgbiao.cn/" "http://20.0.41.8:8080" 0ms

Видно, что он совпадает с кодом состояния неудачного запроса в нашем результате стресс-теста.500а также502, хоть их и больше 4 по количеству, но это пока не важно.

Обычно большинство людей, увидев 500 или 502 на прокси-уровне, подсознательно думают, что это, должно быть, проблема с восходящим сервисом, но такого рода предположения также можно быстро устранить. следует:

  • Во время стресс-теста 500 и 502 продолжайте получать доступ к вышестоящему серверу.
  • Используйте те же параметры теста давления, чтобы напрямую нажать на вышестоящий сервер.
  • Разверните восходящие сервисы на независимой ECS и используйте те же параметры для стресс-тестирования.

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

Измените уровень журнала в traefki наDEBUG:

$ cat traefik-ds-v1.7.6.yaml
....
        args:
        - --api
        - --kubernetes
        - --logLevel=DEBUG
....

Затем просмотрите следующую связанную информацию в журнале:

# 500相关的日志
time="2020-06-28T15:35:05Z" level=debug msg="'500 Internal Server Error' caused by: http: server closed idle connection"
time="2020-06-28T15:35:05Z" level=debug msg="vulcand/oxy/forward/http: Round trip: http://20.0.26.9:8080, code: 500, Length: 21, duration: 1.486276ms"

# 502相关的日志
time="2020-06-28T15:35:05Z" level=debug msg="'502 Bad Gateway' caused by: EOF"
time="2020-06-28T15:35:05Z" level=debug msg="vulcand/oxy/forward/http: Round trip: http://20.0.26.9:8080, code: 502, Length: 11, duration: 1.530677ms"

Из вышеприведенной информации 500 видно, что сервисный процесс traefik активно закрыл незанятую ссылку, а следующие 502 связаны с EOF, создается впечатление, что он был отключен без чтения данных ответа, в результате чего traefik возвращает 502 . Обычно такая ситуация очень распространена в Nginx, и легко настроить соответствующие параметры конфигурации (упомянутые в начале статьи), но режим развертывания и настройку параметров traefik все же нужно обратить внимание на соответствующие документы.

Затем я пролистал вопросы, связанные с traefik, на github и обнаружил, что эта проблема появлялась много раз.

Одна из них — проблема 500, а другая — проблема 502, но обычно обе проблемы идут парами.

500 и 502 решения проблем

Как упоминалось в первом выпуске выше, traefik по умолчанию включает функцию поддержки активности http в функции обратного прокси-сервера http, но функция поддержки активности http не включена в приложении Python, потому что наша тестовая программа выше на самом деле Разработано с использованием Python, сначала настройте параметр.

# 在uwsgi.ini中增加keep-alive参数即可
$ cat uwsgi.ini
[uwsgi]
http = 0.0.0.0:8080
http-keepalive = 1
chdir = /opt/app/
wsgi-file = /opt/app/main.py
callable = app
stats = 0.0.0.0:8081
processes = 2
threads = 10

# 重启应用后,再次进行压测
# 之前出现的502和500错误基本都消失了
# 并发200,共1万个请求,失败的请求数为0,总耗时1min(qps才到160😸)
$ ab  -c 200 -n 10000   -T 'application/json' -p /root/login.json http://data-api.bgbiao.cn/account/users/login/
....
....

Concurrency Level:      200
Time taken for tests:   59.323 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      4670000 bytes
Total body sent:        2320000
HTML transferred:       2500000 bytes
Requests per second:    168.57 [#/sec] (mean)
Time per request:       1186.454 [ms] (mean)
Time per request:       5.932 [ms] (mean, across all concurrent requests)
Transfer rate:          76.88 [Kbytes/sec] received
                        38.19 kb/s sent
                        115.07 kb/s total

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1    3  38.6      1    1035
Processing:   101  942 1457.7    857   32684
Waiting:      101  942 1457.7    857   32684
Total:        102  945 1458.0    861   32685

# p99达到7.3s
Percentage of the requests served within a certain time (ms)
  50%    861
  66%   1033
  75%   1136
  80%   1191
  90%   1886
  95%   2281
  98%   4209
  99%   7399
 100%  32685 (longest request)

Включив параметр keep-alive http бизнес-уровня, проблемы 500 и 502 были временно решены.Можно ли оптимизировать эту проблему через параметры уровня traefik?Это также упоминалось во втором вопросе выше.

То есть путем изменения следующих параметров traefik и повторного развертывания всего кластера traefik:

# 关闭traefik的keep-alive参数,参数默认为200,如果参数为0,则使用go标准库中的DefaultMaxIdleConnsPerHost参数
# keep-alive主要是用来复用链接来减少open files的,但是对于大量的短连接来将这种链接复用就可能出现上述情况
--maxidleconnsperhost=-1
# 即通过设置重试次数,增加空闲链接的超时时间,增加转发响应的超时时间,默认是0次
--retry.attempts=10
# 该参数已经替换为--respondingtimeouts.idletimeout参数了,默认为3m0s
--idletimeout=60s
# 默认是0s
--forwardingtimeouts.responseheadertimeout=60s

# traefik 空闲链接超时
$ ./traefik --help | grep idletimeout
    --idletimeout                                 (Deprecated) maximum amount of time an idle (keep-alive) connection will remain  (default "0s")
    --respondingtimeouts.idletimeout              IdleTimeout is the maximum amount duration an idle (keep-alive) connection will  (default "3m0s")

# 响应超时相关参数
 $ ./traefik --help | grep respondingtimeouts
    --respondingtimeouts                          Timeouts for incoming requests to the Traefik instance                           (default "true")
    --respondingtimeouts.idletimeout              IdleTimeout is the maximum amount duration an idle (keep-alive) connection will  (default "3m0s")
    --respondingtimeouts.readtimeout              ReadTimeout is the maximum duration for reading the entire request, including    (default "0s")
    --respondingtimeouts.writetimeout             WriteTimeout is the maximum duration before timing out writes of the response.   (default "0s")


# 转发的超时
$ ./traefik --help | grep forwardingtimeouts
    --forwardingtimeouts                          Timeouts for requests forwarded to the backend servers                           (default "true")
    --forwardingtimeouts.dialtimeout              The amount of time to wait until a connection to a backend server can be         (default "30s")
    --forwardingtimeouts.responseheadertimeout    The amount of time to wait for a server's response headers after fully writing   (default "0s")

Параметры финального модифицированного траефика следующие:

# 可以根据实际情况考虑是否要关闭keep-alive 即增加参数: --maxidleconnsperhost=-1
        - --api
        - --kubernetes
        - --logLevel=INFO
        - --traefikLog.filePath=/logdata/traefik.log
        - --configfile=/config/traefik.toml
        - --accesslog.filepath=/logdata/access.log
        - --accesslog.bufferingsize=100
        - --forwardingtimeouts.responseheadertimeout=60s
        - --respondingtimeouts.idletimeout=180s
        - --retry.attempts=10
        - --idletimeout=180s

3. Странный 503 в SLB

Обнаружив, что доменное имя было напрямую преобразовано в узел traefik, 502 и 500 больше не появлялись, мы подключили узел traefik к slb интрасети Alibaba Cloud, но снова начали появляться странные проблемы 503.

Простой стресс-тест после подключения к slb (внутренняя сеть использует бесплатный низкопрофильный slb😹)

# client->ali-slb->traefik->pods
$ ab -v 10 -c 200 -n 2000   -T 'application/json' -p postfile.json http://data-api.soulapp.cn/get_doc > slb-log.txt
$ cat slb-log.txt | grep  'HTTP/1.1 200' | wc -l
1322
$ cat slb-log.txt | grep  'HTTP/1.1 503' | wc -l
678


# client->traefik->pods
$ ab -v 10 -c 200 -n 2000   -T 'application/json' -p postfile.json http://data-api.c.bgbiao.cn/get_doc > traefik-log.txt
Completed 200 requests
Completed 400 requests
Completed 600 requests
Completed 800 requests
Completed 1000 requests
Completed 1200 requests
Completed 1400 requests
Completed 1600 requests
Completed 1800 requests
Completed 2000 requests
Finished 2000 requests

$ cat traefik-log.txt  | grep  'HTTP/1.0 200' | wc -l
2000

Позже я увидел следующие документы в документации Alibaba Cloud.По сути, SLB-сторона Alibaba Cloud реализовала определенную текущую стратегию ограничения для SLB разных спецификаций.В настоящее время 503 будет возвращено клиенту.

Облако Alibaba TPS-503


公众号
публика
Категории