If you are using an older version of Linux based on Mint 18 or Ubuntu 16, you may want to get the PPA and get the latest version of Ansible that way:
sudo apt install gpg
sudo add-apt-repository ppa:ansible/ansible
sudo apt update
How to get the add-apt-repository command.
Some bugs in older Ansible versions are where unarchive will retrieve the remote_src as local and in mysql_user where it cannot assign the privileges needed to a user (it can't parse some of the GRANT queries). For the lineinline module it may say there is no parameter "path" found.
Requirements: A Linux machine (eg. VM whether in the Cloud or a local VM on Vbox/VMWare/Proxmox) that you can easily install Anisble on (eg. Debian/Ubuntu/Mint). The VM requires proper/working internet between the Ansible Controller and to the internet.
This will be on our "controller" / source machine which is where we deploy the Ansible Playbooks (.yaml) files from.
Install Ansible
sudo apt install ansible
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
ieee-data python-jinja2 python-netaddr python-yaml
Suggested packages:
python-jinja2-doc ipython python-netaddr-docs
Recommended packages:
python-selinux
The following NEW packages will be installed:
ansible ieee-data python-jinja2 python-netaddr python-yaml
0 upgraded, 5 newly installed, 0 to remove and 153 not upgraded.
Need to get 2,463 kB of archives.
After this operation, 15.7 MB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 python-jinja2 all 2.8-1ubuntu0.1 [106 kB]
Get:2 http://archive.ubuntu.com/ubuntu xenial/main amd64 python-yaml amd64 3.11-3build1 [105 kB]
Get:3 http://archive.ubuntu.com/ubuntu xenial/main amd64 ieee-data all 20150531.1 [830 kB]
Get:4 http://archive.ubuntu.com/ubuntu xenial/main amd64 python-netaddr all 0.7.18-1 [174 kB]
Get:5 http://archive.ubuntu.com/ubuntu xenial-backports/universe amd64 ansible all 2.1.1.0-1~ubuntu16.04.1 [1,249 kB]
Fetched 2,463 kB in 1s (1,474 kB/s)
Selecting previously unselected package python-jinja2.
(Reading database ... 434465 files and directories currently installed.)
Preparing to unpack .../python-jinja2_2.8-1ubuntu0.1_all.deb ...
Unpacking python-jinja2 (2.8-1ubuntu0.1) ...
Selecting previously unselected package python-yaml.
Preparing to unpack .../python-yaml_3.11-3build1_amd64.deb ...
Unpacking python-yaml (3.11-3build1) ...
Selecting previously unselected package ieee-data.
Preparing to unpack .../ieee-data_20150531.1_all.deb ...
Unpacking ieee-data (20150531.1) ...
Selecting previously unselected package python-netaddr.
Preparing to unpack .../python-netaddr_0.7.18-1_all.deb ...
Unpacking python-netaddr (0.7.18-1) ...
Selecting previously unselected package ansible.
Preparing to unpack .../ansible_2.1.1.0-1~ubuntu16.04.1_all.deb ...
Unpacking ansible (2.1.1.0-1~ubuntu16.04.1) ...
Processing triggers for man-db (2.7.5-1) ...
Setting up python-jinja2 (2.8-1ubuntu0.1) ...
Setting up python-yaml (3.11-3build1) ...
Setting up ieee-data (20150531.1) ...
Setting up python-netaddr (0.7.18-1) ...
Setting up ansible (2.1.1.0-1~ubuntu16.04.1) ...
#some distros don't create /etc/ansible or hosts, but we can do that ourselves:
mkdir /etc/ansible
touch /etc/ansible/hosts
vi /etc/ansible/hosts
Let's make a new section/group called "lamp"
Change the IP 10.0.2.16 to the IP of your destination Linux VM
[lamp]
host1 ansible_ssh_host=10.0.2.16 #you could add host2,host3 and as many extra hosts as you want
sudo mkdir -p /etc/ansible/group_vars
vi /etc/ansible/group_vars/lamp
#note that the file name is lamp, if the group was called "abcgroup" then the filename would be "abcgroup" instead otherwise it has no impact if the filename does not match the group name.
ansible_ssh_user: root
#note that we can put other variables in this same file by adding more lines like above
#you could create another variable like this:
rtt_random_var: woot!
ansible -m ping all
#
We also could have specified ansible -m ping lamp to just check connectivity to the lamp group
Oops it didn't work!? But I can ping and ssh to it manually
host1 | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh.",
"unreachable": true
}
If you need to login as root, make sure you enable root login in /etc/ssh/sshd_config.
Example below
ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa
Your public key has been saved in /root/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:Nnb95Vj6mlS/xmhqPdx/DvWp6/BOf+B0SSA8ileygtM root@AnsibleTarget
The key's randomart image is:
+---[RSA 3072]----+
| . |
| . = . |
| o . = o . |
| o E + . . |
| . S . . . *|
| o o .oX=|
| .o+X++|
| .+B+B+|
| ..=**=B|
+----[SHA256]-----+
Do this on your Ansible Controller.
ssh-copy-id root@YourTargetIP
areebhost01 | FAILED! => {
"changed": false,
"module_stderr": "Shared connection to 192.168.1.208 closed.\r\n",
"module_stdout": "/bin/sh: 1: /usr/bin/python: not found\r\n",
"msg": "MODULE FAILURE\nSee stdout/stderr for the exact error",
"rc": 127
}
The error above means the target does not have python which is required for Ansible to work.
#on the target
apt install python python3
Now our ping works
areebhost01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
*I strongly recommend not using become or using any method that uses a manual password or password saved in a variable for both security reasons and convenience.
I especially don't recommend using -K or --ask-become-pass because it uses the same password for all hosts (all hosts should not have the same password). it is also inefficient and insecure to rely on typing the password each time when prompted and defeats the purposes of automation with Ansible.
Try again now that you have your key auth working (if it works you should be able to ssh as root to the server without any password)
host1 | SUCCESS => {
"changed": false,
"ping": "pong"
}
ansible -m shell -a 'free -m' host1
host1 | SUCCESS | rc=0 >>
total used free shared buff/cache available
Mem: 3946 84 3779 5 82 3700
Swap: 974 0 974
*Note we could swap host1 for "all" to do all servers or specify "lamp" for just the lamp group to execute that command on.
The sports analogy or possibly theatre inspired terms are really just slang for "it's a YAML" config file that Ansible then translates into specific commands and operations to achieve the automated task on the destination hosts.
Essentially the YAML you create is the equivalent of a script, think of YAML as a high-end language that is then translated into more complex, high-end commands to the destination server.
The difference between the Play and Playbook, is that a Play is more like a single chapter book (a single play, possibly something like just starting Apache). A Playbook is made up of "multiple Plays" or chapters, that essentially execute a number of ordered plays, usually to achieve a larger and more complex task (eg. install LAMP, then create a DB for Wordpress, then install and configure Wordpress etc.. would be done as a Playbook normally).
1.) It has a list of hosts (eg. a group like lamp that we created earlier).
2.) A list of task(s) to execute on the remote host(s)
*A good rule is that each section/block is indented by two spaces as you can see in the playbook below here.
Notice there are two spaces in front of become, tasks and then each task name is indented another 2 spaces and then adds " - name:" and then the actions below are inline with name (eg. you will see name and apt line up).
*Note that it is indenation sensitive and spacing sensitive, as in the real syntax is based on spacing and the dashes -
---
- hosts: lamp
become: yes
tasks:
- name: install apache2
apt: name=apache2 update_cache=yes state=latest
ansible-playbook areebapache.yaml
_____________
< PLAY [lamp] >
-------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
______________
< TASK [setup] >
--------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
ok: [host1]
________________________
< TASK [install apache2] >
------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
changed: [host1]
____________
< PLAY RECAP >
------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
host1 : ok=2 changed=1 unreachable=0 failed=0
You should be able to visit the IP of each host in the lamp group and see the default Apache2 Debian index
Which playbook works and why, what is different about the two? (Feel free to run each one).
#book 1
---
- hosts: lamp
become: root
tasks:
- name: Install apache2
apt: name=apache2 state=latest
#book 2
---
- hosts: lamp
become: root
tasks:
- name: Install apache2
apt: name=apache2 state=latest
Facts are like default, builtin environment variables that we can use to access information about the target:
Get facts by using "ansible NAME -m setup
"
You can replace NAME with a specific host, all or a group name.
For example if we wanted the ipv4 address we would use this notation to get the nested "address" we added a dot after ansible_default_ipv4
"ansible_default_ipv4": {
"address": "10.0.2.16",
{{ansible_default_ipv4.address}}
host1 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"10.0.2.16"
],
"ansible_all_ipv6_addresses": [
"fec0::dcad:beff:feef:682",
"fe80::dcad:beff:feef:682"
],
"ansible_architecture": "x86_64",
"ansible_bios_date": "04/01/2014",
"ansible_bios_version": "Ubuntu-1.8.2-1ubuntu1",
"ansible_cmdline": {
"BOOT_IMAGE": "/boot/vmlinuz-4.19.0-18-amd64",
"quiet": true,
"ro": true,
"root": "UUID=78481d95-1470-42f0-bf4f-2dd841e4412a"
},
"ansible_date_time": {
"date": "2022-01-25",
"day": "25",
"epoch": "1643136984",
"hour": "13",
"iso8601": "2022-01-25T18:56:24Z",
"iso8601_basic": "20220125T135624284357",
"iso8601_basic_short": "20220125T135624",
"iso8601_micro": "2022-01-25T18:56:24.284585Z",
"minute": "56",
"month": "01",
"second": "24",
"time": "13:56:24",
"tz": "EST",
"tz_offset": "-0500",
"weekday": "Tuesday",
"weekday_number": "2",
"weeknumber": "04",
"year": "2022"
},
"ansible_default_ipv4": {
"address": "10.0.2.16",
"alias": "ens3",
"broadcast": "10.0.2.255",
"gateway": "10.0.2.2",
"interface": "ens3",
"macaddress": "de:ad:be:ef:06:82",
"mtu": 1500,
"netmask": "255.255.255.0",
"network": "10.0.2.0",
"type": "ether"
},
"ansible_default_ipv6": {
"address": "fec0::dcad:beff:feef:682",
"gateway": "fe80::2",
"interface": "ens3",
"macaddress": "de:ad:be:ef:06:82",
"mtu": 1500,
"prefix": "64",
"scope": "site",
"type": "ether"
},
"ansible_devices": {
"fd0": {
"holders": [],
"host": "",
"model": null,
"partitions": {},
"removable": "1",
"rotational": "1",
"sas_address": null,
"sas_device_handle": null,
"scheduler_mode": "cfq",
"sectors": "8",
"sectorsize": "512",
"size": "4.00 KB",
"support_discard": "0",
"vendor": null
},
"sr0": {
"holders": [],
"host": "IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]",
"model": "QEMU DVD-ROM",
"partitions": {},
"removable": "1",
"rotational": "1",
"sas_address": null,
"sas_device_handle": null,
"scheduler_mode": "mq-deadline",
"sectors": "688128",
"sectorsize": "2048",
"size": "1.31 GB",
"support_discard": "0",
"vendor": "QEMU"
},
"vda": {
"holders": [],
"host": "SCSI storage controller: Red Hat, Inc Virtio block device",
"model": null,
"partitions": {
"vda1": {
"sectors": "18968576",
"sectorsize": 512,
"size": "9.04 GB",
"start": "2048"
},
"vda2": {
"sectors": "2",
"sectorsize": 512,
"size": "1.00 KB",
"start": "18972670"
},
"vda5": {
"sectors": "1996800",
"sectorsize": 512,
"size": "975.00 MB",
"start": "18972672"
}
},
"removable": "0",
"rotational": "1",
"sas_address": null,
"sas_device_handle": null,
"scheduler_mode": "mq-deadline",
"sectors": "20971520",
"sectorsize": "512",
"size": "10.00 GB",
"support_discard": "0",
"vendor": "0x1af4"
}
},
"ansible_distribution": "Debian",
"ansible_distribution_major_version": "10",
"ansible_distribution_release": "buster",
"ansible_distribution_version": "10.11",
"ansible_dns": {
"nameservers": [
"10.0.2.3"
]
},
"ansible_domain": "ca",
"ansible_ens3": {
"active": true,
"device": "ens3",
"ipv4": {
"address": "10.0.2.16",
"broadcast": "10.0.2.255",
"netmask": "255.255.255.0",
"network": "10.0.2.0"
},
"ipv6": [
{
"address": "fec0::dcad:beff:feef:682",
"prefix": "64",
"scope": "site"
},
{
"address": "fe80::dcad:beff:feef:682",
"prefix": "64",
"scope": "link"
}
],
"macaddress": "de:ad:be:ef:06:82",
"module": "virtio_net",
"mtu": 1500,
"pciid": "virtio0",
"promisc": false,
"type": "ether"
},
"ansible_env": {
"HOME": "/root",
"LANG": "C",
"LC_ALL": "C",
"LC_MESSAGES": "C",
"LOGNAME": "root",
"MAIL": "/var/mail/root",
"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"PWD": "/root",
"SHELL": "/bin/bash",
"SHLVL": "0",
"SSH_CLIENT": "10.0.2.15 34260 22",
"SSH_CONNECTION": "10.0.2.15 34260 10.0.2.16 22",
"SSH_TTY": "/dev/pts/0",
"TERM": "xterm",
"USER": "root",
"XDG_RUNTIME_DIR": "/run/user/0",
"XDG_SESSION_CLASS": "user",
"XDG_SESSION_ID": "79",
"XDG_SESSION_TYPE": "tty",
"_": "/bin/sh"
},
"ansible_fips": false,
"ansible_form_factor": "Other",
"ansible_fqdn": "areeb-ansible.ca",
"ansible_gather_subset": [
"hardware",
"network",
"virtual"
],
"ansible_hostname": "areeb-ansible",
"ansible_interfaces": [
"lo",
"ens3"
],
"ansible_kernel": "4.19.0-18-amd64",
"ansible_lo": {
"active": true,
"device": "lo",
"ipv4": {
"address": "127.0.0.1",
"broadcast": "host",
"netmask": "255.0.0.0",
"network": "127.0.0.0"
},
"ipv6": [
{
"address": "::1",
"prefix": "128",
"scope": "host"
}
],
"mtu": 65536,
"promisc": false,
"type": "loopback"
},
"ansible_lsb": {
"codename": "buster",
"description": "Debian GNU/Linux 10 (buster)",
"id": "Debian",
"major_release": "10",
"release": "10"
},
"ansible_machine": "x86_64",
"ansible_machine_id": "3c9b9946e31e46d39d7fc12c28fcf2c7",
"ansible_memfree_mb": 3567,
"ansible_memory_mb": {
"nocache": {
"free": 3738,
"used": 208
},
"real": {
"free": 3567,
"total": 3946,
"used": 379
},
"swap": {
"cached": 0,
"free": 974,
"total": 974,
"used": 0
}
},
"ansible_memtotal_mb": 3946,
"ansible_mounts": [
{
"device": "/dev/vda1",
"fstype": "ext4",
"mount": "/",
"options": "rw,relatime,errors=remount-ro",
"size_available": 7193808896,
"size_total": 9492197376,
"uuid": "78481d95-1470-42f0-bf4f-2dd841e4412a"
}
],
"ansible_nodename": "areeb-ansible",
"ansible_os_family": "Debian",
"ansible_pkg_mgr": "apt",
"ansible_processor": [
"GenuineIntel",
"KVM @ 2.00GHz",
"GenuineIntel",
"KVM @ 2.00GHz",
"GenuineIntel",
"KVM @ 2.00GHz",
"GenuineIntel",
"KVM @ 2.00GHz",
"GenuineIntel",
"KVM @ 2.00GHz",
"GenuineIntel",
"KVM @ 2.00GHz"
],
"ansible_processor_cores": 1,
"ansible_processor_count": 6,
"ansible_processor_threads_per_core": 1,
"ansible_processor_vcpus": 6,
"ansible_product_name": "Standard PC (i440FX + PIIX, 1996)",
"ansible_product_serial": "NA",
"ansible_product_uuid": "NA",
"ansible_product_version": "pc-i440fx-xenial",
"ansible_python": {
"executable": "/usr/bin/python",
"has_sslcontext": true,
"type": "CPython",
"version": {
"major": 2,
"micro": 16,
"minor": 7,
"releaselevel": "final",
"serial": 0
},
"version_info": [
2,
7,
16,
"final",
0
]
},
"ansible_python_version": "2.7.16",
"ansible_selinux": false,
"ansible_service_mgr": "systemd",
"ansible_ssh_host_key_ecdsa_public": "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAeJa04CWRa6N2zV+hKt+utDxOVI/23Zntb815bXz+qqK/XZsFoIEL7jYUZFlifJFAxmWgE9CJ6Vtn/4DzHnDx4=",
"ansible_ssh_host_key_ed25519_public": "AAAAC3NzaC1lZDI1NTE5AAAAIBhlJVY9PgACISzzqwviVOgeosQBWAKULGY4UsSRzbKJ",
"ansible_ssh_host_key_rsa_public": "AAAAB3NzaC1yc2EAAAADAQABAAABAQC3rT0zeZS8TS7+XmYIt2aTAK1L/RHAhbJ54+UqpyXRJ0CmlQZySdh6ug65lK6VYMQMrmxC8niKVQ/1pSia2swJjb/qSyRlEUGnGYR8xGmVG1I99OcH1301E3nzvmJw44bcRKx/zf5CYf16X8KAPoNg9EsagvjGB5CYz3b5/x4fJmwJ2Qp7rPgNvDYp2GIqRcCXvtfui1vhf2eSqzDLFeK0nFfGqMj8mrBZn2UPRtJNKd3aFWyTqEePKT3Mm1B1cBgdh3St76X7kw0dKuY1BUqtZAOOGEUw84c/vLAeRmQx5yh78COf6ys5jltj6MBwCZ2iSTLAapRxxh13LQ7oAgIh",
"ansible_swapfree_mb": 974,
"ansible_swaptotal_mb": 974,
"ansible_system": "Linux",
"ansible_system_capabilities": [
"cap_chown",
"cap_dac_override",
"cap_dac_read_search",
"cap_fowner",
"cap_fsetid",
"cap_kill",
"cap_setgid",
"cap_setuid",
"cap_setpcap",
"cap_linux_immutable",
"cap_net_bind_service",
"cap_net_broadcast",
"cap_net_admin",
"cap_net_raw",
"cap_ipc_lock",
"cap_ipc_owner",
"cap_sys_module",
"cap_sys_rawio",
"cap_sys_chroot",
"cap_sys_ptrace",
"cap_sys_pacct",
"cap_sys_admin",
"cap_sys_boot",
"cap_sys_nice",
"cap_sys_resource",
"cap_sys_time",
"cap_sys_tty_config",
"cap_mknod",
"cap_lease",
"cap_audit_write",
"cap_audit_control",
"cap_setfcap",
"cap_mac_override",
"cap_mac_admin",
"cap_syslog",
"cap_wake_alarm",
"cap_block_suspend",
"cap_audit_read+ep"
],
"ansible_system_capabilities_enforced": "True",
"ansible_system_vendor": "QEMU",
"ansible_uptime_seconds": 77628,
"ansible_user_dir": "/root",
"ansible_user_gecos": "root",
"ansible_user_gid": 0,
"ansible_user_id": "root",
"ansible_user_shell": "/bin/bash",
"ansible_user_uid": 0,
"ansible_userspace_architecture": "x86_64",
"ansible_userspace_bits": "64",
"ansible_virtualization_role": "guest",
"ansible_virtualization_type": "kvm",
"module_setup": true
},
"changed": false
}
---
- hosts: lamp
become: root
#note we can put variables here under vars: and we can override variables from group_vars or elsewhere by redefining existing variables with new values (eg. ansible_ssh_user: somefakeuser). You can also even use variable placeholders within the .yml file later on eg. to specify a file path like src: "/some/path/{{thevarname}}"
vars:
avarhere: hellothere
tasks:
- name: Install apache2
apt: name=apache2 state=latest
- name: Install MySQL (really MariaDB now)
apt: name=mariadb-server state=latest
- name: Install php
apt: name=php state=latest
- name: Install php-cgi
apt: name=php-cgi state=latest
- name: Install php-cli
apt: name=php-cli state=latest
- name: Install apache2 php module
apt: name=libapache2-mod-php state=latest
- name: Install php-mysql
apt: name=php-mysql state=latest
Simply add on more tasks to your existing playbook above.
Wordpress requires a database like MariaDB and PHP (installed in our original playbook).
But what else is needed?
#MySQL config
- name: Create MySQL Database
mysql_db:
name: "{{wpdbname}}"
# ignore_errors: yes
- name: Create DB user/pass and give the user all privileges
mysql_user:
name: "{{wpdbuser}}"
password: "{{wpdbpass}}"
priv: '{{wpdbname}}.*:ALL'
state: present
# ignore_errors: yes
#Wordpress stuff
- name: Download and tar -zxvf wordpress
unarchive:
src: https://wordpress.org/latest.tar.gz
remote_src: yes
dest: "{{ wppath }}"
extra_opts: [--strip-components=1]
#creates: "{{ wppath }}"
- name: Set permissions
file:
path: "{{wppath}}"
state: directory
recurse: yes
owner: www-data
group: www-data
- name: copy the config file wp-config-sample.php to wp-config.php so we can edit it
command: mv {{wppath}}/wp-config-sample.php {{wppath}}/wp-config.php #creates={{wppath}}/wp-config.php
- name: Update WordPress config file
lineinfile:
path: "{{wppath}}/wp-config.php"
regexp: "{{item.regexp}}"
line: "{{item.line}}"
with_items:
- {'regexp': "define\\( 'DB_NAME', '(.)+' \\);", 'line': "define( 'DB_NAME', '{{wpdbname}}' );"}
- {'regexp': "define\\( 'DB_USER', '(.)+' \\);", 'line': "define( 'DB_USER', '{{wpdbuser}}' );"}
- {'regexp': "define\\( 'DB_PASSWORD', '(.)+' \\);", 'line': "define( 'DB_PASSWORD', '{{wpdbpass}}' );"}
---
- hosts: all
become: yes
#note this Playbook was made for Debian 10, adjustments for other distros will need to be made
# we can put variables here too that work in addition to what is in group_vars
ignore_errors: yes
vars:
#If you are getting weird errors about Python modules missing that are really there, perhaps the wrong version of Python is executing (eg. python2.7 instead of python3).
#You can fix it by setting the variable below to the python you need
#ansible_python_interpreter: "/usr/bin/python3"
ansible_ssh_user: root
wpdbname: rttdbname
wpdbuser: rttdbuser
wpdbpass: rttinsecurepass
wpdbhost: localhost
wppath: "/var/www/html"
tasks:
- name: Install apache2
apt: name=apache2 state=latest
- name: Install MySQL (really MariaDB now)
apt: name=mariadb-server state=latest
#if you are executing as Python3 then you need python3- instead of python-
- name: Install MySQL python module
apt: name=python-mysqldb state=latest
#if you are executing as Python3 then you need python3- instead of python-
- name: Install PyMySQL
apt: name=python-pymysql
- name: Install php
apt: name=php state=latest
- name: Install apache2 php module
apt: name=libapache2-mod-php state=latest
- name: Install php-mysql
apt: name=php-mysql state=latest
#notify means that we call a handler after this tasks completes
#in this case, remember that PHP in Apache WILL NOT work until apache2 is restarted after the installation of PHP
notify:
- restart apache2
#mysql_db on some distros acts funny and will throw errors about being unable to login or connect
#if this happens it could be that you need to set the login_unix_socket variable to the correct mysqld.sock location
#MySQL config
- name: Create MySQL Database
mysql_db:
name: "{{wpdbname}}"
#the below depends on the OS eg. some socks are in /var/lib/mysql
login_unix_socket: /run/mysqld/mysqld.sock
# ignore_errors: yes
#mysql_user on versions as recent as 2.7 has a bug where it will hardcode trying to connect to the "mysql" database which breaks things of course
- name: Create DB user/pass and give the user all privileges
mysql_user:
name: "{{wpdbuser}}"
password: "{{wpdbpass}}"
priv: '{{wpdbname}}.*:ALL'
state: present
#the below depends on the OS eg. some socks are in /var/lib/mysql
login_unix_socket: /run/mysqld/mysqld.sock
# ignore_errors: yes
- name: enable Apache2 service
service: name=apache2 enabled=yes
#Wordpress stuff
- name: Download and tar -zxvf wordpress
unarchive:
src: https://wordpress.org/latest.tar.gz
remote_src: yes
dest: "{{ wppath }}"
extra_opts: [--strip-components=1]
#creates: "{{ wppath }}"
- name: Set permissions
file:
path: "{{wppath}}"
state: directory
recurse: yes
owner: www-data
group: www-data
- name: copy the config file wp-config-sample.php to wp-config.php so we can edit it
command: mv {{wppath}}/wp-config-sample.php {{wppath}}/wp-config.php #creates={{wppath}}/wp-config.php
become: yes
- name: Update WordPress config file
lineinfile:
path: "{{wppath}}/wp-config.php"
regexp: "{{item.regexp}}"
line: "{{item.line}}"
with_items:
- {'regexp': "define\\( 'DB_NAME', '(.)+' \\);", 'line': "define( 'DB_NAME', '{{wpdbname}}' );"}
- {'regexp': "define\\( 'DB_USER', '(.)+' \\);", 'line': "define( 'DB_USER', '{{wpdbuser}}' );"}
- {'regexp': "define\\( 'DB_PASSWORD', '(.)+' \\);", 'line': "define( 'DB_PASSWORD', '{{wpdbpass}}' );"}
#this is the handlers section, these are tasks that executed after the successful execution of a task that refers to the handler task below eg. "restart apache2" gets notified by the task near the top called "Install apache2"
handlers:
- name: restart apache2
service: name=apache2 state=restarted
The above is fine on Debian 10 but on Mint 20/Ubuntu 20 you may get this:
TASK [Create MySQL Database] ***********************************************************************************************************************************************************************************
fatal: [areebhost01]: FAILED! => {"changed": false, "msg": "The PyMySQL (Python 2.7 and Python 3.X) or MySQL-python (Python 2.X) module is required."}
...ignoring
You'll need to install the "python3-pymysql" package or "python3-mysqldb" package to solve this:
- name: Install PyMySQL
apt: name=python3-pymysql
If the above is not an issue or it is still not working, you may be executing the wrong version of Python on your target. The issue on a target like Mint 20 is that it may be running python2.7, yet there are no modules for python2.7 built-in to Mint/Ubuntu 20. So what you need to do is set a variable in your playbook to use python3 like this:
#
change the /usr/bin/python3 to the location of the target's python3 if different
vars:
ansible_python_interpreter: "/usr/bin/python3"
This is caused by the bug present until at least Ansible 2.7 where mysql_user module is hardcoded to only use the mysql database even though we specify a different one of course.
TASK [Create DB user/pass and give the user all privileges] **********************************************************************************************************************************
fatal: [areebhost01]: FAILED! => {"changed": false, "msg": "unable to connect to database, check login_user and login_password are correct or /root/.my.cnf has the credentials. Exception message: (1049, \"Unknown database 'mysql'\")"}
The solution is to upgrade to a newer version of Ansible (confirmed taht version 2.9 in Ubuntu/Mint 20 does not have the bug, nor does 2.10 in Debian 11).
This issue below, can be caused by not having the correct socket for MySQL/MariaDB (this assumes that you have successfully installed a default standard MySQL/MariaDB server and that it is running on the target):
TASK [Create MySQL Database] *****************************************************************************************************************************************************************
fatal: [debian10]: FAILED! => {"changed": false, "msg": "unable to find /root/.my.cnf. Exception message: (1698, u\"Access denied for user 'root'@'localhost'\")"}
...ignoring
Adding this variable as part of the task usually fixes it:
login_unix_socket: /run/mysqld/mysqld.sock
#MySQL config
- name: Create MySQL Database
mysql_db:
name: "{{wpdbname}}"
#the below depends on the OS eg. some socks are in /var/lib/mysql
login_unix_socket: /run/mysqld/mysqld.sock
# ignore_errors: yes
We can use conditionals (eg like an if statement equivalent) to change the behavior. For example the playbook above installs python-mysqldb on the target, however it works on Debian 10 but not Debian 11 (since that package is deprecrated so we need to install python3-mysqldb instead). How can we do it?
#install python-mysqldb only if we are Debian 10
- name: Install MySQL python2 module Debian 10
apt: name=python-mysqldb state=latest
when: (ansible_facts['distribution'] == "Debian" and ansible_facts['distribution_major_version'] == "10")
- name: Install MySQL python3 module Debian 11
apt: name=python3-mysqldb state=latest
when: (ansible_facts['distribution'] == "Debian" and ansible_facts['distribution_major_version'] == "11")
Seeing it in action, you will see that only one of the two tasks is executed which is the Debian 11 task since the conditional of when matched Debian 11.
More on Ansible conditionals from the documentation.
It would also be wise under the apt: module to add "update_cache=yes" to make sure the packages are up to date.
We could put all of the apt install tasks from the original example into a single task like this:
---
- hosts: lamp
become: yes
tasks:
- name: install LAMP
apt: name={{item}} update_cache=yes state=latest
with_items:
- apache2
- mariadb-server
- php
- php-cgi
- php-cli
- libapache2-mod-php
- php-mysql
#note that the below won't work on older Ansible (eg. 2.1 and will throw a formatting error). If that happens, use the above playbook. I find the style above to be less prone to typos.
ERROR! The field 'loop' is supposed to be a string type, however the incoming data structure is a
The error appears to have been in '/home/markmenow/Ansible/lamp-fullloop.yaml': line 5, column 9, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
tasks:
- name: install LAMP
^ here
---
- hosts: lamp
become: yes
tasks:
- name: install LAMP
apt: name={{item}} state=latest
loop: [ 'apache2', 'mariadb-server', 'php', 'php-cgi', 'php-cli', 'libapache2-mod-php', 'php-mysql' ]
The only downside is that it can be harder to troubleshoot if something fails, since we are installing all of the items as a single apt command in a single task.
By default, Ansible will stop executing the playbook and not move on to the next task. For some reasons there are times where this is no the desirable or correct behavior.
https://docs.ansible.com/ansible/latest/user_guide/playbooks_error_handling.html
You can tell an individual task to ignore errors and continue:
We just add ignore_errors at the same indentation level as our module.
- name: Create DB user/pass and give the user all privileges
mysql_user:
name: "{{wpdbuser}}"
password: "{{wpdbpass}}"
priv: '{{wpdbname}}.*:ALL'
state: present
ignore_errors: yes
We could also do a universal ignore_errors: yes which would apply to all tasks, but this is normally not what you'd want.
---
- hosts: lamp
become: yes
ignore_errors: yes
Handlers - Add this to the end of the above playbook.
handlers:
- name: restart apache2
service: name=apache2 state=restarted
More on handlers from Ansible: https://docs.ansible.com/ansible/latest/user_guide/playbooks_handlers.html
How do we enable a service so it works upon boot?
- name: enable Apache2 service
service: name=apache2 enabled=yes
How can we copy a file?
- name: Copy some file
copy:
src: "files/somefile.ext"
dest: "/var/some/dest/path/"
How can we tell Apache to use a custom index.html?
template means it is a jinja2 file which causes Ansible to replace variables based on the placeholder specified with double braces (eg {{varname}} ). If the varname is not found Ansible will throw an error and not replace the undefined variable and cause the playbook to fail (from the point that the template is called):
fatal: [host1]: FAILED! => {"changed": false, "failed": true, "msg": "AnsibleUndefinedVariable: 'auserr' is undefined"}
- name: Copy index test page
template:
src: "files/index.html.j2"
dest: "/var/www/html/index.html"
To make this work you would need to define the variables in the index.html above within your group_vars file or within the .yml playbook file.
How can we enable an Apache module?
-name: Apache Module - mod_rewrite
apache2_module:
state: present
name: rewrite
How can we enable htaccess?
Inside your files directory (based on the relative dir) place these contents into a file called "htaccess.conf"
*Note you would change the /var/www to another path such as /www/vhosts/ if your vhost directory was different than Apache's default /var/www
Create a new task to actually copy the htaccess enable file into Apache2's config directory on the target server:
- name: Enable htaccess support in /var/www
template:
src: "files/htaccess.conf"
dest: "/etc/apache2/sites-available/htaccess.conf"
Don't forget to symlink it to sites-enabled (which actually enables the htaccess
- name: Enable the htaccess.conf by copying to sites-enabled
file:
src: /etc/apache2/sites-available/htaccess.conf
dest: /etc/apache2/sites-enabled/htaccess.conf
state: link
Edit /etc/ansible/ansible.cfg
Set this line: cow_selection = random
https://docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html
https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html
https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html
https://docs.ansible.com/ansible/2.3/playbooks_variables.html
https://docs.ansible.com/ansible/latest/collections/ansible/builtin/index.html
https://github.com/ansible/ansible-examples
https://docs.ansible.com/ansible-core/devel/reference_appendices/YAMLSyntax.html
https://docs.ansible.com/ansible-core/devel/reference_appendices/playbooks_keywords.html
ansible, tutorial, playbook, install, deploy, wordpress, server, requirements, linux, eg, vm, vbox, vmware, proxmox, anisble, debian, ubuntu, mint, requires, controller, quot, playbooks, yaml, sudo, apt, lists, dependency, additional, packages, installed, ieee, python, jinja, netaddr, ipython, docs, recommended, selinux, upgraded, newly, kb, archives, mb, disk, http, archive, xenial, updates, amd, backports, fetched, selecting, previously, unselected, database, directories, currently, preparing, unpack, _, _all, deb, unpacking, yaml_, _amd, data_, netaddr_, ansible_, processing, triggers, db, hosts, vi, etc, ip, destination, ansible_ssh_host, ssh, username, mkdir, group_vars, abcgroup, filename, ansible_ssh_user, ping, specified, connectivity, didn, manually, unreachable, msg, via, automated, prompt, password, ll, authentication, auth, pong, uptime, shell, commands, rc, buff, cache, mem, swap, analogy, theatre, inspired, slang, config, translates, operations, achieve, task, essentially, equivalent, translated, apache, multiple, chapters, execute, larger, configure, valid, indenation, spacing, syntax, dashes, tasks, update_cache, areebapache, _____________, __, oo, _______, ______________, ok, ________________________, ____________, recap, default, index, format, quiz, expanding, stack, custom, html, mysql, mariadb, php, cgi, cli, module, libapache, mod, restart, handlers, restarted, template, src, dest, var, www, references, https, playbooks_variables, collections, builtin, github, examples, devel, reference_appendices, yamlsyntax, playbooks_keywords,