5/5 - (1 vote)

We continue the cycle of training articles for novice system administrators. In this article, we will write an Ansible role to raise a full-fledged ready-made server. Note that if you are an experienced administrator, you can safely skip this material.

Any writing of an ansible role is accompanied by a plan. This plan will need to include everything that needs to be installed and configured.

Here is our plan:

  1. Point 1. Initial server setup.
  2. Point 2. Installing LEMP.
  3. Item 3. Rights and user.
  4. Step 4. Setting up LEMP.
  5. Item 5. Transfer of site code and database.
  6. Item 6. Testing.
  7. Item 7. Bottom line.

Since if we consider all the points in one article, then the material turns out to be too voluminous, it was decided to divide it into two parts. In the first part, we will discuss points 1, 2 and 3 of the plan. In the next – 4, 5, 6, 7 points.

At the moment, in the /var/ansible directory, which we created and used in the previous article , there are files:

  1. hosts.txt – file with ip addresses of remote machines.
  2. playbook.yml – playbook from the first article.

We generate a role, the role will be called LEMP (you can use any name).

ansible-galaxy init LEMP

Open playbook.yml. We add:

- name: Install LEMP server
  hosts: all
  become: yes
  roles:
    - LEMP
  1. name – the name of this ansible project.
  2. hosts – on which hosts to run.
  3. become – use sudo.
  4. roles – connect the created role.

We save the file and exit. We don’t need this file anymore.

Directory structure in ansible directory:

.
├── hosts.txt
├── LEMP │ ├──
defaults
│ │ └── main .
yml ─ main.yml │ ├── README.md │ ├── tasks │ │ ├── ── vars │ └── main.yml └── playbook.yml

Point 1. Initial server setup.

At this point, you need to configure the server for web applications. Install default applications, set the date, server name and localization.

You need to install applications and packages:

  • dirmngr mc iotop htop telnet tcpdump nmap curl console-cyrillic hexedit sudo zip unzip patch pwgen vim less parted subversion ntp bzip2 lsof strace mutt s-nail ncdu smartmontools tree dnsutils logrotate rsyslog

You need to set up:

  1. time
  2. hostname
  3. localization

The first thing to do is to go to the directory with tasks along the path  /var/ansible/LEMP/tasks/.

cd /var/ansible/LEMP/tasks/

We create a yml file for point 1. This is necessary so that it will be more convenient to administer the ansible role in the future. Because everything will be divided into its files and loaded only when we need it. For each item in the article, we will create a separate configuration file.

We create yml file. You can choose any name:

touch default_settings.yml

We go into the main configuration file in the tasks ./LEMP/tasks/main.yml.

You need to upload the above configuration file. To do this, use the include_tasks module and the file name:

- include_tasks: default_settings.yml

We save changes in main.yml.

Now let’s start editing  default_settings.yml. To install, we need to use the shell module. First you need to update all repositories. We add:

---

  - name: update repo.
    shell: apt update
  1. name – the name of the task.
  2. shell – the module with which the command is executed.

Save and run from the directory where playbook.yml is (/var/ansible/):

ansible-playbook playbook.yml

PLAY [Install LEMP server] **************************************************************************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************************************************************************************

ok: [ansible2]

TASK [test : update repo.] **************************************************************************************************************************************************************************************

changed: [ansible2]

PLAY RECAP ******************************************************************************************************************************************************************************************************

ansible2                   : ok=2    changed=1    unreachable=0    failed=0

As you can see from the output, there is no error.

Let’s start installing applications and packages.

We will also use the shell module , since there are a lot of utilities we need and installing everything through the apt module will be quite long and problematic.

Add to the default_settings.yml file:

 - name: install default app.
     shell:
       cmd: "apt install -y dirmngr mc iotop htop telnet tcpdump nmap curl hexedit sudo zip unzip patch pwgen vim less parted subversion ntp bzip2 lsof strace mutt s-nail ncdu smartmontools tree dnsutils logrotate rsyslog"

cmd is the key in which we specify the command.

We launch:

# ansible-playbook playbook.yml

PLAY [Install LEMP server] **************************************************************************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************************************************************************************

ok: [ansible2]

TASK [test : update repo.] **************************************************************************************************************************************************************************************

changed: [ansible2]

TASK [test : install default app.] ******************************************************************************************************************************************************************************

changed: [ansible2]

