文章目录
-
- 一、总场景:公司内网业务上线(分角色/分模块)
- 二、任务设计
-
- [任务 1:镜像规范化(所有镜像通用)](#任务 1:镜像规范化(所有镜像通用))
- [任务 2:sshd 镜像"安全化"改造(不要把它当真实生产)](#任务 2:sshd 镜像“安全化”改造(不要把它当真实生产))
- [任务 3:systemd 镜像的"代价认知 + 正确运行姿势"](#任务 3:systemd 镜像的“代价认知 + 正确运行姿势”)
- [任务 4:nginx 生产化:反向代理 + 配置挂载 + 日志](#任务 4:nginx 生产化:反向代理 + 配置挂载 + 日志)
- [任务 5:tomcat 生产化:部署应用 + 环境变量 + 日志](#任务 5:tomcat 生产化:部署应用 + 环境变量 + 日志)
- [任务 6:MySQL 生产化:持久化 + 初始化脚本 + 权限最小化](#任务 6:MySQL 生产化:持久化 + 初始化脚本 + 权限最小化)
- [三、综合联调关卡:三层网络 + 只暴露 Nginx](#三、综合联调关卡:三层网络 + 只暴露 Nginx)
- 四、排障训练(最像生产的部分)
- 五、交付物要求
下面我把这套 sshd / systemd / nginx / tomcat / mysql 镜像实验,整合成一套更像"生产环境"的实战场景(带任务、验收点、附加项、常见坑),按任务做完基本就能形成完整链路思维: 镜像规范 → 运行参数 → 数据持久化 → 网络隔离 → 安全 → 健康检查 → 日志排障 。
一、总场景:公司内网业务上线(分角色/分模块)
你们是运维小组,要在一台新服务器上用 Docker 快速部署一个"教学版业务系统":
- Web 层:Nginx(反向代理 + 静态资源)
- App 层:Tomcat(跑一个 demo war)
- DB 层:MySQL(持久化 + 远程访问控制)
- 运维入口:SSH(仅用于排障演示,不建议真实生产这样用)
- 系统管理:systemd 容器(演示 systemctl 管理服务的方式与代价)
要求:可重复部署、可迁移、可排障、可回滚。
二、任务设计
任务 1:镜像规范化(所有镜像通用)
- 每个镜像必须:
- 有清晰 tag:
<service>:<version>-<base>(例nginx:1.12-centos7) - Dockerfile 里写注释、分层合理
- 有清晰 tag:
yum -y update改成可控(生产里不建议 build 时全量 update)- 清理缓存减小镜像:
yum clean all && rm -rf /var/cache/yum
验收点
docker images看 size 是否明显下降- Dockerfile 层数是否更少(例如合并 RUN)
附加项
- 给镜像加
LABEL(维护人、用途、版本、构建时间)
任务 2:sshd 镜像"安全化"改造(不要把它当真实生产)
- 禁止 root 密码登录(改用 key)
- 只允许某个普通用户(如
ops)登录 - SSH 端口改为非 22(容器内仍可 22,宿主映射不同)
- 使用更安全的 sshd_config 片段(最少做到:禁用密码、禁用 root、限制用户)
验收点
- root 不能登录
- 未授权用户不能登录
- 只能通过 key 登录
提示
PermitRootLogin noPasswordAuthentication noAllowUsers ops
步骤
- 创建Dokcerfile文件
shell
mkidr /opt/myssh
cd /myssh
cp /etc/yum.repos.d/CentOS-Base.repo ./
tee Dockerfile <<-'EOF'
FROM centos:7
LABEL maintainer="this is ssh image <jixuancheng@qq.com>"
ENV ALLOWED_USER=ops
# 添加yum源
RUN rm -rf /etc/yum.repos.d/*
ADD CentOS-Base.repo /etc/yum.repos.d/
# 安装和配置
RUN yum clean all && \
yum makecache && \
yum -y install openssh-server openssh-clients sudo && \
yum clean all && \
# 创建用户
useradd -m -s /bin/bash ${ALLOWED_USER} && \
echo "${ALLOWED_USER}:ops@123" | chpasswd && \
echo "${ALLOWED_USER} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
# 生成主机密钥
ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N '' && \
ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -N '' && \
# SSH配置
sed -i \
-e 's/^#PermitRootLogin.*/PermitRootLogin no/' \
-e 's/^PasswordAuthentication.*/PasswordAuthentication no/' \
-e 's/^#PubkeyAuthentication.*/PubkeyAuthentication yes/' \
/etc/ssh/sshd_config && \
# 添加用户限制
echo "AllowUsers ${ALLOWED_USER}" >> /etc/ssh/sshd_config && \
# PAM配置
sed -ri '/^session\s+required\s+pam_loginuid.so/ s/^/#/' /etc/pam.d/sshd && \
sed -i 's/UsePAM yes/UsePAM no/g' /etc/ssh/sshd_config && \
sed -i 's/#UseDNS yes/UseDNS no/g' /etc/ssh/sshd_config && \
# 创建.ssh目录
mkdir -p /home/${ALLOWED_USER}/.ssh && \
chmod 700 /home/${ALLOWED_USER}/.ssh && \
chown -R ${ALLOWED_USER}:${ALLOWED_USER} /home/${ALLOWED_USER}/.ssh
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
EOF
UsePAM no:不使用PAM认证
UseDNS no:取消DNS反向认证,加快访问速度。
sed -ri '/^session\s+required\s+pam_loginuid.so/ s/^/#/' /etc/pam.d/sshd:#取消pam限制
ssh-keygen -t rsa -A:生成密钥认证文件
CMD ["/usr/sbin/sshd" , "-D"]:/usr/sbin/sshd -D 用于前台启动sshd服务
- 客户端连接
shell
cd /opt/myssh
# 1. 生成SSH密钥(如果没有的话)
ssh-keygen -t rsa
# 2. 构建镜像
docker build -t myssh-image .
# 3. 创建authorized_keys文件。
cat ~/.ssh/id_rsa.pub > authorized_keys
# 其他客户端需要连服务器上的myssh-server时,将客户端生成的公钥追加到authorized_keys即可。
scp ~/.ssh/id_ed25519.pub root@容器宿主机/opt/sshd/
cat id_ed25519.pub >> authorized_keys
# 4. 运行容器
docker run -d \
-p 2222:22 \
-v $(pwd)/authorized_keys:/home/ops/.ssh/authorized_keys \
--name myssh-server \
myssh-image
# 5. 连接到容器
ssh -p 2222 ops@容器宿主机
- myssh-server宿主机

- 其他客户端登录

报错日志
- 重复创建容器会提示远程主机标识已更改,删掉
~/.ssh/known_hosts该条记录或直接删掉known_hosts文件即可。
ini
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
shell
rm ~/.ssh/known_hosts
任务 3:systemd 镜像的"代价认知 + 正确运行姿势"
你已经写了 systemd 容器清理 wants 的做法,这是很典型的"能跑 systemctl"的实验。
- 必须解释清楚:为什么需要
--privileged+ cgroup 挂载(安全影响是什么)
systemd 容器需要
--privileged权限来挂载虚拟文件系统和创建设备节点,并挂载宿主机的 cgroup 来管理服务资源,但此模式赋予了容器等同于宿主机的 root 权限,会引入严重的容器逃逸等安全风险。
- 在 systemd 容器里通过
systemctl start/stop/status管理 sshd 或 mysqld



故障日志
- --privileged挂载authorized_keys可能导致ops用户访问该文件权限不足root:root。
shel
ssh -p 自动获取的端口 ops@localhost
Warning: Permanently added '[localhost]:32770' (ECDSA) to the list of known hosts.
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
# 解决方法:
docker exec -it myssh-systemd chown ops:ops /home/ops/.ssh/authorized_keys
- 写一份"什么时候不该用 systemd 容器"的说明
在追求轻量、快速启动、高安全性的生产环境及微服务部署场景中,不应该使用 systemd 容器;它应仅限于开发测试或传统应用迁移的过渡场景。
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 开发/测试环境 | systemd 容器 | 模拟完整系统环境 |
| 生产微服务 | 直接前台启动 | 符合云原生理念 |
| 传统应用迁移 | systemd 容器(过渡期) | 减少迁移成本 |
| 边缘计算 | 直接前台启动 | 资源利用效率高 |
| 学习 systemd | systemd 容器 | 提供真实操作环境 |
验收点
- systemctl 能正常查看服务状态
- 能重启服务后仍正常
附加项
- 对比:不用 systemd 的做法(容器前台启动 vs systemctl)优缺点
systemd
优点:熟悉、服务管理方便
缺点:启动慢、要特权、镜像大
直接启动
优点:启动快、安全、镜像小
缺点:要手动管理
任务 4:nginx 生产化:反向代理 + 配置挂载 + 日志
- Nginx 镜像不再把配置写死在镜像里:用 volume 挂载
/usr/local/nginx/conf/nginx.conf/usr/local/nginx/conf/conf.d/
- Nginx 反向代理到 Tomcat(例如
/app→ tomcat:8080) - 日志持久化挂载:
/usr/local/nginx/logs/
步骤
shell
# 1. 准备软件源和nginx源码包
cd /opt/nginx-centos7
ls
CentOS-Base.repo nginx-1.20.2.tar.gz
# 2. 编写Dockerfile
tee Dockerfile <<-'EOF'
# 基于基础镜像
FROM centos:7
# 用户信息
MAINTAINER this is nginx image <jixuancheng@qq.com>
# 添加yum源
RUN rm -rf /etc/yum.repos.d/*
ADD CentOS-Base.repo /etc/yum.repos.d/
# 安装依赖环境
RUN yum -y install pcre-devel zlib-devel gcc gcc-c++ make curl && \
yum clean all && \
useradd -M -s /sbin/nologin nginx
# 上传nginx软件压缩包,并解压
ADD nginx-1.20.2.tar.gz /opt/
# 指定工作目录
WORKDIR /opt/nginx-1.20.2
RUN ./configure \
--prefix=/usr/local/nginx \
--user=nginx \
--group=nginx \
--with-http_stub_status_module && \
make -j $(nproc) && make install
ENV PATH /usr/local/nginx/sbin:$PATH
# 创建必要的目录结构(容器第一次运行时)
RUN mkdir -p /usr/local/nginx/conf/conf.d && \
mkdir -p /usr/local/nginx/logs
# 声明数据卷(最佳实践:声明但不自动挂载)
VOLUME ["/usr/local/nginx/conf", "/usr/local/nginx/logs", "/usr/local/nginx/html"]
# 指定http和https端口
EXPOSE 80
EXPOSE 443
# 健康检查
HEALTHCHECK --interval=30s \
--timeout=3s \
--start-period=5s \
--retries=3 \
CMD curl -f http://localhost/nginx_status || exit 1
# 关闭 nginx 在后台运行
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]
EOF
# 3. 准备挂载目录
mkdir -p /data/nginx/{conf,logs,html}
# 4. 构建镜像并启动容器
docker build -t nginx:centos7 .
docker network create --subnet=172.18.0.0/16 webnet
docker run -d --name nginx-centos7 \
--net webnet --ip 172.18.0.10 \
-p 80:80 -p 443:443 \
-v /data/nginx/conf:/usr/local/nginx/conf \
-v /data/nginx/logs:/usr/local/nginx/logs \
-v /data/nginx/html:/usr/local/nginx/html \
nginx:centos7
# 5. 拷贝初始配置文件、日志、和页面。
docker cp nginx-test:/usr/local/nginx/conf/. /data/nginx/conf/
docker cp nginx-test:/usr/local/nginx/logs/. /data/nginx/logs/
docker cp nginx-test:/usr/local/nginx/html/. /data/nginx/html/
# 在宿主机给挂载的目录赋权
chmod -R 755 /data/nginx/
# 在容器修改挂载目录的归属(可选)
chown -R nginx:nginx /usr/local/nginx
# 6. 重新加载nginx配置文件
docker exec -it nginx-centos7 nginx -s reload
验收点
- 修改宿主机配置后,reload 生效(容器不重建)
curl http://宿主IP:端口/app能访问到 tomcat- 日志在宿主机可见
附加项
- 做健康检查:用
curl检测 200
shell
curl -I http://localhost
HTTP/1.1 200 OK
任务 5:tomcat 生产化:部署应用 + 环境变量 + 日志
- Tomcat 镜像做到"应用可插拔":
- 通过 volume 挂载
webapps/或挂载 war 包
- 通过 volume 挂载
- 配置 JVM 参数通过环境变量传入(如
JAVA_OPTS) - 日志持久化(
logs/)
验收点
- 替换 war 包无需重建镜像
- 设置
JAVA_OPTS后ps可看到 - 日志落盘到宿主机
任务 6:MySQL 生产化:持久化 + 初始化脚本 + 权限最小化
- 数据必须持久化(volume)
- 初始化动作要可重复(首次初始化执行,已有数据不重复初始化)
- 权限控制:不要
grant all on *.* to root@'%'作为最终答案- 改为创建业务库、业务用户,只授权业务库
验收点
- 删除容器重建,数据不丢
- 业务用户只能访问指定库
- 远程能连,但 root 不允许随便远程(作为加分)
问题
- 为什么生产里更推荐官方 mysql 镜像(减少编译维护成本)
三、综合联调关卡:三层网络 + 只暴露 Nginx
目标拓扑
frontend_net:只放 nginx,对外暴露端口backend_net:tomcat + mysql 在内网,外部不可直接访问- nginx 同时加入两个网络做桥接(或只做反代到 backend)
任务
- 创建两个网络
- mysql 不映射宿主机端口(禁止外部直连)
- 只能通过 nginx 访问到业务页面
- tomcat 只能被 nginx 访问(外部不映射)
验收点
docker ps只有 nginx 有0.0.0.0:xxxx->80- 从宿主机直接连 mysql 失败
- 从 nginx 容器能连 mysql(或 tomcat 能连 mysql)
四、排障训练(最像生产的部分)
要求在限定时间内定位问题并修复:
- nginx 502
- tomcat 未启动 / upstream 写错 / 网络不通
验收:能解释原因 + 修复
- tomcat 未启动 / upstream 写错 / 网络不通
- tomcat 启动但访问 404
- war 未部署 / context 路径错
验收:能找到 webapps 内容并修复映射
- war 未部署 / context 路径错
- mysql 能启动但远程连不上
- 授权、bind-address、端口没暴露、网络不通
验收:能说明"该不该暴露端口",并给出更安全的连接方式
- 授权、bind-address、端口没暴露、网络不通
- 数据丢失
- 忘了 volume
验收:能复现、能给出修复方案、能解释原理
- 忘了 volume
五、交付物要求
最后提交:
- 每个服务的 Dockerfile(或 compose 文件)
- 一份部署文档(含端口、网络、账号策略)
- 一份回滚方案(如何回到上一版镜像)
- 一份安全说明(哪些设置仅用于实验、生产要怎么改)