当前位置:首页 > 技术 > 正文内容

Harbor生产环境高可用搭建指南:DNS集群与SSL证书篇

访客 技术 2026年6月2日 1

整体架构概览

整个Harbor高可用方案包含以下层次:

  • 数据层:PostgreSQL(一主一从一选举节点)+ Redis(一主两从)
  • 存储层:MinIO 4节点分布式集群
  • 接入层:2节点HAProxy配合Keepalived实现多VIP
  • 应用层:双节点Harbor部署
  • DNS层:内部主从DNS用于服务发现

第一阶段:内部DNS集群搭建

建议部署内部DNS(亦可用hosts替代)。在节点104和105上部署Bind9,配置主从同步,将域名(如 harbor.zz520.online)解析至VIP_Harbor,并将所有节点的DNS指向VIP_DNS。

部署DNS主服务器(节点104)

环境准备

关闭防火墙:

systemctl disable ufw --now

解决systemd-resolved占用53端口问题,创建脚本fix_port_53.sh并执行:

#!/bin/bash
# 释放53端口(Ubuntu systemd-resolved占用处理)
echo "检查53端口..."
ss -tulpn | grep :53

systemctl stop systemd-resolved
systemctl disable systemd-resolved

# 备份配置
cp /etc/systemd/resolved.conf /etc/systemd/resolved.conf.bak

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

# 重建resolv.conf
rm -f /etc/resolv.conf
cat > /etc/resolv.conf << 'RESOLVCONF'
nameserver 223.5.5.5
nameserver 114.114.114.114
RESOLVCONF

systemctl daemon-reload
sleep 1
ss -tulpn | grep :53
echo "53端口释放完毕"

主DNS部署脚本

创建deploy_bind_master.sh

#!/bin/bash
apt update && apt install -y bind9 bind9-utils dnsutils

cd /etc/bind
cp named.conf.options named.conf.options.bak
cp named.conf.local named.conf.local.bak

# 主配置文件
cat > named.conf.options << 'OPTIONS'
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; };
};
OPTIONS

# 区域配置
cat > named.conf.local << 'ZONE'
zone "zz520.online" {
        type master;
        file "/etc/bind/db.zz520.online";
        allow-transfer { 10.0.0.105; };
        also-notify { 10.0.0.105; };
};
ZONE

# 区域数据库文件
cat > db.zz520.online << 'DB'
$TTL    604800
@       IN      SOA     ns1.zz520.online. admin.zz520.online. (
                              2
                         604800
                          86400
                        2419200
                         604800 )
@       IN      NS      ns1.zz520.online.
@       IN      NS      ns2.zz520.online.
ns1     IN      A       10.0.0.104
ns2     IN      A       10.0.0.105
harbor  IN      A       10.0.0.204
DB

chown root:bind /etc/bind/db.zz520.online
chmod 644 /etc/bind/db.zz520.online

named-checkconf
if [ $? -ne 0 ]; then
    echo "配置检查失败"
    exit 1
fi

systemctl daemon-reload
systemctl restart named
systemctl enable named
echo "主DNS部署完成"

部署DNS从服务器(节点105)

同样需要关闭防火墙并释放53端口。创建deploy_bind_slave.sh

#!/bin/bash
apt update && apt install -y bind9 bind9-utils dnsutils

cd /etc/bind
cp named.conf.options named.conf.options.bak
cp named.conf.local named.conf.local.bak

cat > named.conf.options << 'OPTIONS'
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; };
};
OPTIONS

cat > named.conf.local << 'ZONE'
zone "zz520.online" {
        type slave;
        masters { 10.0.0.104; };
        file "/var/cache/bind/db.zz520.online";
        allow-notify { 10.0.0.104; };
};
ZONE

named-checkconf
if [ $? -ne 0 ]; then
    echo "配置检查失败"
    exit 1
fi

systemctl daemon-reload
systemctl restart named
systemctl enable named
echo "从DNS部署完成"
sleep 3
ls -la /var/cache/bind/ | grep db.zz520

DNS记录管理工具

在主节点创建manage_bind_records.sh,用于增删查DNS记录:

#!/bin/bash
ZONE_FILE="/etc/bind/db.zz520.online"
ZONE_NAME="zz520.online"