PLAY RECAP ******************************************************************************************************************************************************************************************************

ansible2                   : ok=3    changed=2    unreachable=0    failed=0

Everything is set.

Next, you need to configure the time, localization and hostname.

We need commands:

To set the time:

timedatectl set-timezone Europe/Moscow

To install localization:

locale-gen ru_RU.UTF-8
update-locale LANG=en_US.UTF-8 LC_TIME="ru_RU.UTF-8"

To set hostname:

hostnamectl set-hostname DOMAIN_NAME

Since we are using floating variables, we will add them to the vars directory. So that in the future it would be convenient for us to change them on other servers.

floating variables:

  • Europe/Moscow
  • ru_RU.UTF-8
  • en_US.UTF-8
  • DOMAIN_NAME

Open the file, where we specify the variables for the entire project. The file is located at: /etc/ansible/test/vars/main.yml

Adding variables:

DOMAIN_NAME: domain_name
locale1: ru_RU.UTF-8
locale2: en_US.UTF-8
time_zone: Europe/Moscow

In the future, to call a variable, you will need to use brackets {{variable}}.

Go to default_settings.yml and add tasks:

  - name: time
    shell:
      cmd: "timedatectl set-timezone {{time_zone}}"

  - name: locale settings
    shell:
      cmd: 'locale-gen {{locale1}} && update-locale LANG={{locale2}} LC_TIME="{{locale1}}"'

  - name: hostname
    shell:
      cmd: "hostnamectl set-hostname {{DOMAIN_NAME}}"

We start and check.

ansible-playbook playbook.yml

PLAY [Install LEMP server] **************************************************************************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************************************************************************************

ok: [ansible2]

TASK [test : update repo.] **************************************************************************************************************************************************************************************

changed: [ansible2]

TASK [test : install default app.] ******************************************************************************************************************************************************************************

changed: [ansible2]

TASK [test : time] **********************************************************************************************************************************************************************************************

changed: [ansible2]

TASK [test : locale settings] ***********************************************************************************************************************************************************************************

changed: [ansible2]

TASK [test : hostname] ******************************************************************************************************************************************************************************************

changed: [ansible2]

PLAY RECAP ******************************************************************************************************************************************************************************************************

ansible2                   : ok=6    changed=5    unreachable=0    failed=0

To check, you can send a command using the shell module .

ansible all -m shell -a "date && cat /etc/hostname"
ansible2 | CHANGED | rc=0 >>
Пн июн 20 14:41:49 MSK 2022
domainname

Everything is working. Item 1 completed.

Final file default_settings.yml:

  - name: update repo.
    shell: apt update

  - name: install default app.
    shell:
      cmd: "apt install -y dirmngr mc iotop htop telnet tcpdump nmap curl hexedit sudo zip unzip patch pwgen vim less parted subversion ntp bzip2 lsof strace mutt s-nail ncdu smartmontools tree dnsutils logrotate rsyslog"

  - name: time
    shell:
      cmd: "timedatectl set-timezone {{time_zone}}"

  - name: locale settings
    shell:
      cmd: 'locale-gen {{locale1}} && update-locale LANG={{locale2}} LC_TIME="{{locale1}}"'

  - name: hostname
    shell:
      cmd: "hostnamectl set-hostname {{DOMAIN_NAME}}"

Point 2. Installing LEMP.

At this point, you need to set:

  • nginx apache2 mysql exim4

To install these packages, we will use the apt module .

We will need to separate the installation of packages into different yml files. Since in the future it will be convenient to add and change configurations for each package separately.

We create 4 files:

cd /var/ansible/LEMP/tasks
touch mysql_install.yml nginx_install.yml apache2_install.yml exim4_install.yml

We connect task loading in main.yml:

 #####install mysql
  - include_tasks: mysql_install.yml
  #####install nginx
  - include_tasks: nginx_install.yml
  #####install apache2
  - include_tasks: apache2_install.yml
  #####install exim4
  - include_tasks: exim4_install.yml

Add to the nginx_install.yml file:

  - name: Install nginx
    apt:
      name: nginx
      state: latest
  1. – name – task name.
  2. apt is a module.
  3. name – package name.
  4. state – package version; in this case the latest available version.

Add to the apache2_install.yml file:

  - name: Install apache2
    apt:
      name: apache2
      state: latest

Add to exim4_install.yml file:

  - name: Install exim4
    apt:
      name: exim4
      state: latest

