Configuring high-performance generic error pages for traifik using the Nginx container

Configuring high-performance generic error pages for traifik using the Nginx container

Readers who have been using Traefik for a long time should find that when the service restarts, the original website will display 404 not found blank pages. Although the service recovers quickly in most cases, the recovery time depends on the deployed and started application and the monitoring and inspection configuration strategy. If the traffic switching rules are not configured, sometimes they will see long-term blank pages, which is obviously not a good experience.

In order to improve the error experience, we can use traik middleware to solve this problem. The idea of this article can also deal with the creation of general Nginx error pages.

How to use traifik error page Middleware

although Official documents The usage of "error page" middleware is clearly recorded in:

labels:
  - "traefik.http.middlewares.test-errorpage.errors.status=500-599"
  - "traefik.http.middlewares.test-errorpage.errors.service=serviceError"
  - "traefik.http.middlewares.test-errorpage.errors.query=/{status}.html"

However, this only describes how to use the middleware. We also need the actual "application service" to support the corresponding error page to be displayed to the user when the error occurs. Therefore, the corresponding configuration of this logic is as follows:

labels:
  - "traefik.enable=true"
  - "traefik.docker.network=traefik"

  # Middleware definition
  - "traefik.http.middlewares.error-pages-middleware.errors.status=400-599"
  - "traefik.http.middlewares.error-pages-middleware.errors.service=error-pages-service"
  - "traefik.http.middlewares.error-pages-middleware.errors.query=/{status}.html"

  # Using middleware
  - "traefik.http.routers.error-pages-router.middlewares=error-pages-middleware@docker"
  - "traefik.http.routers.errorpage.entrypoints=https"
  - "traefik.http.routers.errorpage.tls=true"
  - "traefik.http.routers.errorpage.rule=HostRegexp(`{host:.+}`)"
  - "traefik.http.routers.errorpage.priority=1"
  - "traefik.http.services.error-pages-service.loadbalancer.server.port=80"

When configuring, you should also pay attention to one detail:

labels:
  - "traefik.http.routers.errorpage.priority=1"

We must lower the priority of this service to avoid affecting the normal operation of the business. In this way, we can ensure that this page is displayed in case of other business interruption, rather than in some extreme cases, what we see is not what we expect.

In addition, if you don't want to prepare multiple error pages, you can consider {status} Change HTML to the specified fixed page index html:

labels:
  - "traefik.http.middlewares.error-pages-middleware.errors.query=/index.html"

Find open source projects related to HTTP error code pages

After the configuration is written, we need to prepare the corresponding error page. We all know that there are at least 20 commonly used HTTP error codes, so it is very difficult to maintain if we rely on manual processing.

Considering the large number of users of traefik, someone should have similar needs. After searching, we found the project written by foreign brother: https://github.com/tarampampam/error-pages.

It feels good to simply use this open source project, but if you want to customize the page, you need to prepare a little more content:

  • Rely on a page generation tool to build Node and image.
  • Rely on the customized Nginx docker entrypoint SH, and need to build the Nginx running image, and need to modify the default Nginx conf.

The pursuit of simplicity and efficiency is the basic quality of engineers, so can we have a simpler scheme?

Customize using the official Nginx image

We know that Nginx provides a special function after 1.18, which allows users to customize and extend docker entrypoint D script, and support the use of custom Nginx configuration files based on envsust without modifying Nginx in the official image Conf and docker entrypoint SH file.

It's not hard to think of using env subst and the extended docker entry point D to preprocess custom pages.

For the sake of distribution performance, we use the Nginx Docker container image of alpine version.

Write template page

For the sake of demonstration, we will simplify the template structure here and only demonstrate how to use envsubst to complete the requirements:

<html lang="en-US">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>${DEFAULT_CODE} ${DEFAULT_TITLE}</title>
</head>
<body>
  <h1>${DEFAULT_TITLE}</h1>
  <p>${DEFAULT_DESC}</p>
</body>
</html>

After defining the data variables to be used in the page, you can start to prepare the data in the page.

Prepare error code list data

When preparing data, consider planning to use shell for processing. By default, the shell has poor support for JSON processing. Therefore, it is necessary to sort out the error codes here. It is best to sort them into a mode of one line and several columns to facilitate program reading and parsing.

Because the description text may introduce commas in the subsequent adjustment and update process, semicolons are used here as separators to avoid potential problems:

