Ansible16: Playbook advanced usage

Keywords: ansible openssh AWS pip

Catalog

Local execution

If you want to run a specific task locally on the control host, you can use the local action statement.

Suppose that the remote host we need to configure has just started. If we run the playbook directly, it may fail because the sshd service has not started listening. We can use the following example on the control host to wait for the controlled end to listen to the sshd port:

- name: wait for ssh server to be running
  wait_for
      port: 22 
      host: "{{ inventory_hostname }}" 
      search_regex: OpenSSH
  connection: local

Task delegation

Sometimes, we want to run the task associated with the selected host or host group, but this task does not need to be executed on the selected host or host group, but on another server.

This feature applies to the following scenarios:

  • Enable host based alarm in alarm system
  • Add or remove a host to the load balancer
  • Add or modify resolution for a host on dns
  • Create a storage on the storage node for host mount
  • Use an external program to check whether the service on the host is normal

You can use the delegate to statement to run task s on another host:

- name: enable alerts for web servers
  hosts: webservers
  tasks:
    - name: enable alerts
      nagios: action=enable_alerts service=web host="{{ inventory_hostname }}"
      delegate_to: nagios.example.com

If delegate to: 127.0.0.1, it is equivalent to local action

Task pause

In some cases, some tasks need to wait for the recovery of some states. For example, a host or application has just been restarted. We need to wait for a port on it to open. At this time, we need to pause the running tasks until their states meet the requirements.

Ansible provides the wait for module to implement the requirement of task pause

Common parameters of wait for module:

  • Connect Ou timeout: the timeout to wait for a connection before the next task is executed
  • delay: when waiting for a port or file or connecting to a specified state, the default timeout is 300 seconds. During the 300 seconds of waiting, the wait for module will always poll whether the specified object reaches the specified state. delay is how often to poll the state.
  • Host: wait for the address of the host the module is waiting for. The default is 127.0.0.1
  • Port: wait for the port of the host the module is waiting for
  • Path: file path. Only when the file exists, the next task starts to execute, that is, wait for the file to be created
  • State: the waiting state, that is, when the waiting file or port or connection state reaches the specified state, the next task begins to execute. When the waiting object is a port, the status is started, stopped, that is, the port has been monitored or the port has been closed; when the waiting object is a file, the status is present, started, or absent, that is, the file has been created or deleted; when the waiting object is a connection, the status is drawn, that is, the connection has been established. The default is started
  • Timeout: the timeout of wait for, which is 300 seconds by default

Example:

#Wait for port 8080 to listen normally before starting the next task until timeout
- wait_for: 
    port: 8080 
    state: started  
    
#Wait for 8000 port to monitor normally, and check every 10s until waiting timeout
- wait_for: 
    port: 8000 
    delay: 10 
    
#Wait for port 8000 until a connection is established
- wait_for: 
    host: 0.0.0.0 
    port: 8000 
    delay: 10 
    state: drained
    
#Wait for 8000 port to have connection established, if the connection comes from 10.2.1.2 or 10.2.1.3, ignore.
- wait_for: 
    host: 0.0.0.0 
    port: 8000 
    state: drained 
    exclude_hosts: 10.2.1.2,10.2.1.3 
    
#Wait for / tmp/foo file to be created    
- wait_for: 
    path: /tmp/foo 

#Wait for the / tmp/foo file to be created, and it needs to contain the completed string    
- wait_for: 
    path: /tmp/foo 
    search_regex: completed 

#Wait for / var/lock/file.lock to be deleted    
- wait_for: 
    path: /var/lock/file.lock 
    state: absent 
    
#Wait for the specified process to be destroyed
- wait_for: 
    path: /proc/3466/status 
    state: absent 
    
#Wait for openssh to start, check once in 10s
- wait_for: 
    port: 22 
    host: "{{ ansible_ssh_host | default(inventory_hostname) }}" search_regex: OpenSSH 
    delay: 10 

Scroll execution

By default, ansible performs each task on all selected hosts or host groups in parallel, but sometimes we want to be able to run one by one. The most typical example is when updating the application server behind the load balancer. Generally speaking, we will remove the application server from the load balancer one by one, update it, and then add it back. We can use the serial statement in play to tell ansible to limit the number of hosts executing play in parallel.

Here is an example of how to remove the host, update the software package and add it back to the load balancer of amazon EC2:

