ansible

最後更新: 2015-08-04

目錄

  • 介紹
  • Installation
  • 運作
  • Use SSH Keys For Authentication
  • Config File 的載入次序
  • Testing Example
  • Manage Your Inventory
  • sudo
  • Configuration File
  • Modules
  • Debug
  • Speedup Ansible
  • Playbooks
  • Doc

介紹

ansible 是一個 automation engine, Infrastructure as Code 功具.

* no agents ( 因為它是用 ssh 的)
* no additional custom security infrastructure ( ssh key login )
* automation jobs (Playbooks - YAML)

Homepage: https://www.ansible.com/

Program: Python

說明

* Each playbook is composed of one or more ‘plays’ in a list.
* The goal of a play is to map a group of hosts to some well defined "roles"
* Each play contains a list of tasks.

 


Installation

 

Debian11:

apt-get install sshpass ansible

ansible --version

ansible 2.10.8

 


運作

 

Ansible 會有這樣的 File Upload 到 Client:

/home/ansible/.ansible/tmp/ansible-xxxx/command

#!/usr/bin/python

..........

被 upload 的 script 叫 "Ansible Modules"

upload 完後, Ansible 就會用 ssh 執行它

 


Use SSH Keys For Authentication

 

# Set up SSH agent to avoid retyping passwords

ssh-agent bash

ssh-add ~/.ssh/id_rsa

 


Manage Your Inventory

 

/etc/ansible/hosts

# 沒有 Group 的 host
192.168.1.50
server1 ansible_host=192.168.1.51

# 將 3 個 host 組成 1 個 Group
[webservers]
www1.example.com
www2.example.com:2222
www3.example.com ansible_ssh_host=192.168.1.50 ansible_ssh_port=5555

# 用 [1:3] / [a:c] 去定義 3 架機
[ftpservers]
ftp[1:3].example.com

[databases]
db-[a:c].example.com

[apis]
api1.example.com    ansible_connection=ssh    ansible_ssh_user=pro_user
api2.example.com    ansible_connection=ssh    ansible_ssh_user=pro_user

設定

  • ansible_host
  • ansible_connection
  • ansible_ssh_user

Other Host Variables:

  • ansible_ssh_private_key_file=/home/example/.ssh/aws.pem
  • ansible_ssh_pass                                    # --ask-pass (-k)
  • ansible_sudo_pass                                  # --ask-sudo-pass

 


Config File 的載入次序

 

 * FIRST Win

  1. ANSIBLE_CONFIG env variable / $ANSIBLE_CONFIG
  2. ./ansible.cfg
  3. ~/.ansible.cfg
  4. /etc/ansible/ansible.cfg

功具: ansible-config

View ansible configuration

# Displays the current config file

ansible-config view

[defaults]
host_key_checking = False

# list all current configs

# reading lib/constants.py and shows  env  and config file setting names

ansible-config list

 


Testing Example

 

Ping all your nodes

ansible all -m ping

lamp | success >> {
    "changed": false,
    "ping": "pong"
}
mailscanner | FAILED => Authentication failure.

Testing

# Now run a live command on all of your nodes:

ansible all -a "/bin/echo hello"

加速 ansible

1) 停用 Host Key Checking:

# reasonably slow

~/.ansible.cfg

[defaults]
host_key_checking = False

 


Ad-Hoc Commands

 

-a MODULE_ARGS

-u REMOTE_USER

-k, --ask-pass                         # must install the sshpass program (apt-get install sshpass)
-K, --ask-sudo-pass                 # If using sudo features and when sudo requires a password

-f FORKS

-s, --sudo                               # to access sudo mode

-T TIMEOUT
-t TREE, --tree=TREE

--list-hosts                            # outputs a list of matching hosts; does not execute anything else

-i INVENTORY,                       # (default=/etc/ansible/hosts)

-l SUBSET, --limit=SUBSET    # further limit selected hosts to an additional pattern

Example:

Running from another user account

