利用Github Action备份Docker容器中的数据库

利用Github Action备份Docker容器中的数据库

本文方案仅供技术参考与娱乐!

前言

数据很重要,所以我们要经常备份。

那么怎么备份呢?假如我们使用云厂商的数据库,里面已经自带了快照和备份功能了,只要你愿意花钱,就能帮你解决大部分技术问题。它们这些数据库往往是部署在单独一/多台主机实例上,不会放在容器里去跑。为什么?俺也不是专业的运维,俺也不知道。这里给个知乎链接作为参考

对于我们个人项目而言,一般不会去购买昂贵的数据库实例,往往 云主机容器部署 + serverless + oss/cdn 就能满足绝大部分开发的需求了。很多时候简单的 docker compose up -d 就够用了,然后再加个开源的BAAS平台:supabase 一起组网,开发爽的不要不要的。

说远了,接下来进入本篇的正题:如何设计一个方案,把 Github Repo 转化为我们数据库备份的对象存储,并利用CI持续化集成呢?

方案设计与拆解

显然,思考如何把大象装进冰箱,我们要把整个过程拆解成以下几个步骤:

  • 链接远程机器
  • 导出数据库文件
  • 下载与上传数据库备份文件
  • 数据库备份的增删管理

这样我们只需要依次实现对应的功能,再把功能串联起来就达到我们的目标了。

本文运行环境:云主机为 华为云,数据库为 postgres

数据库备份方案

备份数据库通常非常简单,主要分为 2 步:

  1. db dump
  2. upload to Amazon S3/Aliyun OSS/Tencent Cloud COS /...

这个很容易理解,写个 shell 脚本,导出数据库,上传到 OSS。然后把它设置成定时任务就行。

然而本文的邪道方案中,我们需要使用 github action 来远程连上云主机,然后执行脚本获取数据库备份,再同步到 git 仓库。

这显然要复杂许多,于是我就学习了一会shell编程,写了一段脚本,具体思考调试过程可以见注释:

#!/bin/sh
FILENAME=$(date +"%Y%m%d-%H%M%S") # 时间戳文件名
BASENAME="${FILENAME}.dump" # +后缀
KEY_PATH=./xxx.pem # ssh私钥路径
[email protected] # 云主机登录用户以及ip地址
DUMP_FILE_PATH=/path/to/${BASENAME} # 云主机 dump 文件路径
CONTAINER_NAME=container-name # 云主机数据库容器名称
PG_USER=postgres # 云主机数据库容器登录用户

# Permissions 0400 for './*.pem' are too open
# 修改私钥权限,避免 Permissions too open 问题
chmod 400 $KEY_PATH

echo "   -> Connecting $DESTINATION and Dumping"
# dump datebase from docker container
# option StrictHostKeyChecking=accept-new for ssh key prompt
# 这里设置 StrictHostKeyChecking=accept-new 来避免初次由于 .ssh/known_hosts 不存在,导致的 prompt 问题
# 相当于执行了3个命令,ssh <command> / docker exec <command> / sh -c "db_dump"
# 把 dump 出来的数据文件,放到docker的挂载卷中
ssh -o StrictHostKeyChecking=accept-new -i $KEY_PATH $DESTINATION "docker exec -u $PG_USER $CONTAINER_NAME sh -c \"pg_dump -Fc postgres > /var/lib/postgresql/data/${BASENAME}\""

echo "   -> Downloading $DUMP_FILE_PATH"
# download dump file to git repo
# 把dump文件下载到本地
scp -i $KEY_PATH $DESTINATION:$DUMP_FILE_PATH ./${BASENAME}

echo "   -> Deleting $DUMP_FILE_PATH"
# delete dump file
# 下载完成后,删除服务器上的 dump 文件
ssh -i $KEY_PATH $DESTINATION rm $DUMP_FILE_PATH

echo ""
echo "...done!"
echo ""

其中,使用秘钥和 StrictHostKeyChecking=accept-new 都是为了免 prompt 登录。在执行备份数据库命令时要注意字符串的转义。

