做运维这些年,Docker 和 Nginx 这两个东西我几乎天天都在用。出去面试也好,面试别人也好,这两个工具几乎是绕不开的必考点。很多人会说“我会用 Docker 跑个容器”“我会配 Nginx 反代”,但一问到“容器和虚拟机到底差在哪”“Nginx 凭什么能抗住高并发”这类核心原理问题,就聊不下去了。其实是缺一套从底层原理往上长的知识体系,而不是缺命令。
这篇文章我想用一次从头到尾的扫盲式梳理,把环境隔离、镜像分层、反向代理、负载均衡这些核心概念,全部串到 Docker + Nginx 这套组合拳里。无论是准备运维面试、考研复试、还是单纯想搞懂自己服务器上跑的东西是怎么work的,这篇文章都能帮你把地基打牢。我把话放前面:看完这篇文章,你不仅知道怎么配,还能讲清楚为什么这么配。
1. 环境隔离:Docker 到底在隔离什么
1.1 容器和虚拟机,差的不是一层壳
很多人第一次接触 Docker 的时候都会有一个疑问:它和 VMware、VirtualBox 这些虚拟机有什么区别?我当年也绕了很久。简单说,虚拟机是硬件级隔离,虚拟出一个完整的操作系统,包括内核;而 Docker 容器是进程级隔离,它不虚拟硬件,直接复用宿主机的内核,只是在用户空间上做了一层隔离。
打个生活化的比方:虚拟机就像你租了一整栋楼,每户都是独立的水电、独立的墙体,甚至能自己装修成不同风格(装不同操作系统,Windows、Linux 随便来);而容器更像合租公寓,大家都共用一栋楼的水管电路(宿主机内核),但每个房间有独立的门锁和卫生间(命名空间隔离),公共交通和公共区域有人管着分配(控制组)。
因为容器共享宿主机内核,所以它有几个非常明显的特点:启动速度快到秒级、资源占用极低(MB 级)、一台物理机可以同时运行几十上百个容器。但也因此带来一个限制:容器里的进程直接面对宿主机内核,不能运行与宿主机不同内核版本的程序。比如你在 Linux 服务器上跑 Linux 容器没问题,但想在 Linux 容器里跑 Windows 程序,那是做不到的。
1.2 命名空间和控制组,隔离的两把钥匙
Docker 容器能实现“看起来像一台独立服务器”的效果,靠的是两个 Linux 内核特性:Namespace(命名空间)和Cgroups(控制组)。
Namespace 负责“看得见”什么,Cgroups 负责“用得了多少”。
我更愿意用一个比“合租公寓”更形象的例子来理解:想象你在一个公寓里住,你的房间有独立的锁(PID Namespace,让你只能看到自己的进程)、独立的网络接口(Network Namespace,让你有自己的 IP 和端口)、独立的根目录(Mount Namespace,让你访问自己的文件系统)。实际上你还是住在公寓楼里,但你看不见其他住户,你的网络也是独立的,这样就实现了“隔离”。
具体来说,Docker 使用的 Namespace 主要有这么几类:
| Namespace 类型 | 作用 |
|---|---|
| PID Namespace | 隔离进程编号,容器内第一个进程 PID 是 1,看不到宿主机其他进程 |
| Network Namespace | 隔离网络栈,每个容器有自己的 IP、端口、路由表、防火墙规则 |
| Mount Namespace | 隔离文件系统挂载点,容器有自己独立的根目录 |
| UTS Namespace | 隔离主机名和域名,容器内 hostname 是独立的 |
| IPC Namespace | 隔离进程间通信资源,比如消息队列、共享内存 |
| User Namespace | 隔离用户和用户组 ID,让容器内 root 权限受限 |
Cgroups 则是给容器设“使用上限”。比如一个容器最多用 1 核 CPU、512MB 内存,这是通过 Cgroups 的 CPU 子系统、内存子系统、IO 子系统来实现的。当容器里的程序想“放飞自我”吃满所有内存时,Cgroups 会先把它卡住,防止把整个宿主机搞挂。这也是为什么 Docker 比裸机跑进程更稳定的原因之一——它天然给资源上了保险丝。
1.3 镜像层、写时复制和联合文件系统
Docker 的镜像和容器,很多人分不清。我打个比方:镜像就是做蛋糕的模具,容器就是脱模之后的那个蛋糕。你可以用一个模具做很多个蛋糕,每个蛋糕是独立的,你可以在蛋糕上添奶油、加水果(修改容器内容),模具本身不会被破坏。
镜像之所以能反复创建容器,核心在于分层存储 + 写时复制。
Docker 镜像是由一层一层的只读文件系统叠加而成的。每一层对应 Dockerfile 里的一条指令。比如 FROM nginx:latest 一层、COPY index.html /usr/share/nginx/html/ 一层、RUN apt-get install xxx 又一层。这些层是共享的,多个镜像可以共用底层的基础层。
当你用镜像创建容器时,Docker 不会把整个镜像复制一份,而是在最顶端加一个可写层(容器层)。你在容器里对文件的任何修改,都会写在这个可写层里,底层镜像完全不受影响。这就是“写时复制”的威力。
下载镜像慢,是每个新手都会遇到的头号痛点。我在后面 4.2 节给出了详细的加速方案,这里先记住一个概念:Docker Hub 的镜像仓库很多在国外,国内直连速度感人,需要配置镜像加速器,这也是我自己踩了无数坑之后摸索出来的经验。
2. Nginx 核心原理:反向代理为什么是它的主场
2.1 事件驱动模型和 worker 进程机制
Nginx 之所以能成为高性能服务器的代表,核心在于它的事件驱动模型。传统的 Apache 服务器,一个请求来了往往要占用一个进程或线程去处理,这种方式在高并发下会让内存和 CPU 迅速被吃光,因为线程上下文切换的成本很高。
Nginx 采用的是master-worker 多进程 + 事件驱动(epoll)架构。启动 Nginx 后,你会看到有一个 master 进程,它是老板,负责读配置文件、管理 worker 进程;下面还有好几个 worker 进程,它们是干活的人,每个 worker 进程都是一个独立的事件循环。
当请求到达时,worker 进程通过 epoll 机制同时监听成千上万个连接,哪个连接有数据来了就处理谁,没有数据就继续等待。这就像餐厅里一个服务员可以同时伺候二十桌客人:不用每桌一直站着等上菜,而是哪桌按铃了(事件触发)才过去服务。这种机制下,Nginx 能用很小的内存轻松扛住几万甚至几十万的并发连接。
我记得自己第一次压测 Nginx 的时候,看到它轻轻松松扛住了 5 万并发连接,而那个后端 Java 进程早已被压垮,我才真正理解了它为什么叫“反向代理之王”。
2.2 配置文件四大块和典型骨架
Nginx 的配置文件(通常是 nginx.conf)看起来密密麻麻,其实只要抓住结构,就很好懂。它主要由四大块组成:
- main(全局块):设置 worker 进程数、运行用户、PID 文件位置、日志级别等。
- events 块:配置事件驱动模型,比如每个 worker 进程维护的连接数上限。
- http 块:HTTP 服务器全局配置,比如 MIME 类型、默认日志格式、负载均衡 upstream 定义。
- server 块:虚拟主机的配置,一个 http 块里可以有多个 server,每个 server 就是一个站点。
- location 块:server 内部的路由匹配规则,决定了不同 URL 路径怎么处理。
我贴一份最常用的骨架配置,把注释写清楚,新手照着看能省不少时间:
worker_processes auto; # 自动适配 CPU 核心数,一般设成核心数 events { worker_connections 1024; # 每个 worker 能同时维持的最大连接数 } http { include mime.types; # 内容类型映射表 default_type application/octet-stream; # 反向代理 + 负载均衡的服务器组 upstream backend_servers { server 172.18.0.2:8080 weight=3; # 多台后端可以在这里扩展 server 172.18.0.3:8080 weight=1; # weight 越大,分配的请求越多 } server { listen 80; server_name example.com; # 静态文件直接由 Nginx 返回 location / { root /usr/share/nginx/html; index index.html; } # 动态请求转发给后端应用 location /api/ { proxy_pass http://backend_servers; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } }2.3 location 匹配规则,别被优先级坑了
location 的匹配规则经常是面试题里的重灾区,也是实际排错时的隐形炸弹。它有好几种写法,我列一个优先级从高到低的表格,这个必须刻在脑子里:
| 匹配方式 | 示例 | 优先级 |
|---|---|---|
| 精确匹配 | location = /api/login | 最高 |
| 前缀匹配(带 ^~) | location ^~ /api/ | 高 |
| 正则匹配 | location ~ \.php$ | 中 |
| 普通前缀匹配 | location /api | 低 |
| 默认匹配 | location / | 最低 |
有几点我反复踩过的坑,必须强调:
- 正则匹配区分大小写 ~ 和不区分大小写 ~*,如果写错,可能匹配不到 PHP 请求或者匹配到错误的后端。
- **优先匹配优先级最高的,而不是匹配到第一个就停。**绝大多数新手以为 location /api/ 能挡住所有 /api 开头的请求,但如果你后面写了正则
location ~ \.json$,请求/api/config.json会被正则“截胡”走,因为正则匹配优先级在普通前缀之上。 - 我看到有些生产事故就是这么引发的:前端开发改了接口路径,后端运维调了半天,最后才发现是 location 优先级的问题。
2.4 反向代理和负载均衡,Nginx 的左膀右臂
反向代理这个词听起来高深,其实没有那么玄。它本质上是把客户端的请求转发给后端服务器,再把后端返回的结果传回给客户端。如果想不通为什么 Nginx 能做反向代理,试着这样理解:它就是餐厅的前台,客人不用直接找后厨点菜,把菜单递给前台,前台再把菜给后厨做。
反向代理解决了几个非常实际的问题:
- 隐藏真实后端服务器 IP,如果你有多台后端服务器,客户端只知道 Nginx 的 IP,不知道后端地址,安全性更好。
- 统一入口做负载均衡,这是我最常用的场景。当业务量上来,一台后端忙不过来,就可以再部署几台,通过 upstream 块配置多个后端地址,Nginx 会根据负载均衡策略把请求分发到不同后端。
- 承载静态资源,动态请求转发给后端,静态文件直接由 Nginx 返回,后端应用压力能减轻一大截。
负载均衡策略也有几种常用的,我简单列一下:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 轮询(默认) | 每个请求按顺序分配到不同后端 | 后端配置基本相同 |
| weight 权重 | 按权重分配,性能好的机器配更大的 weight | 后端机器性能不一致 |
| ip_hash | 按客户端 IP 的哈希取模,同一个 IP 固定在同一个后端 | 需要会话保持(但更推荐用 Redis 做会话共享) |
| least_conn | 总是分给当前连接数最少的后端 | 长连接多、请求处理时间差异大 |
3. Docker + Nginx 组合实操
3.1 快速跑一个 Nginx 容器并挂载网站目录
先来一个最基础但也最经典的实战:用 Docker 启动 Nginx,并把宿主机上的网站目录挂载进容器。为什么用挂载而不是直接把文件 copy 进容器?因为生产环境中你需要频繁更新代码,挂载目录的话,宿主机改文件容器立刻生效,不需要重新 build 镜像。
命令长这样:
docker run -d \ --name my-nginx \ -p 80:80 \ -v /home/www/my-site:/usr/share/nginx/html:ro \ -v /home/www/nginx-conf:/etc/nginx/conf.d:ro \ nginx:latest我拆解一下每个参数,很多新手只知道照抄,不知道丢了哪块就会出问题:
-d:后台运行容器。--name my-nginx:给容器起个名字,后面docker exec或者docker stop都要用这个名字。-p 80:80:端口映射。宿主机 80 端口映射到容器的 80 端口。如果你把冒号前的 80 改成 8080,就是访问宿主机 8080 端口时映射到容器 80。-v /home/www/my-site:/usr/share/nginx/html:ro:目录挂载。冒号前是宿主机路径,冒号后是容器内路径,:ro表示只读,防止容器内误改宿主机文件。- 第二个挂载把自定义的 Nginx 配置目录挂到
/etc/nginx/conf.d,这样你新增站点配置不用进容器里改文件。
启动后访问http://服务器IP/,就能看到 Nginx 默认欢迎页或你自己的网站了。
3.2 用 Nginx 容器反向代理宿主机的后端服务
场景升级。现在宿主机 8080 端口跑着一个 Spring Boot 或 Java 服务,你希望通过 Nginx 的 80 端口对外提供访问,并且把/api/开头的请求转发给这个后端服务。
这里有一个隐蔽的坑:容器里的 Nginx 访问宿主机服务,不能直接写127.0.0.1:8080。因为容器有自己独立的网络栈,容器里的 127.0.0.1 指向容器自己,而不是宿主机。
正确的写法是:
- 在 Linux 上,可以通过
172.17.0.1(Docker 默认网桥网关)来访问宿主机,或者在启动容器时加参数--add-host=host.docker.internal:host-gateway,然后配置里写proxy_pass http://host.docker.internal:8080;。 - 在 Windows 和 macOS 的 Docker Desktop 中,
host.docker.internal已经内置了,直接用。
完整的配置我放在/home/www/nginx-conf/site.conf:
server { listen 80; server_name myapp.example.com; location /api/ { proxy_pass http://host.docker.internal:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }这里有一个细节我要专门讲一下:proxy_pass后面的 URI 是否带斜杠,会导致转发行为完全不一样。
- 如果写
proxy_pass http://host.docker.internal:8080;(没有斜杠),请求/api/login会完整转发给后端,后端收到的路径还是/api/login。 - 如果写
proxy_pass http://host.docker.internal:8080/;(带斜杠),则/api/这一部分会被替换成/,后端收到的路径是/login。
这个点特别容易出问题。有一次我排查了整整一下午,看到一个接口老是 404,最后发现就是proxy_pass的斜杠写错了。所以读配置的时候一定要留意这一点。
3.3 用 Docker Compose 编排 Nginx 和多个应用容器
如果说上面是单兵作战,那 Docker Compose 就是多容器集团军作战的模式。当你需要同时跑多个服务,并且还要让它们之间互相通信时,用docker run一条条敲命令就不太合适了。这个场景在实际生产中很常见,也正好是目前招聘市场非常看重的能力。
假设这样一个架构:一个 Vue/React 前端静态资源由 Nginx 提供,后端有两个 API 服务,Nginx 需要把请求负载均衡到这两个后端服务。我直接用docker-compose.yml来定义整个系统:
version: "3.8" services: # 后端服务一 backend-1: image: my-app:latest container_name: backend-1 environment: - DB_HOST=mysql - DB_PORT=3306 ports: - "8081:8080" networks: - app-net restart: always # 后端服务二 backend-2: image: my-app:latest container_name: backend-2 environment: - DB_HOST=mysql - DB_PORT=3306 ports: - "8082:8080" networks: - app-net restart: always # Nginx 反向代理 + 静态资源 nginx: image: nginx:latest container_name: nginx ports: - "80:80" - "443:443" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./www:/usr/share/nginx/html:ro - ./ssl:/etc/nginx/ssl:ro depends_on: - backend-1 - backend-2 networks: - app-net restart: always networks: app-net: driver: bridge核心是networks: app-net。在同一个自定义网络里的容器,可以通过**服务名(service name)**互相访问。比如 Nginx 容器里配置反向代理,直接写:
upstream backend_servers { server backend-1:8080 weight=1; server backend-2:8080 weight=1; }注意这里的backend-1和backend-2不是 IP 地址,而是 Docker Compose 里的服务名,Docker 内置的 DNS 会自动把这些服务名解析成对应的容器 IP。这比写死 IP 靠谱多了,因为容器 IP 会变动,服务名不会变。
启动命令是docker compose up -d,查看日志是docker compose logs -f,停掉整个环境是docker compose down。这套流程熟悉之后,部署一套完整环境基本就是几秒钟的事。
3.4 前端项目部署、history 路由与 SSL 配置
前端单页应用(SPA)和 Docker + Nginx 组合是绝配。前端代码 build 完就是一堆静态文件,丢进 Nginx 的 html 目录就完事。但有两个大坑必须在这里讲清楚。
坑一:history 路由模式。如果你用 HTML5 history 模式(也就是 URL 里没有#的那种),刷新某个子页面时,Nginx 会去找/user/123这个路径,结果发现没有这个文件,返回 404。解决方法是配置try_files:
location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }这行的意思是:先尝试访问原始请求路径;找不到就尝试将路径作为目录访问;还找不到就统一返回 index.html,由前端路由接管。
坑二:SSL 证书配置。证书文件放在容器里之后,要保证 Nginx 能读到。通常我会建一个 ssl 目录,把证书.crt(或.pem)和私钥.key放进去,然后挂载到容器:
server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } } # HTTP 自动跳转 HTTPS server { listen 80; server_name example.com; return 301 https://$host$request_uri; }顺便说一下私钥文件格式需要注意的事情。搜索热词里有“nginx 支持哪种类型的私钥”,这里统一解答:Nginx 支持的私钥格式是 PEM 格式,文件内容通常是-----BEGIN PRIVATE KEY-----或-----BEGIN RSA PRIVATE KEY-----开头的文本。如果你的私钥是 DER、PKCS12 或者 JKS 格式,需要先用 OpenSSL 转换成 PEM 格式再放进 Nginx。如果你从证书供应商那儿下载的是.pfx或.p12文件,需要用这条命令转换:
openssl pkcs12 -in yourfile.pfx -out nginx.pem -nodes证书链不完整也是个常见问题,表现为浏览器提示“证书不受信任”。解决方法是把中间证书拼到主证书后面,顺序是主证书在前、中间证书在后。
4. 运维面试真题拆解与避坑指南
4.1 高频面试八股参考答案
写到这里,我把面试中最容易出现的高频题整理了一个答题模板,都是我在面试别人时经常问的,也是被问过的。每个问题我给一个能拿分的回答思路,不讲虚的:
Q1:Docker 镜像和容器有什么区别?
镜像是一个只读的模板,包含了运行应用所需的代码、运行时、系统库和配置;容器是镜像运行时的实例,相当于给镜像加了一个可写层。一个镜像可以创建多个容器,容器之间有隔离。修改容器里的内容不会影响镜像,但可以用docker commit把修改固化成一个新镜像。
Q2:Docker 如何实现环境隔离?
通过 Linux 的 Namespace 实现视图隔离(进程、网络、文件系统、主机名等),通过 Cgroups 实现资源限制(CPU、内存、IO)。它和虚拟机的区别在于,容器共享宿主机内核,而虚拟机有独立的完整内核。
Q3:反向代理和正向代理的区别是什么?
正向代理是为客户端代理,隐藏的是客户端身份,比如内网上网要通过代理服务器;反向代理是为服务器代理,隐藏的是后端服务器身份,客户端只知道反向代理的地址,不知道后端的真实地址。Nginx 主要作为反向代理使用,比如把/api/请求转发给后端应用。
Q4:为什么 Nginx 能支撑高并发?
Nginx 采用多进程 + 事件驱动模型。每个 worker 进程通过 epoll 监听成千上万个连接,有事件才处理,不阻塞等待。与 Apache 的进程/线程模型相比,它避免了每请求一进程的上下文切换开销,内存占用小,并发能力强。
Q5:Nginx 有哪些负载均衡策略?
轮询、加权轮询、ip_hash、least_conn 等。轮询适合后端性能一致;加权轮询适合后端机器配置有差异;ip_hash 可以让同一个客户端 IP 总是请求同一个后端,适合需要本地会话但没做共享 Session 的旧系统。
Q6:proxy_pass不带斜杠和带斜杠有什么区别?
这个我在 3.2 节已经详细讲过。不带斜杠是完整路径转发,带斜杠会替换掉匹配的 location 前缀。回答这个问题时如果能现场举一个例子,面试官会认为你是真在自己动手配过的。
Q7:如何查看容器日志?如何进入容器内部?
docker logs -f 容器名实时查看容器日志;docker exec -it 容器名 bash进入容器执行命令。容器里没有 bash 就用 sh。
Q8:容器运行后如何修改 Nginx 配置?
推荐使用挂载配置文件的方式,宿主机改完配置后执行docker exec 容器名 nginx -s reload热加载。如果没有挂载,也可以先docker cp把配置复制进容器再 reload,但重启容器后配置会丢失,所以生产方式还是得挂载。
以上这些如果都能脱口而出,面试的口碑基本就稳了。
4.2 镜像下载慢、403、502、404 等常见问题排查
搜热词榜单里出现了“docker镜像下载慢”“prowlaar反向代理403”“nginx反向代理403”这几项,说明这些确实是高频困扰。我把它们放到一起彻底讲一下。
镜像下载慢怎么破?
最有效的办法是配置镜像加速器。在/etc/docker/daemon.json中,可以把镜像加速地址配置到registry-mirrors下。国内各大云厂商都有自己的容器镜像加速服务,每个账号会分配专属加速地址,申请后配置进去就行。配置完记得执行systemctl restart docker重启 Docker 服务,让配置生效。
{ "registry-mirrors": ["https://你的专属镜像加速地址"] }如果你找不到可用的镜像加速地址,还有一个应急方案:找一台能流畅访问 Docker Hub 的跳板机,在上面执行docker pull nginx,然后使用docker save -o nginx.tar nginx:latest导出镜像,再传输到目标服务器用docker load -i nginx.tar导入。这种方式虽然笨,但在极端情况下能救命。
反向代理 403 是怎么来的?
403 最常见的原因有三个:
第一,目录权限不对。Nginx worker 进程对网站目录没有读取权限,特别是当你把/home/xxx这种目录挂载给 Nginx 容器时,而宿主机上目录的属主和权限是700,Nginx 的 worker 进程跑在容器内部,UID 通常是 101(nginx 用户),读不到宿主机属主家的目录。解决方法是给网站目录设置755或静默 755,或者把目录属主改成容器内 Nginx 的 UID。
第二,索引文件不存在。访问/时,Nginx 自动去找 index.html 或 index.php,找不到就会返回 403 Forbidden。解决方法是检查 root 目录下是否有正确的索引文件。
第三,配置了deny规则或授权问题。有些配置里加了 IP 黑名单、allow/deny规则,如果客户端 IP 不在允许范围,也会 403。
502 Bad Gateway 怎么排查?
502 表示 Nginx 成功接收了请求,但后端没有给出有效响应。排查路径很固定:先看后端进程是否存活;再看 Nginx 配置里的proxy_pass地址是否能连通(用 curl 从 Nginx 容器里测试);再看后端服务的日志。我遇到最多的情况是:后端服务换了端口、容器重启后 IP 变了,但 Nginx 配置还写死旧 IP。如果你用了 Docker Compose 的服务名访问,这个问题就基本能避免。
4.3 一份实用的容器运维工具箱
最后这部分已经不是面试内容了,纯粹是我个人在实际运维中沉淀下来的经验。如果你打算在生产环境真正使用 Docker + Nginx,下面这些细节一个都别忽略。
日志处理。Nginx 的访问日志默认直接输出到标准输出(stdout),可以通过docker logs看到。但为了持久化,我习惯把日志挂载到宿主机目录:
-v /var/log/nginx:/var/log/nginx同时用 logrotate 做日志轮转,不然时间长了日志文件会撑爆磁盘。Nginx 镜像本身自带 logrotate 配置,但如果你把日志挂载到宿主机,宿主机的 logrotate 也要同步配置,否则这段时间的日志没人管。
健康检查与自愈。容器被强制杀掉后,如果启动时没有加--restart always,就不会自动拉起。生产环境我的统一标准是--restart always或者 compose 里的restart: always。另外可以给 Nginx 容器配置健康检查,用 curl 探测默认网站是否返回 200:
healthcheck: test: ["CMD", "curl", "-f", "http://localhost/"] interval: 30s timeout: 5s retries: 3资源限制。在容器启动时务必加资源限制,防止某个异常容器吃满宿主机所有 CPU 和内存。Docker run 的常用参数是:
docker run -d \ --name my-nginx \ --cpus="1.0" \ --memory="512m" \ nginx:latest时区问题。容器默认时区是 UTC(世界标准时间),如果你发现 Nginx 日志里的时间和本地时间差了 8 个小时,那不是 Nginx 的 bug,而是容器时区没有设置。解决方法是挂载/etc/localtime或通过环境变量TZ=Asia/Shanghai设置时区。
多项目目录挂载。热词里提到“docker安装nginx并挂载多个项目目录”,这个很实用。如果一台服务器上跑了好几个前端项目,更优雅的方式不是一个目录挂载整个 Nginx 的 html,而是分别挂载到不同目录,再用多个 server 块路由。比如:
-v /home/www/project-a:/usr/share/nginx/html/a:ro -v /home/www/project-b:/usr/share/nginx/html/b:ro然后在 Nginx 配置里分别配置location /a/ { alias /usr/share/nginx/html/a/; }和location /b/ { alias /usr/share/nginx/html/b/; }。
还有一个细节:root和alias的区别一定要搞懂。root会把 location 的路径拼接到 root 后面找文件,alias不会拼接,直接把请求路径替换成 alias 指定的路径。如果配置成root /usr/share/nginx/html/a/,请求/a/index.html时实际找的是/usr/share/nginx/html/a/a/index.html,这会直接 404。很多人一上来就全用 root,结果踩了这个坑。
说实话,Docker 和 Nginx 这两个东西单独拿出来任何一个,都能写一本书。实际上也确实有书。但运维面试的核心从来不在于你背了多少命令,而在于你能不能把一个看似简单的概念讲透,让你部署的东西在出了问题时知道自己应该如何排查。我面试过不少做了两三年运维的候选人,最大的差距往往不是工具熟练度,而是对底层机制的理解深度。
如果你准备面试,我最后给一个建议:不要只背命令,而是要亲手搭一套环境,把容器网络、端口映射、数据卷挂载、反向代理、负载均衡这几个点全部玩一遍,遇到 502、403、404 就自己排查一遍。这个过程远比你刷一百篇博客有用。等你亲手把这些问题都解决过一遍之后,面试官问什么,你心里都是有底的。