通過 docker 學習 nginx,附全部配置及 API 測試,可使用 apifox 直接開啟

shanyue發表於2022-07-11

三年經驗的前端或多或少與 nginx 配置打過交道。

nginx 的重要性不言而喻。

本篇文章以前端的視角,介紹下 nginx 的常見配置,並通過 docker 的方式學習 nginx,這保證所有示例配置都能正常執行。

我將本文所有關於 docker/nginx 的配置放置在 simple-deploy,可克隆下來通過 docker compose 快速執行。

並將所有的介面示例維護在了 Learn Nginx By Docker 文件,並可通過 Apifox 開啟並快速除錯。

nginx 的配置檔案

我們通過 nginx 映象來了解 nginx 的配置檔案都有哪些。

$ docker run -it --rm nginx:alpine sh

$ ls -lah /etc/nginx/
total 40K    
drwxr-xr-x    3 root     root        4.0K Nov 13  2021 .
drwxr-xr-x    1 root     root        4.0K Jun 14 07:55 ..
drwxr-xr-x    2 root     root        4.0K Nov 13  2021 conf.d
-rw-r--r--    1 root     root        1.1K Nov  2  2021 fastcgi.conf
-rw-r--r--    1 root     root        1007 Nov  2  2021 fastcgi_params
-rw-r--r--    1 root     root        5.2K Nov  2  2021 mime.types
lrwxrwxrwx    1 root     root          22 Nov 13  2021 modules -> /usr/lib/nginx/modules
-rw-r--r--    1 root     root         648 Nov  2  2021 nginx.conf
-rw-r--r--    1 root     root         636 Nov  2  2021 scgi_params
-rw-r--r--    1 root     root         664 Nov  2  2021 uwsgi_params

nginx 中,其中比較重要的有以下幾個檔案,而它們都是有層層關聯的:

  • /etc/nginx/nginx.conf
  • /etc/nginx/conf.d/default.conf

/etc/nginx/nginx.conf

nginx 主配置檔案,引用了 /etc/nginx/conf.d/ 目錄下的所有配置檔案。

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

/etc/nginx/conf.d/default.conf

