# 通过docker高效学习nginx配置
推荐一种高效学习nginx的方法:[在本地使用nginx镜像并挂在nginx配置启动容器]
通过以下docker-compose可秒级验证nginx配置,无疑是学习nginx的绝佳利器
我将所有关于 nginx 的配置放置在 simple-deploy1 (opens new window),并且每一份配置对应 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
- root: 静态资源的根目录。见文档 (opens new window)
- index: 当请求路径以/结尾时,则自动寻找该路径下的index文件。见文档 (opens new window)
root与index为前端部署的基础,在默认情况下root为/user/share/nginx/html,因此我们部署前端时,往往将构建后的静态资源目录挂载到该地址
server {
listen 80;
server_name localhost;
root /user/share/nginx/html;
index index.html index.htm;
}
# location
location 用以匹配路由,配置语法如下
location [ = | ~ | ~* | ^~ ] uri { ... }
其中uri前可提供以下修饰符
- = 精准匹配。优先级最高
- ^~ 前缀匹配,优先级其次
- ~ 正则匹配,优先级再次(~* 只是不区分大小写,不单列)
- / 通用匹配,优先级再次
为了验证锁匹配的 location,我会在以下示例中添加一个自定义响应头 X-Cconfig, 可通过浏览器控制台网络面板验证其响应头
add_header X-Cconfig B;
# location 修饰符验证
对于此四种修饰符可以在我的nginx下进行验证
由于此处使用了proxy_pass,因此需要locaton2,api两个服务一起启动,在location2服务中,可直接通过service名称作为hostname即http://api:3000访问api服务
而 api 服务,为我自己写的一个 whoami 服务,用以打印出请求路径等信息,详见 shfshanyue/whoami2 (opens new window)。
$ docker-compose up location2 api
以下是关于验证location的配置文件,详见 (opens new window)
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配置,此处仅仅以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代理路径时,有两种情况
- 代理服务器地址不含URI,则此时客户端请求路径与代理服务器路径相同。强烈建议这种方式
- 代理服务器地址含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/;
在举一个例子
- 访问http://localhost:8300/api3/hello,与以下路径匹配成功
- proxy_pass 附有URI
- 匹配路径后对于的路径为/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
控制响应头
由于很多特性都是通过响应头控制,因此基于此命令可做很多事情,比如
- Cache
- CORS
- HSTS
- CSP
- ...
# 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 compose 动态设置端口
如果使用 docker Compose,可以结合bash脚本和环境变量来实现动态端口分配
示例 docker-compose.yml
version: '3.8'
services:
my_service:
image: my_image
ports:
- "${SERVICE_PORT}:80"
脚本启动
#!/bin/bash
# 基础端口号
BASE_PORT=8000
# 最大尝试端口数
MAX_PORT=8100
# 查找可用端口
find_available_port() {
PORT=$BASE_PORT
while [ $PORT -lt $MAX_PORT ]; do
if ! lsof -i:$PORT > /dev/null; then
echo $PORT
return
fi
PORT=$((PORT + 1))
done
echo "No available port found!" >&2
exit 1
}
# 获取可用端口
AVAILABLE_PORT=$(find_available_port)
# 设置环境变量并启动 Docker Compose
export SERVICE_PORT=$AVAILABLE_PORT
echo "Starting service on port $SERVICE_PORT"
docker-compose up -d
说明
- docker-compose.yml 文件中使用 ${SERVICE_PORT}占位符,通过环境变量动态设置端口
- 脚本运行时找到可用端口后,通过 export 将端口注入环境变量。
# Docker容器内自适应端口
在某些场景中,你可以在容器内部运动服务时,允许服务动态绑定到一个随机端口。然后通过Docker 的 -p 参数随机映射到主机的可用端口
启动命令
docker run -d -P --name my_container my_image
查看分配的端口
使用一下命令查看随机映射的端口
docker port my_container
这种方式适合测试环境,但在生成环境中通常需要明确指定端口号
# 让随机端口对外暴露为80
如果你需要对外固定暴露 80 端口,但容器内部扔随机分配,则需要宿主机的Nginx 或 iptables来实现端口转发
# 使用nginx反向代理
安装nginx
sudo apt update & sudo apt install nginx -y配置nginx反向代理
# /etc/nginx/sites-available/default server { listen 80; server_name _; location / { proxy_pass http://127.0.0.1:<RANDOM_PORT>; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }重启nginx
sudo systemctl restart nginx
# demo
docker run --name mynginx -p 8080:80 -v /Users/xuzhe/Desktop/website/public/docker/nginx/conf.d/:/etc/nginx/conf.d/ -v /Users/xuzhe/Desktop/website/public/docker/nginx/www/:/var/www/html/ -d nginx
# 资料
← demo 写给前端的Nginx入门指南 →