站群与多 IP

站群服务器批量建站教程:多 IP 一站一 IP,Nginx 模板部署、批量 SSL 与验证

6 个步骤 12 分钟阅读 71 次阅读 0
本文目录

一台带有一个或多个 /24 IP 段的站群服务器,可以承载数百个网站,每个网站使用独立 IP。在控制面板里逐个点击几百次既慢又容易出错;用几份模板加一个脚本,几分钟就能完成同样的工作,而且每个站点的配置完全一致。本文介绍如何规划域名与 IP 的对应关系、防止站点串到错误的 IP 上(串站)、用模板生成 Nginx 虚拟主机、用 acme.sh 批量签发 SSL 证书,以及检查每个域名是否都在自己的 IP 上正常响应。

本文假设所有 IP 都已绑定在服务器上(参见 Linux 绑定整段 /24 IP),并且每个域名的 A 记录都已指向为其分配的 IP。批量跑几百个域名之前,请先用两三个域名测试脚本。

步骤 1:规划域名与 IP 对照表

用一个纯文本文件作为唯一的数据来源:每行一个站点,写明域名和对应的 IP。其他所有配置都由它生成。

mkdir -p /root/cluster
cat > /root/cluster/sites.txt <<'EOF2'
example-a.com 203.0.113.10
example-b.com 203.0.113.11
example-c.net 203.0.113.12
EOF2

# Check that every IP in the list is actually bound on the server
awk '{print $2}' /root/cluster/sites.txt | while read ip; do ip -br addr | grep -qw "$ip" || echo "NOT BOUND: $ip"; done

步骤 2:防止站点串到其他 IP 上

Nginx 会先根据监听地址(IP 和端口)选出 server,再匹配主机名;主机名不匹配的请求,会由该地址的默认站点(default server)响应,如果没有专门指定,就是该地址上配置的第一个站点。在站群上,这意味着有人直接访问裸 IP,或者把另一个站点的主机名发到这个 IP 时,会看到你的某个站点,从而把这些站点关联起来。需要在两处拦截这类请求:一是为尚未部署站点的 IP 设置兜底配置(见下文),二是在模板中为每个 IP 设置默认站点(步骤 3)。

cat > /etc/nginx/conf.d/00-default.conf <<'EOF2'
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;
}
EOF2

# Hide the Nginx version in headers and error pages
sed -i 's/^\s*#\?\s*server_tokens.*/    server_tokens off;/' /etc/nginx/nginx.conf
grep -q 'server_tokens off' /etc/nginx/nginx.conf || sed -i '/http {/a\    server_tokens off;' /etc/nginx/nginx.conf

步骤 3:创建虚拟主机模板

模板中的占位符由脚本按站点逐一替换。每个站点只监听自己的 IP,该 IP 上任何其他主机名的请求都会被直接断开。

mkdir -p /etc/nginx/sites-cluster /www/sites
cat > /root/cluster/vhost-http.tpl <<'EOF2'
# Unknown hostnames on this IP: close the connection
server {
    listen __IP__:80 default_server;
    server_name _;
    return 444;
}

server {
    listen __IP__:80;
    server_name __DOMAIN__ www.__DOMAIN__;
    root /www/sites/__DOMAIN__/public;
    index index.html index.php;
    access_log /www/sites/__DOMAIN__/logs/access.log;
    error_log  /www/sites/__DOMAIN__/logs/error.log;

    location /.well-known/acme-challenge/ { allow all; }
    location / { try_files $uri $uri/ =404; }
}
EOF2

# Load the generated files from the http block
grep -q 'sites-cluster' /etc/nginx/nginx.conf || sed -i '/http {/a\    include /etc/nginx/sites-cluster/*.conf;' /etc/nginx/nginx.conf

步骤 4:用一个脚本生成全部站点

cat > /root/cluster/deploy.sh <<'EOF2'
#!/bin/bash
set -e
while read -r DOMAIN IP; do
  [ -z "$DOMAIN" ] && continue
  mkdir -p /www/sites/$DOMAIN/public /www/sites/$DOMAIN/logs
  [ -f /www/sites/$DOMAIN/public/index.html ] || echo "<h1>$DOMAIN</h1>" > /www/sites/$DOMAIN/public/index.html
  sed -e "s/__DOMAIN__/$DOMAIN/g" -e "s/__IP__/$IP/g" /root/cluster/vhost-http.tpl > /etc/nginx/sites-cluster/$DOMAIN.conf
done < /root/cluster/sites.txt
chown -R www-data:www-data /www/sites 2>/dev/null || chown -R nginx:nginx /www/sites
nginx -t && systemctl reload nginx
EOF2
chmod +x /root/cluster/deploy.sh && /root/cluster/deploy.sh

把各站点的内容上传到 /www/sites/DOMAIN/public。以后新增站点时,只需在 sites.txt 中追加行,再运行一次脚本;已有内容不会被覆盖。

步骤 5:批量签发 SSL 证书

安装 acme.sh 并将 CA 设置为 Let's Encrypt(参见使用 Certbot 或 acme.sh 申请免费 SSL 证书)。下面的循环通过网站根目录为每个域名签发证书、安装证书,并在该站点自己的 IP 上添加 HTTPS server 块。

cat > /root/cluster/ssl.sh <<'EOF2'
#!/bin/bash
mkdir -p /etc/nginx/ssl
while read -r DOMAIN IP; do
  [ -z "$DOMAIN" ] && continue
  [ -f /etc/nginx/ssl/$DOMAIN.crt ] && continue          # already done
  ~/.acme.sh/acme.sh --issue -d $DOMAIN -d www.$DOMAIN -w /www/sites/$DOMAIN/public || { echo "FAILED: $DOMAIN"; continue; }
  ~/.acme.sh/acme.sh --install-cert -d $DOMAIN \
     --key-file /etc/nginx/ssl/$DOMAIN.key --fullchain-file /etc/nginx/ssl/$DOMAIN.crt \
     --reloadcmd "systemctl reload nginx"
  cat >> /etc/nginx/sites-cluster/$DOMAIN.conf <<EOF3
server {
    listen $IP:443 ssl default_server;
    ssl_reject_handshake on;      # unknown hostnames on this IP (Nginx 1.19.4+)
}
server {
    listen $IP:443 ssl;
    http2 on;
    server_name $DOMAIN www.$DOMAIN;
    ssl_certificate     /etc/nginx/ssl/$DOMAIN.crt;
    ssl_certificate_key /etc/nginx/ssl/$DOMAIN.key;
    root /www/sites/$DOMAIN/public;
    index index.html index.php;
    access_log /www/sites/$DOMAIN/logs/access.log;
}
EOF3
  sleep 5
done < /root/cluster/sites.txt
nginx -t && systemctl reload nginx
EOF2
chmod +x /root/cluster/ssl.sh && /root/cluster/ssl.sh

建议每批只跑几十个域名;Let's Encrypt 限制同一账户在短时间内申请新证书的数量。DNS 尚未指向正确 IP 的域名会签发失败;修正 DNS 后重新运行脚本即可,已完成的域名会自动跳过。证书续期由 acme.sh 的定时任务(cron)自动完成。

步骤 6:逐个验证站点

# Each domain must answer on its own IP with HTTP 200 (or 301 if you redirect to HTTPS)
while read -r DOMAIN IP; do
  code=$(curl -s -o /dev/null -w '%{http_code}' --resolve $DOMAIN:80:$IP http://$DOMAIN/)
  dns=$(dig +short $DOMAIN A | tail -1)
  echo "$DOMAIN $IP http=$code dns=$dns $( [ "$dns" = "$IP" ] && echo OK || echo DNS-MISMATCH)"
done < /root/cluster/sites.txt

# The bare IP must not show any site (connection closed = correct)
curl -sI http://203.0.113.10/ || echo "closed as expected"
curl -skI https://203.0.113.10/ || echo "handshake rejected as expected"

常见问题

可以用宝塔面板来做吗?

可以:宝塔面板可以在站点设置中为每个站点绑定 IP,几十个站点时完全够用(参见为每个网站分配独立 IP)。站点达到几百个时,模板加脚本更快、更不容易出错,数据库和监控仍然可以交给宝塔管理。

出站流量也要走各站点自己的 IP 吗?

如果站点需要抓取内容或调用 API,出站请求默认从服务器主 IP 发出。如需让它们使用各自站点的 IP,请参见为每个网站分配独立 IP 中的出站 IP 部分。

站点应该如何分布在多个 IP 段上?

对于 SEO 站群,把站点分散到多个 /24 段(不同 C 段),并让模板、统计账户和托管信息彼此区分开,比 IP 数量本身更重要。参见多 C 段 IP 的 SEO 站群服务器。

如果按以上步骤操作后问题仍未解决,请提交工单联系 IMIDC 7×24 技术支持,并附上服务器 IP、系统版本、出问题的域名和 IP,以及 nginx -t 的输出,方便工程师快速定位。

这篇文章有帮助吗?

相关教程