写这玩意之前先讲清楚这篇要干啥。你拿到一台 Windows 笔记本,想跑一套微服务脚手架,需要三样东西:一台装了 Linux 的机器(咱们用虚拟机代替)、一个能把中间件丢进去跑的容器引擎(Docker)、以及一套别人配好的中间件编排(docker-compose 拉起 MySQL/Redis/RabbitMQ/ES/Kibana/etcd/FastDFS)。这篇就是带你从零把这三层一次配齐,最后用 xshell 连上开发容器写代码。
整体三层结构先看一眼,免得读到一半迷路:

一句话数据流:VMware 提供硬件虚拟化 → Ubuntu 当宿主机 → Docker 在宿主机上切片出隔离环境 → 中间件容器在各自的隔离环境里跑 → dev-environment 容器是你写代码的工作台 → xshell 从 Windows 跨过去连这个工作台。
一. 虚拟机安装
1.开发环境说明
因为脚⼿架的封装开发涉及了多个中间件的操作,因此对系统资源要求会⽐较⾼⼀些,以下是开发环境的罗列:
软件配置

硬件配置

2. 虚拟机安装
2.1Vmware
2.1.1软件下载
官⽅⽹址:Fusion and Workstation | VMware
1. 在浏览器输⼊⽹址,进⼊下载⻚⾯,点击下载 VMware Workstation Pro for PC

2. 进⼊登录注册⻚⾯,进⾏⽤⼾注册及登录

注册完毕后,使⽤注册时使⽤的邮箱作为⽤⼾名即可登录。
3. 下载软件



我们选择⼀个不算最新的版本


在接下来的⻚⾯中补充⾃⼰的地址信息,点击提交后,重新点击下载即可(点击下载后可能会报错,如果报错可以多尝试⼏次)。
2.1.2 软件安装
软件安装部分没有什么特别需要介绍的,傻⽠式下⼀步安装即可,但是安装的时候,尽量右键安装
包,使⽤管理员进⾏软件安装,防⽌vmware软件的虚拟⽹卡驱动⽆法安装成功。
【为什么必须管理员】VMware 要在 Windows 内核里注册虚拟网卡(VMnet1/VMnet8 那一堆),这是内核驱动级动作,普通用户权限连驱动加载都做不下来,装完表面上能用,但 NAT 网络起不来------你虚拟机里能 ping 通自己,ping 不通外网,到时候排查能让你怀疑人生。右键 → 以管理员身份运行,省掉后面 90% 的网络故障。

安装成功后,点击图标打开软件,选择个⼈使⽤。

2.2Ubuntu22.04
2.2.1 镜像下载
国内源: Index of /ubuntu-releases/
我们也同样,不选择最新版本,⽽是选择⼀个相对稳定的版本22.04版本进⾏下载。

我们选择服务器版本进⾏下载(桌⾯对于我们来说并⽆⽤处)

【为什么选 Server 不选 Desktop】Server 版没有 GUI,省下来的几百兆内存和 CPU 都给容器用。你写后端服务、连 xshell,图形界面一次都用不上。桌面版那些花里胡哨的东西,对服务器开发是纯负担。22.04 是 LTS(长期支持版),官方维护到 2027 年,稳。
2.2.2 虚拟机安装
1. 创建新的虚拟机

2. 选择创建⽅式,这⾥选择典型安装即可

3. 选择稍后安装操作系统

【为什么"稍后安装"】这里要是选"安装程序光盘映像文件(iso)"直接挂载镜像,VMware 的"快速安装"会跳过大量手动配置步骤,用预设值装完。听起来省事,但预设值是按桌面版来的,磁盘分区、网络、SSH 全是默认的,后面你想改还得返工。选"稍后安装"等于"我等会儿自己挂 ISO 慢慢配",全程手控。
4. 选择要安装的系统类型

5. 选择安装路径
这⾥⼀定要⾃⼰在指定位置创建⼀个⽂件夹。

【坑】VMware 默认把虚拟机丢到 C 盘的某个角落,单文件几十 G 起步。装完一两个项目 C 盘直接红条。一开始就改到 D 盘或别的数据盘,单独建文件夹------虚拟机其实就是一个 .vmx 配置文件加几个 .vmdk 虚拟磁盘文件,整个文件夹可以拷贝迁移,路径放好后期搬家也方便。
6. 选择磁盘容量
这⾥⼤家可以尽量设置的⼤⼀些,这⾥设置的容量只是设置⼀个磁盘空间最⼤值,并不会真的占⽤这么多磁盘空间。

【为什么这样设计】VMware 用"稀疏文件"(thin provisioning)存虚拟磁盘:你设 100G,文件一开始可能就几个 G,往里写数据才按需增长。设大一点等于给后面留余地,磁盘不会立刻被吃掉,所以别舍不得设。
7. ⾃定义配置

8. CPU和内存配置(2核8G)

9. 选择系统安装⽅式 
10. 选择⽹络适配器
这⾥⽹络配置推荐使⽤默认的NAT模式(虚拟机将使⽤电脑主机的⽹络进⾏上⽹操作,且vmware会为虚拟机和主机创建⼀个虚拟局域⽹⽤于内部通信,缺陷是只有当前电脑本⾝能访问到虚拟机,外部其他电脑⽆法访问虚拟机)。

【三种网络模式对照】这里选 NAT 是有讲究的,把三种模式摊开看:
NAT :虚拟机躲在你主机后面共享一个外网 IP,主机和虚拟机互通,外部找不到虚拟机。优点:插个 WiFi 就能上网,不抢局域网 IP。缺点:同事连不到你的虚拟机。个人开发默认这个。
桥接:虚拟机直接接入你的物理网络,有自己的局域网 IP,外部能访问。优点:团队协作方便。缺点:换网络环境 IP 就变,每个 WiFi 都要重新查 IP。
仅主机(Host-Only):虚拟机只能和主机通信,不能上网。优点:隔离环境安全。缺点:装 Docker 拉镜像都拉不下来。
完成后,点击关闭,配置就 OK 了。
11. 开启虚拟机,开始安装系统

这里给大家说明一下,tab是切换键。enter是确认键
12. 选择操作类型:选择默认的安装就⾏

【小贴士】安装过程中全程键盘操作,鼠标点不动安装界面是正常的------Server 安装器是字符界面的 newt/whiptail,压根没鼠标。Tab 切换控件、Enter 确认、空格勾选多选项、Esc 返回。这套键位熟了,后面装啥 Linux 都通用。
13. 选择系统语⾔:这⾥默认使⽤英语,回⻋就⾏

【为什么选英语】装系统时选中文,将来 locale 会变成 zh_CN.UTF-8,终端里一些工具的输出、错误信息会变成中文,看起来亲切,但坑很多:脚本 grep 中文报错难匹配、SSH 从 Windows 连过去中文乱码、一些第三方软件不认中文 locale 直接报错。服务器领域默认语言就是英语,没人用中文 locale 跑生产。习惯几天,你反而觉得英语报错更精确
14. 选择键盘布局,这⾥默认回⻋就⾏

15. 安装⽅式
这⾥使⽤Tab键切换选项,我们选择最⼩安装,切换到选项后回⻋勾选,然后Tab切换到done回⻋继续向下。

【为什么最小安装】Ubuntu Server 的最小安装(Minimized)只有系统底座,不带 mail-common、文档工具、一堆 demo 包。咱们后面所有需要的工具都用 apt 按需装,不要的别给我塞进来。磁盘、内存、攻击面都省。这是 Linux 服务端的标准操作姿势。
16. ⽹络配置,默认回⻋即可
这⾥我们默认使⽤DHCP,让vmware安装的虚拟DHCP服务进⾏IP地址分配就⾏

【DHCP vs 静态 IP】DHCP 自动分配 IP,每次开机可能不一样。开发期无所谓------你能 ifconfig 查到当前 IP 就行。生产环境必然用静态 IP,否则 DNS、服务发现全乱套。这里开发环境图省事用 DHCP。
17. 代理地址,默认回⻋即可(我们这⾥不需要设置代理,我们默认就⽤的是电脑⽹络,真的要科学上⽹,那就配置你电脑的⽹络就⾏)

18. 配置软件源,默认回⻋继续
清华源地址:Index of /ubuntu/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror
这⾥推荐尽量填写清华源地址,不要使⽤默认的,因为有时候我们可能连接不上国外的ubuntu默认
源。

【为什么要换源】Ubuntu 默认源在 archive.ubuntu.com,服务器在国外。国内访问要么慢、要么超时,apt update 跑半小时跑不完是常态。清华源是 archive.ubuntu.com 的镜像,包完全一样,只是服务器在清华机房,速度快十几倍。换源不是改协议,只是改 apt 下载地址。
19. 磁盘分区设置,Tab切换到done,使⽤默认配置,回⻋继续就⾏
这⾥我们不需要⾃定义分区,使⽤默认的就⾏,如果有特殊需求,同学们可以⾃⾏研究研究。

20. ⽂件系统挂载设置
这⾥我们删除掉默认的挂载配置,重新进⾏⾃⼰的配置,因为它这⾥并没有将剩余空间都分配给根分区。
Tab键切换到,根分区挂载这⾥,回⻋,先选择Unmount解除根⽬录挂载。

切换到有效设备这⾥删除设备

接下来能够看到未分配空间为最⼤剩余空间了,这⾥我们选择创建逻辑卷

⼀路Tab到create,这⾥不设置⼤⼩,即表⽰使⽤最⼤⼤⼩

能够看到空间就已经全部利⽤了,接下来Tab切换到done进⼊下⼀步

接下来Tab切换到continue,进⾏磁盘格式化安装系统

【为什么折腾这个】Ubuntu 默认分区方案给 /boot、/、/swap 各分一点,根分区经常只剩十几 G,Docker 镜像一拉就满。把所有空间都给根分区(/),Docker 数据目录 /var/lib/docker 就有地方放。这是开发环境的标准姿势------不搞多分区,根分区一把梭,简单粗暴有空间
21. ⽤⼾配置
这⾥⼤家密码设置1即可,虚拟机本⾝就只有你⾃⼰能访问,实在没必要设置过于复杂的密码。

【小权衡】弱密码"1"在开发环境图省事,但前提是这台虚拟机不暴露到公网。NAT 模式下只有你主机能访问,密码弱成 1 也无所谓。哪天你改成桥接、把 22 端口转发到公网------一个弱密码的 ssh 能被扫描器爆破成肉鸡,半天的事。环境变了,密码也得跟着变。
22. 配置ssh远程连接
回⻋勾选安装openssh服务,否则默认⽆法进⾏远程连接(如果你没注意那也问题不⼤,了不起进了系统后在内部通过命令安装 openssh-server即可

【为什么这一步是分水岭】装了 openssh,你才能从 Windows 用 xshell 连进去,从此告别 VMware 窗口里那个难用的字符界面。没装的话,你只能一直在 VMware 的小窗口里敲命令,复制粘贴都费劲。勾上这一项,等于打开了一扇门。
23. 第三⽅驱动检测,默认回⻋开始系统安装

24. 安装完毕后,重启

3.虚拟机基础配置
1. 更新软件源信息
bash
sudo apt update
【为什么必须这一步】apt update 不是升级软件,是刷新本地软件包索引------Ubuntu 的 apt 把所有可用软件的版本信息缓存在 /var/lib/apt/lists/,装系统时这份索引可能是几个月前的,不刷新你 apt install 找不到最新版本的包,甚至找不到包。装新系统第一件事永远是这个,是规矩。
2. 安装⽹络配置⼯具
bash
sudo apt install net-tools
【为什么装 net-tools】Ubuntu 22.04 默认不带 ifconfig,新潮工具是 ip addr。但 ifconfig 输出直观、教学文档里全是它,装个 net-tools 就能用。不装的话敲 ifconfig 直接 command not found,很多人这时候就懵了。
3. 查看⽹络地址
bash
ifconfig

这个ens33下的IP地址就是咱们的远程连接地址了。
【ens33 是什么】Linux 把网卡命名按总线位置来:eno1 是板载网卡、enp0s3 是 PCI 总线 0 槽 3 的网卡、ens33 是 PCI 热插拔槽 33 的网卡。VMware 虚拟出来的网卡就显示成 ens33。这个名儿记一下,将来配静态 IP、桥接、防火墙全要用。
二. Docker简介与安装
2.1 docker简介
docker是⼀个⽤Go语⾔实现的应⽤容器引擎开源项⽬,可以让开发者打包他们的应⽤以及依赖包到⼀个轻量级、可移植的容器中,然后发布到任何流⾏的 Linux 机器上,也可以实现虚拟化。其⼴泛应⽤于开发、测试和⽣产环境中,帮助开发者和系统管理员简化应⽤的部署和管理,实现快速的交付,测试和部署。
【一段大白话翻译】传统装 MySQL 你得 apt 装、改配置、改端口、改密码、启服务、改 systemd......一套搞下来半天。Docker 干的事是:把 MySQL 整个运行环境(二进制 + 依赖库 + 配置 + 数据目录)打包成一个镜像文件,你一条命令拉下来跑起来就是一个能用的 MySQL。换机器?把镜像拷过去,一条命令又起来了,环境零差异。这就是"一次构建,到处运行"的本质------不是营销词,是工程上的真东西。
【容器 vs 虚拟机】很多人讲不清这个区别。虚拟机是装一套完整操作系统,每个虚拟机都自己跑内核,重。容器是共用宿主机内核,只是在用户态切片出隔离的进程空间、网络栈、文件系统------本质上就是一堆带 namespace 隔离的进程,没有 Guest 内核。所以容器启动是毫秒级、内存占用是 MB 级,比虚拟机轻一个数量级。咱们的 dev-environment 容器就是这种"轻量虚拟机"。
2.2 docker安装
1. 安装docker依赖
bash
san@host:~$ sudo apt update
san@host:~$ sudo apt-get install ca-certificates curl gnupg lsb-release
san@host:~$ sudo apt-get install make gcc g++ libz-dev lrzsz vim software-
properties-common
【为啥装这些】每个都有理由:
ca-certificates:让 apt 和 curl 能验证 HTTPS 证书,没它下阿里云源会报证书错误
curl:从网上拉脚本用
gnupg:处理 apt 仓库的 GPG 签名验证
lsb-release:拿当前 Ubuntu 版本代号(22.04 = jammy),配源的时候用
make gcc g++ libz-dev:装 Docker 的某些工具、Python 包等可能要本地编译,先把编译链备好
lrzsz:rz/sz 在 xshell 里传文件用的,老工程师的标配
vim:编辑配置文件
software-properties-common:提供 add-apt-repository 命令,加第三方源少一个都不一定立刻报错,但后面某一步会突然卡住,让你回头查半天。
2. 配置加速地址
bash
# 1. 创建 Docker 配置目录(如果不存在)
sudo mkdir -p /etc/docker
# 2. 写入 daemon.json 配置文件(使用 heredoc 语法)
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://dockerhub.timeweb.cloud",
"https://huecker.io",
"https://do.nark.eu.org",
"https://dc.j8.work",
"https://dockerproxy.com",
"https://docker.mirrors.ustc.edu.cn",
"https://docker.nju.edu.cn",
"https://hub.xdark.top",
"https://dockerpull.org",
"https://dockerproxy.cn",
"https://docker.rainbond.cc",
"https://b1skjlq5.mirror.aliyuncs.com"
]
}
EOF
# 3. 重新加载 systemd 配置,使 Docker 识别新配置
sudo systemctl daemon-reload
# 4. 重启 Docker 服务
sudo systemctl restart docker
【为啥配这么多镜像站】Docker Hub 服务器在国外,国内拉镜像动不动超时。镜像站(registry-mirror)是 Docker Hub 的国内缓存代理,docker pull 时优先从镜像站拉,拉不到再回源。配多个是冗余------今天哪个挂了明天哪个限速都不一定,多备几个轮询着用,总有一个能用。这两年 Docker Hub 封锁越来越严,镜像站经常集体抽风,列这么多不是矫情,是被逼的。
【heredoc 语法】
<<-'EOF' ... EOF是 shell 的"here document"语法:把两个 EOF 之间的内容当标准输入喂给前面的命令。tee命令把 stdin 同时输出到屏幕和文件,所以你能看到内容写入 /etc/docker/daemon.json 的过程。前面的-让 EOF 前面的制表符被忽略,方便缩进。
3. 添加Docker官⽅GPG密钥
bash
curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key
add -
【这步在干啥】apt 在装包时会验证仓库签名,没签名的源 apt 拒绝用。这条命令从阿里云下载 Docker 仓库的公钥,注入到 apt 的密钥环里,让 apt 后续能验证"这包真是 Docker 官方发布的、没被中间人篡改"。少了这步,下一步 add-apt-repository 时 apt 会报"无签名"错误。
4. 添加Docker软件源
bash
sudo add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-
ce/linux/ubuntu $(lsb_release -cs) stable"
sudo apt update
【这一串在干啥】把阿里云的 Docker 仓库地址登记到 apt 的源列表 /etc/apt/sources.list.d/。
[arch=amd64]限定 CPU 架构(你是 x86_64 就是 amd64,ARM 机要改 arm64);$(lsb_release -cs)是命令替换,执行 lsb_release -cs 拿到 Ubuntu 代号(22.04 是 jammy),自动嵌入 URL;stable是仓库分支(稳定版,还有 test、nightly)。apt update刷新索引让新源生效。
5. 安装Docker
bash
sudo apt-get install docker-ce docker-ce-cli containerd.io
【三个包各自干啥】
docker-ce:Docker Engine 服务端,dockerd 守护进程,真正的容器管理逻辑
docker-ce-cli:命令行客户端 docker 命令,你敲 docker ps、docker run 用的就是它
containerd.io:容器运行时,Docker 把容器生命周期管理下放给 containerd 这个 CNCF 项目,Docker 只负责上层 APIcli 和 daemon 分开发布的好处是:你可以在一台机器装 cli 远程连另一台的 daemon,做集中管理。
6. 安装docker-compose
cpp
sudo curl -L
"https://github.com/docker/compose/releases/download/v2.13.0/docker-compose-
linux-x86_64" -o /usr/bin/docker-compose
sudo chmod +x /usr/bin/docker-compose
docker-compose --version
【docker-compose 是干啥的】原生 docker 命令一次只能管一个容器。咱们的微服务环境是 7+ 个容器互相配合(MySQL 给业务用、ES 给搜索用、etcd 给注册中心用),手敲 docker run 七八条命令加十几个参数,错一个就启动失败。docker-compose 用一个 yml 文件描述全部容器和它们的依赖关系,一条
docker-compose up -d全部启动。本质是"批处理脚本"的容器版。【为啥直接下二进制而不是 apt 装】apt 仓库里的 docker-compose 版本落后好几个 patch,老版 v1 还是 Python 写的、慢。直接从 GitHub releases 下 v2 的二进制(Go 写的,比 v1 快一个量级),扔到 /usr/bin/ 加可执行权限,干净利落。
chmod +x不可省,不然执行时报 Permission denied
7. 配置⽤⼾组
bash
sudo groupadd docker
sudo gpasswd -a $USER docker
newgrp docker
【这一步是用户体验优化】默认 docker 命令要 root 权限(守护进程绑 /var/run/docker.sock,权限 660 属 root:docker)。你不加 docker 组,每次 docker ps 都得 sudo,烦。这三步:建 docker 组、把你当前用户加进去、立刻切到新组(不然要重登才生效)。从此 sudo 都不用敲了。
【安全小提示】把用户加 docker 组等同于给了 root 权限------docker 容器里能挂载主机的任意目录、改文件属主,等于绕过了 sudo。生产服务器上一般只给特定运维用户加 docker 组,不给普通开发加。咱们开发环境无所谓
8. 测试-查看版本
bash
docker version
【这步是验收】能打印出 Client 和 Server 两段版本信息,说明 cli 能连上 daemon、daemon 在跑、整套链路通了。如果只看到 Client 段、Server 段报错,多半是 dockerd 服务没起来,
sudo systemctl status docker看日志。
2.3 获取开发环境
2.3.1 克隆仓库:
bash
git clone https://gitee.com/qigezi/dev-environment.git
【为啥用 Gitee 不用 GitHub】同 Docker Hub 一样的理由,GitHub 在国内访问抽风。Gitee 是国内代码托管,clone 速度秒级。仓库里装的是别人已经配好的 docker-compose.yml + 各中间件的配置文件 + 初始化 SQL,clone 下来改改就能用。
2.4 配置说明:
2.4.1 仓库⽬录结构
bash
.
├── dev-environment
│ ├── dev-environment
│ │ └── workspace # 开发环境的挂载目录
│ ├── docker-compose.yml
│ ├── icsearch
│ │ ├── elasticsearch.yml
│ │ └── plugins # 搜索引擎的中文分词插件(会自动安装)
│ ├── kibana
│ │ └── kibana.yml
│ ├── mysql
│ │ ├── my.cnf
│ │ └── sql.d
│ ├── rabbitmq
│ │ ├── 20-management_agent.disable_metrics_collector.conf
│ │ └── Dockerfile # 目前没用到,rabbitmq 直接通过连接拉取的镜像
│ └── redis
│
└── redis.conf
【看懂这个结构的关键】每个中间件一个目录,目录里放该中间件的配置文件。docker-compose.yml 用相对路径
./mysql/my.cnf挂载这些配置进容器,所以整个 dev-environment 文件夹不能随便挪,挪了路径就错。workspace 是开发容器的工作目录,挂载到容器里 /home/dev/workspace,你写的代码放这里------容器销毁了数据还在。
2.4.2 ⾃定义⽹络
bash
networks:
dev-network:
driver: bridge
【为啥要自定义网络】Docker 默认有个 bridge 网络,但默认 bridge 没有内置 DNS ------容器之间只能用 IP 互相访问,IP 一变(重启容器可能换 IP)就失联。自定义 bridge 网络自带 DNS:容器名就是域名,dev-mysql 容器叫 mysql-service,其他容器直接
mysql-service:3306就能连上,重启 IP 变了也没事,DNS 自动跟进。这就是为啥咱们的 yml 里到处用 dev-mysql、dev-redis 这种服务名做连接配置。
2.5 开发环境容器配置
这个容器环境中,包含了项目开发的绝大多数所依赖的第三方库,因此在开发环境容器中进行项目的开发,会让我们不必再为搭建环境而操碎心了。
下边是开发环境的配置,其中有三项注意事项:
开发环境默认用户名密码:
a. 用户名:
devb. 密码:XXX
挂载目录: 我们默认在容器中创建了
workspace目录,并将其挂载到本地的workspace目录下(避免文件随着容器的销毁而销毁),该挂载信息可自行根据需要调整。映射端口: 映射端口决定了外部访问可以通过访问宿主机的哪些端口而进一步访问容器内运行的网络程序,默认情况下我们映射了
2222:22端口用于进行 SSH 连接(访问宿主机的 2222 端口相当于访问容器的 22 端口),9000~9004端口属于预留端口,用于外部访问容器内部我们运行的网络通信程序(可自行根据需要调整)。
代码块
bash
services:
dev-environment:
image: crpi-50dgs1uofzodc9pn.cn-chengdu.personal.cr.aliyuncs.com/dev-envronment/dev-environment:1.0
container_name: dev-env-service
volumes:
- /etc/localtime:/etc/localtime:ro
# 将程序源码目录,配置文件目录,日志目录,以及二进制目录映射出来
- ./dev-environment/workspace:/home/dev/workspace
# environment:
ports:
- "2222:22"
- "9000:9000"
- "9001:9001"
- "9002:9002"
- "9003:9003"
- "9004:9004"
restart: always
privileged: true
networks:
- dev-network
【逐项拆解】
image:阿里云容器镜像服务 ACR 上的私有镜像,地址里有命名空间 dev-envronment、镜像名 dev-environment、tag 1.0。这镜像里预装了开发工具链(g++、cmake、gdb、git 等),所以叫"开发环境"。
container_name:固定容器名 dev-env-service,方便日志和命令引用。
volumes挂载两处:
/etc/localtime:/etc/localtime:ro:把宿主机时间映射进容器(只读),保证容器内时间和你电脑一致。日志时间错位的人都懂这个坑------容器里时间晚 8 小时,排查问题时一头雾水。
./dev-environment/workspace:/home/dev/workspace:把宿主机的 workspace 目录映射到容器内 /home/dev/workspace。这是数据持久化的命脉------你在容器里写代码,实际写到宿主机磁盘上,容器删了数据还在。
ports:把容器端口映射到宿主机端口。2222→22 是 ssh(你 xshell 连的 2222 端口实际进的是容器的 22);9000-9004 是预留给业务服务用的端口,你起 gRPC、HTTP 服务可以从这几个端口暴露出去。
restart: always:容器崩了或宿主机重启了,Docker 自动把它拉起来。开发环境要的是稳定,不是省电。
privileged: true:特权模式,容器里能干宿主机能干的所有事(包括改内核参数、挂载文件系统)。生产环境绝对不能开(等于 root 后门),开发环境开了方便调试。
networks: dev-network:加入自定义网络,能和 mysql-service、redis-service 等用服务名互访。
1.mysql中间件容器
容器内运行 mysql 服务器,可根据需要在配置中设置 mysql 的 root 用户默认密码。
代码块
bash
dev-mysql:
image: crpi-50dgs1uofzodc9pn.cn-chengdu.personal.cr.aliyuncs.com/dev-envronment/mysql:8.0.40
container_name: mysql-service
volumes:
- ./mysql/my.cnf:/etc/my.cnf
- ./mysql/sql.d/:/docker-entrypoint-initdb.d/
- /etc/localtime:/etc/localtime:ro # 与宿主机时钟同步,避免时间的问题
environment:
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: 123456
healthcheck:
test: mysql --user=root --password='123456' -e "SELECT 1;" # 健康检测返回码为0表示成功,非0表示失败
start_period: 30s # 启动预留时间,即30s内的启动失败不计算在超时次数之内
interval: 10s # 每隔10s进行一次检测
timeout: 5s # 健康检测超时时间
retries: 10 # 健康检测重试次数
ports:
- "3306:3306"
restart: always
privileged: true # 给与容器root权限,但是在这里没什么用
networks:
- dev-network
【关键设计点】
./mysql/sql.d/:/docker-entrypoint-initdb.d/:MySQL 官方镜像的特殊挂载点,容器首次启动时自动执行这个目录下所有 .sql 和 .sh 文件。咱们把建库建表的 SQL 放这里,首次起容器自动初始化数据库,不用手动 source。注意是首次------之后再重启容器不会重新执行,数据已经持久化在 Docker volume 里了。
healthcheck:Docker 定期跑mysql -e "SELECT 1"探测 MySQL 是否响应。返回 0 表示活着,非 0 表示挂了。start_period: 30s给 MySQL 30 秒启动缓冲期------MySQL 启动慢,没这个缓冲一开始就被判挂了。
TZ: Asia/Shanghai:时区,让 NOW()、CURRENT_TIMESTAMP 这些函数返回北京时间。
MYSQL_ROOT_PASSWORD:MySQL 官方镜像认的环境变量,容器首次启动时给 root 设密码。改这里就行,别去改 my.cnf。
2.redis中间件容器
容器内运行 redis 服务器,可根据需要在配置文件(./redis/redis.conf 该配置文件所在目录与 docker-compose.yml 同级)中设置 redis 的 default 用户默认密码(在文件 1037 行)。
代码块
bash
1026
1027 # IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatibility
1028 # layer on top of the new ACL system. The option effect will be just setting
1029 # the password for the default user. Clients will still authenticate using
1030 # AUTH <password> as usually, or more explicitly with AUTH default <password>
1031 # if they follow the new protocol: both will work.
1032 #
1033 # The requirepass is not compatible with aclfile option and the ACL LOAD
1034 # command, these will cause requirepass to be ignored.
1035 #
1036 # requirepass foobared
1037 requirepass 123456
1038 # New users are initialized with restrictive permissions by default, via the
1039 # equivalent of this ACL rule 'off resetkeys -@all'. Starting with Redis 6.2, it
1040 # is possible to manage access to Pub/Sub channels with ACL rules as well. The
1041 # default Pub/Sub channels permission if new users is controlled by the
1042 # acl-pubsub-default configuration directive, which accepts one of these values:
1043 #
1044 # allchannels: grants access to all Pub/Sub channels
代码块
bash
dev-redis:
image: crpi-50dgs1uofzodc9pn.cn-chengdu.personal.cr.aliyuncs.com/dev-envronment/redis:7.4.1
container_name: redis-service
volumes:
- ./redis/redis.conf:/usr/local/etc/redis.conf
- /etc/localtime:/etc/localtime:ro
command: redis-server /usr/local/etc/redis.conf
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
interval: 10s
timeout: 5s
retries: 10
ports:
- "6379:6379"
restart: always
privileged: true
networks:
- dev-network
【为啥 Redis 用 command 覆盖】Redis 官方镜像默认入口是直接跑 redis-server 不带配置文件。咱们要自定义配置(密码、持久化策略等),所以
command覆盖默认入口,指定配置文件路径。Redis 启动读这个配置文件,密码就在 1037 行的 requirepass。改密码不用动 yml,改 redis.conf 就行。【为啥密码改在配置文件不在环境变量】Redis 官方镜像没有提供 REDIS_PASSWORD 这种环境变量(不像 MySQL 那么贴心),只能通过配置文件设。配置文件第 1037 行
requirepass 123456------把这行的 123456 改成你的密码。Redis 6 之后 requirepass 实际是给 default 用户设密码(新 ACL 机制的兼容层),不影响老客户端的 AUTH 命令。
3. rabbitmq中间件容器
容器内运行 rabbitmq 服务器,可根据需要在配置中设置 rabbitmq 的默认用户及默认密码。
rabbitmq 容器内的中间件,默认没有开启 web 管理,若需要开启,则启动容器后,进入容器内部手动开启即可。
开启 web 管理指令
bash
:rabbitmq-plugins enable rabbitmq_management
代码块
bash
dev-rabbitmq:
image: crpi-50dgs1uofzodc9pn.cn-chengdu.personal.cr.aliyuncs.com/dev-envronment/rabbitmq:4.0
container_name: rabbitmq-service
environment:
RABBITMQ_DEFAULT_USER: admin
RABBITMQ_DEFAULT_PASS: 123456
volumes:
- ./rabbitmq/20-management_agent.disable_metrics_collector.conf:/etc/rabbitmq/conf.d/20-management_agent.disable_metrics_collector.conf
ports:
- "5672:5672"
- "15672:15672"
healthcheck:
test: [ "CMD", "rabbitmqctl", "status" ]
interval: 10s
timeout: 10s
retries: 10
restart: always
networks:
- dev-network
【两个端口的区别】5672 是 AMQP 协议端口,业务代码连这个发消息、消费消息;15672 是 Web 管理界面端口,浏览器访问 http://宿主机IP:15672 用 admin/123456 登录,能看到队列、交换机、消息积压情况。
【为啥默认不开 web 管理】rabbitmq:4.0 这个 tag 是精简版,只含 AMQP 服务不含管理插件,省内存。需要的话手动
docker exec -it rabbitmq-service rabbitmq-plugins enable rabbitmq_management开启,开了之后 15672 端口才真的有内容。这是 RabbitMQ 的设计哲学------按需加载插件,不浪费。
4.elasticsearch与kibana中间件容器
elasticsearch 容器内运行 elasticsearch 搜索引擎服务器,kibana 容器内运行 elasticsearch 的辅助使用工具 kibana 服务器(目前 kibana 我们仅用于辅助在网页上进行 elasticsearch 的操作)。
可根据需要在配置中设置 elasticsearch 的 elastic 用户默认密码。
代码块
bash
dev-elasticsearch:
image: crpi-50dgs1uofzodc9pn.cn-chengdu.personal.cr.aliyuncs.com/dev-envronment/elasticsearch:7.17.18
container_name: elasticsearch-service
environment:
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- "ELASTIC_PASSWORD=123456"
volumes:
- /etc/localtime:/etc/localtime:ro
- ./elasticsearch/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
- ./elasticsearch/plugins:/usr/share/elasticsearch/plugins
healthcheck:
test: [ "CMD-SHELL", "curl -s http://dev-elasticsearch:9200/_cluster/health" ]
start_period: 30s
retries: 10
interval: 5s
ports:
- "9200:9200"
- "9300:9300"
restart: always
privileged: true
networks:
- dev-network
dev-kibana:
image: crpi-50dgs1uofzodc9pn.cn-chengdu.personal.cr.aliyuncs.com/dev-envronment/kibana:7.17.18
container_name: kibana-service
environment:
- "I18N_LOCALE=zh-CN"
- "ELASTICSEARCH_HOSTS=http://dev-elasticsearch:9200"
volumes:
- /etc/localtime:/etc/localtime:ro
- ./kibana/kibana.yml:/usr/share/kibana/config/kibana.yml
ports:
- "5601:5601"
restart: always
depends_on:
dev-elasticsearch:
condition: service_healthy
privileged: true
networks:
- dev-network
【ES 配置要点】
ES_JAVA_OPTS=-Xms512m -Xmx512m:JVM 堆大小。ES 跑在 JVM 上,默认堆会吃掉 1/4 物理内存(8G 机器就吃 2G),太奢侈。手动限定 512m 起步,单机开发够用了。-Xms 和 -Xmx 设成一样能避免堆动态扩展的 GC 抖动。
ELASTIC_PASSWORD=123456:ES 8+ 默认开启安全认证,必须设密码。这里设的是超级用户 elastic 的密码。
./elasticsearch/plugins:插件目录挂载出来,中文分词器(ik_analyzer)放这里,ES 启动时自动加载。不用进容器装插件。健康检查
curl http://dev-elasticsearch:9200/_cluster/health:访问 ES 的集群健康接口,能拿到 200 表示起来了。【Kibana 配置要点】
I18N_LOCALE=zh-CN:界面中文化,Kibana 自带多语言包,改这个变量就切中文界面。
ELASTICSEARCH_HOSTS=http://dev-elasticsearch:9200:Kibana 连 ES 的地址。注意用服务名 dev-elasticsearch 而不是 localhost------Kibana 在容器里,localhost 是它自己。走自定义网络的 DNS 解析,dev-elasticsearch 这个名字才能解析到 ES 容器。
depends_on: condition: service_healthy:这是依赖编排的关键。Kibana 启动时如果连不上 ES 会崩溃退出。这个配置让 Kibana 等 ES 健康检查通过后再启动,避免启动顺序问题。普通的 depends_on 只保证"容器创建顺序",不保证"业务就绪",加 condition 才是真等到服务能用。
5.etcd中间件容器
etcd 容器内运行的是 etcd 服务器,该服务器在项目中主要用于作为服务注册中心使用。
代码块
bash
dev-etcd:
image: crpi-50dgs1uofzodc9pn.cn-chengdu.personal.cr.aliyuncs.com/dev-envronment/etcd:3.5.12
container_name: etcd-service
volumes:
- /etc/localtime:/etc/localtime:ro
environment:
- ETCDCTL_API=3
- ETCD_NAME=etcd-s1
- ETCD_DATA_DIR=/var/lib/etcd
# 指定服务器端的监听地址-决定了是否可被外部访问
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
# 集群时对外通告的访问地址
- ETCD_ADVERTISE_CLIENT_URLS=http://dev-etcd:2379
ports:
- "2379:2379"
- "2380:2380" # 这里没用到,这个是分布式同伴间的访问端口
restart: always
privileged: true
networks:
- dev-network
【etcd 在微服务里的角色】etcd 是一个分布式 KV 存储,Google 用它做 Kubernetes 的元数据存储而闻名。微服务脚手架用它做服务注册中心:每个服务启动时把自己的 IP+端口写进 etcd,要调别人时从 etcd 查对方地址------这就是服务发现。比把地址写死在配置文件强一百倍,服务扩容/迁移不用改代码。
【关键参数解释】
ETCDCTL_API=3:指定 etcdctl 命令行用 v3 API。v2 是老接口,新项目都用 v3。
ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379:监听本机所有网卡的 2379 端口。0.0.0.0 是关键------写 127.0.0.1 只监听本机回环,外部(包括其他容器)连不进来;0.0.0.0 才能让其他容器通过 dev-etcd:2379 连过来。
ETCD_ADVERTISE_CLIENT_URLS:对外通告的访问地址,告诉集群里其他人"你们可以用这个地址连我"。单机开发其实用不太到,多节点 etcd 集群才是关键。
2380 端口:etcd 节点之间互相通信的端口(peer port)。单节点用不到,注释也说了。
6. fastdfs中间件容器
通过 fastdfs 中间件镜像,我们运行了两个不同的 fastdfs 容器,其中一个内部运行的是 tracker 服务器(分布式存储节点管理服务器),一个内部运行的是 storage 服务器(分布式文件数据存储服务器)。
代码块
bash
dev-storage:
image: crpi-50dgs1uofzodc9pn.cn-chengdu.personal.cr.aliyuncs.com/dev-envronment/fastdfs:6.12.2
container_name: fdfs-storage-service
environment:
- TRACKER_SERVER_ADDR=dev-tracker
- TRACKER_SERVER_PORT=22122
- STORAGE_SERVER_PORT=23000
command: "storage"
ports:
- "23000:23000"
restart: always
privileged: true
networks:
- dev-network
dev-tracker:
image: crpi-50dgs1uofzodc9pn.cn-chengdu.personal.cr.aliyuncs.com/dev-envronment/fastdfs:6.12.2
container_name: fdfs-tracker-service
environment:
- TRACKER_SERVER_PORT=22122
command: "tracker"
ports:
- "22122:22122"
restart: always
privileged: true
networks:
- dev-network
【FastDFS 架构原理】FastDFS 是国人余庆设计的轻量级分布式文件系统,专为大文件存储优化。架构上分两层:
tracker :调度服务器,不存文件,只存"哪个 storage 上有哪些文件"的元数据。客户端上传文件先问 tracker"我该传给谁",tracker 返回一个可用的 storage 地址。
storage:实际存文件的服务器,按 group 组织,同 group 内多台 storage 互为备份。
【这俩容器的关系】storage 启动时要通过
TRACKER_SERVER_ADDR=dev-tracker注册到 tracker,告诉它"我是来干活的"。注意用服务名 dev-tracker 而不是 IP------还是自定义网络 DNS 的功劳。同一镜像用command区分启动模式(storage 还是 tracker),这是 FastDFS 官方镜像的设计。【端口用途】22122 是 tracker 接受客户端调度的端口,23000 是 storage 接收文件数据的端口。你的业务代码上传文件时连 22122 拿地址,再连 23000 传数据。
2.6 启动容器环境
bash
docker-compose up -d
启动后,可通过 docker ps -a 指令查看容器运⾏情况

启动后,可通过
docker ps -a指令查看容器运行情况【这俩命令配合看状态】
docker-compose up -d的-d是 detached 模式,容器在后台跑,不占当前终端。没 -d 你 Ctrl+C 就全停了。第一次启动会慢------要拉镜像、跑初始化 SQL、ES 加载插件、healthcheck 跑一轮,3-5 分钟正常。耐心点。
docker ps -a的-a是包括已停止的容器。不加 -a 只显示运行中的,看不到刚启动失败的容器。排查故障时永远加 -a。
【排查容器起不来】容器 Status 显示 Exited (非0) 或 Restarting,看日志:3
bash
docker logs <container_name>
90% 的问题日志里直接写明白------配置语法错、端口冲突、密码错、依赖服务没起来
2.7 远程连接开发环境
xshell连接
1.查看容器运⾏情况

dev-environment就是开环境容器,已经正常运⾏,并且其将⾃⼰内部的22端⼝映射为2222端⼝,因此我们使⽤本地主机的IP地址 + 2222端⼝就可以连接开发环境了。
2.新建连接
在新建连接中,设置本地主机的IP地址,以及2222端⼝进⾏连接

【为啥是 2222 不是 22】回看 docker-compose.yml 里 dev-environment 的端口映射
"2222:22":宿主机的 2222 端口映射到容器的 22 端口。你 xshell 连的是宿主机的 2222,数据包被 Docker 转发到容器的 22 端口(容器内 sshd 监听那里)。宿主机的 22 端口可能被宿主机自己的 sshd 占着,所以用 2222 避开冲突。
3.连接
开发环境默认⽤⼾名: dev
开发环境密码(你设置的默认为1): xxxx

