前言
这个博客建立之初,为了图方便以及便宜买的是阿里云的服务器。但随着需求的增加才知道阿里云安全组这么个东西,就是说服务器默认只开放22(SSH), 3389(RDP), 80(HTTP)和443(HTTPS)这几个端口。如果想要在服务器上整点别的活,就需要设置安全组放行,或者使用将要提到的端口复用。
Docker+Nginx初步
Docker安装
我使用的是
$ cat /proc/version
Linux version 5.15.0-86-generic (buildd@lcy02-amd64-086) (gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #96-Ubuntu SMP Wed Sep 20 08:23:49 UTC 2023
具体操作参考runoob和aliyun,大致如下(分别为更新apt,下载依赖,设置仓库和安装docker)
sudo apt-get update
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common
sudo add-apt-repository \
"deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/ \
$(lsb_release -cs) \
stable"
sudo apt-get install docker-ce docker-ce-cli containerd.io
之后pull
下载Nginx
以及其他服务的镜像。
docker-compose+Nginx配置
在进行反向代理之前,先尝试让Nginx
正常工作。和直接使用Nginx
相比,容器内部和服务器在网络和文件上都是隔离的,因此需要
- 通过端口映射让
Nginx
监听服务器端口,包括80和443端口 - 通过数据卷挂载让
Nginx
能够访问服务器文件,包括配置文件、博客静态文件和ssl
证书
docker-compose vs dockerfile
网上的一些教程,如
- https://www.docker.com/blog/how-to-use-the-official-nginx-docker-image/
- https://medium.com/@mefengl/step-by-step-guide-to-deploying-nginx-using-docker-on-ubuntu-df0f520bcf2d
- https://www.nginx.com/blog/deploying-nginx-nginx-plus-docker/#Working-with-the-NGINX-Docker-Container
都是用的dockerfile
,这种方式仅适用于构建单个镜像,而且只能匿名挂载,很不方便。就本文的目的而言其实并不需要构建镜像,直接使用docker-compose
创建容器就完事了。而且试了下dockerfile
为空甚至删掉dockerfile
也没什么问题,因此后续主要使用docker-compose
。
另外如果按照网上的一些教程(如runoob)使用docker-compose
调用dockerfile
,此时会先执行dockerfile
中的语句再执行docker-compose
中的设置,即后者可能会覆盖前者。
数据卷挂载
先在服务器中选定文件夹作为根目录,在根目录创建docker-compose.yml
以及nginx
文件夹。再在nginx
文件夹中创建conf
,public
和ssl
文件夹分别用于存放配置文件、博客静态文件和ssl
证书。
由于Nginx
镜像是通用的,可以知道在容器内部
- 配置文件位于
/etc/nginx/conf.d
- 静态文件位于
/usr/share/nginx/html
配置文件如下
./docker-compose.yml
:
version: '2'
services:
nginx:
build:
context: ./nginx
image: nginx:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf:/etc/nginx/conf.d
- ./nginx/public:/usr/share/nginx/html
- ./nginx/ssl:/path/to/ssl
restart: always
./nginx/conf/domain_name.conf
:
server {
listen 443 ssl;
server_name domain_name;
root /usr/share/nginx/html;
index index.html;
ssl_certificate /path/to/ssl/fullchain.pem;
ssl_certificate_key /path/to/ssl/privkey.pem;
}
server {
listen 80;
server_name domain_name;
location / {
rewrite ^(.*)$ https://$host$1 permanent;
}
}
另外由于ssl
证书是周期更新的,需要修改更新脚本,使得每次更新之后抄送到./nginx/ssl
15 2 * */2 * certbot renew --post-hook "cp /etc/letsencrypt/live/omnibbfb.top/fullchain.pem /path/to/nginx/ssl && cp /etc/letsencrypt/live/omnibbfb.top/privkey.pem /path/to/nginx/ssl"
最后回到根目录创建容器并查看
$ docker-compose up -d --remove-orphans
[+] Running 2/2
⠿ Network temp_default Created 0.1s
⠿ Container temp-nginx-1 Started 0.5s
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dafca187ed60 nginx:latest "/docker-entrypoint.…" 4 seconds ago Up 3 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp temp-nginx-1
一个问题
问题如下,即创建容器正常但是查看容器为空
$ docker-compose up -d --remove-orphans
[+] Running 2/2
⠿ Network temp_default Created 0.1s
⠿ Container temp-nginx-1 Started 0.5s
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
这时候可以通过
docker-compose logs
查看日志
$ docker-compose logs
temp-nginx-1 | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
temp-nginx-1 | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
temp-nginx-1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
temp-nginx-1 | 10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf is not a file or does not exist
temp-nginx-1 | /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
temp-nginx-1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
temp-nginx-1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
temp-nginx-1 | /docker-entrypoint.sh: Configuration complete; ready for start up
temp-nginx-1 | 2024/04/20 17:21:46 [emerg] 1#1: cannot load certificate "/etc/letsencrypt/live/omnibbfb.top/fullchain.pem": BIO_new_file() failed (SSL: error:80000002:system library::No such file or directory:calling fopen(/etc/letsencrypt/live/omnibbfb.top/fullchain.pem, r) error:10000080:BIO routines::no such file)
temp-nginx-1 | nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/omnibbfb.top/fullchain.pem": BIO_new_file() failed (SSL: error:80000002:system library::No such file or directory:calling fopen(/etc/letsencrypt/live/omnibbfb.top/fullchain.pem, r) error:10000080:BIO routines::no such file)
发现报错!
猜测可能是容器运行错误导致关闭,但是docker-compose
只负责创建容器,不会显示容器运行过程中的报错,导致乍一看没有什么问题。总之多看日志一定没什么坏处。
实现443端口复用
实现端口复用首先要用Nginx
实现反向代理。
正向代理和反向代理
考虑这样一个关系:客户端 $\Longleftrightarrow$ 代理 $\Longleftrightarrow$ 服务器,那么
- 帮助客户端的就是正向代理(我是客户端,我请求服务,结果是服务器不一定知道客户端)
- 帮助服务器的就是反向代理(我是服务器,我提供服务,结果是客户端不一定知道服务器)
Docker自定义网络
Nginx
反向代理服务器之后,当客户端发送请求到服务器时,Nginx
会进行分流,将请求转发到对应的后端服务上。但是Docker
容器是彼此隔离的,此时需要创建自定义网络,处于同一网络的容器可以互通。注意这里并不需要使用docker network create
创建,直接写在docker-compose.yml
里就好了,比如
networks:
nginx_net:
name: nginx_net
driver: bridge
ipam:
config:
- subnet: 192.168.0.0/16
gateway: 192.168.0.1
services:
nginx:
...
container_name: nginx
ports:
- "80:80"
- "443:443"
networks:
- nginx_net
another_service:
...
container_name: another_service
networks:
- nginx_net
请注意
gateway
前面就是没有-
的。another_service
中不需要设置端口映射,其仅在内网中与Nginx
交互。
自定义网络相关可以看这里。
Nginx配置
在配置文件中加入server
块处理其他上游服务
./nginx/conf/domain_name.conf
:
server {
listen 443 ssl;
server_name sub.server_ip;
include /etc/nginx/conf.d/ssl.conf;
resolver 127.0.0.53;
location / {
proxy_pass http://another_service:3000;
}
}
私以为这里是整个流程中逻辑上最为困难的部分了,因此需要详细说明
- 这里采用的是域名分流的方法,不同的服务使用不同的子域名,即
sub.server_ip
。Nginx
收到请求后,会查看请求的Host
头部字段,依此决定将请求路由到哪个server
块进行处理。 - 起初申请
ssl
证书时使用了--standalone
参数因此只对域名生效,子域名需要另外申请通配符ssl
证书(certbot certonly --manual -d *.domain_name
),同时修改更新脚本。 - 在内部网络中
Nginx
需要使用网络自带的DNS
解析器解析后端服务器的ip
,解析器的地址可以通过cat /etc/resolv.conf
查看。 another_service
是后端服务器的名称,别忘了在docker-compose.yml
中使用container_name
进行指定。端口是这个服务默认的监听端口(内网端口而不是服务器端口),可以通过docker ps
查看。
最后回到根目录创建网络以及容器即可
docker-compose up -d --remove-orphans
子域名解析
以上是需要在服务器上执行的全部操作,而由于使用了子域名,还需要去阿里云官网进行DNS
解析,参考子域管理,这里不再赘述。
通过路径分流
另外也可以通过路径进行分流,大致如下(来自ChatGPT
)
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /path/to/ssl/certificate.pem;
ssl_certificate_key /path/to/ssl/privatekey.pem;
location /service1/ {
proxy_pass http://service1:port/;
}
location /service2/ {
proxy_pass http://service2:port/;
}
}
$\ddot{\smile}$