Skip to content

Latest commit

 

History

History
867 lines (682 loc) · 28.4 KB

workshop.asciidoc

File metadata and controls

867 lines (682 loc) · 28.4 KB

Ansible Workshop v2

Dag Wieers and Jeroen Hoekx v0.1, Feb 2014

Introduction

Slide presentation

To be done

Distributing virtual image

Starting your virtual machines

Ad-Hoc Commands

Using an inventory

Ansible only talks to hosts you want it to talk to. Those hosts are defined in an inventory file.

[ansible@ws01 workshop]$ cat hosts
[vms]
vm-master ansible_ssh_host=192.168.122.X
vm-web ansible_ssh_host=192.168.122.Y
vm-db ansible_ssh_host=192.168.122.Z

Update the IP addresses to correspond with the ones the systems booted with.

Testing connectivity

The most straightforward usage of Ansible it to run commands on remote hosts. The ping module checks connectivity and correct python setup.

Let’s assume our VM is remote and ping it.

[root@ws01 workshop]$ ansible vm-master -m ping -i hosts
vm-master | success >> {
    "changed": false,
    "ping": "pong"
}

The syntax is ansible <selector> <options>.

We only want to run it on the local host, so we choose ws01 as selector. The module is selected with the -m switch. The -i hosts switch tells Ansible which list of hosts to select from.

We can run this command on multiple hosts by using the group in the inventory:

[root@ws01 workshop]$ ansible vms -m ping -i hosts
vm-master | success >> {
    "changed": false,
    "ping": "pong"
}

vm-web| success >> {
    "changed": false,
    "ping": "pong"
}

vm-db| success >> {
    "changed": false,
    "ping": "pong"
}

Using modules

The command module

The ping module is useful for testing things, but let’s run a generic command on all systems:

[ansible@ws01 workshop]$ ansible vms -m command -a 'date' -i hosts
vm-master | success | rc=0 >>
Mon Feb  3 16:31:32 UTC 2014

vm-web | success | rc=0 >>
Mon Feb  3 16:31:33 UTC 2014

vm-db | success | rc=0 >>
Mon Feb  3 16:31:33 UTC 2014

That uses the command module and gives it arguments with -a.

Installing packages

The goal of this workshop is to deploy a web application. A useful thing to have in that situation is a web server. Let’s use Ansible to make sure Apache is installed and started. This introduces the yum and service modules.

[root@ws01 workshop]# ansible vm-web -m yum -a 'pkg=httpd state=installed' -i hosts
vm-web | success >> {
    "changed": false,
    "msg": "",
    "rc": 0,
    "results": [
        "httpd-2.2.15-29.el6.centos.i686 providing httpd is already installed"
    ]
}

Great. Apache was already installed on the system. Notice the arguments to the yum module. The state parameter is used by virtually every Ansible module.

Starting System Services

Let’s start Apache.

[root@ws01 workshop]# ansible vm-web -m service -a 'name=httpd state=started enabled=yes' -i hosts
vm-web | success >> {
    "changed": false,
    "enabled": true,
    "name": "httpd",
    "state": "started"
}

The service module is one of the two distribution agnostic modules (with user). Package managers for example differ too much in their functionality to have one baseline module.

Another thing to notice is the yes argument to the enabled parameter. Boolean parameters in Ansible are yes and no.

Inventory Groups

The application we want to deploy is multi-tiered. There is the web server part and the database part. It makes sense to be able to group hosts in functional groups.

The built-in inventory can do that:

[vms]
vm-master ansible_ssh_host=192.168.122.X
vm-web ansible_ssh_host=192.168.122.Y
vm-db ansible_ssh_host=192.168.122.Z

[web-servers]
vm-web

[database-servers]
vm-db

Playbooks

Running commands on potentially hundreds of systems is nice, but it does not scale as your infrastructure grows. We need a way to describe the system state. Ansible playbooks provide a declarative way to do just that.

Playbooks are written in YAML. One playbook consists of one or more plays. Each play has a number of tasks. Playbooks are run sequentially. This allows orchestration of actions on multiple system groups.

Note
Explain YAML here using slides

Creating your first playbook

Let’s show an example that ensures Apache is installed and running.

- name: Configure Apache
  hosts: web-servers
  user: root

  tasks:

  - name: Install Apache
    action: yum pkg=httpd state=installed

  - name: Start and Enable Apache
    action: service name=httpd state=started enabled=yes

This playbook has one play. In that play are two tasks.

A play always indicates on which hosts it will run and as what user. Many more parameters are possible, but this is the minimum. Well, you could skip the user, but then it will run as your local user.

We’ve updated the hosts file to include a second group web-servers. Our vm-web system is in that group.

You can run a playbook like this:

[root@ws01 workshop]# ansible-playbook apache.yml -i hosts

PLAY [Configure Apache] *******************************************************

GATHERING FACTS ***************************************************************
ok: [vm-web]

TASK: [Install Apache] ********************************************************
ok: [vm-web]

TASK: [Start and Enable Apache] ***********************************************
ok: [vm-web]

PLAY RECAP ********************************************************************
vm-web                     : ok=3    changed=0    unreachable=0    failed=0

You will notice this playbook runs both tasks and has an additional step to gather facts. This step gathers facts from the system. These facts are available as variables in the remainder of the play.

Gathering facts

Let’s see which facts are available:

[root@ws01 workshop]# ansible vm-web -m setup -i hosts
vm-web | success >> {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "192.168.122.21"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::5054:ff:fe23:1614"
        ],
        "ansible_architecture": "i386",
        "ansible_bios_date": "01/01/2011",
        "ansible_bios_version": "Bochs",
        "ansible_cmdline": {
            "KEYBOARDTYPE": "pc",
            "KEYTABLE": "us",
            "LANG": "en_US.UTF-8",
            "SYSFONT": "latarcyrheb-sun16",
            "quiet": true,
            "rd_LVM_LV": "vg_ws01_root/lv_swap",
            "rd_NO_DM": true,
            "rd_NO_LUKS": true,
            "rd_NO_MD": true,
            "rhgb": true,
            "ro": true,
            "root": "/dev/mapper/vg_ws01_root-lv_root"
        },
        "ansible_date_time": {
            "date": "2014-02-03",
            "day": "03",
            "epoch": "1391415917",
            "hour": "08",
            "iso8601": "2014-02-03T08:25:17Z",
            "iso8601_micro": "2014-02-03T08:25:17.248371Z",
            "minute": "25",
            "month": "02",
            "second": "17",
            "time": "08:25:17",
            "tz": "UTC",
            "tz_offset": "+0000",
            "year": "2014"
        },
        "ansible_default_ipv4": {
            "address": "192.168.122.21",
            "alias": "eth0",
            "gateway": "192.168.122.1",
            "interface": "eth0",
            "macaddress": "52:54:00:23:16:14",
            "mtu": 1500,
            "netmask": "255.255.255.0",
            "network": "192.168.122.0",
            "type": "ether"
        },
        "ansible_default_ipv6": {},
        "ansible_devices": {
            "sr0": {
                "holders": [],
                "host": "",
                "model": "QEMU DVD-ROM",
                "partitions": {},
                "removable": "1",
                "rotational": "1",
                "scheduler_mode": "cfq",
                "sectors": "2097151",
                "sectorsize": "512",
                "size": "1024.00 MB",
                "support_discard": "0",
                "vendor": "QEMU"
            },
            "vda": {
                "holders": [],
                "host": "",
                "model": null,
                "partitions": {
                    "vda1": {
                        "sectors": "409600",
                        "sectorsize": 512,
                        "size": "200.00 MB",
                        "start": "2048"
                    },
                    "vda2": {
                        "sectors": "5879808",
                        "sectorsize": 512,
                        "size": "2.80 GB",
                        "start": "411648"
                    }
                },
                "removable": "0",
                "rotational": "1",
                "scheduler_mode": "cfq",
                "sectors": "6291456",
                "sectorsize": "512",
                "size": "3.00 GB",
                "support_discard": "0",
                "vendor": "6900"
            }
        },
        "ansible_distribution": "CentOS",
        "ansible_distribution_release": "Final",
        "ansible_distribution_version": "6.5",
        "ansible_domain": "",
        "ansible_env": {
            "G_BROKEN_FILENAMES": "1",
            "HOME": "/root",
            "LANG": "C",
            "LESSOPEN": "|/usr/bin/lesspipe.sh %s",
            "LOGNAME": "root",
            "MAIL": "/var/mail/root",
            "PATH": "/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin",
            "PWD": "/root",
            "SHELL": "/bin/bash",
            "SHLVL": "2",
            "SSH_CLIENT": "192.168.122.21 37293 22",
            "SSH_CONNECTION": "192.168.122.21 37293 192.168.122.21 22",
            "USER": "root",
            "_": "/usr/bin/python"
        },
        "ansible_eth0": {
            "active": true,
            "device": "eth0",
            "ipv4": {
                "address": "192.168.122.21",
                "netmask": "255.255.255.0",
                "network": "192.168.122.0"
            },
            "ipv4_secondaries": [],
            "ipv6": [
                {
                    "address": "fe80::5054:ff:fe23:1614",
                    "prefix": "64",
                    "scope": "link"
                }
            ],
            "macaddress": "52:54:00:23:16:14",
            "module": "virtio_net",
            "mtu": 1500,
            "promisc": false,
            "type": "ether"
        },
        "ansible_form_factor": "Other",
        "ansible_fqdn": "ws01",
        "ansible_hostname": "ws01",
        "ansible_interfaces": [
            "lo",
            "eth0"
        ],
        "ansible_kernel": "2.6.32-431.el6.i686",
        "ansible_lo": {
            "active": true,
            "device": "lo",
            "ipv4": {
                "address": "127.0.0.1",
                "netmask": "255.0.0.0",
                "network": "127.0.0.0"
            },
            "ipv4_secondaries": [],
            "ipv6": [
                {
                    "address": "::1",
                    "prefix": "128",
                    "scope": "host"
                }
            ],
            "mtu": 16436,
            "promisc": false,
            "type": "loopback"
        },
        "ansible_machine": "i686",
        "ansible_memfree_mb": 375,
        "ansible_memtotal_mb": 498,
        "ansible_mounts": [
            {
                "device": "/dev/mapper/vg_ws01_root-lv_root",
                "fstype": "ext4",
                "mount": "/",
                "options": "rw,noatime",
                "size_available": 1166802944,
                "size_total": 2113748992
            },
            {
                "device": "/dev/vda1",
                "fstype": "ext4",
                "mount": "/boot",
                "options": "rw,noatime",
                "size_available": 165210112,
                "size_total": 203097088
            }
        ],
        "ansible_os_family": "RedHat",
        "ansible_pkg_mgr": "yum",
        "ansible_processor": [
            "QEMU Virtual CPU version 1.7.0",
            "QEMU Virtual CPU version 1.7.0"
        ],
        "ansible_processor_cores": 1,
        "ansible_processor_count": 2,
        "ansible_processor_threads_per_core": 1,
        "ansible_processor_vcpus": 2,
        "ansible_product_name": "Bochs",
        "ansible_product_serial": "NA",
        "ansible_product_uuid": "D17F90A9-2961-401A-ABF2-67A4986DB05A",
        "ansible_product_version": "NA",
        "ansible_python_version": "2.6.6",
        "ansible_selinux": false,
        "ansible_ssh_host_key_dsa_public": "AAAAB3NzaC1kc3MAAACBAPEyQcsdKho53k1iGNPovQZcZfWtdKNXszZlhFl64YbSI6hYHmJVLPnQLrsl4wyjrOk49HFsSIM89wAipEHqvr+X3jKOR3PyEIZbeE5B6BKeP8CmDs9Xp7BIyJnwdcJk6w0gOd17nTbjQ4sJDo4FSacTFw5lxTF+8Xq0O9ErJtzBAAAAFQCib5Lo+HBK2iJpHWXLyUqZZzRKQQAAAIEAzHm7CURjGo9geYcvFTsIRvn2GwMqj4TdS2usv4KwhxUl/46a1Jh9/Gp9OZuNNcentUJQGccJf8gIYBVtFA0PcXcuAp6DS4kdk52bhTL+jRARCTb5aXJFUfSZNw5u/E4AhNW9domtiDiu2zEuY+LpMiUDLUSlmRC+BBQkA11dNAsAAACATlZw4Jr1IouATvxdtt+qqG3WrOz82wYcOAVDx8NFAzbFAcUeRswzt9+yWsn1CytTnOtbMwS2psTPIvtYvW8SKDvzlqBrBIUVgJgDVALBRffQ2YIl0XmSNjxZkgz8xvOCbvDFTzBKxbEqgn/0g7XLmfcEQeJvltikyVU4gTc//xU=",
        "ansible_ssh_host_key_rsa_public": "AAAAB3NzaC1yc2EAAAABIwAAAQEAqWaeArbN0cZDpm7CX8UnQwF1t8fjm0IDRQIBpbJN21kdvwEppVYCJZw/W+oo66fVyUCRPgI83VeLlqgvObxOQYzUAdfbaEp7yUMelp9wwx1c6PnzbsOWg/lDPW5kXLobKSmECP985cMUFpwb2pA8CjcwhQsS4EIQwZgxIYBqV5MTufLtGTnD3v0qzyRQ7l35srKC4myHL04fk+W4F0+qy2B/gMeY1ViKl/AbIvwO1deZ47Xn1Ier/k7Ou57c9WE6M7z5fl6dBzT6YJzgMEjSdy6U5zV+lYbay5CtSn0rRbiOnKqrhZl8EC0L/+W1DpCeDieikbCKQu0WX7liN3uVJQ==",
        "ansible_swapfree_mb": 511,
        "ansible_swaptotal_mb": 511,
        "ansible_system": "Linux",
        "ansible_system_vendor": "Bochs",
        "ansible_user_id": "root",
        "ansible_userspace_architecture": "i386",
        "ansible_userspace_bits": "32",
        "ansible_virtualization_role": "guest",
        "ansible_virtualization_type": "kvm"
    },
    "changed": false
}

When the fact you need is not in there, you can also return your own facts.

Note
explain -vvv

Managing Configuration Files

When managing configuration files you often want to use certain variables in your file. That way one can reuse the same file template for multiple systems.

Let’s configure a virtual host on Apache for the web application we want to deploy.

- name: Configure Apache
  hosts: web-servers
  user: root

  vars:
    webapp_dir: /var/www/html

  handlers:

  - name: restart Apache
    action: service name=httpd state=restarted

  tasks:

  - name: Install Apache
    action: yum pkg=httpd state=installed

  - name: Add webapp vhost
    action: template src=templates/webapp.conf dest=/etc/httpd/conf.d/
    notify:
    - restart Apache

  - name: Start and Enable Apache
    action: service name=httpd state=started enabled=yes

Two things are new here. The first one is the template module. This module allows templating a configuration file using the jinja2 templating language (also used in Flask).

The template is simple. It looks like this:

<VirtualHost *:80>
    ServerName {{ inventory_hostname }}
    DocumentRoot {{ webapp_dir }}
</VirtualHost>

We use our newly defined webapp_dir variable and a magic inventory_hostname variable. The webapp_dir variable is defined in the vars section in the playbook. We’ll look into other ways to define them later.

Of course, if we ever change the configuration again, we want to restart Apache so it picks up the new config. This can be done with handlers and notify. A handler is a normal named task that is run when the task that has it in notify returns changed.

Note
Add ansible_managed to template
[root@ws01 workshop]# ansible-playbook apache.yml -i hosts

PLAY [Configure Apache] *******************************************************

GATHERING FACTS ***************************************************************
ok: [vm-web]

TASK: [Install Apache] ********************************************************
ok: [vm-web]

TASK: [Add webapp vhost] ******************************************************
changed: [vm-web]

TASK: [Start and Enable Apache] ***********************************************
changed: [vm-web]

NOTIFIED: [restart Apache] ****************************************************
changed: [vm-web]

PLAY RECAP ********************************************************************
vm-web                       : ok=5    changed=3    unreachable=0    failed=0

Using with_items

Our web application uses MySQL. For managing MySQL databases with Ansible, the MySQL-Python module is needed. We could use two tasks to install MySQL and the python module, but there is some syntactic sugar that helps.

- name: Configure MySQL
  hosts: database-servers
  user: root

  tasks:

  - name: Install MySQL
    action: yum pkg={{ item }} state=installed
    with_items:
    - mysql-server
    - MySQL-python

  - name: Start MySQL
    action: service name=mysqld state=started enabled=yes

The item variable contains the current item.

[root@ws01 workshop]# ansible-playbook mysql.yml -i hosts

PLAY [Configure MySQL] ********************************************************

GATHERING FACTS ***************************************************************
ok: [vm-db]

TASK: [Install MySQL] *********************************************************
ok: [vm-db] => (item=mysql-server,MySQL-python)

TASK: [Start MySQL] ***********************************************************
ok: [vm-db]

PLAY RECAP ********************************************************************
vm-db                       : ok=3    changed=0    unreachable=0    failed=0

We want to be able to talk to MySQL in the web application we’ll soon write in PHP. Use the with_items syntax to change the Apache installation playbook to install PHP and the MySQL extension.

  - name: Install Apache and PHP
    action: yum pkg={{ item }} state=installed
    with_items:
    - httpd
    - php-mysql

Using results of previous tasks

With the web server and the database up and running, we can start deploying the application.

