如何使用Nginx、Let'sEncrypt和DockerCompose保护容器化的Node.js应用程序

来自菜鸟教程
跳转至:导航、​搜索

介绍

有多种方法可以增强 Node.js 应用程序的灵活性和安全性。 使用像 Nginx 这样的 反向代理 可以为您提供负载平衡请求、缓存静态内容和实现 传输层安全 (TLS) 的能力。 在您的服务器上启用加密的 HTTPS 可确保与您的应用程序之间的通信保持安全。

在容器上使用 TLS/SSL 实现反向代理涉及一组与直接在主机操作系统上工作不同的过程。 例如,如果您从 Let's Encrypt 获取运行在服务器上的应用程序的证书,您可以直接在主机上安装所需的软件。 容器允许您采取不同的方法。 使用 Docker Compose,您可以为您的应用程序、Web 服务器和 Certbot 客户端 创建容器,使您能够获取证书。 通过执行这些步骤,您可以利用容器化工作流的模块化和可移植性。

在本教程中,您将使用 Docker Compose 部署带有 Nginx 反向代理的 Node.js 应用程序。 您将获得与您的应用程序关联的域的 TLS/SSL 证书,并确保它从 SSL Labs 获得高安全等级。 最后,您将设置一个 cron 作业来更新您的证书,以便您的域保持安全。

先决条件

要遵循本教程,您将需要:

  • 一个 Ubuntu 18.04 服务器,一个具有 sudo 权限的非 root 用户,以及一个活动的防火墙。 有关如何设置这些的指导,请参阅此 初始服务器设置指南 。
  • Docker 和 Docker Compose 安装在您的服务器上。 有关安装 Docker 的指导,请遵循 如何在 Ubuntu 18.04 上安装和使用 Docker 的步骤 1 和 2。 有关安装 Compose 的指导,请遵循 如何在 Ubuntu 18.04 上安装 Docker Compose 的步骤 1。
  • 一个注册的域名。 本教程将自始至终使用 example.com。 您可以在 Freenom 免费获得一个,或使用您选择的域名注册商。
  • 为您的服务器设置了以下两个 DNS 记录。 您可以按照 this Introduction to DigitalOcean DNS 了解如何将它们添加到 DigitalOcean 帐户的详细信息(如果您正在使用):
    • 带有 example.com 的 A 记录指向您服务器的公共 IP 地址。
    • 带有 www.example.com 的 A 记录指向您服务器的公共 IP 地址。

第 1 步 — 克隆和测试节点应用程序

作为第一步,我们将使用 Node 应用程序代码克隆存储库,其中包括我们将用于使用 Compose 构建应用程序映像的 Dockerfile。 我们可以首先通过使用 docker 运行命令 构建和运行应用程序来测试应用程序,无需反向代理或 SSL。

在非 root 用户的主目录中,从 DigitalOcean 社区 GitHub 帐户 克隆 nodejs-image-demo 存储库。 此存储库包含 如何使用 Docker 构建 Node.js 应用程序中描述的设置中的代码。

将存储库克隆到名为 node_project 的目录中:

git clone https://github.com/do-community/nodejs-image-demo.git node_project

切换到 node_project 目录:

cd  node_project

在此目录中,有一个 Dockerfile,其中包含使用 Docker 节点:10 映像 和当前项目目录的内容构建 Node 应用程序的说明。 您可以通过键入以下内容查看 Dockerfile 的内容:

cat Dockerfile
OutputFROM node:10-alpine

RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app

WORKDIR /home/node/app

COPY package*.json ./

USER node

RUN npm install

COPY --chown=node:node . .

EXPOSE 8080

CMD [ "node", "app.js" ]

这些说明通过将项目代码从当前目录复制到容器并使用 npm install 安装依赖项来构建 Node 映像。 他们还利用 Docker 的 缓存和图像分层 ,将包含项目列出的依赖项的 package.json 和 package-lock.json 的副本与应用程序的其余部分的副本分开代码。 最后,指令指定容器将以非根 node 用户身份运行,并在应用程序代码和 node_modules 目录上设置适当的权限。

有关此 Dockerfile 和 Node 映像最佳实践的更多信息,请参阅如何使用 Docker 构建 Node.js 应用程序的 第 3 步中的完整讨论。

要在没有 SSL 的情况下测试应用程序,您可以使用 docker build 和 -t 标志构建和标记映像。 我们将图像称为 node-demo,但您可以随意将其命名为其他名称:

docker build -t node-demo .

构建过程完成后,您可以使用 docker images 列出您的图像:

docker images

您将看到以下输出,确认应用程序映像构建:

OutputREPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
node-demo           latest              23961524051d        7 seconds ago       73MB
node                10-alpine           8a752d5af4ce        3 weeks ago         70.7MB

接下来,使用 docker run 创建容器。 我们将在此命令中包含三个标志:

  • -p:这会发布容器上的端口并将其映射到我们主机上的端口。 我们将在主机上使用端口 80,但如果您有另一个进程在该端口上运行,您可以根据需要随意修改此端口。 有关其工作原理的更多信息,请参阅 Docker 文档中关于 端口绑定 的讨论。
  • -d:这会在后台运行容器。
  • --name:这让我们可以给容器起一个好记的名字。

运行以下命令来构建容器:

docker run --name node-demo -p 80:8080 -d node-demo

使用 docker ps 检查正在运行的容器:

docker ps

您将看到确认您的应用程序容器正在运行的输出:

OutputCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
4133b72391da        node-demo           "node app.js"       17 seconds ago      Up 16 seconds       0.0.0.0:80->8080/tcp   node-demo

您现在可以访问您的域来测试您的设置:http://example.com。 记得用自己的域名替换example.com。 您的应用程序将显示以下登录页面:

现在您已经测试了应用程序,您可以停止容器并删除图像。 再次使用 docker ps 得到你的 CONTAINER ID:

docker ps
OutputCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
4133b72391da        node-demo           "node app.js"       17 seconds ago      Up 16 seconds       0.0.0.0:80->8080/tcp   node-demo

使用 docker stop 停止容器。 请务必将此处列出的 CONTAINER ID 替换为您自己的应用程序 CONTAINER ID:

docker stop 4133b72391da

您现在可以使用 docker system prune 和 -a 标志删除停止的容器和所有图像,包括未使用的和悬空的图像:

docker system prune -a

在输出提示时键入 y 以确认您要删除已停止的容器和图像。 请注意,这也会删除您的构建缓存。

测试完您的应用程序映像后,您可以继续使用 Docker Compose 构建其余设置。

第 2 步 — 定义 Web 服务器配置

有了我们的应用程序 Dockerfile,我们可以创建一个配置文件来运行我们的 Nginx 容器。 我们将从一个最小配置开始,其中包括我们的域名、文档根、代理信息和一个位置块,用于将 Certbot 的请求定向到 .well-known 目录,它将在其中放置一个临时文件以验证我们域的 DNS 是否解析到我们的服务器。

首先,在当前项目目录下为配置文件创建一个目录:

mkdir nginx-conf

使用 nano 或您喜欢的编辑器打开文件:

nano nginx-conf/nginx.conf

添加以下服务器块以将用户请求代理到您的 Node 应用程序容器,并将 Certbot 的请求定向到 .well-known 目录。 请务必将 example.com 替换为您自己的域名:

~/node_project/nginx-conf/nginx.conf

server {
        listen 80;
        listen [::]:80;

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;

        server_name example.com www.example.com;

        location / {
                proxy_pass http://nodejs:8080;
        }

        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }
}

这个服务器块将允许我们将 Nginx 容器作为反向代理启动,它将请求传递给我们的 Node 应用程序容器。 它还将允许我们使用 Certbot 的 webroot 插件 来获取我们域的证书。 这个插件依赖于 HTTP-01 验证方法,它使用 HTTP 请求来证明 Certbot 可以从响应给定域名的服务器访问资源。

