with Vagrant using Ansible
Vagrant and Ansible are both of favourite tools in DevOps culture. Vagrant is to automate infrastructure provisioning your as working with virtualization platform VirtualBox. Ansible is configuration management tool that in our case lamp stack server will be configured with its help on specific VMs that created on Vagrant. LAMP stands for Linux, Apache, MySQL, PHP.
Vagrant 2.2.0
which version is installed, means that it's completed.$ mkdir lamp
$ cd lamp
[:lamp]$ vagrant init
$ export http_proxy=PROXY_ADDRESS:PROXY_PORT
$ export https_proxy=PROXY_ADDRESS:PROXY_PORT
$ vagrant plugin install vagrant-proxyconf
config.vm.box = "bento/centos-7.3"
config.vm.define "agent" do |agent|
agent.vm.hostname = "agent"
agent.vm.network "private_network", ip: "192.168.33.10"
end
config.vm.define "web" do |web|
web.vm.hostname = "apache"
web.vm.network "private_network", ip: "192.168.33.20"
web.vm.network "forwarded_port", guest: 80, host: 8080
end
config.vm.define "db" do |db|
db.vm.hostname = "mysql"
db.vm.network "private_network" , ip: "192.168.33.30"
end
if Vagrant.has_plugin?("vagrant-proxyconf")
config.proxy.http = "http://PROXY_ADDRESS:PROXY_PORT/"
config.proxy.https = "https://PROXY_ADDRESS:PROXY_PORT/"
config.proxy.no_proxy = "localhost,127.0.0.1,.example.com"
end
config.vm.box
is uncommented as default and you can assign in here which operating system you will use as we prefer bento/centos-7.3 corresponding to Centos 7.3. For proxy settings new lines should be added to Vagrantfile not containing as default also as shown above with your own values for proxy ip address and port. [:lamp]$ vagrant up
[:lamp]$ vagrant ssh agent
Now we are on agent VM and ready to install Ansible.
[vagrant@agent ~]$ sudo yum -y update
[vagrant@agent ~]$ sudo yum -y install epel-release
[vagrant@agent ~]$ sudo yum -y install ansible
[vagrant@agent ~]$ ansible --version
After checking Ansible version number, installation is confirmed. To work with multiple hosts like web and db over agent, Hosts file & inventory file should be modified with hostnames & IP Addresses to let the agent server know which machines will be configured by Ansible.
[vagrant@agent ~]$ sudo vi /etc/hosts
192.168.33.20 apache
192.168.33.30 mysql
[vagrant@agent ~]$ sudo vi /etc/ansible/hosts
[webservers]
apache
[databases]
mysql
Now we will make ssh connections to web and db servers over agent server to not ask for password.
[vagrant@agent ~]$ ssh-keygen
[vagrant@agent ~]$ cat .ssh/id_rsa.pub
[vagrant@agent ~]$ ssh apache
[vagrant@apache ~]$ vi .ssh/authorized_keys
In Mac OS X, click touchpad to select copy and paste functions.
[vagrant@apache ~]$ logout
[vagrant@agent ~]$ ssh mysql
[vagrant@mysql ~]$ vi .ssh/authorized_keys
[vagrant@mysql ~]$ logout
ssh connections are done; from agent server as logging to servers with ssh apache
and ssh mysql
commands as above, it can be checked that it will not ask a password anymore. At the first time, it will ask for 'yes/no' in prompt, type yes.
[vagrant@agent ~]$ ansible all -a "cat etc/hosts"
In output, command will give us host list (apache & mysql) that ansible can connect.
You can see lamp folder and its subfolders on the left header in Sublime Text.
application.yml file is located as sub-file of lamp folder and ready to write.
- hosts: webservers become: yes tasks: - name: Install the Apache web server yum: name=httpd state=present - name: Ensure that Apache is started and enabled on system boot service: name=httpd state=started enabled=yes - name: Add EPEL repository yum_repository: name: epel description: EPEL YUM repo baseurl: https://download.fedoraproject.org/pub/epel/$releasever/$basearch/ - name: Add public key of EPEL rpm_key: state: present key: https://archive.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-7 - name: Add Webtatic repository yum: name: https://mirror.webtatic.com/yum/el7/webtatic-release.rpm state: present
hosts - hostnames that specified in ansible inventory file will be configured
become: yes - to run commands as sudo
tasks - to identify multitask for playbook
name - definitions of tasks
yum - package manager that will be used, name=httpd - package that will be installed , state=present - latest version of package to install
service - to identify start/stop/restart services, state=started - to ensure apache started , enabled=yes - to start apache on boot
- name: Install PHP 7 and some required packages yum: name: - mod_php71w - php71w-cli - php71w-common - php71w-gd - php71w-mbstring - php71w-mcrypt - php71w-mysqlnd - php71w-xml - unzip - vim - mariadb state: present - name: Download and extract the CodeIgniter archive to /var/www/html unarchive: src: https://github.com/bcit-ci/CodeIgniter/archive/3.1.5.zip dest: /var/www/html remote_src: yes - name: Change the files ownership to be owned by vagrant file: path: /var/www/html owner: vagrant group: vagrant recurse: yes
name - package names to be installed with yum
unarchive - to use unzip module
src - from where you get the file to be unzipped
dest - to where you unzip
remote_src: yes - to identify the file to be unarchived is not a local file from remote source
file - to set attributes of files
path - path to the file being managed
owner - user that should own the file/directory
group - user of the group that should own the file/directory
recurse: yes - to apply the change of ownership to also rest of files underneath of directory specified in path
- name: Change the web home directory to point at /var/www/html/CodeIgniter-3.1.5 lineinfile: path: /etc/httpd/conf/httpd.conf regexp: '^.*DocumentRoot "/var/www/html".*$' line: DocumentRoot "/var/www/html/CodeIgniter-3.1.5" state: present notify: - restart Apache - name: Ensure that mod_rewrite is enabled in Apache lineinfile: path: /etc/httpd/conf.modules.d/00-base.conf regexp: '^.*rewrite_module.*$' line: 'LoadModule rewrite_module modules/mod_rewrite.so' state: present
lineinfile - function to manage lines in file
path - file to be managed
regexp - regular expression to find line to be modified
To search every line with whole text indicated via regular expression starting from ^.* ending with .*$
line - line to be replaced with regexp in file
state: present - to check line is there: if not there, to add it; if there, to change it.
notify - to trigger an action to handlers module
mod_rewrite provides a way to rewrite URLs, it's already uncommented in 00-base.conf file however this step is required to be sure it's enabled.
Ugly URL - www.example.com/index.php?key1=val1&key2=val2
Pretty URL - www.example.com/key1/val1/key2/val2
- hosts: webservers become: yes handlers: - name: restart Apache service: name=httpd state=restarted tasks:
handlers - to identify list of tasks triggered by notify
name: - should be same as notify action name
state=restarted - to restart Apache specified as name=httpd
RewriteEngine on RewriteCond $1 !^(index\.php|resources|robots\.txt) RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php/$1 [L,QSA]
RewriteEngine on - enables mod_rewrite
RewriteCond $1 !^(index\.php|resources|robots\.txt) - a condition to rewrite as long as $1 doesn't equal on of the files listed to the right of the condition or allow these directories and files to be displayed directly
RewriteCond %{REQUEST_FILENAME} !-f - a condition to requested filename is not an existing file
RewriteCond %{REQUEST_FILENAME} !-d - a condition to requested filename is not an existing directory
RewriteRule ^(.*)$ index.php/$1 [L,QSA] - rewrite URL as appending any query strings to the request - QSA and how to add the parameter - $1 with index.php in whole requested path as one parameter - ^(.*)$ then stop processing this .htaccess file - L
- name: Copy htaccess to CodeIgniter copy: src: htaccess dest: /var/www/html/CodeIgniter-3.1.5/.htaccess owner: vagrant group: vagrant - name: Allow .htaccess file to be used lineinfile: path: /etc/httpd/conf/httpd.conf insertafter: '</Directory>' line: | <Directory "/var/www/html/CodeIgniter-3.1.5"> AllowOverride all </Directory> notify: - restart Apachecopy - module to copy local file to remote machine
src - filename to be copied
dest - to where you copy
insertafter - to insert the line after a specified string
line: | - to add more then one lineSo far all steps in application.yml are done to configure web/application server, we can go through running playbook.
[vagrant@agent ~]$ ansible-playbook /vagrant/application.yml
It will take some time to complete process and in prompt task names will showed up while proceeding. Thus every step can be easily followed to confirm it's ok or to look into which step is causing failure. After all process is done, we can check further web/application server is ready.
[vagrant@agent ~]$ ssh apache
[vagrant@apache ~]$ sudo systemctl status httpd
In output, Started The Apache HTTP Server. is showed up and it works as we expected.
[vagrant@apache ~]$ cd /etc/yum.repos.d/
[vagrant@apache ~]$ ls -l
epel.repo and webtatic.repo are showed up in yum repository list as we expected.
[vagrant@apache ~]$ php -v
PHP version number is appeared in output to confirm installation.
[vagrant@apache ~]$ ls -l /var/www/html
[vagrant@apache ~]$ ls -l /var/www/html/CodeIgniter-3.1.5/
First command will give CodeIgniter directory in list owned by vagrant for user and group. Second command is to check vagrant user & group for sub-folders of CodeIgniter as well.
[vagrant@apache ~]$ vim /etc/httpd/conf/httpd.conf
DocumentRoot text will be highlighted when you search it, you can confirm it's changed to "/var/www/html/CodeIgniter-3.1.5" from default page "/var/www/html" as we are intended.
[vagrant@apache ~]$ vim /etc/httpd/conf.modules.d/00-base.conf
mod_rewrite.so can be searched to see it's in there and uncommented.
[vagrant@apache ~]$ vim /etc/httpd/conf/httpd.conf
When you search AllowOverride, you will see it's added after the latest </Directory> line.
database.yml file is located as sub-file of lamp folder and ready to write.
- hosts: databases become: yes tasks: - name: Ensure that MariaDB is installed yum: name: - mariadb-server - MySQL-python - name: Ensure that MariaDB is started and enabled service: name=httpd state=present enabled=yes - name: Set the root password for MySQL mysql_user: name: root password: admin state: present - name: Upload the .my.cnf file to save the credentials copy: src: my.cnf dest: /root/.my.cnf owner: root mode: 0600
[client]
user=root
password=admin
MariaDB is free source edition of MySQL
MySQL-python - it's required to use mysql_user services/modules
mysql_user - to manage user and password in MySQL database, password is assigned for root user because root user has empty password as default.
root/admin user can not login any remote host/machine, it's just allowed to login where database is installed
state: present - to make the user exist
my.cnf - other way to assign password for root user as credentials file for MariaDB
owner: root - root is default user however we confirm it with this step
mode: 0600 - read/write permission for only owner not other user & group
- name: Remove anonymous accounts mysql_user: name: '' host_all: yes state: absent - name: Create application database mysql_db: name: ci_database state: present - name: Create an application user mysql_user: name: ci_user password: cipassword host: '%' priv: ci_database.*:ALL state: present
name: '' - to identify anonymous user accounts
host_all: yes - to apply removing to all hostnames
state: absent - to remove account
mysql_db - module to create database
Application user and password are created to connect database server(mysql) from application server(apache). MariaDB was installed in application server as package via application.yml.
host: '%' - to connect all hosts with specified application user and password
priv: ci_database.*:ALL - privilege to access all tables of application database ci_database
CodeIgniter database credentials should be configured to it's able to connect database server. database.php file containing credentials will be copied to lamp folder, edited and uploaded again via application.yml updated with proper values.
$ scp apache:/var/www/html/CodeIgniter-3.1.5/application/config/database.php /vagrant/
'hostname' => '192.168.33.30',
'username' => 'ci_user',
'password' => 'cipassword',
'database' => 'ci_database',
notify: - restart Apache - name: Upload the database.php file copy: src: database.php dest: /var/www/html/CodeIgniter-3.1.5/application/config/database.php owner: vagrant group: vagrantWhen we run
ansible-playbook /vagrant/application.yml
on agent server again, database.php will be uploaded with updated credentials values. To check, login to webserver withssh apache
and runvim /var/www/html/CodeIgniter-3.1.5/application/config/database.php
You will see that credential values are same as we updated above.Finally we have completed database configuration management. We can go through running playbook for database.yml.
[vagrant@agent ~]$ ansible-playbook /vagrant/database.yml
It will take some time to complete process same as applicaton.yml. After completed, we can check further database server and connection between application and database.
[vagrant@agent ~]$ ssh mysql
[vagrant@mysql ~]$ mysql -u root -padmin
MariaDB[(none)]> prompt is showed up. In database server, we are logged into MariaDB with root user and admin password. To exit MariaDB, type exit or \q
[vagrant@agent ~]$ ssh apache
[vagrant@apache ~]$ mysql -u ci_user -pcipassword -h 192.168.33.30
MariaDB[(none)]> prompt is showed up. From web/application server we are logged into MariaDB as connecting database server with ci_user user, cipassword password and 192.168.33.30 IP number of database server.
$ /usr/bin/ruby -e "$(curl -fsSL
https://raw.githubusercontent.com/Homebrew/install/master/install)"
Press Enter and type your password when it requires to complete installation.
$ brew install ansible
Lastly with ansible --version
command as checking version number, installation can be confirmed.
vagrant up
command will be sufficient to launch pre-configured LAMP stack environment automatically. [:lamp]$ vagrant destroy web
[:lamp]$ vagrant destroy db
config.vm.define "agent" do |agent| agent.vm.hostname = "agent" agent.vm.network "private_network", ip: "192.168.33.10" end config.vm.define "web" do |web| web.vm.hostname = "apache" web.vm.network "private_network", ip: "192.168.33.20" web.vm.network "forwarded_port", guest: 80, host: 8080 web.vm.provision "ansible" do |ansible| ansible.playbook = "application.yml" end end config.vm.define "db" do |db| db.vm.hostname = "mysql" db.vm.network "private_network" , ip: "192.168.33.30" db.vm.provision "ansible" do |ansible| ansible.playbook = "database.yml" end end
Vagrantfile is ready however we need to change hosts value in playbooks. With latest changes, Vagrantfile is already updated according to which VM will be configured by specific playbook. On the other hand, webservers and databases values are identified in etc/hosts and etc/ansible/host on agent server that we will not use to connect web/application and database server over it at this time. I mean local machine will not figure out these hosts so changing value to all is required.
- hosts: all become: yes handlers: - name: restart Apache service: name=httpd state=restarted tasks:
- hosts: all become: yes tasks:
All changes are completed and ready to run.
[:lamp]$ vagrant up
While Vagrant is creating VMs also Ansible will run playbooks that specified to each VMs. After completed, we can confirm that they are accessible and configured properly. Vagrant may give error in some cases and quit unexpectedly, you can run vagrant provision
command to complete process somehow. Another way to handle instant errors, over VirtaulBox GUI you are able to power off & remove VMs so you can bring VMs up from scratch with vagrant up
command.
[:lamp]$ vagrant ssh web
[vagrant@apache ~]$ mysql -u ci_user -pcipassword -h 192.168.33.30
MariaDB[(none)]> prompt is showed up so connection is confirmed to database server with database.php credentials.
[:lamp]$ vagrant ssh db
[vagrant@mysql ~]$ mysql -u root -padmin
MariaDB[(none)]> prompt is showed up so we can confirm that database server is configured with my.cnf
Finally, we can check access to localhost:8080 in web browser; as we set, CodeIgniter will welcome us.
In conclusion, LAMP stack is prepared in two ways; setting up VMs with Vagrant and configuration with Ansible over agent server, other one is integrating Ansible with Vagrant accessible over local machine directly.
You can find all required files that we worked on from here
Thanks!