It is possible to async a local_action in ansible? - ansible

I'm using Ansible to provision vagrant machines. Now I want the playbook to uncompress the database dump, import it in the vm and then recompress it back.
In "normal" mode this is not a big deal, but since my dumps can be pretty big the uncompress/compress operation take lots of time. I would like to use the "fire and forget" method described here https://docs.ansible.com/playbooks_async.html
The idea is:
"fire and forget" the dump bunzip
[ do all the other operations like package install, configurations ecc]
get back to the bunzip
import dump
fire and forget dump recompression
If I attempt to bunzip using a local_action it dies with ERROR: unexpected error: unable to determine jid
It is possible to do an async local task?
Edit
Tasks list example
# start async unzip
- name: bunzip dump
command: bunzip2 /vagrant/vagrant_provision/dump.sql.bz2
async: 10000
poll: 0
register: bunzip_status
#[... do other things ...]
# connect back to unzip and wait for it to end
- name: Check for dump bunzip
async_status: jid={{ bunzip_status.ansible_job_id }}
register: bunzip_result
until: bunzip_result.finished
retries: 80
#[... Import db ...]
# Fire and forget dump recompression
- name: Recompress dump
command: bzip2 /vagrant/vagrant_provision/dump.sql
async: 10000
poll: 0
Now.. since I'm using this to provision a vagrant environment I partially solved by putting my playbook and files inside the vagrant shared folder and referencing them by absolute path, and it works.
But the question is: It is possible to async a local_action (or even simply a delegate_to)?
In this case the use of local_action instead to do the archive/unarchive remotely allows me to use all my cpus (4 versus 1 assigned to vm) to do those operations and that I can even shutdown the vm during the final recompression without having to wait for it to finish.

I've gotten to the point where I make use of gnu screen whenever I want to background/async a command in Ansible so that I can verify that the command is running properly.
Given what you're describing you'd need to come up with a way to notify Ansible when you've reached step #3. To do that you'd probably want to create a temporary flag file that Ansible can look for. So to do what you describe I'd probably do something along these lines:
First, I'd create a wrapper script to unbzip the file just to make things a bit cleaner. It would create the flag file that I mentioned when the bunzip is complete. Here's a bare-bones example:
#!/bin/bash
rm -f /tmp/bunzip.done
bunzip /path/to/file.bz2
touch /tmp/bunzip.done
exit
Then I'd then execute this from within Ansible in a screen session like this (I use sudo in this example since I also typically sudo my screen sessions to a specific user):
- name: invoke bunzip script
local_action: command /usr/bin/screen -d -m sudo -u someuser /path/to/bzip_script.sh
async: True
poll: 0
At this point you can do whatever you need to do within Ansible. Once you get to step #3 then you would want to do something like this:
- name: wait for bunzip if it is still running
local_action: wait_for path=/tmp/bunzip.done state=present
As long as the wait_for script returns without error you should be able to safely reference the bunzipped data at this point. wait_for defaults to a 300 second timeout, so you may need to increase that if you expect bunzip to take longer.

Related

Not able to switch user in ansible

We have a system where we have user A and user B. We can switch to user B from A using "sudo su" command only. Direct login to B user is not allowed.
Now From Ansible master, we can login to A user (as ansible remote user) successfully. Our use case is, We have to run some commands as user B using ansible. But we are failing to switch to B user and run those commands.
Our yml file looks like -
Module to copy java to the target host.
- name: Copying Java jdk1.8.0_192
remote_user: A
become_user: B
become: true
become_method: su
copy:
src: /etc/ansible/jboss7-cluster/raw_template/jdk1.8.0_192.zip
dest: "{{ java_install_dir }}"
Any inputs?
In your case, I believe the become_method should be the default sudo. Have you tried using that? If so, what is the result? Can you copy/paste the result here?
Also, can you try to run an ad hoc command against the host, and post the result here?
Something like this:
ansible -i inventory.ini -u A --become --become-user B -m ping myhost
And one more thing: note that there are some restrictions when using become to switch to a non-privileged user:
"In addition to the additional means of doing this securely, Ansible 2.1 also makes it harder to unknowingly do this insecurely. Whereas in Ansible 2.0.x and below, Ansible will silently allow the insecure behaviour if it was unable to find another way to share the files with the unprivileged user, in Ansible 2.1 and above Ansible defaults to issuing an error if it can’t do this securely. If you can’t make any of the changes above to resolve the problem, and you decide that the machine you’re running on is secure enough for the modules you want to run there to be world readable, you can turn on allow_world_readable_tmpfiles in the ansible.cfg file. Setting allow_world_readable_tmpfiles will change this from an error into a warning and allow the task to run as it did prior to 2.1."
And just as a side note: please avoid using sudo su - use sudo -i, or at least sudo su - . These will populate the environment correctly, unlike sudo su. For a fun read about why you want this, see here.

Ansible to legacy network device (pseudo terminal problem)

Can anyone help me with a command running on a legacy network device (switch) from an Ansible (v2.5) connection? Because the network device is legacy I cannot use an Ansible module, which is why I have to resort to raw. Also, it is worth pointing out that I have no control over the legacy device - I cannot replace it with a more up-to-date model, or alter the configuration in any but the most trivial ways.
The Ansible command I want to run is quite basic. Something like this (obviously not this, in reality the command is 'nsshow', but I don't want to focus on that as I am sure there will be other commands with the same problem):
- name: "Device Command"
raw: "command1"
register: command
That does work, but not in the way required. The problem is that the command runs in the wrong context on the target device, so even though the command runs successfully the output it produces is not useful.
I have done some investigation with SSH commands to discover the nature of the problem. Initially I tried using SSH to connect to the device and entering the command manually. That worked, and I could see from the command prompt on the device that the command was running in the correct context. The next thing I tried was running the command as a pipeline. I found that the first of these commands didn't work (in the same way as Ansible), but that the second worked correctly:
echo -e "command1" | ssh myaccount#mydevice
echo -e "command1" | ssh -tt myaccount#mydevice
So, it seems that the problem relates to pseudo terminals. I realised I needed the -tt option when the first command gave a warning error 'Pseudo-terminal will not be allocated because stdin is not a terminal'. Going back to the Ansible command I can see that if I run Ansible with verbose output that -tt is used on the SSH command line. So why doesn't the Ansible command work? I then discovered that this command also hits the warning error problem when run from the command line:
ssh -tt myaccount#mydevice command1
I think that is more like what Ansible is doing than the pipeline examples I used above and that this explains why Ansible is not working.
Of course within Ansible I can run the command like this, which does work, but I'd rather avoid it.
- name: "Device Command"
local_action:
module: shell echo -e "command1" | ssh -tt myaccount#{{ inventory_hostname }}
register: command
So, the actual question is 'how can I run an Ansible play that runs a raw command on the target device that avoids the psuedo terminal issue'?
Thanks in advance for any help or suggestions.
You can always add ansible_ssh_args to your play:
- hosts: legacy_device
vars:
ansible_ssh_args: -tt
tasks:
- name: "Device Command"
raw: "command1"
register: command
or better yet to the inventory, or host_vars, or group_vars.

Ansible wait_for for connecting machine to actually login

In my working environment, virtual machines are created and after creating login access information is added to them and there can be delays so just waiting for my ansible script to check if SSH is available is not enough, I actually need to check if ansible can get inside the remote machine via ssh.
Here is my old script which fails me:
- name: wait for instances to listen on port:22
wait_for:
state: started
host: "{{ item }}"
port: 22
with_items: myservers
How can I rewrite this task snippet to achieve waiting for the localmachine can ssh into the remote machines (again not only checking if ssh is ready at the remote but it can actually authenticate to it).
This is somewhat ugly, but given your needs it might work:
- local_action: command ssh myuser#{{ ansible_inventory_hostname }} exit
register: log_output
until: log_output.stdout.find("Last login") > -1
retries: 10
delay: 5
The first line would cause your ansible host to try to ssh into the target host and immediately issue an "exit" to return control back to ansible. Any output from that command gets stored in the log_output variable. The until clause will check the output for the string 'Last login' (you may want to change this to something else depending on your environment), and Ansible will retry this task up to 10 times with a 5 second delay between attempts.
Bruce P's answer was close to what I needed, but my ssh doesn't print any banner when running a command, so checking stdout is problematic.
- local_action: command ssh "{{ hostname }}" exit
register: ssh_test
until: ssh_test.rc == 0
retries: 25
delay: 5
So instead I use the return code to check for success
As long as your Ansible user is already installed on the image you are using to create the new server instance, the wait_for command works well.
If that is not the case, then you need to poll the system that adds that user to the newly created instance for when you should continue - of course that system will have to have something to poll against...
The (very ugly) alternative is to put a static pause in your script that will wait the appropriate amount of time between the instance being created and the user being added like so:
- pause: seconds=1
Try not to though, static pauses are a bad way of solving this issue.

Ansible: Test if SSH login possible without FATAL error?

I have a setup playbook that takes a freshly installed linux instance, logs in as the default user (we'll call user1), creates another user (we'll call user2), then disables user1. Because user1 can only access the instance before this set of tasks is executed, the tasks are in a special playbook we have to remember to run on new instances. After that, all the common tasks are run by user2 because user1 no longer exists.
I want to combine the setup and common playbooks so we don't have to run the setup playbook manually anymore. I tried to create a task to see which user exists on the instance to make the original setup tasks conditional by attempting to login via SSH as user1. The problem is that if I try the SSH login for either user, ansible exits with a FATAL error because it can't login: user2 doesn't exist yet on new instances or user1 has been disabled after the setup playbook executes.
I believe testing the login via SSH is the only way to determine externally what condition the instance is in. Is there a way to test SSH logins without getting a FATAL error to then execute tasks conditionally based on the results?
One approach would be to use shell via a local_action to invoke a simple ssh command to user1 and see if it succeeds or not. Something along these lines:
- name: Test for user1
local_action: shell ssh user1#{{ inventory_hostname }} "echo success"
register: user1_enabled
Then you could use something like this in another task to see if it worked:
when: user1_enabled.stdout.find("success") != -1
With Ansible >= 2.5 it is possible to use the wait_for_connection_module (https://docs.ansible.com/ansible/2.5/modules/wait_for_connection_module.html).
- name: Wait 600 seconds for target connection to become reachable/usable
wait_for_connection:

Using ansible to launch a long running process on a remote host

I'm new to Ansible. I'm trying to start a process on a remote host using a very simple Ansible Playbook.
Here is how my playbook looks like
-
hosts: somehost
gather_facts: no
user: ubuntu
tasks:
- name: change directory and run jetty server
shell: cd /home/ubuntu/code; nohup ./run.sh
async: 45
run.sh calls a java server process with a few parameters.
My understanding was that using async my process on the remote machine would continue to run even after the playbook has completed (which should happen after around 45 seconds.)
However, as soon as my playbook exits the process started by run.sh on the remote host terminals as well.
Can anyone explain what's going and what am I missing here.
Thanks.
I have ansible playbook to deploy my Play application. I use the shell's command substitution to achieve this and it does the trick for me. I think this is because command substitution spawns a new sub-shell instance to execute the command.
-
hosts: somehost
gather_facts: no
user: ubuntu
tasks:
- name: change directory and run jetty server
shell: dummy=$(nohup /run.sh &) chdir={{/home/ubuntu/code}}
Give a longer time to async say 6 months or an year or evenmore and this should be fine.
Or convert this process to an initscript and use the service module.
and add poll: 0
I'd concur. Since it's long running, I'd call it a service and run it like so. Just create an init.d script, push that out with a 'copy' then run the service.

Resources