Hmm, no replies. Damn.
Here's my solution, in case anyone else has a similar problem.
I realised I can skip the approach of writing to disk, and keep it all in
memory.
So I have a task run by all 'lambda' hosts, which is:
set_fact:
this_host_vars: "{{ hostvars[inventory_hostname] | dict_filter(filter_by)
| to_yaml | from_yaml }}"
Where `dict_filter` is a custom filter I wrote to do
x = {k:v for k,v in d.items() if k in filter_by}
Then on the single main host, I do:
with_items: "{{ groups.lambdas }}"
vars:
to_add: "{{ { item: hostvars[item]['this_host_vars'] } }}"
set_fact:
lambda_confs: "{{ lambda_confs | default({}) | combine(to_add) }}"
For two dozen 'lambdas', it takes a few seconds to do these two tasks.
Regards,
Matt
On Wednesday, April 22, 2020 at 1:31:24 PM UTC+10, Matthew Davis wrote:
>
> I have a use case which is a bit different to most, but Ansible seems to
> do a pretty good job.
> I'm trying to figure out the best way to leverage Ansible's inherent
> concurrency for multiple hosts, when deploying to serverless
> infrastructure, which has no hosts. (Instead just using tasks which do API
> calls on localhost.)
>
> # The Task
>
> I'm building, testing and deploying a project using Ansible.
>
> My target infrastructure is all serverless. I'm deploying code to AWS
> Lambda functions, with CloudFormation. So there are no 'hosts' for Ansible
> to connect to.
>
> I have something currently which works, but is slow and a bit messy.
>
> I've got a big playbook, all connecting to one host, which is 'localhost'
> (connection=local).
> I've got a yaml file with a list of my lambda functions and relevant
> config (e.g. timeout, name, comment, environment variables etc).
>
>
> I've got one role for all my Lambda function stuff.
>
> 1. Use `include_vars` to load in aforementioned variable file
> 2. using `with_dict` and the variable from that file, `pip install`
> the modules each one depends on,
> 3. copy over the code for each Lambda function to where those
> dependencies where installed, also with_dict
> 4. run a unit test for each (shell: python main.py), also with_dict
> 5. zip up each folder, also with_dict
> 6. upload each zip to S3, using with_nested (for each lambda function,
> for each region)
>
> In particular the uploading is slow, because it's in series. I'd like to
> do it in parallel.
>
> Then I have other roles for other things which are not per-lambda. (e.g.
> deploying cloudformation templates, configuring a firewall etc.)
>
> # The problem
>
> Inside that role for Lambda stuff, almost every task has with_dict or
> with_items, or sometimes with_nested (e.g. upload each lambda zip for each
> of multiple regions), in addition to extensive `when` conditions. (I made
> it so I can pass in `-e only_lambda=x` to Ansible, to skip tasks for
> Lambdas I haven't changed.)
> This ends up quite messy.
>
> Overall the whole thing is quite slow too, because it does everything in
> series not parallel. Some things are I/O bound, and could be sped up a lot
> by doing them concurrently.
> e.g. I used `async` to do the upload in parallel.
>
> Another thing that get's messy is that I want to keep that variable file
> with lambda config minimal. I don't want to copy-paste boiler plate for
> each one. (It's already too huge.)
> So each lambda has a field called 'Name'.
> I want to add another field like: `local_zip_name: "{{ build_dir }}/{{
> self.cf_name }}.zip".
> Modifying a dict in Ansible is surprisingly messy. Currently I do this
> using `set_fact`.
> Although it turns out that if you load the variable from a file with `-e
> @file.yaml`, it seems that changes with `set_fact` don't take effect.
> So I have to use an include_vars task instead or loading a var file with
> command line arguments.
>
> # Attempted solution
>
> I'm trying to add each 'lambda function' to static inventory.
> Each one being localhost, but having unique host variables equal to what I
> had in that yaml file.
> This way I can leverage all the work Ansible devs have already done to do
> things concurrently.
>
> It turns out you can omit the IP address, and it defaults to localhost
> anyway, and Ansible will happily work with multiple hosts that are actually
> the exact same host.
> It all just works. Yay!
> I can get rid of all the `with_items`, `with_dict` etc, and just run the
> role against the group of hosts.
> And I can reduce the number of tasks by moving set_fact definitions into
> host definitions.
>
> Then for other tasks which I only need to do once, I run those roles with
> a different host group, that's one Ansible host connecting to localhost.
>
> This host approach also makes it nice to set things like `local_zip_fname`
> (mentioned above). I can set that variable once for the whole group, and
> lazy variable evaluation means it's evaluated differently for each host.
> This means I don't have to worry about dependency issues about which
> variables are defined first, and there's one place for all the default
> variables.
>
> But, there are 2 issues.
>
> 1. I want to reference that `local_zip_fname` variable (defined using
> Jinja2 templating for each Lambda host) from the other host (one host,
> localhost). But `local_zip_fname` is not defined for that other host. So I
> tried the thing where you dig into `host_vars['other_host']['my_var']` to
> get it. But now lazy variable evaluation bites back. The variable I want
> (`local_zip_fname`) includes Jinja2 templating for another variable
> (cf_name), which was present on those other hosts (one per Lambda), but not
> this host.
>
> Question: How can I access variables for other hosts, forcing Jinja2
> evaluation based on that other host's environment?
> The only solution I can think of is to save the variables to disk for each
> of those other hosts (using | to_yaml to force Jinja evaluation), and then
> loading that file from the host I want to read the variables. Is there a
> better way?
>
> The other issue is that the speed improvement is no where near as good as
> I hoped. If I deploy from a beefy machine with 8 CPUs instead of my usual
> 2, it seems to speed up the relevant tasks by a factor of 2. (Not by a
> factor of 8/2). If I try to deploy with my usual 2 CPU, there's no
> noticeable difference in speed.
> It seems that there is a lot of overhead per host, which is taking away
> from the speed increase from converting sequential `with_items` to
> concurrent hosts.
>
> I've tried to optimise things with:
> gathering=no
> or using fact caching,
> and I've already set forks=50 (More than the number of hosts).
> And I enabled pipelining (which probably does nothing for connection=local)
>
> Is there any other way to optimise this? Is there a way to tell Ansible
> that these 30 hosts are actually all the same machine, so it only needs to
> do some things once not 30 times?
>
> Or is there some other approach to solve my problem? Is this the x y
> problem <http://xyproblem.info/>?
>
>
> Thanks,
> Matt
>
> (GH: matt-telstra and mlda065)
>
--
You received this message because you are subscribed to the Google Groups
"Ansible Project" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/ansible-project/51a91203-1602-41ec-9ad4-ac32088d11d8%40googlegroups.com.