基于共享存储的Harbor高可用-Docker部署方案

Posted 琦彦

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于共享存储的Harbor高可用-Docker部署方案相关的知识,希望对你有一定的参考价值。

部署规划

架构图

Redis也可以和Harbor集中部署。这样避免了在不使用Reids集群模式下单节点的故障。

使用此模式,目前测试对于Harbor的登录,镜像信息查看,在计算节点,镜像的推送和下载没有问题。

其他情况,尚未可知,待验证。

部署组件说明

VIP:用户将通过VIP访问harbor集群,访问数据库集群。只有持有 VIP 的服务器才会提供服务。

Harbor instance1,2:与 Keepalived 共享 VM1,2。

DB Cluster:存储用户认证信息、镜像元数据信息等

Shared Storage:共享存储用于存储 Harbor 使用的 Docker 存储。用户推送的镜像实际上存放在这个共享存储中。共享存储确保多个 Harbor 实例具有一致的存储后端。共享存储可以是 Swift、NFS、S3、azure、GCS 或 OSS。其次,提供数据备份的能力。

Redis :存储 Harbor UI 会话数据和存储镜像仓库元数据缓存,当一个 Harbor 实例失败或负载均衡器将用户请求路由到另一个 Harbor 实例时,任何 Harbor 实例都可以查询 Redis 以检索会话信息,以确保最终用户具有持续的会话。Redis也可以和Harbor集中部署。

部署先决条件

  1. 需要独立的 DB集群 (PostgreSQL )
  2. 需要支持NFS或S3的共享存储(Parastor验证)
  3. 需要Redis。
  4. Harbor 实例的 n 个 (n >=2)
  5. 1 个静态 IP 地址(用作 VIP)
资源类型资源数量版本
DB集群1PostgreSQL 9.6.21 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 6.3.0, 64-bit
Redis集群1 (Sentinal模式)redis_version:4.0.14
共享存储1支持Swift、NFS、S3、azure、GCS 或 OSS
Harbor 实例>=2Harbor 2.2.2
静态 IP 地址1 (VIP)Keepalived v1.3.5 (03/19,2017)
DockernClient:18.09.0 ; Server:18.09.0
Docker Composen1.28.28

部署节点规划

主机名用途备注
Harbor 1Harbor镜像仓库-主挂载Parastor共享存储
harbor nHarbor镜像仓库-备挂载Parastor共享存储
keepalived01高可用漂移地址+PostgreSQL主+Redis单机配置VIP
keepalived02高可用漂移地址+PostgreSQL备配置VIP

部署流程

1. 安装Harbor,导出基础harbor数据

1. 安装docker-compose (离线)

# 在线安装
curl -L "https://github.com/docker/compose/releases/download/1.28.6/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose

2. Docker配置镜像加速与国内docker-cn源

sudo tee /etc/docker/daemon.json <<-'EOF'

  "registry-mirrors": ["https://uy35zvn6.mirror.aliyuncs.com"],
   "insecure-registries": ["https://harbor.fly.com"]

EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

3. 安装Harbor2.2.2(离线)

# 3.1 下载Harbor
wget -P /usr/local wget https://github.com/goharbor/harbor/releases/download/v2.2.2/harbor-online-installer-v2.2.2.tgz

tar zxf /usr/local/harbor-online-installer-v2.2.2.tgz -C /opt/harbor

# 3.2 修改配置文件,根据自己的需求进行修改
cd /var/www/dream/harbor
cp harbor.yml.tmpl harbor.yml
# harbor.yml中按需修改或添加如下内容
# Configuration file of Harbor

# The IP address or hostname to access admin UI and registry service.
# DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients.
hostname: harbor.fly.com

# http related config
http:
  # port for http, default is 80. If https enabled, this port will redirect to https port
  port: 80

# https related config
https:
  # https port for harbor, default is 443
  port: 443
  # The path of cert and key files for nginx
  certificate: /data/harbor/ssl/fly.com.cer
  private_key: /data/harbor/ssl/fly.com.key

# # Uncomment following will enable tls communication between all harbor components
# internal_tls:
#   # set enabled to true means internal tls is enabled
#   enabled: true
#   # put your cert and key files on dir
#   dir: /etc/harbor/tls/internal

# Uncomment external_url if you want to enable external proxy
# And when it enabled the hostname will no longer used
# external_url: https://reg.mydomain.com:8433

# The initial password of Harbor admin
# It only works in first time to install harbor
# Remember Change the admin password from UI after launching Harbor.
# 初始password,可以修改成自己需要的,然后后续在WEBUI上自行修改。
harbor_admin_password: 1234567
## 添加禁止用户自注册
self_registration: off
## 设置只有管理员可以创建项目
project_creation_restriction: adminonly
# The default data volume
data_volume: /data/harbor


# 3.3 执行安装命令
bash /data/harbor/install.sh
# 如果对配置文件harbor.yml,需要使用./prepare脚本重新生成
./prepare
# 重启
docker-compose -f docker-compose.yml up  -d --force-recreate

4. 常用命令示例

