Configure high-performance generic error pages for Traefik using Nginx containers

If you have been using Traefik for a long time, you will notice a blank 404 Not Found page displayed on the original site when the service is restarted. Although in most cases the service is restored quickly, the recovery time depends on the application deployed and the monitoring check configuration policy. If the traffic switching rule is not configured, Sometimes, you’ll see a blank page for a long time, which is obviously not a good experience.

To improve the experience, we can use Traefik’s error page middleware to solve this problem and optimize the access experience. This idea can also handle the creation of common Nginx error pages.

How do I use Traefik error page middleware

Although the official documentation clearly describes the use of “error page” middleware:

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"
Copy the code

However, this only describes how to use the middleware, we also need the actual “application service” to support the error page to be displayed to the user when an error occurs, so the configuration for handling this logic is as follows:

labels: - "traefik.enable=true" - "traefik.docker.network=traefik "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. The query = / {status}. The HTML" use 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"Copy the code

There is one more detail to note when configuring:

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

We must reduce the priority of this service to avoid affecting the normal operation of business. This ensures that the page is displayed when other business is interrupted, rather than when we encounter some extreme situation that is not what we expect.

Alternatively, if you don’t want to prepare multiple error pages, consider changing {status}.html to the specified fixed page index.html:

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

Find open source projects related to HTTP error pages

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

Considering that there are many traefik users now, there should be someone with similar needs. After searching, I found a project written by a foreign boy: github.com/tarampampam… .

Using this open source project is fine, but if you want to customize the page, you need to prepare slightly more:

  • Rely on a page generation tool, build Node build image.
  • Rely on custom Nginxdocker-entrypoint.shAnd need to build Nginx run image, as well as need to modify the defaultNginx.conf.

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

Customize using the official Nginx image

We know that Nginx provides a special feature after 1.18 that allows users to customize and extend additional docker-entrypoint.d scripts, And support for using custom Nginx configuration files based on envsubst without modifying the nginx.conf and docker-entrypoint.sh files in the official image.

Extending the idea a little, it is not difficult to think of using ENvsubst and the extended Docker-entryPoint. D to carry out custom page preprocessing.

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

Write the template page

For demonstration purposes, here we simplify our template structure to show only how to use envsubst to fulfill 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>
Copy the code

Once you have defined the data variables you want to use in the page, you can prepare the data in the page.

Prepare error code list data

When preparing data, consider using shell for processing. Shell does not support JSON processing well by default. Therefore, you need to sort out error codes in a row or several columns mode, which is convenient for the program to read and parse.

Because the description text may introduce commas during subsequent adjustments and updates, semicolons are used 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" versionCopy the code

After saving the above content as pages.csv, continue writing the data parsing script.

Writing parsing scripts

Because we expect to use the Alpine version of the image, the default image is only sh, so we can’t use the array split method to write the function, we need to modify:

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
Copy the code

Execute the script to verify and you can see that the parsing results are as expected:

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
...
Copy the code

Core functions completed, the next is to stand on the “shoulders of giants”, reference the official mirror script, to achieve “automatic reading data to generate a variety of error code pages”.

Write template generation scripts

The “docker-entryPoint.d /20-envsubst-on-templates. Sh” script used in the official container to generate the nginx configuration is written like this:

#! /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 0Copy the code

We can see that the idea is still relatively clear, we will be the above parsing script and this script appropriate merge, 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 0Copy the code

Save the content as 30-envsubst-on-pages.sh for later use.

Write Nginx configuration

Since the official image supports extended configuration, we don’t need to change the main nginx.conf, just write the new configuration as needed:

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: /"; }}Copy the code

Save the above content as default.conf.template, and then complete the container configuration to use the service.

Write the service container configuration

Our container configuration file is 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: trueCopy the code

You may be wondering why there are three default environment variables DEFAULT_CODE, DEFAULT_TITLE, and DEFAULT_DESC, which are used to process the index.html file on the first page of the service site, and are free to play around with different content if you wish.

The last

Want to see online, for example, you can visit: error.soulteary.com/, example template to write reference for the design of www.mantralabsglobal.com/404, thank Mantra Labs to share.

I have to say that the new version of Nginx container image is quite powerful, and I like it from historical articles: small, simple, high-performance, and rich in interfaces. If you’re still using an older version of Nginx, consider upgrading to the latest version.

–EOF


I now have a small toss group, which gathered some like to toss small partners.

In the case of no advertisement, we will talk about software, HomeLab and some programming problems together, and also share some technical salon information in the group from time to time.

Like to toss small partners welcome to scan code to add friends. (Please specify source and purpose, otherwise it will not be approved)

All this stuff about getting into groups


This article is published under a SIGNATURE 4.0 International (CC BY 4.0) license. Signature 4.0 International (CC BY 4.0)

Author: Su Yang

Creation time: on December 6, 2020 statistical word count: 9154 words reading time: 19 minutes to read this article links: soulteary.com/2020/12/06/…