问题发现

在刚开始研究和学习docker容器化技术的时候,一些应用和服务的容器化部署,无论是通过docker run命令还是docker compose编排,都没有统一,都是跟着部署样例进行操作,导致有些容器在部署的时候没有进行数据持久化。

然后我发现服务器上有的时候需要备份、或者数据转移、或者是容器需要更新到最新版本镜像的时候,数据没做持久化,就会很困扰。

容器数据持久化概念

容器持久化是指确保容器在停止、删除或重建后,其内部生成或修改的关键数据能够保留下来。默认情况下,容器的文件系统是临时的(其可写层的数据会随容器生命周期而消失)。

核心原理在于将数据存储与容器本身解耦。我们不将重要数据写入容器自身的可写层,而是通过挂载(Mapping)的方式,将宿主机上的外部存储空间映射到容器内部的指定路径。

主要实现方式有两种

  1. 数据卷 (Volumes): 这是Docker推荐且管理的数据存储区域,生命周期独立于容器,提供更可靠的数据管理。

  2. 绑定挂载 (Bind Mounts): 直接将宿主机上的任意目录或文件挂载到容器内,简单灵活,但宿主机路径需自行管理。

解决思路

1.从当前正在运行的容器中,把配置数据复制到宿主机上。

2.停止并移除旧容器。

3.使用新版镜像,并通过挂载宿主机的目录(就是你刚才复制出来的配置数据存放的目录)到容器内部,从而创建新的容器。

实操案例:Sun-Panel容器

背景:我的容器最初通过docker run命令进行容器部署,但是没有对数据进行挂载(也就是数据持久化),这样每当容器被删除或者重建的时候,数据就会全部丢失(比如前段时间的1panel系统版本从V1升级V2升级,我需要对整个服务器的容器进行备份的时候,我就发现了问题)

方法1(终端copy命令):

步骤1:进入容器、找到配置文件和数据文件的存放路径(参考Sun-panel的文档说明),应该是容器内的:/app/conf文件夹

#从服务器终端,通过以下命令进入容器内部
docker exec -it <你的sun-panel容器ID或名称> sh    # 或者 bash,如果sh不行

#进入容器后,查找配置文件和数据文件通常存在的目录。(这里我查了文档知道容器内路径,也可以通过搜索文件关键字来搜索)
ls -l /app/conf

find / -name "*config*" -type d          #包含config名称的文件
find / -name "*.db"                      #后缀是.db的文件 
find / -name "*.sqlite*"                 #后缀是.sqlite的文件

#在容器内找到对应的配置和数据文件目录后,退出容器
exit

步骤2:将容器内的数据复制到宿主机

#在宿主机上创建一个用于存放 Sun-Panel 数据的目录。
mkdir -p /opt/sun-panel/data

#获取你当前 sun-panel 容器的 ID 或名称。
docker ps   #找到 sun-panel 容器,记录下 CONTAINER ID 或 NAMES 列的名称

#将容器内部的数据复制到宿主机创建的目录。
docker cp <你的sun-panel容器ID或名称>:/app/conf /opt/sun-panel/data/
#这里的 /app/data 是容器内的路径,而 /opt/sun-panel/data/ 是宿主机上的路径。

#检查复制的数据是否都在宿主机的新目录中。
ls -l /opt/sun-panel/data

第二步我的服务器因为未知原因(提示找不到容器中的目录),所以我采取第二种数据复制方法

步骤3:停止并删除旧容器

#停止旧的 sun-panel 容器。
docker stop <你的sun-panel容器ID或名称>

#删除旧的 sun-panel 容器。
docker rm <你的sun-panel容器ID或名称>

步骤4:拉取最新的镜像并且创建新容器,设置好数据持久化挂载volume

#拉取最新版本的 sun-panel 镜像
docker pull HCLonely/sun-panel:latest

#使用数据卷挂载启动新容器,端口自己设置,-v是挂载路径(格式:宿主机路径:容器内路径)
docker run -d \
  --name sun-panel \
  -p <你的宿主机端口>:80 \
  -v /opt/sun-panel/data:/app/data \
  --restart unless-stopped \
  hslr/sun-panel:latest

#检查新容器的状态和日志
docker ps -a
docker logs sun-panel

方法2(容器内压缩、移动、宿主机解压缩):

步骤1:

进入容器、找到配置文件和数据文件的存放路径(参考Sun-panel的文档说明),结果找到数据文件在:/app/conf/database/database.db

为了安全起见,我们最好把整个/app/conf 目录都持久化出来,因为除了数据库文件,可能还有其他配置文件(即使你没改过,程序内部也可能有默认配置在这个目录里)。

步骤2:将容器内的数据和配置文件的目录打包

#重新进入 sun-panel 容器的终端
docker exec -it f7fa45a68f96 /bin/sh
# 如果你的容器默认shell是bash,也可以用 /bin/bash

# 进入 /app 目录,这样打包时目录结构保持为 `./conf`
cd /app

# 打包 conf 目录,并保存到 /tmp/conf_backup.tar
tar -cvf /tmp/conf_backup.tar conf
#tar -cvf: c 创建归档,v 显示详细信息,f 指定文件名。
#conf: 这里是相对路径,表示当前目录下的 conf 目录。

#确认 tarball 文件已创建成功
ls -l /tmp/conf_backup.tar

#退出容器终端
exit

步骤3:把打包文件转移到宿主机并且解压,检查无误后,清理压缩包。

#在宿主机上创建用于存放 Sun-Panel 数据的根目录
mkdir -p /opt/sun-panel/

#将容器内部的 conf_backup.tar 文件复制到宿主机创建的目录
docker cp 容器id:/tmp/conf_backup.tar /opt/sun-panel/

#在宿主机上解压 tarball 文件
cd /opt/sun-panel/
tar -xvf conf_backup.tar

#验证解压后的目录结构和内容
ls -l /opt/sun-panel/conf/
ls -l /opt/sun-panel/conf/database/

#清理:删除宿主机上的 tarball 文件
rm /opt/sun-panel/conf_backup.tar

于我们打包时是从 /app 目录进行的 tar -cvf /tmp/conf_backup.tar conf,所以解压后你会得到一个 /opt/sun-panel/conf/ 目录。

步骤4:停止删除旧容器、拉取最新镜像构建新容器(带数据挂载),在/sun-panel目录下创建docker-compose.yaml

#停止旧的 sun-panel 容器
docker stop 容器名称或者容器id

#删除旧的 sun-panel 容器
docker rm 容器名称或者容器id

#拉取最新版本的 sun-panel 镜像
docker pull hslr/sun-panel:latest

#然后用右侧compose编排文件创建容器
docker compose up -d

version: "3.2"
 
services:
  sun-panel:
    image: "hslr/sun-panel:latest" 
    container_name: sun-panel
    volumes:
      - ./conf:/app/conf # 关键:将宿主机当前目录下的 conf 目录映射到容器的 /app/conf
    ports:
      - 3002:3002 # 端口映射,宿主机端口3002 -> 容器端口3002
    restart: always
    networks: 
      - ABCD   #如果不额外写网络,则会自动创建一个新的桥接网络,这里我们沿用之前已经创建好的桥接网络ABCD

networks:
  ABCD:
    driver: bridge
    external: true

最终大功告成。