Vagrantで開発環境を構築するガイド:「自分のマシンでは動く」問題に終止符を

Virtualization tutorial - IT technology blog
Virtualization tutorial - IT technology blog

「自分のマシンでは動く」という罠と実際の話

stagingへのデプロイに失敗したとき「自分のマシンでは問題なく動く」という言葉、何度聞いたことか。この光景にはもう見慣れてしまった。あるdeveloperはUbuntu 20.04を使い、別の人はmacOS Ventura、上司はWindows 11で動かしている——当然、Node.jsのバージョンもそれぞれ違えば、MySQLの設定も異なり、Pythonのパスに至っては語る気にもならない。

そのコストは明らかだ。チームに新しく参加したメンバーが環境構築だけで2〜3日費やし、コードを1行も書けないまま時間が過ぎていく。バグが出ても、コードが原因なのか環境が原因なのか誰にもわからない。デバッグに何時間もかけてようやく気づくのは、パッケージのバージョンが1つ違っていたというだけのこと。developerが怠惰なのではない——誰も積極的に解決しようとしない構造的な問題なのだ。

なぜ開発環境は「ドリフト」してしまうのか

分解してみると、いくつかの根本的な原因が見えてくる:

  • それぞれ異なるOSを使っている — macOS、Windows、Ubuntu — 同じコマンドでも動作が全く異なることがあり、特にファイルのパーミッションや改行コードに関して顕著だ
  • システムバージョンをロックする仕組みがないpackage.jsonでNodeパッケージはロックできるが、OSのバージョンやOpenSSL、glibcをロックする手段はない
  • READMEを見ながらの手動セットアップ — READMEが古かったり、手順が抜けていたり、6ヶ月前にインストールした依存パッケージをドキュメント化し忘れていたりする
  • Accumulated drift — developerはそれぞれ自分のタイミングでマシンを更新するため、徐々に環境が乖離していき、不思議なバグが出るまで誰も気づかない

私はProxmox VEで12台のVMとコンテナを管理するhomelabを運用している——productionに移行する前にあらゆる設定をテストするための実験場だ。そこで多くのVMを管理するうちに実感したことがある:VMの手動作成はスケールしない、プロセス全体を自動化しなければならないと。

解決策:手動から自動化まで

方法1:手動VM + スナップショット

「標準」のVMを作り、スナップショットを取って各developerにクローンする。一見良さそうだが、実際にはスナップショットファイルが数GBにもなり、gitにコミットできない。新しい依存関係を追加する際には全員に手動更新を通知しなければならない。さらに重要なことに:「標準」が何をどの順番でインストールするものなのか誰も把握しておらず——再現性がない。

方法2:Docker

Dockerはアプリのパッケージングが非常に得意だ——まさにそのために作られたツールである。しかし、kernel-levelのアクセスやsystemd、またはマルチサーバートポロジーのシミュレーションが必要な開発環境には、コンテナでは不十分だ。さらに、Windows/macOS上ではDocker Desktopが内部の隠れたVM上で動作しており、小さなファイルが多いプロジェクト(LaravelやWordPressなど)でのボリュームマウントのパフォーマンスは筆舌に尽くしがたいほど悪い。

方法3:Vagrant——コードでVMを定義する

HashiCorp(TerraformやVaultと同じ会社)のVagrantは、Vagrantfileと呼ばれるテキストファイルでVMを定義できる。OS、RAM、CPU、ネットワーク、プリインストールするソフトウェアなど、すべての設定がこのファイルに記述され、プロジェクトのコードと一緒にgitにコミットできる。

なぜVagrantfileがこの問題に効くのか

主な4つの強みが、それぞれ前述の問題に的確に対応している:

  • 再現性(Reproducible):どのマシンでvagrant upを実行しても、まったく同じ環境が構築される
  • バージョン管理(Version controlled):純粋なテキストファイルなのでgitにコミットでき、変更履歴が明確に残る
  • 共有可能(Shareable):新しいdeveloperがリポジトリをクローンしてvagrant upを実行するだけで完了——20ページのREADMEを読む必要がない
  • 使い捨て可能(Disposable):作業が終わったらvagrant destroyして、いつでもvagrant upで最初からやり直せる——100%クリーンな環境が手に入る