完成编辑后,保存并关闭文件。 想了解更多关于Nginx服务器和位置块算法的知识,请参考这篇文章了解Nginx服务器和位置块选择算法。

有了 Web 服务器配置详细信息,我们可以继续创建我们的 docker-compose.yml 文件,这将允许我们创建我们的应用程序服务和我们将用来获取证书的 Certbot 容器。

第三步——创建 Docker Compose 文件

docker-compose.yml 文件将定义我们的服务,包括 Node 应用程序和 Web 服务器。 它将指定诸如命名卷之类的详细信息,这对于在容器之间共享 SSL 凭据以及网络和端口信息至关重要。 它还允许我们在创建容器时指定要运行的特定命令。 该文件是定义我们的服务如何协同工作的核心资源。

打开当前目录中的文件:

nano docker-compose.yml

首先,定义应用服务:

~/node_project/docker-compose.yml

version: '3'

services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped

nodejs 服务定义包括以下内容:

  • build:这定义了 Compose 构建应用程序映像时将应用的配置选项,包括 context 和 dockerfile。 如果你想使用像 Docker Hub 这样的注册表中的现有镜像,你可以使用 image 指令 来代替,其中包含有关你的用户名、存储库和镜像标签的信息。
  • context:这定义了应用程序映像构建的构建上下文。 在这种情况下,它是当前项目目录。
  • dockerfile:这指定 Compose 将用于构建的 Dockerfile - 您在 Step 1 中查看的 Dockerfile。
  • image、container_name:这些将名称应用于图像和容器。
  • restart:定义重启策略。 默认为 no,但我们已将容器设置为重启,除非它停止。

请注意,我们不包括此服务的绑定挂载,因为我们的设置侧重于部署而不是开发。 有关详细信息,请参阅有关 bind mounts 和 volumes 的 Docker 文档。

为了启用应用程序和 Web 服务器容器之间的通信,我们还将在重启定义下方添加一个名为 app-network 的桥接网络:

~/node_project/docker-compose.yml

services:
  nodejs:
...
    networks:
      - app-network

像这样的用户定义的桥接网络可以在同一 Docker 守护进程主机上的容器之间进行通信。 这简化了应用程序内的流量和通信,因为它打开了同一桥接网络上容器之间的所有端口,同时不向外界公开任何端口。 因此,您可以选择性地仅打开公开前端服务所需的端口。

接下来,定义 webserver 服务:

~/node_project/docker-compose.yml

...
 webserver:
    image: nginx:mainline-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
    depends_on:
      - nodejs
    networks:
      - app-network

我们为 nodejs 服务定义的一些设置保持不变,但我们还进行了以下更改:

  • image:这告诉 Compose 从 Docker Hub 中提取最新的 Alpine-based Nginx 映像。 有关 alpine 镜像的更多信息,请参阅 如何使用 Docker 构建 Node.js 应用程序的第 3 步。
  • ports:这会暴露端口 80 以启用我们在 Nginx 配置中定义的配置选项。

我们还指定了以下命名卷和绑定挂载:

  • web-root:/var/www/html:这会将我们网站的静态资产添加到容器上的 /var/www/html 目录中,复制到名为 web-root 的卷中。
  • ./nginx-conf:/etc/nginx/conf.d:这会将宿主机上的 Nginx 配置目录绑定挂载到容器上的相关目录,确保我们对宿主机上的文件所做的任何更改都会反映在容器中。
  • certbot-etc:/etc/letsencrypt:这会将我们域的相关 Let's Encrypt 证书和密钥挂载到容器上的相应目录。
  • certbot-var:/var/lib/letsencrypt:这会将 Let's Encrypt 的默认工作目录挂载到容器上的相应目录。

接下来,为 certbot 容器添加配置选项。 请务必将域名和邮箱信息替换为您自己的域名和联系邮箱:

~/node_project/docker-compose.yml

...
  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --staging -d example.com  -d www.example.com 

