登录
  • 人们都希望被别人需要 却往往事与愿违
  • 每当有事情发生, 懦夫会问: '这么做安全吗?' 患得患失者会问: '这么做明智吗?' 虚荣者会问: '这么做受欢迎吗?' 但是良知只会问: '这么做正确吗?@马丁·路德·金

Docker container热更新

编程 Benny小土豆 2617次浏览 2730字 2个评论
文章目录[显示]

我的YouTube Download有一个worker是麻烦盆友帮我跑的,每次更新代码都要让人家重新pull image然后再up,有没有什么能够让我没有ssh也能自助更新代码吗?

最简单的办法是把docker的socket暴露给容器,这样容器就可以为所欲为了。但是这也太危险而且太麻烦

既然大部分情况下也都是更新代码,不会有太大的变更,那么似乎只要想办法把代码更新一下,然后重启下容器内的进程就可以了。

容器内使用git

首先,要确保在Dockerfile中安装了git哦。

我的image都是使用GitHub Actions构建的,并且不知道GitHub Actions给我加入了什么神奇的配置项,导致无法无缝pull。毕竟我的仓库是公开的啊🤔

反正image也是公开的,我就不打码了

Docker container热更新

所以首先要魔改一下,先给unset掉

git config --unset http.https://github.com/.extraheader

然后unshallow pull

git pull origin --unshallow

下次就可以正常的git pull了,大概的代码如下,能用就行

git_path = "your project root" # or maybe pathlib.Path().cwd().parent.as_posix()
logging.info("Hot patching on path %s...", git_path)

unset = "git config --unset http.https://github.com/.extraheader"
pull_unshallow = "git pull origin --unshallow"
pull = "git pull"

subprocess.call(unset, shell=True, cwd=git_path)
if subprocess.call(pull_unshallow, shell=True, cwd=git_path) != 0:
    logging.info("Already unshallow, pulling now...")
    subprocess.call(pull, shell=True, cwd=git_path)

重启应用程序

自动重启应用方法很多,只要确保容器不被删除直接restart就可以。然后我们需要用一个守护进程来帮忙拉起来,比如docker的restart policy可以设置成on-failure,或者容器内上supervisor也不是不行。

问题是如何退出进程呢?

并对于运行在主线程中的处理函数来说,类似 sys.exit(1)就可以。注意exit code要和restart policy相配合。比如restart policy是on-failure,那么exit(0) 就不行了

对于运行在子线程中的处理函数,exit只会退出当前线程而不会把主线程退出。那么一个简单有效的办法就是获取到整个进程的PID,然后kill掉,Python的psutil提供了这样的功能:

psutil.Process().kill()

使用kill而不是terminate也是为了避免exit code是0的问题

如果想要通过apscheduler的BackgroundScheduler来让自己完全自杀,那么这样就是比较好的了。

celery worker broadcast

我的YouTube Download的整体架构是master+N个worker的模式。因此在热更新的时候不仅要更新master,也要把worker都更新了。

直接发布消息是不行的,除非只有一个worker,所以我们需要发布broadcast来确保所有的worker都会收到并且执行命令。

app.control.broadcast("ping")

ping就是表示让worker执行的命令,默认大概有几十个,包括ping、revoke、heartbeat什么的。

我们可以自定义一个command,使用@Panel.register装饰器

from celery.worker.control import Panel
@Panel.register
def hello (*args):
    print("patch...")

Docker container热更新

然后

app.control.broadcast("hello")

所有worker就都能收到这条消息了

更多……

更有甚者,可以自定义好在热更新的时候要做什么,比如更新代码,更新 Python Packages,更新系统相关工具。反正无非就是在容器内各种subprocess就好了。

当然了别忘了做权限控制,要不然就RCE啦😏😏😏😏😏

完整代码

# tasks.py
import psutil
from celery.worker.control import Panel

@Panel.register
def hot_patch(*args):
    git_path = "your project root" # or maybe pathlib.Path().cwd().parent.as_posix()
    logging.info("Hot patching on path %s...", git_path)

    unset = "git config --unset http.https://github.com/.extraheader"
    pull_unshallow = "git pull origin --unshallow"
    pull = "git pull"

    subprocess.call(unset, shell=True, cwd=git_path)
    if subprocess.call(pull_unshallow, shell=True, cwd=git_path) != 0:
        logging.info("Already unshallow, pulling now...")
        subprocess.call(pull, shell=True, cwd=git_path)

    logging.info("Code is updated, applying hot patch now...")
    psutil.Process().kill()


# control 
app.control.broadcast("hot_patch")

参考资料

https://github.com/tgbot-collection/ytdlbot/blob/master/ytdlbot/tasks.py#L246

https://github.com/tgbot-collection/ytdlbot/blob/master/ytdlbot/ytdl_bot.py#L100

https://stackoverflow.com/q/65204671/10264400


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/container-hot-patch.html
喜欢 (50)
分享:-)
关于作者:
If you have any further questions, feel free to contact me in English or Chinese.
发表我的评论(代码和日志请使用Pastebin或Gist)
取消评论

                     

去你妹的实名制!

  • 昵称 (必填)
  • 邮箱 (必填,不要邮件提醒可以随便写)
  • 网址 (选填)
(2)个小伙伴在吐槽
  1. 接了github的action,完全可以知道什么时候更新了代码,守护进程主要就监听处理这些action就好了,然后容器里做ci的流程,或者简单一点就重新编译一个二进制文件,重新运行就好了。 可以搞两个容器在一个pod里面,或者就一个容器,运行两个进程,一个主进程是守护进程,处理所有的action和重新编译的工作,另一个是真正的服务。
    michaelson2022-07-25 02:09 回复
  2. 0.0 看不懂的宝宝路过
    1234562022-07-12 14:19 回复