【原创】Gerrit replication 插件实现主从同步---读写分离 方案

背景

公司采用 Gerrit 代码版本控制软件管理 Android 代码,随着仓库的增加,研发访问量的逐步上升,Gerrit 服务器本身压力逐渐呈现。

现决定利用 Gerrit 自身的 Replication 插件搭建主从备份机制,以期缓解主 Gerrit 服务器压力。

要求

  • 实现主从备份,主服务器所有仓库数据备份到从服务器。
  • 实现读写分离,推送到主服务器,读取下载从从服务器。
  • 当主服务器宕机后,从服务器可以马上启用,并充当主服务器。
  • 从服务启用后,Changes 可以在从服务器网页上显示。

软件环境

  • 主服务器

    • Docker
    • Gerrit: gerritcodereview/gerrit:3.7.2
    • ip: 11.11.11.11 (为方便理解区分)
    • 数据库: NoteDB
    • 宿主机账号:xxadmin
  • 从服务器

    • Docker
    • Gerrit: gerritcodereview/gerrit:3.7.2
    • ip: 22.22.22.22 (为方便理解区分)
    • 数据库: NoteDB
    • 宿主机账号:xxadmin

配置 Gerrit

我采用的是 Docker 部署(主要便于维护以及隔离),但此处有一个坑:docker 容器内的运行账号与宿主机账号的不一致(UID 与 GID 不一致),会导致 Replication 之后,从服务器的Gerrit 无法访问 git 数据。

如果没有我这种情况的,请直接跳过此步骤,直接前往 主服务器配置。

问题现象

公司内提供的服务器已经预装好了 CentOS,并且有一个初始的系统管理员账号 XXX,因此它占用了 1000 这个 UID;

而 Docker 容器运行时,一般都是以 1000 这个 UID 运行,例如此处的 Gerrit ,在运行容器时,内部账号是:

uid=1001(gerrit) gid=1001(gerrit) groups=1001(gerrit)

同时 replication 插件在同步的时候是采用 ssh 协议,因此是以从服务器(此处 UID 是 1001) 的宿主机账号登录并且推送,导致同步到从服务器之后的代码文件权限也是 1001 ,与 docker 容器内的 Gerrit 账号 (UID 1000)不一致,导致无法访问。

因此重新构建了一个 Gerrit Docker 镜像,使 Gerrit 账号以 1001 运行即可。

重新构建 Gerrit 镜像

Dockerfile 如下:

FROM gerritcodereview/gerrit:3.7.2

USER root
RUN set -xe \
    && usermod -u 1001 gerrit \
    && groupmod -g 1001 gerrit

USER gerrit

构建 Docker 镜像:

docker build -t new_gerrit-3.7.2:1.0 .

docker-compose 编排

用 docker-compose 编排 容器:

version: "3"
services:

    gerrit_server:
        restart: always
        hostname: gerrit_server
        image: "new_gerrit-3.7.2:1.0"
        ports:
            - "29418:29418"
            - "8080:8080"
        volumes:
            - /etc/localtime:/etc/localtime:ro
            - /etc/timezone:/etc/timezone:ro
            - /data/gerrit/review_site/git:/var/gerrit/git
            - /data/gerrit/review_site/db:/var/gerrit/db
            - /data/gerrit/review_site/index:/var/gerrit/index
            - /data/gerrit/review_site/cache:/var/gerrit/cache
            - /data/gerrit/review_site/etc:/var/gerrit/etc
        environment:
            - HTTPD_LISTEN_URL=proxy-http://*:8080
            - CANONICAL_WEB_URL=http://11.11.11.11:8080
        deploy:
            resources:
                limits:
                    memory: 32G

启动容器

docker-compose up -d gerrit_server

注意事项

第一次运行容器的时候,可能会报权限错误,原因是第一次会自动创建 /var/gerrit/[git,db,index,cache,etc] 几个目录,并且是以 root 创建。

建议在运行容器前,先手动创建目录:

mkdir -p /data/gerrit/review_site/git
mkdir -p /data/gerrit/review_site/db
mkdir -p /data/gerrit/review_site/index
mkdir -p /data/gerrit/review_site/cache
mkdir -p /data/gerrit/review_site/etc
mkdir -p /data/gerrit/review_site/etc/mail

或者在失败后,手动修改目录权限:

sudo chown -R 1001:1001 /data/gerrit/

/data/gerrit 是我本地的目录,请根据自己情况修改。

主从服务器都可按照相同配置先部署并启动。

主服务器配置

  1. 配置 replication.config

主服务器启动后,新增 replication 配置文件:

/data/gerrit/review_site/etc/replication.config

增加内容:

[gerrit]
    autoReload = true
    replicateOnStartup = true
    defaultForceUpdate = true

[remote "gerrit_slave"]
    url = ssh://xxadmin@22.22.22.22:/data/gerrit/review_site/git/${name}.git
    push = +refs/*:refs/*
    mirror = true
    replicatePermissions = true
    rescheduleDelay = 5
  1. 配置主服务器 ssh config

    由于主服务器 Replication 插件采用 ssh 协议,因此需要配置主服务器到从服务器的 ssh 免密登录。

    我这里采用的 docker 部署,因此需要在主服务器的 Gerrit 容器内配置。

    • 进入容器

      docker exec -it gerrit_gerrit_server_1 bash
      
    • 生成 ssh key

      $ ssh-keygen
      Generating public/private rsa key pair.
      Enter file in which to save the key (/var/gerrit/.ssh/id_rsa):
      Created directory '/var/gerrit/.ssh'.
      Enter passphrase (empty for no passphrase):
      Enter same passphrase again:
      Your identification has been saved in /var/gerrit/.ssh/id_rsa.
      Your public key has been saved in /var/gerrit/.ssh/id_rsa.pub.
      The key fingerprint is:
      SHA256:4A9i0XH0wxqR04V9odXshHutgE+U6Yw696wxFLWnGYA gerrit@gerrit_server
      The key's randomart image is:
      +---[RSA 3072]----+
      |      ..++.+ooo= |
      |     . oE+oo=+o +|
      |    . o ..=*+..+.|
      |     o . ooo==. +|
      |    o o S..oo. o |
      |   . . oo.. . .  |
      |        .ooo     |
      |           oo    |
      |          ..     |
      +----[SHA256]-----+
      
    • 配置 /var/gerrit/.ssh/config 文件:

      IdentityFile ~/.ssh/id_rsa
      StrictHostKeyChecking no
      PreferredAuthentications publickey
      Host *
          IdentityFile ~/.ssh/id_rsa
          PreferredAuthentications publickey
      

      保存退出。

  2. 配置免密登录

ssh-copy-id -i ~/.ssh/id_rsa.pub xxadmin@22.22.22.22

输入 yes ,然后输入密码后,成功!

bash-4.4$ ssh-copy-id -i ~/.ssh/id_rsa.pub xxadmin@22.22.22.22
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/var/gerrit/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed

/usr/bin/ssh-copy-id: WARNING: All keys were skipped because they already exist on the remote system.
		(if you think this is a mistake, you may want to use -f option)

从服务器配置

在从服务器上只需要做一个配置:

  1. 配置从服务器的 etc/gerrit.config
/data/gerrit/review_site/etc/gerrit.config

在 container 配置段中增加配置项:

[container]
    slave = true
  1. 重启 从服务器的 Gerrit 容器:
docker restart gerrit_gerrit_server_1

并且配置此项后,从服务器的 web 端将不可用:

在这里插入图片描述

web 页面提示 “Not Found”

官方如此设计是为了防止用户在从服务器的页面修改、删除、提交等操作后,导致主从服务器的信息不同步。

并且,如果尝试往从服务器推送,会出现以下错误信息:

$ git push origin HEAD:refs/for/master
fatal: Service not enabled
fatal: remote error: Service not enabled

$ git push origin HEAD:master
fatal: Service not enabled
fatal: remote error: Service not enabled

直接推送和推送代码审核分支都不行。

但是 fetch 和 pull 不会报错。

$ git fetch origin -v
remote: Counting objects: 5, done
remote: Finding sources: 100% (3/3)
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), 388 bytes | 97.00 KiB/s, done.
From ssh://22.22.22.22:29418/test
   645220d..fff98e5  master     -> origin/master

启动 Replication

需要利用 Gerrit ssh 后台管理接口加载并启动 Replication

重新加载主服务器的 Replication 插件:

11.11.11.11 是主服务器

# 重新加载插件
ssh -p 29418 xxadmin@11.11.11.11 gerrit plugin reload replication

启动同步任务

ssh -p 29418 xxadmin@11.11.11.11 replication start --all

查看同步任务

ssh -p 29418 xxadmin@11.11.11.11 replication list --detail

至此,基于 Gerrit Replication 插件实现的主从服务器(镜像备份服务器)的搭建已完成,主服务器上的所有变更:分支、Tag、Gerrit 所有 Changes 都会自动同步备份到从服务器。

实现了 Gerrit 的热备份。

同时,按照这个方法步骤,也可以实现一主多从的方案,只需要在主服务器的 replication.config 中配置多个 [remote] 段即可。

[gerrit]
    autoReload = true
    replicateOnStartup = true
    defaultForceUpdate = true

[remote "gerrit_slave"]
    url = ssh://xxadmin@22.22.22.22:/data/gerrit/review_site/git/${name}.git
    push = +refs/*:refs/*
    mirror = true
    replicatePermissions = true
    rescheduleDelay = 5

[remote "gerrit_slave_beijing"]
    url = ssh://xxadmin@10.79.xxx.xxx:/data/gerrit/review_site/git/${name}.git
    push = +refs/*:refs/*
    mirror = true
    replicatePermissions = true
    rescheduleDelay = 5

[remote "gerrit_slave_shanghai"]
    url = ssh://xxadmin@10.79.xxx.xxx:/data/gerrit/review_site/git/${name}.git
    push = +refs/*:refs/*
    mirror = true
    replicatePermissions = true
    rescheduleDelay = 5

当然,其中的从服务器代码仓路径可根据实际情况修改。

更多其他 replication.config 的参数可以参考官方文档:

https://gerrit.googlesource.com/plugins/replication/+doc/master/src/main/resources/Documentation/about.md

或者本地搭建的 Gerrit 服务中的文档:

http://[本地-Gerrit-IP]/plugins/replication/Documentation/config.md

替换为自己的IP地址。

配置读写分离

为了缓解 Gerrit 主服务器的压力,研发本地配置代码的读写分离,写操作(push)走主服务器,读操作(fetch、pull)走从服务器。

仅需要在 研发本地修改 git 配置(在 git 仓库目录下执行):

git 配置命令

jjran 修改为自己的账号。

# 替换 push url 地址, 将 22.22.22.22 替换为 11.11.11.11
git config --add url."ssh://jjran@11.11.11.11:29418".pushInsteadOf ssh://jjran@22.22.22.22:29418

需要注意这个命令两个 ip 地址的顺序含义:在 push 操作的时候,用左边的 ip 地址替换右边的 ip 地址

执行完成后,当前 git 仓库内的 .git/config 文件内容:

忽略其他配置项

[remote "origin"]
	url = ssh://jjran@22.22.22.22:29418/test
	fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
	remote = origin
	merge = refs/heads/master
[url "ssh://jjran@11.11.11.11:29418"]
	pushInsteadOf = ssh://jjran@22.22.22.22:29418

我这里仅对一个 git 代码仓生效,如果要对本地所有 git 仓库生效,可以加上全局参数 --global

git config --global --add url."ssh://jjran@11.11.11.11:29418".pushInsteadOf ssh://jjran@22.22.22.22:29418

经过以上配置之后,本地的读写即可实现分离功能:

$ git push origin HEAD:refs/for/master
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 325 bytes | 325.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote: Processing changes: refs: 1, new: 1, done
remote:
remote: SUCCESS
remote:
remote:   http://11.11.11.11:8080/c/test/+/42 ranyi add 33 [NEW]
remote:
To ssh://11.11.11.11:29418/test
 * [new reference]   HEAD -> refs/for/master

$ git fetch origin
remote: Counting objects: 5, done
remote: Finding sources: 100% (3/3)
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), 367 bytes | 91.00 KiB/s, done.
From ssh://22.22.22.22:29418/test
   ef8f246..645220d  master     -> origin/master

可以看到上面在执行 push 的时候 url 是 ssh://11.11.11.11:29418

而在 fetch 的时候 url 是 ssh://22.22.22.22:29418

更简单的查看 url 方式:

$ git remote -v
origin	ssh://jjran@22.22.22.22:29418/test (fetch)
origin	ssh://jjran@11.11.11.11:29418/test (push)

注意:建议使用 --global 参数针对全局性配置

git config --global --add url."ssh://jjran@11.11.11.11:29418".pushInsteadOf ssh://jjran@22.22.22.22:29418

避坑指南

关于 git config pushInsteadOf 配置,有一个小坑需要注意:

我这里跳过了这个坑,是因为我当前初始 clone 是从 从服务器 进行的。

如果本地 git 仓库最初 clone 的时候,是直接从 主服务器 clone,

即,使用 clone 命令:

$ git clone ssh://jjran@11.11.11.11:29418/test

那么本地 .git/config 配置应该是:

省略其他配置项

[remote "origin"]
	url = ssh://jjran@11.11.11.11:29418/test
	fetch = +refs/heads/*:refs/remotes/origin/*

git remote -v 查看:

$ git remote -v
origin	ssh://jjran@11.11.11.11:29418/test (fetch)
origin	ssh://jjran@11.11.11.11:29418/test (push)

然后添加 pushInsteadOf 之后:

[remote "origin"]
	url = ssh://jjran@11.11.11.11:29418/test
	fetch = +refs/heads/*:refs/remotes/origin/*
[url "ssh://jjran@11.11.11.11:29418"]
	pushInsteadOf = ssh://jjran@22.22.22.22:29418

此时 push 没有问题,但 fetch 的时候仍然是走主服务器:

$ git remote -v
origin	ssh://jjran@11.11.11.11:29418/test (fetch)
origin	ssh://jjran@11.11.11.11:29418/test (push)

这并不符合我们期望!

所以,还是需要将 remote url 改为从服务器地址才行,或者初始 clone 仍然需要从 从服务器 clone:

[remote "origin"]
	url = ssh://jjran@22.22.22.22:29418/test
	fetch = +refs/heads/*:refs/remotes/origin/*
[url "ssh://jjran@11.11.11.11:29418"]
	pushInsteadOf = ssh://jjran@22.22.22.22:29418

尝试添加 insteadOf 选项并没有成功,发现 insteadOf 的优先级高于 pushInsteadOf

$ git config --add url."ssh://jjran@11.11.11.11:29418".pushInsteadOf ssh://jjran@22.22.22.22:29418
$ git config --add url."ssh://jjran@22.22.22.22:29418".insteadOf ssh://jjran@11.11.11.11:29418
$ git remote -v
    origin	ssh://jjran@22.22.22.22:29418/test (fetch)
    origin	ssh://jjran@22.22.22.22:29418/test (push)

并未生效,目测 insteadOf 优先级高于 pushInsteadOf !

扩展一下

关于读写分离的 git 配置,还有另外一种配置方法:

  • remote 字段中单独指定 pushurl:

    .git/config 文件:

    [remote "origin"]
        url = ssh://jjran@11.11.11.11:29418/test
        pushurl = ssh://jjran@22.22.22.22:29418/test
        fetch = +refs/heads/*:refs/remotes/origin/*
    

    执行 git remote -v 查看:

    $ git remote -v
        origin	ssh://jjran@11.11.11.11:29418/test (fetch)
        origin	ssh://jjran@22.22.22.22:29418/test (push)
    

    可以生效!

从服务器转正

对服务器的灾备一直是所有技术公司必备的一个话题,都是为了以防万一。

这里 Gerrit 的主从搭建方案也是为了防止主服务器宕机后,无法提交下载代码。

由于我们已经搭建了主从备份,并且配置了 +refs/:refs/ 因此 git 仓库下的所有 ref 都会备份到从服务器。

  1. 主服务器下线

主服务器已宕机,完全不可访问。

  1. 修改从服务器配置

我们只需要在修改从服务器上的配置文件 etc/gerrit.config 中,注释掉或者删除 slave = true 即可:

[container]
    # slave = true

然后重启从服务器:

此处我使用 docker 部署

$ docker restart gerrit_gerrit_server_1
  1. 从服务器 webui 显示

从服务器重启后,web 页面是空的,changes 无法显示出来。

在这里插入图片描述

这是由于,replication 同步的是 git 仓库数据,也就是 refs/* 下的所有内容,虽然也包含了 changes (refs/changes)

但,Gerrit 页面上的显示内容是 Gerrit Index 数据,即索引数据,是需要用 Gerrit 的 reindex 重新构建索引才能在网页中显示出来。

执行 Gerrit ssh 命令:

注意此处是要执行 从服务器 上的 Gerrit 命令

$ ssh -p 29418 xxadmin@22.22.22.22 gerrit index start changes --force
Reindexer started

查看从服务器的日志

在这里插入图片描述

不出意料的报错了。这里有个内部错误:

com.google.gerrit.server.notedb.InvalidServerIdException: invalid server id, expected 5775d2e3-0d62-4988-bcfc-edd41a70359a: actual: 7080f5c8-d3f7-4ea3-8926-82e478e1df3a

它的意思是,在 Gerrit 执行索引重建的时候发现 changes 的创建是 7080f5c8-d3f7-4ea3-8926-82e478e1df3a 这个服务器(主服务器) ID 创建的,

但当前的服务器ID与之不符,因此导致无法进行索引重建。

这是 Gerrit 内部设计,解决也容易,将从服务器的 serverId 修改为跟主服务器一致即可。

修改 Gerrit 服务器配置文件 etc/gerrit.config:

[gerrit]
        basePath = git
        canonicalWebUrl = http://22.22.22.22:8080
        serverId = 7080f5c8-d3f7-4ea3-8926-82e478e1df3a

保存退出,然后重启 Gerrit 服务,再重新执行索引重建:

$ docker restart gerrit_gerrit_server_1
$ ssh -p 29418 xxadmin@22.22.22.22 gerrit index start changes --force
Reindexer started

再次观察 Gerrit 日志:

[2023-07-26T02:37:00.976Z] [Reindex changes v79-v79] INFO  com.google.gerrit.server.index.OnlineReindexer : Starting online reindex of changes from schema version 79 to 79
[2023-07-26T02:37:01.493Z] [Reindex changes v79-v79] INFO  com.google.gerrit.server.index.OnlineReindexer : Reindex changes to version 79 complete
[2023-07-26T02:37:01.493Z] [Reindex changes v79-v79] INFO  com.google.gerrit.server.index.OnlineReindexer : Using changes schema version 79

日志中有这样的输出,并且没有错误的话,就表示索引数据重建成功。

web 页面再次刷新:

在这里插入图片描述

最终完成从服务器转正,然后通知研发修改本地 ip 地址即可。

扩展 – 基于 AOSP 源码 manifest 读写分离配置

目前所有基于安卓的源码几乎都是在 1000 上下的 git 仓库量级,并且谷歌自己开发了 repo 工具来进行这种多库的管理,并且引入 manifest 配置文件。

repo 工具以及 manifest 配置文件,也是支持 git 库读写分离配置的。

只需要在 manifest 文件的 remote 标签中增加 pushurl 属性即可:

<?xml version="1.0" encoding="UTF-8"?>
<manifest>
  <remote name="origin" fetch="http://22.22.22.22:8080" pushurl="http://11.11.11.11:8080" review="http://11.11.11.11:8080" />
  <default revision="master" remote="origin" sync-j="4"/>

  <project name="test" path="test" />
  <project name="ranyi_test1" path="ranyi_test1" />
</manifest>

pushurl=“http://11.11.11.11:8080/” 属性单独指定 push 命令的 url 地址。这样我们在使用 git push 命令的时候,就使用该属性指定的 url。

review=“http://11.11.11.11:8080/” review 属性在使用 repo upload 命令的时候才会用到,否则可以不用指定。

修改后的结果:

$ repo forall -pvc git remote -v
project ranyi_test1/
origin	http://22.22.22.22:8080/ranyi_test1 (fetch)
origin	http://11.11.11.11:8080/ranyi_test1 (push)

project test/
origin	http://22.22.22.22:8080/test (fetch)
origin	http://11.11.11.11:8080/test (push)

提示: 如果按照前面提到的配置了 git config --global,

git config --global --add url."ssh://jjran@11.11.11.11:29418".pushInsteadOf ssh://jjran@22.22.22.22:29418

那么其实就不用再进行 manifest 的配置了。

结语

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值