简体   繁体   中英

Grails watch files doesn't work inside Docker container running inside a Vagrant virtual machine

I have a fairly nested structure:

  1. MacOSX workstation running a...
  2. Vagrant VirtualBox virtual machine with ubuntu/trusty64 running a...
  3. Docker container running...
  4. my application written in Grails

Every layer is configured in such a way as to share a portion of the file system from the layer above. This way:

  • Vagrant, with config.vm.synced_folder directive in Vagrantfile
  • Docker, with the -v command like switch and VOLUME directive in the Dockerfile

This way I can do development on my workstation and the Grails application at the bottom should (ideally) detect changes and recompile/reload on the fly. This is a feature that used to work when I was running the same application straight on the MacOSX, but now grails seems totally unaware of file changes. Of course, if I open the files with an editor (inside the Docker container) they are indeed changed and in fact if I stop/restart the grails app the new code is used.

I don't know how grails implements the watch strategy, but if it depends on some operating system level feature I suspect that file change notifications get lost somewhere in the chain.

Anyone has an idea of what could be the cause and/or how I could go about debugging this?

There are two ways to detect file changes (that I'm aware of):

Polling , which means checking timestamps of all files in a folder at a certain interval. Getting to "near instant" change detection requires very short intervals. This is CPU and disk intensive.

OS Events (inotify on Linux, FSEvents on OS X), where changes are detectable because file operations pass through the OS subsystems. This is easy on the CPU and disk.

Network File Systems (NFS) and the like don't generate events. Since file changes do not pass through the guest OS subsystem, the OS is not aware of changes; only the OS making the changes (OS X) knows about them.

Grails and many other File Watcher tools depend on FSEvents or inotify (or similar) events.

So what to do? It's not practical to 'broadcast' NFS changes from host to all guests under normal circumstances, considering the traffic that would potentially generate. However, I am of the opinion that VirtualBox shares should count as a special exception...

A mechanism to bridge this gap could involve a process that watches the host for changes and triggers a synchronization on the guest.

Check these articles for some interesting ideas and solutions, involving some type of rsync operation:

http://drunomics.com/en/blog/syncd-sync-changes-vagrant-box (Linux) https://github.com/ggreer/fsevents-tools (OS X)

Rsync-ing to a non-NFS folder on your guest (Docker) instance has the additional advantage that I/O performance increases dramatically. VirtualBox shares are just painfully slow.

Update!

Here's what I did. First install lsyncd (OS X example, more info at http://kesar.es/tag/lsyncd/ ):

brew install lsyncd

Inside my Vagrant folder on my Mac, I created the file lsyncd.lua :

settings {
    logfile = "./lsyncd.log",
    statusFile = "./lsyncd.status",
    nodaemon = true,
    pidfile = "./lsyncd.pid",
    inotifyMode = "CloseWrite or Modify",
}

sync {
    default.rsync,
    delay = 2,
    source = "./demo",
    target = "vagrant@localhost:~/demo",
    rsync = {
        binary   = "/usr/bin/rsync",
        protect_args = false,
        archive = true,
        compress = false,
        whole_file = false,
        rsh = "/usr/bin/ssh -p 2222 -o StrictHostKeyChecking=no"
    },
}

What this does, is sync the folder demo inside my Vagrant folder to the guest OS in /home/vagrant/demo . Note that you need to set up login with SSH keys to make this process frictionless.

Then, with the vagrant VM running, I kicked off the lsyncd process. The -log Exec is optional; it logs its activity to the stdout:

sudo lsyncd lsyncd.lua -log Exec 

On the vagrant VM I started Grails (2.4.4) in my synced folder:

cd /home/vagrant/demo
grails -reloading run-app

Back on my Mac in IntelliJ I edited a Controller class. It nearly immediately triggered lsyncd (2 sec delay) and quickly after that I confirmed Grails recompiled the class!

To summarize:

  • Edit your project files on your Mac, execute on your VM
  • Use lsyncd to rsync your changes to a folder inside your VM
  • Grails notices the changes and triggers a reload
  • Much faster disk performance by not using VirtualBox share

Issues: Textmate triggers a type of FSEvent that lsyncd does not (yet) recognize, so changes are not detected. Vim and IntelliJ were fine, though.

Hope this helps someone! Took me a day to figure this stuff out.

The best way I have found to have filesystem notifications visible within the container was as follows:

  1. Create two folders, one to map the project and a "mirror"
  2. Map the project in the first folder
  3. Keep a background script running in the container, rsyncing the project folder to "mirror"
  4. Run the project from "mirror"

It may not be the most efficient or most elegant way, but this way was transparent to users of the container. No additional script needs to be run.

Not tested in a larger project, but in my case I did not realize performance issues.

https://github.com/altieres/docker-jekyll-s3

Vagrant already includes some options for rsync, so it's not necessary to install a special program on the host machine.

In Vagrantfile, I configured rsync:

config.vm.synced_folder ".", "/vagrant", type: "rsync",  rsync__exclude: [ "./build", ".git/" ]

Then on command line (in host), I run:

vagrant rsync

Which performs a single synchronization from host to guest.

or

vagrant rsync-auto

Which runs automatically when changes on host are detected.

See more at Vagrant rsync Documentation and rsync-auto

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM