This article is a tutorial that is intended for novice system administrators, so experienced professionals can safely skip it. The publication is a continuation of a series of educational articles here , here .
In this article, we will practice writing an Ansible role to automatically raise a web server.
So, as I mentioned in the first part , any ansible-role writing is accompanied by a plan. This plan should include everything that will need to be installed and configured.
If we consider all the points in one article, then the material turns out to be too voluminous – in fact, that is why it was decided to divide it into two parts. Here is our plan, in which the first three points that we completed in the first part are crossed out :
Point 1. Initial server setup.Point 2. Installing LEMP.Item 3. Rights and user.- Step 4. Setting up LEMP.
- Item 5. Transfer of site code and database.
- Item 6. Testing.
- Item 7. Bottom line.
In this article, we will complete the remaining four steps. So, at the moment in the /var/ansible directory is:
.
├── hosts.txt
├── LEMP
│ ├──defaults
│ │ └── main.yml
│ ├── files
│ ├── handlers
│ │ └── main.yml
│ ├── meta
│ │ └── main.yml
│ ├── README.md
│ ├── tasks
│ │ ├── apache2_install.yml
│ │ ├── default_settings.yml
│ │ ├── default_user_settings.yml
│ │ ├── exim4_install.yml
│ │ ├── main.yml
│ │ ├── mysql_install.yml
│ │ └── nginx_install.yml
│ ├── templates
│ ├── tests
│ │ ├── inventory
│ │ └── test.yml
│ └── vars
│ └── main.yml
└── playbook.yml
Item 4. Initial server setup.
At this stage, we will configure the following services:
- php
- nginx
- mysql
- apache2
- exim4
php setup.
The first step is to install php on the remote server. To do this, create an empty file /var/ansible/LEMP/taskscalled php.yml. We use the apt module .
cd /var/ansible/LEMP/tasks touch php.yml
We add:
- name: install php apt: name={{ item }} update_cache=yes state=latest with_items: - git - php - php-curl - php-gd - php-mbstring - php-xml - php-xmlrpc - php-soap - php-intl - php-zip
- with_items : – create an array of variables from the packages we need.
- name={{ item }} – here we use the item array to install php modules. apt will run until it has installed all the packages in the item array.
Don’t forget to include the new yml file in main.yml.
Add to main.yml.
#####install php - include_tasks: php.yml
For php we will need to pass a php.ini file.
Create a php directory and a php-ansible.ini file in /var/ansible/LEMP/files/.
cd /var/ansible/LEMP/files/ mkdir php touch php/php.ini
Insert into the php-ansible.ini file, after [PHP], any configuration you need:
[PHP]
Next, you need to transfer this file to /etc/php/7.3/apache2/. It will be automatically loaded and applied after restarting the php interpreter. In this case it is apache2.
Open the php.yml file in jobs (/var/ansible/LEMP/tasks/php.yml)and add file copy and apache2 reload.
We add:
- name: copy config copy: src: php/php-ansible.ini dest: /etc/php/7.3/apache2 - name: restart apache2 service: name: apache2 state: restarted
Setting up nginx.
- To configure, we need to transfer the nginx configuration files:
- nginx.conf
- site configuration. domain_name.conf.
- Enable site.
- Reload nginx.
Let’s go to the directory /var/ansible/LEMP/files.
Create an nginx directory.
mkdir nginx cd nginx/
In the nginx directory, you need to create 2 files: nginx.conf and domain_name.conf .
touch nginx.conf domain_name.conf
- nginx.conf must be passed to the nginx root directory
- domain_name.conf needs to be passed to sites-available.
In nginx.conf we insert the configuration from our previous article .
The domain_name.conf is the configuration from our previous article .
After adding the configuration, go to ./tasks/nginx_install.yml.
We transfer the configuration to the remote server using the copy and file module .
We add:
- name: Copy config. copy: src: nginx/nginx.conf dest: /etc/nginx/nginx.conf - name: Copy domain_name config copy: src: nginx/domain_name.conf dest: /etc/nginx/sites-available/domain_name.conf - name: enable site domain_name file: src: /etc/nginx/sites-available/domain_name.conf dest: /etc/nginx/sites-enabled/domain_name.conf state: link - name: restart nginx service: name: nginx state: restarted
- src – file in ./files directory;
- dest – the address where the file is stored on the remote server.
Let’s run and check:
TASK [LEMP : include_tasks] ************************************************************************************************************************************************************************************* included: /var/ansible/LEMP/tasks/nginx_install.yml for ansible2 TASK [LEMP : Install nginx] ************************************************************************************************************************************************************************************* ok: [ansible2] TASK [LEMP : Copy config.] ************************************************************************************************************************************************************************************** changed: [ansible2] TASK [LEMP : Copy domain_name config] ************************************************************************************************************************************************************************** changed: [ansible2] TASK [LEMP : enable site domain_name] *************************************************************************************************************************************************************************** changed: [ansible2] PLAY RECAP ****************************************************************************************************************************************************************************************************** ansible2 : ok=4 changed=3 unreachable=0 failed=0
nginx config added.
Outcome:
- The nginx configuration and domain_name are passed.
- A symbolic link is created in sites-enabled.
- The service is restarted.
mysql setup.
You need to create a mysql.cnf configuration file and then transfer it to a remote server.
Create mysql directory in /var/ansible/LEMP/files.
cd /var/ansible/LEMP/files mkdir mysql cd mysql/ touch mysql.cnf
Insert after [mysqld] the configuration you need.
[mysqld]
Open ansible mysql configuration file/var/ansible/LEMP/tasks/mysql_install.yml
We transfer the configuration file to the remote server and restart mysql.
We add:
- name: Copy config copy: src: mysql/mysql.cnf dest: /etc/mysql/mysql.conf.d/mysql.cnf - name: restart mysql service: name: mysql state: restarted
Outcome:
- Restart the mysql service.
- mysql setup is complete.
apache2 setup.
We do similar actions as in nginx
Need to transfer
- apache2.conf
- domain_name.conf
- ports.conf
We create a directory and the above listed files in the directory /var/ansible/LEMP/files.
Configurations for apache2 and domain_name can be taken from the following articles:
- apache2.conf configuration
- configuration domain_name.conf .
We configure apache2 to work on port 81. Add configuration for ports.conf:
Listen 81 <IfModule ssl_module> Listen 443 </IfModule> <IfModule mod_gnutls.c> Listen 443 </IfModule>
Next we go to/var/ansible/LEMP/tasks/apache2_install.yml
We add:
- name: copy config copy: src: apache2/apache2.conf dest: /etc/apache2/apache2.conf - name: copy domain_name config copy: src: apache2/domain_name.conf dest: /etc/apache2/sites-available/domain_name.conf - name: enable site domain_name file: src: /etc/apache2/sites-available/domain_name.conf dest: /etc/apache2/sites-enabled/domain_name.conf state: link - name: copy ports copy: src: apache2/ports.conf dest: /etc/apache2/ports.conf - name: restart apache2 service: name: apache2 state: restarted
Outcome:
- Transferring configuration files;
- Restart the apache2 service.
Setting up exim4.
Necessary:
- Upload configuration file /etc/exim4/update-exim4.conf.conf;
- Reload exim4.
Create an exim4 directory and update-exim4.conf.conf file in /var/ansible/LEMP/files.
Add to the exim4 configuration file:
# /etc/exim4/update-exim4.conf.conf # # Edit this file and /etc/mailname by hand and execute update-exim4.conf # yourself or use 'dpkg-reconfigure exim4-config' # # Please note that this is _not_ a dpkg-conffile and that automatic changes # to this file might happen. The code handling this will honor your local # changes, so this is usually fine, but will break local schemes that mess # around with multiple versions of the file. # # update-exim4.conf uses this file to determine variable values to generate # exim configuration macros for the configuration file. # # Most settings found in here do have corresponding questions in the # Debconf configuration, but not all of them. # # This is a Debian specific file dc_eximconfig_configtype='local' dc_other_hostnames='domain_name' dc_local_interfaces='127.0.0.1 ; ::1' dc_readhost='' dc_relay_domains='' dc_minimaldns='false' dc_relay_nets='' dc_smarthost='' CFILEMODE='644' dc_use_split_config='false' dc_hide_mailname='' dc_mailname_in_oh='true' dc_localdelivery='mail_spool'
Add the desired configuration to this file.
(/var/ansible/LEMP/tasks/exim4_install.yml)Add “copy file” and “reload exim4” to the configuration file.
- name: copy config copy: src: exim4/update-exim4.conf.conf dest: /etc/exim4/update-exim4.conf.conf - name: restart exim4 service: name: exim4 state: restarted
The configuration for exim4 is ready.
Structure of files directory with configuration:
├── files
│ ├── apache2
│ │ ├── apache2.conf
│ │ ├── domain_name.conf
│ │ └── ports.conf
│ ├── exim4
│ │ └── update-exim4.conf.conf
│ ├── mysql
│ │ └── mysql.cnf
│ ├── nginx
│ │ ├── domain_name.conf
│ │ └── nginx.conf
│ └── PHP
│ └── php-ansible.ini
At the moment, the configuration of services is completed. If you need to upload or change additional files, you can also add and change them using the above method.
Item 5. Transfer of site code and database.
At this point, you need to move the site dump to load it into the database and transfer the site code.
We will need 2 directories in the files directory.
It:
- mysql_dump – site dump directory;
- data – directory with code.
First, add the platform code to the /data.
In our case, the site code will be the wordpress installer .
Next, transfer the dump of your database to the directory /mysql_dump.
Create a new yml file called file.yml at /var/ansible/LEMP/tasks/.
Include it in main.yml
####add_site_file - include_tasks: file.yml
Add a task to it:
- name: copy file domain_name copy: src: data/ dest: /var/www/domain_name/data/ owner: domain_name group: domain_name - name: copy dump copy: src: mysql_dump/dump.sql dest: /tmp/dump.sql - name: mysql_dump mysql_db: name: domain_name_db state: import target: /tmp/dump.sql login_user: root login_password: "{{ mysql_root_password }}"
In order to load the dump into the database, the mysql_db module is used.
Site files and dump were moved to the server.
Item 6. Testing.
At the moment the directory structure looks like this:
.
├── hosts.txt
├── LEMP
│ ├── defaults
│ │ └── main.yml
│ ├── files
│ │ ├── apache2
│ │ │ ├── apache2.conf
│ │ │ ├── mysql
_
_
_
_
_
_ cnf
│ │ ├── mysql_dump
│ │ │ └── dump.sql │ │
├── nginx
│ │ │ ├── ─ php-ansible.ini │ ├── handlers
│ │ └── main.yml
│ ├── meta
│ │ └── main.yml
│ ├── README.md
│ ├── tasks
│ │ ├── apache2_install.yml
│ │ ├── default_settings.yml
│ │ ├── default_user_settings.yml
│ │ ├── exim4_install.yml
│ │ ├── file.yml
│ │ ├── main.yml
│ │ ├── mysql_install.yml
│ │ ├── nginx_install.yml
│ ── php.yml
│ ├── templates
│ ├── tests
│ │ ├── inventory
│ │ └── test.yml
│ └── vars
│ └── main.yml
├── php.ini
├── playbook.retry
└── playbook.yml
Task configuration in .tasks directory:
apache2_install.yml :
- name: Install apache2 apt: name: apache2 state: latest - name: copy config copy: src: apache2/apache2.conf dest: /etc/apache2/apache2.conf - name: copy domain_name config copy: src: apache2/domain_name.conf dest: /etc/apache2/sites-available/domain_name.conf - name: copy ports copy: src: apache2/ports.conf dest: /etc/apache2/ports.conf - name: restart apache2 service: name: apache2 state: restarted
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}}"
default_user_settings.yml :
- 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
exim4_install.yml :
- name: Install exim4 apt: name: exim4 state: latest - name: copy config copy: src: exim4/update-exim4.conf.conf dest: /etc/exim4/update-exim4.conf.conf - name: restart exim4 service: name: exim4 state: restarted
main.yml :
# - include_tasks: default_settings.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 #####default user settings # - include_tasks: default_user_settings.yml #####install php # - include_tasks: php.yml #####copy file domain_name - include_tasks: file.yml
mysql_install.yml:
- 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 - name: Copy config copy: src: mysql/mysql.cnf dest: /etc/mysql/mysql.conf.d/mysql.cnf - name: restart mysql service: name: mysql state: restarted
nginx_install.yml :
- name: Install nginx apt: name: nginx state: latest - name: Copy config. copy: src: nginx/nginx.conf dest: /etc/nginx/nginx.conf - name: Copy domain_name config copy: src: nginx/domain_name.conf dest: /etc/nginx/sites-available/domain_name.conf - name: enable site domain_name file: src: /etc/nginx/sites-available/domain_name.conf dest: /etc/nginx/sites-enabled/domain_name.conf state: link - name: restart nginx service: name: nginx state: restarted
php.yml :
- name: install php apt: name={{ item }} update_cache=yes state=latest with_items: - git - php - php-curl - php-gd - php-mbstring - php-xml - php-xmlrpc - php-soap - php-intl - php-zip - name: copy config copy: src: php/php-ansible.ini dest: /etc/php/7.3/apache2 - name: restart apache2 service: name: apache2 state: restarted
Variable file /var/ansible/LEMP/vars/main.yml:
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
For testing, we go to a remote server. Add to /etc/hostsrecord like:
127.0.0.1 domain_name.com
Checking the status of services:
systemctl status nginx apache2 mysql exim4
If all services are working, we make a request to the site using curl.
# curl -LI domain_name.com HTTP/1.1 302 Found Server: nginx Date: Wed, 29 Jun 2022 11:32:10 GMT Content-Type: text/html; charset=UTF-8 Connection: keep-alive Keep-Alive: timeout=15 Location: http://domain_name.com/wp-admin/setup-config.php HTTP/1.1 200 Server: nginx Date: Wed, 29 Jun 2022 11:32:10 GMT Content-Type: text/html; charset=UTF-8 Connection: keep-alive Keep-Alive: timeout=15 Expires: Wed, 11 Jan 1984 05:00:00 GMT Cache-Control: no-cache, must-revalidate, max-age=0
Everything is working! Next, you need to configure the site in the web interface.
Item 7. Bottom line.
Well, here we are and got acquainted with Ansible and the basic modules that are most often used in its work. Of course, this ansible role is not ideal, and was created only for learning. This mini-course will be enough to understand how ansible works at a basic level.
Related posts:
- About Ansible for Beginners: Practice (Part I)
- Setting up a LEMP server with Ansible for simple projects. Part One: Getting to Know Ansible
- Setting up a server with docker for simple projects. Part two: docker-compose
- Setting up a LEMP server for simple projects. Instructions for the little ones. Part three