ansible atlanta -f 10 -a "/usr/bin/foo" -u username

Simultaneous processes

ansible atlanta -f 10 -a "/usr/bin/foo"

* 10 simultaneous processes to use (Default: 5)

shell module

ansible raleigh -m shell -a 'echo $TERM'

* If we want to execute a module using a shell

yum module

# 在 foo.example.com 上以 yum 安裝 httpd

* state=present|latest|absent

absent => remove
present => install
latest  => upgrade

ansible foo.example.com -m yum  -a "name=httpd state=present"

File Transfer:(file module)

ansible atlanta -m copy -a "src=/etc/hosts dest=/tmp/hosts"

ansible webservers -m file -a "dest=/srv/foo/b.txt mode=600 owner=mdehaan group=mdehaan"

File Permission (file module)

# Grant user Joe read access to a file
name=/etc/foo.conf entity=joe etype=user permissions="r" state=present

lineinfile

\s               # it matches any whitespace character, this is equivalent to the set [ \t\n\r\f\v]

# backup=yes: main.cf.2015-07-17@03:32~
# state=present: 當不中 "regexp"時, 亦會加這行. 這行係加在檔尾的
# state=absent: remove 中 regexp 的那行

# create: the file will be created if it does not already exist

i.e.

*** Disable selinux

- lineinfile: dest=/etc/selinux/config regexp=^SELINUX=enforcing line=SELINUX=disabled

- lineinfile: dest=/etc/sudoers state=absent regexp="^%wheel"

- lineinfile: dest=/etc/hosts regexp='^127\.0\.0\.1' line='127.0.0.1 localhost'

# Add a line to a file if it does not exist, without passing regexp
- lineinfile: dest=/tmp/testfile line="192.168.1.99 foo.lab.net foo"

- lineinfile: dest=/etc/postfix/main.cf regexp='myhostname' replace='myhostname={{ droplet }}' backup=yes

replace

# Default: backup=no
# regexp: Uses Python regular expressions (https://docs.python.org/2/library/re.html)
# replace: backreferences(\1 , \2) that will get expanded with the regexp capture groups if the regexp matches

- replace: dest=/etc/hosts regexp='(\s+)old\.host\.name(\s+.*)?$' replace='\1new.host.name\2' backup=yes

Replace String

# backup=no

replace: dest=/etc/hosts regexp='(\s+)old\.host\.name(\s+.*)?$' replace='\1new.host.name\2' backup=yes

Managing Services:

started/stopped

* started/stopped are idempotent actions that will not run commands unless necessary

# restart service

ansible webservers -m service -a "name=httpd state=restarted"

* restarted will always bounce the service.

# enable service

ansible webservers -m service -a "name=httpd enabled=yes"

Gathering Facts:

ansible all -m setup

Users and Groups:

* uid
* comment
* append=no         # only add groups
* home
* createhome=yes
* group=""          # Optionally sets the user's primary group
* groups=""         # Puts the user in this comma-delimited list of groups
* state=present     # taking action if the state is different from what is stated.
* system=no
* shell=""

# Remove the user 'tester'
- user: name=tester state=absent remove=yes

ansible all -m user -a "name=foo password=<crypted password here>"

建立 crypted password:

# generate crypted passwords for the user module
# -m, --method=TYPE
# mkpasswd -m help

mkpasswd --method=sha-512

 


Selinux

 

當 guest 有用 selinux 時, 那一定要安裝 "libselinux-python" package

# 在 guest

yum install libselinux-python

# ansible

# install libselinux-python
  - name: install libselinux-python
    yum: name=libselinux-python state=present
  - lineinfile: dest=/etc/selinux/config regexp=^SELINUX=enforcing line=SELINUX=disabled
  - command: setenforce 0
    args:
      removes: /selinux/enforce

 


sudo

 

ansible all -m ping -u bruce --sudo

在用 --sudo (-s) 時, 會有以下 default value

  • sudo_user=root
  • sudo_exe=sudo
  • sudo_flags=-H

