Ansible Code Promotion in a Shared Hosting Environment ###################################################### We are deploying our nifty web portal to two web and two app servers. The app servers will also act as alternate web servers, so they must be configered for both roles. * The machines are **web01**, **web02**, **app01**, and **app02**. * They are members of the groups **webs** and **apps**, respectively. .. graphviz:: digraph ansiblecodepromotion { rankdir = "LR"; node [shape = "box", color=blue]; content [shape="folder", label="code and\ncontent"] roles [shape="folder"] groups [shape="folder"] nodes [shape="folder"] variables [shape="folder"] ansible [label="Configuration\nEnforcement"] subgraph cluster_0 { label = "webs"; color = "orange"; invis0 [color="transparent", fontcolor=transparent, shape=point] web01; web02; invis0 -> web01 [color=transparent]; invis0 -> web02 [color=transparent]; } subgraph cluster_1 { label = "apps"; color = "orange"; invis1 [color="transparent", fontcolor=transparent, shape=point]; app01; app02; invis1 -> app01 [color=transparent]; invis1 -> app02 [color=transparent]; } content -> roles roles -> ansible groups -> ansible nodes -> ansible variables -> groups variables -> nodes ansible -> invis0 [label="web role", fontcolor="red", color="red"] ansible -> invis1 [label="web and app role", fontcolor="purple", color="purple"] ansible -> web01 [label="node config", fontcolor="blue", color="blue", style="dotted"] ansible -> web02 [label="node config", fontcolor="blue", color="blue", style="dotted"] ansible -> app01 [label="node config", fontcolor="blue", color="blue", style="dotted"] ansible -> app02 [label="node config", fontcolor="blue", color="blue", style="dotted"] } Structure of a Role =================== A sample role directory looks like this. Have the CDS (e.g. Jenkins) deploy to *.../roles//src/*. ============= === Content Purpose ============= === .../tasks Contains the main.yml that specifies state for the installation. This is the file that calls for templating configuration based on variables. .../vars Contains the main.yml that lists mock (or dummy) variables. .../handlers Contains the main.yml for triggered tasks. This is very convenient. For example, a change in httpd.conf should trigger an Apache2 restart. .../templates Contains the *\*.j2* files that become text files built with variable content. These are Jinja2 and the variables are in plain YAML. No tricks. .../src This is a home for all that copies to the host *without* templating. Putting everything in "templates" is not harmful as long as tasks/main.yml makes sense. .../meta Contains the main.yml that tracks dependencies among roles. ============= === Sample Web Server Role ====================== These tasks install Apache and push the configuration file. I can also add an application server role in this doc, but you probably get the point. .. code-block:: yaml :linenos: :caption: /etc/ansible/roles/webserver/tasks/main.yml --- - name: Ensure Apache2 exists and is the current version yum: name=httpd state=latest - name: Build our configuration files from templates and variables template: src=/etc/ansible/roles/webserver/templates/{{ item }}.j2 dest=/etc/httpd/conf.d/{{item }} mode=0644 owner=www-data group=www-data setype=httpd_config_t backup=yes with_items: - portal.conf - ssl.conf notify: "restart Apache" This handler restarts Apache when triggered. .. code-block:: yaml :linenos: :caption: /etc/ansible/roles/webserver/handlers/main.yml - name: Restart and enable Apache service: name=httpd state=restarted enabled=true listen: "restart Apache" This Apache configuration template serves a directory Ansible replaces {{portalpath}} with the content of that variable. See `Ansible configuration`_ below. .. _`Ansible configuration`: #id1 .. code-block:: jinja :linenos: :caption: /etc/ansible/roles/webserver/templates/portal.conf.j2 Alias /portal {{ portalpath }} # This is obviously not a portal. I just wanted an illustrative Apache config. AuthType None Options Indexes FollowSymLinks MultiViews IndexOptions +FancyIndexing +FoldersFirst +ScanHTMLTitles +TrackModified +DescriptionWidth=* AllowOverride None Order allow,deny Allow from all Playbooks ========= To apply these roles, we need a playbook to bind our instructions together. .. code-block:: yaml :linenos: :caption: /etc/ansible/playbooks/mywebsite.yml --- # This file is a wrapper for the project - include: web.yml - include: app.yml Notice how the web.yml installs the web server role to both the web servers and the application servers. This is for illustration. .. code-block:: yaml :linenos: :caption: /etc/ansible/playbooks/web.yml --- # This file tells Ansible to apply the web server role - name: ensure web servers are deployed hosts: webs apps become: true gather_facts: true roles: - web Whereas the app role only applies to the application servers. .. code-block:: yaml :linenos: :caption: /etc/ansible/playbooks/app.yml --- # This file tells Ansible to apply the application server role - name: ensure application servers are deployed hosts: apps become: true gather_facts: true roles: - app Ansible configuration ===================== Our hosts file groups our nodes and declares them for Ansible. .. code-block:: ini :linenos: :caption: /etc/ansible/hosts.ini [webs] web01 web02 [apps] app01 app02 Our group variables file sets these variables for every member of **webs**. .. code-block:: yaml :linenos: :caption: /etc/ansible/group_vars/webs.yml portalpath: /var/www/html/portal/ somelistvariable: - value1 - value2 One of our nodes needs special treatment, so its variable file overwrites the value of *portalpath*. .. code-block:: yaml :linenos: :caption: /etc/ansible/host_vars/web02.yml portalpath: /var/www/html/site/ Run the Playbook ================ Now, we run this thing. The option flags are... * -k to prompt for an SSH password * -K to prompt for a sudo password (or press enter for them to be the same) * -D show the deltas imposed by Ansible on the target node. .. code-block:: bash :linenos: ansible-playbook -kKD /etc/ansible/playbooks/mywebsite.yml