架构总览

  • 数据层:PostgreSQL (1 主 1 从 1 选举) + Redis (1 主 2 从)

  • 存储层:MinIO 分布式集群 (4 节点)

  • 接入层:HAProxy + Keepalived (2 节点,多 VIP)

  • 应用层:Harbor 双节点

  • DNS 层:内部 DNS 主从 (用于服务发现)

第一阶段:内部 DNS 集群 (可选,建议部署)不部署的话可以使用hosts

  • 在 104/105 上部署 CoreDNS 或 Bind9。
  • 配置主从同步。
  • 将域名 (如 harbor.zz520.online) 解析指向 VIP_Harbor
  • 将所有节点的 DNS resolver 指向 VIP_DNS。

一、部署dns服务器

1.部署主服务器
1.1准备工作(关闭防火墙)104/105执行
systemctl disable ufw --now
优化53端口
root@ha1:~# cat fix_port_53.sh 
#!/bin/bash
# 解决 Ubuntu systemd-resolved 占用 53 端口的问题

echo "正在检查 53 端口占用情况..."
ss -tulpn | grep :53

echo ""
echo "1. 停止并禁用 systemd-resolved 服务..."
systemctl stop systemd-resolved
systemctl disable systemd-resolved

echo ""
echo "2. 修改 /etc/systemd/resolved.conf 关闭 Stub..."
# 备份
cp /etc/systemd/resolved.conf /etc/systemd/resolved.conf.bak

# 写入配置
cat > /etc/systemd/resolved.conf << 'EOF'
[Resolve]
DNS=223.5.5.5 114.114.114.114
FallbackDNS=8.8.8.8
#Domains=
LLMNR=no
MulticastDNS=no
DNSSEC=no
DNSOverTLS=no
Cache=no
DNSStubListener=no
#ReadEtcHosts=yes
EOF

echo ""
echo "3. 重建 resolv.conf (非常重要)..."
# 删除旧的软链接
rm -f /etc/resolv.conf

# 创建新的静态 resolv.conf
cat > /etc/resolv.conf << 'EOF'
nameserver 223.5.5.5
nameserver 114.114.114.114
EOF

echo ""
echo "4. 重新加载 systemd 配置 (可选)..."
systemctl daemon-reload

echo ""
echo "5. 再次检查 53 端口..."
sleep 1
ss -tulpn | grep :53

if [ $? -eq 0 ]; then
    echo ""
    echo "⚠️  警告: 53 端口仍被占用,请检查上面的输出!"
else
    echo ""
    echo "✅ 成功!53 端口已释放。"
    echo "现在可以安装 Bind9 了。"
fi
1.2开始部署104
cat deploy_bind_master.sh
#!/bin/bash
# Ubuntu Bind9 主服务器一键部署脚本

echo "1. 更新软件源并安装 Bind9..."
apt update && apt install -y bind9 bind9-utils dnsutils

echo "2. 备份原始配置..."
cd /etc/bind
cp named.conf.options named.conf.options.bak
cp named.conf.local named.conf.local.bak

echo "3. 写入主配置文件 named.conf.options..."
cat > named.conf.options << 'EOF'
options {
        directory "/var/cache/bind";

        // 允许查询的网段 (你的内网)
        allow-query { localhost; 10.0.0.0/24; };
        
        // 允许递归查询
        recursion yes;
        
        // 转发到公网DNS
        forwarders {
                223.5.5.5;
                114.114.114.114;
        };
        
        dnssec-validation auto;
        listen-on-v6 { any; };
};
EOF

echo "4. 写入区域配置 named.conf.local..."
cat > named.conf.local << 'EOF'
// 定义正向区域
zone "zz520.online" {
        type master;
        file "/etc/bind/db.zz520.online";
        // 允许从服务器传输区域文件
        allow-transfer { 10.0.0.105; };
        // 主动通知从服务器更新
        also-notify { 10.0.0.105; };
};
EOF

echo "5. 创建区域数据库文件 db.zz520.online..."
cat > db.zz520.online << 'EOF'
$TTL    604800
@       IN      SOA     ns1.zz520.online. admin.zz520.online. (
                              2         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL
;
@       IN      NS      ns1.zz520.online.
@       IN      NS      ns2.zz520.online.

; 定义 NS 记录
ns1     IN      A       10.0.0.104
ns2     IN      A       10.0.0.105

; 预定义你的 Harbor VIP (示例,后续可通过脚本修改)
harbor  IN      A       10.0.0.204
EOF

echo "6. 修复权限..."
chown root:bind /etc/bind/db.zz520.online
chmod 644 /etc/bind/db.zz520.online

echo "7. 检查配置语法..."
named-checkconf
if [ $? -ne 0 ]; then
    echo "配置语法错误,请检查!"
    exit 1
fi

echo "8. 重启 Bind9 服务..."
systemctl daemon-reload
systemctl restart named
systemctl enable named

echo "===== Master 部署完成 ====="
echo "请确保防火墙允许 10.0.0.105 访问本机的 53 端口 (TCP/UDP)"
2.部署从服务器105
2.1准备工作
关闭防火墙
systemctl disable ufw --now
优化53端口
root@ha1:~# cat fix_port_53.sh 
#!/bin/bash
# 解决 Ubuntu systemd-resolved 占用 53 端口的问题

echo "正在检查 53 端口占用情况..."
ss -tulpn | grep :53

echo ""
echo "1. 停止并禁用 systemd-resolved 服务..."
systemctl stop systemd-resolved
systemctl disable systemd-resolved

echo ""
echo "2. 修改 /etc/systemd/resolved.conf 关闭 Stub..."
# 备份
cp /etc/systemd/resolved.conf /etc/systemd/resolved.conf.bak

# 写入配置
cat > /etc/systemd/resolved.conf << 'EOF'
[Resolve]
DNS=223.5.5.5 114.114.114.114
FallbackDNS=8.8.8.8
#Domains=
LLMNR=no
MulticastDNS=no
DNSSEC=no
DNSOverTLS=no
Cache=no
DNSStubListener=no
#ReadEtcHosts=yes
EOF

echo ""
echo "3. 重建 resolv.conf (非常重要)..."
# 删除旧的软链接
rm -f /etc/resolv.conf

# 创建新的静态 resolv.conf
cat > /etc/resolv.conf << 'EOF'
nameserver 223.5.5.5
nameserver 114.114.114.114
EOF

echo ""
echo "4. 重新加载 systemd 配置 (可选)..."
systemctl daemon-reload

echo ""
echo "5. 再次检查 53 端口..."
sleep 1
ss -tulpn | grep :53

if [ $? -eq 0 ]; then
    echo ""
    echo "⚠️  警告: 53 端口仍被占用,请检查上面的输出!"
else
    echo ""
    echo "✅ 成功!53 端口已释放。"
    echo "现在可以安装 Bind9 了。"
fi
2.2部署从节点dns服务器
root@ha2:~# cat deploy_bind_slave.sh 
#!/bin/bash
# Ubuntu Bind9 从服务器一键部署脚本

echo "1. 更新软件源并安装 Bind9..."
apt update && apt install -y bind9 bind9-utils dnsutils

echo "2. 备份原始配置..."
cd /etc/bind
cp named.conf.options named.conf.options.bak
cp named.conf.local named.conf.local.bak

echo "3. 写入配置文件 named.conf.options..."
cat > named.conf.options << 'EOF'
options {
        directory "/var/cache/bind";

        allow-query { localhost; 10.0.0.0/24; };
        recursion yes;
        
        forwarders {
                223.5.5.5;
                114.114.114.114;
        };
        
        dnssec-validation auto;
        listen-on-v6 { any; };
};
EOF

echo "4. 写入区域配置 named.conf.local (Slave模式)..."
cat > named.conf.local << 'EOF'
zone "zz520.online" {
        type slave;
        masters { 10.0.0.104; };
        file "/var/cache/bind/db.zz520.online";
        // 只允许主服务器通知
        allow-notify { 10.0.0.104; };
};
EOF

echo "5. 检查配置语法..."
named-checkconf
if [ $? -ne 0 ]; then
    echo "配置语法错误,请检查!"
    exit 1
fi

echo "6. 重启 Bind9 服务..."
systemctl daemon-reload
systemctl restart named
systemctl enable named

echo "===== Slave 部署完成 ====="
echo "正在尝试从 Master 同步区域文件..."
sleep 3
ls -la /var/cache/bind/ | grep db.zz520
3.添加解析104执行
root@ha1:~# cat manage_bind_records.sh 
#!/bin/bash
# Bind9 记录管理脚本 (仅在 Master 上运行)

ZONE_FILE="/etc/bind/db.zz520.online"
ZONE_NAME="zz520.online"

# 颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

function increment_serial {
    echo "正在更新 SOA 序列号..."
    # 获取当前序列号
    CURRENT_SERIAL=$(grep -Eo '^[[:space:]]*[0-9]+[[:space:]]*;[[:space:]]*Serial' $ZONE_FILE | awk '{print $1}')
    
    if [ -z "$CURRENT_SERIAL" ]; then
        echo -e "${RED}无法读取序列号!${NC}"
        exit 1
    fi

    # 生成新序列号 (YYYYMMDD + 01,或者简单的+1)
    # 这里使用简单的 +1 策略,最稳妥
    NEW_SERIAL=$((CURRENT_SERIAL + 1))
    
    # 替换文件中的序列号
    sed -i "s/^[[:space:]]*${CURRENT_SERIAL}[[:space:]]*;[[:space:]]*Serial/                             ${NEW_SERIAL}         ; Serial/" $ZONE_FILE
    
    echo -e "${GREEN}序列号已更新: ${CURRENT_SERIAL} -> ${NEW_SERIAL}${NC}"
}

function add_record {
    echo "--- 添加新的 A 记录 ---"
    read -p "请输入主机名 (例如 harbor, 不带域名): " HOST
    read -p "请输入 IP 地址: " IP
    
    # 检查是否已存在
    if grep -q "^${HOST}[[:space:]]" $ZONE_FILE; then
        echo -e "${YELLOW}警告: 记录 ${HOST} 似乎已存在。${NC}"
        read -p "确定要继续添加吗?(y/n): " -n 1 -r
        echo
        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
            return
        fi
    fi
    
    # 追加到文件末尾
    echo "${HOST}     IN      A       ${IP}" >> $ZONE_FILE
    echo -e "${GREEN}记录已添加。${NC}"
    
    increment_serial
    
    echo "正在检查配置..."
    named-checkzone $ZONE_NAME $ZONE_FILE
    if [ $? -eq 0 ]; then
        echo "正在重载 Bind9..."
        rndc reload
        echo -e "${GREEN}完成!Slave 节点将在几分钟内自动同步。${NC}"
    else
        echo -e "${RED}配置检查失败,请手动检查文件!${NC}"
    fi
}

function view_records {
    echo "--- 当前 DNS 记录 ---"
    echo "Host                IP"
    echo "--------------------------------"
    grep 'IN[[:space:]]*A' $ZONE_FILE | awk '{printf "%-20s %s\n", $1, $NF}'
}

# 主菜单
while true; do
    echo ""
    echo "=============================="
    echo "  Bind9 DNS 管理工具"
    echo "  操作节点: $(hostname)"
    echo "=============================="
    echo "1. 查看当前记录"
    echo "2. 添加 A 记录"
    echo "3. 退出"
    read -p "请选择 [1-3]: " choice

    case $choice in
        1)
            view_records
            ;;
        2)
            add_record
            ;;
        3)
            echo "再见。"
            break
            ;;
        *)
            echo "无效输入"
            ;;
    esac