400;Bad Request;The server did not understand the request
401;Unauthorized;The requested page needs a username and a password
403;Forbidden;Access is forbidden to the requested page
404;Not Found;The server can not find the requested page
405;Method Not Allowed;The method specified in the request is not allowed
407;Proxy Authentication Required;You must authenticate with a proxy server before this request can be served
408;Request Timeout;The request took longer than the server was prepared to wait
409;Conflict;The request could not be completed because of a conflict
410;Gone;The requested page is no longer available
411;Length Required;The "Content-Length" is not defined. The server will not accept the request without it
412;Precondition Failed;The pre condition given in the request evaluated to false by the server
413;Payload Too Large;The server will not accept the request, because the request entity is too large
416;Requested Range Not Satisfiable;The requested byte range is not available and is out of bounds
418;I'm a teapot;Attempt to brew coffee with a teapot is not supported
429;Too Many Requests;Too many requests in a given amount of time
500;Internal Server Error;The server met an unexpected condition
502;Bad Gateway;The server received an invalid response from the upstream server
503;Service Unavailable;The server is temporarily overloading or down
504;Gateway Timeout;The gateway has timed out
505;HTTP Version Not Supported;The server does not support the "http protocol" version

Save the above content as pages After CSV, continue to write the data parsing script.

Write parsing script

Because we expect to use the image of alpine version, and only sh is in the image by default, we cannot use the method of array splitting when writing the function here, and we need to make modifications:

cat "pages.csv" | grep ";" | while read line; do
    CODE=$(echo "$line" | cut -d";" -f1)
    TITLE=$(echo "$line" | cut -d";" -f2)
    DESC=$(echo "$line" | cut -d";" -f3)

    echo $CODE;
    echo $TITLE;
    echo $DESC;
done

Execute the script for verification, and you can see that the parsing result is in line with the expectation:

400
Bad Request
The server did not understand the request
401
Unauthorized
The requested page needs a username and a password
403
Forbidden
Access is forbidden to the requested page
...

After the core functions are written, the next step is to stand on the "shoulders of giants" and refer to the script of the official image to realize "automatically read data and generate various error code pages".

Write template generation script

The "docker entrypoint. D / 20 envsubst on templates. Sh" script used to generate nginx configuration in the official container is written as follows:

#!/bin/sh

set -e

ME=$(basename $0)

auto_envsubst() {
  local template_dir="${NGINX_ENVSUBST_TEMPLATE_DIR:-/etc/nginx/templates}"
  local suffix="${NGINX_ENVSUBST_TEMPLATE_SUFFIX:-.template}"
  local output_dir="${NGINX_ENVSUBST_OUTPUT_DIR:-/etc/nginx/conf.d}"

  local template defined_envs relative_path output_path subdir
  defined_envs=$(printf '${%s} ' $(env | cut -d= -f1))
  [ -d "$template_dir" ] || return 0
  if [ ! -w "$output_dir" ]; then
    echo >&3 "$ME: ERROR: $template_dir exists, but $output_dir is not writable"
    return 0
  fi
  find "$template_dir" -follow -type f -name "*$suffix" -print | while read -r template; do
    relative_path="${template#$template_dir/}"
    output_path="$output_dir/${relative_path%$suffix}"
    subdir=$(dirname "$relative_path")
    # create a subdirectory where the template file exists
    mkdir -p "$output_dir/$subdir"
    echo >&3 "$ME: Running envsubst on $template to $output_path"
    envsubst "$defined_envs" < "$template" > "$output_path"
  done
}

auto_envsubst

exit 0

It can be seen that the idea is still relatively clear. We properly combine the parsing script in the previous article with this script to complete our requirements.

#!/bin/sh

set -e

ME=$(basename $0)

auto_envsubst() {

  local template_dir="${ERRORPAGE_ENVSUBST_TEMPLATE_DIR:-/pages}"
  local suffix="${ERRORPAGE_ENVSUBST_TEMPLATE_SUFFIX:-.html}"
  local output_dir="${ERRORPAGE_ENVSUBST_OUTPUT_DIR:-/usr/share/nginx/html}"

  local template defined_envs relative_path output_path subdir
  defined_envs=$(printf '${%s} ' $(env | cut -d= -f1))
  [ -d "$template_dir" ] || return 0
  if [ ! -w "$output_dir" ]; then
    echo >&3 "$ME: ERROR: $template_dir exists, but $output_dir is not writable"
    return 0
  fi

  find "$template_dir" -follow -type f -name "*$suffix" -print | while read -r template; do
    relative_path="${template#$template_dir/}"
    output_path="$output_dir/${relative_path%$suffix}$suffix"
    subdir=$(dirname "$relative_path")
    # create a subdirectory where the template file exists
    mkdir -p "$output_dir/$subdir"
    echo >&3 "$ME: Running envsubst on $template to $output_path"
    envsubst "$defined_envs" < "$template" > "$output_path"

    sed -i "s/^[[:space:]\t\n]*//g" "$output_path"

    cat "${template_dir}/pages.csv" | grep ";" | while read line; do
        CODE=$(echo "$line" | cut -d";" -f1)
        TITLE=$(echo "$line" | cut -d";" -f2)
        DESC=$(echo "$line" | cut -d";" -f3)

        export DEFAULT_CODE=$CODE
        export DEFAULT_TITLE=$TITLE
        export DEFAULT_DESC=$DESC
        export output_path="$output_dir/$CODE$suffix"

        envsubst "$defined_envs" < "$template" > "$output_path"
    done

  done
}

