
Dockerfile 多阶段构建
最近在手撕Gitea源码的时候,发现项目的Dockerfile里有这样的片段:
FROM golang:1.17-alpine3.15 AS build-env
#...
RUN go build contrib/environment-to-ini/environment-to-ini.go
FROM alpine:3.15
#...
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
经过一些搜索,发现docker支持多阶段构建。 所谓的多阶段构建就是在一个Dockerfile里面,可以通过构建中间镜像,方便的利用各种现成的集成环境来编译最终运行环境需要的文件。
最终环境里面可以做到非常干净,只有最终运行需要的文件,而不需要安装编译环境。
Docker版本过低和CentOS系统停止维护
我修改完我的Dockerfile,同时重构了一下我的网站项目结构,把前端和后端放进一个代码仓库里。 在推送到服务器后,意外的发现由于我的Docker版本过低,导致我的项目无法正常构建。
而且从我的版本更新到最新的Docker版本需要卸载就版本再装新版本,我的网站的数据都在Docker数据卷里,直接重装不太可能会有好结果。 正好最近CentOS
系统宣布停止维护。一个停止维护的系统bug一定会越来越多,软件支持率也会越来越低。 所以我决定不如换一个Ubuntu服务器吧。
野生运维之路
更换服务器意味着数据备份与恢复,不像在普通的系统里,将数据拖进拖出U盘里就叫备份和恢复。 在Docker里,数据是放在Docker数据卷里,并在容器启动时动态挂载到指定的容器上。 如何将它从数据卷里取出来再放回去,是一门艺术。
大致的过程可以分为,先将静态的文件打包并压缩,
backup.sh
VOLUME_NAME=("volume1" "volume2" "volume3")
for VOLUME in "${VOLUME_NAME[@]}"
do
docker run --rm \
# 创建数据卷,将项目的命名数据卷挂载到本中间容器上
--volume "${VOLUME}:${TEMPORARY_DIRECTORY_TO_STORE_VOLUME_DATA}" \
# 创建第二个数据卷,将主机上一个路径与容器内的备份数据所在目录进行映射
--volume "${BACKUP_DATA_DIRECTORY}:${TEMPORARY_DIRECTORY_TO_STORE_BACKUP_FILE}" \
# 用最小巧的alpine镜像来处理文件的压缩。
alpine \
# 容器内执行压缩命令
tar cvf "${TEMPORARY_DIRECTORY_TO_STORE_BACKUP_FILE}/${VOLUME}.tar" ${TEMPORARY_DIRECTORY_TO_STORE_VOLUME_DATA}
# 压缩完之后我们就可以在 BACKUP_DATA_DIRECTORY 里找到对应数据了
done
对于数据库文件,不要直接拷贝,而是利用用数据库对应的备份恢复命令。我使用的是PostgreSQL。
docker run -it --rm \
# 在跑服务的时候最好自己设定网络名称,限制变化,这里将项目的网络与这个中间容器关联
--network=${DOCKER_COMPOSE_PREFIX}_${NETWORK_NAME} \
# 将项目的命名数据卷挂载到本中间容器
--volume "${DOCKER_COMPOSE_PREFIX}_${DB_VOLUME_NAME}:${TEMPORARY_DIRECTORY_TO_STORE_VOLUME_DATA}" \
# 可以在环境变量中指定数据库的密码
--env PGPASSWORD="${PGPASSWORD}" \
# 这里我直接使用与数据库相同的镜像,
postgres:13.3-alpine \
# pg_dump 命令输出数据库备份文件
pg_dump \
# 这里的host一般就是数据库容器名,因为与项目网络相连,所以才访问得到
--host=${DB_HOST} \
--username=${DB_USERNAME} \
--dbname=${DB_NAME} \
--format=p > "${BACKUP_DATA_DIRECTORY}/db_data/backup.sql"
备份完了文件,可以使用scp命令直接将文件传输到新服务器
# 在旧服务器运行
scp -r /path/to/backup/file/directory username@new_server:/path/to/new/directory
不放心也可以先拷到自己电脑里一份
# 在本地运行
scp -r username@old_server:/path/to/backup/file/directory /path/to/my/computer/directory
当然要运行这两条命令,你需要先设置好登陆密钥对。
传输好了备份文件,终于可以在新服务器上恢复数据了。 在新服务器上构建好容器之后,先不急启动容器。 容器启动之后难免有新数据产生,特别是数据库,容器启动之后再恢复很有可能起冲突。
restore.sh
for VOLUME in "${VOLUME_NAME[@]}"
do
echo "${DOCKER_COMPOSE_PREFIX}_${VOLUME}"
docker run --rm \
--volume "${DOCKER_COMPOSE_PREFIX}_${VOLUME}:${TEMPORARY_DIRECTORY_TO_STORE_VOLUME_DATA}" \
--volume "${BACKUP_DATA_DIRECTORY}:${TEMPORARY_DIRECTORY_TO_STORE_BACKUP_FILE}" \
alpine \
# 对应的解压命令
tar xvf "${TEMPORARY_DIRECTORY_TO_STORE_BACKUP_FILE}/${VOLUME}.tar" -C ${TEMPORARY_DIRECTORY_TO_STORE_VOLUME_DATA} --strip 1
done
docker run -it --rm \
--network=${DOCKER_COMPOSE_PREFIX}_${NETWORK_NAME} \
--volume "${BACKUP_DATA_DIRECTORY}:${TEMPORARY_DIRECTORY_TO_STORE_BACKUP_FILE}" \
--env PGPASSWORD="${PGPASSWORD}" \
postgres:13.3-alpine \
# 使用psql命令读取备份文件并恢复
psql \
--host=${DB_HOST} \
--username=${DB_USERNAME} \
--dbname=${DB_NAME} \
--file="${TEMPORARY_DIRECTORY_TO_STORE_BACKUP_FILE}/db_data/backup.sql"
这时候再启动容器,就跟在旧服务器上感觉一模一样啦!
优化过程,享受生活
在实际情况下,这些脚本都保存在文件里,可以直接执行,也可以利用GNUMake的Makefile编写脚本的具体行逻辑。
有人会说Makefile又是什么东西?如果说.sh文件的作用是把linux shell命令扎堆执行,那么Makefile就是更加花里胡哨的shell命令执行器。
你可以编写文件让一系列又臭又长的shell命令只用一两个单词就按你的要求执行。这时候终于可以喝杯咖啡了。
又是手冲☕️的一天