done

二、部署keepalived实现高可用

我们将在 10.0.0.104 (ha1)10.0.0.105 (ha2) 上配置 Keepalived,创建 5 个独立的虚拟 IP (VIP)

1.系统优化104/105执行
vim optimize_ubuntu_for_harbor_ha.sh
#!/bin/bash
# ============================================================
# Harbor HA 集群生产环境优化脚本 (Ubuntu 24.04+)
# 功能:
# 1. 内核参数优化 (Keepalived/ARP/网络性能)
# 2. 系统资源限制优化 (文件句柄/进程数)
# ============================================================

set -e

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# 检查是否为 root 用户
if [ "$EUID" -ne 0 ]; then 
    echo -e "${RED}请使用 root 权限运行此脚本 (sudo su)${NC}"
    exit 1
fi

# 网卡名称配置 (请根据实际情况修改)
TARGET_INTERFACE="ens33"

echo "============================================================"
echo "  Harbor HA 生产环境优化脚本"
echo "  目标系统: Ubuntu 24.04"
echo "  检测网卡: ${TARGET_INTERFACE}"
echo "============================================================"
echo ""

# -------------------------- 1. 内核参数优化 --------------------------
echo -e "${YELLOW}[1/3] 正在配置内核参数 (/etc/sysctl.d/99-harbor-ha.conf)...${NC}"

SYSCTL_FILE="/etc/sysctl.d/99-harbor-ha.conf"

# 备份旧配置
if [ -f "$SYSCTL_FILE" ]; then
    cp "$SYSCTL_FILE" "${SYSCTL_FILE}.bak.$(date +%s)"
fi

cat > "$SYSCTL_FILE" << EOF
# ==================================================
# Keepalived & LVS 核心优化
# ==================================================
net.ipv4.ip_nonlocal_bind = 1
net.ipv4.ip_forward = 1

# ARP 抑制
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.default.arp_ignore = 1
net.ipv4.conf.${TARGET_INTERFACE}.arp_ignore = 1

# ARP 通告
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.default.arp_announce = 2
net.ipv4.conf.${TARGET_INTERFACE}.arp_announce = 2

# ==================================================
# 网络并发与性能优化
# ==================================================
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
EOF

