VagrantとAnsibleで作る一貫した開発環境:「自分のマシンでは動くのに」という悪夢を終わらせる

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

午前2時のデバッグの悪夢

時刻は午前2時15分。目は霞み、私は全く意味の分からないスタックトレースを凝視しています。コードは完璧なはずでした。自分のラップトップでは、アプリケーションは何の問題もなく動作しています。しかし、ステージングサーバーでは起動時にクラッシュします。原因は、私自身も気づいていなかった依存関係におけるOpenSSLのわずかなバージョンの違いでした。これが典型的な「自分のマシンでは動くのに」症候群です。おそらく、現代のソフトウェア開発においてエンジニアの工数を最も奪っている最大の要因でしょう。

何年もの間、私は新しく入社したメンバーが、ローカルマシンに本番環境を再現しようとして丸3日間を費やすのを見てきました。彼らは古びた50ステップもあるWikiページに従い、壁にぶつかり、シニアエンジニアに連絡し、また同じサイクルを繰り返します。それは手動でエラーが起きやすい、混乱した状態でした。ついに私は限界を迎え、ローカル開発環境を本番インフラと全く同じように、つまり「コード」として扱うことに決めました。

それ以来、私は20人以上の開発チームにこのワークフローを導入しました。その結果はどうだったでしょうか?12ステップに及ぶ手動の悪夢を、macOS、Linux、Windowsを問わず、10分足らずで完了するたった一つのコマンドへと変貌させたのです。

なぜVagrantとAnsibleなのか?

Dockerは業界の寵児ですが、万能薬ではありません。時には完全な仮想マシン(VM)が必要なこともあります。カーネルモジュールのテストや、複雑なiptablesルールのデバッグ、あるいはUbuntuの本番VPSをsystemdレベルまで再現したサンドボックスが必要な場合、Vagrantが最適なツールです。VagrantはVMのライフサイクル管理という重労働を担ってくれます。

しかし、起動したばかりのVagrantボックスはただの空白のキャンバスに過ぎません。PHP 8.2、Python 3.11、Nginxなど、スタックをインストールする必要があります。シェルスクリプトがよく使われますが、それらは非常に脆いことで知られています。一度実行したものを再度実行すると、壊れてしまうことが多々あります。Ansibleは「冪等性(べきとうせい)」によってこの問題を解決します。あるべき状態を定義すれば、Ansibleがそれを実現します。パッケージが既に存在すれば、そのまま次に進みます。不足していればインストールします。この2つを組み合わせることで、鉄壁の自動化レイヤーが構築されるのです。

自動化されたサンドボックスの構築

標準的なWebサーバーを立ち上げるために私が実際に使用している設計図を紹介します。Vagrantを使用してUbuntu 22.04のVMを作成し、Ansibleを使用してカスタム設定済みのNginxをレイヤーとして重ねます。

事前準備

開始するには、ホストマシンに3つのツールが必要です:

  • VirtualBox: 実際に仮想ハードウェアを動かすエンジン。
  • Vagrant: VirtualBoxと対話するマネージャー。
  • Ansible: 設定の頭脳(pip またはシステムのパッケージマネージャー経由でインストール)。

ステップ1:Vagrantfile

Vagrantfile は、ハードウェアの目録(マニフェスト)だと考えてください。RAMの割り当て、CPUコア数、ネットワーク設定などを規定します。新しいディレクトリを作成し、これを Vagrantfile として保存します:

Vagrant.configure("2") do |config|
  # 公式のUbuntu 22.04 LTS (Jammy Jellyfish) boxを使用
  config.vm.box = "ubuntu/jammy64"

  # ホストからブラウザでアクセスしやすくするための固定IP
  config.vm.network "private_network", ip: "192.168.56.10"

  config.vm.provider "virtualbox" do |vb|
    vb.memory = "2048" # Web開発には2GBが最適
    vb.cpus = 2
    vb.name = "itfromzero-dev-box"
  end

  # ソフトウェアレイヤーのためにAnsibleと連携
  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "setup.yml"
  end
end

ステップ2:Ansible Playbook

次に、setup.yml でソフトウェアの状態を定義します。コマンドの羅列ではなく、「何を望むか」を記述します。このプレイブックは、システムのキャッシュを更新し、Nginxをインストールし、サービスが稼働していることを確認します。これにより、チームの全開発者が全く同じバージョンのWebサーバーを実行していることが保証されます。

---
- name: 開発環境のプロビジョニング
  hosts: all
  become: yes
  tasks:
    - name: aptキャッシュの更新
      apt:
        update_cache: yes
        cache_valid_time: 3600

    - name: Nginxのインストール
      apt:
        name: nginx
        state: present

    - name: Nginxサービスの開始
      service:
        name: nginx
        state: started
        enabled: yes

    - name: カスタムインデックスページの作成
      copy:
        content: "<h1>環境準備完了 - Powered by ITFromZero</h1>"
        dest: /var/www/html/index.html
        mode: '0644'

ステップ3:環境の起動

ここからが魔法の本番です。これらのファイルがリポジトリにあれば、新しい開発者が実行する必要があるのは一つのコマンドだけです:

vagrant up

それだけです。VagrantがUbuntuイメージを取得し、ネットワークを設定し、残りをAnsibleに引き継ぎます。AnsibleはSSH経由でログインし、パッケージをインストールし、設定を検証します。5分から10分以内に、http://192.168.56.10 にアクセスしてサイトが稼働しているのを確認できます。手動での微調整は一切不要です。

現場で得た教訓

これを大規模に運用することで、いくつかの重要な教訓を得ました。第一に、すべてをバージョン固定することです。Ansibleのタスクで単に「nginxをインストール」と記述すると、ある開発者はバージョン1.18を入手し、来月参加した別の開発者は1.24を入手する可能性があります。構成ドリフトを防ぐために、name: nginx=1.18.0* のように明示的に指定してください。

第二に、共有フォルダ(Shared Folders)を活用することです。ターミナルウィンドウ内のVimで苦労する必要はありません。Vagrantはプロジェクトフォルダを自動的にVM内の /vagrant にマッピングします。ホストOSでVS CodeやJetBrainsを使い続けてください。変更は即座にLinuxのランタイムに同期されます。GUIの快適さと、本番級のバックエンドのパワーを両立できます。

最後に、vagrant destroy を受け入れることです。環境の挙動が怪しくなったら、1時間もかけてトラブルシューティングをしないでください。環境を破棄して vagrant up を実行しましょう。自動化によって環境をゼロから一気に再構築できないのであれば、その自動化は壊れているということです。「再構築の容易さ」をテストすることこそが、次のメンバーのオンボーディングを確実にスムーズにする唯一の方法です。

まとめ

手動のセットアップを捨てたことは、私の生産性における転換点となりました。もはやオンボーディングを恐れることはありませんし、午前2時の「環境の不一致」という怪談話も事実上消え去りました。Vagrantfile とAnsibleプレイブックに1時間を投資することで、チーム全体で何百時間ものデバッグ時間を節約できます。もし、あなたがまだローカルスタック構築のためにREADMEを読ませているなら、それはリスクの高いゲームをしているのと同じです。自動化を導入し、本当に重要なコードを書くことに戻りましょう。

Share: