ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Docker实战:镜像加速、数据卷与Compose编排解决部署难题

Docker实战:镜像加速、数据卷与Compose编排解决部署难题 上一讲我们把Docker是什么、怎么安装、怎么跑通第一个容器都讲完了。按理说到这一步你已经能把一个Nginx或者MySQL拉起来玩一玩。但据我观察多数人在这个阶段会卡住要么镜像下载慢到怀疑人生要么容器一删数据全没了要么想同时跑几个服务却发现它们之间根本不通信。这一篇就是来解决这些“入门之后立刻遇到”的实际问题的。我会用部署MySQL 8.0和Redis主从这两个最经典的场景把镜像加速、常用命令进阶、数据卷持久化、端口映射、Docker Compose编排这些Docker核心知识点串起来讲。内容偏实操每一条都是能直接复制去用的也会把我踩过的坑一并交代清楚。适合已经装好Docker、准备正式上手部署服务的同学也适合那些大概知道docker run -it但一遇到真实项目就发懵的朋友。先说明一下这一篇不会再去解释Docker比虚拟机好在哪、内核共享是什么意思这类概念。如果这些还没搞懂建议先翻翻前一篇文章。我们要开始干正事了。1. 镜像加速与仓库先把镜像拉下来再谈别的1.1 下载慢的真正原因与解决思路Docker官方镜像仓库Docker Hub部署在国外国内直连经常抽风一个几百MB的镜像能拖半小时。这不是你网络的问题也不是Docker坏了是物理距离导致的。理解这一层之后解决方案就很清晰让Docker通过离你更近的源去拉镜像或者干脆绕过镜像仓库直接塞进宿主机。前者用的是镜像加速器后者用的是离线导入。两种我都用过日常开发用加速器就够了离线场景则适合内网安装。Docker Hub本身就像手机的应用商店里面有你想要的各种镜像。你可以用docker search mysql去搜也可以直接在hub.docker.com网页上看文档很多官方镜像在页面里会写明环境变量、端口、数据卷路径。这也是部署前必看的一手资料。但它的默认源在国内真不好用只在偶尔拉小镜像的时候能忍下面两种办法能实打实解决问题。1.2 配置镜像加速源的完整操作在Linux上Docker的镜像源配置集中在/etc/docker/daemon.json这个文件里。如果文件不存在就新建一个内容是一段JSON。常见公共加速源有好几个但我不建议一次性全部塞进去因为Docker会按顺序尝试第一个挂了才走下一个源太多反而拖慢速度。sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json EOF { registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.net, https://docker.mirrors.ustc.edu.cn ] } EOF写完以后必须重启Docker才能生效sudo systemctl restart docker docker info在docker info的输出里找到Registry Mirrors这一段能看到刚才配置的地址就说明生效了。需要提醒的是公共加速器有时候会挂挂掉的表现就是docker pull还是慢或者超时。这时候换一个可用地址就行。验证地址能不能用可以用curl -I 地址/v2/如果能返回HTTP 200说明这个源还能用。另外注册云厂商的容器镜像服务一般会给你一个专属加速地址那个通常最稳可以优先考虑。1.3 离线安装Docker与镜像导入导出在没有外网的内网环境加速器是没法用的。我实际帮客户迁移服务时遇到过一次这种情况整个机房只有一台能访问外网的机器其余都是内网。这时候的标准做法是在有网的机器上把镜像打成tar包再把tar包导入到内网Docker里。# 在有外网的机器上保存镜像 docker save -o mysql8.tar mysql:8.0 docker save -o all-images.tar mysql:8.0 redis:7.0 nginx:1.25 # 在内网机器上加载镜像 docker load -i mysql8.tar docker load -i all-images.tar有几个坑值得说。第一跨架构的镜像包不能随便传递在amd64机器上save的包拿到arm64机器load即使能加载跑起来也多半报exec format error。第二包含多个镜像的tar包load的时候会把里面所有镜像一次加载不用一条条喂。第三如果镜像非常大save和load的过程会有点慢中间不要中断终端等它自己跑完。离线安装Docker本身也有类似思路官方提供离线安装包下载后解压、拷贝二进制文件再配置systemd服务。流程比在线安装繁琐但原理不复杂Docker就是一个常驻后台的守护进程只要把二进制和配置放好它就能跑起来。2. Docker常用命令进阶从会用到用得顺手2.1 一条docker run命令的完整拆解很多教程直接丢给你一条长长的一行命令看起来像魔法实际拆开以后每个参数都是明确的。以部署MySQL 8.0为例这是我最常用的启动方式docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDRoot123456 \ -v /opt/mysql-data:/var/lib/mysql \ -v /etc/localtime:/etc/localtime:ro \ --restartalways \ mysql:8.0逐个参数说-d后台运行启动后返回容器ID不会占用当前终端。没有它终端就会一直贴在容器日志前CtrlC一按容器也停了。--name mysql8给容器起名字。容器默认会有个随机ID用名字管理方便太多后面ps、logs、exec都能用名字。-p 3306:3306端口映射左边是宿主机端口右边是容器内服务监听端口。有的场景宿主机的3306已经被占了可以改成-p 3307:3306客户端就连宿主机3307。-e MYSQL_ROOT_PASSWORDRoot123456给MySQL镜像传环境变量。官方MySQL镜像第一次启动时读取这些环境变量来完成初始化root密码、默认数据库、时区都在这里配。-v /opt/mysql-data:/var/lib/mysql数据卷挂载。MySQL的数据目录在容器里的/var/lib/mysql不挂载的话容器一删数据全丢挂载之后数据写到宿主机/opt/mysql-data。这个后面第3章会细讲。-v /etc/localtime:/etc/localtime:ro把宿主机的时区文件挂进去解决容器内时区不对的问题。:ro表示只读防止容器内乱改。--restartalways容器异常退出时自动拉起适合常驻服务。手动docker stop的情况下不会自己起来这个行为可以放心。实际运行中用mysql -h127.0.0.1 -P3306 -uroot -p连一下能连上就说明端口映射和数据初始化都正常。2.2 进容器看日志与执行命令调试容器内的服务最常用的两板斧是docker logs和docker exec。# 实时查看容器日志类似tail -f docker logs -f mysql8 # 只看最近200行不要再刷屏了 docker logs --tail 200 mysql8 # 进入正在运行的容器拿到一个shell docker exec -it mysql8 bashdocker exec -it mysql8 bash这句的意思是在mysql8容器里执行bash命令-i保持标准输入打开-t分配一个伪终端。两个配合起来就能交互式操作。如果容器里连bash都没有比如一些精简的alpine镜像那就用docker exec -it 容器名 sh。进去以后可以看目录、看进程、执行MySQL客户端。但我不太建议在容器里改配置再重启因为容器原则上应该是“用完即弃”的修改很容易在容器重建时丢失。更靠谱的做法是把配置做成文件挂载进去或者启动时通过环境变量和command参数带进去。还有个命令是docker inspect mysql8用来查看容器的详细元信息IP地址、挂载路径、端口、环境变量全在里面。想快速拿某个字段可以用Go模板语法docker inspect -f {{.NetworkSettings.IPAddress}} mysql82.3 资源查看与清理跑的日子久了Docker目录会越来越大全是停止的容器、悬空的镜像、没人用的网络层。我习惯定期看一眼docker ps -a docker stats docker system dfdocker ps -a能看所有容器包括退出状态的。很多Exited的容器其实是之前调试遗留下来的确认没用之后清掉docker rm 容器ID。加-f可以强制删正在运行的容器但生产环境慎用。docker stats是实时显示容器CPU、内存、网络流量的面板性能排查很好用。docker system df则像手机里的存储管理能看到镜像、容器、数据卷分别占了多少空间。清理的杀器是docker system prune -a它会把所有没有在用的容器、镜像、网络全部清掉。注意默认不会删数据卷这是Docker故意设计的就是怕你把数据库数据误删。想连数据卷一起清得加--volumes我劝你务必确认断了备份再用。3. 持久化与端口映射容器不够“存”的硬伤3.1 容器文件系统的生命周期与数据卷初学者最容易犯的错是把文件直接写进容器里。容器是分层文件系统最上面有一层可写层容器还在时文件都在一旦docker rm删除这层可写层也跟着没了数据就像被蒸发了一样。想让数据活下来就得用Docker的数据卷Volume机制。一句话解释数据卷就是把宿主机的一个目录或一个由Docker管理起来的存储空间挂载进容器。容器内看到的路径是在写文件实际写到了宿主机上容器删了数据还在下个容器还能挂回去继续用。数据卷有三种挂载方式给个对比类型写法示例使用场景匿名卷-v /var/lib/mysql临时实验容器删了卷还在但不好找回命名卷-v mysql-data:/var/lib/mysql纯数据存储不想关心宿主机路径绑定挂载-v /opt/mysql-data:/var/lib/mysql需要直接看到或修改宿主机里的数据命名卷由Docker管理实际数据放在/var/lib/docker/volumes/mysql-data/_data下面。绑定挂载则直接把宿主机某个目录映射进容器改宿主机文件容器内立刻能看到反过来也一样。做开发调试时我喜欢用绑定挂载方便纯生产环境则经常用命名卷管理更干净。备份恢复的思路也很清晰。如果MySQL在跑最简单的备份方式是mysqldump。但通用做法是用一个临时容器把命名卷打包成tardocker run --rm \ -v mysql-data:/data \ -v $(pwd):/backup \ alpine tar czf /backup/mysql-data.tar.gz -C /data .恢复就是把tar解压回卷里docker run --rm \ -v mysql-data:/data \ -v $(pwd):/backup \ alpine tar xzf /backup/mysql-data.tar.gz -C /data3.2 端口映射与容器网络基础容器有自己独立的网络命名空间相当于一台小电脑。没有端口映射时宿主机访问不到容器内的服务。-p就是给这个“小电脑”开一个门把宿主机某个端口的流量转进去。除了端口映射容器之间的互相访问也会让新手头疼。Docker默认提供bridge、host、none三种网络。默认bridge网络下每个容器拿到一个172.17.x.x的IP但IP会在容器重启后变化靠IP通信不靠谱。更稳的方案是手动创建自定义bridge网络同一网络里的容器可以用容器名直接互访Docker内置DNS会解析。docker network create app-net docker run -d --name mysql8 --network app-net mysql:8.0 docker run -d --name myapp --network app-net my-app-image这样在myapp容器里配置数据库地址直接填mysql8:3306就行不用关心它的IP是多少。这在Compose编排里也是标配玩法第4章会看到。一个容易踩坑的点容器里的localhost指的是容器自己不是宿主机。你在myapp容器里写数据库地址用localhost它会尝试连自己结果连不上。正确写法是用数据库容器的名字或者在宿主机上访问时用127.0.0.1加映射端口。4. 用Docker Compose编排多容器一个文件拉起整套服务4.1 Compose的价值一条条docker run记录真记不住尤其服务一多端口、卷、环境变量、依赖关系全混在一起。Compose就是把这些声明写进一个YAML文件然后一条命令全部创建启动。Compose的优势不只是简化命令更在于配置即代码。docker-compose.yml放进代码仓库团队任何人拉下来执行docker compose up -d就能得到和开发环境一致的服务。新人上手也不需要背一堆参数看文件就知道每个服务怎么配的。新版Docker已经内置docker compose插件不需要额外安装旧版docker-compose。4.2 用Compose部署MySQL 8.0在项目目录下新建docker-compose.yml内容如下services: mysql: image: mysql:8.0 container_name: mysql8 restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: Root123456 MYSQL_DATABASE: testdb TZ: Asia/Shanghai command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci ports: - 3306:3306 volumes: - ./mysql-data:/var/lib/mysql几个要点services下面每个键代表一个服务这个例子只有一个mysql。command会把镜像默认的启动命令覆盖掉这里给MySQL加上了默认字符集参数避免建表时中文乱码。environment里的MYSQL_DATABASE会在首次初始化时自动创建一个testdb数据库不配也没关系但有它部署后省一道建库步骤。restart: unless-stopped表示除非手动stop否则容器挂了会自动拉起。线上服务我基本都用这个策略。./mysql-data是相对当前目录的路径Compose会自动创建这个目录。启动docker compose up -d docker compose ps docker compose logs -f mysqldocker compose down可以停掉所有服务并移除容器但不会删除./mysql-data里的数据所以放心用。4.3 用Compose搭建Redis主从Redis主从复制很适合做Compose示例因为主从两个服务差别很小能在同一个文件里对比出“模板”的味道。以Redis 7.x为例services: redis-master: image: redis:7.0 container_name: redis-master restart: unless-stopped command: redis-server --appendonly yes ports: - 6379:6379 volumes: - ./master-data:/data redis-slave: image: redis:7.0 container_name: redis-slave restart: unless-stopped depends_on: - redis-master command: redis-server --replicaof redis-master 6379 --appendonly yes ports: - 6380:6379 volumes: - ./slave-data:/data--replicaof redis-master 6379是让从节点把自己挂到主节点下面redis-master就是Compose自动创建的网络里主节点的服务名不用写IP。注意Redis 5.0以后配置项叫replicaof不叫slaveof了网上老教程经常看到旧写法新手照抄会报错。有个细节depends_on只能保证主节点容器先启动不能保证Redis进程已经准备好接受连接但从节点会不断重试所以一般不会出问题最多日志里多几行连接失败记录。启动后用命令验证主从状态docker compose up -d docker exec -it redis-slave redis-cli info replication输出里看到role:slave和master_link_status:up就说明主从已经搭好了。往主节点写一个key从节点能查到复制就正常的。5. Docker常见问题排查实录每一行报错都是经验5.1 Windows下Docker Desktop启动失败Windows上最常见的报错是启动时提示virtualization support not detected或者Docker Desktop failed to start。第一反应别急着重装先确认虚拟化是不是开着。解决思路按顺序排查进入BIOS/UEFI找到Intel VT-x或者AMD-V相关选项确保开启。不同品牌主板路径不一样但关键词就这么几个。打开Windows功能确保Hyper-V、适用于Linux的Windows子系统、虚拟机平台这三个功能全部勾选然后重启系统。确认WSL2已经安装并设置为默认版本。Docker Desktop现在主要基于WSL2后端运行落了一项都会启动失败。Docker Desktop Installer.exe安装完点击启动没反应也可以先看看Windows事件日志很多情况都是虚拟化没开而不是软件本身坏了。5.2 Linux下服务启动失败与权限报错Linux上sudo systemctl restart docker之后起不来先看服务日志systemctl status docker journalctl -xeu docker | tail -50日志里最常见的坑是daemon.json写坏了。JSON是严格格式多一个逗号、少一个引号都会让Docker直接拒绝启动。这时候你挨个检查字符不如交给工具python3 -m json.tool /etc/docker/daemon.json有语法错误会立刻报出来改完再重启Docker就行。还有个高频权限报错是在不切root用户的前提下直接敲docker ps得到Got permission denied while trying to connect to the Docker daemon socket这是因为当前用户不在docker组里。解决办法sudo usermod -aG docker $USER然后重新登录或者newgrp docker。这里多说一句加入docker组等同于获得了宿主机root级别权限因为可以挂载任意目录到容器里生产环境给用户加组要慎重。5.3 容器网络通信异常容器网络报错种类很多这里说两个最典型的。第一个是跨容器访问失败尤其数据库连不上。先docker inspect 容器名查确认容器都在同一个自定义网络里用容器名而不是IP访问。如果发现不在同一网络用docker network connect把容器加进去。第二个是Kafka这类依赖“广告地址”的服务。部署Kafka到Docker后客户端连它经常报Error while fetching metadata with correlation id 1 : {server:9092java.nio.channels.ClosedChannelException}这个错误的大意是客户端从bootstrap地址拿到了Kafka返回的元信息但元信息里广播的broker地址客户端访问不到。在Docker环境里Kafka配置了容器内主机名而客户端在容器外自然就连不上。单机测试的解法是设置KAFKA_ADVERTISED_LISTENERSPLAINTEXT://localhost:9092这样Kafka对客户端广播的就是localhost:9092宿主机上客户端就能连了。生产环境需要区分内网访问和外网访问配置会更复杂但根本思路是一样的确保Kafka广播给客户端的地址是客户端能访问到的。5.4 高频问题速查表症状常见原因解决方法docker pull超时或几乎不动默认镜像源在国外配置registry-mirrors加速源Docker Desktop提示virtualization support not detectedBIOS未开启虚拟化开启VT-x/AMD-Vdocker ps提示permission denied用户不在docker组sudo usermod -aG docker $USERDocker服务启动失败daemon.json格式错误python3 -m json.tool检查MySQL容器启动后连接失败端口占用或数据目录权限查看logs换端口检查宿主机3306Redis主从master_link_status:down从节点配置或网络问题确认replicaof指向正确服务名同网段Kafka客户端一直拿不到元数据advertised.listeners不匹配设置PLAINTEXT://localhost:9092说实话从“把容器跑起来”到“把服务稳稳当当跑着”中间隔着的就是持久化、网络、编排这几道坎。我在实际使用中最深的体会是凡是需要保存的数据一律走数据卷凡是多服务协作一律先创建自定义网络或者直接用Compose不要贪方便一条命令跑到底。还有个日常用得特别多的小技巧我会在~/.bashrc里给docker compose配置一行alias把docker compose logs -f --tail 50缩成一个短命令维护的时候能省不少事。最后想说的是刚开始记不住命令很正常多跑几次真实项目Docker的这些概念自然就串起来了。
返回列表