Vagrantをゼロからインストールする

VagrantにはハイパーバイザーとしてVirtualBox(無料)が必要だ。以下の順番でインストールしていく:

ステップ1:VirtualBoxのインストール

# Ubuntu/Debian
sudo apt install virtualbox

# macOS (dùng Homebrew)
brew install --cask virtualbox

ステップ2:Vagrantのインストール

# Ubuntu/Debian — thêm repo chính thức của HashiCorp
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
brew install --cask vagrant

# Kiểm tra cài đặt thành công
vagrant --version

LEMPスタックのWebプロジェクト向け実践Vagrantfile

以下は私がLEMPスタック(Linux + Nginx + MySQL + PHP)でよく使うVagrantfileだ:

Vagrant.configure("2") do |config|
  # Base box: Ubuntu 22.04 LTS
  config.vm.box = "ubuntu/jammy64"
  config.vm.hostname = "dev-lemp"

  # Port forwarding: truy cập web qua localhost:8080
  config.vm.network "forwarded_port", guest: 80, host: 8080
  config.vm.network "forwarded_port", guest: 3306, host: 33060

  # Private network để SSH và kết nối nội bộ
  config.vm.network "private_network", ip: "192.168.56.10"

  # Sync thư mục code từ máy host vào VM
  config.vm.synced_folder "./src", "/var/www/html"

  # Cấu hình tài nguyên VM
  config.vm.provider "virtualbox" do |vb|
    vb.name   = "dev-lemp"
    vb.memory = "2048"
    vb.cpus   = 2
  end

  # Provisioning: tự động cài phần mềm khi vagrant up lần đầu
  config.vm.provision "shell", inline: <<-SHELL
    apt-get update -y
    apt-get install -y nginx mysql-server php8.1-fpm php8.1-mysql
    systemctl enable nginx
    systemctl start nginx
    echo "Môi trường dev LEMP sẵn sàng!"
  SHELL
end

このファイルがあれば、新しいdeveloperはリポジトリをクローンして、たった2つのコマンドを実行するだけで完了だ:

# Lần đầu: tạo VM và cài đặt tất cả (mất khoảng 5-10 phút)
vagrant up

# SSH vào VM
vagrant ssh

# Tắt VM khi không dùng (giữ nguyên state)
vagrant halt

# Bật lại VM (nhanh hơn, không provision lại)
vagrant up

# Reload và re-provision khi sửa Vagrantfile
vagrant reload --provision

# Xóa hoàn toàn VM
vagrant destroy

SSH経由でIDEをVMに接続する

VSCode Remote SSHとJetBrains Gatewayはどちらも、Vagrant VMへの直接接続をサポートしている。SSH設定は次のコマンドで取得できる:

# In ra thông tin SSH config để dùng với IDE
vagrant ssh-config

# Hoặc thêm vào ~/.ssh/config luôn
vagrant ssh-config >> ~/.ssh/config

あとはVSCodeでRemote-SSH → Connect to Host → dev-lempを選択するだけだ。コードはホストマシンで書き、VMの中で直接実行される——同じ物理マシン上にあるため、レイテンシはほぼゼロだ。

VagrantとDocker——どちらを使うべきか

これはよく聞かれる質問だ。両方を何年も使ってきた経験から、簡潔にまとめると:

  • Vagrantを使う:本番サーバーをシミュレートする必要がある場合——フルOS、systemd、または複数台構成のトポロジー(例:Webサーバー1台 + DBサーバー1台 + ロードバランサー1台)
  • Dockerを使う:軽快な開発環境が必要で、アプリがすでにコンテナ化されている場合
  • 両方を使う:VagrantでVMを作り、その中でDockerを動かす。私のhomelabでの構成:ProxmoxでVM作成 → VagrantをVM上で動かす → VagrantがDockerをプロビジョニング——3層構成だが、各層の役割は明確だ

チームが環境の不統一問題に悩まされているなら、リポジトリにVagrantfileを1つ追加するのが最も手っ取り早い解決策だ。ファイルの作成に30分かければ、その後のデバッグに費やす何十時間もの時間を節約できる——そして何より、スプリントレビューのたびに「どのマシンのせいだ」という議論が生まれなくなる。

Share: