如何使用 Nginx、LetsEncrypt 和 Certbot 安全访问 MinIO

How to Use Nginx, LetsEncrypt and Certbot for Secure Access to MinIO

在设置任何基础设施时,您都希望以可扩展且安全的方式进行。您的 MinIO 数据基础设施也是如此。您需要确保

  • MinIO 集群的流量得到均匀分配
  • MinIO 集群中没有单点故障
  • 与 MinIO 集群的通信是安全的
  • 在将 MinIO 节点离线时易于维护

我们将介绍如何使用 Nginx 和 LetsEncrypt/Certbot 为 MinIO 设置负载均衡和 TLS。我们将使用容器,但它不会完全自动化,例如 Kubernetes 中的 cert-manager,这样我们就可以了解各个组件如何工作以及如何集成在一起。如果您稍后希望自动化它,我实际上建议这样做,因为随着应用程序数量的增加,手动从负载均衡器中添加/删除 MinIO 节点或每 3 个月更新一次证书可能会变得很麻烦。

让我们看一下即将设置的架构图

MinIO

我们将启动 3 个相同的 MinIO 节点,每个节点有 4 个磁盘。MinIO 可以在任何地方运行 - 物理机、虚拟机或容器 - 并且在本概述中,我们将使用 Docker 创建的容器。

节点设置

让我们将这 3 个节点命名为

  • minio1
  • minio2
  • minio3

我们将逐步介绍 minio1 的部署,然后您可以对 minio2minio3 使用相同的流程

对于这 4 个磁盘,在 minio1 的主机上创建目录

mkdir -p /home/aj/minio-<id>/disk-1 \
mkdir -p /home/aj/minio-<id>/disk-2 \
mkdir -p /home/aj/minio-<id>/disk-3 \
mkdir -p /home/aj/minio-<id>/disk-4

<id> 替换为 1。对 minio2minio3 也重复上述操作。

启动节点

使用以下规范启动 3 个 Docker 容器,类似地将 <id> 替换为 MinIO 节点的 1、2 和 3

docker run -d \
  -p 2009<id>:9001 \
  -v /home/aj/minio-<id>/disk-1:/mnt/disk1 \
  -v /home/aj/minio-<id>/disk-2:/mnt/disk2 \
  -v /home/aj/minio-<id>/disk-3:/mnt/disk3 \
  -v /home/aj/minio-<id>/disk-4:/mnt/disk4 \
  --name minio<id> \
  --hostname minio<id> \
  quay.io/minio/minio server http://minio{1...3}/mnt/disk{1...4}/minio --console-address ":9001"

-p <主机>:<容器>: 用于将 MinIO 控制台 UI 端口暴露到主机上,方便我们查看存储桶、对象和管理 MinIO。

-v <主机>:<容器>: 用于将之前创建的本地目录作为磁盘挂载到容器中。

--name--hostname: 将这两个参数设置为相同的值,以便在配置容器时保持一致性。

quay.io/minio/minio server http://minio{1...3}/mnt/disk{1...4}/minio --console-address ":9001": minio{1...3}disk{1...4} 在应用于服务器配置之前会被扩展。

当所有 3 个节点都成功启动并相互通信后,运行 docker logs minio1

状态:         12 在线, 0 离线。
API: http://172.20.0.2:9000  http://127.0.0.1:9000

控制台: http://172.20.0.2:9001 http://127.0.0.1:9001

文档: https://docs.min.io


如果看到 12 在线,则表示您已成功设置了包含 3 个 MinIO 节点(每个节点 4 个磁盘)的集群,总共有 12 个磁盘在线。

测试新的 MinIO 集群

在浏览器中访问 3 个容器中的一个的 MinIO 控制台,其端口为 http://localhost:2009<id>,将 <id> 替换为 1、2 或 3 以访问相应的 MinIO 节点。点击 创建存储桶 按钮并创建 testbucket123

如果我们访问另一个 MinIO 容器的控制台,我们将看到相同的存储桶。

Nginx

负载均衡

Nginx 最初是一个 Web 服务器,后来发展成为 Kubernetes 中的入口控制器。它在各个方面都有发展,但它 arguably 做得很好的一件事是将请求分发到一组后端节点,这个过程被称为反向代理。