increment_serial() {
    local serial=$(grep -Eo '^[[:space:]]*[0-9]+[[:space:]]*;[[:space:]]*Serial' "$ZONE_FILE" | awk '{print $1}')
    local new_serial=$((serial + 1))
    sed -i "s/^[[:space:]]*${serial}[[:space:]]*;[[:space:]]*Serial/                             ${new_serial}         ; Serial/" "$ZONE_FILE"
    echo "序列号:$serial -> $new_serial"
}

add_record() {
    read -p "主机名(不含域名): " HOST
    read -p "IP地址: " IP
    if grep -q "^${HOST}[[:space:]]" "$ZONE_FILE"; then
        read -p "记录已存在,继续? (y/n): " confirm
        [[ "$confirm" != "y" ]] && return
    fi
    echo "${HOST}     IN      A       ${IP}" >> "$ZONE_FILE"
    echo "记录已添加"
    increment_serial
    named-checkzone "$ZONE_NAME" "$ZONE_FILE" && rndc reload
}

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

while true; do
    echo "1. 查看记录"
    echo "2. 添加A记录"
    echo "3. 退出"
    read -p "选择: " ch
    case $ch in
        1) view_records;;
        2) add_record;;
        3) exit;;
        *) echo "无效";;
    esac
done

第二阶段:Keepalived高可用配置

系统优化(节点104/105均执行)

创建optimize_ubuntu_for_harbor_ha.sh

#!/bin/bash
set -e

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

# 内核参数
cat > "$SYSCTL_FILE" << 'EOF'
net.ipv4.ip_nonlocal_bind = 1
net.ipv4.ip_forward = 1
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.default.arp_ignore = 1
net.ipv4.conf.ens33.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.default.arp_announce = 2
net.ipv4.conf.ens33.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

# 资源限制
cat > /etc/security/limits.d/99-harbor-ha.conf << 'EOF'
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

sysctl --system
echo "优化完成,建议重启"
read -p "立即重启? (y/n): " reboot_confirm
[[ "$reboot_confirm" == "y" ]] && reboot

安装Keepalived

apt update && apt install -y keepalived

部署健康检查脚本

创建deploy_health_checks.sh在/etc/keepalived/scripts/目录下生成各服务检查脚本:

#!/bin/bash
set -e
SCRIPT_DIR="/etc/keepalived/scripts"
mkdir -p "$SCRIPT_DIR"
cd "$SCRIPT_DIR"

# HAProxy检查
cat > chk_haproxy.sh << 'EOF'
#!/bin/bash
pgrep haproxy &>/dev/null
exit $?
EOF

# 端口检查
cat > chk_port.sh << 'EOF'
#!/bin/bash
ss -tulpn | grep -q ":${1}" &>/dev/null
exit $?
EOF

# DNS检查
cat > chk_dns.sh << 'EOF'
#!/bin/bash
pgrep named &>/dev/null
exit $?
EOF

# MinIO检查
cat > chk_minio.sh << 'EOF'
#!/bin/bash
LOCAL_IP="10.0.0.104"  # 手动修改为本机IP
ss -tulpn | grep -q ":9000" &>/dev/null
exit $?
EOF

# PostgreSQL角色检查
cat > chk_pgsql_repmgr.sh << 'EOF'
#!/bin/bash
PG_USER="postgres"
REPMGR_BIN="/usr/local/bin/repmgr"
REPMGR_CONF="/apps/repmgr/etc/repmgr.conf"
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_REC=$(su - "$PG_USER" -c "psql -t -c \"$SQL\" 2>/dev/null" | xargs)
    [[ "$IN_REC" == "f" ]] && ROLE="primary" || ROLE="standby"
fi
[[ "$ROLE" == "primary" ]] && exit 0 || exit 1
EOF

# Redis检查
cat > chk_redis.sh << 'EOF'
#!/bin/bash
pgrep redis-server &>/dev/null
exit $?
EOF

chmod +x *.sh
chown -R root:root "$SCRIPT_DIR"
echo "健康检查脚本部署完毕,请修改chk_minio.sh中的LOCAL_IP"

生成Keepalived配置

创建交互式脚本setup_keepalived_conf.sh

#!/bin/bash
set -e
CONF_FILE="/etc/keepalived/keepalived.conf"
IFACE="ens33"
NET_PREFIX="10.0.0"

echo "选择节点类型:"
select NODE in "ha1 (104 Master)" "ha2 (105 Backup)"; do
    case $REPLY in
        1) ROUTER_ID="LVS_HA1"; STATE="MASTER"; PRIO=150; break;;
        2) ROUTER_ID="LVS_HA2"; STATE="BACKUP"; PRIO=100; break;;
        *) echo "无效选择";;
    esac
done

[[ -f "$CONF_FILE" ]] && cp "$CONF_FILE" "${CONF_FILE}.bak.$(date +%s)"

cat > "$CONF_FILE" << EOF
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
}

vrrp_instance VI_DNS {
    state $STATE; interface $IFACE; virtual_router_id 51; priority $PRIO; advert_int 1
    authentication { auth_type PASS; auth_pass dnsvip1; }
    virtual_ipaddress { ${NET_PREFIX}.205/32 dev $IFACE label ${IFACE}:dns; }
    track_script { chk_dns; chk_haproxy; }
}
vrrp_instance VI_MINIO {
    state $STATE; interface $IFACE; virtual_router_id 54; priority $PRIO; advert_int 1
    authentication { auth_type PASS; auth_pass minivip1; }
    virtual_ipaddress { ${NET_PREFIX}.203/32 dev $IFACE label ${IFACE}:minio; }
    track_script { chk_minio; }
}
vrrp_instance VI_HARBOR {
    state $STATE; interface $IFACE; virtual_router_id 55; priority $PRIO; advert_int 1
    authentication { auth_type PASS; auth_pass harborvip1; }
    virtual_ipaddress { ${NET_PREFIX}.204/32 dev $IFACE label ${IFACE}:harbor; }
    track_script { chk_harbor; chk_haproxy; }
}
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 { ${NET_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 { ${NET_PREFIX}.202/32 dev $IFACE label ${IFACE}:redis; }
    track_script { chk_redis; }
}
EOF

echo "配置生成完毕:$CONF_FILE"

启动与测试

systemctl enable keepalived --now

在主节点查看VIP:ip addr show ens33,应看到5个VIP。模拟故障:在主节点停止keepalived,VIP应漂移至从节点。恢复主节点后,无状态VIP自动迁回,有状态VIP(PG、Redis)保持不动。

第三阶段:SSL证书申请

配置DNSPod API密钥

在DNSPod后台获取API Token后,执行安全初始化脚本setup-secrets.sh

#!/bin/bash
set -euo pipefail
SECRET_DIR="/etc/acme-secrets"
mkdir -p "$SECRET_DIR"
chmod 700 "$SECRET_DIR"

read -p "DNSPod ID: " dp_id
read -s -p "DNSPod 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 "密钥已保存"

申请通配符证书

创建acme_tencent_issue.sh

#!/bin/bash
set -euo pipefail

MAIN_DOMAIN="zz520.online"
ACCOUNT_EMAIL="admin@${MAIN_DOMAIN}"
SECRET_DIR="/etc/acme-secrets"
TARGET_DIR="/etc/ssl/acme/${MAIN_DOMAIN}"

# 读取密钥
DP_Id=$(cat "$SECRET_DIR/dp_id" | tr -d '[:space:]')
DP_Key=$(cat "$SECRET_DIR/dp_key" | tr -d '[:space:]')
export DP_Id DP_Key

# 安装acme.sh
ACME_BIN="$HOME/.acme.sh/acme.sh"
[[ ! -f "$ACME_BIN" ]] && curl https://get.acme.sh | sh

# 设置CA和申请证书
"$ACME_BIN" --set-default-ca --server letsencrypt
"$ACME_BIN" --issue --dns dns_dp -d "$MAIN_DOMAIN" -d "*.${MAIN_DOMAIN}" --keylength ec-256

# 部署
mkdir -p "$TARGET_DIR"
"$ACME_BIN" --install-cert -d "$MAIN_DOMAIN" -d "*.${MAIN_DOMAIN}" --ecc \
    --key-file "$TARGET_DIR/privkey.pem" \
    --fullchain-file "$TARGET_DIR/fullchain.pem"

chmod 700 "$TARGET_DIR"
chmod 600 "$TARGET_DIR/privkey.pem"
chmod 644 "$TARGET_DIR/fullchain.pem"
chown -R root:root "$TARGET_DIR"

echo "证书路径:"
echo "  Full Chain: $TARGET_DIR/fullchain.pem"
echo "  Private Key: $TARGET_DIR/privkey.pem"

附:Vim粘贴缩进修复:在~/.vimrc中添加:

augroup pastetoggle
  autocmd!
  au InsertEnter * set paste
  au InsertLeave * set nopaste
augroup END

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。