基于共享存储的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集中部署。
部署先决条件
- 需要独立的 DB集群 (PostgreSQL )
- 需要支持NFS或S3的共享存储(Parastor验证)
- 需要Redis。
- Harbor 实例的 n 个 (n >=2)
- 1 个静态 IP 地址(用作 VIP)
资源类型 | 资源数量 | 版本 | |
---|---|---|---|
DB集群 | 1 | PostgreSQL 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 实例 | >=2 | Harbor 2.2.2 | |
静态 IP 地址 | 1 (VIP) | Keepalived v1.3.5 (03/19,2017) | |
Docker | n | Client:18.09.0 ; Server:18.09.0 | |
Docker Compose | n | 1.28.28 |
部署节点规划
主机名 | 用途 | 备注 |
---|---|---|
Harbor 1 | Harbor镜像仓库-主 | 挂载Parastor共享存储 |
harbor n | Harbor镜像仓库-备 | 挂载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)和 repmgr
的 HA PostgreSQL
集群:
- POSTGRESQL_PASSWORD:postgres 用户的密码。没有默认值。
- POSTGRESQL_PASSWORD_FILE:包含 postgres 用户密码的文件的路径。 这将覆盖 POSTGRESQL_PASSWORD 中指定的值。 没有默认值。
- REPMGR_USERNAME:
repmgr
用户的用户名。默认为repmgr
。 - REPMGR_PASSWORD_FILE:包含
repmgr
用户密码的文件的路径。这将覆盖REPMGR_PASSWORD
中指定的值。 没有默认值。 - REPMGR_PASSWORD:
repmgr
用户的密码。没有默认值。 - REPMGR_USE_PASSFILE:配置
repmgr
以在其配置中使用passfile
和PGPASSFILE
而不是纯文本密码。 - 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: 建立同步提交的类型。可用选项有:
on
、remote_apply
、remote_write
、local
和off
。 默认值为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_ipaddress ,static_routes:static_ipaddress和static_routes区域配置的是是本节点的IP和路由信息。如果你的机器上已经配置了IP和路由,那么这两个区域可以不用配置。其实,一般情况下你的机器都会有IP地址和路由信息的,因此没必要再在这两个区域配置。
- vrrp_script:用来做健康检查的,当时检查失败时会将vrrp_instance的priority减少相应的值。
- vrrp_instance:用来定义对外提供服务的VIP区域及其相关属性。
- vrrp_rsync_group:用来定义vrrp_intance组,使得这个组内成员动作一致。
- virtual_server_group、virtual_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参数设置,脚本主要作用为:
- 检测主库是否存活。
- 若主库不可用,则应该关闭主库的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 共用两台机器
节点规划
主机名 | 用途 | 备注 |
---|---|---|
Node01 | Keepalived主+PostgreSQL主 | 配置VIP |
Node02 | Keepalived从+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主
也恢复正常
这时候需要,
Node01的PostgreSQL从
依然是Node01的PostgreSQL从
,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部署方案的主要内容,如果未能解决你的问题,请参考以下文章