首页 / 资讯中心 / 文章详情

主机只装Docker:容器化部署MySQL、Redis与Nginx实战

主机只装Docker:容器化部署MySQL、Redis与Nginx实战 ★ FEATURED ARTICLE
好几年前我刚接触服务器部署的时候也干过一件蠢事拿到一台全新的服务器先装个 Nginx再装 MySQL然后又装 Redis每装一个都要处理一堆依赖、编译报错、版本冲突。等折腾完一轮系统已经变得又脏又乱换台机器又得从头再来一遍。后来我彻底转向“主机只装 Docker所有服务都进容器”的思路才发现这才是省心省力的正解。这篇文章就结合我自己实际配置一台“只支持 Docker 的服务器”的过程把方案设计、环境准备、镜像加速、常用服务部署和故障排查完整过一遍给正在折腾 Docker 的你看个明白。这个方案的核心思路很简单主机系统能跑多干净就多干净只保留一个 Docker 运行时其余 MySQL、Redis、Nginx、定时任务面板等等全都用容器来跑。它特别适合下面这几类人手上有一台配置不高的小内存 VPS 或云主机想省资源又不想被各种编译折腾需要在多台机器上快速复现同一套环境比如测试环境、预发环境刚接触 Linux 服务器不想一上来就被各种“源码编译装软件”劝退的人或者你已经踩过“装 A 软件破坏了 B 软件依赖”这种坑的老手。1. 整体设计思路与方案选型1.1 为什么选择“主机只装 Docker”这套方案先说结论如果你想长期维护一台服务器把服务容器化是性价比最高的路子。原因其实不复杂就是隔离和标准化。我以前在裸机上部署过 MySQL 8.0过程极其痛苦要自己解压二进制包、建 mysql 用户、初始化数据目录、配置 systemd 服务、处理日志轮转还要考虑 glibc 版本兼容性。一旦系统升级或者误操作删了什么依赖MySQL 可能直接起不来。而用 Docker 部署的话这些都是镜像里现成的东西。用一句生活化的话来类比裸机装软件就像你在自己家里开餐厅锅碗瓢盆、食材储藏、灶具维修全都得自己管Docker 部署则像直接租了个中央厨房东西都是现成的你只需要把菜谱compose 文件带过去随时可以开张不带任何私人物品也能干活。这套方案还能保证多台机器环境一致。我在本地的 Ubuntu 上写好了 docker-compose.yml测试没问题丢到线上的 CentOS、Debian 服务器上同样能跑起来基本不会出现“本地好好的线上就炸”的问题。1.2 系统选型Ubuntu 24.04 LTS 是首选服务器操作系统我首选Ubuntu 24.04 LTS。原因有三点第一Ubuntu 的 apt 源对 Docker 官方源支持最好官方安装文档直接提供apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin不用自己折腾依赖关系。第二24.04 LTS 的内核版本是 6.8对 cgroup v2、iptables、overlay2 这些 Docker 存储和网络底层组件的支持都很成熟很少出现内核模块加载失败的问题。第三Ubuntu 社区活跃你遇到的 Docker 报错90% 都能在网上搜到现成的解决方案。如果你手头是 CentOS 7 这类老系统建议优先考虑重装成 Ubuntu省下的折腾时间远超重装成本。1.3 Docker Engine 与系统资源的取舍很多人一开始容易混淆Docker Desktop 是给 Windows/macOS 上的开发者用的带图形界面、自带虚拟机而我们做服务器部署用的应该是Docker Engine它是以守护进程方式运行在 Linux 系统里的资源占用极小纯命令行操作。服务器上跑 Docker Engine containerd 几个常用容器空载状态下内存占用也就 100~200MB 左右对于 2GB 内存的小服务器来说完全没压力。我刚买的那台 2C2G 的小主机跑起来 MySQL 8.0分配 512MB加 Redis 主从加青龙面板内存还能剩下 400MB 多非常够用。2. 核心细节解析与实操要点2.1 服务器底座的系统初始化拿到云服务器或者物理机以后第一步不是急着装 Docker而是先把系统的“底子”打好。这一步做得好后面能省掉很多不必要的坑。我一般会做这么四件事更新系统软件源并升级现有软件包apt update apt upgrade -y这步是为了让系统处于最新状态避免安装 Docker 时出现依赖冲突设置主机名和时区用timedatectl set-timezone Asia/Shanghai把时区改成北京时间。很多容器默认用的是 UTC 时区如果主机时区不对容器里的日志时间也全是乱的配置时间同步。Docker 容器默认共享宿主机的网络命名空间和内核时钟如果宿主机时间不准容器里跑的任何业务都会受影响比如登录票据校验失败、定时任务执行时间错乱。我强烈建议装一个 NTP 服务apt install chrony -y然后systemctl enable --now chronyd用公共 NTP 服务器做时间校准调整防火墙规则。如果你用iptables 或者 ufw得提前看清默认策略。很多云服务器安全组默认只放行 22 端口你提前把 80、443 和容器映射的端口放行了免得后面容器的端口映射好了外部访问却一直超时。2.2 Docker 安装官方源一步到位系统初始化好以后就能装 Docker 了。我更推荐用 Docker 官方提供的 apt 源安装而不是直接用系统自带源好处是版本最新、bug 修复及时。操作顺序是这样的# 安装依赖工具 apt install apt-transport-https ca-certificates curl gnupg lsb-release -y # 添加 Docker 官方 GPG 密钥 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # 添加 Docker apt 源 echo deb [archamd64 signed-by/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | tee /etc/apt/sources.list.d/docker.list /dev/null # 更新并安装 apt update apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y这里有个坑要注意如果服务器在国内download.docker.com可能访问不稳定安装就可能卡在curl这一步。我自己的处理办法是把 GPG 密钥和 apt 源地址换成一个国内可正常访问的公共镜像源具体根据你可以访问的网络环境选。装完以后执行systemctl enable --now docker然后docker version看到客户端和服务端版本都有输出说明 Docker 已经活了。2.3 配置镜像加速解决拉取超时服务端能跑起来只是第一步紧接着就会遇到“拉镜像拉不动”的经典问题。docker pull mysql:8.0半天没动静甚至直接超时非常打击信心。解决办法是配置 registry mirror也就是镜像加速器。你需要在/etc/docker/daemon.json里加上这样一个配置{ registry-mirrors: [https://你自己的加速地址], log-opts: { max-size: 10m, max-file: 3 } }注意两点第一registry-mirrors的地址一定要填你能正常访问的公共镜像加速地址。不同网络环境下可用性不一样建议先curl -I探一下通不通再写进配置文件。改完配置文件必须执行systemctl restart docker才生效。第二我顺手把容器的日志大小限制加上了。不然 Docker 默认把容器 stdout 日志全部保留跑几个月的 MySQL 日志文件能把磁盘塞满严重时整个服务器都会卡死。max-size: 10m表示单日志文件最大 10MB超出自动轮转这是我在生产环境里踩过磁盘满的坑之后才加上的。2.4 docker-compose 的统一编排装完 Docker Engine 以后我建议再装 docker-compose-plugin这样就能用docker compose命令直接读 YAML 文件把所有容器的启动参数、网络、数据卷集中到一个文件里管理。为什么推荐 compose 而不是一个个docker run因为容器一多命令参数的记忆成本就上来了而且容器之间的网络连接还得自己建 bridge 网络非常麻烦。用 compose 以后不同服务之间只要通过服务名就能互相访问比如 PHP 容器里连 MySQL主机名直接写服务名mysql不用记 IP非常方便。一个最小的docker-compose.yml长这样version: 3.8 services: mysql: image: mysql:8.0 container_name: mysql8 restart: always ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: yourpassword volumes: - ./mysql-data:/var/lib/mysql networks: - app-net networks: app-net: driver: bridge后续加服务比如加 Redis、加 Nginx只需要在services:下面补一段配置即可维护成本非常低。3. 实操过程与核心环节实现3.1 用容器跑 MySQL 8.0 并配置远程访问MySQL 是几乎每个服务器都躲不开的组件。在 Docker 里跑 MySQL 8.0有几个关键点必须处理好否则你会在远程连接和数据持久化上反复踩坑。先说我现在的标准配置mysql-compose.ymlservices: mysql8: image: mysql:8.0 container_name: mysql8 restart: always ports: - 3306:3306 environment: TZ: Asia/Shanghai MYSQL_ROOT_PASSWORD: 你的强密码 command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci - --default-authentication-pluginmysql_native_password volumes: - /data/mysql/conf:/etc/mysql/conf.d - /data/mysql/data:/var/lib/mysql networks: - app-net networks: app-net: driver: bridge这里有几个容易踩的坑逐个说持久化数据目录必须挂载出来。如果容器是你docker run临时起的里面产生的数据只存在容器的可写层。一旦容器被删除比如升级镜像、误操作docker rm数据也跟着没了。我吃过一次亏某次docker-compose down -v把整个 MySQL 数据卷一起删掉几十条业务数据直接蒸发。所以我建议所有有状态服务数据目录一定要挂载到宿主机。远程连接的问题。很多人连不上 MySQL 第一反应是去调容器内部的授权表其实不对。容器外面连不上90% 是宿主机防火墙/安全组没放行 3306 端口或者端口映射没写对。先在宿主机上ss -lntp | grep 3306确认端口在监听再去云控制台检查安全组规则最后才考虑 MySQL 内部的 root 用户是不是只允许 localhost 登录。MySQL 8.0 的认证插件。如果你是拿 5.7 时期的客户端工具去连 8.0 的库经常会报Authentication plugin caching_sha2_password cannot be loaded。这个报错国外的 Stack Overflow 上一堆人问一句话解释MySQL 8.0 默认认证插件改成了 caching_sha2_password老版的客户端不支持。所以我直接在启动命令里把默认插件强制指回mysql_native_password这样 Navicat、老项目的 JDBC 驱动都能直接兼容。3.2 Redis 主从复制一行配置实现高可用Redis 在 Docker 里跑起来比 MySQL 简单得多但很多人的需求不只是“能跑”而是“主从复制”也就是一台 Redis 写入数据另外一台自动同步备份。我在服务器上实际搭的 Redis 主从结构是这样的services: redis-master: image: redis:7.0 container_name: redis-master restart: always ports: - 6379:6379 command: [redis-server, --requirepass, 主库密码, --appendonly, yes] volumes: - /data/redis-master:/data networks: - app-net redis-slave: image: redis:7.0 container_name: redis-slave restart: always ports: - 6380:6379 command: sh -c sleep 5 redis-server --port 6379 --replicaof redis-master 6379 --masterauth 主库密码 --appendonly yes depends_on: - redis-master volumes: - /data/redis-slave:/data networks: - app-net这里最核心的就是从库的--replicaof redis-master 6379和--masterauth 主库密码。前者是告诉从库“你去 6379 端口找主库”后者是同步时需要的认证密码不写的话第一次握手就直接失败。为什么redis-slave启动命令里要加sleep 5因为容器编排里depends_on只保证主库容器起来了不保证主库内部已经能接受连接。不加延迟的话从库可能一启动就发现主库连不上然后不断重试。虽然 Redis 本身有重试机制但加了延迟会让日志干净很多也不容易误判为故障。这套配置跑起来以后我在主库写入数据从库几毫秒内就能拉到复制延迟用redis-cli info replication的master_repl_offset一对比就能看出来。实际体验非常稳。3.3 青龙面板与依赖管理小内存机器的利器青龙面板的部署是我最近经常被问到的。很多做自动化任务的朋友手头并没有独立服务器就是一台便宜的小 VPS2GB 内存已经算大方了。这种机器上你要再装一个 Python 环境再配一个 node 环境内存分分钟爆表。而青龙面板已经把这些运行环境全都打包进镜像了你只需要拉镜像跑起来即可。我用的是官方镜像启动方式很简单services: qinglong: image: whyour/qinglong:latest container_name: qinglong restart: always ports: - 5700:5700 volumes: - /data/qinglong/config:/ql/config - /data/qinglong/log:/ql/log - /data/qinglong/db:/ql/db - /data/qinglong/scripts:/ql/scripts environment: - TZAsia/Shanghai问题来了很多用户跑完以后在面板里装依赖比如你要跑 Python 脚本得手动装requests、pandas点半天按钮没反应或者干脆报错。这个其实是历史老版本的通病。现在新版面板的依赖管理已经改成你直接选择“Python3 / Node.js / Linux”分类然后在对应分类下面写包名面板会在容器内部自动 pip/npm 安装。如果依赖安装卡住十有八九是网络问题。解决方法是给容器配置代理或调整镜像源但更简单粗暴的是把 pip 源和 npm 源都改成你能正常访问的镜像站点然后重启容器再试。3.4 Nginx 反向代理容器与宿主机端口规划多服务跑起来以后你就得面对另一问题怎么让这些容器对外提供服务直接暴露一堆乱七八糟的端口给用户既不安全也不优雅。所以我最后都会加一个 Nginx 容器做反向代理统一入口按域名转发到不同容器。比如我有三个服务一个 Web 站点走 80 端口一个青龙面板走 5700 端口一个 API 服务走 8080 端口。在宿主机上我只把 80 和 443 端口暴露出去其他服务全部关在 Docker 内网里通过 Nginx 容器中转。Nginx 容器部署起来也很简单services: nginx: image: nginx:1.27-alpine container_name: nginx restart: always ports: - 80:80 - 443:443 volumes: - /data/nginx/conf.d:/etc/nginx/conf.d - /data/nginx/html:/usr/share/nginx/html - /data/nginx/logs:/var/log/nginx networks: - app-net然后/data/nginx/conf.d目录下放一个站点配置文件把请求按域名转发到对应的服务名加端口上server { listen 80; server_name ql.example.com; location / { proxy_pass http://qinglong:5700; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }注意proxy_pass里用的是qinglong:5700而不是宿主机 IP 加端口。这是因为 Nginx 容器和青龙容器都在同一个 Docker 自定义网络app-net中Docker 自带的 DNS 解析会把qinglong直接解析到对应的容器 IP这个机制非常好用不需要你去查容器 IP 再写死。4. 常见问题与排查技巧实录4.1 “Virtualization support wasn’t detected” 类报错的真相这个报错通常出现在 Docker Desktop 的用户身上典型的就是在 Windows 上安装 Docker Desktop 后启动时弹窗提示 virtualization support 没有被检测到。这个报错跟我这篇文章主题不完全一样但既然热搜里出现频率很高还是值得说清楚底层的原理。本质上Docker Desktop 在 Windows 上并不是直接跑的 Linux 容器而是要在 Hyper-V 或者 WSL2 里建一个轻量级 Linux 虚拟机再在虚拟机里跑容器。如果你的 CPU 虚拟化功能没开BIOS/UEFI 里 Intel VT-x 或 AMD-V 被禁用了或者 Windows 的 Hyper-V 功能没启用那 Docker Desktop 自然就起不来。要排查的话按这个顺序来重启进 BIOS找到 CPU 虚拟化相关的设置Intel 平台一般是Intel Virtualization TechnologyAMD 平台一般是SVM Mode改成 EnabledWindows 里执行systeminfo看输出的最后一段有“Hyper-V 要求”字样如果显示“检测到虚拟机监控程序。将不显示 Hyper-V 所需的功能。”说明虚拟化已经是开启状态如果 BIOS 也开了那可能是 Windows 的虚拟机平台功能没启用打开“启用或关闭 Windows 功能”把“虚拟机平台”和“适用于 Linux 的 Windows 子系统”两个选项勾上重启系统。如果你跟我一样是直接在 Linux 服务器上跑 Docker Engine这个报错基本不会出现因为你已经在真机内核上运行不需要额外的虚拟化层。4.2 容器端口拉通但外部访问不通这个问题几乎每个新手都会遇到具体表现是容器起来了日志正常宿主机上curl内网地址也通但公网就是访问不了。排查步骤我建议按这个顺序确认端口映射docker ps看 PORTS 列要显示0.0.0.0:3306-3306/tcp才说明宿主机端口确实转发到了容器确认监听地址ss -lntp | grep 3306看监听地址是不是 0.0.0.0如果监听在 127.0.0.1那只能在本地访问放行云安全组这是最容易被忽视的一步。很多云厂商默认安全组只放行 22、80、4433306 之类的端口全被拦住了宿主防火墙iptables -L -n看一下 OUTPUT/INPUT 链规则必要时临时放行测试。另外还有一个我踩过多次的坑排查防火墙时用systemctl stop firewalld停掉防火墙但 iptables 规则是 Docker 自己生成的容器网络本身依赖 iptables 做 DNAT如果你过度清理 iptables反而会让 Docker 网络直接瘫痪。正确做法是你只放行你需要的外部端口而不是把所有 iptables 规则都清掉。4.3 Docker 磁盘占满与日志膨胀处理说了很多部署最后聊一个运维上的持久战问题磁盘。Docker 容器跑的时间长了镜像、日志、数据卷会不断膨胀。特别是那些不设日志轮转的容器比如 Nginx 每天产生的访问日志几个月不清理轻松占满几十 GB 硬盘。我自己的服务器上出现过一次磁盘 100% 导致 MySQL 直接崩溃的经历教训太深刻了。日常建议做三件事在daemon.json里配日志轮转前面已经提到这是最根本的办法定期用docker system df查看磁盘占用分布看看是退出的容器太多还是悬空镜像太多定期清理docker system prune -af这条命令会删除所有停止的容器和悬空镜像但注意别加-v加了会把数据卷一起删掉我前面已经吃过这个亏了。4.4 常见问题速查现象原因处理方式容器启动后立即退出启动命令/环境变量有误docker logs 容器名查看日志重点看最后几行报错远程连不上 MySQL端口未放行 / 认证插件不兼容按 4.2 排查四步走必要时切换为 mysql_native_passwordRedis 从库无法同步主库有密码但从库没配 masterauth在从库启动命令中加--masterauth 主库密码拉镜像超时失败官方源访问不稳定配置 registry-mirrors改完务必systemctl restart docker容器时间比北京时间早/晚 8 小时容器时区没设置环境变量加TZAsia/Shanghai或挂载/etc/localtime磁盘空间被日志占满Docker 容器日志无限增长daemon.json 里配置 max-size/max-file然后清理旧日志写在最后折腾了这么久我个人最大的体会就是服务器的价值在于稳定运行而不是让你天天折腾它的系统。用 Docker 把所有服务关进容器里主机保持干净出问题直接删容器重建连系统都不用重装这种安全感和自由度是裸机部署永远给不了的。如果你一开始觉得 compose 文件麻烦不妨就从最简单的 MySQL 容器开始上手跑通了再逐步加服务。等你习惯了“改配置、重启容器、看日志”这套循环大概就能理解为什么那些老运维都爱说一句话能容器化的别裸跑能编排的别手敲命令。这套玩法现在还远没到上限K8s、Docker Swarm 以及各种云原生工具都在等你去尝试但先把一台干净、稳定的 Docker 服务器跑起来绝对是值得的第一步。
阅读完成 · 觉得有帮助?
咨询建站