Let’s first consider the case where the system is fresh. In that case we want to create a database and populate it with initial data. In subsequent reruns, we don’t want to populate the database.

- name: Prepare deployment
  hosts: database-servers
  user: root

  tasks:

  - name: Create a temporary directory for database migratons
    action: file dest=/tmp/db state=directory

  - name: Create the database
    action: mysql_db name=ws state=present
    register: db_create

  - name: Copy initial schema
    action: copy src=app/db/initial.sql dest=/tmp/db/
    when: db_create|changed

  - name: Initialize database
    action: mysql_db name=ws state=import target=/tmp/db/initial.sql
    when: db_create|changed

  - name: Create a ws user
    action: mysql_user name=ws host={{ hostvars[item]["ansible_ssh_host"] }} password=''
                       priv=ws.*:SELECT state=present
    with_items:
    - groups["web-servers"]

This introduces a few new modules.

We are mostly interested in the first mysql_db task. That task uses the register keyword. This stores the results of that task in the variable db_create.

Conditionals

The last two tasks use the when keyword. A task will only run when the condition in when is true. In this case we use the built-in jinja2 filter changed to only run that task when the task that registered db_create did change the system.

Globbing files

Now we can deploy the application. We will do it in a quick and dirty way, by just copying everything in a particular folder…​