echo -e "${GREEN}内核参数文件写入完成。${NC}"
echo ""

# -------------------------- 2. 资源限制优化 --------------------------
echo -e "${YELLOW}[2/3] 正在配置系统资源限制 (/etc/security/limits.d/99-harbor-ha.conf)...${NC}"

LIMITS_FILE="/etc/security/limits.d/99-harbor-ha.conf"

if [ -f "$LIMITS_FILE" ]; then
    cp "$LIMITS_FILE" "${LIMITS_FILE}.bak.$(date +%s)"
fi

cat > "$LIMITS_FILE" << EOF
# ==================================================
# Harbor HA 集群资源限制
# ==================================================

# 针对 root 用户
root soft nofile 1048576
root hard nofile 1048576
root soft nproc 1048576
root hard nproc 1048576

# 针对所有用户
* soft nofile 1048576
* hard nofile 1048576
* soft nproc 1048576
* hard nproc 1048576
EOF

echo -e "${GREEN}资源限制文件写入完成。${NC}"
echo ""

# -------------------------- 3. 应用配置 --------------------------
echo -e "${YELLOW}[3/3] 正在应用内核参数...${NC}"
sysctl --system > /dev/null 2>&1

echo ""
echo "============================================================"
echo -e "${GREEN}✅ 所有优化配置已完成!${NC}"
echo "============================================================"
echo ""
echo "⚠️  重要提示:"
echo "1. 内核参数已即时生效。"
echo "2. 资源限制 (ulimit) ${RED}必须重启服务器${NC}后才能完全生效。"
echo "3. 请确认脚本中的网卡名 (${TARGET_INTERFACE}) 是否正确。"
echo ""
read -p "是否现在重启服务器? (y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
    echo "正在重启..."
    reboot
fi

2.安装keepalived(104/105均执行)
apt update && apt install -y keepalived
3.部署健康检查脚本 (deploy_health_checks.sh)
#!/bin/bash
# ============================================================
# 脚本功能:部署 Harbor HA 集群 Keepalived 健康检查脚本
# Repmgr 路径: /apps/repmgr/etc/repmgr.conf
# ============================================================

set -e

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

if [ "$EUID" -ne 0 ]; then 
    echo -e "${RED}请使用 root 权限运行${NC}"
    exit 1
fi

SCRIPT_DIR="/etc/keepalived/scripts"
echo -e "${YELLOW}正在创建脚本目录: ${SCRIPT_DIR}${NC}"
mkdir -p $SCRIPT_DIR
cd $SCRIPT_DIR

# ----------------------------------------------------
# 1. chk_haproxy.sh
# ----------------------------------------------------
echo "正在生成 chk_haproxy.sh..."
cat > chk_haproxy.sh << 'EOF'
#!/bin/bash
/usr/bin/pgrep haproxy > /dev/null 2>&1
exit $?
EOF

# ----------------------------------------------------
# 2. chk_port.sh
# ----------------------------------------------------
echo "正在生成 chk_port.sh..."
cat > chk_port.sh << 'EOF'
#!/bin/bash
PORT=$1
if [ -z "$PORT" ]; then exit 1; fi
/usr/bin/ss -tulpn | grep -q ":${PORT}" > /dev/null 2>&1
exit $?
EOF

# ----------------------------------------------------
# 3. chk_dns.sh
# ----------------------------------------------------
echo "正在生成 chk_dns.sh..."
cat > chk_dns.sh << 'EOF'
#!/bin/bash
/usr/bin/pgrep named > /dev/null 2>&1
exit $?
EOF

# ----------------------------------------------------
# 4. chk_minio.sh (需要用户后续手动改 IP)
# ----------------------------------------------------
echo "正在生成 chk_minio.sh..."
cat > chk_minio.sh << 'EOF'
#!/bin/bash
# 请修改为当前机器的物理 IP (10.0.0.104 或 10.0.0.105)
LOCAL_IP="10.0.0.104" 
MINIO_PORT="9000"
/usr/bin/ss -tulpn | grep -q ":${MINIO_PORT}" > /dev/null 2>&1
exit $?
EOF

# ----------------------------------------------------
# 5. chk_pgsql_repmgr.sh (核心,已修正路径)
# ----------------------------------------------------
echo "正在生成 chk_pgsql_repmgr.sh..."
cat > chk_pgsql_repmgr.sh << 'EOF'
#!/bin/bash
# repmgr 角色检查脚本
# 配置项
PG_USER="postgres"
REPMGR_BIN="/usr/local/bin/repmgr" # 如 repmgr 不在标准 PATH,请修改此处
REPMGR_CONF="/apps/repmgr/etc/repmgr.conf"

