Docker
Docker 学习笔记整理版
本文档由原始 Docker 笔记重新整理而来,主要做了这些处理:清理 HTML/字体标签、统一标题层级、把命令整理为代码块、按学习路径重新分章,并对部分高风险示例做了占位符处理。
注意:原文中的示例密码已替换为 <MYSQL_ROOT_PASSWORD>,镜像仓库地址、宿主机 IP 等也尽量使用占位符表示。实际执行时请替换成自己的真实值。
#1. Docker 架构
Docker 主要由以下几个部分组成:
- Docker Client:客户端,负责向 Docker daemon 发送命令。
- Docker daemon / dockerd:Docker 守护进程,负责管理镜像、容器、网络、数据卷等对象。
- Docker Image:镜像,容器运行的模板。
- Docker Container:容器,由镜像启动出来的运行实例。
- Docker Network:容器网络。
- Docker Volume:容器数据持久化方案。
- Docker Registry:镜像仓库,例如 Docker Hub、阿里云 ACR、私有 Registry。
Docker 客户端和 Docker 守护进程之间通过 Docker API 通信。通信方式通常是 UNIX Socket,也可以是 TCP 网络接口。
常见 Docker 客户端包括: - Docker CLI
- Docker Desktop
- Docker Compose
- 各语言的 Docker API Client SDK
#2. Docker 镜像与 Dockerfile
#2.1 使用 Dockerfile 构建镜像
Dockerfile 是用来构建 Docker 镜像的文本文件,由一条条指令组成。
构建流程:
编写 Dockerfile -> docker build 构建镜像 -> docker run 启动容器
示例:基于 Ubuntu 镜像安装 net-tools 和 iputils-ping。
1FROM ubuntu:latest2 3RUN apt-get update \4 && apt-get install -y net-tools iputils-ping \5 && apt-get clean \6 && rm -rf /var/lib/apt/lists/*7 8CMD ["/bin/bash"]构建镜像:
1docker build -t ccby/ubuntu .查看镜像构建历史:
1docker image history ccby/ubuntu#2.2 Dockerfile 基础规则
- Dockerfile 指令通常使用大写,例如 FROM、RUN、COPY、CMD。
- 第一条有效指令必须是 FROM。
- 指令按照从上到下顺序执行。
-
#表示注释。
- 每条指令都会创建一个新的镜像层。
#2.3 常用 Dockerfile 指令
| 指令 | 作用 |
|---|---|
| FROM | 指定基础镜像。 |
| RUN | 构建镜像时执行命令。 |
| COPY | 从宿主机复制文件到镜像中。 |
| ADD | 类似 COPY,额外支持自动解压和 URL。一般优先用 COPY。 |
| WORKDIR | 设置工作目录。 |
| ENV | 设置环境变量。 |
| EXPOSE | 声明容器监听端口。 |
| CMD | 容器启动时默认执行的命令。 |
| ENTRYPOINT | 容器入口命令,通常用于固定启动程序。 |
#2.4 镜像优化建议
选择更小的基础镜像
例如使用 Alpine:
1FROM alpine:latest2 3RUN apk add --no-cache python34COPY . /app5CMD ["python3", "/app/app.py"]合并 RUN 命令,减少镜像层
1RUN apk update \2 && apk add --no-cache python3 curl清理缓存
Debian / Ubuntu 系镜像:
1RUN apt-get update \2 && apt-get install -y python3 \3 && apt-get clean \4 && rm -rf /var/lib/apt/lists/*利用 Docker 构建缓存
Docker 构建镜像时会缓存每一层。建议:
- 不常变化的依赖安装步骤放在前面。
- 经常变化的业务代码复制步骤放在后面。
- 先复制依赖清单,例如 package.json、requirements.txt,再安装依赖,最后复制业务代码。
Node.js 示例:
1FROM node:20-alpine2 3WORKDIR /app4 5COPY package*.json ./6RUN npm ci --omit=dev7 8COPY . .9 10EXPOSE 300011CMD ["node", "server.js"]#3. Docker 常见命令
#3.1 查看 Docker 信息
1docker info2docker --help3docker system df#3.2 镜像命令
1# 查看本地镜像2docker images3 4# 查看所有镜像,包括中间层镜像5docker images -a6 7# 只显示镜像 ID8docker images -q9 10# 搜索镜像11docker search nginx12 13# 拉取镜像14docker pull nginx15 16# 删除镜像17docker rmi nginx:latest18 19# 强制删除镜像20docker rmi -f nginx:latest21 22# 删除多个镜像23docker rmi -f 镜像1:TAG 镜像2:TAG24 25# 删除全部镜像,慎用26docker rmi -f $(docker images -qa)#3.3 容器命令
1# 运行容器2docker run 镜像名3 4# 查看正在运行的容器5docker ps6 7# 查看所有容器,包括已经退出的容器8docker ps -a9 10# 查看最近创建的容器11docker ps -l12 13# 查看最近 n 个创建的容器14docker ps -n 515 16# 只显示容器 ID17docker ps -q18 19# 启动容器20docker start 容器ID或容器名21 22# 停止容器23docker stop 容器ID或容器名24 25# 强制停止容器26docker kill 容器ID或容器名27 28# 删除已停止容器29docker rm 容器ID或容器名30 31# 强制删除容器,包括正在运行的容器32docker rm -f 容器ID或容器名33 34# 查看容器日志35docker logs 容器ID或容器名36 37# 持续查看日志38docker logs -f 容器ID或容器名39 40# 查看容器详细信息41docker inspect 容器ID或容器名42 43# 查看容器资源使用情况44docker stats#3.4 容器文件复制与导入导出
1# 从容器复制文件到宿主机2docker cp 容器ID:/容器内路径 /宿主机路径3 4# 从宿主机复制文件到容器5docker cp /宿主机路径 容器ID:/容器内路径6 7# 导出整个容器文件系统8docker export 容器ID > nginx.tar9 10# 从导出的 tar 文件导入为镜像11cat nginx.tar | docker import - 用户名/镜像名:版本号#4. 容器运行、进入与退出
#4.1 docker run 常用参数
1docker run [参数] 镜像名 [命令]常用参数:
| 参数 | 作用 |
|---|---|
| --name | 指定容器名称。 |
| -d | 后台运行容器。 |
| -i | 交互模式。 |
| -t | 分配伪终端,通常和 -i 一起使用。 |
| -p | 指定端口映射。 |
| -P | 随机端口映射。 |
| -v | 挂载数据卷。 |
| --network | 指定容器网络。 |
| --restart | 设置重启策略。 |
| 端口映射示例: |
1# 宿主机 8080 -> 容器 802docker run -d -p 8080:80 nginx3 4# 指定监听地址5docker run -d -p 10.0.0.1:8080:80 nginx启动交互式 Ubuntu 容器:
1docker run -it --name ubuntu ubuntu /bin/bash#4.2 退出容器
进入交互式容器后:
exit
exit 会退出容器终端,并且如果该 shell 是容器主进程,容器也会停止。
如果希望退出终端但不停止容器,可以使用:
Ctrl + P,然后 Ctrl + Q
#4.3 exec 和 attach 的区别
进入正在运行的容器:
1docker exec -it 容器ID或容器名 /bin/bash或者:
1docker attach 容器ID或容器名区别:
| 命令 | 特点 |
|---|---|
| docker exec | 在容器中启动一个新的进程。退出该进程不会导致容器停止。推荐使用。 |
| docker attach | 进入容器主进程的终端。退出可能导致容器停止。 |
#4.4 为什么 Ubuntu 后台运行会自动停止
执行下面命令时:
1docker run -d ubuntuUbuntu 容器可能会马上退出。原因是 Ubuntu 镜像默认没有长期运行的前台进程。容器的生命周期依赖于主进程,主进程结束后容器就会退出。
解决方式:
1docker run -it --name my_ubuntu_container ubuntu /bin/bash或者让容器执行一个长期运行的命令:
1docker run -d --name my_ubuntu_container ubuntu tail -f /dev/null#5. 镜像分层与 docker commit
#5.1 为什么 Docker 镜像要分层
Docker 镜像由多层只读层组成,容器运行时会在镜像层之上增加一个可写层。
分层设计的好处:
- 共享和复用:多个镜像可以共享相同的基础层,节省磁盘空间。
- 快速部署:本地已有的镜像层不需要重复下载。
- 减少存储占用:更新镜像时只需要变更发生变化的层。
- 便于回滚:镜像层天然支持增量管理。
- 增量传输:推送和拉取镜像时只传输缺失层。
- 安全性更好:基础镜像层只读,容器只修改自己的可写层。
底层通常依赖联合文件系统,例如 OverlayFS、AUFS、Btrfs 等。
#5.2 docker commit
1docker commit 可以把当前容器的状态提交成一个新的镜像。语法:
1docker commit -m="提交描述" -a="作者" 容器ID 目标镜像名:标签示例:
1docker commit -m="vim cmd add ok" -a="caiyaobin" 628daf79a302 cyb/ubuntu:24.04.1注意:生产环境更推荐使用 Dockerfile 构建镜像,而不是长期依赖 docker commit。Dockerfile 更容易版本管理、复现和审计。
#6. 镜像仓库与镜像推送
#6.1 推送镜像到阿里云 ACR
基本流程:
- 登录阿里云容器镜像服务。
- 创建命名空间。
- 创建镜像仓库。
- 登录镜像仓库。
- 给本地镜像打 tag。
- 推送镜像。
示例:
1# 查看本地镜像2docker images3 4# 给镜像打 tag5docker tag 本地镜像ID registry.example.com/namespace/image_name:tag6 7# 推送镜像8docker push registry.example.com/namespace/image_name:tag9 10# 拉取镜像11docker pull registry.example.com/namespace/image_name:tag#6.2 搭建本地私有 Registry
拉取 Registry 镜像:
1docker pull registry创建数据目录:
1mkdir -p /caiyaobin/myregistry运行私有仓库:
1docker run -d \2 -p 5000:5000 \3 -v /caiyaobin/myregistry:/var/lib/registry \4 --name my-registry \5 registry提交容器为镜像:
1docker commit -m="vim cmd add" -a="cyb" 9d0b5683c0c8 ubuntu:24.04.1推送到本地私有仓库:
1docker tag ubuntu:24.04.1 127.0.0.1:5000/ubuntu:24.04.12docker push 127.0.0.1:5000/ubuntu:24.04.1#7. Docker 数据卷
#7.1 数据卷的作用
Docker 数据卷用于持久化容器数据,解决以下问题:
- 容器删除后数据仍然保留。
- 容器重建后可以继续使用原数据。
- 多个容器之间可以共享数据。
- 容器内数据可以备份到宿主机。
#7.2 挂载宿主机目录
1docker run -it \2 -v /宿主机绝对路径:/容器内路径 \3 镜像名示例:
1docker run -it \2 -v /data/test:/app/data \3 ubuntu /bin/bash#7.3 只读挂载
1docker run -it \2 -v /宿主机绝对路径:/容器内路径:ro \3 镜像名ro 表示容器内部只读,不能写入该挂载目录。
#7.4 SELinux 权限问题
如果在 CentOS / Rocky / RHEL 等系统中挂载目录后出现:
cannot open directory: Permission denied
常见原因是 SELinux 限制。
可以临时使用:
1docker run --privileged=true ...但 --privileged=true 会放大容器权限,不建议作为长期方案。更推荐根据实际情况使用:
1# 共享 SELinux 标签2docker run -v /宿主机目录:/容器目录:z 镜像名3 4# 私有 SELinux 标签5docker run -v /宿主机目录:/容器目录:Z 镜像名或者调整宿主机目录权限和 SELinux 上下文。
#7.5 卷的继承和共享
使用 --volumes-from 让一个容器继承另一个容器的数据卷:
1docker run -it \2 --volumes-from 父容器名或ID \3 --name 新容器名 \4 镜像名#8. Docker 网络
#8.1 查看与管理 Docker 网络
1# 查看 Docker 网络2docker network ls3 4# 创建网络,默认类型是 bridge5docker network create 网络名6 7# 创建指定网段的 bridge 网络8docker network create \9 --driver bridge \10 --subnet 192.168.50.0/24 \11 my_custom_network12 13# 查看网络详情14docker network inspect 网络名15 16# 删除网络17docker network rm 网络名#8.2 默认 bridge 和自定义 bridge 的区别
| 对比项 | 默认 bridge | 自定义 bridge |
|---|---|---|
| 容器名解析 | 默认不支持通过容器名互相解析。 | 内置 DNS,支持通过容器名或别名互相访问。 |
| 隔离性 | 所有未指定网络的容器默认加入该网络,隔离性较弱。 | 只有加入该网络的容器才能互通,隔离性更好。 |
| 生产推荐 | 不推荐作为生产主要网络。 | 更推荐。 |
| 配置灵活性 | 较弱。 | 可以自定义网段、网关、别名等。 |
#8.3 把容器加入自定义网络
1# 创建自定义网络2docker network create my_custom_network3 4# 将已存在的容器连接到自定义网络5docker network connect my_custom_network my_container如果希望从默认 bridge 迁移到自定义网络:
1# 停止容器2docker stop my_container3 4# 断开默认 bridge 网络5docker network disconnect bridge my_container6 7# 连接到自定义网络8docker network connect my_custom_network my_container9 10# 启动容器11docker start my_container#8.4 bridge 网络和 docker0
安装 Docker 后,宿主机会自动创建 docker0 网桥。
容器通过 bridge 网络访问外部的大致流程:
容器 eth0 -> veth pair -> docker0 -> iptables NAT -> 宿主机物理网卡 -> 外部网络
关键点:
- Docker 会为容器和宿主机创建一对虚拟网卡,也就是 veth pair。
- 容器一端在容器 Network Namespace 中。
- 宿主机一端连接到 docker0 或自定义 bridge。
- 容器访问外网时,Docker 通过 iptables 做 SNAT/MASQUERADE。
#8.5 host 网络
host 模式下,容器和宿主机共享同一个 Network Namespace。
特点:
- 容器不再拥有独立 IP。
- 容器直接使用宿主机 IP 和端口。
- 不需要做 Docker bridge NAT。
- 性能开销更低,但端口冲突风险更高,隔离性更弱。
示例:
1docker run -d --network host --name t1 tomcat适用场景:
- 容器需要大量网络连接。
- 对网络性能敏感。
- 可以接受网络隔离性降低。
#8.6 none 网络
none 模式下,Docker 不为容器配置网络。
特点:
- 容器只有 loopback。
- 没有默认网卡、IP、路由。
- 需要用户自己手动配置网络。
示例:
1docker run -it --network none ubuntu /bin/bash#8.7 container 网络
container 网络模式表示新容器共享另一个已有容器的网络命名空间。
特点:
- 两个容器共享 IP、端口、路由等网络配置。
- 文件系统、进程空间仍然隔离。
- 常见于 sidecar 模式。
示例:
1# 先启动一个 tomcat 容器2docker run -d -p 8080:8080 --name t1 tomcat3 4# nginx 容器共享 t1 的网络命名空间5docker run -d --network container:t1 --name nginx-sidecar nginx注意:使用 --network container:t1 时,新容器不能再单独使用 -p 暴露端口,因为它已经共享了目标容器的端口空间。
#8.8 固定容器 IP 的注意事项
可以在自定义网络中指定固定 IP,但不建议在业务代码里长期写死容器 IP。
原因:
- 容器重建后 IP 可能变化。
- 固定 IP 容易造成 IP 冲突。
- 服务迁移和扩容不灵活。
更推荐: - 使用自定义网络中的容器名解析。
- 使用 Docker Compose 的 service name。
- 使用服务发现或反向代理。
#8.9 Docker网络进阶学习
当Docker进程启动以后,会自动创建一个docker 0的网桥,每当启动一个容器后,会分别在容器和宿主机上创建一个虚拟网卡,Docker 将 veth pair 设备的一端放在新创建的容器中,并命名为 eth0(容器的网卡),另一端放在主机中,以 vethxxx 这样类似的名字命名,并将这个网络设备加入到 docker0 网桥中。
1# 使用docker inspect查看容器对应的Pid[root@jenkins ~]# docker inspect test | grep Pid
"Pid": 3425,
"PidMode": "",
"PidsLimit": null,
1# 然后使用nsenter进行查看容器的网络nsenter -t 3425 -n
[root@jenkins ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 9a:d9:b5:42:46:69 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
可以看出分别会在宿主机和容器内部创建一个虚拟网卡进行互联
容器A
┌─────────┐
│ eth0 │ 172.17.0.2
└────┬────┘
│
veth pair
│
┌────┴────┐
│vethabcd │
└────┬────┘
│
┌────┴────┐
│docker0 │
└────┬────┘
│
宿主机
#9. Docker Compose
Docker Compose 是 Docker 官方提供的多容器编排工具,用于通过一个 YAML 文件管理多个容器。
典型文件名:
1docker-compose.yml基本操作:
1# 启动Docker Compose V2 推荐:
1docker compose up -d2 3# 老版本命令:4docker-compose up -d5 6# 停止并删除容器、网络7docker compose down8 9# 查看服务状态10docker compose ps11 12# 查看日志13docker compose logs -f14 15# 重新拉取镜像16docker compose pullCompose 适合管理:
- Web 服务 + 数据库。
- Nginx + 应用容器。
- Redis / MySQL / Kafka 等中间件组合。
- 本地开发环境。
#10. Portainer 可视化管理
Portainer 是一个轻量级 Docker 图形化管理工具,支持单机 Docker 和部分集群环境。
新版镜像使用:
portainer/portainer-ce
启动命令:
1docker run -d \2 -p 8000:8000 \3 -p 9000:9000 \4 --name portainer \5 --restart=always \6 -v /var/run/docker.sock:/var/run/docker.sock \7 -v portainer_data:/data \8 portainer/portainer-ce:2.13.0-alpine参数说明:
- --restart=always:Docker 引擎重启后自动拉起容器。
- /var/run/docker.sock:允许 Portainer 管理本机 Docker。
- portainer_data:/data:持久化 Portainer 数据。
#11. Docker daemon 配置与日志排错
#11.1 daemon.json
daemon.json 是 Docker daemon 的配置文件,通常路径为:
/etc/docker/daemon.json
示例:开启 live-restore。
{
"live-restore": true
}
live-restore 的作用:
- Docker daemon 重启或异常退出时,尽量不影响已经运行的容器。
- 可以减少 Docker 升级、维护时对业务容器的影响。
修改后重新加载 Docker 配置:
1systemctl reload docker如果 reload 不生效,可以重启 Docker:
1systemctl restart docker#11.2 Docker 日志排错
查看 Docker 服务日志:
1journalctl -xu docker.service查看最近日志:
1journalctl -u docker.service -n 100持续查看:
1journalctl -u docker.service -f#12. 常用软件部署示例
#12.1 Tomcat
拉取镜像:
1docker pull tomcat运行容器:
1docker run -d \2 -p 8080:8080 \3 --name tomcat \4 tomcat如果访问 http://IP:8080 显示 404,可以进入容器调整 webapps:
1docker exec -it tomcat /bin/bashrm -rf webapps
mv webapps.dist webapps
#12.2 MySQL 单机部署
基础启动:
1docker run -d \2 -p 3306:3306 \3 -e MYSQL_ROOT_PASSWORD=<MYSQL_ROOT_PASSWORD> \4 --name mysql5.7 \5 mysql:5.7带数据卷启动:
1docker run -d \2 -p 3306:3306 \3 --privileged=true \4 -v /data/mysql/log:/var/log/mysql \5 -v /data/mysql/data:/var/lib/mysql \6 -v /data/mysql/conf:/etc/mysql/conf.d \7 -e MYSQL_ROOT_PASSWORD=<MYSQL_ROOT_PASSWORD> \8 --name mysql5.7 \9 mysql:5.7进入容器:
1docker exec -it mysql5.7 /bin/bash2mysql -uroot -p查看字符集:
1SHOW VARIABLES LIKE 'character%';在宿主机 /data/mysql/conf/my.cnf 中添加:
[client]
default_character_set=utf8
[mysqld]
collation_server=utf8_general_ci
character_set_server=utf8
重启容器:
1docker restart mysql5.7再次查看字符集:
1SHOW VARIABLES LIKE 'character%';#12.3 MySQL 主从复制
启动主库容器
1docker run -d \2 -p 3307:3306 \3 --name mysql-master \4 --privileged=true \5 -v /data/mysql-master/log:/var/log/mysql \6 -v /data/mysql-master/data:/var/lib/mysql \7 -v /data/mysql-master/conf:/etc/mysql/conf.d \8 -e MYSQL_ROOT_PASSWORD=<MYSQL_ROOT_PASSWORD> \9 mysql:5.7主库配置文件:/data/mysql-master/conf/my.cnf
[mysqld]
server_id=101
binlog-ignore-db=mysql
log-bin=mall-mysql-bin
binlog_cache_size=1M
binlog_format=mixed
expire_logs_days=7
slave_skip_errors=1062
重启主库:
1docker restart mysql-master进入主库创建复制用户:
1docker exec -it mysql-master mysql -uroot -p2CREATE USER 'repl'@'%' IDENTIFIED BY '<REPL_PASSWORD>';3GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'%';4FLUSH PRIVILEGES;查看主库状态:
1SHOW MASTER STATUS;记录输出中的:
- File
- Position
启动从库容器
1docker run -d \2 -p 3308:3306 \3 --name mysql-slave \4 --privileged=true \5 -v /data/mysql-slave/log:/var/log/mysql \6 -v /data/mysql-slave/data:/var/lib/mysql \7 -v /data/mysql-slave/conf:/etc/mysql/conf.d \8 -e MYSQL_ROOT_PASSWORD=<MYSQL_ROOT_PASSWORD> \9 mysql:5.7从库配置文件:/data/mysql-slave/conf/my.cnf
[mysqld]
server_id=102
binlog-ignore-db=mysql
log-bin=mall-mysql-slave1-bin
binlog_cache_size=1M
binlog_format=mixed
expire_logs_days=7
slave_skip_errors=1062
relay_log=mall-mysql-relay-bin
log_slave_updates=1
read_only=1
重启从库:
1docker restart mysql-slave进入从库配置主库信息:
1docker exec -it mysql-slave mysql -uroot -p2CHANGE MASTER TO3 MASTER_HOST='<宿主机IP或主库地址>',4 MASTER_USER='repl',5 MASTER_PASSWORD='<REPL_PASSWORD>',6 MASTER_PORT=3307,7 MASTER_LOG_FILE='mall-mysql-bin.000001',8 MASTER_LOG_POS=777,9 MASTER_CONNECT_RETRY=30;注意:
- MASTER_LOG_FILE 要和主库 SHOW MASTER STATUS 的 File 一致。
- MASTER_LOG_POS 要和主库 SHOW MASTER STATUS 的 Position 一致。
查看从库状态:
1SHOW SLAVE STATUS\G;重点关注:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
启动复制:
1START SLAVE;测试方式:
- 在主库创建数据库、表、插入数据。
- 在从库查看是否同步成功。
#12.4 Redis 单机部署
拉取镜像:
1docker pull redis:6.0.8运行 Redis:
1docker run -d \2 -p 6379:6379 \3 --name redis \4 redis:6.0.8#12.5 Redis 三主三从集群
启动 6 个 Redis 节点,使用 host 网络:
1docker run -d --name redis-node-1 --net host --privileged=true \2 -v /data/redis/share/redis-node-1:/data \3 redis:6.0.8 --cluster-enabled yes --appendonly yes --port 63814 5docker run -d --name redis-node-2 --net host --privileged=true \6 -v /data/redis/share/redis-node-2:/data \7 redis:6.0.8 --cluster-enabled yes --appendonly yes --port 63828 9docker run -d --name redis-node-3 --net host --privileged=true \10 -v /data/redis/share/redis-node-3:/data \11 redis:6.0.8 --cluster-enabled yes --appendonly yes --port 638312 13docker run -d --name redis-node-4 --net host --privileged=true \14 -v /data/redis/share/redis-node-4:/data \15 redis:6.0.8 --cluster-enabled yes --appendonly yes --port 638416 17docker run -d --name redis-node-5 --net host --privileged=true \18 -v /data/redis/share/redis-node-5:/data \19 redis:6.0.8 --cluster-enabled yes --appendonly yes --port 638520 21docker run -d --name redis-node-6 --net host --privileged=true \22 -v /data/redis/share/redis-node-6:/data \23 redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6386创建集群:
1redis-cli --cluster create \2 <宿主机IP>:6381 \3 <宿主机IP>:6382 \4 <宿主机IP>:6383 \5 <宿主机IP>:6384 \6 <宿主机IP>:6385 \7 <宿主机IP>:6386 \8 --cluster-replicas 1进入节点查看集群:
1redis-cli -p 63812cluster info3cluster nodes#12.6 分布式存储:哈希取余算法
哈希取余算法常用于把数据分散到多个节点:
node_index = hash(key) % 节点数量
优点:
- 实现简单。
- 数据可以按 key 分散到多个节点。
缺点: - 节点数量变化时,大量 key 需要重新映射。
- 扩容和缩容成本较高。
Redis Cluster 使用的是槽位机制,而不是简单的哈希取余。Redis Cluster 一共有 16384 个 hash slot,key 会映射到某个 slot,再由 slot 分配到具体节点。
#13. Nginx + Docker Compose 示例
#13.1 问题说明
如果直接使用 Docker Compose 挂载 Nginx 配置目录,而宿主机目录是空的,可能导致 Nginx 容器反复重启。
原因:
- 容器内 /etc/nginx 被宿主机空目录覆盖。
- Nginx 启动时找不到默认配置文件。
解决思路:
- 先创建宿主机目录。
- 使用临时 Nginx 容器把默认配置复制到宿主机。
- 再通过 Compose 挂载目录启动 Nginx。
#13.2 创建目录结构
1mkdir -p /data/nginx/conf2mkdir -p /data/nginx/html3mkdir -p /data/nginx/log#13.3 初始化默认配置
1docker run --rm \2 -v /data/nginx/conf:/mnt/conf \3 -v /data/nginx/html:/mnt/html \4 nginx bash -c "cp -r /etc/nginx/* /mnt/conf/ && cp -r /usr/share/nginx/html/* /mnt/html/"#13.4 docker-compose.yml
文件路径:/data/nginx/docker-compose.yml
services:
nginx:
image: nginx:latest
container_name: nginx-server
ports:
- "80:80"
- "443:443"
volumes:
- /data/nginx/conf:/etc/nginx
- /data/nginx/html:/usr/share/nginx/html
- /data/nginx/log:/var/log/nginx
restart: unless-stopped
启动:
1cd /data/nginx2docker compose up -d如果使用旧版本 Compose:
1docker-compose up -d#13.5 一键部署脚本
#!/bin/bash
set -e
BASE_DIR="/data/nginx"
CONF_DIR="$BASE_DIR/conf"
HTML_DIR="$BASE_DIR/html"
LOG_DIR="$BASE_DIR/log"
COMPOSE_FILE="$BASE_DIR/docker-compose.yml"
echo "Step 1: 创建目录结构..."
1mkdir -p "$CONF_DIR" "$HTML_DIR" "$LOG_DIR"echo "Step 2: 初始化默认配置和 HTML 文件..."
1docker run --rm \2 -v "$CONF_DIR":/mnt/conf \3 -v "$HTML_DIR":/mnt/html \4 nginx bash -c "cp -r /etc/nginx/* /mnt/conf/ && cp -r /usr/share/nginx/html/* /mnt/html/"echo "Step 3: 生成 docker-compose.yml 文件..."
1cat > "$COMPOSE_FILE" << EOFservices:
nginx:
image: nginx:latest
container_name: nginx-server
ports:
- "8011:80"
- "443:443"
volumes:
- $CONF_DIR:/etc/nginx
- $HTML_DIR:/usr/share/nginx/html
- $LOG_DIR:/var/log/nginx
restart: unless-stopped
EOF
echo "docker-compose.yml 已生成在 $COMPOSE_FILE"
echo "Step 4: 启动容器..."
1cd "$BASE_DIR"2 3docker compose up -d || docker-compose up -decho "Nginx 容器已启动。"
echo "访问地址:http://服务器IP:8011"
echo "日志目录:$LOG_DIR"
echo "如需热加载配置:docker exec nginx-server nginx -s reload"
#14. 学习建议:按这个顺序掌握 Docker
建议按照下面顺序学习和练习:
- 镜像和容器基础:docker run、docker ps、docker logs、docker exec。
- Dockerfile:自己构建镜像,理解构建缓存和镜像层。
- 数据卷:掌握 -v、named volume、宿主机目录挂载。
- Docker 网络:理解 bridge、host、none、container、自定义网络。
- Compose:把多个服务写进一个 docker-compose.yml。
- 排错:看容器日志、Docker daemon 日志、端口占用、挂载权限、网络连通性。
- 生产化:镜像仓库、重启策略、日志策略、资源限制、安全权限控制。
常用排错命令:
1# 看容器状态2docker ps -a3 4# 看日志5docker logs -f 容器名6 7# 看容器详细配置8docker inspect 容器名9 10# 进入容器11docker exec -it 容器名 /bin/bash12 13# 看端口监听14ss -lntup15 16# 看 Docker 网络17docker network ls18docker network inspect 网络名19 20# 看 Docker 服务日志21journalctl -u docker.service -n 100