- name: Deploy the demo application
  hosts: web-servers
  user: root

  vars:
    webapp_dir: /var/www/html

  tasks:

  - name: Copy all php scripts to the web server
    action: copy src={{ item }}
                 dest={{ webapp_dir }}/
                 owner=apache group=apache
    with_fileglob: app/web/*

This uses the with_fileglob lookup plugin, which provides exactly the functionality you’d expect it to have.

Including variables from files

That last play feels wrong. We’ve defined the webapp_dir variable here. We also defined the variable in the Apache playbook. When the webapp location changes we have to remember to change it in two places.

There is a better solution: including variables from files.

- name: Deploy the demo application
  hosts: web-servers
  user: root

  vars_files:
  - config.yml

  tasks:

  - name: Copy all php scripts to the web server
    action: template src={{ item }} dest={{ webapp_dir }}/ owner=apache group=apache
    with_fileglob: app/web/*

config.yml contains:

webapp_dir: /var/www/html

Working with database migrations

Application developers have the nasty habit of not being able to come up with a good data model. They require database schema changes with every deployment. This complicates the whole deployment process and makes rollbacks incredibly hard. We will do as every professional PaaS does, or even more, and allow only forward migrations.

This makes our complete deployment playbook:

- name: Prepare deployment
  hosts: database-servers
  user: root

  vars:
    db_dir: /tmp/db

  tasks:

  - name: Create a temporary directory for database migrations
    action: file dest={{ db_dir }} state=directory

  - name: Create the database
    action: mysql_db name=ws state=present
    register: db_create

  - name: Create a ws user
    action: mysql_user name=ws host={{ hostvars[item]["ansible_ssh_host"] }} password=''
                       priv=ws.*:SELECT state=present
    with_items:
    - groups["web-servers"]

  - name: Copy initial schema
    action: copy src=app/db/initial.sql dest={{ db_dir }}/
    when: db_create|changed

  - name: Initialize database
    action: mysql_db name=ws state=import target={{ db_dir }}/initial.sql
    when: db_create|changed

  - name: Upload database migrations
    action: copy src=app/db/migrations.sql dest={{ db_dir }}/

  - name: Run database migrations
    action: mysql_db name=ws state=import target={{ db_dir }}/migrations.sql

- name: Deploy the demo application
  hosts: web-servers
  user: root

  vars_files:
  - config.yml

  tasks:

  - name: Copy all php scripts to the web server
    action: template src={{ item }} dest={{ webapp_dir }}/ owner=apache group=apache
    with_fileglob: app/web/*

This playbook has three plays. They are run sequentially.

[root@ws01 workshop]# ansible-playbook deploy.yml -i hosts

PLAY [Prepare deployment] *****************************************************

GATHERING FACTS ***************************************************************
ok: [ws01]

TASK: [Create a temporary directory for database migrations] *******************
ok: [ws01]

TASK: [Create the database] ***************************************************
ok: [ws01]

TASK: [Copy initial schema] ***************************************************
skipping: [ws01]

TASK: [Initialize database] ***************************************************
skipping: [ws01]

PLAY [Update the database] ****************************************************

TASK: [Create a temporary directory for database migrations] *******************
ok: [ws01]

TASK: [Upload database migrations] ********************************************
ok: [ws01]

TASK: [Run database migrations] ***********************************************
changed: [ws01]

PLAY [Deploy the demo application] ********************************************

TASK: [Copy all php scripts to the web server] ********************************
ok: [ws01] => (item=/root/workshop/app/web/index.php)

PLAY RECAP ********************************************************************
ws01                       : ok=7    changed=1    unreachable=0    failed=0

Ignoring errors

We can now deploy applications from our management system, which could be you CI system. The typical PaaS allows application deployment through git. That is just a few additional lines of code.

We will add a new directory under /root with a bare git repository (i.e. one without working tree). A git post-receive hook runs the application whenever you push to the repository.

- name: Set up automatic application deployment repository
  hosts: vm-master
  user: root

  tasks:

  - name: Clone the workshop repository
    action: git repo=/root/workshop
                dest=/root/deploy
                bare=yes
    ignore_errors: yes

  - name: Add post-receive hook to deploy
    action: copy src=templates/post-receive
                 dest=/root/deploy/hooks/
                 mode=0755

Run the new playbook:

[root@ws01 workshop]# ansible-playbook auto-deploy.yml -i hosts

PLAY [Set up automatic application deployment repository] *********************

GATHERING FACTS ***************************************************************
ok: [vm-master]

TASK: [Clone the workshop repository] *****************************************
changed: [vm-master]

TASK: [Add post-receive hook to deploy] ***************************************
changed: [vm-master]

PLAY RECAP ********************************************************************
vm-master                   : ok=4    changed=2    unreachable=0    failed=0

Try to run it again.

TASK: [Clone the workshop repository] *****************************************
failed: [ws01] => {"cmd": "/usr/bin/git ls-remote origin -h refs/heads/master", "failed": true, "rc": 128}
stderr: fatal: 'origin' does not appear to be a git repository
fatal: The remote end hung up unexpectedly

msg: fatal: 'origin' does not appear to be a git repository
fatal: The remote end hung up unexpectedly
...ignoring

This is something in the Ansible git module that makes incorrect assumptions about bare repositories. It needs to be fixed, but it serves as a good example of the ignore_errors directive.

PaaSy deployment

Add the new repository as a remote of the current repository.

$ git remote add deploy /root/deploy

Now change the title of the application in app/web/index.php, commit and:

root@ws01 workshop]# git add app/web/index.php
[root@ws01 workshop]# git commit -v
[master 8feb5ee] Update title.
 Committer: root <root@ws01.(none)>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly:

    git config --global user.name "Your Name"
    git config --global user.email [email protected]

If the identity used for this commit is wrong, you can fix it with:

    git commit --amend --author='Your Name <[email protected]>'

 1 files changed, 1 insertions(+), 1 deletions(-)
[root@ws01 workshop]# git push deploy master
Counting objects: 9, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 403 bytes, done.
Total 5 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (5/5), done.
remote: Deploying Ansible Workshop Web Application
remote: Initialized empty Git repository in /tmp/ws-deploy/.git/
remote:
remote: PLAY [Prepare deployment] *****************************************************
remote:
remote: GATHERING FACTS ***************************************************************
remote: ok: [vm-db]
remote:
remote: TASK: [Create a temporary directory for database migrations] ******************
remote: ok: [vm-db]
remote:
remote: TASK: [Create the database] ***************************************************
remote: ok: [vm-db]
remote:
remote: TASK: [Copy initial schema] ***************************************************
remote: skipping: [vm-db]
remote:
remote: TASK: [Initialize database] ***************************************************
remote: skipping: [vm-db]
remote:
remote: PLAY [Update the database] ****************************************************
remote:
remote: TASK: [Create a temporary directory for database migrations] ******************
remote: ok: [vm-db]
remote:
remote: TASK: [Upload database migrations] ********************************************
remote: ok: [vm-db]
remote:
remote: TASK: [Run database migrations] ***********************************************
remote: changed: [vm-db]
remote:
remote: PLAY [Deploy the demo application] ********************************************
remote:
remote: TASK: [Copy all php scripts to the web server] ********************************
remote: changed: [vm-web] => (item=/tmp/ws-deploy/app/web/index.php)
remote:
remote: PLAY RECAP ********************************************************************
remote: vm-db                       : ok=7    changed=2    unreachable=0    failed=0
remote:
To /root/deploy
   7874de6..8feb5ee  master -> master