Run a remote script/application in detached mode in Ansible

ansiblebashscript

I am having trouble running a remote application startup script "detachedly" from an Ansible playbook. The script will run, but I can't get it to get/stay detached. I am probably doing something wrong, but what?

Here is my reproducer.

  1. My remote Java application in Test.java runs for 10 seconds:

    class Test {
        public static void main(String[] args) {
            for (int I = 1; I <= 10; i++) {
                System.out.println("Hello world " + I);
    
                try { Thread.sleep(1000);
                } catch (Exception e) { System.out.println("Exception caught: " + e);
                }
    } } }
    

Compiling this to Test.class (javac Test.java), then running that class with "java Test" works as expected (gives 10 output messages and then exits).

  1. My executable shell script (as in chmod 755) running this application looks as follows:

    #!/bin/bash
    java Test &
    

Running this manually is also perfectly fine: Java app runs and generates the same standard output in my console, but shell script has exited and I am back in control of my bash session.

  1. Now to run it through an ansible playbook from another server. I tried using the "command" module and the "shell" module in different ways but to no avail…

    ---
    - hosts: vagrant1
      gather_facts: false
    
      tasks:
      - debug: msg="Running test app through Ansible shell module..."
    
      - name: Start application
        shell: "/tmp/test.sh"
        args:
          chdir: "/tmp"
          executable: "/bin/bash"
    
      - debug: msg="Running test app through Ansible command module..."
    
      - name: Start application
        command: "nohup /tmp/test.sh &"
        args:
          chdir: "/tmp"
    

All of this runs just fine, i.e. the shell script runs, the Java application runs and does its thing (i.e. run for 10 seconds, generate output and quit). But ansible-playbook runs until the Java application has finished and then returns the output the Java application generated. Why won't it just detach the shell script and finish the playbook?

The ansible-playbook output is:

    monsterkill@monsterkill-ub-dt:~/playbooks$ ansible-playbook testrun.yaml -v

    PLAY [vagrant1] *************************************************************** 

    TASK: [debug msg="Running test app through Ansible shell module..."] ********** 
    ok: [vagrant1] => {
        "msg": "Running test app through Ansible shell module..."
    }

    TASK: [Start application] ***************************************************** 
    changed: [vagrant1] => {"changed": true, "cmd": " /tmp/test.sh ", "delta": "0:00:10.052927", "end": "2015-01-29 00:14:43.327418", "rc": 0, "start": "2015-01-29 00:14:33.274491", "stderr": "", "stdout": "Hello world 1\nHello world 2\nHello world 3\nHello world 4\nHello world 5\nHello world 6\nHello world 7\nHello world 8\nHello world 9\nHello world 10"}

    TASK: [debug msg="Running test app through Ansible command module..."] ******** 
    ok: [vagrant1] => {
        "msg": "Running test app through Ansible command module..."
    }

    TASK: [Start application] ***************************************************** 
    changed: [vagrant1] => {"changed": true, "cmd": ["nohup", "/tmp/test.sh", "&"], "delta": "0:00:10.045643", "end": "2015-01-29 00:14:53.557164", "rc": 0, "start": "2015-01-29 00:14:43.511521", "stderr": "nohup: ignoring input", "stdout": "Hello world 1\nHello world 2\nHello world 3\nHello world 4\nHello world 5\nHello world 6\nHello world 7\nHello world 8\nHello world 9\nHello world 10"}

    PLAY RECAP ******************************************************************** 
    vagrant1                   : ok=4    changed=2    unreachable=0    failed=0

Best Answer

Just use an asynchronous action with poll: 0:

- command: yourscript.sh
  async: 45
  poll: 0

These options are needed to prevent ansible from killing the child processes of that task with os.killpg.

Then in yourscript.sh:

java Test &
disown

disown removes the process from job table, so SIGHUP does not get sent to it. nohup does the same, but is not necessary.

Edit: Note that disown is not available in all shells (eg. dash)

Related Question