此定义告诉 Compose 从 Docker Hub 中提取 certbot/certbot 映像。 它还使用命名卷与 Nginx 容器共享资源,包括 certbot-etc 中的域证书和密钥,certbot-var 中的 Let's Encrypt 工作目录,以及 web-root 中的应用程序代码]。

同样,我们使用 depends_on 来指定 certbot 容器应在 webserver 服务运行后启动。

我们还包括一个 command 选项,用于指定容器启动时要运行的命令。 它包括带有以下选项的 certonly 子命令:

  • --webroot:这告诉 Certbot 使用 webroot 插件将文件放在 webroot 文件夹中进行身份验证。
  • --webroot-path:指定 webroot 目录的路径。
  • --email:您用于注册和恢复的首选电子邮件。
  • --agree-tos:表示您同意 ACME 的订阅者协议 。
  • --no-eff-email:这告诉 Certbot 您不希望与 Electronic Frontier Foundation (EFF) 共享您的电子邮件。 如果您愿意,可以随意省略它。
  • --staging:这告诉 Certbot 您想使用 Let's Encrypt 的暂存环境来获取测试证书。 使用此选项可让您测试配置选项并避免可能的域请求限制。 有关这些限制的更多信息,请参阅 Let's Encrypt 的 速率限制文档 。
  • -d:这允许您指定要应用于您的请求的域名。 在本例中,我们包含了 example.com 和 www.example.com。 请务必将这些替换为您自己的域首选项。

作为最后一步,添加卷和网络定义。 请务必将此处的用户名替换为您自己的非 root 用户:

~/node_project/docker-compose.yml

...
volumes:
  certbot-etc:
  certbot-var:
  web-root:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/views/
      o: bind

networks:
  app-network:
    driver: bridge

我们的命名卷包括我们的 Certbot 证书和工作目录卷,以及我们网站静态资产的卷 web-root。 在大多数情况下,Docker 卷的默认驱动程序是 local 驱动程序,它在 Linux 上接受类似于 挂载命令 的选项。 多亏了这一点,我们能够使用 driver_opts 指定驱动程序选项列表,在运行时将主机上的 views 目录(包含我们应用程序的静态资产)挂载到卷上。 然后可以在容器之间共享目录内容。 关于views目录内容的更多信息,请参见如何使用Docker构建Node.js应用程序的第2步。

完成后 docker-compose.yml 文件将如下所示:

~/node_project/docker-compose.yml

version: '3'

services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped
    networks:
      - app-network

  webserver:
    image: nginx:mainline-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
    depends_on:
      - nodejs
    networks:
      - app-network

  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --staging -d example.com  -d www.example.com 

volumes:
  certbot-etc:
  certbot-var:
  web-root:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/views/
      o: bind

networks:
  app-network:
    driver: bridge  

准备好服务定义后,您就可以启动容器并测试您的证书请求了。

第 4 步 — 获取 SSL 证书和凭证

我们可以使用 docker-compose up 启动我们的容器,它将按照我们指定的顺序创建和运行我们的容器和服务。 如果我们的域请求成功,我们将在输出中看到正确的退出状态,并在 webserver 容器的 /etc/letsencrypt/live 文件夹中看到正确的证书。

使用 docker-compose up 和 -d 标志创建服务,这将在后台运行 nodejs 和 webserver 容器:

docker-compose up -d

您将看到确认您的服务已创建的输出:

OutputCreating nodejs ... done
Creating webserver ... done
Creating certbot   ... done

使用 docker-compose ps 检查服务的状态:

docker-compose ps

如果一切顺利,您的 nodejs 和 webserver 服务应该是 Up 并且 certbot 容器将退出并显示 0 状态消息:

Output  Name                 Command               State          Ports
------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0
nodejs      node app.js                      Up       8080/tcp
webserver   nginx -g daemon off;             Up       0.0.0.0:80->80/tcp

如果您在 nodejs 和 webserver 服务的 State 列中看到 Up 以外的任何内容,或者 0 以外的退出状态对于 certbot 容器,请务必使用 docker-compose logs 命令检查服务日志:

docker-compose logs service_name

您现在可以使用 docker-compose exec 检查您的凭据是否已安装到 webserver 容器:

docker-compose exec webserver ls -la /etc/letsencrypt/live

如果您的请求成功,您将看到如下输出:

Outputtotal 16
drwx------ 3 root root 4096 Dec 23 16:48 .
drwxr-xr-x 9 root root 4096 Dec 23 16:48 ..
-rw-r--r-- 1 root root  740 Dec 23 16:48 README
drwxr-xr-x 2 root root 4096 Dec 23 16:48 example.com

现在您知道您的请求将成功,您可以编辑 certbot 服务定义以删除 --staging 标志。

打开docker-compose.yml:

nano docker-compose.yml

找到文件中具有 certbot 服务定义的部分,并将 command 选项中的 --staging 标志替换为 --force-renewal 标志,这将告诉 Certbot您要申请与现有证书具有相同域的新证书。 certbot 服务定义现在应该如下所示:

~/node_project/docker-compose.yml

...
  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --force-renewal -d example.com -d www.example.com
...

您现在可以运行 docker-compose up 来重新创建 certbot 容器及其相关卷。 我们还将包含 --no-deps 选项来告诉 Compose 它可以跳过启动 webserver 服务,因为它已经在运行:

docker-compose up --force-recreate --no-deps certbot

您将看到指示您的证书请求成功的输出:

Outputcertbot      | IMPORTANT NOTES:
certbot      |  - Congratulations! Your certificate and chain have been saved at:
certbot      |    /etc/letsencrypt/live/example.com/fullchain.pem
certbot      |    Your key file has been saved at:
certbot      |    /etc/letsencrypt/live/example.com/privkey.pem
certbot      |    Your cert will expire on 2019-03-26. To obtain a new or tweaked
certbot      |    version of this certificate in the future, simply run certbot
certbot      |    again. To non-interactively renew *all* of your certificates, run
certbot      |    "certbot renew"
certbot      |  - Your account credentials have been saved in your Certbot
certbot      |    configuration directory at /etc/letsencrypt. You should make a
certbot      |    secure backup of this folder now. This configuration directory will
certbot      |    also contain certificates and private keys obtained by Certbot so
certbot      |    making regular backups of this folder is ideal.
certbot      |  - If you like Certbot, please consider supporting our work by:
certbot      |
certbot      |    Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
certbot      |    Donating to EFF:                    https://eff.org/donate-le
certbot      |
certbot exited with code 0

准备好证书后,您可以继续修改 Nginx 配置以包含 SSL。

第 5 步 — 修改 Web 服务器配置和服务定义

在我们的 Nginx 配置中启用 SSL 将涉及向 HTTPS 添加 HTTP 重定向并指定我们的 SSL 证书和密钥位置。 它还将涉及指定我们的 Diffie-Hellman 组,我们将用于 Perfect Forward Secrecy。

由于您要重新创建 webserver 服务以包含这些添加,因此您现在可以停止它:

docker-compose stop webserver

接下来,在当前项目目录中为您的 Diffie-Hellman 密钥创建一个目录:

mkdir dhparam

使用 openssl 命令 生成您的密钥:

sudo openssl dhparam -out /home/sammy/node_project/dhparam/dhparam-2048.pem 2048

生成密钥需要一些时间。

要将相关的 Diffie-Hellman 和 SSL 信息添加到您的 Nginx 配置中,请首先删除您之前创建的 Nginx 配置文件:

rm nginx-conf/nginx.conf

打开另一个版本的文件:

nano nginx-conf/nginx.conf

将以下代码添加到文件以将 HTTP 重定向到 HTTPS 并添加 SSL 凭据、协议和安全标头。 请记住将 example.com 替换为您自己的域:

~/node_project/nginx-conf/nginx.conf

server {
        listen 80;
        listen [::]:80;
        server_name example.com www.example.com;

        location ~ /.well-known/acme-challenge {
          allow all;
          root /var/www/html;
        }

        location / {
                rewrite ^ https://$host$request_uri? permanent;
        }
}

server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name example.com www.example.com;

        server_tokens off;

        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

        ssl_buffer_size 8k;

        ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;

        ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
        ssl_prefer_server_ciphers on;

        ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

        ssl_ecdh_curve secp384r1;
        ssl_session_tickets off;

        ssl_stapling on;
        ssl_stapling_verify on;
        resolver 8.8.8.8;

        location / {
                try_files $uri @nodejs;
        }

        location @nodejs {
                proxy_pass http://nodejs:8080;
                add_header X-Frame-Options "SAMEORIGIN" always;
                add_header X-XSS-Protection "1; mode=block" always;
                add_header X-Content-Type-Options "nosniff" always;
                add_header Referrer-Policy "no-referrer-when-downgrade" always;
                add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
                # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
                # enable strict transport security only if you understand the implications
        }

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;
}

HTTP 服务器块为 .well-known/acme-challenge 目录的 Certbot 续订请求指定 webroot。 它还包括一个 重写指令 ,它将 HTTP 请求指向根目录到 HTTPS。

HTTPS 服务器块启用 ssl 和 http2。 要详细了解 HTTP/2 如何在 HTTP 协议上迭代以及它对网站性能的好处,请参阅 如何在 Ubuntu 18.04 上设置支持 HTTP/2 的 Nginx 的介绍。 此块还包括一系列选项,以确保您使用的是最新的 SSL 协议和密码,并且 OSCP 装订已打开。 OSCP 装订允许您在初始 TLS 握手 期间提供来自 证书颁发机构 的时间戳响应,这可以加快身份验证过程。

该块还指定您的 SSL 和 Diffie-Hellman 凭据和密钥位置。

最后,我们将代理传递信息移至此块,包括带有 try_files 指令的位置块,将请求指向我们的别名 Node.js 应用程序容器,以及该别名的位置块,其中包括安全标头,这将使我们能够在 SSL Labs 和 Security Headers 服务器测试站点等内容上获得 A 评级。 这些标头包括 X-Frame-Options、X-Content-Type-Options、Referrer Policy、Content-Security-Policy、和X-XSS-保护。 HTTP Strict Transport Security (HSTS) 标头已被注释掉——只有在您了解其含义并评估其 “预加载”功能 时才启用此功能。

完成编辑后,保存并关闭文件。

在重新创建 webserver 服务之前,您需要在 docker-compose.yml 文件中的服务定义中添加一些内容,包括 HTTPS 的相关端口信息和 Diffie-Hellman 卷定义。

打开文件:

nano docker-compose.yml

在 webserver 服务定义中,添加以下端口映射和 dhparam 命名卷:

~/node_project/docker-compose.yml

...
 webserver:
    image: nginx:latest
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - dhparam:/etc/ssl/certs
    depends_on:
      - nodejs
    networks:
      - app-network

接下来,将 dhparam 卷添加到 volumes 定义中:

~/node_project/docker-compose.yml

...
volumes:
  ...
  dhparam:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/dhparam/
      o: bind

与 web-root 卷类似,dhparam 卷会将存储在主机上的 Diffie-Hellman 密钥挂载到 webserver 容器中。

完成编辑后保存并关闭文件。

重新创建 webserver 服务:

docker-compose up -d --force-recreate --no-deps webserver

使用 docker-compose ps 检查您的服务:

docker-compose ps

您应该看到输出表明您的 nodejs 和 webserver 服务正在运行:

Output  Name                 Command               State                     Ports
----------------------------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0
nodejs      node app.js                      Up       8080/tcp
webserver   nginx -g daemon off;             Up       0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp

最后,您可以访问您的域以确保一切都按预期工作。 将浏览器导航到 https://example.com,确保将 example.com 替换为您自己的域名。 您将看到以下登录页面:

您还应该在浏览器的安全指示器中看到锁定图标。 如果您愿意,可以导航到 SSL 实验室服务器测试登录页面 或 Security Headers 服务器测试登录页面 。 我们包含的配置选项应该让您的网站在这两个方面都获得 A 评级。

第 6 步 — 更新证书

Let's Encrypt 证书的有效期为 90 天,因此您需要设置一个自动续订过程以确保它们不会失效。 一种方法是使用 cron 调度实用程序创建作业。 在这种情况下,我们将使用脚本来安排 cron 作业,该脚本将更新我们的证书并重新加载我们的 Nginx 配置。

在您的项目目录中打开一个名为 ssl_renew.sh 的脚本:

nano ssl_renew.sh

将以下代码添加到脚本以更新您的证书并重新加载您的 Web 服务器配置:

~/node_project/ssl_renew.sh

#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"

cd /home/sammy/node_project/
$COMPOSE run certbot renew --dry-run && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af

此脚本首先将 docker-compose 二进制文件分配给名为 COMPOSE 的变量,并指定 --no-ansi 选项,它将在没有 ANSI 控制的情况下运行 docker-compose 命令字符。 然后它对 docker 二进制文件执行相同的操作。 最后,它切换到 ~/node_project 目录并运行以下 docker-compose 命令:

  • docker-compose run:这将启动一个 certbot 容器并覆盖我们的 certbot 服务定义中提供的 command。 我们在这里不使用 certonly 子命令,而是使用 renew 子命令,它将更新即将到期的证书。 我们在这里包含了 --dry-run 选项来测试我们的脚本。
  • docker-compose kill:这将向 webserver 容器发送 SIGHUP 信号 以重新加载 Nginx 配置。 有关使用此过程重新加载 Nginx 配置的更多信息,请参阅 this Docker blog post on deploying the official Nginx image with Docker。

然后它运行 docker system prune 以删除所有未使用的容器和图像。

完成编辑后关闭文件。 使其可执行:

chmod +x ssl_renew.sh

接下来,打开您的 root crontab 文件,以指定的时间间隔运行更新脚本:

sudo crontab -e 

如果这是您第一次编辑此文件,您将被要求选择一个编辑器:

crontab

no crontab for root - using an empty one
Select an editor.  To change later, run 'select-editor'.
  1. /bin/ed
  2. /bin/nano        <---- easiest
  3. /usr/bin/vim.basic
  4. /usr/bin/vim.tiny
Choose 1-4 [2]: 
...

在文件的底部,添加以下行:

crontab

...
*/5 * * * * /home/sammy/node_project/ssl_renew.sh >> /var/log/cron.log 2>&1

这会将作业间隔设置为每五分钟一次,因此您可以测试您的续订请求是否按预期工作。 我们还创建了一个日志文件 cron.log 来记录作业的相关输出。

五分钟后,查看cron.log,查看续费请求是否成功:

tail -f /var/log/cron.log

您应该会看到确认续订成功的输出:

Output- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Killing webserver ... done

您现在可以修改 crontab 文件以设置每日间隔。 例如,要在每天中午运行脚本,您可以将文件的最后一行修改为如下所示:

crontab

...
0 12 * * * /home/sammy/node_project/ssl_renew.sh >> /var/log/cron.log 2>&1

您还需要从 ssl_renew.sh 脚本中删除 --dry-run 选项:

~/node_project/ssl_renew.sh

#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"

cd /home/sammy/node_project/
$COMPOSE run certbot renew && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af

您的 cron 作业将确保您的 Let's Encrypt 证书不会在符合条件时更新它们而失效。 您还可以 使用 Logrotate 实用程序 设置日志轮换,以轮换和压缩您的日志文件。

结论

您已经使用容器来设置和运行带有 Nginx 反向代理的 Node 应用程序。 您还为应用程序的域保护了 SSL 证书,并设置了 cron 作业以在必要时更新这些证书。

如果您有兴趣了解有关 Let's Encrypt 插件的更多信息,请参阅我们关于使用 Nginx 插件 或 独立插件 的文章。

您还可以通过查看以下资源了解有关 Docker Compose 的更多信息:

Compose 文档 也是了解更多关于多容器应用程序的重要资源。