从独立容器迁移到独立的 Web 和数据容器

你好,Jay @pfaffman,感谢你发布的这篇帖子,以及关于这个“双容器”主题的其他帖子,包括 Sam 在这方面写的文章。

问题:

我们一直在尝试按照你提到的方式设置两个容器:一个用于 data,另一个用于 web-only,但在 macOS 上运行过程中遇到了不少阻碍。

不过,在担心如何在 macOS 或 Ubuntu 上调试这个“双容器配置”之前,我们想先确认这样做是否出于正确的理由。

我们想要进行“双容器操作”的原因是,希望在重建 Web 应用(例如安装插件时)时,网站不会停机。此外,当我们调整自研插件时,有时发现唯一能确保更改生效的方法就是重建(这是另一天的故事)。我也一直在努力搭建一个“快速且友好”的 Web 开发环境,但尚未达到满意程度;不过这也是另一个话题。

所以,我的问题是:“双容器”设置是否能显著减少仅重建 Web 部分应用时的停机时间?

这样理解是正确的,对吧?

当我们安装插件或对其进行调整时,是否只需要重建“web-only”的 yml 文件,而不需要重建 data yml 文件?

我们来自 LAMP 论坛背景,因此对插件的更改大多可以在生产环境中实时完成(除非操作失误,否则不会停机)。此外,我们也曾开发过一些 VueJS Web 应用,在桌面端构建后,只需上传并将新应用部署到位,升级/更新 VueJS 部分几乎不会造成停机。然而,在使用 Discourse 时,我们会遇到停机时间,这是我们不希望发生的(哪怕只有几秒钟)。

“双容器”方案在我们执行以下操作时,是否能显著减少停机时间:(1) 重建应用(用于插件、代码调整等)或 (2) 从完整备份恢复?

我觉得再次提出这个问题可能会“挨骂”,因为我们正在寻找一种在生产环境中运行 Discourse 并实现近乎零停机更改的方法,而目前尚未找到像 LAMP 或 VueJS 应用那样容易实现的方式。

因此,我们才对“双容器”方法如此感兴趣并努力尝试,但至今尚未成功部署。

谢谢!

是的。在构建新容器时,现有的 Web 容器会继续运行。因此,停机时间仅取决于启动新 Web 服务器所需的时间,通常不到一分钟,但这绝非零停机方案。如果您需要零停机,则需要在前面部署一个反向代理,以便在新容器启动并开始工作后再关闭旧容器。(此外,如果新容器的数据库迁移导致旧容器出现问题,除非采取其他复杂措施,否则仍会出现停机时间)。

从备份恢复时没有区别。

4 个赞

谢谢你,Jay @pfaffman

你绝对是这里最顶尖、最有价值的资源,毫无疑问!

你觉得这个可能有点疯狂的想法怎么样(基于我目前有限的理解):

在前端设置 nginx 作为反向代理;参考这个教程:

然后设置两个目录/实例,使用 discourse_docker(独立模式),例如:

  1. /var/discourse1
  2. /var/discourse2

在这两个实例中,将 discourse_docker(独立模式)配置为监听不同的套接字,修改每个实例中的以下模板:

 - "templates/web.socketed.template.yml"

简而言之,我们只需在某个安静时段重建生产环境,使其在另一个容器中运行并监听不同的套接字(nginx.https.sock2),这样就不会有套接字冲突;我们也可以以独立模式构建(目标是消除对两个容器(dataweb-only)的需求)。

例如(用于讨论/说明),在 discourse1 的 web.socketed.template.yml 中:

  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 80;/
     to: |
       listen unix:/shared/nginx.http.sock;
       set_real_ip_from unix:;
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 443 ssl http2;/
     to: |
       listen unix:/shared/nginx.https.sock ssl http2;
       set_real_ip_from unix:;

而在 discourse2 中:

 - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 80;/
     to: |
       listen unix:/shared/nginx.http.sock2;
       set_real_ip_from unix:;
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 443 ssl http2;/
     to: |
       listen unix:/shared/nginx.https.sock2 ssl http2;
       set_real_ip_from unix:;

不过,与其让 discourse 模板自动完成这些操作,我们只需手动切换 /etc/nginx/conf.d/discourse.conf 中的套接字并重启 nginx,这样就可以从 web.socketed.template.yml 模板中移除 replace: 指令。

在这个提议的(可能有点疯狂)配置中,我们可以让两个独立容器监听两个不同的套接字(互不冲突),只需配置 nginx 连接到所需的套接字并重启 nginx 即可。

这看起来清晰、简单,或许对某些不想(或不需要)为单个 discourse 实例(应用)维护两个容器(data 和 web-only)复杂性的用户来说很有用(特别是在实时实例没有新帖子的空闲时段)。

当然,从数据角度来看,最稳健的配置(尤其是对于繁忙的网站)仍然是“双容器”方案,因为我们会希望 dataweb-only 实例(现在分别监听两个不同的套接字:socksock2)。