连接成功后,在dev⽤⼾⽬录下默认会有⼀个workspace⽬录,以后所有创建的⽂件尽量都在该⽬录下保存,因为该⽬录被挂载到了主机本地,所以只有这个⽬录下的⽂件不会随着容器的销毁⽽销毁。
【这条规矩的分量】这是整篇最重要的一句话。容器是"易失"的------
docker-compose down把容器一删,里面没挂载的数据全没。workspace 通过 volumes 映射到宿主机的./dev-environment/workspace,宿主机文件系统是持久的。所有代码、配置、二进制都要放 workspace,其他位置的文件随容器一起消失。我见过有人把一周的代码写在家目录,第二天 docker 重启容器,人麻了一上午。

三.整体记忆口诀
把整个环境串起来记一遍,齐活:
三层结构 :VMware 装 Ubuntu,Ubuntu 跑 Docker,Docker 切中间件容器。 网络一条线 :NAT 上网、ens33 拿 IP、dev-network 内部 DNS 互访、2222 端口外接 xshell。 数据持久化三件套 :宿主机 workspace 挂进容器、MySQL sql.d 自动初始化、Redis 配置文件外挂。 容器编排铁律 :自定义网络有 DNS、depends_on 加 service_healthy、healthcheck 配 start_period。 ssh 连接:用户名 dev、密码 1、端口 2222、工作目录 /home/dev/workspace。
四.坑点清单
| # | 位置 | 坑 | 后果 / 修法 |
|---|---|---|---|
| 1 | VMware 安装 | 不用管理员权限装 | 虚拟网卡驱动装不上,NAT 不通;右键管理员运行 |
| 2 | 虚拟机路径 | 默认放 C 盘 | 几十 G 文件挤爆系统盘;改到数据盘单独建文件夹 |
| 3 | VMware 网络 | 选桥接 vs NAT 选错 | 桥接换 WiFi 后 IP 变、连不上;个人开发默认 NAT |
| 4 | Ubuntu 语言 | 装系统时选中文 | locale 变 zh_CN.UTF-8,SSH 中文乱码、第三方软件报错;选英语 |
| 5 | Ubuntu 分区 | 用默认分区 | 根分区太小,Docker 镜像一拉就满;删默认重建逻辑卷用最大空间 |
| 6 | Ubuntu 软件 | 不配清华源 | apt update 半天跑不完、超时失败;换清华源 |
| 7 | Ubuntu 网卡 | 没装 net-tools | ifconfig 命令找不到;apt install net-tools |
| 8 | Docker 安装 | 不配 daemon.json 镜像站 | docker pull 超时失败;配多个国内镜像站冗余 |
| 9 | Docker 安装 | 不加 docker 用户组 | 每次都要 sudo docker;gpasswd -a $USER docker |
| 10 | docker-compose | apt 装老版本 | v1 是 Python 写的慢,且版本落后;直接下 v2 二进制 |
| 11 | dev-environment | 在容器家目录写代码 | docker down 后数据全没;只写 /home/dev/workspace |
| 12 | MySQL | 改密码改 yml 不生效 | 环境变量只在首次启动生效;已初始化的容器要进容器改 |
| 13 | Redis | 用环境变量设密码 | Redis 镜像没这变量;改 redis.conf 第 1037 行 requirepass |
| 14 | RabbitMQ | 默认访问 15672 是空 | 没开 management 插件;docker exec 进容器 rabbitmq-plugins enable |
| 15 | ES | 不限制 JVM 堆 | 8G 机器被 ES 占 2G,其他容器全卡;ES_JAVA_OPTS=-Xms512m -Xmx512m |
| 16 | Kibana | 用 localhost 连 ES | 容器内 localhost 是自己,连不到 ES;用 dev-elasticsearch:9200 |
| 17 | Kibana | 不加 condition: service_healthy | 启动时 ES 没就绪,Kibana 崩溃退出;加 service_healthy |
| 18 | etcd | 监听 127.0.0.1 | 其他容器连不进来;改 ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 |
| 19 | FastDFS | storage 连 tracker 用 IP | 重启 IP 变就失联;用 dev-tracker 服务名 |
| 20 | docker-compose down | 误删数据 | 容器内未挂载的目录全没;挂载到 workspace / volumes 的数据保留 |
| 21 | 时区 | 不挂 /etc/localtime | 容器时间晚 8 小时,日志排查错位;挂载 ro |
五.完结撒花
到这一步,你手里应该有:一台跑着 Ubuntu 22.04 的虚拟机,里面用 docker-compose 拉起了 MySQL/Redis/RabbitMQ/ES/Kibana/etcd/FastDFS 七大中间件加一个开发容器,xshell 能 2222 端口连进开发容器写代码。这就是微服务脚手架的地基------后面写业务代码、起 gRPC 服务、做服务注册发现,都是在这套地基上展开。
这套环境的好处是可复制 :你换台电脑,clone 同一个仓库,
docker-compose up -d,环境一模一样起来,不会出现"我电脑上能跑你电脑上跑不了"的玄学问题。这就是容器化的核心价值。