# 尝试获取角色
# 方法:通过 repmgr node status 解析
ROLE=$(su - ${PG_USER} -c "${REPMGR_BIN} -f ${REPMGR_CONF} node status 2>/dev/null | grep 'Role' | awk '{print \$NF}'")

# 如果上面的方法失败,尝试直接查数据库 (备用方案)
if [ -z "$ROLE" ]; then
    SQL="SELECT pg_is_in_recovery();"
    IN_RECOVERY=$(su - ${PG_USER} -c "psql -t -c \"${SQL}\" 2>/dev/null | xargs")
    if [ "$IN_RECOVERY" == "f" ]; then
        ROLE="primary"
    else
        ROLE="standby"
    fi
fi

# 逻辑判断
if [ "$ROLE" == "primary" ]; then
    exit 0
else
    exit 1
fi
EOF

# ----------------------------------------------------
# 6. chk_redis.sh
# ----------------------------------------------------
echo "正在生成 chk_redis.sh..."
cat > chk_redis.sh << 'EOF'
#!/bin/bash
/usr/bin/pgrep redis-server > /dev/null 2>&1
exit $?
EOF

# ----------------------------------------------------
# 收尾:赋权
# ----------------------------------------------------
echo "正在赋予执行权限..."
chmod +x $SCRIPT_DIR/*.sh
chown -R root:root $SCRIPT_DIR/

echo ""
echo -e "${GREEN}============================================================"
echo -e "✅ 所有健康检查脚本部署完成!"
echo -e "============================================================${NC}"
echo ""
echo -e "${YELLOW}⚠️  请记得手动编辑以下文件:${NC}"
echo -e "   1. ${SCRIPT_DIR}/chk_minio.sh"
echo -e "      -> 修改 LOCAL_IP 为本机物理 IP"
echo -e "   2. 请确认 repmgr 二进制文件路径是否正确"
echo ""
4.生成 Keepalived 配置 (setup_keepalived_conf.sh)
#!/bin/bash
# ============================================================
# 脚本功能:交互式生成 Keepalived 配置文件
# ============================================================

set -e

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

if [ "$EUID" -ne 0 ]; then 
    echo -e "${RED}请使用 root 权限运行${NC}"
    exit 1
fi

echo "============================================================"
echo "  Keepalived 配置生成器"
echo "============================================================"

# 1. 询问节点类型
echo ""
PS3="请选择当前节点类型: "
options=("ha1 (10.0.0.104 - Master)" "ha2 (10.0.0.105 - Backup)")
select opt in "${options[@]}"
do
    case $opt in
        "ha1 (10.0.0.104 - Master)")
            NODE_TYPE="ha1"
            ROUTER_ID="LVS_HA1"
            STATELESS_STATE="MASTER"
            STATELESS_PRIO="150"
            break
            ;;
        "ha2 (10.0.0.105 - Backup)")
            NODE_TYPE="ha2"
            ROUTER_ID="LVS_HA2"
            STATELESS_STATE="BACKUP"
            STATELESS_PRIO="100"
            break
            ;;
        *) echo "无效选项 $REPLY";;
    esac
done

# 2. 询问网卡名
echo ""
read -p "请输入网卡名称 (默认 ens33): " IFACE
IFACE=${IFACE:-ens33}

# 3. 确认 IP 前缀 (假设是 10.0.0.x)
NETWORK_PREFIX="10.0.0"

# ----------------------------------------------------
# 生成配置
# ----------------------------------------------------
CONF_FILE="/etc/keepalived/keepalived.conf"
echo ""
echo -e "${YELLOW}正在生成配置文件: ${CONF_FILE}${NC}"

# 备份旧配置
if [ -f "$CONF_FILE" ]; then
    cp "$CONF_FILE" "${CONF_FILE}.bak.$(date +%s)"
fi

cat > $CONF_FILE << EOF
! /etc/keepalived/keepalived.conf
! Auto-generated for Node: ${NODE_TYPE}

global_defs {
   router_id ${ROUTER_ID}
   script_user root
   enable_script_security
}

# -------------------------- 脚本定义区 --------------------------

vrrp_script chk_haproxy {
    script "/etc/keepalived/scripts/chk_haproxy.sh"
    interval 2
    weight -20
    fall 2
    rise 2
}

vrrp_script chk_dns {
    script "/etc/keepalived/scripts/chk_dns.sh"
    interval 2
    weight -20
}

vrrp_script chk_minio {
    script "/etc/keepalived/scripts/chk_minio.sh"
    interval 3
    weight -20
}

vrrp_script chk_harbor {
    script "/etc/keepalived/scripts/chk_port.sh 443"
    interval 3
    weight -20
}

vrrp_script chk_pgsql {
    script "/etc/keepalived/scripts/chk_pgsql_repmgr.sh"
    interval 2
    weight -30
    fall 1
    rise 2
}

vrrp_script chk_redis {
    script "/etc/keepalived/scripts/chk_redis.sh"
    interval 2
    weight -20
}

# -------------------------- VIP 实例区 --------------------------

# ==================================================
# Group A: 无状态服务 (抢占模式)
# ==================================================

vrrp_instance VI_DNS {
    state ${STATELESS_STATE}
    interface ${IFACE}
    virtual_router_id 51
    priority ${STATELESS_PRIO}
    advert_int 1
    authentication { auth_type PASS; auth_pass dnsvip1; }
    virtual_ipaddress { ${NETWORK_PREFIX}.205/32 dev ${IFACE} label ${IFACE}:dns; }
    track_script { chk_dns; chk_haproxy; }
}

vrrp_instance VI_MINIO {
    state ${STATELESS_STATE}
    interface ${IFACE}
    virtual_router_id 54
    priority ${STATELESS_PRIO}
    advert_int 1
    authentication { auth_type PASS; auth_pass minivip1; }
    virtual_ipaddress { ${NETWORK_PREFIX}.203/32 dev ${IFACE} label ${IFACE}:minio; }
    track_script { chk_minio; }
}

vrrp_instance VI_HARBOR {
    state ${STATELESS_STATE}
    interface ${IFACE}
    virtual_router_id 55
    priority ${STATELESS_PRIO}
    advert_int 1
    authentication { auth_type PASS; auth_pass harborvip1; }
    virtual_ipaddress { ${NETWORK_PREFIX}.204/32 dev ${IFACE} label ${IFACE}:harbor; }
    track_script { chk_harbor; chk_haproxy; }
}

# ==================================================
# Group B: 有状态服务 (非抢占模式)
# ==================================================

vrrp_instance VI_PG {
    state BACKUP
    interface ${IFACE}
    virtual_router_id 52
    priority 100
    advert_int 1
    nopreempt
    authentication { auth_type PASS; auth_pass pgvip1; }
    virtual_ipaddress { ${NETWORK_PREFIX}.201/32 dev ${IFACE} label ${IFACE}:pg; }
    track_script { chk_pgsql; }
}

vrrp_instance VI_REDIS {
    state BACKUP
    interface ${IFACE}
    virtual_router_id 53
    priority 100
    advert_int 1
    nopreempt
    authentication { auth_type PASS; auth_pass redisvip1; }
    virtual_ipaddress { ${NETWORK_PREFIX}.202/32 dev ${IFACE} label ${IFACE}:redis; }
    track_script { chk_redis; }
}
EOF

echo ""
echo -e "${GREEN}============================================================"
echo -e "✅ Keepalived 配置生成完成!"
echo -e "============================================================${NC}"
echo ""
echo "配置文件路径: $CONF_FILE"
echo ""
echo "下一步操作:"
echo "1. 检查配置文件内容是否正确"
echo "2. 启动并设置开机自启 Keepalived: systemctl enable keepalived --now"
echo ""
5.测试keepalived是否生效
root@ha1:~# ip addr show ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:50:56:37:b7:41 brd ff:ff:ff:ff:ff:ff
    altname enp2s1
    inet 10.0.0.104/24 brd 10.0.0.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet 10.0.0.205/32 scope global ens33:dns;
       valid_lft forever preferred_lft forever
    inet 10.0.0.203/32 scope global ens33:minio;
       valid_lft forever preferred_lft forever
    inet 10.0.0.204/32 scope global ens33:harbor;
       valid_lft forever preferred_lft forever
    inet 10.0.0.201/32 scope global ens33:pg;
       valid_lft forever preferred_lft forever
    inet 10.0.0.202/32 scope global ens33:redis;
       valid_lft forever preferred_lft forever
    inet6 fe80::250:56ff:fe37:b741/64 scope link 
       valid_lft forever preferred_lft forever

6. 模拟故障转移测试 (可选但推荐)

在 ha1 上手动停止 Keepalived,观察 VIP 是否飘向 ha2:

root@ha1:~# systemctl stop keepalived
root@ha1:~# ip addr show ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:50:56:37:b7:41 brd ff:ff:ff:ff:ff:ff
    altname enp2s1
    inet 10.0.0.104/24 brd 10.0.0.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::250:56ff:fe37:b741/64 scope link 
       valid_lft forever preferred_lft forever



root@ha2:~# ip addr show ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:50:56:2b:bc:18 brd ff:ff:ff:ff:ff:ff
    altname enp2s1
    inet 10.0.0.105/24 brd 10.0.0.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet 10.0.0.205/32 scope global ens33:dns;
       valid_lft forever preferred_lft forever
    inet 10.0.0.203/32 scope global ens33:minio;
       valid_lft forever preferred_lft forever
    inet 10.0.0.204/32 scope global ens33:harbor;
       valid_lft forever preferred_lft forever
    inet 10.0.0.201/32 scope global ens33:pg;
       valid_lft forever preferred_lft forever
    inet 10.0.0.202/32 scope global ens33:redis;
       valid_lft forever preferred_lft forever
    inet6 fe80::250:56ff:fe2b:bc18/64 scope link 
       valid_lft forever preferred_lft forever

7.ha1重启keepalived

# 在 ha1 上重新启动,VIP 会自动抢回来 (无状态服务)

root@ha1:~# ip addr show ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:50:56:37:b7:41 brd ff:ff:ff:ff:ff:ff
    altname enp2s1
    inet 10.0.0.104/24 brd 10.0.0.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet 10.0.0.205/32 scope global ens33:dns;
       valid_lft forever preferred_lft forever
    inet 10.0.0.204/32 scope global ens33:harbor;
       valid_lft forever preferred_lft forever
    inet 10.0.0.203/32 scope global ens33:minio;
       valid_lft forever preferred_lft forever
    inet6 fe80::250:56ff:fe37:b741/64 scope link 
       valid_lft forever preferred_lft forever


root@ha2:~# ip a s ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:50:56:2b:bc:18 brd ff:ff:ff:ff:ff:ff
    altname enp2s1
    inet 10.0.0.105/24 brd 10.0.0.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet 10.0.0.201/32 scope global ens33:pg;
       valid_lft forever preferred_lft forever
    inet 10.0.0.202/32 scope global ens33:redis;
       valid_lft forever preferred_lft forever
    inet6 fe80::250:56ff:fe2b:bc18/64 scope link 
       valid_lft forever preferred_lft forever

三、申请ssl证书待用

1.1查看dns api

https://console.dnspod.cn/account/token/token
1.2添加dns api
 cat setup-secrets.sh
#!/usr/bin/env bash
#==============================================================
# 一次性执行:安全初始化 DNSPod API 密钥
#==============================================================
set -euo pipefail

SECRET_DIR="/etc/acme-secrets"
mkdir -p "${SECRET_DIR}"
chmod 700 "${SECRET_DIR}"

echo "🔐 请输入 DNSPod API 凭据"
read -p "DNSPod ID: " dp_id
read -s -p "DNSPod API Key: " dp_key
echo

# 保存密钥
echo "${dp_id}" > "${SECRET_DIR}/dp_id"
echo "${dp_key}" > "${SECRET_DIR}/dp_key"

# 严格权限
chmod 600 "${SECRET_DIR}/dp_id" "${SECRET_DIR}/dp_key"
chown root:root "${SECRET_DIR}/dp_id" "${SECRET_DIR}/dp_key"

echo "✅ 凭据已安全存储至 ${SECRET_DIR}"
2.申请证书
 cat acme_tencent_issue.sh
#!/usr/bin/env bash
set -euo pipefail

# ==========================================
#  acme.sh + 腾讯云 DNSPod 通配符证书申请
# ==========================================

# -------------------------- 请在此处配置 --------------------------

# 1. 你的主域名 (将同时申请 domain.com 和 *.domain.com)
MAIN_DOMAIN="zz520.online"

# 2. 用于注册 Let's Encrypt 账户的邮箱 (用于接收过期通知)
ACCOUNT_EMAIL="admin@${MAIN_DOMAIN}"

# 3. 密钥存储路径 (与你的初始化脚本保持一致)
SECRET_DIR="/etc/acme-secrets"

# 4. 证书最终部署位置
TARGET_DIR="/etc/ssl/acme/${MAIN_DOMAIN}"

# -------------------------- 配置区域结束 --------------------------

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

log() { echo -e "${GREEN}[✅]${NC} $1"; }
err() { echo -e "${RED}[❌]${NC} $1"; }
warn() { echo -e "${YELLOW}[⚠️]${NC} $1"; }

# ==========================================
#  1. 安全检查与环境加载
# ==========================================

if [ ! -d "${SECRET_DIR}" ]; then
    err "密钥目录不存在: ${SECRET_DIR}"
    exit 1
fi

# 读取 DNSPod 密钥并去除首尾空白
DP_Id=$(cat "${SECRET_DIR}/dp_id" | tr -d '[:space:]')
DP_Key=$(cat "${SECRET_DIR}/dp_key" | tr -d '[:space:]')

if [ -z "${DP_Id}" ] || [ -z "${DP_Key}" ]; then
    err "DNSPod 密钥为空,请检查 ${SECRET_DIR} 下的文件"
    exit 1
fi

# 导出环境变量 (acme.sh 官方要求的变量名)
export DP_Id="${DP_Id}"
export DP_Key="${DP_Key}"
log "已加载 DNSPod API 凭据 (ID: ${DP_Id})"

# ==========================================
#  2. 安装/初始化 acme.sh
# ==========================================
ACME_BIN="$HOME/.acme.sh/acme.sh"

if [ ! -f "${ACME_BIN}" ]; then
    log "正在安装 acme.sh ..."
    # 官方推荐的在线安装方式
    curl https://get.acme.sh | sh -s email="${ACCOUNT_EMAIL}"

    # 重新加载环境变量,确保当前会话可用
    export PATH="$HOME/.acme.sh:$PATH"
    if [ -f "$HOME/.acme.sh/acme.sh.env" ]; then
        # shellcheck source=/dev/null
        source "$HOME/.acme.sh/acme.sh.env"
    fi
else
    log "acme.sh 已安装 (${ACME_BIN})"
fi

# 强制切换默认 CA 为 Let's Encrypt (官方默认现在是 ZeroSSL)
"${ACME_BIN}" --set-default-ca --server letsencrypt > /dev/null
log "已设置默认 CA 为 Let's Encrypt"

# ==========================================
#  3. 申请证书 (ECC 算法)
# ==========================================
log "正在申请证书 (ECC/EC-256) ..."
log "域名列表: ${MAIN_DOMAIN}, *.${MAIN_DOMAIN}"

# 官方文档标准命令: --dns dns_dp (对应 DNSPod.cn)
# 注意: 首次运行不要加 --force,以免触发速率限制
"${ACME_BIN}" --issue \
    --dns dns_dp \
    -d "${MAIN_DOMAIN}" \
    -d "*.${MAIN_DOMAIN}" \
    --keylength ec-256

# ==========================================
#  4. 部署证书到生产目录
# ==========================================
log "正在部署证书到 ${TARGET_DIR} ..."
mkdir -p "${TARGET_DIR}"

# 官方文档强调: 必须使用 --install-cert 安装到目标路径
# 不要直接使用 ~/.acme.sh 下的文件
"${ACME_BIN}" --install-cert \
    -d "${MAIN_DOMAIN}" \
    -d "*.${MAIN_DOMAIN}" \
    --ecc \
    --key-file "${TARGET_DIR}/privkey.pem" \
    --fullchain-file "${TARGET_DIR}/fullchain.pem" \
    --reloadcmd "echo '证书文件已更新,请根据需要手动重启服务 (如: systemctl reload nginx/postgresql)'"

# ==========================================
#  5. 安全权限加固
# ==========================================
chmod 700 "${TARGET_DIR}"
chmod 600 "${TARGET_DIR}/privkey.pem"
chmod 644 "${TARGET_DIR}/fullchain.pem"
# 尝试锁定文件所有者为 root (仅当以 root 运行时)
chown -R root:root "${TARGET_DIR}" 2>/dev/null || true

# ==========================================
#  6. 自动续期确认
# ==========================================
# acme.sh 安装时已自动配置 crontab,这里仅做提示
log "检查自动续期任务..."
if crontab -l | grep -q acme.sh; then
    log "自动续期任务已存在 (crontab)"
else
    warn "未在 crontab 中找到 acme.sh 任务,建议检查安装日志"
fi

# 开启自动升级 acme.sh (官方推荐)
"${ACME_BIN}" --upgrade --auto-upgrade > /dev/null 2>&1

# ==========================================
#  输出总结
# ==========================================
log " "
log "=============================================="
log "         🎉 通配符证书部署完成 🎉"
log "=============================================="
log -e "  主域名:  \033[1;34m${MAIN_DOMAIN}\033[0m"
log -e "  泛域名:  \033[1;34m*.${MAIN_DOMAIN}\033[0m"
log " "
log "  🔐 证书路径 (请在您的服务配置中使用):"
log "   - Full Chain: ${TARGET_DIR}/fullchain.pem"
log "   - Private Key: ${TARGET_DIR}/privkey.pem"
log " "
log "  🛠️  维护命令:"
log "   - 手动续期测试: ${ACME_BIN} --cron --home $HOME/.acme.sh --force"
log "   - 查看证书信息: openssl x509 -in ${TARGET_DIR}/fullchain.pem -noout -text"
log "=============================================="

附:vim缩进错乱解决办法

vim ~/.vimrc

" 自动切换 paste 模式
augroup pastetoggle
  autocmd!
  au InsertEnter * set paste
  au InsertLeave * set nopaste
augroup END

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