另外在调试时,还遇到了一个问题,我们 docker exec -it <c_name>,进入容器中执行 su <user> 是可行的,但是直接 docker exec <c_name> <command> 里面 su,生成出来的 dump 文件所属却是 root! 必须要使用 -u 参数,指定 <user> 才行。这里我并不理解,希望懂的人可以告诉我这个问题的原因。

通过这些步骤,就顺利的把数据库备份文件,给下载到了 git 仓库里了。

删除数据库Blob文件方案

既然我们已经下载到了数据库文件了,我们就要对这些文件进行管理。

比如我们目标是,保存最近 7 次备份的文件,那么显然我们要把比较旧的数据库文件给删除掉,那么怎么做呢?

这里我也做了一个简单设计:

DUMPS=$(ls | grep ".dump$") # 获取当前目录所有的 .dump
COUNT=$(echo "$DUMPS" | wc -l) # .dump文件个数
SORTED_LIST=$(echo "$DUMPS" | sort -k1.1n) # 按照时间排个序

KEEP_BLOB_COUNT=7 # 保留数据文件的个数

if [ "$COUNT" -gt "${KEEP_BLOB_COUNT}" ]; then
    DEL_COUNT=$(expr $COUNT - ${KEEP_BLOB_COUNT}) # 删除个数
    echo "DELETE COUNT:${DEL_COUNT}"
    DEL_LIST=$(echo "$SORTED_LIST" | head -n $DEL_COUNT) # 删除文件名列表

    for i in $DEL_LIST; do
        echo "DELETEING ${i}..."
        rm $i # 删除过时的数据
        echo "DELETE ${i} SUCCESSFUL!"
    done
fi

避免Git仓库过大方案

我们知道 git 仓库一直是在增大的,当我们删除一个文件的时候,看似这个文件从我们的工作目录中消失了,实际上这个文件并没有被删除,而是跑到了 git history 里面去,久而久之这个项目就越来越大了,因为之前所以被删除的文件,还是被保存在 .git 文件夹里。

那么怎么避免这个问题呢?

Github给了一个解决方案见 removing-sensitive-data-from-a-repository。文章里,给我们介绍了 2 种工具,分别是 java 写的 BFG Repo-Cleanerpython 写的 git-filter-repo

然而我都不想用...

我回想起了很多年前,看到的一则谣言:程序员枪杀四名同事的新闻。

霎时,想到了一个天怒人怨的命令:git push -f !

我们可以使用这个方式,强制更新我们的 git 仓库,把它当成一个 OSS 来用啊!

那么脚本就很容易设计出来了:

git config --global user.email "[email protected]"
git config --global user.name "icebreaker-bot"
git checkout --orphan latest_branch # 创建个纯洁的孤儿分支
git add -A
git commit -am "project recreate"
git branch -D main
git branch -m main
git push -fu origin main # 嘿嘿

当然,这段脚本只适合在自己把控范围内去使用。切勿在工作中使用,不然就会出现几把机械键盘直接砸到脸上的暴力场景。

Github Action CI

最后,我们接下来把上述这三段脚本,串联起来。然后写一个 yml 文件交给 action 定时执行就大功告成啦!

name: Sync_Datebase

on:
  schedule:
    # UTC时间触发
    - cron: "0 0 * * *"
  workflow_dispatch:

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run sync script
        run: |
          chmod 755 ./bak.sh
          chmod 755 ./del.sh
          chmod 755 ./git-clear.sh
          ./bak.sh

这样每次执行完这个脚本,整个仓库焕然一新,历史永远就只有一条了。(笑~)

尾言

这种方式去备份数据库,显然是一种邪魔歪道,有点钻牛角尖,不过思考实现的过程却比较有趣,有兴趣的同学可以参照本文实现一下。

还有 Github ssh 下载实际上速度是很快的,但是由于某些zg特色因素,大概率网速会变成小水管。这种情况可以转而使用某些国内代码托管商,来尝试这个方案。

最后方案并不完美,欢迎建议和意见。

猜你喜欢

转载自juejin.im/post/7233209358980595769