1 - Nginx:向外代理暴露Web服务

如何配置 Pigsty 中的 Nginx,对外代理并暴露内部 Web 服务,提供本地软件仓库服务。

Pigsty 的 Infra 模块默认会在节点上安装 Nginx,这是一个高性能的 Web 服务器。 Pigsty 使用 Nginx 作为所有本地 WebUI 服务的统一入口,并将其用作本地软件仓库,向内网其他节点提供服务。

当然,用户可以根据需求,调整配置,将 Nginx 用作标准的 Web 服务器,对外提供服务。 无论是作为反向代理,还是直接作为网站服务器,都可以通过适当的配置实现。 Pigsty 本身的文档站与仓库也是通过 Pigsty 自建的 Nginx 对外提供的。


配置概览

Nginx 服务器配置由 infra_portal 参数指定。

用户在这里声明所有需要通过 Nginx 代理的域名,以及对应的上游服务器端点(endpoint)或本地目录路径(path)。

例如,默认情况下,Pigsty 会这样配置 Nginx,下面的配置会使用 Nginx 对外暴露 Home,Grafana,Prometheus,Alertmanager 四项服务:

infra_portal:  # domain names and upstream servers
  home         : { domain: h.pigsty }
  grafana      : { domain: g.pigsty ,endpoint: "${admin_ip}:3000" , websocket: true }
  prometheus   : { domain: p.pigsty ,endpoint: "${admin_ip}:9090" }
  alertmanager : { domain: a.pigsty ,endpoint: "${admin_ip}:9093" }
  blackbox     : { endpoint: "${admin_ip}:9115" }
  loki         : { endpoint: "${admin_ip}:3100" }

当安装 Pigsty 时,Pigsty 会自动根据以上配置生成 Nginx 的配置文件。

/etc/nginx/conf.d/haproxy/           # <--- 存放着 HAPROXY 管理界面的位置定义
/etc/nginx/conf.d/home.conf          # <--- Pigsty 默认服务器定义(本地软件源,HAPROXY转发)
/etc/nginx/conf.d/grafana.conf       # <--- 代理访问内网 Grafana 服务器
/etc/nginx/conf.d/prometheus.conf    # <--- 代理访问内网 Prometheus 服务器
/etc/nginx/conf.d/alertmanager.conf  # <--- 代理访问内网 Alertmanager 服务器

Nginx 默认服务于 80/443 端口,home 服务器是本地软件源,同时也是默认的 Nginx 服务器。 如果你想通过 Nginx 访问其他服务,只需要在 infra_portal 中添加相应的配置即可,任何带有 domain 参数的配置都会被 Nginx 自动代理。


配置剧本

当安装 Pigsty 时,这些配置会在默认的 install.yml 剧本,或者 infra.yml 剧本中自动生效。 但是用户也可以在 Pigsty 部署后使用 infra.yml 剧本中的 nginx 子任务重新初始化 Nginx 配置。

./infra.yml -t nginx           # 重新配置 Nginx
./infra.yml -t nginx_config    # 重新生成 Nginx 配置
./infra.yml -t nginx_launch    # 重新启动 Nginx 服务

这意味着如果您想要调整 Nginx 服务器的配置,只需要修改 pigsty.yml 配置文件,并执行上面的 nginx 任务即可生效。

当然,你也可以选择先使用 nginx_config 子任务重新生成 Nginx 配置文件,人工检查后使用 nginx -s reload 重新在线加载配置。


配置详情

配置变量 infra_portal 通常定义在全局变量 all.vars 中,默认值如下所示:

all:
  vars:
    infra_portal:  # domain names and upstream servers
      home         : { domain: h.pigsty }
      grafana      : { domain: g.pigsty ,endpoint: "${admin_ip}:3000" , websocket: true }
      prometheus   : { domain: p.pigsty ,endpoint: "${admin_ip}:9090" }
      alertmanager : { domain: a.pigsty ,endpoint: "${admin_ip}:9093" }
      blackbox     : { endpoint: "${admin_ip}:9115" }
      loki         : { endpoint: "${admin_ip}:3100" }

默认配置意味着,用户默认可以通过:

  • h.pigsty 访问 home 服务器,这是默认服务器,对应 Pigsty 的文件系统首页与本地软件源,通常指向本机上的 /www 目录。
  • g.pigsty 访问 grafana 服务器,这是默认的 Grafana 服务,通常指向管理节点(admin_ip)上的 3000 端口。
  • p.pigsty 访问 prometheus 服务器,这是默认的 Prometheus 服务,通常指向管理节点(admin_ip)上的 9090 端口。
  • a.pigsty 访问 alertmanager 服务器,这是默认的 Alertmanager 服务,通常指向管理节点(admin_ip)上的 9093 端口。

注意这里的 blackboxloki 没有配置 domain 参数,因此不会被添加到 Nginx 的配置中,因为它们没有配置 domain 参数。 但是这并不意味着用户可以直接把这两项定义移除掉,因为内网中的其他服务可能会引用这里的配置(例如日志 Agent 会引用 Loki endpoint 地址发送日志)

用户可以通过丰富的配置参数,为不同的服务配置不同的配置,如下所示:


服务器参数

每一条服务器记录都有一个独一无二的 name 作为 key,一个配置字典作为 value。在配置字典中,目前有以下几个可用配置项:

  • domain可选,指定代理的域名,如果不填写,则 Nginx 不会对外暴露此服务。

    对于那些需要知道 endpoint 地址,但不想对外暴露的服务(例如 Loki, Blackbox Exporter),可以不填写此参数

  • endpoint可选,指定上游服务的地址,可以是 IP:PORT 或者 DOMAIN:PORT

    • 当此服务器为上游服务时,可以指定此参数,Pigsty 会生成一个标准的反向代理配置,并将请求转发给上游的 endpoint 地址。
    • 此参数与 path 参数是互斥的,不能同时存在:如果一个服务器是反向代理服务器,那么它不能同时是本地网页服务器。
    • 在此参数值中,可以使用 ${admin_ip} 占位符,Pigsty会填入 admin_ip 的值。
    • 如果指定了此参数,则 Pigsty 默认会使用 endpoint.conf 配置模板,这是反向代理的标准模板
    • 如果同时指定了 conf 参数,则 conf 参数指定的模板有更高的优先级。
    • 如果上游强制要求 HTTPS 访问,你可以额外设置 scheme: https 参数。
  • path可选,指定本地 Web 服务器的根目录,可以是绝对路径或者相对路径。

    • 当此服务器为本地 Web 服务器时,可以指定此参数,Pigsty 会生成一个标准的本地 Web 服务器配置,并将请求转发给本地的 path 目录。
    • 此参数与 endpoint 参数是互斥的,不能同时存在,如果一个服务器是本地网页服务器,那么它不能同时是上游代理服务器。
    • 如果指定了此参数,则 Pigsty 默认会使用 path.conf 配置模板,这是本地 Web 服务器的标准模板
    • 如果同时指定了 conf 参数,则 conf 参数指定的模板有更高的优先级。
    • 如果你希望 Nginx 自动生成文件列表索引,可以设置 index: true 参数,默认是不打开的。
  • conf: 可选,如果指定,则将会使用 templates/nginx/ 中定义的配置模板。

    • 当你想要任意定制 Nginx 配置时可以指定此参数,指定一个存在于 templates/nginx 目录中的模板文件名。
    • 如果没有指定此参数,Pigsty 将根据服务器的类型(Home, Proxy, Path)自动应用相应的 默认模板
      • home.conf: 默认服务器的模板(用于 home)。
      • endpoint.conf: 上游服务代理的模板(例如:用于Grafana,Prometheus 等)
      • path.conf:本地 Web 服务器的模板(默认没有使用,但 home 是特殊的本地服务器)
  • certbot: 可选,指定此服务器的 certbot 证书名称,Pigsty 会自动使用 certbot 生成的证书。

    • 如果您的证书是使用 certbot 生成的,你可以指定此参数为 certbot 生成的证书名称,Pigsty 会自动使用此证书。
    • 您应当填入 certbot 生成证书的域名部分。例如 certbot: pigsty.cc 会自动使用
      • 证书:/etc/letsencrypt/live/pigsty.cc/fullchain.pem,但可以被显式指定的 cert 参数(完整路径)覆盖。
      • 私钥:/etc/letsencrypt/live/pigsty.cc/privkey.pem,但可以被显式指定的 key 参数(完整路径)覆盖。
    • certbot 证书名称通常与 domain 参数相同,例如 g.pigsty 的证书名称为 g.pigsty.cc,但也有特例:当你同时申请多个证书时,certbot 会生成一个捆绑证书,名称为申请列表中的第一个域名。
  • cert: 可选,指定此服务器的 SSL 证书文件名,需要给出完整路径。

  • key: 可选,指定此服务器的 SSL 私钥文件名,需要给出完整路径。

  • domains: 可选,除了默认的 domain 域名,您还可以为此服务器指定多个额外的域名

  • scheme: 可选,指定此服务器的协议(http/https),留空则默认使用 http,通常用于强制要求 HTTPS 访问的上游 Web 服务。

  • index: 可选,如果设置为 true, Nginx 会为目录自动生成文件列表索引页面,方便浏览文件,对于软件仓库类通常可以打开,对于网站通常应当关闭。

  • log: 可选,如果指定,则日志将打印到 <value>.log ,而非默认的 access.log 中。

    • 作为特例,上游代理服务器的日志总是会打印到 <name>.log 中。

复杂的配置样例

Pigsty 自带的配置模板 conf/demo.yml 有一个更详细的案例,给出了 Pigsty 文档站的配置样例。

是的,Pigsty 的文档站也是在一台普通云服务器上使用 Pigsty 本身建设的,其配置如下所示:

infra_portal:
  home         : { domain: home.pigsty.cc }
  grafana      : { domain: demo.pigsty.cc ,endpoint: "${admin_ip}:3000" ,websocket: true ,cert: /etc/cert/demo.pigsty.cc.crt ,key: /etc/cert/demo.pigsty.cc.key }
  prometheus   : { domain: p.pigsty.cc    ,endpoint: "${admin_ip}:9090" }
  alertmanager : { domain: a.pigsty.cc    ,endpoint: "${admin_ip}:9093" }
  cc           : { domain: pigsty.cc      ,path:     "/www/pigsty.cc"   ,cert: /etc/cert/pigsty.cc.crt ,key: /etc/cert/pigsty.cc.key }

  blackbox     : { endpoint: "${admin_ip}:9115" }
  loki         : { endpoint: "${admin_ip}:3100" }
  minio        : { domain: m.pigsty.cc    ,endpoint: "${admin_ip}:9001" ,scheme: https ,websocket: true }
  postgrest    : { domain: api.pigsty.cc  ,endpoint: "127.0.0.1:8884"   }
  pgadmin      : { domain: adm.pigsty.cc  ,endpoint: "127.0.0.1:8885"   }
  pgweb        : { domain: cli.pigsty.cc  ,endpoint: "127.0.0.1:8886"   }
  bytebase     : { domain: ddl.pigsty.cc  ,endpoint: "127.0.0.1:8887"   }
  jupyter      : { domain: lab.pigsty.cc  ,endpoint: "127.0.0.1:8888", websocket: true }
  gitea        : { domain: git.pigsty.cc  ,endpoint: "127.0.0.1:8889" }
  wiki         : { domain: wiki.pigsty.cc ,endpoint: "127.0.0.1:9002" }
  noco         : { domain: noco.pigsty.cc ,endpoint: "127.0.0.1:9003" }
  supa         : { domain: supa.pigsty.cc ,endpoint: "10.10.10.10:8000" ,websocket: true }
  dify         : { domain: dify.pigsty.cc ,endpoint: "10.10.10.10:8001" ,websocket: true }
  odoo         : { domain: odoo.pigsty.cc ,endpoint: "127.0.0.1:8069"   ,websocket: true }
  mm           : { domain: mm.pigsty.cc   ,endpoint: "10.10.10.10:8065" ,websocket: true }

这个文档站的 Nginx 配置要比默认的配置复杂一些:

  • home 服务器使用了一个真实的公网域名 home.pigsty.cc
  • grafana 服务器使用了一个真实的公网域名 demo.pigsty.cc,并配置了 websocket: true 以支持 WebSocket 连接。
  • cc 服务器是 Pigsty 的文档站,它使用了真实公网域名 pigsty.cc,并指向了本地的 /www/pigsty.cc 目录。
  • 下面还定义了一系列 Docker App 服务器,将这些应用的 Web 界面通过域名对外暴露。
  • ccgrafana 直接通过 certkey 参数指定了 HTTPS 证书。

配置域名

您可以通过 IP:Port 直接访问特定服务,例如 IP:3000 访问 Grafana,IP:9090 访问 Prometheus。 但这样的行为通常并不可取,常规安全最佳实践要求您通过域名访问服务,而不是通过 IP:Port 直接访问。 通过域名访问意味着你只需要对外暴露一个 Nginx 服务,减小攻击面,并便于统一添加访问控制!

使用域名访问 Pigsty WebUI 时,您需要配置 DNS 解析,有以下几种方式:

  • 使用真域名,通过云厂商/域名厂商的 DNS 解析服务,将公网域名指向你的服务器公网IP
  • 使用内网域名,在内网 DNS 服务器上添加内网域名,并指向你服务器的内网IP地址
  • 使用本机域名,在你浏览器所在主机的(/etc/hosts)添加一条静态解析记录

通过本机访问

如果你是唯一的用户,那么可以直接修改本地 /etc/hosts 文件(Linux/MacOS), 在 Windows 系统上,您需要修改 C:\Windows\System32\drivers\etc\hosts 文件。 无论是什么系统,修改此文件通常需要管理员权限。

你可以添加以下静态解析记录,将 Pigsty 的默认域名指向你的服务器IP地址:

<ip_address>  h.pigsty a.pigsty p.pigsty g.pigsty

如果你会用到其他服务,也可以添加其他服务对应的域名解析记录:

10.10.10.10 h.pigsty a.pigsty p.pigsty g.pigsty
10.10.10.10 api.pigsty ddl.pigsty adm.pigsty cli.pigsty lab.pigsty
10.10.10.10 supa.pigsty noco.pigsty odoo.pigsty dify.pigsty

通过办公网访问

如果您的服务需要在办公网共享访问,例如让所有同事都可以通过域名访问,那么除了让同事都在自己的电脑上添加上面的静态解析记录之外,更正规的做法是使用内网DNS服务器。

您可以要求网络管理员在公司内部 DNS 服务器中添加相应的解析记录,将其指向 Nginx 服务器所在的 IP 地址。

当然还有另一种选项,Pigsty 默认安装也会在 53 端口提供一个 DNS 服务器,您可以通过配置 /etc/resolv.conf 文件或图形化界面配置 DNS 服务器,来使用内网域名访问部署在办公网中的服务。

通过互联网访问

如果您的服务需要直接暴露在互联网网上,通常您需要通过 DNS 服务商(Cloudflare,Aliyun DNS 等)解析互联网域名。 当然,你依然可以使用本地 DNS 服务器或本地静态解析记录来访问服务,但这样你的服务(除了默认的home服务器)就没法被互联网上的其他用户访问了。

您可以将所有需要暴露的域名都解析到 Nginx 服务器所在的 IP 地址。或者更简单的将 @* A 记录都解析到 Nginx 服务器所在的 IP 地址。 当你申请好新域名并将其指向你的服务器公网IP后,你还需要修改 Pigsty 的 infra_portal ,将域名填入各个服务器条目的 domain 字段中。

通过公网访问的域名,最佳实践是申请 HTTPS 证书,并始终使用 HTTPS 访问。我们将在下一节介绍这个主题。


配置HTTPS

HTTPS 是当代 Web 服务的主流配置,然而并非所有用户都熟悉 HTTPS 的配置方法。因此 Pigsty 默认为用户启用 HTTPS 支持。

如果你的 Nginx 只是对内网,办公网提供服务,那么 HTTPS 是一个 可选项; 如果你的 Nginx 需要对 互联网 提供服务,那么我们 强烈建议 您使用真实的域名与真正的 HTTPS 证书。 使用 HTTPS 不仅能够加密您的网络流量避免非法窥探篡改,而且能够避免访问 “未备案” 域名时恼人的体验。

本地域名与自签名证书

因为 Pigsty 默认使用的域名都是本地域名(x.pigsty),无法申请真正的域名 HTTPS 证书,所以 Pigsty 默认使用自签名证书。

Pigsty 会使用自签名的 CA 为所有的 infra_portal 中的域名签发证书。当然此证书并非权威证书,在浏览器中会提示证书不可信。你可以选择:

  • “我知道不安全,继续访问”
  • 使用 Chrome 浏览器时,你也可以使用敲击键入 thisisunsafe 来绕过证书验证
  • 将 Pigsty 自动生成的 pigsty-ca CA 证书加入浏览器所在电脑的 信任的根 CA 列表
  • 回退到 HTTP 或者 IP:Port 访问,不使用 HTTPS (不推荐)
  • 不使用本地域名与自签名证书,而是使用真正的域名与真正的 HTTPS 证书。

默认生成的自签名 CA 公钥和私钥位于 pigsty 本地目录的:files/pki/ca/ca.crtfiles/pki/ca/ca.key

真实域名与真证书

HTTPS 证书通常是一项收费服务,但是您可以使用诸如 certbot 这类工具申请免费的 Let’s Encrypt 证书。

使用 Certbot 申请真正 HTTPS 证书的教程将在下一篇 Certbot教程:申请免费HTTPS证书 中详细介绍。




2 - Certbot:申请公网HTTPS证书

如何使用 Certbot 申请免费的公网HTTPS证书?

Pigsty 自带了 Certbot 工具,并默认于 Infra 节点上安装启用。

这意味着你可以直接通过 certbot 命令行工具,为你的 Nginx 服务器与公网域名申请真正的 Let’Encrypt 免费 HTTPS 证书,而不是使用 Pigsty 自签名的 HTTPS 证书。

为了做到这一点,你需要:

  1. 确定哪些域名需要证书
  2. 将这些域名指向您的服务器
  3. 使用 Certbot 申请证书
  4. 将Nginx配置文件纳入管理
  5. 配置更新证书的定时任务
  6. 申请证书的一些注意事项

以下是如何去做的详细说明:


确定哪些域名需要证书

首先,您需要决定哪些 “上游服务” 需要真正的公网证书

infra_portal:
  home         : { domain: h.pigsty.cc }
  grafana      : { domain: g.pigsty.cc ,endpoint: "${admin_ip}:3000" ,websocket: true  }
  prometheus   : { domain: p.pigsty.cc ,endpoint: "${admin_ip}:9090" }
  alertmanager : { domain: a.pigsty.cc ,endpoint: "${admin_ip}:9093" }
  blackbox     : { endpoint: "${admin_ip}:9115" }
  loki         : { endpoint: "${admin_ip}:3100" }
  minio        : { domain: m.pigsty.cc    ,endpoint: "${admin_ip}:9001" ,scheme: https ,websocket: true }
  web          : { domain: pigsty.cc      ,path: "/www/web.cc" }
  repo         : { domain: repo.pigsty.cc ,path: "/www/repo"   }

例如在 infra_portal 中,假设我们要对外暴露以下五项服务:

  • Grafana 可视化监控面板的 g.pigsty.cc 域名
  • Prometheus 时序数据库的 p.pigsty.cc 域名
  • AlertManager 告警面板的 a.pigsty.cc 域名
  • Pigsty 文档站的 pigsty.cc 域名,指向本地文档目录
  • Pigsty 软件仓库的 repo.pigsty.cc 域名,指向软件仓库

这里的例子里特意没有选择为 home 主页申请真的 Let’s Encrypt 证书,原因见最后一节。


将这些域名指向您的服务器

接下来,您需要将上面选定的域名指向您服务器的 公网IP地址。 例如,Pigsty CC 站点的 IP 地址是 47.83.172.23,则可在域名注册商(如阿里云DNS控制台)上设置以下域名解析 A 记录:

47.83.172.23 pigsty.cc
47.83.172.23 g.pigsty.cc
47.83.172.23 p.pigsty.cc
47.83.172.23 a.pigsty.cc
47.83.172.23 repo.pigsty.cc 

修改完之后,可以使用


使用 Certbot 申请证书

第一次申请的时候,certbot 会提示你输入邮箱,并是否同意协议,按提示输入即可。

$ certbot --nginx -d pigsty.cc -d repo.pigsty.cc -d g.pigsty.cc -d p.pigsty.cc -d a.pigsty.cc
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
 (Enter 'c' to cancel): rh@vonng.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.4-April-3-2024.pdf. You must agree in
order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: N
Account registered.
Requesting a certificate for pigsty.cc and 4 more domains

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/pigsty.cc/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/pigsty.cc/privkey.pem
This certificate expires on 2025-05-18.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

Deploying certificate
Successfully deployed certificate for pigsty.cc to /etc/nginx/conf.d/web.conf
Successfully deployed certificate for repo.pigsty.cc to /etc/nginx/conf.d/repo.conf
Successfully deployed certificate for g.pigsty.cc to /etc/nginx/conf.d/grafana.conf
Successfully deployed certificate for p.pigsty.cc to /etc/nginx/conf.d/prometheus.conf
Successfully deployed certificate for a.pigsty.cc to /etc/nginx/conf.d/alertmanager.conf
Congratulations! You have successfully enabled HTTPS on https://pigsty.cc, https://repo.pigsty.cc, https://g.pigsty.cc, https://p.pigsty.cc, and https://a.pigsty.cc

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

首次申请之后,以后申请就可以省略这些步骤,直接使用这些命令了。

更简单的办法是直接以非交互式的方法调用,直接使用以下命令,传入你的邮箱和要注册的域名。

certbot --nginx --agree-tos --email rh@vonng.com -n -d supa.pigsty.cc

将Nginx配置文件纳入管理

使用 certbot 签发证书后,默认会修改 Nginx 的配置文件,将 HTTP 服务器重定向到 HTTPS 服务器,而这可能并非你想要的。

你可以通过修改 Pigsty 配置文件中的 infra_portal 参数,将 Certbot 已经 成功签发证书的域名配置到 Nginx 的配置文件中。

infra_portal:
  home         : { domain: h.pigsty.cc }
  grafana      : { domain: g.pigsty.cc ,endpoint: "${admin_ip}:3000" ,websocket: true , certbot: pigsty.cc }
  prometheus   : { domain: p.pigsty.cc ,endpoint: "${admin_ip}:9090"                  , certbot: pigsty.cc }
  alertmanager : { domain: a.pigsty.cc ,endpoint: "${admin_ip}:9093"                  , certbot: pigsty.cc }
  blackbox     : { endpoint: "${admin_ip}:9115" }
  loki         : { endpoint: "${admin_ip}:3100" }
  minio        : { domain: m.pigsty.cc    ,endpoint: "${admin_ip}:9001" ,scheme: https ,websocket: true    }
  web          : { domain: pigsty.cc      ,path: "/www/web.cc"                        , certbot: pigsty.cc }
  repo         : { domain: repo.pigsty.cc ,path: "/www/repo"                          , certbot: pigsty.cc }

这里,修改签发证书的服务器定义项,添加 certbot: <domain-name> ,这里的 <domain-name> 指的是 certbot 签发的文件名。 通常与 domain 一样,但如果你同时申请多个域名证书,certbot 会将其合并为一个证书,比如这里合并为两个文件:

Certificate is saved at: /etc/letsencrypt/live/pigsty.cc/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/pigsty.cc/privkey.pem

因此将证书中间的 pigsty.cc 抽出来填入 certbot,然后重新运行:

./infra.yml -t nginx_config,nginx_launch

即可让 Pigsty 重新生成 Nginx 配置文件,回退 Certbot 对配置进行的其他修改,只保留申请的证书。 以后需要续期更新证书的时候就不需要重复这个过程了,直接使用 certbot renew 即可。


配置更新证书的定时任务

默认情况下,申请的证书有效期为三个月,所以如果在证书有效期到期之前,你应该使用 certbot renew 对证书进行续期。

如果你需要更新证书,执行以下命令即可。

certbot renew

在真正执行之前,你可以使用 DryRun 模式来测试续期是否正常:

certbot renew --dry-run

如果你修改过 Nginx 配置文件,请务必确保 certbot 的修改不会影响你的配置文件。

你可以将这个命令配置为 crontab ,在每个月的第一天凌晨执行续期并打印日志。




3 - Docker:启用容器与镜像代理

如何在 Pigsty 启用 Docker 容器支持?Docker 的安装部署配置,以及如何解决DockerHub被“墙”的问题

Pigsty 提供了 DOCKER 模块,但默认并不安装。

您可以使用 docker.yml 剧本在指定节点上安装并启用 Docker。

./docker.yml -l <ip|group|cls>   # 在指定的节点、分组、集群上安装并启用 Docker

如何建配置代理服务器?

本文不会介绍如何“翻墙”,而是假设你已经有了一个可用的 HTTP(s) 代理服务器,应该如何配置,让 Docker 可以通过代理服务器,访问 docker hub 或 quay.io 等镜像站点:

你的代理服务器软件应该会提供一个形如:

  • http://<ip|domain>:<port> 或者 https://[user]:[pass]@<ip|domain>:<port> 的代理地址

例如,假设您使用的代理服务器在 127.0.0.1:12345 上提供服务,那么你可以通过以下环境变量来使用它:

export ALL_PROXY=http://192.168.0.106:8118
export HTTP_PROXY=http://192.168.0.106:8118
export HTTPS_PROXY=http://192.168.0.106:8118
export NO_PROXY="localhost,127.0.0.1,10.0.0.0/8,192.168.0.0/16,*.pigsty,*.aliyun.com,mirrors.*,*.myqcloud.com,*.tsinghua.edu.cn"

您可以使用 curl 命令,检验代理服务器是否可以正常工作,例如成功可以访问 Google,通常说明代理服务器工作正常。

curl -x http://192.168.0.106:8118 -I http://www.google.com

如何为Docker Daemon配置代理服务器?

如果您希望 Docker 在 Pull 镜像时使用代理服务器,那么应当在 pigsty.yml 配置文件的全局变量中,指定 proxy_env 参数:

all:
  vars:
    proxy_env:                        # global proxy env when downloading packages
      no_proxy: "localhost,127.0.0.1,10.0.0.0/8,192.168.0.0/16,*.pigsty,*.aliyun.com,mirrors.*,*.myqcloud.com,*.tsinghua.edu.cn"
      http_proxy: http://192.168.0.106:8118
      all_proxy: http://192.168.0.106:8118
      https_proxy: http://192.168.0.106:8118

那么当 Docker 剧本执行,时,这些配置会被渲染为 /etc/docker/daemon.json 中的代理配置:

{
  "proxies": {
    "http-proxy": "{{ proxy_env['http_proxy'] }}",
    "https-proxy": "{{ proxy_env['http_proxy'] }}",
    "no-proxy": "{{ proxy_env['no_proxy'] }}"
  }
}

请注意,Docker Daemon 不使用 all_proxy 参数

如果您希望手工指定代理服务器,可以选则直接修改 /etc/docker/daemon.json 中的 proxies 配置; 或者也可以修改 /lib/systemd/system/docker.service (Debian/Ubuntu) 与 /usr/lib/systemd/system/docker.service 的服务定义,在 [Service] 一节中添加环境变量声明,并重启生效:

[Service]
Environment="HTTP_PROXY=http://192.168.0.106:8118"
Environment="HTTPS_PROXY=http://192.168.0.106:8118"
Environment="NO_PROXY=localhost,127.0.0.1,10.0.0.0/8,192.168.0.0/16,*.pigsty,*.aliyun.com,mirrors.*,*.myqcloud.com,*.tsinghua.edu.cn"

重启后生效:

systemctl restart docker

如何使用其他镜像站点?

您可以在 docker_registry_mirrors 参数中指定其他镜像站点,例如阿里云、腾讯云、清华大学等镜像站点:

[ "https://mirror.ccs.tencentyun.com" ]         # tencent cloud mirror, intranet only
["https://registry.cn-hangzhou.aliyuncs.com"]   # aliyun cloud mirror, login required

不过目前来看,所有位于中国大陆的 DockerHub 公有镜像站都已经被封禁了,建议使用代理服务器直接访问 Docker Hub

如果您需要使用其他镜像站,例如 quay.io,可以首先执行:

docker login quay.io
username #> # input your username
password #> # input your password

4 - 使用 PostgreSQL 作为 Ansible 的配置清单与 CMDB

使用 PostgreSQL ,而不是静态 YAML 配置文件作为 Ansible 的配置源,从而更好地与外部系统集成整合。

您可以使用 PostgreSQL 作为 Pigsty 的配置源,替代静态 YAML 配置文件。

使用 CMDB 作为 Ansible 的动态 配置清单 具有一些优点:元数据以高度结构化的方式以数据表的形式呈现,并通过数据库约束确保一致性。 同时,使用 CMDB 允许您使用第三方的工具来编辑管理 Pigsty 元数据,便于与外部系统相互集成。


Ansible配置原理

Pigsty 默认的配置文件路径在 ansible.cfg 中指定为:inventory = pigsty.yml

修改该参数,可以更改默认使用的配置文件路径。如果您将其指向一个可执行的脚本文件,那么 Ansible 会使用动态 Inventory 机制,执行该脚本,并期待该脚本返回一份配置文件。

修改配置源实质上是编辑Pigsty目录下的 ansible.cfg 实现的:

---
inventory = pigsty.yml
+++
inventory = inventory.sh

inventory.sh 则是一个从 PostgreSQL CMDB 的记录中,生成等效 YAML/JSON 配置文件的简单脚本。

你可以使用 bin/inventory_cmdb 切换到动态的 CMDB 清单, 使用 bin/inventory_conf 返回到本地配置文件。 你还需要使用 bin/inventory_load 将当前的配置文件清单加载到 CMDB。


加载配置

Pigsty CMDB的模式会在pg-meta元数据库初始化时自动创建(files/cmdb.sql),位于meta数据库的pigsty 模式中。使用bin/inventory_load可以将静态配置文件加载至CMDB中。

必须在元节点完整执行 infra.yml 安装完毕后,方可使用 CMDB

usage: inventory_load [-h] [-p PATH] [-d CMDB_URL]

load config arguments

optional arguments:
  -h, --help            show this help message and exit„
  -p PATH, --path PATH  config path, ${PIGSTY_HOME}/pigsty.yml by default
  -d DATA, --data DATA  postgres cmdb pgurl, ${METADB_URL} by default

默认情况下,不带参数执行该脚本将会把$PIGSTY_HOME/pigsty.yml的名称载入默认CMDB中。

bin/inventory_load
bin/inventory_load -p conf/demo.yml
bin/inventory_load -p conf/prod.yml -d postgresql://dbuser_meta:DBUser.Meta@10.10.10.10:5432/meta

当原有配置文件加载至 CMDB 作为初始数据后,即可配置 Ansible 使用 CMDB 作为配置源:

bin/inventory_cmdb

您可以切换回静态配置文件:

bin/inventory_conf

5 - 使用 PostgreSQL 作为 Grafana 后端数据库

使用 PostgreSQL 而不是 SQLite 作为 Grafana 后端使用的远程存储数据库,获取更好的性能与可用性。

您可以使用 PostgreSQL 作为 Grafana 后端使用的数据库。

这是了解Pigsty部署系统使用方式的好机会,完成此教程,您会了解:


太长不看

vi pigsty.yml # 取消注释DB/User定义:dbuser_grafana  grafana 
bin/pgsql-user  pg-meta  dbuser_grafana
bin/pgsql-db    pg-meta  grafana

psql postgres://dbuser_grafana:DBUser.Grafana@meta:5436/grafana -c \
  'CREATE TABLE t(); DROP TABLE t;' # 检查连接串可用性
  
vi /etc/grafana/grafana.ini # 修改 [database] type url
systemctl restart grafana-server

创建数据库集群

我们可以在pg-meta上定义一个新的数据库grafana,也可以在新的机器节点上创建一个专用于Grafana的数据库集群:pg-grafana

定义集群

如果需要创建新的专用数据库集群pg-grafana,部署在10.10.10.1110.10.10.12两台机器上,可以使用以下配置文件:

pg-grafana: 
  hosts: 
    10.10.10.11: {pg_seq: 1, pg_role: primary}
    10.10.10.12: {pg_seq: 2, pg_role: replica}
  vars:
    pg_cluster: pg-grafana
    pg_databases:
      - name: grafana
        owner: dbuser_grafana
        revokeconn: true
        comment: grafana primary database
    pg_users:
      - name: dbuser_grafana
        password: DBUser.Grafana
        pgbouncer: true
        roles: [dbrole_admin]
        comment: admin user for grafana database

创建集群

使用以下命令完成数据库集群pg-grafana的创建:pgsql.yml

bin/createpg pg-grafana    # 初始化pg-grafana集群

该命令实际上调用了Ansible Playbook pgsql.yml 创建数据库集群。

./pgsql.yml -l pg-grafana  # 实际执行的等效Ansible剧本命令 

定义在 pg_userspg_databases 中的业务用户与业务数据库会在集群初始化时自动创建,因此使用该配置时,集群创建完毕后,(在没有DNS支持的情况下)您可以使用以下连接串访问数据库(任一即可):

postgres://dbuser_grafana:DBUser.Grafana@10.10.10.11:5432/grafana # 主库直连
postgres://dbuser_grafana:DBUser.Grafana@10.10.10.11:5436/grafana # 直连default服务
postgres://dbuser_grafana:DBUser.Grafana@10.10.10.11:5433/grafana # 连接串读写服务

postgres://dbuser_grafana:DBUser.Grafana@10.10.10.12:5432/grafana # 主库直连
postgres://dbuser_grafana:DBUser.Grafana@10.10.10.12:5436/grafana # 直连default服务
postgres://dbuser_grafana:DBUser.Grafana@10.10.10.12:5433/grafana # 连接串读写服务

因为默认情况下Pigsty安装在单个元节点上,接下来的步骤我们会在已有的pg-meta数据库集群上创建Grafana所需的用户与数据库,而并非使用这里创建的pg-grafana集群。


创建Grafana业务用户

通常业务对象管理的惯例是:先创建用户,再创建数据库。 因为如果为数据库配置了owner,数据库对相应的用户存在依赖。

定义用户

要在pg-meta集群上创建用户dbuser_grafana,首先将以下用户定义添加至pg-meta集群定义中:

添加位置:all.children.pg-meta.vars.pg_users

- name: dbuser_grafana
  password: DBUser.Grafana
  comment: admin user for grafana database
  pgbouncer: true
  roles: [ dbrole_admin ]

如果您在这里定义了不同的密码,请在后续步骤中将相应参数替换为新密码

创建用户

使用以下命令完成dbuser_grafana用户的创建(任一均可)。

bin/pgsql-user pg-meta dbuser_grafana # 在pg-meta集群上创建`dbuser_grafana`用户

实际上调用了Ansible Playbook pgsql-createuser.yml 创建用户

./pgsql-user.yml -l pg-meta -e pg_user=dbuser_grafana  # Ansible

dbrole_admin 角色具有在数据库中执行DDL变更的权限,这正是Grafana所需要的。


创建Grafana业务数据库

定义数据库

创建业务数据库的方式与业务用户一致,首先在pg-meta的集群定义中添加新数据库grafana定义

添加位置:all.children.pg-meta.vars.pg_databases

- { name: grafana, owner: dbuser_grafana, revokeconn: true }

创建数据库

使用以下命令完成grafana数据库的创建(任一均可)。

bin/pgsql-db pg-meta grafana # 在`pg-meta`集群上创建`grafana`数据库

实际上调用了Ansible Playbook pgsql-createdb.yml 创建数据库

./pgsql-db.yml -l pg-meta -e pg_database=grafana # 实际执行的Ansible剧本

使用Grafana业务数据库

检查连接串可达性

您可以使用不同的服务接入方式访问数据库,例如:

postgres://dbuser_grafana:DBUser.Grafana@meta:5432/grafana # 直连
postgres://dbuser_grafana:DBUser.Grafana@meta:5436/grafana # default服务
postgres://dbuser_grafana:DBUser.Grafana@meta:5433/grafana # primary服务

这里,我们将使用通过负载均衡器直接访问主库的 Default服务访问数据库。

首先检查连接串是否可达,以及是否有权限执行DDL命令。

psql postgres://dbuser_grafana:DBUser.Grafana@meta:5436/grafana -c \
  'CREATE TABLE t(); DROP TABLE t;'

直接修改Grafana配置

为了让Grafana使用 Postgres 数据源,您需要编辑 /etc/grafana/grafana.ini,并修改配置项:

[database]
;type = sqlite3
;host = 127.0.0.1:3306
;name = grafana
;user = root
# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;"""
;password =
;url =

将默认的配置项修改为:

[database]
type = postgres
url =  postgres://dbuser_grafana:DBUser.Grafana@meta/grafana

随后重启Grafana即可:

systemctl restart grafana-server

从监控系统中看到新增的 grafana 数据库已经开始有活动,则说明Grafana已经开始使用Postgres作为首要后端数据库了。 但一个新的问题是,Grafana中原有的Dashboards与Datasources都消失了!这里需要重新导入监控面板Postgres数据源


管理Grafana监控面板

您可以使用管理用户前往 Pigsty 目录下的files/ui目录,执行grafana.py init重新加载Pigsty监控面板。

cd ~/pigsty/files/ui
./grafana.py init    # 使用当前目录下的Dashboards初始化Grafana监控面板

执行结果:

vagrant@meta:~/pigsty/files/ui
$ ./grafana.py init
Grafana API: admin:pigsty @ http://10.10.10.10:3000
init dashboard : home.json
init folder pgcat
init dashboard: pgcat / pgcat-table.json
init dashboard: pgcat / pgcat-bloat.json
init dashboard: pgcat / pgcat-query.json
init folder pgsql
init dashboard: pgsql / pgsql-replication.json
init dashboard: pgsql / pgsql-table.json
init dashboard: pgsql / pgsql-activity.json
init dashboard: pgsql / pgsql-cluster.json
init dashboard: pgsql / pgsql-node.json
init dashboard: pgsql / pgsql-database.json
init dashboard: pgsql / pgsql-xacts.json
init dashboard: pgsql / pgsql-overview.json
init dashboard: pgsql / pgsql-session.json
init dashboard: pgsql / pgsql-tables.json
init dashboard: pgsql / pgsql-instance.json
init dashboard: pgsql / pgsql-queries.json
init dashboard: pgsql / pgsql-alert.json
init dashboard: pgsql / pgsql-service.json
init dashboard: pgsql / pgsql-persist.json
init dashboard: pgsql / pgsql-proxy.json
init dashboard: pgsql / pgsql-query.json
init folder pglog
init dashboard: pglog / pglog-instance.json
init dashboard: pglog / pglog-analysis.json
init dashboard: pglog / pglog-session.json

该脚本会侦测当前的环境(安装时定义于~/pigsty),获取Grafana的访问信息,并将监控面板中的URL连接占位符域名(*.pigsty)替换为真实使用的域名。

export GRAFANA_ENDPOINT=http://10.10.10.10:3000
export GRAFANA_USERNAME=admin
export GRAFANA_PASSWORD=pigsty

export NGINX_UPSTREAM_YUMREPO=yum.pigsty
export NGINX_UPSTREAM_CONSUL=c.pigsty
export NGINX_UPSTREAM_PROMETHEUS=p.pigsty
export NGINX_UPSTREAM_ALERTMANAGER=a.pigsty
export NGINX_UPSTREAM_GRAFANA=g.pigsty
export NGINX_UPSTREAM_HAPROXY=h.pigsty

题外话,使用grafana.py clean会清空目标监控面板,使用grafana.py load会加载当前目录下所有监控面板,当Pigsty的监控面板发生变更,可以使用这两个命令升级所有的监控面板。

管理Postgres数据源

当使用 pgsql.yml 创建新PostgreSQL集群,或使用pgsql-createdb.yml创建新业务数据库时,Pigsty会在Grafana中注册新的PostgreSQL数据源,您可以使用默认的监控用户通过Grafana直接访问目标数据库实例。应用pgcat的绝大部分功能有赖于此。

要注册Postgres数据库,可以使用pgsql.yml中的register_grafana任务:

./pgsql.yml -t register_grafana             # 重新注册当前环境中所有Postgres数据源
./pgsql.yml -t register_grafana -l pg-test  # 重新注册 pg-test 集群中所有的数据库

一步到位更新Grafana

您可以直接通过修改Pigsty配置文件,更改Grafana使用的后端数据源,一步到位的完成切换Grafana后端数据库的工作。编辑pigsty.ymlgrafana_databasegrafana_pgurl参数,将其修改为:

grafana_database: postgres
grafana_pgurl: postgres://dbuser_grafana:DBUser.Grafana@meta:5436/grafana

然后重新执行 infral.yml中的grafana任务,即可完成 Grafana升级

./infra.yml -t grafana

6 - 使用 TimescaleDB + Promscale 存储 Prometheus 时序指标数据

您可以通过 Promscale,使用TimescaleDB持久化Prometheus指标数据。

虽然这并不是推荐的行为,但这是了解Pigsty部署系统使用方式的好机会。

注意,使用 Promscale 存储 Prometheus 指标占用的存储空间大约是 Prometheus 的 4 倍,但是可以使用 SQL 来查询分析 Prometheus 监控指标。

准备Postgres数据库

vi pigsty.yml # 取消注释DB/User定义:dbuser_prometheus  prometheus

pg_databases:                           # define business users/roles on this cluster, array of user definition
  - { name: prometheus, owner: dbuser_prometheus , revokeconn: true, comment: prometheus primary database }
pg_users:                           # define business users/roles on this cluster, array of user definition
  - {name: dbuser_prometheus , password: DBUser.Prometheus ,pgbouncer: true , createrole: true,  roles: [dbrole_admin], comment: admin user for prometheus database }

创建 Prometheus 业务数据库与业务用户。

bin/createuser  pg-meta  dbuser_prometheus
bin/createdb    pg-meta  prometheus

检查数据库可用性并创建扩展

psql postgres://dbuser_prometheus:DBUser.Prometheus@10.10.10.10:5432/prometheus -c 'CREATE EXTENSION timescaledb;'

配置Promscale

在元节点上执行以下命令安装 promscale

yum install -y promscale 

如果默认软件包中没有,可以直接下载:

wget https://github.com/timescale/promscale/releases/download/0.6.1/promscale_0.6.1_Linux_x86_64.rpm
sudo rpm -ivh promscale_0.6.1_Linux_x86_64.rpm

编辑 promscale 的配置文件 /etc/sysconfig/promscale.conf

PROMSCALE_DB_HOST="127.0.0.1"
PROMSCALE_DB_NAME="prometheus"
PROMSCALE_DB_PASSWORD="DBUser.Prometheus"
PROMSCALE_DB_PORT="5432"
PROMSCALE_DB_SSL_MODE="disable"
PROMSCALE_DB_USER="dbuser_prometheus"

最后启动promscale,它会访问安装有 timescaledb 的数据库实例,并创建所需的schema

# launch 
cat /usr/lib/systemd/system/promscale.service
systemctl start promscale && systemctl status promscale

配置Prometheus

Prometheus可以使用Remote Write/ Remote Read的方式,通过Promscale,使用Postgres作为远程存储。

编辑Prometheus配置文件:

vi /etc/prometheus/prometheus.yml

添加以下记录:

remote_write:
  - url: "http://127.0.0.1:9201/write"
remote_read:
  - url: "http://127.0.0.1:9201/read"

重启Prometheus后,监控数据即可放入Postgres中。

systemctl restart prometheus

7 - 使用 Keepalived 为 Pigsty 节点集群配置二层 VIP

如何在 Pigsty 中为节点集群绑定一个二层 VIP?什么情况下用不了?如何解决?

您可以在节点集群上绑定一个可选的 L2 VIP —— 前提条件是:集群中的所有节点都在一个二层网络中

在节点集群(任何一个 Ansible Group,包括数据库集群定义都可以视作一个节点集群)上,启用 vip_enabled 参数,即可在节点集群上启用 Keepalived ,绑定一个2层 VIP。

proxy:
  hosts:
    10.10.10.29: { nodename: proxy-1 } # 您可以显式指定初始的 VIP 角色:MASTER / BACKUP
    10.10.10.30: { nodename: proxy-2 } # , vip_role: master }
  vars:
    node_cluster: proxy
    vip_enabled: true
    vip_vrid: 128
    vip_address: 10.10.10.99
    vip_interface: eth1

使用以下命令,刷新节点的 Keepalived 配置,并生效:

./node.yml -l proxy -t node_vip     # 首次启用 VIP 
./node.yml -l proxy -t vip_refresh  # 刷新 vip 配置(例如指定 master)

专用的场景

针对 PostgreSQL 高可用场景, Pigsty 提供了基于 vip-manager 的 L2 VIP 解决方案

vip-manager 是一个独立的组件,它读取 etcd 中的 PostgreSQL 集群领导者,并在领导者所在节点上绑定一个 L2 VIP。

因此我们建议您使用 vip-manager 来实现 PostgreSQL 的高可用性,而不是使用 Keepalived 来实现。请参考 PGSQL VIP 了解更多。


不适用的场景

在诸如 AWS,阿里云这样的云环境中,通常不支持使用 L2 VIP。

在这种情况下,我们建议您使用四层负载均衡器来实现类似的功能。

例如 Pigsty 提供了 HAProxy 的配置支持。

8 - 使用 VIP-Manager 为 PostgreSQL 集群配置二层 VIP

如何在 Pigsty 中为 PostgreSQL 集群绑定一个二层 VIP?

您可以在 PostgreSQL 集群上绑定一个可选的 L2 VIP —— 前提条件是:集群中的所有节点都在一个二层网络中。

这个 L2 VIP 强制使用 Master - Backup 模式,Master 始终指向在数据库集群主库实例所在的节点。

这个 VIP 由 VIP-Manager 组件管理,它会从 DCS (etcd) 中直接读取由 Patroni 写入的 Leader Key,从而判断自己是否是 Master。


启用VIP

在 PostgreSQL 集群上定义 pg_vip_enabled 参数为 true,即可在集群上启用 VIP 组件。当然您也可以在全局配置中启用此配置项。

# pgsql 3 node ha cluster: pg-test
pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }   # primary instance, leader of cluster
    10.10.10.12: { pg_seq: 2, pg_role: replica }   # replica instance, follower of leader
    10.10.10.13: { pg_seq: 3, pg_role: replica, pg_offline_query: true } # replica with offline access
  vars:
    pg_cluster: pg-test           # define pgsql cluster name
    pg_users:  [{ name: test , password: test , pgbouncer: true , roles: [ dbrole_admin ] }]
    pg_databases: [{ name: test }]

    # 启用 L2 VIP
    pg_vip_enabled: true
    pg_vip_address: 10.10.10.3/24
    pg_vip_interface: eth1

请注意,pg_vip_address 必须是一个合法的 IP 地址,带有网段,且在当前二层网络中可用。

请注意,pg_vip_interface 必须是一个合法的网络接口名,并且应当是与 inventory 中使用 IPv4 地址一致的网卡。 如果集群成员的网卡名不一样,用户应当为每个实例显式指定 pg_vip_interface 参数,例如:

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary , pg_vip_interface: eth0  }
    10.10.10.12: { pg_seq: 2, pg_role: replica , pg_vip_interface: eth1  }
    10.10.10.13: { pg_seq: 3, pg_role: replica , pg_vip_interface: ens33 }
  vars:
    pg_cluster: pg-test           # define pgsql cluster name
    pg_users:  [{ name: test , password: test , pgbouncer: true , roles: [ dbrole_admin ] }]
    pg_databases: [{ name: test }]

    # 启用 L2 VIP
    pg_vip_enabled: true
    pg_vip_address: 10.10.10.3/24
    #pg_vip_interface: eth1

使用以下命令,刷新 PG 的 vip-manager 配置并重启生效:

./pgsql.yml -t pg_vip 

9 - Citus:部署原生高可用集群

如何部署 Citus 高可用分布式集群?

Citus 是一个 PostgreSQL 扩展,可以将 PostgreSQL 原地转换为一个分布式数据库,并实现在多个节点上水平扩展,以处理大量数据和大量查询。

Patroni 在 v3.0 后,提供了对 Citus 原生高可用的支持,简化了 Citus 集群的搭建,Pigsty 也对此提供了原生支持。


Citus集群

Pigsty 原生支持 Citus。可以参考 conf/citus.yml,以及更复杂的 十节点集群

这里使用 Pigsty 四节点沙箱,定义了一个 Citus 集群 pg-citus,其中包括一个两节点的协调者(Coordinator)集群 pg-citus0,以及两个工作者(Worker)集群 pg-citus1pg-citus2

pg-citus:
  hosts:
    10.10.10.10: { pg_group: 0, pg_cluster: pg-citus0 ,pg_vip_address: 10.10.10.2/24 ,pg_seq: 1, pg_role: primary }
    10.10.10.11: { pg_group: 0, pg_cluster: pg-citus0 ,pg_vip_address: 10.10.10.2/24 ,pg_seq: 2, pg_role: replica }
    10.10.10.12: { pg_group: 1, pg_cluster: pg-citus1 ,pg_vip_address: 10.10.10.3/24 ,pg_seq: 1, pg_role: primary }
    10.10.10.13: { pg_group: 2, pg_cluster: pg-citus2 ,pg_vip_address: 10.10.10.4/24 ,pg_seq: 1, pg_role: primary }
  vars:
    pg_mode: citus                            # pgsql cluster mode: citus
    pg_version: 16                            # citus does not have pg16 available
    pg_shard: pg-citus                        # citus shard name: pg-citus
    pg_primary_db: citus                      # primary database used by citus
    pg_vip_enabled: true                      # enable vip for citus cluster
    pg_vip_interface: eth1                    # vip interface for all members
    pg_dbsu_password: DBUser.Postgres         # all dbsu password access for citus cluster
    pg_extensions: [ citus, postgis, pgvector, topn, pg_cron, hll ]  # install these extensions
    pg_libs: 'citus, pg_cron, pg_stat_statements' # citus will be added by patroni automatically
    pg_users: [{ name: dbuser_citus ,password: DBUser.Citus ,pgbouncer: true ,roles: [ dbrole_admin ]    }]
    pg_databases: [{ name: citus ,owner: dbuser_citus ,extensions: [ citus, vector, topn, pg_cron, hll ] }]
    pg_parameters:
      cron.database_name: citus
      citus.node_conninfo: 'sslmode=require sslrootcert=/pg/cert/ca.crt sslmode=verify-full'
    pg_hba_rules:
      - { user: 'all' ,db: all  ,addr: 127.0.0.1/32  ,auth: ssl   ,title: 'all user ssl access from localhost' }
      - { user: 'all' ,db: all  ,addr: intra         ,auth: ssl   ,title: 'all user ssl access from intranet'  }

相比标准 PostgreSQL 集群,Citus 集群的配置有一些特殊之处,如下所示:

首先,你需要确保 Citus 扩展被下载,安装,加载并启用,这涉及到以下四个参数

  • repo_packages:必须包含 citus 扩展,或者你需要使用带有 Citus 扩展的 PostgreSQL 离线安装包。
  • pg_extensions:必须包含 citus 扩展,即你必须在每个节点上安装 citus 扩展。
  • pg_libs:必须包含 citus 扩展,而且首位必须为 citus,但现在 Patroni 会自动完成这件事了。
  • pg_databases: 这里要定义一个首要数据库,该数据库必须安装 citus 扩展。

其次,你需要确保 Citus 集群的配置正确:

  • pg_mode: 必须设置为 citus,从而告知 Patroni 使用 Citus 模式。
  • pg_primary_db:必须指定一个首要数据库的名称,该数据库必须安装 citus 扩展,这里名为 citus
  • pg_shard:必须指定一个统一的名称,字符串,作为所有水平分片PG集群的集群名称前缀,这里为 pg-citus
  • pg_group:必须指定一个分片号,从零开始依次分配的整数,0 号固定代表协调者集群,其他为 Worker 集群。
  • pg_cluster 必须与 pg_shardpg_group 组合后的结果对应。
  • pg_dbsu_password:必须设置为非空的纯文本密码,否则 Citus 无法正常工作。
  • pg_parameters:建议设置 citus.node_conninfo 参数,强制要求 SSL 访问并要求节点间验证客户端证书。

配置完成后,您可以像创建普通 PostgreSQL 集群一样,使用 pgsql.yml 剧本部署 Citus 集群。


管理Citus集群

定义好 Citus 集群后,部署 Citus 集群同样使用的剧本 pgsql.yml

./pgsql.yml -l pg-citus    # 部署 Citus 集群 pg-citus

使用任意成员的 DBSU(postgres)用户,都能通过 patronictlalias: pg) 列出 Citus 集群的状态:

$ pg list
+ Citus cluster: pg-citus ----------+---------+-----------+----+-----------+--------------------+
| Group | Member      | Host        | Role    | State     | TL | Lag in MB | Tags               |
+-------+-------------+-------------+---------+-----------+----+-----------+--------------------+
|     0 | pg-citus0-1 | 10.10.10.10 | Leader  | running   |  1 |           | clonefrom: true    |
|       |             |             |         |           |    |           | conf: tiny.yml     |
|       |             |             |         |           |    |           | spec: 20C.40G.125G |
|       |             |             |         |           |    |           | version: '16'      |
+-------+-------------+-------------+---------+-----------+----+-----------+--------------------+
|     1 | pg-citus1-1 | 10.10.10.11 | Leader  | running   |  1 |           | clonefrom: true    |
|       |             |             |         |           |    |           | conf: tiny.yml     |
|       |             |             |         |           |    |           | spec: 10C.20G.125G |
|       |             |             |         |           |    |           | version: '16'      |
+-------+-------------+-------------+---------+-----------+----+-----------+--------------------+
|     2 | pg-citus2-1 | 10.10.10.12 | Leader  | running   |  1 |           | clonefrom: true    |
|       |             |             |         |           |    |           | conf: tiny.yml     |
|       |             |             |         |           |    |           | spec: 10C.20G.125G |
|       |             |             |         |           |    |           | version: '16'      |
+-------+-------------+-------------+---------+-----------+----+-----------+--------------------+
|     2 | pg-citus2-2 | 10.10.10.13 | Replica | streaming |  1 |         0 | clonefrom: true    |
|       |             |             |         |           |    |           | conf: tiny.yml     |
|       |             |             |         |           |    |           | spec: 10C.20G.125G |
|       |             |             |         |           |    |           | version: '16'      |
+-------+-------------+-------------+---------+-----------+----+-----------+--------------------+

您可以将每个水平分片集群视为一个独立的 PGSQL 集群,使用 pg (patronictl) 命令管理它们。 但是务必注意,当你使用 pg 命令管理 Citus 集群时,需要额外使用 --group 参数指定集群分片号

pg list pg-citus --group 0   # 需要使用 --group 0 指定集群分片号

Citus 中有一个名为 pg_dist_node 的系统表,用于记录 Citus 集群的节点信息,Patroni 会自动维护该表。

PGURL=postgres://postgres:DBUser.Postgres@10.10.10.10/citus

psql $PGURL -c 'SELECT * FROM pg_dist_node;'       # 查看节点信息
 nodeid | groupid |  nodename   | nodeport | noderack | hasmetadata | isactive | noderole  | nodecluster | metadatasynced | shouldhaveshards
--------+---------+-------------+----------+----------+-------------+----------+-----------+-------------+----------------+------------------
      1 |       0 | 10.10.10.10 |     5432 | default  | t           | t        | primary   | default     | t              | f
      4 |       1 | 10.10.10.12 |     5432 | default  | t           | t        | primary   | default     | t              | t
      5 |       2 | 10.10.10.13 |     5432 | default  | t           | t        | primary   | default     | t              | t
      6 |       0 | 10.10.10.11 |     5432 | default  | t           | t        | secondary | default     | t              | f

此外,你还可以查看用户认证信息(仅限超级用户访问):

$ psql $PGURL -c 'SELECT * FROM pg_dist_authinfo;'   # 查看节点认证信息(仅限超级用户访问)

然后,你可以使用普通业务用户(例如,具有 DDL 权限的 dbuser_citus)来访问 Citus 集群:

psql postgres://dbuser_citus:DBUser.Citus@10.10.10.10/citus -c 'SELECT * FROM pg_dist_node;'

使用Citus集群

在使用 Citus 集群时,我们强烈建议您先阅读 Citus 官方文档,了解其架构设计与核心概念。

其中核心是了解 Citus 中的五种表,以及其特点与应用场景:

  • 分布式表(Distributed Table)
  • 参考表(Reference Table)
  • 本地表(Local Table)
  • 本地管理表(Local Management Table)
  • 架构表(Schema Table)

在协调者节点上,您可以创建分布式表和引用表,并从任何数据节点查询它们。从 11.2 开始,任何 Citus 数据库节点都可以扮演协调者的角色了。

我们可以使用 pgbench 来创建一些表,并将其中的主表(pgbench_accounts)分布到各个节点上,然后将其他小表作为引用表:

PGURL=postgres://dbuser_citus:DBUser.Citus@10.10.10.10/citus
pgbench -i $PGURL

psql $PGURL <<-EOF
SELECT create_distributed_table('pgbench_accounts', 'aid'); SELECT truncate_local_data_after_distributing_table('public.pgbench_accounts');
SELECT create_reference_table('pgbench_branches')         ; SELECT truncate_local_data_after_distributing_table('public.pgbench_branches');
SELECT create_reference_table('pgbench_history')          ; SELECT truncate_local_data_after_distributing_table('public.pgbench_history');
SELECT create_reference_table('pgbench_tellers')          ; SELECT truncate_local_data_after_distributing_table('public.pgbench_tellers');
EOF

执行读写测试:

pgbench -nv -P1 -c10 -T500 postgres://dbuser_citus:DBUser.Citus@10.10.10.10/citus      # 直连协调者 5432 端口
pgbench -nv -P1 -c10 -T500 postgres://dbuser_citus:DBUser.Citus@10.10.10.10:6432/citus # 通过连接池,减少客户端连接数压力,可以有效提高整体吞吐。
pgbench -nv -P1 -c10 -T500 postgres://dbuser_citus:DBUser.Citus@10.10.10.13/citus      # 任意 primary 节点都可以作为 coordinator
pgbench --select-only -nv -P1 -c10 -T500 postgres://dbuser_citus:DBUser.Citus@10.10.10.11/citus # 可以发起只读查询

更严肃的生产部署

要将 Citus 用于生产环境,您通常需要为 Coordinator 和每个 Worker 集群设置流复制物理副本。

例如,在 simu.yml 中定义了一个 10 节点的 Citus 集群。

pg-citus: # citus group
  hosts:
    10.10.10.50: { pg_group: 0, pg_cluster: pg-citus0 ,pg_vip_address: 10.10.10.60/24 ,pg_seq: 0, pg_role: primary }
    10.10.10.51: { pg_group: 0, pg_cluster: pg-citus0 ,pg_vip_address: 10.10.10.60/24 ,pg_seq: 1, pg_role: replica }
    10.10.10.52: { pg_group: 1, pg_cluster: pg-citus1 ,pg_vip_address: 10.10.10.61/24 ,pg_seq: 0, pg_role: primary }
    10.10.10.53: { pg_group: 1, pg_cluster: pg-citus1 ,pg_vip_address: 10.10.10.61/24 ,pg_seq: 1, pg_role: replica }
    10.10.10.54: { pg_group: 2, pg_cluster: pg-citus2 ,pg_vip_address: 10.10.10.62/24 ,pg_seq: 0, pg_role: primary }
    10.10.10.55: { pg_group: 2, pg_cluster: pg-citus2 ,pg_vip_address: 10.10.10.62/24 ,pg_seq: 1, pg_role: replica }
    10.10.10.56: { pg_group: 3, pg_cluster: pg-citus3 ,pg_vip_address: 10.10.10.63/24 ,pg_seq: 0, pg_role: primary }
    10.10.10.57: { pg_group: 3, pg_cluster: pg-citus3 ,pg_vip_address: 10.10.10.63/24 ,pg_seq: 1, pg_role: replica }
    10.10.10.58: { pg_group: 4, pg_cluster: pg-citus4 ,pg_vip_address: 10.10.10.64/24 ,pg_seq: 0, pg_role: primary }
    10.10.10.59: { pg_group: 4, pg_cluster: pg-citus4 ,pg_vip_address: 10.10.10.64/24 ,pg_seq: 1, pg_role: replica }
  vars:
    pg_mode: citus                            # pgsql cluster mode: citus
    pg_version: 16                            # citus does not have pg16 available
    pg_shard: pg-citus                        # citus shard name: pg-citus
    pg_primary_db: citus                      # primary database used by citus
    pg_vip_enabled: true                      # enable vip for citus cluster
    pg_vip_interface: eth1                    # vip interface for all members
    pg_dbsu_password: DBUser.Postgres         # enable dbsu password access for citus
    pg_extensions: [ citus, postgis, pgvector, topn, pg_cron, hll ]  # install these extensions
    pg_libs: 'citus, pg_cron, pg_stat_statements' # citus will be added by patroni automatically
    pg_users: [{ name: dbuser_citus ,password: DBUser.Citus ,pgbouncer: true ,roles: [ dbrole_admin ]    }]
    pg_databases: [{ name: citus ,owner: dbuser_citus ,extensions: [ citus, vector, topn, pg_cron, hll ] }]
    pg_parameters:
      cron.database_name: citus
      citus.node_conninfo: 'sslrootcert=/pg/cert/ca.crt sslmode=verify-full'
    pg_hba_rules:
      - { user: 'all' ,db: all  ,addr: 127.0.0.1/32  ,auth: ssl   ,title: 'all user ssl access from localhost' }
      - { user: 'all' ,db: all  ,addr: intra         ,auth: ssl   ,title: 'all user ssl access from intranet'  }

我们将在后续教程中覆盖一系列关于 Citus 的高级主题

  • 读写分离
  • 故障处理
  • 一致性备份与恢复
  • 高级监控与问题诊断
  • 连接池

10 - 高可用演习:3坏2如何处理

高可用典型场景处理预案:三节点坏了两个节点,高可用不生效了,怎么从紧急状态中恢复?

如果经典3节点高可用部署同时出现两台(多数主体)故障,系统通常无法自动完成故障切换,需要人工介入:

首先判断另外两台服务器的情况,如果短时间内可以拉起,优先选择拉起另外两台服务。否则进入 紧急止血流程

紧急止血流程假设您的管理节点故障,只有单台普通数据库节点存活,在这种情况下,最快的恢复操作流程为:

  • 调整 HAProxy 配置,将流量指向主库。
  • 关闭 Patroni,手动提升 PostgreSQL 从库为主库。

调整HAProxy配置

如果你通过其他方式绕开 HAProxy 访问集群,那么可以跳过这一步。 如果你通过 HAProxy 方式访问数据库集群,那么你需要调整负载均衡配置,将读写流量手工指向主库。

  • 编辑 /etc/haproxy/<pg_cluster>-primary.cfg 配置文件,其中 <pg_cluster> 为你的 PostgreSQL 集群名称,例如 pg-meta
  • 将健康检查配置选项注释,停止进行健康鉴擦好
  • 将服务器列表中,其他两台故障的机器注释掉,只保留当前主库服务器。
listen pg-meta-primary
    bind *:5433
    mode tcp
    maxconn 5000
    balance roundrobin

    # 注释掉以下四行健康检查配置
    #option httpchk                               # <---- remove this
    #option http-keep-alive                       # <---- remove this
    #http-check send meth OPTIONS uri /primary    # <---- remove this
    #http-check expect status 200                 # <---- remove this

    default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
    server pg-meta-1 10.10.10.10:6432 check port 8008 weight 100

    # 注释掉其他两台故障的机器
    #server pg-meta-2 10.10.10.11:6432 check port 8008 weight 100 <---- comment this
    #server pg-meta-3 10.10.10.12:6432 check port 8008 weight 100 <---- comment this

配置调整完成后,先不着急执行 systemctl reload haproxy 重载生效,等待后续主库提升后一起执行。 以上配置的效果是,HAProxy 将不再进行主库健康检查(默认使用 Patroni),而是直接将写入流量指向当前主库


手工提升备库

登陆目标服务器,切换至 dbsu 用户,执行 CHECKPOINT 刷盘后,关闭 Patroni,重启 PostgreSQL 并执行 Promote。

sudo su - postgres                     # 切换到数据库 dbsu 用户
psql -c 'checkpoint; checkpoint;'      # 两次 Checkpoint 刷脏页,避免PG后重启耗时过久
sudo systemctl stop patroni            # 关闭 Patroni
pg-restart                             # 重新拉起 PostgreSQL
pg-promote                             # 将 PostgreSQL 从库提升为主库
psql -c 'SELECT pg_is_in_recovery();'  # 如果结果为 f,表示已经提升为主库

如果你上面调整了 HAProxy 配置,那么现在可以执行 systemctl reload haproxy 重载 HAProxy 配置,将流量指向新的主库。

systemctl reload haproxy                # 重载 HAProxy 配置,将写入流量指向当前实例

避免脑裂

紧急止血后,第二优先级问题为:避免脑裂。用户应当防止另外两台服务器重新上线后,与当前主库形成脑裂,导致数据不一致。

简单的做法是:

  • 将另外两台服务器直接 断电/断网,确保它们不会在不受控的情况下再次上线。
  • 调整应用使用的数据库连接串,将其 HOST 直接指向唯一幸存服务器上的主库。

然后应当根据具体情况,决定下一步的操作:

  • A:这两台服务器是临时故障(比如断网断电),可以原地修复后继续服务
  • B:这两台故障服务器是永久故障(比如硬件损坏),将移除并下线。

临时故障后的复原

如果另外两台服务器是临时故障,可以修复后继续服务,那么可以按照以下步骤进行修复与重建:

  • 每次处理一台故障服务器,优先处理 管理节点 / INFRA 管理节点
  • 启动故障服务器,并在启动后关停 Patroni

ETCD 集群在法定人数恢复后,将恢复工作,此时可以启动幸存服务器(当前主库)上的 Patroni,接管现有 PostgreSQL,并重新获取集群领导者身份。 Patroni 启动后进入维护模式。

systemctl restart patroni
pg pause <pg_cluster>

在另外两台实例上以 postgres 用户身份创建 touch /pg/data/standby.signal 标记文件将其标记为从库,然后拉起 Patroni:

systemctl restart patroni

确认 Patroni 集群身份/角色正常后,退出维护模式:

pg resume <pg_cluster>

永久故障后的复原

出现永久故障后,首先需要恢复管理节点上的 ~/pigsty 目录,主要是需要 pigsty.ymlfiles/pki/ca/ca.key 两个核心文件。

如果您无法取回或没有备份这两个文件,您可以选择部署一套新的 Pigsty,并通过 备份集群 的方式将现有集群迁移至新部署中。

请定期备份 pigsty 目录(例如使用 Git 进行版本管理)。建议吸取教训,下次不要犯这样的错误。

配置修复

您可以将幸存的节点作为新的管理节点,将 ~/pigsty 目录拷贝到新的管理节点上,然后开始调整配置。 例如,将原本默认的管理节点 10.10.10.10 替换为幸存节点 10.10.10.12

all:
  vars:
    admin_ip: 10.10.10.12               # 使用新的管理节点地址
    node_etc_hosts: [10.10.10.12 h.pigsty a.pigsty p.pigsty g.pigsty sss.pigsty]
    infra_portal: {}                    # 一并修改其他引用旧管理节点 IP (10.10.10.10) 的配置

  children:

    infra:                              # 调整 Infra 集群
      hosts:
        # 10.10.10.10: { infra_seq: 1 } # 老的 Infra 节点
        10.10.10.12: { infra_seq: 3 }   # 新增 Infra 节点

    etcd:                               # 调整 ETCD 集群
      hosts:
        #10.10.10.10: { etcd_seq: 1 }   # 注释掉此故障节点
        #10.10.10.11: { etcd_seq: 2 }   # 注释掉此故障节点
        10.10.10.12: { etcd_seq: 3 }    # 保留幸存节点
      vars:
        etcd_cluster: etcd

    pg-meta:                            # 调整 PGSQL 集群配置
      hosts:
        #10.10.10.10: { pg_seq: 1, pg_role: primary }
        #10.10.10.11: { pg_seq: 2, pg_role: replica }
        #10.10.10.12: { pg_seq: 3, pg_role: replica , pg_offline_query: true }
        10.10.10.12: { pg_seq: 3, pg_role: primary , pg_offline_query: true }
      vars:
        pg_cluster: pg-meta

ETCD修复

然后执行以下命令,将 ETCD 重置为单节点集群:

./etcd.yml -e etcd_safeguard=false -e etcd_clean=true

根据 ETCD重载配置 的说明,调整对 ETCD Endpoint 的引用。

INFRA修复

如果幸存节点上没有 INFRA 模块,请在当前节点上配置新的 INFRA 模块并安装。执行以下命令,将 INFRA 模块部署到幸存节点上:

./infra.yml -l 10.10.10.12

修复当前节点的监控

./node.yml -t node_monitor

PGSQL修复

./pgsql.yml -t pg_conf                            # 重新生成 PG 配置文件
systemctl reload patroni                          # 在幸存节点上重载 Patroni 配置

各模块修复后,您可以参考标准扩容流程,将新的节点加入集群,恢复集群的高可用性。