TLS 终止

使用反向代理时,我们应该在代理层终止 TLS 连接。这样,当证书过期时,您就不必担心在每个节点上更新证书。如果您已实施配置管理流程,则可以简化和自动化此过程。

配置

我们的 MinIO 集群中有两个端口,服务器运行在端口 9000 上,控制台运行在端口 9001 上,我们需要通过 Nginx 代理这两个端口。

为这两个端口(9000 和 9001)分别创建两个上游服务器,并列出我们的后端 MinIO 服务器。

upstream minio_server {
  server minio1:9000;
  server minio2:9000;
  server minio3:9000;
}

upstream minio_console {
    ip_hash;
  server minio1:9001;
  server minio2:9001;
  server minio3:9001;
}

minio_server 用于端口 9000minio_console 用于 9001

为每个上游服务器创建两个 Nginx 服务器指令。

server {
    listen       9000;
    listen  [::]:9000;
    server_name  localhost;

    ignore_invalid_headers off;
    client_max_body_size 0;
    proxy_buffering off;
    proxy_request_buffering off;

    location / {
        proxy_set_header Host $http_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_connect_timeout 300;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        chunked_transfer_encoding off;

        proxy_pass http://minio_server;
    }
}

server {
    listen       9001;
    listen  [::]:9001;
    server_name  localhost;

    ignore_invalid_headers off;
    client_max_body_size 0;
    proxy_buffering off;
    proxy_request_buffering off;

    location / {
        proxy_set_header Host $http_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_set_header X-NginX-Proxy true;

        real_ip_header X-Real-IP;

        proxy_connect_timeout 300;
       
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
       
        chunked_transfer_encoding off;

        proxy_pass http://minio_console;
    }
}

编写完配置后,将其保存为 default.conf,并将其保存到稍后可以挂载到 Nginx 容器中的位置,在本例中,我使用了 /home/aj/nginx/conf.d 目录。

使用自定义配置,启动一个 Nginx 镜像并进行端口映射。在本例中,我们将主机端口 3900039001 分别映射到 nginx 容器端口 9000 和 9001。

docker run -d \
  -p 39000:9000 \
  -p 39001:9001 \
  -v /home/aj/nginx/conf.d:/etc/nginx/conf.d \
  --name nginx \
  --hostname nginx \
  nginx:latest

测试反向代理

3 个 MinIO 容器现在由 Nginx 反向代理提供服务。我们可以通过访问 http://localhost:39001 来确认这一点,我们应该看到相同的 testbucket123


Nginx 将确保请求得到均匀分配,因此我们无需访问各个容器即可访问控制台。服务器也是如此,您可以在 localhost:39000 上访问它,Nginx 将负责负载均衡。

Let's Encrypt

如今,使用 Let's Encrypt 证书创建和管理 TLS 证书的事实标准方法是通过 Certbot。我们将使用 Certbot 以自动化方式帮助管理证书的生命周期。它将帮助您完成以下一些操作:

  • 创建证书
  • 续订证书
  • 吊销证书

在本例中,我们将使用 Certbot 创建一个证书,并在上一步的 Nginx 中使用它。

创建证书

在本教程中,我们不会像在 Kubernetes 中使用 cert-manager 一样使用完全自动化,但我们将使用 Certbot 使证书的生成和续订更容易一些——让我们不要使其过于复杂 :)

在各种平台上安装 Certbot 的方法有很多,这里提供了 说明。在为您的特定平台安装 Certbot 后,可以使用他们所谓的“挑战”通过两种方式来验证您与 Let's Encrypt API 的证书。

挑战

例如,假设您尝试为其创建证书的域名是 minio.example.com

HTTP

  • HTTP 挑战会在端口 80 上查找 URL 路径 /.well-known/acme-challenge/:id,在本例中为 http://minio.example.com/.well-known/acme-challenge/:id 是否可访问。

然后,Certbot 将在证书生成过程中将文件写入 /.well-known/acme-challenge/:idroot 目录,并进行验证。例如,以下是挑战的 Nginx 配置示例:

位置 /.well-known/acme-challenge/ {
    根目录 /var/html/certbot;
}


