告别“在我电脑上能跑”——用 Vagrant 一键搭建可复现的开发环境
前言
文章摘要
本文是一篇全面的 Vagrant 入门与实践指南,通过一个真实的 Web 应用Terramino演示了 Vagrant 的核心功能。主要内容包括:
Vagrant 解决的问题:解决传统开发环境中的“在我电脑上能跑”问题,通过“环境即代码”实现环境一致性、可移植性和隔离性。
安装与基础使用:详细介绍了 Vagrant 和 VirtualBox 的安装步骤,以及如何通过
vagrant init、vagrant up、vagrant ssh等命令创建和管理第一个虚拟机环境。自动化配置(Provisioning):使用 Shell 脚本在虚拟机启动时自动安装 Docker、克隆代码库并启动应用,实现环境的一键部署。
宿主机与虚拟机互联:通过端口转发和同步文件夹功能,实现宿主机直接访问虚拟机服务,并支持宿主机编码、虚拟机运行的开发模式。
多机环境模拟微服务架构:在一个 Vagrantfile 中定义多台虚拟机(redis、backend、frontend),模拟生产环境的微服务架构,并演示了服务故障模拟与恢复。
核心价值:Vagrant 通过 Vagrantfile、Box、Provisioning、端口转发、同步文件夹和多机环境等功能,为团队提供了可重复、可共享、可销毁的开发环境解决方案。
核心工作流:vagrant init→ 编写 Vagrantfile →vagrant up→vagrant ssh→ 开发 →vagrant destroy
你有没有遇到过这样的场景:项目在你自己电脑上跑得好好的,一提交代码,同事那边就各种报错?或者新同事入职,光是搭建开发环境就折腾了两三天?
这就是经典的“在我电脑上能跑”问题。而 Vagrant 的出现,正是为了终结这个噩梦。
Vagrant 是一个用于在单一工作流中构建和管理虚拟机环境的工具。它通过简单易用的工作流和自动化能力,大幅缩短开发环境搭建时间,提高环境一致性。
简单来说,Vagrant 就是用代码来定义、管理和销毁开发环境。你把环境配置写在一个文件里,团队成员只需要执行一条命令,就能得到一模一样的开发环境。
本文将以一个真实的 Web 应用Terramino为例,带你从零开始,一步步掌握 Vagrant 的核心用法。所有代码均可直接运行,建议你跟着操作一遍。
一、Vagrant 解决了什么问题?
1.1 传统开发环境的痛点
在传统的开发流程中,每个开发者需要手动在自己的机器上安装各种软件:数据库、缓存、编程语言运行时、Web 服务器……这个过程不仅耗时,而且极易出错:
- 配置漂移:开发 A 用的是 MySQL 5.7,开发 B 用的是 MySQL 8.0,同样的代码可能表现不同。
- 依赖冲突:项目 A 需要 Python 3.8,项目 B 需要 Python 3.11,手动切换版本非常麻烦。
- 环境不可复现:新成员入职、旧项目维护,环境搭建成本极高。
1.2 Vagrant 的解决方案
Vagrant 通过“环境即代码”的方式解决上述问题:
- 一致性:所有团队成员使用相同的 Vagrantfile,得到完全一致的环境。
- 可移植性:Vagrantfile 可以提交到 Git,任何人都可以一键复现。
- 隔离性:每个项目的环境相互隔离,互不干扰。
- 可销毁性:环境用完即焚,不留任何残留文件。
Vagrant 支持多种虚拟化提供商(Provider),包括 VirtualBox、VMware、Docker 等,你可以根据需求灵活选择。
二、安装 Vagrant
在开始之前,你需要先安装 Vagrant 和虚拟化软件。
2.1 安装 VirtualBox
Vagrant 默认使用 VirtualBox 作为虚拟化提供商。前往 VirtualBox 官网 下载并安装7.1.4 或更新版本。
macOS Apple Silicon 用户注意:如果遇到虚拟机启动问题,可能需要执行以下命令取消一个 VirtualBox 全局设置:
$ VBoxManage setextradata global"VBoxInternal/Devices/pcbios/0/Config/DebugLevel"
2.2 安装 Vagrant
HashiCorp 将 Vagrant 作为一个二进制包分发。根据你的操作系统选择对应的安装方式:
macOS / Linux
下载对应平台的压缩包,解压后将vagrant二进制文件移动到 PATH 中的某个目录(例如/usr/local/bin):
$mv~/Downloads/vagrant /usr/local/bin/Windows
下载安装包后按向导安装即可,安装程序会自动配置 PATH。
2.3 验证安装
打开终端,执行以下命令确认安装成功:
$ vagrant--helpUsage: vagrant[options]<command>[<args>]...如果看到命令列表,说明安装成功了。
三、第一个 Vagrant 环境
3.1 初始化项目
创建一个目录并初始化 Vagrant 环境:
$mkdirlearn-vagrant-get-started $cdlearn-vagrant-get-started $ vagrant init hashicorp-education/ubuntu-24-04 --box-version0.1.0 A`Vagrantfile`has been placedinthis directory. You are now ready to`vagrant up`your first virtual environment!这里我们指定了一个名为hashicorp-education/ubuntu-24-04的box(即虚拟机基础镜像)。Vagrant 会从 HCP Vagrant Registry 获取这个镜像。
执行完后,目录下会生成一个Vagrantfile:
Vagrant.configure("2")do|config|config.vm.box="hashicorp-education/ubuntu-24-04"config.vm.box_version="0.1.0"end这个文件就是整个环境的核心配置文件。一定要把它提交到 Git,这样团队成员都能复用同样的环境。
什么是 Box?Box 是 Vagrant 的基础镜像,相当于虚拟机的“初始状态”。Vagrant 从 Box 克隆出虚拟机,而不会修改 Box 本身,所以多个项目可以共用同一个 Box。
3.2 启动虚拟机
执行vagrant up启动环境:
$ vagrant up==>default: Box'hashicorp-education/ubuntu-24-04'could not be found. Attempting tofindand install... default: Box Provider: virtualbox...==>default: Machine booted and ready!Vagrant 会自动下载 Box、创建虚拟机并完成初始化。第一次启动可能需要几分钟,之后就会快很多。
3.3 连接虚拟机
使用 SSH 连接到虚拟机内部:
$ vagrantsshWelcome to Ubuntu24.04.1 LTS(GNU/Linux6.8.0-51-generic aarch64)进去后可以执行任何 Linux 命令:
$ lsb_release-aDistributor ID: Ubuntu Description: Ubuntu24.04.1 LTS Release:24.04退出虚拟机:
$logout3.4 生命周期管理
Vagrant 提供了一套完整的生命周期管理命令:
| 命令 | 作用 |
|---|---|
vagrant up | 启动虚拟机(首次会创建) |
vagrant ssh | SSH 连接到虚拟机 |
vagrant suspend | 挂起虚拟机(保存内存状态) |
vagrant resume | 恢复被挂起的虚拟机 |
vagrant halt | 优雅关机 |
vagrant destroy | 销毁虚拟机(删除所有数据) |
vagrant box remove | 删除已下载的 Box 镜像 |
注意:
vagrant destroy会删除虚拟机,但不会删除 Box 文件。Box 可以被其他项目复用。
四、Provisioning:自动化配置环境
手动在虚拟机里安装软件太麻烦了。Vagrant 的Provisioning(预配置)功能可以在虚拟机启动时自动执行脚本,完成环境初始化。
4.1 创建安装脚本
我们来为 Terramino 应用安装 Docker 和依赖。创建一个install-dependencies.sh文件:
#!/bin/bash# Install dependencies for Terramino demo app# Update package listapt-getupdate# Install required packagesapt-getinstall-yca-certificatescurlgnupggit# Add Docker's official GPG keyinstall-m0755-d/etc/apt/keyringscurl-fsSLhttps://download.docker.com/linux/ubuntu/gpg|gpg--dearmor-o/etc/apt/keyrings/docker.gpgchmoda+r /etc/apt/keyrings/docker.gpg# Add Docker repositoryecho\"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ "$(./etc/os-release&&echo"$VERSION_CODENAME")" stable"|\tee/etc/apt/sources.list.d/docker.list>/dev/null# Install Docker packagesapt-getupdateapt-getinstall-ydocker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin# Add vagrant user to docker groupusermod-aGdockervagrant# Clone Terramino repositoryif[!-d"/home/vagrant/terramino-go/.git"];thencd/home/vagrantrm-rfterramino-gogitclone https://github.com/hashicorp-education/terramino-go.gitcdterramino-gogitcheckout containerizedfi# Create reload scriptcat>/usr/local/bin/reload-terramino<<'EOF' #!/bin/bash cd /home/vagrant/terramino-go docker compose down docker compose build --no-cache docker compose up -d EOFchmod+x /usr/local/bin/reload-terramino# Add aliasesecho'alias play="docker compose -f /home/vagrant/terramino-go/docker-compose.yml exec -it backend ./terramino-cli"'>>/home/vagrant/.bashrcecho'alias reload="sudo /usr/local/bin/reload-terramino"'>>/home/vagrant/.bashrcecho"source /home/vagrant/.bashrc">>/home/vagrant/.bash_profile然后让脚本可执行:
$chmod+x install-dependencies.sh4.2 更新 Vagrantfile
在 Vagrantfile 中添加 provisioning 配置:
Vagrant.configure("2")do|config|config.vm.box="hashicorp-education/ubuntu-24-04"config.vm.box_version="0.1.0"# Install Docker and dependenciesconfig.vm.provision"shell",name:"install-dependencies",path:"install-dependencies.sh"# Start Terraminoconfig.vm.provision"shell",name:"start-terramino",inline:<<-SHELLcd /home/vagrant/terramino-go docker compose up -dSHELL# Reload Terramino (only when explicitly invoked)config.vm.provision"shell",name:"reload-terramino",run:"never",inline:<<-SHELL/usr/local/bin/reload-terraminoSHELLend这里定义了三个 provisioner:
install-dependencies:安装 Docker 和依赖start-terramino:启动 Terramino 应用reload-terramino:设置run: "never",只有手动指定时才会执行
4.3 执行 Provisioning
重新创建虚拟机并执行所有 Provisioning 脚本:
$ vagrant up--provision如果虚拟机已在运行,可以用vagrant provision重新执行:
$ vagrant provision4.4 测试应用
SSH 进入虚拟机,测试 Terramino 是否正常运行:
$ vagrantssh$curllocalhost:8080 Terramino – HashiCorp Demo App https://developer.hashicorp.com/运行游戏:
$ play Terramino CLI Controls: ← →:Move left/right ↑:Rotate ↓:Soft drop Space:Hard drop q:Quit Press Enter to start...五、共享资源:宿主机与虚拟机互联
5.1 端口转发
虚拟机内部的服务默认只能在虚拟机内部访问。通过端口转发,我们可以从宿主机直接访问虚拟机里的服务。
在 Vagrantfile 中添加端口映射:
# Forward ports for Terramino (8081 for frontend, 8080 for backend)config.vm.network"forwarded_port",guest:8080,host:8080config.vm.network"forwarded_port",guest:8081,host:8081重新加载虚拟机使配置生效:
$ vagrant reload--provision现在,在宿主机浏览器中访问http://localhost:8081,就能直接玩 Terramino 了。
5.2 同步文件夹
更强大的功能是同步文件夹(synced folders)——宿主机和虚拟机之间的目录双向同步。你在宿主机用 IDE 写代码,虚拟机里自动同步更新。
在 Vagrantfile 中添加:
# Sync the terramino-go directoryconfig.vm.synced_folder"./terramino-go","/home/vagrant/terramino-go",create:true重新加载:
$ vagrant reload--provision5.3 验证同步
现在在宿主机上修改terramino-go/main.go,添加一个健康检查接口:
http.HandleFunc("/health",func(w http.ResponseWriter,r*http.Request){w.Write([]byte("OK"))})然后在宿主机上验证:
$curllocalhost:8080/health OK修改实时生效!这就是同步文件夹的威力——宿主机编码,虚拟机运行。
如果销毁虚拟机(vagrant destroy),本地目录不会被删除,它是数据的“源”。
六、多机环境:模拟微服务架构
真实的生产环境往往由多个服务组成。Vagrant 的多机(Multi-Machine)功能允许你在一个 Vagrantfile 中定义多台虚拟机,模拟微服务架构。
6.1 架构设计
我们将 Terramino 拆分为三个服务,分别运行在三台独立的虚拟机上:
| 服务 | 作用 | 端口 | IP |
|---|---|---|---|
| redis | 存储游戏高分 | 6379 | 192.168.56.10 |
| backend | 游戏逻辑和 API | 8080 | 192.168.56.11 |
| frontend | Web 界面 | 8081 | 192.168.56.12 |
6.2 创建公共依赖脚本
先创建一个common-dependencies.sh,在所有虚拟机上安装 Docker:
#!/bin/bash# Install Docker and clone repo (common dependencies for multi-machine tutorial)apt-getupdateapt-getinstall-yca-certificatescurlgnupggitavahi-daemon libnss-mdnsinstall-m0755-d/etc/apt/keyringscurl-fsSLhttps://download.docker.com/linux/ubuntu/gpg|gpg--dearmor-o/etc/apt/keyrings/docker.gpgchmoda+r /etc/apt/keyrings/docker.gpgecho"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu$(./etc/os-release&&echo"$VERSION_CODENAME")stable"|tee/etc/apt/sources.list.d/docker.list>/dev/nullapt-getupdateapt-getinstall-ydocker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginusermod-aGdockervagrant# Clone repoif[!-d"/home/vagrant/terramino-go/.git"];thengitclone https://github.com/hashicorp-education/terramino-go.git /home/vagrant/terramino-gocd/home/vagrant/terramino-gogitcheckout containerizedfi$chmod+x common-dependencies.sh6.3 编写多机 Vagrantfile
# Service configuration referenceSERVICES={'redis'=>{ip:'192.168.56.10',ports:{6379=>6379}},'backend'=>{ip:'192.168.56.11',ports:{8080=>8080}},'frontend'=>{ip:'192.168.56.12',ports:{8081=>8081}}}Vagrant.configure("2")do|config|# Common configurationconfig.vm.box="hashicorp-education/ubuntu-24-04"config.vm.box_version="0.1.0"# Common provisioning script for all VMsconfig.vm.provision"shell",name:"common",path:"common-dependencies.sh"# Redis Serverconfig.vm.define"redis"do|redis|redis.vm.hostname="redis"redis.vm.network"private_network",ip:SERVICES['redis'][:ip]redis.vm.network"forwarded_port",guest:6379,host:6379redis.vm.synced_folder"./redis/terramino-go","/home/vagrant/terramino-go",create:trueredis.vm.provision"shell",name:"start-redis",inline:<<-SHELLcd /home/vagrant/terramino-go docker compose up -d redisSHELLend# Backend Serverconfig.vm.define"backend"do|backend|backend.vm.hostname="backend"backend.vm.network"private_network",ip:SERVICES['backend'][:ip]backend.vm.network"forwarded_port",guest:8080,host:8080backend.vm.synced_folder"./backend/terramino-go","/home/vagrant/terramino-go",create:truebackend.vm.provision"shell",name:"start-backend",inline:<<-SHELLcd /home/vagrant/terramino-go # Get Redis IP dynamically for i in {1..30}; do if REDIS_IP=$(getent hosts redis.local | awk '{print $1}'); then break fi echo "Waiting for redis.local to be resolvable..." sleep 2 done docker build -f Dockerfile.backend -t backend . docker run -d -p 8080:8080 \ -e REDIS_HOST=redis.local \ -e REDIS_PORT=6379 \ -e TERRAMINO_PORT=8080 \ --add-host redis.local:${REDIS_IP} \ backendSHELLend# Frontend Serverconfig.vm.define"frontend"do|frontend|frontend.vm.hostname="frontend"frontend.vm.network"private_network",ip:SERVICES['frontend'][:ip]frontend.vm.network"forwarded_port",guest:8081,host:8081frontend.vm.synced_folder"./frontend/terramino-go","/home/vagrant/terramino-go",create:truefrontend.vm.provision"shell",name:"start-frontend",inline:<<-SHELLcd /home/vagrant/terramino-go # Update nginx.conf to use backend hostname sed -i 's#proxy_pass http://backend:8080#proxy_pass http://backend.local:8080#' nginx.conf # Get backend IP dynamically for i in {1..30}; do if BACKEND_IP=$(getent hosts backend.local | awk '{print $1}'); then break fi echo "Waiting for backend.local to be resolvable..." sleep 2 done docker build -f Dockerfile.frontend -t frontend . docker run -d -p 8081:8081 \ --add-host backend.local:${BACKEND_IP} \ frontendSHELLendend6.4 启动多机环境
启动所有虚拟机:
$ vagrant up也可以单独启动某台:
$ vagrant up redis $ vagrant up backend $ vagrant up frontend查看状态:
$ vagrant status Current machine states: redis running(virtualbox)backend running(virtualbox)frontend running(virtualbox)6.5 模拟服务故障
多机环境的另一个价值是模拟生产故障。挂起 backend 服务:
$ vagrantsuspendbackend刷新浏览器http://localhost:8081,高分区域会显示SVC_DOWN,说明前端检测到了后端不可用。
恢复 backend:
$ vagrant resume backend刷新页面,功能恢复正常。
这种能力对于测试系统的容错性非常有价值。
七、总结
通过本文的实践,你应该已经掌握了 Vagrant 的核心能力:
| 功能 | 解决的问题 |
|---|---|
| Vagrantfile | 用代码定义环境,可提交 Git |
| Box | 基础镜像复用,加速环境创建 |
| Provisioning | 自动化安装软件和配置 |
| 端口转发 | 从宿主机访问虚拟机服务 |
| 同步文件夹 | 宿主机编码,虚拟机运行 |
| 多机环境 | 模拟微服务架构,测试容错 |
核心工作流
vagrant init → 编写 Vagrantfile → vagrant up → vagrant ssh → 开发 → vagrant destroy下一步
- 在 HCP Vagrant Registry 发现更多 box
- 探索 Vagrant 插件生态
- 学习与 Ansible、Chef、Puppet 等配置管理工具集成
记住:Vagrantfile 一定要提交到 Git。从此以后,新成员加入项目只需要两步:git clone和vagrant up。
