深夜2時、dev環境が燃え上がる
深夜2時17分。stagingへのデプロイが完了し、すべて正常に動作していた。翌朝、developerからバグレポートが届く:「自分のマシンでは再現できない。」5分間画面を眺めて、ようやく原因に気づいた — そのdevはUbuntu 20.04を使っていて、サーバーはUbuntu 22.04で動いていて、自分はmacOS上でデバッグしていた。三つの異なる環境、一つのcodebase。
Vagrantはまさにその痛みを解決してくれる。「最高」でも「最先端」でもないが、理由はシンプルだ:VagrantfileをgitにコミットしておけGe ば、Mac、WindowsどのマシンでcloneしてもGeGe vagrant upするだけで同じ環境が手に入る。
アプローチの比較:手動VM、Docker、Vagrant
3つのツール、3つの設計思想。選ぶ前にそれぞれを正しく理解しよう — 間違ったツールを選んで痛い目を見たのは、これが初めてではない:
Approach 1: 手動VM (VirtualBox/VMware)
VMを手動で作成し、OSをインストールし、dependenciesをセットアップして、snapshotを取る。自分のhomelabではこの方法を使っていた — 現在はProxmox VEで12台のVMとcontainerを管理し、productionに上げる前に何でも試せるplaygroundとして活用している。homelabならいいが、dev teamでやると悪夢になる。
- configをcodeとして共有できない
- 各devが手動でsetup → 環境が統一されない
- 新メンバーのonboardingに丸一日かかる
- 環境をversion controlできない
Approach 2: Docker + Docker Compose
Container-based、軽量、portable。microservicesで最もよく使うアプローチだ。ただし、「本番サーバーに近い環境」が必要な場合にはDockerにも弱点がある:
- ContainerはhostとkernelをShareする — full OS isolationではない
- systemd、kernel module、または実際のnetwork stackのテストが必要なツールもある
- Mac/WindowsでDocker Desktopを動かすと、Linux nativeと比べてoverheadが生じ、挙動も異なる — bind mountの処理方法の違いで、Linuxでしか再現できないpermissionのバグを2時間かけてデバッグしたことがある。Mac上ではfile 777でも何も問題が起きなかった
- firewall設定、disk partitioning、kernel tuningのテストには向かない
Approach 3: Vagrant
VagrantはVMの作成と管理を一つのconfig file — Vagrantfile — でラップしてくれる。Dockerの代替ではなく、full VMが必要なケースでDockerを補完するものだ。
- VagrantfileをgitにcommitできるためGeGe、環境がcodeになりversion historyが残る
- Full VM isolation、kernel levelの検証も可能
- 複数のproviderをサポート:VirtualBox、VMware、Hyper-V、libvirt
- Vagrant VMの中でDockerを動かすことも可能
Vagrantのメリット・デメリット分析
メリット
- Infrastructure as Code:VagrantfileはRuby DSLで記述され、読みやすく編集しやすく、gitにcommitできる
- Reproducible:
vagrant destroy && vagrant upで、まっさらな環境を再現できる - Multi-machine:一つのVagrantfileで複数のVMを定義できる(web + db + cache)
- Provisioning統合:Shell script、Ansible、Chef、Puppetがすぐに使える
- Shared folders:codeはhostに置き、VMの中で実行 — ファイルをコピーせずに使い慣れたIDEで編集できる
デメリット
- Dockerより重い:Full VMはcontainerより遥かにRAMとdiskを消費する
- 起動が遅い:VMの起動に30〜60秒かかるが、containerなら数秒で済む
- Hypervisorのオーバーヘッド:VirtualBox(デフォルト)が必要 — WindowsではHyper-Vの競合によりDocker Desktopとconflictする可能性がある
- Microservicesには不向き:serviceが20個あるようなprojectには、Docker Composeの方が遥かに合理的
VagrantかDockerか:どちらをいつ使う?
間違ったツール選択を何度も繰り返した末に辿り着いた、シンプルなルール:
- Dockerを使う:web service、microservices、素早いspin up/down、CI/CDパイプライン
- Vagrantを使う:system levelの検証、multi-serverのシミュレーション、チームにMac/WindowsユーザーがいるがサーバーはLinux、またはkernelとnetworkレベルでproductionと100%同一の環境が必要な場合
自分が最もよくVagrantを使う実際のユースケース:productionに適用する前のAnsible playbookのテスト、複数VMでのnetwork topologyのシミュレーション、特定のkernel versionでしか再現しないバグの再現。
Vagrantをゼロからセットアップする手順
ステップ1:インストール
VagrantにはVM providerが必要だ。デフォルトではVirtualBoxを使う:
# Ubuntu/Debian — VirtualBoxをインストール
sudo apt update && sudo apt install -y virtualbox
# HashiCorpの公式リポジトリからVagrantをインストール
wget -O- https://apt.releases.hashicorp.com/gpg | \
sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install vagrant
# macOS (Homebrew)
brew tap hashicorp/tap
brew install hashicorp/tap/vagrant
# インストール確認
vagrant --version
# Vagrant 2.4.x
ステップ2:最初のVagrantfileを作成する
projectディレクトリを作成してVagrantfileを書く。vagrant initで生成するより手書きの方が多い — 生成されるfileはコメントで埋め尽くされていて、読むだけで半日かかる。自分で書いた方が短くてずっと分かりやすい。以下はシンプルなLAMP stackのconfig:
Vagrant.configure("2") do |config|
# Base box — Ubuntu 22.04 LTS
config.vm.box = "ubuntu/jammy64"
config.vm.box_check_update = false
# port 80と3306をhostに転送
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.network "forwarded_port", guest: 3306, host: 3306
# 静的IPのprivate network(覚えやすい)
config.vm.network "private_network", ip: "192.168.56.10"
# Shared folder:hostのcodeをVM内の/var/wwwにマウント
config.vm.synced_folder "./src", "/var/www/html"
# VMリソース
config.vm.provider "virtualbox" do |vb|
vb.memory = "2048"
vb.cpus = 2
vb.name = "dev-lamp"
end
# Provisioning:VM初回作成時にshell scriptを実行
config.vm.provision "shell", inline: <<-SHELL
apt-get update -q
apt-get install -y -q apache2 mysql-server php php-mysql
systemctl enable apache2 mysql
systemctl start apache2 mysql
echo "完了!アクセス先: http://192.168.56.10"
SHELL
end
ステップ3:VMを起動してSSH接続する
# VMを作成・起動(初回はboxのダウンロード ~500MB)
vagrant up
# VMにSSH接続
vagrant ssh
# VM内で確認
uname -a
# Linux ubuntu-jammy 5.15.0-xx-generic ...
# 終了
exit
ステップ4:日常のワークフロー
# VMを起動(作成済みのため初回より速い)
vagrant up
# 一時停止 — RAMの状態を保持(最速)
vagrant suspend
vagrant resume
# VMを完全にシャットダウン(RAMを解放)
vagrant halt
# VMを完全に削除して最初から再作成
vagrant destroy -f && vagrant up
ステップ5:Multi-machine — productionアーキテクチャをシミュレートする
これこそが手動セットアップと比べてVagrantが真に光る部分だ。web serverとdatabase serverを分離してシミュレートし、まさにproductionと同じ構成を再現する:
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/jammy64"
# Webサーバー
config.vm.define "web" do |web|
web.vm.hostname = "web-server"
web.vm.network "private_network", ip: "192.168.56.10"
web.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
end
web.vm.provision "shell", inline: "apt-get install -y -q nginx"
end
# データベースサーバー
config.vm.define "db" do |db|
db.vm.hostname = "db-server"
db.vm.network "private_network", ip: "192.168.56.11"
db.vm.provider "virtualbox" do |vb|
vb.memory = "2048"
end
db.vm.provision "shell", inline: "apt-get install -y -q postgresql"
end
end
# すべてのmachineを起動
vagrant up
# web serverのみ起動
vagrant up web
# 各machineにSSH接続
vagrant ssh web
vagrant ssh db
Tip:shell inlineの代わりにAnsible provisionerを使う
shell script inlineはさっと試すには十分だ。本格的なprojectでは、Ansible provisionerに切り替えることにしている:
config.vm.provision "ansible" do |ansible|
ansible.playbook = "provision/setup.yml"
ansible.verbose = "v"
end
一石二鳥の利点:同じplaybookを本番の実サーバーにそのままapplyできる — まさに「configの一行一行までproductionと同じdev環境」を実現できる。
実践的な結論
環境の不一致のせいで何度も深夜2時にデバッグを繰り返した末に至った結論:どちらかを選ぶ必要はない。VagrantでVMを動かし、そのVM内でDockerのcontainerを動かす — この組み合わせでほぼすべてのユースケースをカバーできる。
「サーバーでしか起きない」バグを再現したい?新メンバーをsetupで半日潰さずにonboardしたい?productionに適用する前にAnsible playbookをテストしたい?git repoのVagrantfileが答えだ。vagrant upで完了。