然后,certbot --webroot-path 的值为 /var/html/certbot

与其手动设置以上内容,最简单的方法是

  • 启动一台具有公网 IP 的虚拟机,并确保端口 80 开放。
  • minio.example.com 的 DNS A 记录更新为虚拟机的公网 IP。
  • 运行以下命令,该命令将启动一个临时服务器,设置验证并生成证书
  • sudo certbot certonly --standalone

DNS 验证

  • 在我看来,DNS 验证稍微简单一些,因为您只需在 minio.example.com 的 DNS 区域设置一条记录即可。无需创建额外的基础设施或服务器。
  • 运行以下命令开始设置基于 DNS 的验证过程
  • certbot -d minio.example.com --manual --preferred-challenges dns certonly
  • 以上命令将在提示符处停止,提示您将 _acme-challenge.minio.example.comTXT 记录设置为特定值。一旦该记录能够成功解析,验证将通过,您将获得 minio.example.com 的新证书。

DNS 验证方法的缺点之一是 DNS 传播不是即时的。即使您可以在本地笔记本电脑上解析它,Let's Encrypt 的 DNS 服务器可能无法解析,因此您需要等待。通常这很快,但不能保证。

配置 Nginx

无论您使用哪种方法,一旦证书生成,应该会有两个文件 fullchain.pemprivkey.pem。将这些文件保存到一个目录中,以便稍后挂载到 Nginx 容器的 /etc/nginx/certs 目录中。在本例中,我们将它们放在 /home/aj/nginx/certs 目录中。

修改 Nginx default.conf 文件中的 2 个 server { … } 块,如下所示

  • server_name localhost 更改为 server_name minio.example.com

创建以下 2 个指令

  • ssl_certificate /etc/nginx/certs/fullchain.pem
  • ssl_certificate_key /etc/nginx/certs/privkey.pem

两个 server { … } 块应该看起来像这样

server {
    ..
    server_name  minio.example.com;
    ssl_certificate /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;
    ..
}

不要忘记在 Nginx 的 docker run 命令中添加一个额外的 -v 参数,以便在下一步中 Nginx 可以访问证书。

docker run -d \
  -p 39000:9000 \
  -p 39001:9001 \
  -v /home/aj/nginx/conf.d:/etc/nginx/conf.d \
  -v /home/aj/nginx/certs:/etc/nginx/certs \
  --name nginx \
  --hostname nginx \
  nginx:latest

执行以上命令以启动 Nginx 容器。

测试 TLS

好的,我们已经配置了所有内容,但我们没有可以为 minio.example.com 的 A 记录设置的 Nginx Docker 容器的公网 IP,那么我们如何测试 TLS 呢?

一种测试方法是

  • 在本地笔记本电脑的 /etc/hosts 文件中添加新行
  • 如果 Docker 主机与您的本地笔记本电脑相同,您可以添加行 127.0.0.1 minio.example.com
  • 如果 Docker 主机在另一台具有私网 IP 的虚拟机上运行,您可以添加行 <vm_private_ip> minio.example.com
  • 在浏览器中访问 https://minio.example.com:39001,您应该能够看到安全连接。

进一步改进

  • 也许 Nginx 可以运行在一个端口上,并在同一端口上使用 2 个 location /… 指令分别用于 /server/console
  • 按照我们的配置,Nginx 是单点故障。一个可能的选项是使用多个 Nginx 节点,并结合 DNS 轮询来分发流量,以防其中一个 Nginx 实例需要离线维护等。

最终想法

当人们谈论 TLS 证书时,Let's Encrypt/Certbot 是首先想到的;当谈论反向代理和 Kubernetes Ingress 时,Nginx 是首先想到的。同样,每个人都知道 MinIO 是最好的对象存储。这些的共同点是什么?它们共享了编写良好的云原生软件的简单性,每个软件都提供核心基础设施服务。

我们不可能使用其他对象存储来编写这个简短的教程。其他类似的对象存储极其复杂,设置起来非常耗时,但我们只需使用几个容器就设置了一个负载均衡且 TLS 安全的多节点 MinIO 集群。

不过,不用相信我们的话 - 自己试试看。您可以在这里下载 MinIO,也可以在这里加入我们的 Slack 频道。