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

请注意,我们不包括此服务的绑定挂载,因为我们的设置侧重于部署而不是开发。 有关详细信息,请参阅有关 bind mountsvolumes 的 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.comwww.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 标志创建服务,这将在后台运行 nodejswebserver 容器:

docker-compose up -d

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

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

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

docker-compose ps

如果一切顺利,您的 nodejswebserver 服务应该是 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

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

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

最后,我们将代理传递信息移至此块,包括带有 try_files 指令的位置块,将请求指向我们的别名 Node.js 应用程序容器,以及该别名的位置块,其中包括安全标头,这将使我们能够在 SSL LabsSecurity Headers 服务器测试站点等内容上获得 A 评级。 这些标头包括 X-Frame-OptionsX-Content-Type-OptionsReferrer PolicyContent-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

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

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 文档 也是了解更多关于多容器应用程序的重要资源。