ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Docker容器化实战:从镜像原理到MySQL与Redis部署编排

Docker容器化实战:从镜像原理到MySQL与Redis部署编排 1. 先搞明白Docker到底解决了什么问题1.1 从“在我机器上是好的”说起——环境一致性的本质接触容器化这些年Docker是我最常用也最想推荐先学的工具。以前部署一套环境要在服务器上手动装依赖、调配置、处理版本冲突折腾半小时是常事换了机器还得重来一遍。用Docker之后一条命令拉起服务交付的是整个运行环境而不是一堆安装步骤。很多人第一次听到Docker会把它理解成一个轻量虚拟机。这个类比方向对但不全对。虚拟机虚拟的是整台硬件里面要装完整操作系统启动要几十秒甚至几分钟占用几个GB磁盘很常见。Docker虚拟的是操作系统内核之上的运行空间容器共享宿主机内核只打包你自己的应用和它需要的库、配置启动以毫秒计镜像压缩后往往只有几十到几百MB。打个比方虚拟机是整套出租房含全部家电家具容器是搬家公司精打包后的箱子里面只装你需要的东西到了新场地拆开就能用。这套机制解决了分布式开发和交付里最让人头疼的环境不一致问题。你本地跑通的代码上了服务器就起不来多半是系统库版本、依赖路径、环境变量有差异。镜像把应用和运行时环境固化成不可变对象镜像在哪里环境就还原到哪里。这种可复现性比任何部署文档都可靠。1.2 镜像、容器、仓库三个核心概念一次说清刚接触Docker时镜像Image、容器Container、仓库Repository这三个概念最容易绕晕。我当时的理解方式是镜像就是安装包容器就是安装包运行起来之后的进程实例仓库就是存放安装包的软件源。拿MySQL举例。docker pull mysql:8.0是从仓库把镜像下载到本地类似于下载了一个安装包docker run基于镜像启动一个容器类似于执行安装并启动服务。你可以在同一台机器上基于同一个镜像启动多个容器互相隔离。改容器里的东西不影响镜像就像你修改某个程序运行时内存里的数据不会改变磁盘上的安装包一样。要保存修改可以用docker commit生成一个新镜像但我几乎不用这个可复现性差正确做法是用Dockerfile构建。关联操作随手列一下后面实战会反复用到docker pull 镜像名:标签拉取镜像到本地docker images查看本地镜像列表docker run 参数 镜像名创建并启动容器docker ps -a查看所有容器含已停止的docker exec -it 容器名 bash进入容器内部docker logs 容器名查看容器日志docker rm 容器名/docker rmi 镜像名删除容器/镜像这三个概念的关系是理解后面所有操作的地基。地基稳了再去看Dockerfile、Compose、Kubernetes都有顺藤摸瓜的感觉。2. 安装Docker与解决镜像下载慢的焦虑2.1 Docker Engine和Docker Desktop怎么选安装Docker之前先分清两个东西在Linux服务器上跑的是Docker Engine——纯命令行工具集只有守护进程和CLI没有图形界面在macOS或Windows上用的Docker Desktop——一个带GUI的桌面应用内部其实也跑着一个Linux虚拟机来承载容器。选哪个取决于场景。生产环境、云服务器、Linux开发机一律装Docker Engine轻量、稳定、资源开销小。日常开发在Mac或Windows上装Docker Desktop方便很多因为可以在写代码的同时直接跑容器不用额外维护远程机器。国内用户装Docker Desktop时注意安装包体积大下载前确认网络稳定使用WSL2后端的Windows用户建议用WSL2发行版自带的Docker体验更顺滑。Linux上安装Docker Engine最省事的方式是使用官方脚本curl -fsSL https://get.docker.com | sh执行完成后把当前用户加入docker组避免每次敲命令都用sudosudo usermod -aG docker $USER newgrp docker验证是否成功docker version docker run hello-world看到Hello from Docker!的输出说明引擎已经正常工作了。如果这条命令卡住不动多半是拉取镜像慢接着往下看加速配置。2.2 镜像下载慢先把加速器配好镜像下载慢是刚上手时体感最强的问题。原因不复杂默认的Docker Hub仓库部署在海外跨国传输带宽有限拉一个大镜像动辄几十分钟甚至卡到超时。这跟访问某些海外网站速度慢是同一类问题解决方案也相似——把镜像请求打到离自己更近的仓库节点也就是配置镜像加速器。Docker的守护进程支持通过/etc/docker/daemon.json文件配置镜像源。用一个数组列出可用的镜像仓库地址Docker会优先从这些地址拉取镜像。sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json -EOF { registry-mirrors: [ https://docker.m.daocloud.io, https://hub-mirror.c.163.com ] } EOF sudo systemctl daemon-reload sudo systemctl restart docker配置完成后用下面这条命令确认镜像加速器已经生效docker info | grep -A 2 Registry Mirrors如果输出里有你配置的地址说明Docker已经在使用加速器拉取镜像了。实测下来配置加速器之后拉取MySQL、Redis这种两三百MB的镜像基本可以在几分钟内完成体验质的提升。提示镜像加速器地址偶尔会失效或变慢建议多配两到三个Docker会按顺序尝试。如果某个时间点拉取还是很慢换个地址再试一次。2.3 镜像仓库的选择与加速方案除了加速器镜像仓库本身也有很多选择。Docker Hub是官方默认仓库镜像最全社区最活跃但服务器在国外。国内使用较多的是阿里云容器镜像服务个人版注册后在控制台可以拿到专属加速地址格式类似https://xxx.mirror.aliyuncs.com把它填入daemon.json即可。还有一个思路对于像mysql、redis、nginx这类基础镜像国内很多云厂商都已经做了同步缓存用它们的加速地址下载速度往往比直接从官方地址快不少。这里我不具体推荐某一家因为不同地区和不同时段的表现差异很大你可以自己实测对比。特殊情况下有些组织会把镜像发布在GitHub容器仓库ghcr.io或Red Hat的Quay.io上这类仓库地址不能直接当作加速器用。拉这些镜像慢的时候我一般会找替代方案要么从Docker Hub上找同作者的官方镜像要么在有代理的跳板机上手动拉取后导出为tar包再导入到目标服务器。后一种方法就是docker save和docker load的用法后面在离线场景下也经常用到。3. 实战Docker安装MySQL 8.0并使用3.1 拉取镜像与首次启动动手装MySQL 8.0之前先说一个关键决策官方镜像的tag怎么选。mysql:latest是当前最新稳定版mysql:8.0会一直跟随8.0版本的小版本更新mysql:8.0.x是锁定具体小版本。生产环境我强烈建议锁定到具体小版本比如mysql:8.0.33避免某天重启容器时因为latest变化导致不兼容。为了演示这里用mysql:8.0即可。拉取镜像并启动docker pull mysql:8.0 docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e TZAsia/Shanghai \ mysql:8.0逐个参数拆解-d后台运行容器--name mysql8给容器命名方便后续管理-p 3306:3306把主机的3306端口映射到容器的3306端口外部连接MySQL时连宿主机的3306即可-e MYSQL_ROOT_PASSWORD123456设置root用户密码。这个环境变量只在首次创建数据目录时生效容器初始化完成后就废弃了-e TZAsia/Shanghai设置容器时区不设的话默认是UTC时间比北京时间慢8个小时mysql:8.0指定镜像和标签启动之后检查一下容器状态docker ps docker logs mysql8首次启动要做数据库初始化日志里滚动刷新一段时间。看到ready for connections字样说明MySQL已经就绪。3.2 数据持久化与配置文件挂载上面那条命令能跑通但有一个隐藏的坑容器一旦被删除docker rm或重建里面的数据全部丢失。MySQL的数据默认写在容器内部的/var/lib/mysql容器没了数据跟着没。正确做法是把数据目录挂载到宿主机上用-v参数指定。mkdir -p /data/mysql8/conf mkdir -p /data/mysql8/data docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e TZAsia/Shanghai \ -v /data/mysql8/data:/var/lib/mysql \ -v /data/mysql8/conf/my.cnf:/etc/mysql/conf.d/my.cnf \ mysql:8.0这里做了两个挂载数据目录挂载到/data/mysql8/data配置文件挂载到/etc/mysql/conf.d/my.cnf。后者是MySQL官方镜像预留的自定义配置目录会自动加载里面的所有.cnf文件。我遇到过一个实际案例某次给MySQL容器加配置时直接替换了镜像内默认的my.cnf结果启动失败因为官方镜像的默认配置里引用了一些额外路径被覆盖后就找不到了。所以官方镜像预留的conf.d目录才是挂载自定义配置的正确位置。生产环境建议在my.cnf里至少加上这几项[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci default-time-zone08:00 max_connections500 slow_query_log1 slow_query_log_file/var/log/mysql/slow.log long_query_time2修改配置后重启容器docker restart mysql8注意某些参数比如lower_case_table_names关于表名大小写敏感的设置必须在首次初始化数据目录时生效改配置之后重启容器是没用的。这种参数要放在第一次docker run之前就准备好配置文件或者干脆用docker run的命令行参数传进去。这块是易错点踩坑的人很多。3.3 连接MySQL并验证容器启动就绪后先到容器里验证账号能登录docker exec -it mysql8 mysql -uroot -p123456登录成功后执行SELECT VERSION(); SHOW VARIABLES LIKE character_set_server;确认版本是8.0.x字符集是utf8mb4说明初始化正常。宿主机上用客户端连接时确保MySQL容器里已经创建了可远程访问的账号。MySQL 8.0默认的root账号只允许从localhost连接从宿主机直接连会报权限错误。可以进入容器执行授权CREATE USER app% IDENTIFIED BY App123456; GRANT ALL PRIVILEGES ON *.* TO app%; FLUSH PRIVILEGES;Navicat或DBeaver连接时填宿主机IP和映射端口3306即可。遇到Public Key Retrieval is not allowed错误在连接参数里加上allowPublicKeyRetrievaltrue就行——这是MySQL 8.0缓存SHA2密码插件导致的经典报错不是配置问题。还有一个容易被忽略的点端口映射。如果宿主机3306已经被其他MySQL占用了容器会启动失败。这种情况改用其他宿主端口比如-p 3307:3306外部连接用3307端口。我建议在服务器上干脆给每个容器分配独立端口段方便追踪防止冲突。4. 实战Docker部署Redis主从架构4.1 主从结构怎么设计Redis主从复制的作用是给读请求做水平扩展以及给数据做个实时热备。MySQL可以一主多从Redis同样可以。这次部署一主一从结构是主节点负责写从节点同步主节点的数据并负责读流量主节点挂掉时从节点可以手动提升为新主。虽然现在企业里普遍用哨兵或Cluster做高可用但理解手工主从是理解哨兵机制的起点所以先把这个搭明白。Docker里跑主从第一步是建一个自定义网络让两个容器可以通过容器名互相访问而不是依赖随时变化的IP地址docker network create redis-net4.2 主节点配置Redis官方镜像默认没有配置文件启动时会用内置默认配置。主从复制要求绑定可访问的地址所以得自己准备配置文件。创建主节点配置/data/redis/master/redis.confbind 0.0.0.0 port 6379 appendonly yes appendfilename appendonly.aof requirepass 123456bind 0.0.0.0允许任意地址连接容器里必须这么设否则容器外访问不到appendonly yes开启AOF持久化防止重启丢数据requirepass 123456设置主节点密码从节点同步时也要用这个密码认证启动主节点docker run -d \ --name redis-master \ --network redis-net \ -p 6379:6379 \ -v /data/redis/master/redis.conf:/etc/redis/redis.conf \ redis:7.0 \ redis-server /etc/redis/redis.conf这里注意镜像后面跟的参数覆盖了Dockerfile里的默认启动命令指定使用我们挂载进去的配置文件启动。4.3 从节点配置与验证从节点配置里最关键的一行是replicaof指定主节点的容器名和端口。Docker自定义网络自带DNS解析容器名可以当作主机名用这是比写IP稳得多的方案——主节点重建导致IP变化时从节点还能自动找到它。创建从节点配置/data/redis/slave/redis.confbind 0.0.0.0 port 6379 appendonly yes appendfilename appendonly.aof replicaof redis-master 6379 masterauth 123456 requirepass 123456masterauth是从节点向主节点做同步认证时用的密码必须和主节点的requirepass保持一致。如果主节点没设密码这一行可以不要。启动从节点docker run -d \ --name redis-slave \ --network redis-net \ -p 6380:6379 \ -v /data/redis/slave/redis.conf:/etc/redis/redis.conf \ redis:7.0 \ redis-server /etc/redis/redis.conf宿主机的6380端口映射到从节点的6379这样从外部看主从分别是宿主机IP:6379和宿主机IP:6380。进入从节点容器验证主从状态docker exec -it redis-slave redis-cli -a 123456 127.0.0.1:6379 info replication输出里应该看到role:slave并且master_link_status:upslave_repl_offset不断增长。再往主节点写一条数据从节点立刻能查到说明复制链路已经打通。提示Redis 5.0之前配置从节点的指令是slaveof5.0之后改成了replicaof。虽然老命令仍然兼容但新配置一律用replicaof更规范。这套主从结构里最值得记住的教训是容器之间通信要借助自定义网络而不是依赖-p端口映射。自定义网络里的容器可以通过容器名互相访问网络隔离也更安全端口映射只是给外部访问用的入口。很多人在Docker里搭集群网络不通问题就出在没建自定义网络、写死了IP地址。5. 用Docker Compose从单容器走向整站编排5.1 Compose是什么什么时候用手动docker run部署单个容器还好一旦服务多了问题就来了十几个容器要按依赖顺序启动、要共享网络配置、要统一管理环境变量光靠命令完全记不住每重建一次都要翻历史找当初的参数。Docker Compose就是来解决这个问题的——它用一个YAML文件描述整套服务一条命令完成创建、启动、停止。Compose适用的场景很清晰单机上的多容器编排。如果你只是调试一个MySQL容器手动docker run完全够用一旦涉及“MySQL Redis 后端应用 Nginx”这种组合直接上Compose。单机多服务的编排是它的舒适区Kubernetes负责的跨多台机器的编排则是另一个量级的话题不该用Compose硬扛。5.2 用compose.yaml编排MySQL和RedisCompose默认的配置文件名是compose.yaml或compose.yml官方在2023年后的新文档里建议用compose.yaml因为和docker-compose命令的旧缩写区分开来。写一个包含MySQL和Redis的编排文件services: mysql: image: mysql:8.0 container_name: app-mysql restart: always ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: 123456 TZ: Asia/Shanghai volumes: - ./mysql/data:/var/lib/mysql - ./mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf networks: - app-net redis: image: redis:7.0 container_name: app-redis restart: always ports: - 6379:6379 command: redis-server /etc/redis/redis.conf volumes: - ./redis/data:/data - ./redis/conf/redis.conf:/etc/redis/redis.conf networks: - app-net networks: app-net: driver: bridge这个文件里几个要点restart: always容器意外退出时自动重启服务器重启后容器也会跟着恢复这在生产环境里几乎是标配volumes使用相对路径./Compose会基于文件所在目录解析路径所以整个项目目录可以像代码一样提交到仓库换台机器拉下来直接up环境秒级复现networks定义一个桥接网络两个服务都在这个网络里互相用服务名mysql、redis访问在配置文件同级目录执行docker compose up -dCompose会依次拉取镜像、创建网络、启动容器。查看状态和日志docker compose ps docker compose logs -f mysql停止和清理docker compose down # 停止并删除容器数据卷默认保留 docker compose down -v # 连同数据卷一起删除慎用数据会丢我在项目里习惯把整套服务的目录整理成这样的结构app/ ├── compose.yaml ├── mysql/ │ ├── data/ │ └── conf/my.cnf └── redis/ ├── data/ └── conf/redis.conf这样一套目录就是一个完整项目代码、配置、数据入口都聚在一起团队成员拉起环境只需要装好Docker、clone仓库、执行docker compose up -d三分钟搞定。5.3 Compose进阶编排带主从的Redis前面手动部署的Redis主从用Compose编排会更清晰。在redis-master和redis-slave两个服务的配置里replicaof可以直接写服务名redis-masterCompose网络天然支持容器名解析services: redis-master: image: redis:7.0 container_name: redis-master restart: always ports: - 6379:6379 command: redis-server /etc/redis/redis.conf volumes: - ./redis/master/redis.conf:/etc/redis/redis.conf networks: - redis-net redis-slave: image: redis:7.0 container_name: redis-slave restart: always ports: - 6380:6379 command: redis-server /etc/redis/redis.conf volumes: - ./redis/slave/redis.conf:/etc/redis/redis.conf networks: - redis-net networks: redis-net: driver: bridge把主从两个容器放到Compose里管理后最大的改变是启动顺序不用管了。Docker负责网络创建两个服务之间只要在运行时能解析到对方就行。Compose在启动时会自动处理服务依赖用depends_on可进一步控制顺序整体运维成本比手动跑命令低一个量级。6. 实操中常见的坑与排查技巧实录6.1 端口冲突容器起不来日志却没报错场景docker run执行完容器状态一直Exited。查docker logs发现MySQL报bind: Address already in use但宿主机上看不到对应进程——因为占用端口的是另一个容器而不是普通进程。排查思路先用docker ps -a找所有容器再用docker inspect 容器名 | grep -i port看端口映射最后判断是不是容器之间端口冲突。如果宿主机本身有进程占端口用lsof -i:3306能看到。解决方案优先改新容器的宿主端口而不是去动旧服务。6.2 容器内时间不对日志全是UTC现象容器日志时间比本地时间慢8小时。原因是基础镜像默认使用UTC时区容器内没有同步宿主机的timezone。解决方式运行容器时加环境变量-e TZAsia/Shanghai或者在Compose文件里给每个服务加environment: TZ: Asia/Shanghai。对某些不读TZ环境变量的镜像可以在Dockerfile里设置时区或者挂载宿主机的/etc/localtime。最简单稳定的方案还是环境变量一劳永逸。6.3 Docker日志把磁盘塞满容器默认会把标准输出和标准错误写入json日志文件。长时间不清理某个日志量大的容器可能占据几个GB。查磁盘使用情况du -sh /var/lib/docker/containers/*/*.log应对方案有两个层面。一是运行时限制日志大小在/etc/docker/daemon.json里配置{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }配置后重启Docker新容器的日志单个文件最大10MB最多保留3个老容器不受影响。二是在应用层面把日志输出到文件再挂载出来通过宿主机日志轮转工具统一管理。生产环境建议两者配合。6.4 容器退出码怎么看docker ps -a里最后一列是退出码这个数字很有用0正常退出1程序运行出错可能是配置错误、依赖缺失137被强制杀掉通常是OOM内存不足或手动docker kill139段错误可能是镜像兼容性问题排查顺序先docker logs看应用日志再docker inspect看容器的状态和资源限制最后结合退出码定位方向。很多情况下看到退出码就能猜个大概137就去看内存限制1就去看配置文件。6.5 一句话避坑清单镜像tag锁版本别用latest上生产数据目录务必挂载到宿主机跨容器通信用自定义网络不要依赖IP改MySQL大小写敏感、字符集等参数要在首次初始化前配置Redis主从同步里masterauth和主节点requirepass必须一致Compose里down -v会删数据卷执行前想清楚这些坑基本都是我踩过一遍、在线上吃过亏才记住的。写在这里希望能帮你省掉几晚上的排查时间。我在实际项目中养成的习惯是所有服务优先考虑容器化但容器化不等于把配置都塞进镜像里而是数据卷挂载、配置外置、日志系统统一管理。这套方法论一开始可能觉得麻烦用顺手之后就会发现环境交付从步骤文档变成了一个命令变更回滚从手工操作变成了换镜像版本重跑整体的效率和稳定性完全不一样。接下来你可以在自己的项目里做个实验把一个传统部署的MySQL迁移到Docker再把Redis主从用Compose编排起来跑一段时间对比看运维体验。容器化真正带来的改变不是省了安装依赖的时间而是让环境本身变成了可重复构建、可版本管理的资产。这个收益用起来才能体会到。
返回列表