# The default is "-H" which preserves the environment of the original user.

Debug

ansible -vvv sshgw -m ping -s -K

<192.168.123.13> ESTABLISH CONNECTION FOR USER: ansible
<192.168.123.13> EXEC ['sshpass', '-d6', 'ssh', '-tt', '-q', '-o', .... "/bin/sh -c 'mkdir -p $HOME/.ansible/tmp/ansible-1389164997.16-134005641234727 ...'"]
<192.168.123.13> REMOTE_MODULE ping
<192.168.123.13> PUT /tmp/tmpY4V4M2 TO /home/ansible/.ansible/tmp/ansible-1388998234.57-103349027143503/ping
<192.168.123.13> PUT /tmp/tmpTKCHln TO /home/ansible/.ansible/tmp/ansible-1388998234.57-103349027143503/arguments
<192.168.123.13> EXEC ['sshpass', '-d6', 'ssh', '-tt', '-q', '-o', ...  -u root /bin/sh -c ... /usr/bin/python ...

那 user 要可以 sudo /bin/shmaster 要安 sshpass

# 當沒有 NOPASSWD: 時, 那要用 -K
Host_Alias      OFFICE=sshgw
ansible         OFFICE=(root)   NOPASSWD:       /bin/sh

 


Configuration File

 

~/.ansible.cfg

#ask_pass=True
#ask_sudo_pass=True
forks=5
hostfile = /etc/ansible/hosts
host_key_checking=True
log_path=/var/log/ansible.log
action_plugins = /usr/share/ansible_plugins/action_plugins
# default SSH timeout to use on connection attempts
timeout = 10

accelerate_port = 5099

 


Modules

 

http://www.ansibleworks.com/docs/modules.html

* All modules technically return JSON format data

add_host

加 Client:

# add host to group 'just_created' with variable foo=42

- add_host: name={{ ip_from_ec2 }} groups=just_created foo=42

# add a host with a non-standard port local to your machines

- add_host: name={{ new_ip }}:{{ new_port }}

# add a host alias that we reach through a tunnel

- add_host: hostname={{ new_ip }}
            ansible_ssh_host={{ inventory_hostname }}
            ansible_ssh_port={{ new_port }}

firewalld

firewalld: port=8081/tcp permanent=true state=disabled

Run cmd

command:

*  variables like $HOME and operations like "<", ">", "|", and "&" will not work in command.

usage:

ansible lamp -m command -a 'iptables -nL'

args:

# Run the command if the specified file does not exist.(creates)

- command: /usr/bin/make_database.sh arg1 arg2 creates=/path/to/database

# You can also use the 'args' form to provide the options.

# This command will change the working directory to somedir/ and
# will only run when /path/to/database doesn't exist.

- command: /usr/bin/make_database.sh arg1 arg2
  args:
    chdir: somedir/
    creates: /path/to/database

* creates         # a filename or glob pattern, when it already exists, this step will not be run.

* chdir            # cd into this directory before running the command

* removes       # when it does not exist, this step will not be run

Error Handling In Playbooks

- name: this will not be counted as a failure
  command: /bin/false
  ignore_errors: yes

shell module:

command through a shell (/bin/sh)

i.e.

<1>

- shell: somescript.sh >> somelog.txt

<2>

- shell: somescript.sh >> somelog.txt
  args:
    chdir: somedir/
    creates: somelog.txt

args:

removes: filename         # 當 filename 不在時, 不會 run
creates: filename         # 當 filename 存在時, 不會 run
chdir: /selinux           # cd into this directory
executable: /bin/bash     # 轉用另一個 shell
 


Debug

 

Step1: ping

ansible sshgw -m ping

sshgw | success >> {
    "changed": false,
    "ping": "pong"
}

Step2: -vvv

-vvv

ansible -vvv sshgw -m ping -s

Step3: debug module

Print statements during execution

- debug: msg="System {{ inventory_hostname }} has uuid {{ ansible_product_uuid }}"

TASK: [debug msg="Install useful package"] ************************************
ok: [x.y.z] => {
    "msg": "Install useful package"
}

 

 


Speedup Ansible

 

在 Ansible > 1.5 就可以不用 "accelerated mode"了, 因為有 SSH pipelining (2-6x faster) 了

SSH pipelining 原理

* reduces the number of SSH operations required to execute a module
* when using "sudo:", you must first disable "requiretty" in /etc/sudoers

SSH pipelining Setting

# Default: False
pipelining=True

P.S.

在 Enterprise Linux 6 上的 OpenSSH 是用不到的.

所以 Ansible 會用 pure-python SSH client (paramiko) 去 connect guest

Accelerated Mode 原理

ControlPersist (10x faster)

launching a temporary daemon(port 5099) over SSH

* uses SSH for initial secure key exchange( AES key)

* daemon will accept connections for 30 minutes)