auto_envsubst

exit 0

Save the content as 30 env subst on pages SH, use later.

Write Nginx configuration

Because the official image supports extended configuration, we do not need to modify the primary nginx Conf, just write a new configuration according to the requirements:

server {
    listen        ${NGINX_PORT};
    server_name   ${NGINX_HOST};

    charset       utf-8;
    gzip on;

    access_log    off;
    log_not_found off;
    server_tokens off;

    location / {
        root   /usr/share/nginx/html;
        index  index.html;
    }

    error_page 400 /400.html;
    error_page 401 /401.html;
    error_page 403 /403.html;
    error_page 404 /404.html;
    error_page 405 /405.html;
    error_page 407 /407.html;
    error_page 408 /408.html;
    error_page 409 /409.html;
    error_page 410 /410.html;
    error_page 411 /411.html;
    error_page 412 /412.html;
    error_page 413 /413.html;
    error_page 416 /416.html;
    error_page 418 /418.html;
    error_page 429 /429.html;
    error_page 500 /500.html;
    error_page 502 /502.html;
    error_page 503 /503.html;
    error_page 504 /504.html;
    error_page 505 /505.html;

    location = /favicon.ico {
        add_header 'Content-Type' 'image/x-icon';
        return 200 "";
    }

    location = /robots.txt {
        return 200 "User-agent: *\nDisallow: /";
    }
}

Save the above content as default Conf.template. After completing the container configuration, you can use this service.

Write service container configuration

Our container configuration file is actually very simple:

version: '3'

services:

  errorpage-nginx:
    image: nginx:1.19.4-alpine
    volumes:
       - ./templates:/etc/nginx/templates:ro
       - ./docker-entrypoint.d/30-envsubst-on-pages.sh:/docker-entrypoint.d/30-envsubst-on-pages.sh:ro
       - ./pages:/pages:ro
    environment:
      - NGINX_HOST=localhost
      - NGINX_PORT=80
      - DEFAULT_CODE=404
      - DEFAULT_TITLE=The page you're looking for is now beyond our reach. Let's get you..
      - DEFAULT_DESC=Page not found
    networks:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"

      - "traefik.http.routers.errorpage.entrypoints=https"
      - "traefik.http.routers.errorpage.tls=true"
      - "traefik.http.routers.errorpage.rule=HostRegexp(`{host:.+}`)"
      - "traefik.http.routers.errorpage.priority=1"
      - "traefik.http.services.error-pages-service.loadbalancer.server.port=80"

      - "traefik.http.routers.error-pages-router.middlewares=error-pages-middleware@docker"
      - "traefik.http.middlewares.error-pages-middleware.errors.status=400-599"
      - "traefik.http.middlewares.error-pages-middleware.errors.service=error-pages-service"
      - "traefik.http.middlewares.error-pages-middleware.errors.query=/{status}.html"

networks:
  traefik:
    external: true

You may wonder why there are three default environment variables, DEFAULT_CODE,DEFAULT_TITLE,DEFAULT_DESC, these variables are used to process the index of the home page of the service site HTML file, if you like, you can freely play the whole point of different content.

last

For online examples, visit: https://error.soulteary.com/ , the example template is written for reference https://www.mantralabsglobal.com/404 Thank Mantra Labs for sharing your design ideas.

It has to be said that the new version of Nginx container image is quite powerful, from Historical articles It should also be seen that I like it: small, concise, high-performance and rich interfaces. If you're still using the old version of Nginx, consider upgrading to the latest version.

–EOF

I like a small group of friends now.

Without advertising, we will talk about some problems in software, HomeLab and programming together, and share some information of Technology Salon in the group from time to time.

Friends who like to toss are welcome to scan the code to add friends. (please indicate the source and purpose, otherwise it will not pass the review)

About tossing the group into the group

This article uses the "signature 4.0 International (CC BY 4.0)" license agreement. You are welcome to reprint, modify or use it again, but you need to indicate the source. Signature 4.0 International (CC BY 4.0)

Author: Su Yang

Creation time: December 6, 2020
Statistics: 9154 words
Reading time: 19 minutes
Link to this article: https://soulteary.com/2020/12/06/use-nginx-container-to-configure-high-performance-general-error-pages-for-traefik.html

Tags: Docker Nginx traefik

Posted by syacoub on Tue, 03 May 2022 11:49:38 +0300