- name: upgrade pkgs on servers behind load balancer
  hosts: myhosts
  serial: 1
  tasks:
    - name: get the ec2 instance id and elastic load balancer id
      ec2_facts:

    - name: take the host out of the elastic load balancer id
      local_action: ec2_elb
      args:
        instance_id: "{{ ansible_ec2_instance_id }}"
        state: absent

    - name: upgrade pkgs
      apt: 
          update_cache: yes 
          upgrade: yes

    - name: put the host back n the elastic load balancer
      local_action: ec2_elb
      args:
        instance_id: "{{ ansible_ec2_instance_id }}"
        state: present
        ec2_elbs: "{{ items }}"
      with_items: ec2_elbs

In the above example, the value of serial is 1, which means that play is only executed on one host in a certain period of time. If it is 2, there are 2 hosts running play at the same time.

Generally speaking, when a task fails, ansible will stop executing the task on the failed host, but continue to execute it on other hosts. In the scenario of load balancing, we would prefer ansible to stop play before all hosts fail to execute. Otherwise, we are likely to face the scenario where all hosts are removed from the load balancer and the execution fails, resulting in service unavailability. At this time, we can use the serial statement with the max fail percentage statement. Max fail percentage indicates that when the proportion of the largest failed hosts reaches, ansible will fail the whole play. An example is as follows:

- name: upgrade pkgs on fservers behind load balancer
  hosts: myhosts
  serial: 1
  max_fail_percentage: 25
  tasks:
    ......

If there are four hosts behind load balancing and one fails to execute, ansible will continue to run. To stop Play, it must be more than 25%. So if you want to stop execution if you want to fail, we can set the value of Max fail percentage to 24. If we want to give up execution whenever it fails, we can set the value of Max fail percentage to 0.

Only once

Sometimes, we want a task to be executed only once, even if it is bound to multiple hosts. For example, there are multiple application servers behind a load balancer. If we want to perform a database migration, we only need to perform operations on one application server.

You can use the run Hou once statement to handle:

- name: run the database migrateions
  command: /opt/run_migrateions
  run_once: true

It can also be used with local action as follows:

- name: run the task locally, only once
  command: /opt/my-custom-command
  connection: local
  run_once: true

It can also be used in conjunction with delegate to to make this one-time task run on the specified machine:

- name: run the task locally, only once
  command: /opt/my-custom-command
  run_once: true
  delegate_to: app.a1-61-105.dev.unp

Setting environment variables

When we execute some commands at the command line, they may need to rely on environment variables. For example, when installing some packages, you may need to use an agent to complete the installation. Or a script may need to call an environment variable to run.

ansible supports defining some environment variables through the environment keyword.

Environment variables may be required in the following scenarios:

  • When you run the shell, you need to set the path variable
  • Some libraries need to be loaded, which are not in the standard library path of the system

Here is a simple example:

---
- name: upload a remote file to aws s3
  hosts: test
  tasks:
    - name: install pip
      yum:
        name: python-pip
        state: installed
    
    - name: install the aws tools
      pip:
        name: awscli
        state: present
    
    - name upload file to s3
      shell aws s3 put-object --bucket=my-test-bucket --key={{ ansible_hostname }}/fstab --body=/etc/fstab --region=eu-west-1
      environment:
        AWS_ACCESS_KEY_ID: xxxxxx
        AWS_SECRET_ACCESS_KEY: xxxxxx

In fact, environment can also be stored in variables:

- hosts: all
  remote_user: root
  vars:
    proxy_env:
      http_proxy: http://proxy.example.com:8080
      https_proxy: http://proxy.bos.example.com:8080
  tasks:
    - apt: name=cobbler state=installed
      environment: proxy_env

interactive prompt

In a few cases, the ansible task needs users to input some data in the process of running. These data are either relatively secret and inconvenient, or the data is dynamic. Different users have different requirements, such as entering users' own accounts and passwords or entering different version numbers will trigger different subsequent operations. Ansible's vars'u prompt keyword is used to deal with the above-mentioned interaction with users.

 - hosts: all
   remote_user: root
   vars_prompt:
      - name: share_user
        prompt: "what is your network username?"
        private: yes
 
      - name: share_pass
        prompt: "what is your network password"
        private: yes
        
    tasks:
      - debug:
          var: share_user
      - debug:
          var: share_pass

Vars? Prompt common options Description:

  • private: yes by default, indicating that the value entered by the user is not visible on the command line
  • Default: defines the default value, which is used when the user has not entered it
  • confirm: if it is set to yes, the user will be required to enter it twice, which is suitable for entering the password

Posted by roonnyy on Mon, 20 Apr 2020 00:57:21 -0700