3
votes

I'm having a really hard time doing what seems like a fairly standard task so I'm hoping somebody can help me. I've googled this like crazy and most of the examples are not in VPC or use deprecated structure that makes them wrong or unusable in my use case.

Here are my goals:

  1. I want to launch a whole mess of new instances in my VPC (the same code below has 3 but it could be a hundred)
  2. I want to wait for thoseinstances to come alive
  3. I then want to configure those instances (ssh into them, change hostname, enable some services, etc. etc.)

Now I could probably do this in 2 tasks. I could create the instances in 1 playbook. Wait for them to settle down. Then run a 2nd playbook to configure them. That's probably what I'm going to do now because I want to get moving - but there has to be a one shot answer to this.

Here's what I have so far for a playbook

---
- hosts: localhost
  connection: local
  gather_facts: False
  tasks:
    - name: Provision Lunch
      with_items:
        - hostname: eggroll1
        - hostname: eggroll2
        - hostname: eggroll3
      ec2:
        region: us-east-1
        key_name: eggfooyong
        vpc_subnet_id: subnet-8675309
        instance_type: t2.micro
        image: ami-8675309
        wait: true
        group_id: sg-8675309
        exact_count: 1
        count_tag:
          Name: "{{ item.hostname }}"
        instance_tags:
          Name: "{{ item.hostname }}"
          role:   "supper"
          ansibleowned: "True"
      register: ec2

    - name: Wait for SSH to come up
      wait_for: host={{ item.private_ip }} port=22 delay=60 timeout=900 state=started
      with_items: '{{ec2.instances}}'

    - name: Update hostname on instances
      hostname: name={{ item.private_ip }}
      with_items: '{{ec2.instances}}'

And that doens't work. What I get is

TASK [Wait for SSH to come up] *************************************************
[DEPRECATION WARNING]: Skipping task due to undefined Error, in the future this will be a fatal error.. This feature will be removed in a future release. Deprecation warnings can be disabled by setting
deprecation_warnings=False in ansible.cfg.

TASK [Update hostname on instances] ********************************************
[DEPRECATION WARNING]: Skipping task due to undefined Error, in the future this will be a fatal error.. This feature will be removed in a future release. Deprecation warnings can be disabled by setting
deprecation_warnings=False in ansible.cfg.

Which makes me sad. Now this is my latest incarnation of that playbook. But I've tried to rewrite it using every example I can find on the internet. Most of them have with_items written in a different way, but ansible tells me that way is depricated, and then fails.

So far ansible has been fun and easy, but this is making me want to toss my laptop across the street.

Any suggestions? Should I be using register and with_items at all? Would I be better off using something like this:

add_host: hostname={{item.public_ip}} groupname=deploy

instead? I'm wide open to a rewrite here. I'm going to go write this up in 2 playbooks and would love to get suggestions.

Thanks!

****EDIT**** Now it's just starting to feel broken or seriously changed. I've googled dozens of examples and they all are written the same way and they all fail with the same error. This is my simple playbook now:

---
- hosts: localhost
  connection: local
  gather_facts: False
  vars:
    builderstart: 93
    builderend: 94
  tasks:
    - name: Provision Lunch
      ec2:
        region: us-east-1
        key_name: dakey
        vpc_subnet_id: subnet-8675309
        instance_type: t2.micro
        image: ami-8675309
        wait: True
        group_id: sg-OU812
        exact_count: 1
        count_tag:
          Name: "{{ item }}"
        instance_tags:
          Name: "{{ item }}"
          role:   "dostuff"
          extracheese: "True"
      register: ec2
      with_sequence: start="{{builderstart}}" end="{{builderend}}" format=builder%03d


    - name: the newies
      debug: msg="{{ item }}"
      with_items: "{{ ec2.instances }}"

It really couldn't be more straight forward. No matter how I write it, no matter how I vary it, I get the same basic error:

[DEPRECATION WARNING]: Skipping task due to undefined Error, in the future this will be a fatal error.: 'dict object' has no attribute 'instances'.

So it looks like it's the with_items: "{{ ec2.instances }}" line that's causing the error.

I've used debug to print out ec2 and that error looks accurate. It looks like the structure changed to me. It looks like ec2 now contains a dictionary with results as a key to another dictionary object and that instances is a key in that dictionary. But I can't find a sane way to access the data.

For what it's worth, I've tried accessing this in 2.0.1, 2.0.2, and 2.2 and I get the same problem in every case.

Are the rest of you using 1.9 or something? I can't find an example anywhere that works. It's very frustrating.

Thanks again for any help.

3
Do you need to use the "mutable infrastructure" paradigm? One option is to configure your AMIs in advance using Packer. You could even have Packer execute your Ansible playbook for you. Then you can just launch the AMI directly. Regarding your Ansible error, not sure on that one.Josh Padnick
Well, your problem here is in way how you using EC2 modulevvchik
you could use an autoscaling group to add the ec2, then have ansible finish off the configVorsprung
I thought of that, but I'm trying not to embed too many AWS services in my solution because I'm not sure we'll still be there in a year. I'm trying to be cloud agnostic in my approach. But thanks.eric woodworth

3 Answers

1
votes

Don't do it like this:
- name: Provision Lunch with_items: - hostname: eggroll1 - hostname: eggroll2 - hostname: eggroll3 ec2: region: us-east-1

Because by using it you flushing all info from ec2 in your item.
You receiving following output:

TASK [Launch instance] *********************************************************
changed: [localhost] => (item={u'hostname': u'eggroll1'})
changed: [localhost] => (item={u'hostname': u'eggroll2'})

but item should be like this:

changed: [localhost] => (item={u'kernel': None, u'root_device_type': u'ebs', u'private_dns_name': u'ip-172-31-29-85.ec2.internal', u'public_ip': u'54.208.138.217', u'private_ip': u'172.31.29.85', u'id': u'i-003b63636e7ffc27c', u'ebs_optimized': False, u'state': u'running', u'virtualization_type': u'hvm', u'architecture': u'x86_64', u'ramdisk': None, u'block_device_mapping': {u'/dev/sda1': {u'status': u'attached', u'delete_on_termination': True, u'volume_id': u'vol-37581295'}}, u'key_name': u'eggfooyong', u'image_id': u'ami-fce3c696', u'tenancy': u'default', u'groups': {u'sg-aabbcc34': u'ssh'}, u'public_dns_name': u'ec2-54-208-138-217.compute-1.amazonaws.com', u'state_code': 16, u'tags': {u'ansibleowned': u'True', u'role': u'supper'}, u'placement': u'us-east-1d', u'ami_launch_index': u'1', u'dns_name': u'ec2-54-208-138-217.compute-1.amazonaws.com', u'region': u'us-east-1', u'launch_time': u'2016-04-19T08:19:16.000Z', u'instance_type': u't2.micro', u'root_device_name': u'/dev/sda1', u'hypervisor': u'xen'})

Try to use following code

- name: Create a sandbox instance
  hosts: localhost
  gather_facts: False
  vars:
    keypair: eggfooyong
    instance_type: t2.micro
    security_group: ssh
    image: ami-8675309
    region: us-east-1
    subnet: subnet-8675309
    instance_names:
      - eggroll1
      - eggroll2
  tasks:
    - name: Launch instance
      ec2:
        key_name: "{{ keypair }}"
        group: "{{ security_group }}"
        instance_type: "{{ instance_type }}"
        image: "{{ image }}"
        wait: true
        region: "{{ region }}"
        vpc_subnet_id: "{{ subnet }}"
        assign_public_ip: no
        count: "{{ instance_names | length }}"
      register: ec2

    - name: tag instances
      ec2_tag:
        resource: '{{ item.0.id }}'
        region: '{{ region }}'
        tags:        
          Name: '{{ item.1 }}'
          role:   "supper"
          ansibleowned: "True"
      with_together:
        - '{{ ec2.instances }}'
        - '{{ instance_names }}'

    - name: Wait for SSH to come up
      wait_for: host={{ private_ip }} port=22 delay=60 timeout=320 state=started
      with_items: '{{ ec2.instances }}'

Assumption that your ansible host located inside of VPC

0
votes

To achieve this goal, I have written a really small filter plugin get_ec2_info.

Create a directory with the named filter_plugins

Create a plugin file get_ec2_info.py with the following content:

from jinja2.utils import soft_unicode

class FilterModule(object):

    def filters(self):
        return {
            'get_ec2_info': get_ec2_info,
        }

def get_ec2_info(list, ec2_key):

    ec2_info = []
    for item in list:
        for ec2 in item['instances']:
            ec2_info.append(ec2[ec2_key])
    return ec2_info

Then you can use this in your playbook:

---
- hosts: localhost
  connection: local
  gather_facts: False
  tasks:
    - name: Provision Lunch
      ec2:
        region: us-east-1
        key_name: eggfooyong
        vpc_subnet_id: subnet-8675309
        instance_type: t2.micro
        image: ami-8675309
        wait: true
        group_id: sg-8675309
        exact_count: 1
        count_tag:
          Name: "{{ item.hostname }}"
        instance_tags:
          Name: "{{ item.hostname }}"
          role:   "supper"
          ansibleowned: "True"
      register: ec2
      with_items:
        - hostname: eggroll1
        - hostname: eggroll2
        - hostname: eggroll3
   - name: Create SSH Group to login dynamically to EC2 Instance(s)
     add_host: 
       hostname: "{{ item }}"
       groupname: my_ec2_servers
     with_items: "{{ ec2.results | get_ec2_info('public_ip') }}"

   - name: Wait for SSH to come up on EC2 Instance(s)
     wait_for:
       host: "{{ item }}" 
       port: 22 
       state: started
     with_items: "{{ ec2.results | get_ec2_info('public_ip') }}" 

# CALL THE DYNAMIC GROUP IN THE SAME PLAYBOOK
- hosts: my_ec2_servers
  become: yes 
  remote_user: ubuntu
  gather_facts: yes
  tasks:
    - name: DO YOUR TASKS HERE

EXTRA INFORMAITON:

  • using ansible 2.0.1.0
  • assuming you are spinning up ubuntu instances, if not then change the value in remote_user: ubuntu
    • assuming ssh key is properly configured

Please consult these github repos for more help:

0
votes

I thinks this would be helpful for debug.

https://www.middlewareinventory.com/blog/ansible-dict-object-has-no-attribute-stdout-or-stderr-how-to-resolve/

The ec2 register is a dict type. And it has a key results.

results key has many elements including dict and list like below:

{
    "msg": {
        "results": [
            {
                "invocation": {
                },
                "instances": [],
                "changed": false,
                "tagged_instances": [
                    {
                    }
                ],
                "instance_ids": null,
                "failed": false,
                "item": [
                ],
                "ansible_loop_var": "item"
            }
        ],
        "msg": "All items completed",
        "changed": false
    },
    "_ansible_verbose_always": true,
    "_ansible_no_log": false,
    "changed": false
}

So, you can get the desired data using ., for instance, item.changed which has false boolean value.

- debug:
    msg: "{{ item.changed }}"
  loop: "{{ ec2.results }}"