server {
    listen       80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

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

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

/usr/share/nginx/html

預設的靜態資源目錄,也是 nginx 的歡迎頁面。

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

通過 docker 高效學習 nginx 配置

推薦一種高效學習 nginx 的方法: 在本地使用 nginx 映象並掛載 nginx 配置啟動容器

Learning Nginx

通過以下 docker-compose 可秒級驗證 nginx 配置,無疑是學習 nginx 的絕佳利器。

我將所有關於 nginx 的配置放置在 simple-deploy,並且每一份配置對應 docker-compose 中的一個 service 如以下 nginx、location、order1 就是 service

version: "3"
services:
  # 關於 nginx 最常見配置的學習
  nginx:
    image: nginx:alpine
    ports:
      - 8080:80
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - .:/usr/share/nginx/html
  # 關於 location 的學習
  location: ...
  # 關於 location 匹配順序的學習
  order1: ...

每次修改配置時,需要重啟容器,可根據服務名學習指定內容。

$ docker-compose up <service>

# 學習 nginx 最基礎的配置
$ docker-compose up nginx

# 學習關於 location 的配置
$ docker-compose up location

本篇文章所有的 nginx 配置均可以通過 docker 來進行學習,並附全部程式碼及配置。

root 與 index

rootindex 為前端部署的基礎,在預設情況下 root 為 /usr/share/nginx/html,因此我們部署前端時,往往將構建後的靜態資源目錄掛載到該地址。

server {
    listen       80;
    server_name  localhost;

    root   /usr/share/nginx/html;
    index  index.html index.htm;
}

location

location 用以匹配路由,配置語法如下。

location [ = | ~ | ~* | ^~ ] uri { ... }

其中 uri 前可提供以下修飾符

  • = 精確匹配,優先順序最高。
  • ^~ 字首匹配,優先順序其次。如果同樣是字首匹配,走最長路徑。
  • ~ 正則匹配,優先順序再次 (~* 只是不區分大小寫,不單列)。如果同樣是正則匹配,走第一個路徑。
  • / 通用匹配,優先順序再次。

為了驗證所匹配的 location,我會在以下示例中新增一個自定義響應頭 X-Config,可通過瀏覽器控制檯網路皮膚驗證其響應頭。

add_header X-Config B;

注意,我所有配置檔案中的連結可直接點選,避免了在 compose 配置檔案中尋找對映埠號的不方便

location 修飾符驗證

對於此四種修飾符可以在我的 nginx 下進行驗證。

由於此處使用了 proxy_pass,因此需要 location2api 兩個服務一起啟動,在 location2 服務中,可直接通過 service 名稱作為 hostname 即 http://api:3000 訪問 api 服務。

而 api 服務,為我自己寫的一個 whoami 服務,用以列印出請求路徑等資訊,詳見 shfshanyue/whoami

$ docker-compose up location2 api

以下是關於驗證 location 的配置檔案,詳見 shfshanyue/simple-daploy:learn-nginxs

server {
    listen       80;
    server_name  localhost;

    root   /usr/share/nginx/html;
    index  index.html index.htm;

    # 通用匹配,所有 /xxx 任意路徑都會匹配其中的規則
    location / {
        add_header X-Config A;
        try_files  $uri $uri.html $uri/index.html /index.html;
    }

    # http://localhost:8120/test1           ok
    # http://localhost:8120/test1/          ok
    # http://localhost:8120/test18          ok
    # http://localhost:8120/test28          not ok
    location /test1 {
        # 可通過檢視響應頭來判斷是否成功返回
        add_header X-Config B;
        proxy_pass http://api:3000;
    }

    # http://localhost:8120/test2           ok
    # http://localhost:8120/test2/          not ok
    # http://localhost:8120/test28          not ok
    location = /test2 {
        add_header X-Config C;
        proxy_pass http://api:3000;
    }

    # http://localhost:8120/test3           ok
    # http://localhost:8120/test3/          ok
    # http://localhost:8120/test38          ok
    # http://localhost:8120/hellotest3      ok
    location ~ .*test3.* {
        add_header X-Config D;
        proxy_pass http://api:3000;
    }

    # http://localhost:8120/test4           ok
    # http://localhost:8120/test4/          ok
    # http://localhost:8120/test48          ok
    # http://localhost:8120/test28          not ok
    location ^~ /test4 {
        # 可通過檢視響應頭來判斷是否成功返回
        add_header X-Config E;
        proxy_pass http://api:3000;
    }
}

location 優先順序驗證

在我配置檔案中,以 order 打頭來命名所有優先順序驗證的 nginx 配置,總共有四個配置檔案,詳見 docker-compose

此處僅僅以 order1 為例進行驗證,配置如下:

# 以下配置,訪問以下連結,其 X-Config 為多少
#
# http://localhost:8210/shanyue,為 B,若都是字首匹配,則找到最長匹配的 location

server {
    root   /usr/share/nginx/html;

    # 主要是為了 shanyue 該路徑,因為沒有字尾名,無法確認其 content-type,會自動下載
    # 因此這裡採用 text/plain,則不會自動下載
    default_type text/plain;

    location ^~ /shan {
        add_header X-Config A;
    }

    location ^~ /shanyue {
        add_header X-Config B;
    }
}

啟動服務:

$ docker-compose up order1

curl 驗證:

當然也可以通過瀏覽器控制檯網路皮膚驗證,由於此處只需要驗證響應頭,則我們通過 curl --head 只傳送 head 請求即可。

# 檢視其 X-Config 為 B
$ curl --head http://localhost:8210/shanyue
HTTP/1.1 200 OK
Server: nginx/1.21.4
Date: Fri, 03 Jun 2022 10:15:11 GMT
Content-Type: text/plain
Content-Length: 15
Last-Modified: Thu, 02 Jun 2022 12:44:23 GMT
Connection: keep-alive
ETag: "6298b0a7-f"
X-Config: B
Accept-Ranges: bytes

proxy_pass

proxy_pass 反向代理,也是 nginx 最重要的內容,這也是常用的解決跨域的問題。

當使用 proxy_pass 代理路徑時,有兩種情況

  1. 代理伺服器地址不含 URI,則此時客戶端請求路徑與代理伺服器路徑相同。強烈建議這種方式
  2. 代理伺服器地址含 URI,則此時客戶端請求路徑匹配 location,並將其 location 後的路徑附在代理伺服器地址後。
# 不含 URI
proxy_pass http://api:3000;

# 含 URI
proxy_pass http://api:3000/;
proxy_pass http://api:3000/api;
proxy_pass http://api:3000/api/;

再舉一個例子:

  1. 訪問 http://localhost:8300/api3/hello,與以下路徑匹配成功
  2. proxy_pass 附有 URI
  3. 匹配路徑後多餘的路徑為 /hello,將其附在 proxy_pass 之後,得 http://api:3000/hello/hello
location /api3 {
    add_header X-Config C;

    # http://localhost:8300/api3/hello -> proxy:3000/hello/hello
    proxy_pass http://api:3000/hello;
}

有點拗口,在我們試驗環境有多個示例,使用以下程式碼啟動可反覆測試:

$ docker-compose up proxy api

由於 proxy_pass 所代理的服務為 whoami,可列印出真實請求路徑,可根據此進行測試

server {
    listen       80;
    server_name  localhost;

    root   /usr/share/nginx/html;
    index  index.html index.htm;

    # 建議使用此種 proxy_pass 不加 URI 的寫法,原樣路徑即可
    # http://localhost:8300/api1/hello -> proxy:3000/api1/hello
    location /api1 {
        # 可通過檢視響應頭來判斷是否成功返回
        add_header X-Config A;
        proxy_pass http://api:3000;
    }

    # http://localhost:8300/api2/hello -> proxy:3000/hello
    location /api2/ {
        add_header X-Config B;
        proxy_pass http://api:3000/;
    }

    # http://localhost:8300/api3/hello -> proxy:3000/hello/hello
    location /api3 {
        add_header X-Config C;
        proxy_pass http://api:3000/hello;
    }

    # http://localhost:8300/api4/hello -> proxy:3000//hello
    location /api4 {
        add_header X-Config D;
        proxy_pass http://api:3000/;
    }
}

add_header

控制響應頭。

由於很多特性都是通過響應頭控制,因此基於此指令可做很多事情,比如:

  1. Cache
  2. CORS
  3. HSTS
  4. CSP
  5. ...

Cache

location /static {
    add_header Cache-Control max-age=31536000;
}

CORS

location /api {
    add_header Access-Control-Allow-Origin *;
}

HSTS

location / {
    listen 443 ssl;

    add_header Strict-Transport-Security max-age=7200;
}

CSP

location / {
    add_header Content-Security-Policy "default-src 'self';";
}

作業

  • 初階: 基於 docker 學習 nginx 配置,並可配置 index.html 強快取 60s 時間
  • 中階: 如何使用 nginx 與 whoami 映象,模擬 502/504
  • 高階: 基於 docker 學習 nginx 配置,並可配置 gzip/brotli
  • 面試: brotli/gzip 有何區別

相關文章