如何用Nginx、Let's Encrypt和Docker Compose来保护容器化的Node.js应用程序?

694 阅读20分钟

简介

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

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

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

前提条件

要学习本教程,你将需要。

  • 一台Ubuntu 18.04服务器,一个具有sudo 权限的非root用户,以及一个活跃的防火墙。关于如何设置这些的指导,请阅读这个初始服务器设置指南

  • 在你的服务器上安装Docker和Docker Compose。关于安装Docker的指导,请遵循《如何在Ubuntu 18.04上安装和使用Docker》的步骤1和2。关于安装Compose的指导,请按照《如何在Ubuntu 18.04上安装Docker Compose》中的第1步

  • 一个注册的域名。本教程将自始至终使用your_domain。你可以在Freenom免费获得一个,或者使用你选择的域名注册商。

  • 为你的服务器设置好以下两个DNS记录。如果你使用的是DigitalOcean帐户,你可以按照这个介绍来了解如何将它们添加到DigitalOcean帐户的细节。

    • 一个A记录 your_domain指向你的服务器的公共IP地址。
    • 一个A记录 www.your_domain指向你的服务器的公共IP地址。

一旦你设置好了一切,你就可以开始第一步了。

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

作为第一步,你将克隆带有Node应用代码的仓库,其中包括用Compose构建应用镜像的Docker文件。然后,你将通过构建和运行docker run 命令来测试应用程序,不需要反向代理或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

在这个目录中,有一个Docker文件,其中包含使用Dockernode: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 的副本与其余的应用程序代码的副本分开。最后,说明指定容器将以非root节点用户的身份运行,并在应用程序代码和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://your_domain.记住要把 your_domain换成你自己的域名。你的应用程序将显示以下登陆页面。

Application Landing Page

现在你已经测试了应用程序,你可以停止容器并删除图像。使用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步 - 定义网络服务器配置

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

首先,在当前项目目录下创建一个目录,node_project ,用于存放配置文件。

mkdir nginx-conf

nano 或你喜欢的编辑器创建并打开该文件。

nano nginx-conf/nginx.conf

添加以下服务器块来代理用户请求到你的Node应用容器,并将Certbot的请求指向.well-known 目录。请务必将 your_domain 换成你自己的域名。

~/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 your_domain www.your_domain;

        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可以从响应给定域名的服务器访问资源。

完成编辑后,保存并关闭该文件。如果你使用了nano ,你可以通过按CTRL + X ,然后按Y ,再按ENTER 。要了解更多关于Nginx服务器和位置块算法的信息,请参考这篇文章:了解Nginx服务器和位置块选择算法

随着Web服务器配置细节的到位,你可以继续创建你的docker-compose.yml 文件,这将允许你创建你的应用服务和Certbot容器,你将用它来获取证书。

第3步 - 创建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:这定义了配置选项,包括contextdockerfile ,这些选项将在Compose构建应用程序镜像时应用。如果你想使用来自Docker Hub等注册中心的现有镜像,你可以使用image 指令来代替,其中包括关于你的用户名、存储库和镜像标签的信息。
  • context:这定义了应用程序镜像构建的构建环境。在这种情况下,它是当前的项目目录,用. 表示。
  • dockerfile::这指定了 Compose 将用于构建的 Dockerfile - 在步骤 1 中审查的 Dockerfile。
  • image,container_name: 这些适用于镜像和容器的名称。
  • restart:这定义了重启策略。默认是no ,但在这个例子中,容器被设置为重新启动,除非它被停止。

注意,你不包括与该服务的绑定挂载,因为你的设置侧重于部署而不是开发。欲了解更多信息,请阅读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 服务定义的一些设置保持不变,但做了以下一些改变。

还指定了以下命名卷和绑定挂载。

  • web-root:/var/www/html:这将把你的网站的静态资产,复制到一个名为web-root 的卷中,添加到容器的/var/www/html 目录中。
  • ./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@your_domain --agree-tos --no-eff-email --staging -d your_domain  -d www.your_domain 

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

同样,你用depends_on 来指定,一旦webserver 服务运行,certbot 容器就应该被启动。

command 选项指定了容器启动时要运行的命令。它包括带有下列选项的certonly 子命令。

  • --webroot:这告诉Certbot使用webroot插件将文件放到webroot文件夹中进行验证。
  • --webroot-path:这指定了webroot目录的路径。
  • --email:你喜欢的用于注册和恢复的电子邮件。
  • --agree-tos:: 这表明您同意ACME的用户协议
  • --no-eff-email:这告诉Certbot你不希望与电子前线基金会(EFF)分享你的电子邮件。如果你愿意的话,请随意省略。
  • --staging:这告诉Certbot您希望使用Let's Encrypt的暂存环境来获取测试证书。使用这个选项可以测试你的配置选项,避免可能的域名请求限制。关于这些限制的更多信息,请阅读Let's Encrypt的速率限制文档
  • -d:这允许你指定你想应用于你的请求的域名。在这个例子中,你已经包括了 your_domainwww.your_domain.请确保用你自己的域名替换这些域名。

作为最后一步,添加卷和网络的定义。请确保将这里的用户名替换为你自己的非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上接受类似于mount 命令的选项。得益于此,你能够用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@your_domain --agree-tos --no-eff-email --staging -d your_domain  -d www.your_domain 

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 服务将是Upcertbot 容器将退出,并有一个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 之外的任何东西,或者certbot 容器的退出状态除了0 之外,请确保用以下命令检查服务日志 docker-compose logs命令检查服务日志。例如,如果你想检查Certbot的日志,你可以运行。

docker-compose logs certbot

你现在可以检查你的证书是否已经被挂载到webserver 容器中,用 docker-compose exec:

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 your_domain

现在你知道你的请求将被成功,你可以编辑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@your_domain --agree-tos --no-eff-email --force-renewal -d your_domain -d www.your_domain
...

当你完成编辑后,保存并退出该文件。你现在可以运行docker-compose up ,重新创建certbot 容器及其相关卷。通过包括--no-deps 选项,你在告诉 Compose 它可以跳过启动webserver 服务,因为它已经在运行了。

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

下面的输出表明你的证书请求是成功的。

OutputRecreating certbot ... done
Attaching to certbot
certbot      | Account registered.
certbot      | Renewing an existing certificate for your_domain and www.your_domain
certbot      |
certbot      | Successfully received certificate.
certbot      | Certificate is saved at: /etc/letsencrypt/live/your_domain/fullchain.pem
certbot      | Key is saved at:         /etc/letsencrypt/live/your_domain                               phd.com/privkey.pem
certbot      | This certificate expires on 2022-11-03.
certbot      | These files will be updated when the certificate renews.
certbot      | NEXT STEPS:
certbot      | - The certificate will need to be renewed before it expires. Cert                               bot can automatically renew the certificate in the background, but you may need                                to take steps to enable that functionality. See https://certbot.org/renewal-setu                               p for instructions.
certbot      | Saving debug log to /var/log/letsencrypt/letsencrypt.log
certbot      |
certbot      | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -                                - - - - - - -
certbot      | If you like Certbot, please consider supporting our work by:
certbot      |  * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/do                               nate
certbot      |  * Donating to EFF:                    https://eff.org/donate-le
certbot      | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -                                - - - - - - -
certbot exited with code 0

有了证书,你就可以继续修改Nginx的配置,使其包括SSL。

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

在Nginx配置中启用SSL将涉及添加HTTP重定向到HTTPS,并指定SSL证书和密钥位置。它还将涉及到指定Diffie-Hellman组,你将使用它来实现完美前向保密

由于你要重新创建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凭证、协议和安全头文件。记住要用你自己的域名替换 your_domain用你自己的域名。

~/node_project/nginx-conf/nginx.conf


server {
        listen 80;
        listen [::]:80;
        server_name your_domain www.your_domain;

        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 your_domain www.your_domain;

        server_tokens off;

        ssl_certificate /etc/letsencrypt/live/your_domain/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/your_domain/privkey.pem;

        ssl_buffer_size 8k;

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

        ssl_protocols TLSv1.2;
        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服务器块指定了Certbot更新请求到.well-known/acme-challenge 目录的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实验室安全头文件服务器测试网站等地方获得A级评级。这些头信息包括 X-Frame-Options, X-Content-Type-Options, Referrer Policy, Content-Security-Policy, , 和 X-XSS-Protection.HTTPStrict 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 定义中。记得替换sammynode_project 目录,以符合你的要求。

~/node_project/docker-compose.yml

...
volumes:
  ...
  webroot:
  ...
  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://your_domain,确保将 your_domain用你自己的域名代替。

Application Landing Page

在你的浏览器的安全指示灯中应该出现一个锁的图标。如果你愿意,你可以浏览SSL实验室服务器测试登陆页面安全头文件服务器测试登陆页面。包括的配置选项应该能使你的网站在SSL实验室服务器测试中获得A级评级。为了在安全头文件服务器测试中获得A级评级,你必须取消对nginx-conf/nginx.conf 文件中的严格传输安全(HSTS)头文件的注释。

~/node_project/nginx-conf/nginx.conf

…
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
        }
…

同样,只有在你了解其影响并评估了其"预加载 "功能的情况下,才能启用该选项。

第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 --ansi never"
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:SIGHUP 这将向 webserver 容器发送一个信号,以重新加载Nginx的配置。

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

编辑完毕后关闭文件,然后使其可执行。

chmod +x ssl_renew.sh

接下来,打开你的 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/your_domain/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Killing webserver ... done
Output…
Congratulations, all simulated renewals succeeded: 
  /etc/letsencrypt/live/your_domain/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Killing webserver ... 
Killing webserver ... done
Deleted Containers:
00cad94050985261e5b377de43e314b30ad0a6a724189753a9a23ec76488fd78

Total reclaimed space: 824.5kB

在你的终端输入CTRL + C ,退出。

现在你可以修改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文档也是学习更多关于多容器应用的一个重要资源。