When installing mysql, you will need to include the repositories. Open mysql_install.yml. We use the get_url module to download the deb. file from the official site.

- name: add mysql repo
  get_url:
    url: https://dev.mysql.com/get/mysql-apt-config_0.8.6-1_all.deb
    dest: "/tmp"
    mode: 0440
  1. get_url – module for downloading a file from a link, analogous to wget.
  2. dest – the location where the file will be loaded.
  3. mode – assign rights to the uploaded file.

The next step is to install the downloaded repository.

- name: install mysql repo
    apt: "deb=/tmp/mysql-apt-config_0.8.6-1_all.deb"
    become: true

Add the repository key and update the repositories:

  - name: add key mysql and update repo
    shell: "apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 467B942D3A79BD29 && apt update"

Install python-mysqldb for further interaction.

 - name: install python-mysqldb
    apt:
      name: python-mysqldb
      state: present
      update_cache: yes

We check the available version and display for verification:

- name: check latest version of mysql 5.7
  command: bash -c "apt-cache showpkg mysql-server|grep 5.7|head -1|cut -d' ' -f1"
  register: latestmysql57
- debug: msg="{{ latestmysql57.stdout }}"

Install:

 - name: install mysql 57
   apt:
     name: mysql-server={{ latestmysql57.stdout }}
     state: present
     update_cache: yes

We launch:

ansible-playbook playbook.yml

TASK [LEMP : include_tasks] *************************************************************************************************************************************************************************************included: /var/ansible/LEMP/tasks/mysql_install.yml for ansible2

TASK [LEMP : add mysql repo] ************************************************************************************************************************************************************************************ok: [ansible2]

TASK [LEMP : install mysql repo] ********************************************************************************************************************************************************************************ok: [ansible2]

TASK [LEMP : add key mysql] *************************************************************************************************************************************************************************************changed: [ansible2]

TASK [LEMP : update repo] ***************************************************************************************************************************************************************************************changed: [ansible2]

TASK [LEMP : check latest version of mysql 5.7] *****************************************************************************************************************************************************************changed: [ansible2]

TASK [LEMP : debug] *********************************************************************************************************************************************************************************************ok: [ansible2] => {

    "msg": "5.7.38-1debian10"

}

TASK [LEMP : install mysql 57] **********************************************************************************************************************************************************************************changed: [ansible2]

TASK [LEMP : include_tasks] *************************************************************************************************************************************************************************************included: /var/ansible/LEMP/tasks/nginx_install.yml for ansible2

TASK [LEMP : Install nginx] *************************************************************************************************************************************************************************************ok: [ansible2]

TASK [LEMP : include_tasks] *************************************************************************************************************************************************************************************included: /var/ansible/LEMP/tasks/apache2_install.yml for ansible2

TASK [LEMP : Install apache2] ***********************************************************************************************************************************************************************************ok: [ansible2]

TASK [LEMP : include_tasks] *************************************************************************************************************************************************************************************included: /var/ansible/LEMP/tasks/exim4_install.yml for ansible2

TASK [LEMP : Install exim4] *************************************************************************************************************************************************************************************ok: [ansible2]

All 4 services we need are installed.

To check, you can send a request to a remote server using the shell module:

ansible all -m shell -a "systemctl status nginx apache2 mysql exim4"

Item 3. Rights and user.

Necessary:

  1. Create user/home directory.
  2. Set permissions for the /var/www directory.
  3. Create database and database user.

Create a configuration file for this item:

touch /var/ansible/LEMP/tasks/default_user_settings.yml

We connect the configuration in the main-file:

 #####default user settings
  - include_tasks: default_user_settings.yml

We create a directory structure by analogy with our first article .

At this stage, we need to add variables to /var/ansible/LEMP/vars, a user/group number on the system, and a password.

User_uid: 10000
Group_GID: 10000
user_password: password

Open default_user_settings.yml. To add a user, you will need to use the user and group module . Adding a group:

  - name: add group
    group:
      name: "{{ DOMAIN_NAME }}"
      state: present
      gid: "{{ Group_GID }}"
  1. state – the state of the group, if the group is available on the server, then ansible will not re-create it or try to change it.
  2. gid – group number in the system
  3.  “{{ DOMAIN_NAME }}” is the variable that we added in Point 1.

