1. 动手之前,先确认CentOS版本和内核这些底层问题
1.1 在CentOS 7.x和Stream之间做选择
先说一个我踩过的坑。有次我拿到一台新服务器,看官方文档直接照搬CentOS 8的安装命令,结果yum源报错不说,连docker-ce的仓库都拉不下来。后来才意识到,CentOS 8停止维护之后,仓库地址全变了,网上大量教程还停留在旧版本阶段。
目前生产环境里最常见的组合是CentOS 7.9 + Docker CE,这套组合经过大量线上环境验证,社区资料最全,遇到问题基本都能搜到答案。CentOS 7.9的内核是3.10.0系列,Docker官方对这版内核有专门适配,overlay2存储驱动可以正常工作,iptables的兼容性也经过长期打磨。
CentOS Stream 9则不一样,虽然内核升级到了5.14以上,但对Docker CE的支持反而更别扭。主要原因在于CentOS Stream 9默认使用nftables作为防火墙后端,而Docker的网络模式(尤其是端口映射和容器间通信)对iptables的依赖很深,安装完Docker后经常出现容器网络不通的情况。如果你只是学习或者非生产环境,CentOS Stream 9可以玩,但真要部署业务,我还是建议老实使用CentOS 7.9。
另外还要注意系统架构问题。Docker要求64位系统,x86_64架构的兼容性最好,ARM架构(比如鲲鹏、飞腾)虽然也能装,但部分镜像没有ARM版本,或者运行时有性能损耗。用uname -m看一眼,返回x86_64就可以继续。
1.2 内核版本对Docker运行时的直接影响
很多新手忽略了一个关键点:Docker不是纯用户态软件,它依赖内核特性来隔离资源。cgroups(资源限制)、namespaces(进程隔离)、overlayfs(镜像分层)这些机制都跟内核版本强相关。
CentOS 7.x虽然内核版本号是3.10,但Red Hat在编译时给内核打了大量补丁,实际上对容器特性的支持比较完整。真正要注意的是,不要在内核版本很老的机器上强行安装新版Docker。举个例子,Docker 20.10之后对内核有一些新要求,如果uname -r显示内核低于3.10,建议先升级内核再装Docker,否则用docker info检查时会看到警告,甚至直接无法启动。
还有个容易忽略的点是SELinux。CentOS默认开启SELinux(Enforcing模式),Docker官方虽然支持SELinux,但在某些场景下(比如挂载数据卷时)会遇到权限限制。如果你对SELinux不熟悉,最简单的做法是把它设置为Permissive模式,命令是setenforce 0,然后修改/etc/selinux/config把SELINUX=enforcing改成SELINUX=permissive。当然,如果你的安全要求很高,可以在SELinux开启状态下跑通Docker,只是排查问题时多一个变量。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从yum源到docker run的完整安装链路
2.1 清理旧版本和基础依赖
安装新版本之前,先检查系统里有没有残留的旧版Docker或相关组件。不同版本的Docker组件名称不一样,老版本叫docker、docker-engine,新版本叫docker-ce。如果你不确定有没有装过,直接执行:
bash复制yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
系统会提示这些包不存在,没关系,这本来就是个清理动作,确保环境干净。
接着安装yum-utils和device-mapper-persistent-data,前者提供yum-config-manager命令,用来管理yum源,后者是Docker存储驱动依赖的底层工具。
bash复制yum install -y yum-utils device-mapper-persistent-data lvm2
lvm2是逻辑卷管理工具,Docker的devicemapper存储驱动会用到,虽然现在默认推荐overlay2,但装上有备无患。
2.2 配置docker-ce的yum源
这是整个安装过程中最容易出问题的环节。Docker官方源地址是https://download.docker.com/linux/centos/docker-ce.repo,但国内服务器直接访问这个地址经常超时或速度极慢,这时候就要用镜像站替换。
清华镜像站和阿里云的配置方式类似,以清华源为例:
bash复制yum-config-manager --add-repo https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/docker-ce.repo
配置完务必执行yum makecache fast生成缓存。这里有个细节:如果你用的是CentOS 7,执行完yum-config-manager后会生成一个/etc/yum.repos.d/docker-ce.repo文件,打开看baseurl会是https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/7/x86_64/stable,确认路径里带7就行。
还有一点,检查一下/etc/yum.repos.d/下的其他repo文件,如果系统里有多个repo源且配置有冲突,安装时可能出现"base"和"extras"源版本不一致的问题。一般新装的CentOS不会有这个问题,但如果是别人交接的服务器,建议先yum repolist看一眼仓库列表。
2.3 安装Docker引擎三件套
Docker CE的安装包拆成了三个核心组件:docker-ce(引擎主程序)、docker-ce-cli(命令行工具)、containerd.io(容器运行时)。
bash复制yum install -y docker-ce docker-ce-cli containerd.io
为什么要单独拆出containerd.io?因为Docker底层的容器生命周期管理已经移交给了containerd,docker daemon只负责上层API、镜像管理、网络配置这些事情。理解这个关系对排查问题很重要:有时候容器起不来,报错信息里会带containerd字样,这时候要检查systemctl status containerd,而不是只盯着docker服务本身。
安装过程中如果提示需要接受GPG密钥,直接确认即可。
2.4 启动服务并用hello-world验证
安装完成后启动Docker:
bash复制systemctl start docker
systemctl enable docker
然后用经典镜像验证:
bash复制docker run hello-world
这条命令的执行链路是这样的:docker客户端把请求发给daemon,daemon检查本地没有hello-world镜像,于是从registry拉取,然后交底给containerd去创建并启动容器,容器启动后输出一段欢迎信息,执行完自动退出。
如果看到Hello from Docker!的输出,说明整套链路已经通了。如果卡在拉取镜像这步,大概率是网络问题,我在下一节详细说解决方案。
3. 镜像加速配置:这一步没做,后续全卡在拉取镜像上
3.1 daemon.json里不只是mirrors
Docker装好当天我就急着拉MySQL镜像,结果上百MB的镜像下载慢得像蜗牛,断断续续下了一个多小时还没完。后来配置好镜像加速才发现,下载时间从一小时缩短到几分钟。
修改Docker的守护进程配置文件/etc/docker/daemon.json,如果文件不存在就新建:
json复制{
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com"
],
"data-root": "/data/docker",
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
},
"exec-opts": ["native.cgroupdriver=systemd"]
}
这里有几个配置项值得展开说:
data-root,Docker默认把所有镜像、容器、数据卷都存在/var/lib/docker。如果系统盘只有50GB,而你的业务镜像动辄好几个GB,很快就把根分区撑爆。最好把data-root指向数据盘,比如/data/docker,注意目录要提前建好,重启Docker后原来在/var/lib/docker下的旧数据不会自动迁移,所以这个配置最好在装完Docker、还没拉镜像时就设置好。
log-driver和log-opts是控制容器日志的。容器默认把日志写到json-file里,如果不限制大小,一个高频写日志的容器几天就能吃掉几个GB磁盘空间。配置max-size: 100m和max-file: 3的意思是每个日志文件最大100MB,最多保留3个文件,即日志总量上限300MB。我见过不少线上事故就是日志把磁盘写满导致Docker挂掉,这个配置强烈建议加上。
exec-opts里的native.cgroupdriver=systemd是在CentOS 7上推荐的配置。Docker默认的cgroup驱动是cgroupfs,而systemd自己也管cgroup,两套并存可能导致资源限制不生效甚至节点不稳定。统一成systemd能避免kubelet和Docker的cgroup冲突,即使你现在不跑Kubernetes,也建议加上这个配置。
修改完成后:
bash复制systemctl daemon-reload
systemctl restart docker
3.2 为什么配置了加速还是慢
我见过不少人配完镜像加速后抱怨"还是慢",排查下来主要有几个原因:
第一,配置完没重启Docker。daemon.json是Docker启动时读取的,只改文件不重启不会生效。用docker info看一下Registry Mirrors这一栏,如果显示的还是空,说明配置没加载。
第二,镜像本身太大。比如MySQL 8.0的镜像解压后有500多MB,Python 3.9的镜像加上各种依赖也能到1GB,这类大镜像在高峰期下载速度就是会慢,加速器也不是万能的。
第三,registry-mirrors的格式问题。必须是https://开头的完整URL,不能直接填域名。
第四,有些镜像没有走加速器。比如你直接docker pull k8s.gcr.io/xxx,这种第三方registry的镜像不会被registry-mirrors拦截,加速器只能加速Docker Hub官方镜像。拉第三方仓库的镜像,要么直接用对应仓库的地址,要么配置insecure-registries信任私有仓库。
配置加速之后用docker pull busybox这种几十MB的小镜像测试,如果速度还是几十KB/s,再考虑是不是服务器出网带宽本身的问题。
4. 安装完之后的常用操作与数据持久化习惯
4.1 日常管理命令和容器日志处理
Docker装完不是终点,日常运维才是大头。先列几个我高频使用的命令:
bash复制# 查看所有容器(包含已退出)
docker ps -a
# 查看容器日志(最近100行,持续跟踪)
docker logs --tail 100 -f 容器名
# 进入容器的交互式Shell
docker exec -it 容器名 bash
# 查看容器资源占用
docker stats
# 查看镜像、容器、数据卷占用的磁盘
docker system df
docker system df可能很多人不常用,但排查磁盘空间问题时特别好用。它会分门别类显示镜像、容器、数据卷、构建缓存各占了多少空间。构建缓存这个最坑,反复构建镜像时旧缓存不清理,动辄几个GB。清理命令是docker system prune,但注意这个命令会删掉所有停止的容器、没用到的网络和悬空镜像,执行前确认一下没有需要保留的中间产物。
容器日志还有一个硬性习惯:不要用docker logs -f挂在后台长期监控日志。原因是docker logs在日志量大时非常消耗daemon性能,而且它只能看stdout/stderr,看不到应用写进文件里的日志。正确做法是让容器把日志写到挂载目录的文件里,用tail -f去跟日志文件,或者直接接入ELK这类日志系统。
4.2 挂载数据目录,别把容器当虚拟机用
新手最容易犯的错误是把容器当成一台小虚拟机,什么东西都放在容器内部。容器是临时性的,删除重建之后容器里的数据就没了。正确的姿势是:无状态的应用代码打进镜像,有状态的数据放到挂载卷里。
Docker的数据持久化有两种主要方式。一种是bind mount,直接把宿主机目录挂到容器路径:
bash复制docker run -d \
-v /data/nginx:/usr/share/nginx/html \
-p 80:80 \
nginx
这种方式的优势是直观,你直接在宿主机/data/nginx里放文件,容器里马上就能看到,适合配置文件、静态页面、导出文件这类场景。
另一种是volume,用docker volume create创建,然后挂载:
bash复制docker volume create mysql_data
docker run -d \
-v mysql_data:/var/lib/mysql \
mysql:8.0
volume的优点是数据完全由Docker管理,不受宿主机目录权限影响,备份恢复也方便。缺点是数据实际存在/data/docker/volumes/下面,不直观。
我的建议是:数据库类应用用volume或绑定挂载都行,只要保证目录权限正确;配置文件建议用bind mount,方便修改后docker restart生效;日志文件也建议bind mount到宿主机,方便采集。
启动容器时还要考虑--restart策略。不加这个参数,服务器重启后容器不会自动拉起,对于生产环境这是不可接受的。推荐设置:
bash复制--restart=always
这个策略的含义是:无论容器是正常退出还是异常崩溃,Docker守护进程都会自动重启它,而且Docker服务重启时也会跟随启动。如果某些容器不想自动重启,可以用--restart=unless-stopped,差别在于手动停止的容器不会在Docker重启时自动拉起。
4.3 用docker compose管理多容器应用
单容器用docker run没问题,但应用一多起来(Nginx + MySQL + Redis + 应用服务),一个个敲启动命令很容易出错,而且服务间的网络配置、依赖关系也不好管理。这时候就该上docker compose。
CentOS 7上安装docker compose插件有两种方式。Docker官方推荐的方式:
bash复制yum install -y docker-compose-plugin
装完后验证:docker compose version。注意是新版的docker compose命令(带空格),老版本的docker-compose(带横线)是Python写的独立工具,CentOS 7通过EPEL源也能装,但功能和新版有差异,建议直接用插件版。
一个典型的compose文件长这样:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: myapp-mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: myapp
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:7
container_name: myapp-redis
restart: always
volumes:
- redis_data:/data
ports:
- "6379:6379"
app:
build: .
container_name: myapp-server
restart: always
depends_on:
- mysql
- redis
ports:
- "8080:8080"
volumes:
mysql_data:
redis_data:
在工程目录下执行docker compose up -d就会自动构建、拉取镜像、创建网络、启动所有服务。depends_on确保MySQL和Redis先启动,虽然这个依赖只保证启动顺序,不保证服务ready,但对于绝大多数场景够用了。
5. 结合热搜场景:MySQL 8.0、Redis主从、GitLab这样部署
5.1 MySQL 8.0容器化部署与远程连接
MySQL容器化部署有几个关键点,配置不对会遇到各种诡异问题。
下面是一个可以直接抄的启动命令:
bash复制mkdir -p /data/mysql/{data,logs,conf}
docker run -d \
--name mysql8 \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=YourPassword123 \
-e TZ=Asia/Shanghai \
-v /data/mysql/data:/var/lib/mysql \
-v /data/mysql/logs:/var/log/mysql \
-v /data/mysql/conf:/etc/mysql/conf.d \
--restart=always \
mysql:8.0
几个环境变量和数据卷的作用解释一下:
MYSQL_ROOT_PASSWORD是MySQL官方镜像的初始化机制,首次启动时会创建root用户并设置密码。如果这个目录之前已经有数据,这个变量会被忽略,所以修改root密码的正确方式是进入容器后用SQL命令改,不是改这个环境变量重启。
TZ=Asia/Shanghai设置时区,不设的话容器内默认是UTC时间,比北京时间慢8小时。应用记录日志或比较时间时就会对不上。
数据卷挂载要注意目录权限。MySQL容器内的mysql用户UID是999,所以宿主机/data/mysql/data的所有者要设为UID 999:
bash复制chown -R 999:999 /data/mysql/data
如果不设置,MySQL容器启动时会报"Permission denied"而退出。这个坑我帮人排查过好几次,症状都一样:docker logs mysql8里看到[ERROR] Failed to open file‘/var/lib/mysql/...’。
启动后用Navicat或mysql客户端远程连接测试。连接失败最常见的原因是:一是防火墙没放行3306端口,CentOS 7默认firewalld是开启的,需要firewall-cmd --permanent --add-port=3306/tcp && firewall-cmd --reload;二是云服务器的安全组规则没放开3306端口;三是MySQL 8.0默认的认证插件是caching_sha2_password,部分旧版本的客户端工具不支持,验证方式不支持会直接报"Authentication plugin 'caching_sha2_password' cannot be loaded"。如果确实需要兼容旧客户端,可以在容器里执行SQL把root用户的认证插件改回mysql_native_password,但我建议还是升级客户端更简单。
5.2 Redis主从复制:两条docker run命令搞定
Redis容器化部署比MySQL简单很多,但主从复制的配置细节还是要留意。
先准备好主库和从库的配置文件:
bash复制mkdir -p /data/redis/{master,slave}
主库的master.conf:
conf复制# 允许任意IP连接
bind 0.0.0.0
# 开启AOF持久化
appendonly yes
# 设置密码
requirepass Redis@2024
# 主从同步也要求密码
masterauth Redis@2024
从库的slave.conf,核心是replicaof这行,注意CentOS 7仓库自带的redis版本可能是3.x,只认slaveof而不是replicaof,但如果用Docker镜像跑Redis 7就不存在这个问题:
conf复制bind 0.0.0.0
appendonly yes
requirepass Redis@2024
masterauth Redis@2024
replicaof 宿主机IP 6379
然后分别启动:
bash复制docker run -d \
--name redis-master \
-p 6379:6379 \
-v /data/redis/master/redis.conf:/etc/redis/redis.conf \
--restart=always \
redis:7 redis-server /etc/redis/redis.conf
docker run -d \
--name redis-slave \
-p 6380:6379 \
-v /data/redis/slave/redis.conf:/etc/redis/redis.conf \
--restart=always \
redis:7 redis-server /etc/redis/redis.conf
注意从库端口映射是6380:6379,宿主机6380端口转发到容器的6379端口。启动后验证主从关系:
bash复制docker exec -it redis-slave redis-cli -a Redis@2024 info replication
看到role:slave并且master_link_status:up就说明同步成功。如果状态一直是down,最常见的坑有:从库配置里的replicaof写成了容器的名字而不是宿主机IP(两个容器分别处于不同网络时要能互相访问,用--link或自定义bridge网络,但更简单的方案是直接填宿主机IP);主库设置了requirepass但从库没设置masterauth;从库连不上主库的6379端口,检查防火墙。
5.3 GitLab部署:内存是硬指标
GitLab这个家伙很吃资源,官方建议至少4GB内存,1GB内存跑起来会让你怀疑人生。如果你的服务器只有2GB内存,建议直接在服务器上开2G swap,否则安装过程中就会OOM。
部署命令:
bash复制docker run -d \
--name gitlab \
-p 8443:443 \
-p 8082:80 \
-p 2222:22 \
-v /data/gitlab/etc:/etc/gitlab \
-v /data/gitlab/log:/var/log/gitlab \
-v /data/gitlab/data:/var/opt/gitlab \
--restart=always \
--shm-size 256m \
gitlab/gitlab-ce:latest
注意几个关键点:
端口映射里,宿主机22端口通常被sshd占用,所以把容器的22端口映射到宿主机的2222。这意味着GitLab的SSH克隆地址会变成ssh://git@服务器IP:2222/group/project.git,需要在GitLab管理界面或/etc/gitlab/gitlab.rb里把gitlab_rails['gitlab_shell_ssh_port'] = 2222配置好,否则Web界面上显示的克隆地址没有端口号,照着点克隆会失败。
首次启动GitLab初始化非常慢,日志会长时间停在"Services"的启动循环里,5分钟左右才起来都算正常。用docker logs -f gitlab观察,看到GitLab is ready!字样才算初始化完成。
初始root密码不一定是网上教程说的那个路径。新版本GitLab首次启动时会随机生成一个密码存放在/data/gitlab/etc/initial_root_password文件里,24小时有效。如果错过这个时间窗口,可以在容器里执行:
bash复制docker exec -it gitlab gitlab-rake "gitlab:password:reset"
按提示重置。
5.4 用Dockerfile构建你自己的应用镜像
部署别人的镜像只是Docker的一半能力,另一半是把你自己写的代码打包成镜像。以Java Spring Boot应用为例,一个最简Dockerfile:
dockerfile复制FROM openjdk:8-jre-alpine
WORKDIR /app
COPY app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
然后在项目根目录执行:
bash复制docker build -t myapp:v1 .
docker run -d -p 8080:8080 --name myapp myapp:v1
如果是用IDEA的话,社区版需要装Docker插件,安装后配置远程连接服务器上的Docker daemon,勾选"Expose daemon on tcp://localhost:2375 without TLS",然后右键Dockerfile点Run就能直接在服务器上构建镜像。但这种直接暴露2375端口的方式有安全隐患,生产环境建议走TLS证书认证。
构建镜像时几个实用技巧:
.dockerignore文件一定要写,把target目录、.git目录、日志文件排除掉,既减少构建上下文大小,又避免把敏感信息带进镜像。- 基础镜像尽量用alpine版本,体积能小好几倍。比如
openjdk:8-jre-alpine只有100MB多,而完整的openjdk:8-jdk要500MB。 - 构建时用
--no-cache虽然慢,但能避免缓存导致的依赖不一致问题,尤其适合排查"昨天还能跑今天就不行"的诡异问题。
构建好镜像后,用docker image ls查看镜像大小,如果发现镜像特别大(超过1GB),大概率是基础镜像选错了,或者把编译工具链也打进镜像了。这时候用多阶段构建,第一阶段编译,第二阶段只拷贝产物:
dockerfile复制FROM maven:3.8-jdk-8 AS build
COPY . /workspace
WORKDIR /workspace
RUN mvn clean package -DskipTests
FROM openjdk:8-jre-alpine
COPY --from=build /workspace/target/app.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
这种方式最终镜像不包含Maven和JDK编译环境,体积能控制在200MB以内。
到现在为止,CentOS上装Docker、配加速、部署数据库和中间件、打包自己的应用,这条链路已经完整跑通了。后面再遇到问题,第一反应不应该是搜"xx命令怎么执行",而是docker logs看日志、docker inspect看配置,用Docker自己提供的工具链去定位问题。