# 登录
docker login https://harbor.fly.com
# 拉取
docker pull busybox
# 打包
docker build -t busybox:v1 . 
docker build -t busybox:v1 -f Dockerfile .
# 打TAG
docker tag busybox:latest harbor.fly.com/ops/busybox:latest
# 上传
docker push harbor.fly.com/library/busybox:latest

5. 备份harbor库,并且导出用于恢复

如果是全新部署,不需要导出备份Harbor库,Harbor在创建时候会自动创建数据库和表

# 进入容器备份
docker container exec -it harbor-db /bin/bash

# 容器内执行pg备份
pg_dump -U postgres registry > /tmp/registry.sql 
pg_dump -U postgres notarysigner > /tmp/notarysigner.sql  
pg_dump -U postgres notaryserver > /tmp/notaryserver.sql

# 宿主机执行,复制到本地宿主机
docker container cp harbor-db:/tmp/registry.sql /data/harbor/backup_sql/
docker container cp harbor-db:/tmp/notarysigner.sql /data/harbor/backup_sql/
docker container cp harbor-db:/tmp/notaryserver.sql /data/harbor/backup_sql/

2. 部署PG主从复制集群

1. PG主从复制部署

前置条件

  • 准备两个 Linux 服务器(可使用虚拟机)
  • 已经安装 Docker
  • 用 Bitnami PostgreSQL Docker Image 设置流复制集群
主从复制特点

主节点宕机,恢复后,可以自动访问主

主机点宕机,从节点具备可读能力,写能力

数据库落地到共享存储中

使用流复制和 repmgr 设置 HA PostgreSQL 集群

使用以下环境变量,可以使用 Bitnami PostgreSQL HA Docker 镜像轻松设置具有[流复制](Streaming replication)和 repmgrHA PostgreSQL 集群:

  • POSTGRESQL_PASSWORD:postgres 用户的密码。没有默认值。
  • POSTGRESQL_PASSWORD_FILE:包含 postgres 用户密码的文件的路径。 这将覆盖 POSTGRESQL_PASSWORD 中指定的值。 没有默认值。
  • REPMGR_USERNAMErepmgr 用户的用户名。默认为 repmgr
  • REPMGR_PASSWORD_FILE:包含 repmgr 用户密码的文件的路径。这将覆盖 REPMGR_PASSWORD 中指定的值。 没有默认值。
  • REPMGR_PASSWORDrepmgr 用户的密码。没有默认值。
  • REPMGR_USE_PASSFILE:配置 repmgr 以在其配置中使用 passfilePGPASSFILE 而不是纯文本密码。
  • REPMGR_PASSFILE_PATH:密码文件的位置,如果它不存在,它将使用 REPMGR 凭据创建。
  • REPMGR_PRIMARY_HOST:初始主节点的主机名。没有默认值。
  • REPMGR_PARTNER_NODES:集群中的伙伴节点的逗号分隔列表。没有默认值。
  • REPMGR_NODE_NAME:节点名称。没有默认值。
  • REPMGR_NODE_NETWORK_NAME:节点主机名。没有默认值。
  • REPMGR_PGHBA_TRUST_ALL:这将在生成的 pg_hba.conf 中设置 auth-method。仅当你使用带有 LDAP 身份验证的 pgpool 时才将其设置为 yes。默认为 no
  • POSTGRESQL_NUM_SYNCHRONOUS_REPLICAS: 确定将启用同步复制的副本数。此数量不得超过您在集群中配置的 slave 的数量。
  • POSTGRESQL_SYNCHRONOUS_COMMIT_MODE: 建立同步提交的类型。可用选项有:onremote_applyremote_writelocaloff。 默认值为 on。有关更多信息,请查看官方 PostgreSQL 文档

HA PostgreSQL 集群中,你可以拥有一个主节点和零个或多个备用节点。主节点处于读写模式,而备用节点处于只读模式。

注意:对于 9.6 版之前的 Postgresql,REPMGR_USE_PASSFILE 和 REPMGR_PASSFILE_PATH 将被忽略。

使用 REPMGR_PASSFILE_PATH 挂载外部密码文件时,还需要相应地配置 REPMGR_PASSWORD 和 REPMGR_USERNAME。

Step 1: 创建 network
# 方式1:主从部署在同一个节点
docker network create sharednet --driver bridge

# 方式2:主从部署在不同的节点
## docker swarm 跨节点通信
##开启以下端口 (这个不能偷懒,三台都要执行,或者嫌麻烦的话,直接关闭防火墙也行,但是生产环境不建议这样做) 
firewall-cmd --add-port=2377/tcp --permanent #TCP端口2377用于集群管理通信 
firewall-cmd --add-port=7946/tcp --permanent #TCP和UDP端口7946用于节点之间的通信 
firewall-cmd --add-port=7946/udp --permanent 
firewall-cmd --add-port=4789/udp --permanent #UDP端口4789用于覆盖网络流量 
firewall-cmd --reload                        #重新载入刷新修改
firewall-cmd --zone=public --list-ports      #查看开通的端口
## 初始化`Swarm`集群服务
docker swarm init --advertise-addr=10.0.41.55
## 如果没有记住加入集群的`token`,以下可以重新获取*
docker swarm join-token worker
## 其他节点分别加入`Swarm`集群
docker swarm join --token SWMTKN-1-tokenxxxxxxx 10.0.0.11:2377
## 在节点上创建网络
#### - -d(driver):网络驱动类型 
#### - --attachable:声明当前创建的overlay网络可以被容器加入
#### - sharednet:自定义的网络名称
docker network create -d overlay --attachable sharednet

##各个节点离开集群
# 工作节点离开集群
# docker swarm leave
# 管理节点离开集群
# docker swarm leave --force #必须使用参数--force,强制离开集群,否则会报错
## 删除失效节点-docker node rm
Step 2: 创建初始主节点

第一步是启动初始主节点:

docker run --detach --name pg-0 -p 5433:5432 \\
  --network sharednet \\
  --env REPMGR_PARTNER_NODES=pg-0,pg-1 \\
  --env REPMGR_NODE_NAME=pg-0 \\
  --env REPMGR_NODE_NETWORK_NAME=pg-0 \\
  --env REPMGR_PRIMARY_HOST=pg-0 \\
  --env REPMGR_PASSWORD=root123 \\
  --env POSTGRESQL_DATABASE=registry \\
  --env POSTGRESQL_PASSWORD=root123 \\
  bitnami/postgresql-repmgr:9.6.21
Step 3: 创建备用节点

接下来我们启动一个备用节点:

docker run --detach --name pg-1 -p 5433:5432 \\
  --network sharednet \\
  --env REPMGR_PARTNER_NODES=pg-0,pg-1 \\
  --env REPMGR_NODE_NAME=pg-1 \\
  --env REPMGR_NODE_NETWORK_NAME=pg-1 \\
  --env REPMGR_PRIMARY_HOST=pg-0 \\
  --env REPMGR_PASSWORD=root123 \\
  --env POSTGRESQL_PASSWORD=root123 \\
  bitnami/postgresql-repmgr:9.6.21

使用这三个命令,你现在可以启动并运行一个两节点 PostgreSQL 主备流复制集群。 你可以通过添加/删除备用节点来扩展集群,而不会导致任何停机时间。

注意:集群会完整地复制主节点,其中包括所有用户和数据库。

如果主节点宕机,repmgr 将确保任何备用节点担任主节点,从而保证高可用性。

LOG:  database system is ready to accept read only connections
LOG:  started streaming WAL from primary at 0/5000000 on timeline 1
 done
server started
postgresql-repmgr 08:04:49.98 INFO  ==> ** Starting repmgrd **
[2022-06-08 08:04:49] [NOTICE] repmgrd (repmgrd 5.2.1) starting up
INFO:  set_repmgrd_pid(): provided pidfile is /opt/bitnami/repmgr/tmp/repmgr.pid
[2022-06-08 08:04:50] [NOTICE] starting monitoring of node "pg-1" (ID: 1001)
ERROR:  cannot execute UPDATE in a read-only transaction
STATEMENT:  UPDATE "public"."tag" SET "pull_time" = '2022-06-08 06:47:10' WHERE "id" = 75
FATAL:  could not receive data from WAL stream: server closed the connection unexpectedly
		This probably means the server terminated abnormally
		before or while processing the request.

# 1. 发现主机点宕机
LOG:  invalid record length at 0/528F650: wanted 24, got 0
FATAL:  could not connect to the primary server: could not connect to server: Connection refused
		Is the server running on host "pg-0" (172.23.0.2) and accepting
		TCP/IP connections on port 5432?

# 2. 尝试连接主机点(3次)
[2022-06-08 08:40:00] [WARNING] unable to ping "user=repmgr password=repmgrpass host=pg-0 dbname=repmgr port=5432 connect_timeout=5"
[2022-06-08 08:40:00] [DETAIL] PQping() returned "PQPING_NO_ATTEMPT"
[2022-06-08 08:40:00] [WARNING] unable to connect to upstream node "pg-0" (ID: 1000)
[2022-06-08 08:40:00] [WARNING] unable to ping "user=repmgr password=repmgrpass connect_timeout=5 dbname=repmgr host=pg-0 port=5432 fallback_application_name=repmgr"
[2022-06-08 08:40:00] [DETAIL] PQping() returned "PQPING_NO_ATTEMPT"
[2022-06-08 08:40:05] [WARNING] unable to ping "user=repmgr password=repmgrpass connect_timeout=5 dbname=repmgr host=pg-0 port=5432 fallback_application_name=repmgr"
[2022-06-08 08:40:05] [DETAIL] PQping() returned "PQPING_NO_ATTEMPT"
FATAL:  could not connect to the primary server: could not translate host name "pg-0" to address: Name or service not known
	
FATAL:  could not connect to the primary server: could not translate host name "pg-0" to address: Name or service not known
	
[2022-06-08 08:40:10] [WARNING] unable to ping "user=repmgr password=repmgrpass connect_timeout=5 dbname=repmgr host=pg-0 port=5432 fallback_application_name=repmgr"
[2022-06-08 08:40:10] [DETAIL] PQping() returned "PQPING_NO_ATTEMPT"

# 3. 尝试连接主机点失败(3次),从备用节点中选主
[2022-06-08 08:40:10] [WARNING] unable to reconnect to node "pg-0" (ID: 1000) after 3 attempts
[2022-06-08 08:40:10] [NOTICE] this node is the only available candidate and will now promote itself
NOTICE: using provided configuration file "/opt/bitnami/repmgr/conf/repmgr.conf"
DEBUG: connecting to: "user=repmgr password=repmgrpass connect_timeout=5 dbname=repmgr host=pg-1 port=5432 fallback_application_name=repmgr options=-csearch_path="
DEBUG: set_config():
  SET synchronous_commit TO 'local'
INFO: connected to standby, checking its state
DEBUG: get_recovery_type(): SELECT pg_catalog.pg_is_in_recovery()
DEBUG: get_node_record():
  SELECT n.node_id, n.type, n.upstream_node_id, n.node_name,  n.conninfo, n.repluser, n.slot_name, n.location, n.priority, n.active, n.config_file, '' AS upstream_node_name, NULL AS attached   FROM repmgr.nodes n  WHERE n.node_id = 1001
DEBUG: get_replication_info():
 SELECT ts,         in_recovery,         last_wal_receive_lsn,         last_wal_replay_lsn,         last_xact_replay_timestamp,         CASE WHEN (last_wal_receive_lsn = last_wal_replay_lsn)           THEN 0::INT         ELSE           CASE WHEN last_xact_replay_timestamp IS NULL             THEN 0::INT           ELSE             EXTRACT(epoch FROM (pg_catalog.clock_timestamp() - last_xact_replay_timestamp))::INT           END         END AS replication_lag_time,         last_wal_receive_lsn >= last_wal_replay_lsn AS receiving_streamed_wal,         wal_replay_paused,         upstream_last_seen,         upstream_node_id    FROM (  SELECT CURRENT_TIMESTAMP AS ts,         pg_catalog.pg_is_in_recovery() AS in_recovery,         pg_catalog.pg_last_xact_replay_timestamp() AS last_xact_replay_timestamp,         COALESCE(pg_catalog.pg_last_xlog_receive_location(), '0/0'::PG_LSN) AS last_wal_receive_lsn,         COALESCE(pg_catalog.pg_last_xlog_replay_location(),  '0/0'::PG_LSN) AS last_wal_replay_lsn,         CASE WHEN pg_catalog.pg_is_in_recovery() IS FALSE           THEN FALSE           ELSE pg_catalog.pg_is_xlog_replay_paused()         END AS wal_replay_paused,         CASE WHEN pg_catalog.pg_is_in_recovery() IS FALSE           THEN -1           ELSE repmgr.get_upstream_last_seen()         END AS upstream_last_seen,         CASE WHEN pg_catalog.pg_is_in_recovery() IS FALSE           THEN -1           ELSE repmgr.get_upstream_node_id()         END AS upstream_node_id           ) q 
INFO: searching for primary node
DEBUG: get_primary_connection():
  SELECT node_id, conninfo,          CASE WHEN type = 'primary' THEN 1 ELSE 2 END AS type_priority	   FROM repmgr.nodes    WHERE active IS TRUE      AND type != 'witness' ORDER BY active DESC, type_priority, priority, node_id
INFO: checking if node 1000 is primary
DEBUG: connecting to: "user=repmgr password=repmgrpass connect_timeout=5 dbname=repmgr host=pg-0 port=5432 fallback_application_name=repmgr options=-csearch_path="
ERROR: connection to database failed
DETAIL: 
could not translate host name "pg-0" to address: Name or service not known
DETAIL: attempted to connect using:
  user=repmgr password=repmgrpass connect_timeout=5 dbname=repmgr host=pg-0 port=5432 fallback_application_name=repmgr options=-csearch_path=
INFO: checking if node 1001 is primary
DEBUG: connecting to: "user=repmgr password=repmgrpass connect_timeout=5 dbname=repmgr host=pg-1 port=5432 fallback_application_name=repmgr options=-csearch_path="
DEBUG: set_config():
  SET synchronous_commit TO 'local'
DEBUG: get_recovery_type(): SELECT pg_catalog.pg_is_in_recovery()
DEBUG: get_node_replication_stats():
 SELECT pg_catalog.current_setting('max_wal_senders')::INT AS max_wal_senders,         (SELECT pg_catalog.count(*) FROM pg_catalog.pg_stat_replication) AS attached_wal_receivers,         current_setting('max_replication_slots')::INT AS max_replication_slots,         (SELECT pg_catalog.count(*) FROM pg_catalog.pg_replication_slots WHERE slot_type='physical') AS total_replication_slots,         (SELECT pg_catalog.count(*) FROM pg_catalog.pg_replication_slots WHERE active IS TRUE AND slot_type='physical')  AS active_replication_slots,         (SELECT pg_catalog.count(*) FROM pg_catalog.pg_replication_slots WHERE active IS FALSE AND slot_type='physical') AS inactive_replication_slots,         pg_catalog.pg_is_in_recovery() AS in_recovery
DEBUG: get_active_sibling_node_records():
  SELECT n.node_id, n.type, n.upstream_node_id, n.node_name,  n.conninfo, n.repluser, n.slot_name, n.location, n.priority, n.active, n.config_file, '' AS upstream_node_name, NULL AS attached     FROM repmgr.nodes n    WHERE n.upstream_node_id = 1000      AND n.node_id != 1001      AND n.active IS TRUE ORDER BY n.node_id 
DEBUG: clear_node_info_list() - closing open connections
DEBUG: clear_node_info_list() - unlinking
DEBUG: get_node_record():
  SELECT n.node_id, n.type, n.upstream_node_id, n.node_name,  n.conninfo, n.repluser, n.slot_name, n.location, n.priority, n.active, n.config_file, '' AS upstream_node_name, NULL AS attached   FROM repmgr.nodes n  WHERE n.node_id = 1001
NOTICE: promoting standby to primary
DETAIL: promoting server "pg-1" (ID: 1001) using "/opt/bitnami/postgresql/bin/pg_ctl -o "--config-file="/opt/bitnami/postgresql/conf/postgresql.conf" --external_pid_file="/opt/bitnami/postgresql/tmp/postgresql.pid" --hba_file="/opt/bitnami/postgresql/conf/pg_hba.conf"" -w -D '/bitnami/postgresql/data' promote"
LOG:  received promote request
LOG:  redo done at 0/528F628
LOG:  last completed transaction was at log time 2022-06-08 08:39:58.858907+00
NOTICE: waiting up to 60 seconds (parameter "promote_check_timeout") for promotion to complete
DEBUG: get_recovery_type(): SELECT pg_catalog.pg_is_in_recovery()
LOG:  selected new timeline ID: 2
LOG:  archive recovery complete
LOG:  MultiXact member wraparound protections are now enabled
LOG:  database system is ready to accept connections
LOG:  autovacuum launcher started
DEBUG: get_recovery_type(): SELECT pg_catalog.pg_is_in_recovery()
INFO: standby promoted to primary after 1 second(s)
DEBUG: setting node 1001 as primary and marking existing primary as failed
DEBUG: begin_transaction()
DEBUG: commit_transaction()
NOTICE: STANDBY PROMOTE successful
DETAIL: server "pg-1" (ID: 1001) was successfully promoted to primary
DEBUG: _create_event(): event is "standby_promote" for node 1001
DEBUG: get_recovery_type(): SELECT pg_catalog.pg_is_in_recovery()
DEBUG: _create_event():
   INSERT INTO repmgr.events (              node_id,              event,              successful,              details             )       VALUES ($1, $2, $3, $4)    RETURNING event_timestamp 
DEBUG: _create_event(): Event timestamp is "2022-06-08 08:40:11.867269+00"
DEBUG: _create_event(): command is '/opt/bitnami/repmgr/events/router.sh %n %e %s "%t" "%d"'
INFO: executing notification command for event "standby_promote"
# 4. 产生新的主节点
DETAIL: command is:
  /opt/bitnami/repmgr/events/router.sh 1001 standby_promote 1 "2022-06-08 08:40:11.867269+00" "server \\"pg-1\\" (ID: 1001) was successfully promoted to primary"
DEBUG: clear_node_info_list() - closing open connections
DEBUG: clear_node_info_list() - unlinking
[2022-06-08 08:40:11] [NOTICE] node 1001 has recovered, reconnecting
[2022-06-08 08:40:12] [NOTICE] monitoring cluster primary "pg-1" (ID: 1001)

# 5. 原来的主节点恢复正常后,会变为备用节点
[2022-06-08 08:48:55] [NOTICE] new standby "pg-0" (ID: 1000) has connected

注意:集群中其他节点的配置需要更新,以便它们知道它们。这将需要重新启动旧节点,以适应 REPMGR_PARTNER_NODES 环境变量。

脚本:Docker 部署 bitnami/postgresql-repmgr

在虚拟机上直接部署 pgsql 集群在时间成本上,还是不太容易的。我们这里使用 docker 去管理,会轻松一点。

创建 volume,由于复制管理器映像的 PostgreSQL 是非 root 用户,因此您还需要为主机中的挂载目录设置适当的权限:

# 主实例
# docker volume create pg-0
# chgrp -R root /var/lib/docker/volumes/pg-0
# chmod -R g+rwX /var/lib/docker/volumes/pg-0

# 从实例
# docker volume create pg-1
# chgrp -R root /var/lib/docker/volumes/pg-1
# chmod -R g+rwX /var/lib/docker/volumes/pg-1

我们这里将主从部署在不同的主机上,所以两组命令应该在两台主机上执行。从而保证不同时挂掉。

启动 pgsql 实例的脚本:

#创建存储和配置文件目录
mkdir -p /opt/pgsql/bitnami/postgresql
mkdir -p /opt/pgsql/custom-conf
chgrp -R root /opt/pgsql
chmod -R g+rwX /opt/pgsql

#创建存储和配置文件目录
cat > /opt/pgsql/start-pg.sh << "EOF"
#!/bin/bash

#  Exit immediately if any untested command fails
#set -o errexitb

# 参数校验
node=$1
if [[ -z "$node" ]]; then
        echo "Error: need node argument, example: pg-0"
        exit -1
fi

# pg容器是否启动
existUp=$(docker ps -f name=$node -q)

if [[ -n "$existUp" ]]; then
        # nothing
        echo "node: $node is Up"
        exit 0
fi

existNotUp=$(docker ps -a -f name=$node -q)

if [[ -n "$existNotUp" ]]; then
        # start
        echo "node: $node is not Up, will start it"
        docker start $existNotUp
        exit 0
fi

# create
## 创建 network
docker network ls | grep sharednet
#docker network create -d overlay --attachable sharednet

## 创建 pg
docker run --detach --name $node -p 5433:5432 \\
--network sharednet \\
--env REPMGR_PARTNER_NODES=pg-0,pg-1 \\
--env REPMGR_NODE_NAME=$node \\
--env REPMGR_NODE_NETWORK_NAME=$node \\
--env REPMGR_PRIMARY_HOST=pg-0 \\
--env REPMGR_PASSWORD=root123 \\
--env POSTGRESQL_PASSWORD=root123 \\
--env POSTGRESQL_DATABASE=registry \\
--env BITNAMI_DEBUG=true \\
--env TZ=Asia/Shanghai \\
-v /opt/pgsql/bitnami/postgresql/$node:/bitnami/postgresql \\
-v /opt/pgsql/custom-conf/:/bitnami/repmgr/conf/ \\
bitnami/postgresql-repmgr:9.6.21
EOF

# 赋予执行权限
chmod 777 /opt/pgsql/start-pg.sh

启动时,用:

# 容器名为 pg-0(主)或者 pg-1(从)
/opt/pgsql/start-pg.sh 容器名

/opt/pgsql/start-pg.sh pg-0
/opt/pgsql/start-pg.sh pg-1
pgsql 挂掉自启动

docker 容器挂掉后,用 crontab 保证容器可以重新启动,30s 为间隔去执行 start-pg.sh 脚本。

执行 crontab -e 在最后新增以下内容,然后:wq 保存退出即可:

# Need these to run on 30-sec boundaries, keep commands in sync.
* * * * *              /pgsql/start-pg.sh pg-1
* * * * * ( sleep 30 ; /pgsql/start-pg.sh pg-1 )
查询复制状态
-- 主库查看wal日志发送状态
select * from pg_stat_replication;

-- 从库查看wal日志接收状态
select * from pg_stat_wal_receiver;


-- 也可以通过该名称查看
pg_controldata  | grep state

-- 也可以查看这个,主库是f代表false ;备库是t,代表true
select pg_is_in_recovery();

2. 登录PG主节点导入备份的数据

如果是全新部署,不需要导入备份的Harbor库,Harbor在创建时候会自动创建数据库和表

3. 配置keepalived

安装

yum install -y keepalived

配置文件详解

文件说明
/usr/sbin/keepalived二进制程序
/etc/keepalived/keepalived.conf配置文件
/usr/lib/systemd/system/keepalived.service服务文件
/var/log/messages日志文件

tail -100f /var/log/messages | grep Keep*

配置keepalived

里面主要包括以下几个配置区域,分别是:

  • global_defs: 主要是配置故障发生时的通知对象以及机器标识。
  • static_ipaddressstatic_routes:static_ipaddress和static_routes区域配置的是是本节点的IP和路由信息。如果你的机器上已经配置了IP和路由,那么这两个区域可以不用配置。其实,一般情况下你的机器都会有IP地址和路由信息的,因此没必要再在这两个区域配置。
  • vrrp_script:用来做健康检查的,当时检查失败时会将vrrp_instance的priority减少相应的值。
  • vrrp_instance:用来定义对外提供服务的VIP区域及其相关属性。
  • vrrp_rsync_group:用来定义vrrp_intance组,使得这个组内成员动作一致。
  • virtual_server_groupvirtual_server:定义LVS集群服务,可以是IP+PORT;也可以是fwmark 数字,也就是防火墙规则,所以通过这里就可以看出来keepalive天生就是为ipvs而设计的

以下所有脚本在主备库都创建:

主keepalived.conf

cat > /etc/keepalived/keepalived.conf <<"EOF"

global_defs 
  # 路由id:当前安装keepalived的节点主机标识符,保证全局唯一
   router_id keep_dev


# 业务应用检测脚本
vrrp_script check_pg_alived 
   # 业务应用检测脚本
   #一句指令或者一个脚本文件,需返回0(成功)或非0(失败),keepalived以此为依据判断其监控的服务状态。
   script "/etc/keepalived/check_pg.sh"
   # 每隔两秒运行上一行脚本
   interval 2
   # 脚本运行的超时时间
   timeout 5
   # 配置几次检测失败才认为服务异常
   #2次失败再降级,两次返回1(即两次进程不存在)则优先级下降10
   fall 2
   # 配置几次检测成功才认为服务正常
   # rise 1
   # adjust priority by this weight, default 0
   # 优先级变化幅度,如果script中的指令执行失败,那么相应的vrrp_instance的优先级会减少10个点。
   weight -10


vrrp_instance VI_1 
    # 表示状态是MASTER主机还是备用机BACKUP
    state MASTER
    nopreempt
    # 该实例绑定的网卡
    interface eth0
    # 保证主备节点一致即可
    virtual_router_id 10
    # 权重,master权重一般高于backup,如果有多个,那就是选举,谁的权重高,谁就当选
    priority 100
    # 主备之间同步检查时间间隔,单位秒
    advert_int 1
    # 认证权限密码,防止非法节点进入
    authentication 
        auth_type PASS
        auth_pass lhr
    

    track_script 
        check_pg_alived
    
    # 虚拟出来的ip,可以有多个(vip)
    virtual_ipaddress 
        # 注意:主备两台的vip都是一样的,绑定到同一个vip
        10.0.41.156
    



EOF

以上是Keepalived主节点的配置,Keepalived备节点的state参数改为BACKUP, priority参数改成90,其余参数配置一样。

备keepalived.conf

cat > /etc/keepalived/keepalived.conf <<"EOF"

global_defs 
  # 路由id:当前安装keepalived的节点主机标识符,保证全局唯一
   router_id keep_dev


# 业务应用检测脚本
vrrp_script check_pg_alived 
   # 业务应用检测脚本
   #一句指令或者一个脚本文件,需返回0(成功)或非0(失败),keepalived以此为依据判断其监控的服务状态。
   script "/etc/keepalived/check_pg.sh"
   # 每隔两秒运行上一行脚本
   interval 2
   # 脚本运行的超时时间
   timeout 5
   # 配置几次检测失败才认为服务异常
   #2次失败再降级,两次返回1(即两次进程不存在)则优先级下降10
   fall 2
   # 配置几次检测成功才认为服务正常
   # rise 1
   # adjust priority by this weight, default 0
   # 优先级变化幅度,如果script中的指令执行失败,那么相应的vrrp_instance的优先级会减少10个点。
   weight -10


vrrp_instance VI_1 
    # 表示状态是MASTER主机还是备用机BACKUP
    state BACKUP
    nopreempt
    #  该实例绑定的网卡
    interface eth0
    # 保证主备节点一致即可
    virtual_router_id 10
    # 权重,master权重一般高于backup,如果有多个,那就是选举,谁的权重高,谁就当选
    priority 100
    # 主备之间同步检查时间间隔,单位秒
    advert_int 1
    # 认证权限密码,防止非法节点进入
    authentication 
        auth_type PASS
        auth_pass lhr
    

    track_script 
        check_pg_alived
    
    # 虚拟出来的ip,可以有多个(vip)
    virtual_ipaddress 
        # 注意:主备两台的vip都是一样的,绑定到同一个vip
        10.0.41.156
    



EOF

PG状态检测check_pg.sh

对主从PG 状态进行监控,监控脚本 check_pg.sh:

cat > /etc/keepalived/check_pg.sh << "EOF"
#!/bin/bash
pgstate=$(netstat -na|grep "LISTEN"|grep "5433"|wc -l)
echo "【PG 状态(0-不活跃;非0-活跃)】:" $pgstate
if [ "$pgstate" -eq 0 ]; then
# 使用weight,是否不需要stop??
# 相同weight,VIP漂移在哪里??
# 检查进程是否存在,如果存在检查联通性,如果联通了。则返回0, 如果不存在或者不联通则返回1  
echo 'PG 状态不正常'
systemctl stop keepalived
fi
EOF

chmod 755 /etc/keepalived/check_pg.sh

此脚本每隔10秒执行一次,执行频率由keepalived.conf配置文件中interval参数设置,脚本主要作用为:

  1. 检测主库是否存活。
  2. 若主库不可用,则应该关闭主库的keepalived服务。

定时任务-状态检测check_keepalived.sh

对主从PG 状态进行监控,监控脚本 check_keepalived.sh:

mkdir /etc/vip

cat > /etc/vip/check_vip.sh << "EOF"
#!/bin/bash
kpstate=$(ps -ef | grep keepalived | grep -v grep | wc -l)
echo "【keepalived 状态(0-不活跃;非0-活跃)】:" $kpstate
if [ "$kpstate" -eq 0 ]; then
echo 'keepalived 状态不正常'
# 杀掉占用5433端口的PG进程
docker rm -f `docker ps -a|grep "5433"|awk 'print $1'`
echo '杀掉占用5433端口的PG进程'
fi
EOF

chmod 755 /etc/vip/check_vip.sh

前提:vip是和PG主库再一个节点

场景:某个节点拥有vip,keepalived挂掉后,这时候VIP会漂移到另外一个节点,用 crontab 保证PG主库能够随之切换(停止PG主,PG从会随之切换为主)。

执行 crontab -e 或者 vi /etc/crontab 在最后新增以下内容(30s 为间隔去执行 start-pg.sh 脚本),然后:wq 保存退出即可:

# Need these to run on 30-sec boundaries, keep commands in sync.
# 在每分钟的第一秒开始执行crontab任务
* * * * *  sh /etc/vip/check_vip.sh
# 在每分钟的第30秒开始执行crontab任务
* * * * * sleep 30; sh /etc/vip/check_vip.sh
crontab 的延时: 原理:通过延时方法 sleep N 来实现每N秒执行

启动keepalived

systemctl restart keepalived && systemctl enable keepalived && systemctl status keepalived

systemctl status keepalived

systemctl stop keepalived && systemctl status keepalived

验证VIP信息

[root@aiserver harbor]# ip addr | grep -C 3 10.0.41.156
    link/ether 52:54:00:48:eb:27 brd ff:ff:ff:ff:ff:ff
    inet 10.0.41.55/24 brd 10.0.41.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    # VIP信息     
    inet 10.0.41.156/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::5054:ff:fe48:eb27/64 scope link 
       valid_lft forever preferred_lft forever

故障场景

前提: PostgreSQL和 Keepalived 共用两台机器

节点规划

主机名用途备注
Node01Keepalived主+PostgreSQL主配置VIP
Node02Keepalived从+PostgreSQL从配置VIP

PostgreSQL数据库具备故障转移,主从切换能力,即:PostgreSQL主宕机,PostgreSQL从会自动切换为主节点,PostgreSQL从从原来的只读能力,变为读写能力

Keepalived主从配置的weight权重一样(不抢占),同时设置了nopreempt(不抢占)

1. VIP漂移在Node01,Node01上的PostgreSQL主宕机,Node02的PostgreSQL从会变为Node02的PostgreSQL主

这时候需要,

  • Node01的Keepalived主也随之停止服务,使得VIP漂移到Node02

2. Node01的PostgreSQL从恢复正常,Node01的Keepalived主也恢复正常

这时候需要,

  1. Node01的PostgreSQL从依然是Node01的PostgreSQL从
  2. Node02的Keepalived从依然占有VIP – 可以借助于nopreempt(不抢占)策略

备注:

Node01的PostgreSQL从恢复正常的命令:参见:2. 部署PG主从复制集群

Node01的Keepalived主恢复正常的命令

systemctl restart keepalived && systemctl enable keepalived && systemctl status keepalived

3. VIP漂移在Node02,Node02的PostgreSQL主宕机,Node01的PostgreSQL从会变为Node01的PostgreSQL主

这时候需要,

  • Node02的Keepalived从也随之停止服务,使得VIP漂移到Node01

4. VIP漂移在Node01,Node01的Keepalived主宕机,Node02的Keepalived从变为主,Node01上的PostgreSQL主正常

这时候需要,

  • Node01的PG主也随之停止服务

常见问题

1. keepalived启动报错:IPVS: Can’t initialize ipvs: Protocol not available

lsmod | grep ip_vs
modprobe ip_vs
modprobe ip_vs_wrr
lsmod | grep ip_vs
# 如果是容器,那么宿主机也需要加载ip_vs模块。

4. Horbor开启外部PG、Redis配置

1. 对harbor.yml(所有Harbor实例)进行调整,开启外部PG、Redis配置,注释默认的PG数据库配置

# Configuration file of Harbor

# The IP address or hostname to access admin UI and registry service.
# DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients.
hostname: 10.0.41.55

# http related config
http:
  # port for http, default is 80. If https enabled, this port will redirect to https port
  port: 5000

# https related config
#https:
  # https port for harbor, default is 443
#  port: 5000
  # The path of cert and key files for nginx
#  certificate: /data/cert/harbor19.crt
#  private_key: /data/cert/harbor19.key

# # Uncomment following will enable tls communication between all harbor components
# internal_tls:
#   # set enabled to true means internal tls is enabled
#   enabled: true
#   # put your cert and key files on dir
#   dir: /etc/harbor/tls/internal

# Uncomment external_url if you want to enable external proxy
# And when it enabled the hostname will no longer used
# external_url: https://reg.mydomain.com:8433

# The initial password of Harbor admin
# It only works in first time to install harbor
# Remember Change the admin password from UI after launching Harbor.
harbor_admin_password: Harbor12345

# Harbor DB configuration
#database:
  # The password for the root user of Harbor DB. Change this before any production use.
#  password: root123
  以上是关于基于共享存储的Harbor高可用-Docker部署方案的主要内容,如果未能解决你的问题,请参考以下文章

harbor基于keepalive高可用部署

构建docker高可用私有仓库基于Harbor开源系统

harbor基于keepalive高可用部署

Harbor高可用集群设计及部署(实操+视频),基于离线安装方式

k8s部署-19-harbor高可用部署

50-Docker-分布式仓库Harbor高可用