Adding a user:

  - name: add user
    user:
      name: "{{ DOMAIN_NAME }}"
      password: "{{ user_password | password_hash('sha512') }}"
      uid: "{{ User_uid }}"
      group: "{{ DOMAIN_NAME }}"
      state: present
      update_password: on_create
      home: "/var/www/{{ DOMAIN_NAME }}"
      shell: /bin/bash
  1. password: “{{ user_password | password_hash(‘sha512’) }}” – since ansible can’t send an unencrypted password, we encrypt it with password_hash(‘sha512’).
  2. update_password: on_create – means that the password will be added only once on the first execution of this command, i.e. it will not be overwritten if the role is restarted.
  3.   home: “/var/www/{{ DOMAIN_NAME }}” – user’s home directory.

Thus, the user and groups are created. Now we need to create the directory structure. The file module will help us :

 - name: create home directory
    file:
      path: "/var/www/{{ DOMAIN_NAME }}"
      owner: "{{ DOMAIN_NAME }}"
      group: "{{ DOMAIN_NAME }}"
      mode: 0751
      state: directory
  1. path: “/var/www/{{ DOMAIN_NAME }}” – directory address
  2. owner: “{{ DOMAIN_NAME }}” – Assign directory to user
  3. group: “{{ DOMAIN_NAME }}” – Assign a directory to a group.
  4. mode: 0751 – directory permissions
  5. state: directory – means that a directory is created and not a file.

Directory structure:

.└── domain_name

├── data

├── log

│ ├── apache2

│ └── nginx

├── sess

├── tmp

└── Upload

The next step is to create a database and a database user.

Since mysql is configured in a specific file, we will use it (/var/ansible/LEMP/tasks/mysql_install.yml).

First, you need to create a root password. Add root password to /var/ansible/LEMP/vars/main.yml variables.

mysql_root_password: password

Go to /var/ansible/LEMP/tasks/mysql_install.yml. We add:

 - name: update mysql root password for all root accounts
   become: true
   mysql_user:
     name: root
     host: "{{ item }}".
     password: "{{ mysql_root_password }}"
     login_user: root
     login_password: ''
     check_implicit_admin: yes
     priv: "*.*:ALL,GRANT"
     state: present
   with_items:
     - 127.0.0.1
     - ::1
     - localhost
  1. The mysql_user module is used .
  2.  name: root – the username of which we use in mysql.
  3.  host: “{{ item }}”. – addresses of hosts on which the password will be changed; all hosts are specified in with_items.
  4.  password: “{{ mysql_root_password }}” is the new password.
  5. check_implicit_admin – performs a mysql login check without a password (if, for example, the password is specified in .my.cnf in the home directory); If login without a password fails, the password specified in login_password will be used.
  6. login_user: root and login_password: ” username and password with which we log into mysql.
  7. priv: “*.*:ALL,GRANT” – what privileges are assigned to the user.
  8. state: present – assigned once.

Next, add the database. Add database name and user variables to /var/ansible/LEMP/vars/main.yml:

name_db: domain_name_db
user_db: domain_name_db
password_user_db: password

Go back to /var/ansible/LEMP/tasks/mysql_install.yml. Adding a database:

  - name: Create a new database with name 'DOMAIN_NAME_DB'
    mysql_db:
      login_user: root
      login_password: "{{ mysql_root_password }}"
      name: "{{name_db}}"
      state: present
  1. We use the mysql_db module .
  2. name: “{{name_db}}” – the name of the database.

Add a user and assign rights to the database.

  - name: add user DOMAIN_NAME_USR
    mysql_user:
      login_user: root
      login_password: "{{ mysql_root_password }}"
      host: localhost
      name: "{{user_db}}"
      password: "{{password_user_db}}"
      priv: '{{name_db}}.*:ALL,GRANT'
      state: present

The database user and database have been created. The resulting files under this item are as follows:


  - name: add group
    group:
      name: "{{ DOMAIN_NAME }}"
      state: present
      gid: "{{ Group_GID }}"

  - name: add user
    user:
      name: "{{ DOMAIN_NAME }}"
      password: "{{ user_password | password_hash('sha512') }}"
      uid: "{{ User_uid }}"
      group: "{{ DOMAIN_NAME }}"
      state: present
      update_password: on_create
      home: "/var/www/{{ DOMAIN_NAME }}"
      shell: /bin/bash

  - name: create home directory
    file:
      path: "/var/www/{{ DOMAIN_NAME }}"
      owner: "{{ DOMAIN_NAME }}"
      group: "{{ DOMAIN_NAME }}"
      mode: 0751
      state: directory

  - name: create other directory
    file:
      path: "/var/www/{{ DOMAIN_NAME }}/data"
      owner: "{{ DOMAIN_NAME }}"
      group: "{{ DOMAIN_NAME }}"
      mode: 0755
      state: directory

  - name: create other directory
    file:
      path: "/var/www/{{ DOMAIN_NAME }}/log"
      owner: "{{ DOMAIN_NAME }}"
      group: "{{ DOMAIN_NAME }}"
      mode: 0755
      state: directory

  - name: create other directory
    file:
      path: "/var/www/{{ DOMAIN_NAME }}/sess"
      owner: "{{ DOMAIN_NAME }}"
      group: "{{ DOMAIN_NAME }}"
      mode: 0755
      state: directory

  - name: create other directory
    file:
      path: "/var/www/{{ DOMAIN_NAME }}/tmp"
      owner: "{{ DOMAIN_NAME }}"
      group: "{{ DOMAIN_NAME }}"
      mode: 0755
      state: directory

  - name: create other directory
    file:
      path: "/var/www/{{ DOMAIN_NAME }}/upload"
      owner: "{{ DOMAIN_NAME }}"
      group: "{{ DOMAIN_NAME }}"
      mode: 0755
      state: directory

  - name: create other directory
    file:
      path: "/var/www/{{ DOMAIN_NAME }}/log/apache2"
      owner: "{{ DOMAIN_NAME }}"
      group: "{{ DOMAIN_NAME }}"
      mode: 0755
      state: directory

  - name: create other directory
    file:
      path: "/var/www/{{ DOMAIN_NAME }}/log/nginx"
      owner: "{{ DOMAIN_NAME }}"
      group: "{{ DOMAIN_NAME }}"
      mode: 0755
      state: directory

mysql_install.yml file:

  - name: add mysql repo
    get_url:
      url: https://dev.mysql.com/get/mysql-apt-config_0.8.6-1_all.deb
      dest: "/tmp"
      mode: 0440

  - name: install mysql repo
    apt: "deb=/tmp/mysql-apt-config_0.8.6-1_all.deb"
    become: true

  - name: add key mysql and update repo
    shell: "apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 467B942D3A79BD29 && apt update"

  - name: install python-mysqldb
    apt:
      name: python-mysqldb
      state: present
      update_cache: yes

  - name: check latest version of mysql 5.7
    command: bash -c "apt-cache showpkg mysql-server|grep 5.7|head -1|cut -d' ' -f1"
    register: latestmysql57
  - debug: msg="{{ latestmysql57.stdout }}"

  - name: install mysql 57
    apt:
      name: mysql-server={{ latestmysql57.stdout }}
      state: present
      update_cache: yes

  - name: update mysql root password for all root accounts
    become: true
    mysql_user:
      name: root
      host: "{{ item }}"
      password: "{{ mysql_root_password }}"
      login_user: root
      login_password: 12345
      check_implicit_admin: yes
      priv: "*.*:ALL,GRANT"
      state: present
    with_items:
      - 127.0.0.1
      - ::1
      - localhost

  - name: Create a new database with name 'DOMAIN_NAME_DB'
    mysql_db:
      login_user: root
      login_password: "{{ mysql_root_password }}"
      name: "{{name_db}}"
      state: present

  - name: add user DOMAIN_NAME_USR
    mysql_user:
      login_user: root
      login_password: "{{ mysql_root_password }}"
      host: localhost
      name: "{{user_db}}"
      password: "{{password_user_db}}"
      priv: '{{name_db}}.*:ALL,GRANT'
      state: present

Variable file:

DOMAIN_NAME: domain_name
locale1: ru_RU.UTF-8
locale2: en_US.UTF-8
time_zone: Europe/Moscow
User_uid: 10000
Group_GID: 10000
user_password: password
mysql_root_password: password
name_db: domain_name_db
user_db: domain_name_usr
password_user_db: password

Outcome

In this article, we have covered the first half of the practical part of the ansible tutorial series. We put into practice the knowledge gained from the first article. In the next article, we will cover the rest of the points, after which we will have a full-fledged ansible role ready for quickly deploying simple projects. We will also post this ansible role on github for review.