土豆不好吃

Docker container热更新

文章目录[显示]

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

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

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

容器内使用git

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

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

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

所以首先要魔改一下,先给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...")

然后

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
退出移动版