我正在考虑在 K8s 中设置一个 bitnami mongodb 复制集,如here所解释。
但是,作为注释,他们在升级时提到了这一点:
注意:如果启用了仲裁器且 MongoDB 副本数量为 2,则更新会使 MongoDB 副本集脱机。 Helm 同时对 MongoDB 实例和 Arbiter 的有状态集应用更新,因此您失去了三分之二的法定投票。
我不确定“使副本集离线”实际上意味着什么:
是否整个 mongo 服务都离线/关闭,因此在执行 helm 升级时服务会出现停机?
是否在执行升级时,副本集将只有一个 mongodb 服务器在工作,而另一台 mongodb 服务器和仲裁器都处于离线状态并正在升级?在这种情况下,它并不意味着停机,只是暂时只有一台服务器可用(而不是两台),与独立设置类似。
Bitnami 开发者在这里
在执行升级时,副本集是否会具有 只有一台 mongodb 服务器在工作,而另一台 mongodb 服务器都在工作 和仲裁者,是否已离线并正在升级?
这取决于您有多少副本。如果您安装带有 2 个副本 + 1 个仲裁器的图表,是的,这意味着。但是,如果您安装带有 4 个副本 + 1 个仲裁器的图表,则您将只有 1 个副本,并且仲裁器会同时重新启动,而其他 3 个副本也会启动。
在这种情况下,并不意味着停机,只是暂时的,有 只有一台服务器可用(而不是两台),因此类似于 独立设置。
正如之前的回复中提到的,如果未满足活动副本的最小法定数量,则确实意味着短暂的停机时间。
我不知道 Helm 是什么,不知道它有什么作用,也不知道为什么它一次会关闭两个节点,但是:
在这种情况下,它不会意味着停机,只是暂时只有一台服务器可用(而不是两台),与独立设置类似。
独立的进程在启动时就会接受写入。副本集节点仅在为主节点时才接受写入,并且要拥有主节点,大多数节点必须处于运行状态。因此,如果副本集的三个节点中只有一个启动,则不可能有主节点,并且没有节点将接受写入。
在三个节点中有一个可用的情况下,如果该节点是数据承载的,您仍然可以使用允许辅助读取首选项(主要首选或次要或次要首选)发出读取,但主要读取将不起作用,写入也将不起作用工作。
对于任何尝试在不停机的情况下升级 bitnami mongoDB Helm Chart 的人,这里有一些我用来在理论上尽可能地在 PSA 结构下消除它的技巧。
bitnami图表的主要问题是仲裁器和数据节点是两个独立的StatefulSet(mongodb和mongodb-arbiter),因此任何使用helm的更新都会导致两个StatefulSet都进入升级状态,并且您可能只有一个数据节点在线某段时间。根据定义,一个数据节点将无法选择新的主节点,因此不健康。
为了维护主节点,副本集中每次只能有一个节点宕机,因此您可以简单地在关闭数据节点时添加一些延迟,以便仲裁器比任何数据节点关闭都更快地关闭和备份。您可以简单地使用PreStop Hook。不过,我在关闭之前做了一些检查——基本上确保当数据节点关闭时,副本集中的每个成员都应该是健康的。
这里是一个示例脚本,您可以作为 PreStop 挂钩运行,并记住相应地调整您的
terminationGracePeriodSeconds
以考虑到您的实例在给定时间范围内未停止的某些情况。
const startTime = Date.now();
const timeLeft = () => Math.round((Date.now() - startTime) / 1000);
let success = 0;
let failedTimes = 0;
const sleepTime = 60; // seconds
// Match graceful shutdown time here
const forceKillTime = 60; // 60 mins
const exitGap = 5; // Leave 5 minutes just in case
const exitTime = forceKillTime * 60 - exitGap * 60;
const CONSECUTIVE_SUCCESS = 5;
while (true) {
const elapsedTime = (Date.now() - startTime) / 1000;
if (elapsedTime > exitTime) {
print(
`Operation does not finished within time frame: Elapsed(${elapsedTime}) > exit(${exitTime})`
);
quit(1);
}
const res = rs.status();
if (res.members) {
const everyoneIsHealthy = res.members.every((obj) => obj.health === 1);
if (everyoneIsHealthy) {
success += 1;
if (success >= CONSECUTIVE_SUCCESS) {
print(
`success count (${success} >= ${CONSECUTIVE_SUCCESS}) larger than threshold, proceeed with shutdown`
);
// First, we try to step down
// Then, we go ahead to shutdown ourselves
db.adminCommand({
replSetStepDown: timeLeft() + 60, // add some space just in case
secondaryCatchUpPeriodSecs: timeLeft() - 60,
force: false,
});
// If step down has timeout, we basically cannot do anything but proceed to force shutdown anyway
// to ensure the shutdown happened. However the final killoff is done by k8s, shutdown is also for
// index task to wrap up, etc.
db.shutdownServer({
timeoutSec: timeLeft(),
});
} else {
print(
`success count (${success} < ${CONSECUTIVE_SUCCESS}), keep checking...`
);
}
} else {
success = 0;
failedTimes += 1;
print(`[Failed ${failedTimes}] not everyone is healthy, reset counter `);
}
} else {
success = 0;
failedTimes += 1;
print(`[Failed ${failedTimes}] members info not available, retrying...`);
}
sleep(sleepTime * 1000);
}
要安装脚本,您可以使用 bitnami 图表参数:
extraVolumeMounts
和 extraVolumes
。将您的脚本打包到单独的包中,例如 default/mongodb-addon
。下面是一个修补 StatefulSet 以使用 helmfiles 配置的示例:
- name: mongodb
needs:
- default/mongodb-addon
namespace: default
version: 9.0.1
chart: bitnami-full-index/mongodb
strategicMergePatches:
- apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongodb
namespace: default
spec:
template:
spec:
# If this value changed, changed the shutdown script timer as well
terminationGracePeriodSeconds: 3600 # 60 minutes
containers:
- name: mongodb
lifecycle:
preStop:
exec:
command:
- /bin/bash
# Write some wrapper scripts to execute on *local* container instance only, do not connect as replicaset mode!
# such as bash -c "mongosh 'mongodb://localhost' $SCRIPT_DIR/shutdown.js"
- /scripts/shutdown.sh
不过,你可以通过在钩子中添加一些睡眠来避免这些麻烦,在我看来应该没问题。如果一切顺利,并且所有实例都正确关闭,您的副本集永远不会丢失主副本集。实例关闭意味着降级。
这里的另一个注意事项是 mongoDB Arbiter 似乎没有按预期工作。例如,如果您要从 4.2 升级到 4.4,如果 Arbiter 映像升级到 4.4,则不会使用 FCV 4.2,因此 replset 无法恢复到健康状态并阻止我们设置中的升级。
我们目前的解决方案是再次打补丁,并将仲裁器版本固定为4.2,先滚动数据承载节点,待一切稳定后删除补丁。