在带有 nginx 前端的“双容器”方案中,“标准配置”是让两个 web-only 容器监听同一个套接字,因此它们不能同时运行;但如果(仅举例)让它们监听不同的套接字,它们就可以同时运行,我们只需使用 nginx 配置文件(并重启 nginx)即可在两者之间切换。

我的理解正确吗?

我是不是正在(虽然缓慢但希望稳步地)理解这一切?

谢谢!

仅作为补充说明: 我已经在其中一台桌面 Mac 上成功配置了“双容器”方案:

Screen Shot 2020-04-11 at 12.41.24 PM

我们安装过程中唯一的注意事项是必须手动创建这些目录(并设置所有权和权限),因为这些目录出于某种原因没有被脚本自动创建:

~discourse/discourse/shared/data
~discourse/discourse/shared/web-only

当然,一开始我尝试使用空密码连接数据库,但失败了(虽然说明中确实提到要设置密码,但我当时只是在尝试)。

接下来,我将设置 nginx 前端,并尝试将 web-only 应用切换到该配置,使用 websocket

这确实信息量很大。不过,没必要保留两个 discourse 目录。只需在 containers 目录下创建多个 yml 文件即可,文件名可以随意命名。

2 个赞

感谢确认。经过今天的尝试,我们的配置确实如此(单一目录)。

两个容器的配置(2CC)一切顺利,但在 macOS 上配置 nginx 反向代理时遇到了困难。

无法建立到 /shared 目录中 Unix 域套接字的有效连接,尽管该套接字在容器外部是可以访问的。我们尝试了 nginx、Python 和 socat(用于测试),但总是出现 61 错误(连接被拒绝)。嗯……

一整天都卡在“连接被拒绝”的问题上!!

明天又是新的一天。

我有一个小问题。
如果我们只有一个容器配置(只有 ‘app.yml’),并执行 ./launcher bootstrap app 命令。
那么我们的网站/前端会停止吗?

如果会停止,那么为什么 ‘bootstrap web-only’ 不会停止我们的网站?

如果不会停止,那么在容器重建时,双容器设置在时间节省方面有什么好处?换句话说,如果我们可以在引导单个容器的同时保持网站运行,那为什么还需要两个独立的容器呢?

1 个赞

不会,前提是你创建一个新的 .yml 文件,例如命名为 new_image

如果配置正确,bootstrap 操作本身不会启动或停止任何服务。经过 bootstrap 的镜像并未运行,这就是它们被称为“已 bootstrap”的原因。

不过,你需要创建一个新的 yml 文件,因为你需要创建一个具有新镜像名称的新镜像。

因此,使用新的镜像名称时,新的已 bootstrap 镜像尚未运行。它仅仅处于“已 bootstrap”状态。

你可以用新名称(不是 app)构建新镜像,构建完成后,我们称之为 new_image

然后,如果你想替换正在运行的镜像(我们称之为 old_image),可以执行以下操作:

./launcher stop old_image; ./launcher start new_image

在你的情况下:

./launcher bootstrap new_image          # 数据镜像和 Web "app" 镜像正在运行
./launcher stop app; ./launcher start new_image

因为数据容器已经构建完成,你可以节省时间:(1) 无需重新构建数据镜像;(2) 在重建 Web 镜像时无需停机,因为你可以在其他镜像运行时构建它(即 bootstrap 镜像)。

这要快得多;但使用反向代理置于容器前面会更快。

在你最初的问题中,你有一个名为 app 的正在运行的镜像。如果你尝试再次 bootstrap 这个名为 app 的镜像,那么你就是在重建同一个镜像(同名)。这不会为你节省任何时间,正如你的直觉告诉你的那样。

现在清楚了吗?

如果还不清楚,请随时提问。每个人都可以学习,而学习是令人兴奋的。这(实际上)很容易理解,但如果你是初次接触这些概念,可能需要一点时间。

2 个赞

说实话,我什么都没理解。我尝试过,但失败了。

我的问题很简单。
如果我们有两个容器的设置,并且我们“启动”(而不是重建)我们的“仅 Web”容器,那么当前的 Web 容器会继续工作(因为我们的网站显示为正常/可用)。

如果我们是单容器设置,比如 ‘app.yml’,然后我们启动这个 ‘app’ 容器,我们的网站是否仍能继续工作?

如果解释很简单,那么为什么会这样?

1 个赞

也许你应该雇个人来帮你?

1 个赞

没问题。

只是个小疑问。其中一部分,或许可以用“是/否”来回答。

没什么大不了的。

1 个赞

我的建议是,亲自尝试一下,享受这个过程。

实际上,在自己的测试环境中亲自尝试,比在论坛上提问并等待回复所花的时间更少。

如果你亲自尝试,就能得到问题的答案;因此,你应该在测试场景中尝试这些操作,这样就不会破坏你的生产应用 :slight_smile:

Discourse 是开源的,并且可以免费下载和配置,这要归功于联合创始人慷慨的奉献。这意味着你可以而且应该利用这一开源优势,自由地创建、销毁、重建和再次销毁测试应用(次数不限)。

如果你不愿意在基础系统管理任务上投入一些精力,@codinghorror 曾有一个很好的建议,即在这里雇佣本地人才。

1 个赞

是的。(除非引导过程以某种方式迁移数据库,导致正在运行的容器无法再使用它)。在旧容器关闭和新容器启动的过程中,您会有停机时间。

不能。您无法让两个数据库进程同时访问相同的文件。

3 个赞

哦!!
感谢澄清这个问题。

1 个赞

因此,通过将数据库置于您正在构建的容器之外,您可以构建一个新的 Web 容器来操作该数据库,而另一个 Web 容器则继续运行。

1 个赞

关于这个方案有个小疑问:在这种设置下,如何进行 Rebuild 操作?

假设我们从头开始采用 @pfaffman 添加的双容器设置,那么会有两个容器:“app”: “data” 和 “web_only”。

所有的 Rebuild 操作都指向 “web_only” 容器,那么我们需要对 “data” 容器做什么吗?还是说 “web_only” 的启动过程会自动处理?(之所以这么问,是因为我在做测试时发现 data 容器从未在任何时刻停止运行。)

我们使用“三个容器”和一个反向 nginx 代理:

  1. data
  2. socket1
  3. socket2

假设我们当前正在运行 socket1(即你所说的“仅 Web”模式),并且想要重建并测试某些内容。我们会重建 socket2,而 socket1 继续运行。由于每个容器都使用 Unix 域套接字,且共享套接字位于不同的文件中(位置不同),因此它们可以同时运行。

然后,假设 socket2 已构建完成并准备就绪。

我会执行以下操作:

ln -sf /var/discourse/shared/socket2/nginx.http.sock /var/run/nginx.http.sock

现在我们已经切换到 socket2 并在线运行。

例如,如果出现问题,我只需执行以下操作:

ln -sf /var/discourse/shared/socket1/nginx.http.sock /var/run/nginx.http.sock

这样我们就回到了之前的状态,继续运行 socket1。

为了好玩,我在 bash 登录配置文件中添加了一些代码,这样每次登录服务器时,它都会告诉我当前运行的是哪个套接字(容器):

Last login: Fri May 15 09:39:39 2020 from 159.192.33.138
srw-rw---- 1 root docker  0 May  5 07:38 /var/run/docker.sock
lrwxrwxrwx 1 root root   44 May 15 09:16 /var/run/nginx.http.sock -> /var/discourse/shared/socket/nginx.http.sock

希望这能帮到你。

就我个人的看法(IMHO),这(总体而言)是“最佳做法”(也是我在生产环境中的“默认标准”,而非在测试或开发环境中),但这仅代表我个人观点,你的情况可能有所不同(YMMV)。设置过程需要稍微多花一点功夫,但我觉得在生产环境中非常值得。


注意事项:


最好将原始模板 yml 文件备份到安全的地方,因为模板可能会被覆盖,从而引发问题。因此,我们倾向于“保存所有 yml 文件”,先执行“拉取(pull)”,然后“检查 yml 文件”,最后再启动(出于充分的谨慎考虑)。

2 个赞

除非 PostgreSQL(刚刚发生)或 Redis(大约在过去 6 个月内发生)进行升级,否则您无需重新构建数据容器。

大多数情况下,您只需将说明中的 web_only 替换为 app。我的笔记位于:Managing a Two-Container Installation - Documentation - Literate Computing Support

5 个赞

那么,在这种方法中,您拥有两个 Web 实例,并利用闲置的那个用相同的数据进行测试?这很有趣,一旦我确定了整个“节省空间”的策略,我一定会尝试一下 :stuck_out_tongue:

非常感谢。我已通读您的文章,只有一个小问题:您这里指的是“数据”容器对吗?(即如文章中所述,重建数据容器而不是 web_only)?还是说有什么我没注意到的技巧(例如:在进行重大升级时,需要将它们合并在一起,然后再分开)?

是的,我们在一个基于 socket 的容器中进行测试、添加和重建,同时在另一个容器上运行。

是的,两者都使用同一个数据容器。数据容器并不“关心”它正在与哪个 Web 应用程序“通信”。

其实非常简单,一旦您掌握了如何在共享的 Unix 域 socket 上运行容器,而不是通过外部暴露的 TCP/IP 端口运行,就明白了。

我们发现这并不会占用太多空间,不过话说回来,我们在生产环境中并不会因为磁盘空间有限而受限,因为磁盘空间并不昂贵。

1 个赞

感谢您的详细说明。我刚刚在测试环境中尝试让两个“应用容器”指向同一个 Data One,结果运行得非常顺利。

不过,我无法将其迁移到生产环境(PRD),因为我无法在生产环境中重建数据容器,而且在没有解决这个问题之前,我不想更改任何配置。目前它只是停止运行,并报错:discourse@discourse FATAL: terminating connection due to administrator command