Guest 要 requirement

* python 2.5

* python-keyczar (centos6 default 沒有這個 package)

Accelerated Mode Setting

# controller

# port to use for accelerated mode
accelerate_port = 5099

# A keepalive packet is sent back to the controller every 15 seconds
accelerate_timeout = 30

*.yml

- hosts: all
  accelerate: true
  timeout: 300
  minutes: 30
# accelerate_port: 5099
..........

sudo 的限制

You must remove requiretty from your sudoers options.
the NOPASSWD option is required for sudo’ed commands.

 


Playbooks

 

/usr/bin/ansible-playbook

modules: can be executed directly on remote hosts or through Playbooks

Each module supports taking arguments. Nearly all modules take key=value arguments

Version

ansible-playbook --version

ansible-playbook 1.5.4

Ops

-i INVENTORY

--list-hosts

--list-tasks

--private-key=PRIVATE_KEY_FILE

-s, --sudo            run operations with sudo (nopasswd)
-K, --ask-sudo-pass   ask for sudo password

--step                one-step-at-a-time: confirm each task before running

Run Script(yml):

ansible-playbook --syntax-check server.yml

ansible-playbook server.yml

當 yml 沒有指定 hosts 時

---
- hosts: x.y.z

ansible-playbook -i hosts server.yml

 


YAML Syntax

 

# indicates the start of a document
---

# Members of a list, must same indentation level
- Apple
- Orange
- Strawberry

# A dictionary, key: value

name: Example Developer
job: Developer
skill: Elite

# 相等

{name: Example Developer, job: Developer, skill: Elite}

 

Example1: 建立一個 smtp server

file: hosts

[smtpserver]
gw1.local
gw2.local

file: server.yml

---
# separated by colons
- hosts: w.x.y.z

# Variables, call by {{ droplet }}
  vars:
    droplet: w.x.y.z

# running things as another user
  remote_user: root
  sudo: yes

  tasks:

# create user
  - name: create user
    user: name=smtpuser password="xxxx"

# iptables
  - name: config iptables
    copy: src=/root/config_file/iptables dest=/etc/sysconfig/iptables
  - name: config iptables
    service: name=iptables state=restarted

# dovecot
  - name: install dovecot
    yum: name=dovecot state=present
  # If the action line is getting too long for comfort you can
  # break it on a space and indent any continuation lines
  - name: copy config file
    copy: src=/root/config_file/10-master.conf
     dest=/etc/dovecot/conf.d/10-master.conf
  - name: restart dovecot
    service: name=dovecot state=restarted
  - name: auto start dovecot
    service: name=dovecot enabled=yes

# postfix
  - copy: src=/root/config_file/main.cf dest=/etc/postfix
  - lineinfile: dest=/etc/postfix/main.cf regexp='^myhostname' line='myhostname = {{ droplet }}' backup=yes
  - copy: src=/root/config_file/header_checks.cf dest=/etc/postfix
  - service: name=postfix state=restarted
  - service: name=postfix enabled=yes

 


Doc

 

 

Creative Commons license icon Creative Commons license icon