├── .gitignore ├── .travis.yml ├── LICENSE-tutorials.md ├── LICENSE.md ├── README.md ├── images ├── Makefile ├── ansible-tutorial │ └── Dockerfile ├── common │ ├── .profile │ ├── .vimrc │ ├── ansible.cfg │ ├── id_rsa │ ├── id_rsa.pub │ └── start.sh └── ubuntu-1604-ansible-docker-host │ └── Dockerfile ├── tutorial.sh └── tutorials ├── 0-step-00.nutsh ├── 1-step-01.nutsh ├── 10-step-10.nutsh ├── 11-step-11.nutsh ├── 12-step-12.nutsh ├── 13-step-13.nutsh ├── 14-freeplay.nutsh ├── 2-step-02.nutsh ├── 3-step-03.nutsh ├── 4-step-04.nutsh ├── 5-step-05.nutsh ├── 6-step-06.nutsh ├── 7-step-07.nutsh ├── 8-step-08.nutsh ├── 9-step-09.nutsh ├── common.nutsh ├── files ├── step-1-2 │ └── hosts ├── step-10 │ ├── apache.yml │ ├── files │ │ └── awesome-app │ ├── haproxy.yml │ ├── hosts │ └── templates │ │ └── haproxy.cfg.j2 ├── step-11 │ ├── apache.yml │ ├── files │ │ └── awesome-app │ ├── group_vars │ │ └── haproxy │ ├── haproxy.yml │ ├── host_vars │ │ ├── host0.example.org │ │ ├── host1.example.org │ │ └── host2.example.org │ ├── hosts │ └── templates │ │ └── haproxy.cfg.j2 ├── step-12 │ ├── apache_handlers.yml │ ├── apache_tasks.yml │ ├── files │ │ └── awesome-app │ ├── group_vars │ │ └── haproxy │ ├── haproxy_handlers.yml │ ├── haproxy_tasks.yml │ ├── host_vars │ │ ├── host0.example.org │ │ ├── host1.example.org │ │ └── host2.example.org │ ├── hosts │ ├── site.yml │ └── templates │ │ └── haproxy.cfg.j2 ├── step-13 │ ├── hosts │ └── jenkins.yaml ├── step-4 │ ├── apache.yml │ └── hosts ├── step-5 │ ├── apache.yml │ ├── files │ │ └── awesome-app │ └── hosts ├── step-6 │ ├── apache.yml │ ├── files │ │ └── awesome-app │ └── hosts ├── step-7 │ ├── apache.yml │ ├── files │ │ └── awesome-app │ └── hosts ├── step-8 │ ├── apache.yml │ ├── files │ │ └── awesome-app │ └── hosts └── step-9 │ ├── apache.yml │ ├── files │ └── awesome-app │ └── hosts └── info.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | workspace 3 | tutorials/progress.yaml 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: bash 3 | services: 4 | - docker 5 | 6 | env: 7 | - DOCKER_IMAGETAG="1.1" 8 | before_script: 9 | - ./tutorial.sh -r 10 | - if [ "${DOCKER_IMAGETAG}" == "latest" ]; then pushd images && make build_all TAG=latest&& popd; fi 11 | 12 | script: 13 | - LESSON_NAME="0-step-00" ./tutorial.sh -t 14 | - LESSON_NAME="1-step-01" ./tutorial.sh -t 15 | - LESSON_NAME="2-step-02" ./tutorial.sh -t 16 | - LESSON_NAME="3-step-03" ./tutorial.sh -t 17 | - LESSON_NAME="4-step-04" ./tutorial.sh -t 18 | - LESSON_NAME="5-step-05" ./tutorial.sh -t 19 | - LESSON_NAME="6-step-06" ./tutorial.sh -t 20 | - LESSON_NAME="7-step-07" ./tutorial.sh -t 21 | - LESSON_NAME="8-step-08" ./tutorial.sh -t 22 | - LESSON_NAME="9-step-09" ./tutorial.sh -t 23 | - LESSON_NAME="10-step-10" ./tutorial.sh -t 24 | - LESSON_NAME="11-step-11" ./tutorial.sh -t 25 | - LESSON_NAME="12-step-12" ./tutorial.sh -t 26 | - LESSON_NAME="13-step-13" ./tutorial.sh -t 27 | - LESSON_NAME="14-freeplay" ./tutorial.sh -t 28 | 29 | after_script: 30 | - ./tutorial.sh -r 31 | -------------------------------------------------------------------------------- /LICENSE-tutorials.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Michel Blanc Copyright (c) 2014, Aladin Jaermann Copyright (c) 2014, Alexis Gallagher Copyright (c) 2014, Alice Ferrazzi Copyright (c) 2014, Alice Pote Copyright (c) 2014, Amit Jakubowicz Copyright (c) 2014, Arbab Nazar Copyright (c) 2014, Atilla Mas Copyright (c) 2014, Ben Visser Copyright (c) 2014, Benny Wong Copyright (c) 2014, Bernardo Vale Copyright (c) 2014, Chris Schmitz Copyright (c) 2014, dalton Copyright (c) 2014, Daniel Howard Copyright (c) 2014, David Golden Copyright (c) 2014, Eric Corson Copyright (c) 2014, Eugene Kalinin Copyright (c) 2014, Hartmut Goebel Copyright (c) 2014, Jelly Robot Copyright (c) 2014, Justin Garrison Copyright (c) 2014, Levon Copyright (c) 2014, Karlo Copyright (c) 2014, Marchenko Alexandr Copyright (c) 2014, mxxcon Copyright (c) 2014, Patrick Pelletier Copyright (c) 2014, Pierre-Gilles Levallois Copyright (c) 2014, Ruud Kamphuis Copyright (c) 2014, [torenware] (https://github.com/torenware) Copyright (c) 2014, Victor Boivie 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | 11 | Copyright (c) 2017, Hasan Turken turkenh@gmail.com 12 | 13 | All rights reserved. 14 | 15 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 16 | 17 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 18 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Hasan Turken turkenh@gmail.com 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ansible-interactive-tutorial 2 | 3 | [![Build Status](https://travis-ci.org/turkenh/ansible-interactive-tutorial.svg?branch=master)](https://travis-ci.org/turkenh/ansible-interactive-tutorial) 4 | 5 | Interactive tutorials for Ansible 6 | 7 | ## Prerequisite 8 | 9 | Only prerequisite is **docker** 10 | 11 | Requires docker version 1.9+ and tested with 1.12+ 12 | 13 | If you don't have docker installed, you can also run on http://play-with-docker.com (just click "+ ADD NEW INSTANCE" button and clone this repo there) 14 | 15 | ## How to Run 16 | 17 | ```bash 18 | ./tutorial.sh 19 | ``` 20 | 21 | [![demo](https://asciinema.org/a/CPUhOGGlcLiXVlZKIuiuk5Q7f.png)](https://asciinema.org/a/CPUhOGGlcLiXVlZKIuiuk5Q7f?autoplay=1) 22 | 23 | ## Clean up 24 | 25 | ```bash 26 | ./tutorial.sh --remove 27 | ``` 28 | 29 | ## More Details 30 | 31 | ### Tutorials 32 | 33 | Almost all of the tutorials are adapted from the great [leucos/ansible-tuto](https://github.com/leucos/ansible-tuto) repository: 34 | 35 | ``` 36 | 1. Getting Started 37 | 2. Basic inventory 38 | 3. First modules and facts 39 | 4. Groups and variables 40 | 5. Playbooks 41 | 6. Playbooks, pushing files on nodes 42 | 7. Playbooks and failures 43 | 8. Playbook conditionals 44 | 9. Git module 45 | 10. Extending to several hosts 46 | 11. Templates 47 | 12. Variables again 48 | 13. Migrating to roles! 49 | 14. Using roles from Ansible Galaxy - Install a Jenkins server 50 | 15. Free play 51 | ``` 52 | 53 | You can run each lesson individually but it is **highly encouraged to follow the order** as most of them are built on top of the previous one! 54 | 55 | 56 | ### Containers 57 | 58 | `tutorial.sh` starts 4 docker containers behind the scenes. 1 for running the tutorial itself and 3 as ansible nodes which behave exactly same as (virtual or physical) machines throughout the tutorial. 59 | 60 | **ansible.tutorial** is an alpine based tutorial container in which ansible and [nutsh](https://github.com/turkenh/nutsh) (a framework for creating interactive command line tutorials) are available. 61 | 62 | **host0.example.org**, **host1.example.org** and **host2.example.org** are the Ubuntu 16.04 based containers that act as ansible nodes. These nodes were already provisioned with the ssh key of **ansible.tutorial** container. So that you don't have to deal with setting up keys. 63 | 64 | ### Port Mapping 65 | 66 | There are some checkpoints in the tutorials where you can check and verify your deployments. For this purpose some ports of the containers are exposed as host ports as follows: 67 | 68 | Container|Container Port|Host Port 69 | :---|:---:|:---: 70 | host0.example.org|80|`$HOSTPORT_BASE`   71 | host1.example.org|80|`$HOSTPORT_BASE+1` 72 | host2.example.org|80|`$HOSTPORT_BASE+2` 73 | host0.example.org|8080|`$HOSTPORT_BASE+3` 74 | host1.example.org|30000|`$HOSTPORT_BASE+4` 75 | host2.example.org|443|`$HOSTPORT_BASE+5` 76 | 77 | `HOSTPORT_BASE` is set to `42726` by default and can be changed while starting the tutorial (in case any of the consecutive 6 ports is not available) as follows: 78 | 79 | ```bash 80 | ./tutorial.sh --remove # Make sure you shut down the previous ones 81 | HOSTPORT_BASE= ./tutorial.sh 82 | ``` 83 | 84 | ### Workspace Directory 85 | `ansible-interactive-tutorial/workspace` directory on your local machine is mounted as `/root/workspace` inside the **ansible.tutorial** container. So, you can use your favorite editor on your local machine to edit files. Editing files is not necessary to follow the lessons though. 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /images/Makefile: -------------------------------------------------------------------------------- 1 | USER=turkenh 2 | TAG=1.1 3 | 4 | .DEFAULT_GOAL := all 5 | 6 | build: 7 | docker build -t $(USER)/${IMAGE}:${TAG} -f ${IMAGE}/Dockerfile . 8 | 9 | push: 10 | docker push $(USER)/${IMAGE}:${TAG} 11 | 12 | build_and_push: build push 13 | 14 | build_all: 15 | IMAGE=ubuntu-1604-ansible-docker-host $(MAKE) build 16 | IMAGE=ansible-tutorial $(MAKE) build 17 | 18 | push_all: 19 | IMAGE=ubuntu-1604-ansible-docker-host $(MAKE) push 20 | IMAGE=ansible-tutorial $(MAKE) push 21 | 22 | all: build_all push_all 23 | -------------------------------------------------------------------------------- /images/ansible-tutorial/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM turkenh/nutsh:1.1 2 | 3 | RUN apk add --no-cache ansible openssh fping curl 4 | 5 | WORKDIR /root 6 | 7 | RUN mkdir -p /root/.ssh && chmod 700 /root/.ssh && mkdir /etc/ansible 8 | 9 | COPY common/ansible.cfg /etc/ansible/ansible.cfg 10 | 11 | COPY ["common/id_rsa", "common/id_rsa.pub", "/root/.ssh/"] 12 | COPY ["common/.vimrc", "common/.profile", "/root/"] 13 | 14 | RUN chmod 644 /root/.ssh/id_rsa.pub && chmod 600 /root/.ssh/id_rsa 15 | 16 | ENTRYPOINT ["nutsh", "run", "/tutorials"] 17 | -------------------------------------------------------------------------------- /images/common/.profile: -------------------------------------------------------------------------------- 1 | export PS1='\e[33;1m\u@\h: \e[31m\W\e[0m\$ ' -------------------------------------------------------------------------------- /images/common/.vimrc: -------------------------------------------------------------------------------- 1 | syntax on 2 | set autoindent 3 | set expandtab 4 | set number 5 | set shiftwidth=2 6 | set softtabstop=2 -------------------------------------------------------------------------------- /images/common/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | host_key_checking = False -------------------------------------------------------------------------------- /images/common/id_rsa: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAvByrLVhAQNPmEEKGio7tvld+2WhFQqGAUvHSzMmZ+dvJuH1u 3 | BeTFSxwZ+gsTcKVkH8+53ENPNPMGp6NNVqm8c4elBytkhZD1nptGgjTUzXBIrjes 4 | HfvfpkGvNhqH5Sdk+Q5Y2LYcW3cLJjUy+w+sXlDXP9WNj9nqCKvbpEHHVHcvkKxw 5 | tQ+t5AH5if6z6BQmMVbx8LM8RJs/Il9z3omgK0dhLXtKaGAGmbvTTjbpYYKQxlhp 6 | NosmhenOvvaBnAoMamMqYnA8F0d31QfWmzfjqe1W1l2PRiWp0nPCpJn6EjdoE5b6 7 | XARcWTvhDTWfA5+5wBRXP2HMQ+NWJqgQVbWckQIDAQABAoIBAQCClFALEQjBN5ar 8 | UNK+GgtUpr3qQuRZJWzDpxQSjhtxgy1bAM7ADpcZ1W7+0HN1LYn8PyEgkiTId671 9 | 7XkBIKAtfkqDWI7V3NNzfwwJ1Iyn8zqwnPrAcZhFe7EZmH8fBiKkdF4eSQQUReWa 10 | ZzF1b+LSMcHJkP9MjRNVY577PaIVsdlGr6TWUcRP7jaTtSeAcZLgmSRTgLgXOPIF 11 | M/X8BhrROsupvw2+RnqME8d7I2lYjfCummfXLesmKTKEWhQPvBMRtyZrSEkJ7rot 12 | mWidgjMRWHFaN5jcZlBSosfNE25N7Awy5Nz3UzWBwpaDdNleILWjhWFdS1Z3Hq5o 13 | vp04toCxAoGBAOiiAr4W9dy63m8IQdh7UUjOh/rbacFAKbjth6Af7Tm2FRWE8UEY 14 | F/ZAniKOIHpcw8VXCzzz8PIvHzRQ/Uiet42aiorybOlXxV8moJ/qVMVachlCTbFP 15 | CUM59L2a+aGqZzfW9Pgwov8ns9kNyN+IVr/FwqNI3YEq2Heu7lJPyBMtAoGBAM8B 16 | 1diaBfjKF4Bjs3bOcxuttPe+WI7mErLBpeDyN8TqLX2QM/cKRCGkoyd2iku5hMyL 17 | wJXHY8JRx/53sazyC2QCssSB4wKDgCj286341o4e4yejwbLZcXJhIRIJjrlsOogE 18 | OCwRnirUce5466Spi9FxiakwSaXKGkvB7Ruf8911AoGABeR0pVP3jku9bpqK8GzP 19 | NkTk9Nmx6A8z/1ck5E1quNd/2Tew1LFDih1A5Cctc2v+GXWG91hGdhvzcmPKxpge 20 | lhuf/rz9PTvX5/0WfHZ3BzivQyyGw0L5PRauuE7Am/OxAZ0UP2Ub+9KJn6nVuLO/ 21 | XsQnlfM2iRsIuJ9ZsgQLUVECgYBYU/QnZDFAIxs8EU62VwkOumQJghLPhqRb2f0p 22 | qrDZOQMIHMvPkXXY1SM453/DJ/nePZODVXzfoX2wuun5XZ42omAdMT7oB6xbzmPT 23 | y+Xg5pUOiPVRly6tG3y88Y8Q13lyKDq/sxTGObgThU0nCaE7UTLoGk6Si1YFFU5T 24 | B0FLMQKBgQCaeaduVTPgjNYrJGlnvNqNAB5FB01HDJNxaoBF6jHXD9jA5y7gzCm8 25 | Uy+jWm+sWOmxWIQJc7nCzkvd6gbvoh/W/nmUyXrDyMEnHYM+JB75N+x8e5pT9i4q 26 | UvXoh9qgSj3DL0OFUdOO79XS+TU475XwPKKEUpN/eRPObVSbEKz/ow== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /images/common/id_rsa.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8HKstWEBA0+YQQoaKju2+V37ZaEVCoYBS8dLMyZn528m4fW4F5MVLHBn6CxNwpWQfz7ncQ0808wano01Wqbxzh6UHK2SFkPWem0aCNNTNcEiuN6wd+9+mQa82GoflJ2T5DljYthxbdwsmNTL7D6xeUNc/1Y2P2eoIq9ukQcdUdy+QrHC1D63kAfmJ/rPoFCYxVvHwszxEmz8iX3PeiaArR2Ete0poYAaZu9NONulhgpDGWGk2iyaF6c6+9oGcCgxqYypicDwXR3fVB9abN+Op7VbWXY9GJanSc8KkmfoSN2gTlvpcBFxZO+ENNZ8Dn7nAFFc/YcxD41YmqBBVtZyR root@e1ede117fb1e -------------------------------------------------------------------------------- /images/common/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | service ssh start > /dev/null && \ 3 | service rsyslog start > /dev/null && \ 4 | while ! "${ALLOW_EXIT}" ; do script -q -c "/bin/bash -l" /dev/null ; done && \ 5 | script -q -c "/bin/bash -l" /dev/null && \ 6 | script -q -c "tail -f /dev/null" 7 | -------------------------------------------------------------------------------- /images/ubuntu-1604-ansible-docker-host/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | MAINTAINER Hasan Turken 3 | 4 | RUN apt-get update && apt-get install -y rsyslog iputils-ping netbase net-tools python openssh-server vim sudo curl python-yaml python-jinja2 python-paramiko python-crypto 5 | RUN mkdir ~/.ssh && ssh-keygen -N "" -t rsa -f ~/.ssh/id_rsa 6 | 7 | COPY common/id_rsa.pub /root/.ssh/authorized_keys 8 | COPY ["common/.vimrc", "common/.profile", "./root/"] 9 | COPY common/start.sh /start.sh 10 | 11 | WORKDIR /root 12 | 13 | ENV ALLOW_EXIT true 14 | 15 | CMD /start.sh 16 | -------------------------------------------------------------------------------- /tutorial.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 3 | 4 | NOF_HOSTS=3 5 | NETWORK_NAME="ansible.tutorial" 6 | WORKSPACE="${BASEDIR}/workspace" 7 | TUTORIALS_FOLDER="${BASEDIR}/tutorials" 8 | 9 | HOSTPORT_BASE=${HOSTPORT_BASE:-42726} 10 | # Extra ports per host to expose. Should contain $NOF_HOSTS variables 11 | EXTRA_PORTS=( "8080" "30000" "443" ) 12 | # Port Mapping 13 | # +-----------+----------------+-------------------+ 14 | # | Container | Container Port | Host Port | 15 | # +-----------+----------------+-------------------+ 16 | # | host0 | 80 | $HOSTPORT_BASE | 17 | # +-----------+----------------+-------------------+ 18 | # | host1 | 80 | $HOSTPORT_BASE+1 | 19 | # +-----------+----------------+-------------------+ 20 | # | host2 | 80 | $HOSTPORT_BASE+2 | 21 | # +-----------+----------------+-------------------+ 22 | # | host0 | EXTRA_PORTS[0] | $HOSTPORT_BASE+3 | 23 | # +-----------+----------------+-------------------+ 24 | # | host1 | EXTRA_PORTS[1] | $HOSTPORT_BASE+4 | 25 | # +-----------+----------------+-------------------+ 26 | # | host2 | EXTRA_PORTS[2] | $HOSTPORT_BASE+5 | 27 | # +-----------+----------------+-------------------+ 28 | 29 | DOCKER_IMAGETAG=${DOCKER_IMAGETAG:-1.1} 30 | DOCKER_HOST_IMAGE="turkenh/ubuntu-1604-ansible-docker-host:${DOCKER_IMAGETAG}" 31 | TUTORIAL_IMAGE="turkenh/ansible-tutorial:${DOCKER_IMAGETAG}" 32 | 33 | function help() { 34 | echo -ne "-h, --help prints this help message 35 | -r, --remove remove created containers and network 36 | -t, --test run lesson tests 37 | " 38 | } 39 | function doesNetworkExist() { 40 | return $(docker network inspect $1 >/dev/null 2>&1) 41 | } 42 | 43 | function removeNetworkIfExists() { 44 | doesNetworkExist $1 && echo "removing network $1" && docker network rm $1 >/dev/null 45 | } 46 | 47 | function doesContainerExist() { 48 | return $(docker inspect $1 >/dev/null 2>&1) 49 | } 50 | 51 | function isContainerRunning() { 52 | [[ "$(docker inspect -f "{{.State.Running}}" $1 2>/dev/null)" == "true" ]] 53 | } 54 | 55 | function killContainerIfExists() { 56 | doesContainerExist $1 && echo "killing/removing container $1" && { docker kill $1 >/dev/null 2>&1; docker rm $1 >/dev/null 2>&1; }; 57 | } 58 | 59 | function runHostContainer() { 60 | local name=$1 61 | local image=$2 62 | local port1=$(($HOSTPORT_BASE + $3)) 63 | local port2=$(($HOSTPORT_BASE + $3 + $NOF_HOSTS)) 64 | 65 | echo "starting container ${name}: mapping hostport $port1 -> container port 80 && hostport $port2 -> container port ${EXTRA_PORTS[$3]}" 66 | if doesContainerExist ${name}; then 67 | docker start "${name}" > /dev/null 68 | else 69 | docker run -d -p $port1:80 -p $port2:${EXTRA_PORTS[$3]} --net ${NETWORK_NAME} --name="${name}" "${image}" >/dev/null 70 | fi 71 | if [ $? -ne 0 ]; then 72 | echo "Could not start host container. Exiting!" 73 | exit 1 74 | fi 75 | } 76 | 77 | function runTutorialContainer() { 78 | local entrypoint="" 79 | local args="" 80 | if [ -n "${TEST}" ]; then 81 | entrypoint="--entrypoint nutsh" 82 | args="test /tutorials ${LESSON_NAME}" 83 | fi 84 | killContainerIfExists ansible.tutorial > /dev/null 85 | echo "starting container ansible.tutorial" 86 | docker run -it -v "${WORKSPACE}":/root/workspace:Z -v "${TUTORIALS_FOLDER}":/tutorials:Z --net ${NETWORK_NAME} \ 87 | --env HOSTPORT_BASE=$HOSTPORT_BASE \ 88 | ${entrypoint} --name="ansible.tutorial" "${TUTORIAL_IMAGE}" ${args} 89 | return $? 90 | } 91 | 92 | function remove () { 93 | for ((i = 0; i < $NOF_HOSTS; i++)); do 94 | killContainerIfExists host$i.example.org 95 | done 96 | removeNetworkIfExists ${NETWORK_NAME} 97 | } 98 | 99 | function setupFiles() { 100 | # step-01/02 101 | local step_01_hosts_file="${BASEDIR}/tutorials/files/step-1-2/hosts" 102 | rm -f "${step_01_hosts_file}" 103 | for ((i = 0; i < $NOF_HOSTS; i++)); do 104 | #ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' host$i.example.org) 105 | ip=$(docker network inspect --format="{{range \$id, \$container := .Containers}}{{if eq \$container.Name \"host$i.example.org\"}}{{\$container.IPv4Address}} {{end}}{{end}}" ${NETWORK_NAME} | cut -d/ -f1) 106 | echo "host$i.example.org ansible_host=$ip ansible_user=root" >> "${step_01_hosts_file}" 107 | done 108 | } 109 | function init () { 110 | mkdir -p "${WORKSPACE}" 111 | doesNetworkExist "${NETWORK_NAME}" || { echo "creating network ${NETWORK_NAME}" && docker network create "${NETWORK_NAME}" >/dev/null; } 112 | for ((i = 0; i < $NOF_HOSTS; i++)); do 113 | isContainerRunning host$i.example.org || runHostContainer host$i.example.org ${DOCKER_HOST_IMAGE} $i 114 | done 115 | setupFiles 116 | runTutorialContainer 117 | exit $? 118 | } 119 | 120 | ### 121 | MODE="init" 122 | TEST="" 123 | for i in "$@"; do 124 | case $i in 125 | -r|--remove) 126 | MODE="remove" 127 | shift # past argument=value 128 | ;; 129 | -t|--test) 130 | TEST="yes" 131 | shift # past argument=value 132 | ;; 133 | -h|--help) 134 | help 135 | exit 0 136 | shift # past argument=value 137 | ;; 138 | *) 139 | echo "Unknown argument ${i#*=}" 140 | exit 1 141 | esac 142 | done 143 | 144 | if [ "${MODE}" == "remove" ]; then 145 | remove 146 | elif [ "${MODE}" == "init" ]; then 147 | init 148 | fi 149 | exit 0 150 | -------------------------------------------------------------------------------- /tutorials/0-step-00.nutsh: -------------------------------------------------------------------------------- 1 | lesson_name("Getting Started") 2 | make_and_go_ws 3 | 4 | "Hello, welcome to the Ansible Interactive Tutorial!" 5 | 6 | "This lesson is to make you familiar with the environment." 7 | 8 | "Now, you are inside a docker container on which you have ansible installed." 9 | 10 | "Verify that you have `ansible` installed by running the following command:" 11 | 12 | "*ansible --version*" 13 | 14 | prompt { 15 | if success && command == "ansible --version" { 16 | expect ("ansible --version") 17 | "" 18 | break 19 | } 20 | } 21 | 22 | "There are 3 more docker containers which behave exactly same as (virtual or physical) machines acting as Ansible nodes throughout the tutorial." 23 | 24 | "Run the following command to verify that they are up and running:" 25 | 26 | "*fping host{0,1,2}.example.org*" 27 | 28 | prompt { 29 | if success && command == "fping host{0,1,2}.example.org" { 30 | expect ("fping host{0,1,2}.example.org") 31 | "" 32 | "Cool!" 33 | break 34 | } 35 | } 36 | 37 | "These hosts were already provisioned with the ssh key of this node. So you don't have to deal with setting up keys and can directly ssh into any of them as root user." 38 | 39 | "Try it and don't forget to get back to continue (run *exit* once you got there):" 40 | 41 | "*ssh host0.example.org*" 42 | 43 | prompt { 44 | if success && command =~ "ssh host0.example.org.*" { 45 | expect (`ssh host0.example.org -o "StrictHostKeyChecking no" "whoami"`) 46 | "" 47 | "Welcome back :)" 48 | break 49 | } 50 | } 51 | 52 | "`ansible-interactive-tutorial/workspace` folder is mounted as `/root/workspace` inside this container. So, you can use your favorite editor on your local machine to edit files. Editing files is not necessary to follow the lessons though." 53 | 54 | "You can run each lesson invididually but it is `highly encouraged to follow the order` as most of them are built on top of the previous one!" 55 | 56 | "Please keep in mind, you can quit from any lesson by running *exit* from terminal" 57 | 58 | "Now you are ready to start!" 59 | 60 | "Please press the \"`Enter`\" key to continue!" 61 | prompt { 62 | if command == "" { 63 | expect (" ") 64 | break 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tutorials/1-step-01.nutsh: -------------------------------------------------------------------------------- 1 | lesson_name("Basic inventory") 2 | make_and_go_ws 3 | clear_ws 4 | 5 | "`INVENTORY`" 6 | 7 | "Before continuing, you need an inventory file. The default place for such a file is `/etc/ansible/hosts`." 8 | "However, you can configure ansible to look somewhere else, use an environment variable (`ANSIBLE_HOSTS`), or use the `-i` flag in ansible commands and provide the inventory path." 9 | 10 | "Please press the \"`Enter`\" key to continue!" 11 | prompt { 12 | if command == "" { 13 | expect(" ") 14 | break 15 | } 16 | } 17 | 18 | run(`cp /tutorials/files/step-1-2/hosts hosts`) 19 | 20 | "I've created an inventory file for you in the workspace named `hosts`" 21 | 22 | "Please run *cat hosts* to see the content of your first inventory file" 23 | 24 | prompt { 25 | if success && command =~ "^cat hosts" { 26 | expect ("cat hosts") 27 | "`ansible_host` is a special `variable` that sets the IP ansible will use when trying to connect to this host." 28 | 29 | "`ansible_user` is another special `variable` that tells ansible to connect as this user when using ssh. By default ansible would use your current username, or use another default provided in ~/.ansible.cfg (`remote_user`)." 30 | break 31 | } 32 | } 33 | 34 | "Please press the \"`Enter`\" key to continue!" 35 | prompt { 36 | if command == "" { 37 | expect (" ") 38 | break 39 | } 40 | } 41 | 42 | "`TESTING`" 43 | 44 | "Now that ansible is installed, let's check everything works properly." 45 | 46 | "What ansible will try to do here is just executing the ping module (more on modules later) on each host." 47 | 48 | "Please run *ansible -m ping all -i hosts*" 49 | 50 | prompt { 51 | if success && command =~ `^ansible -m ping all -i hosts` { 52 | expect ("ansible -m ping all -i hosts") 53 | "Good! All 3 hosts are alive and kicking, and ansible can talk to them." 54 | break 55 | } 56 | } 57 | 58 | "This is the end of this lesson!" 59 | 60 | "Please press the \"`Enter`\" key to continue!" 61 | prompt { 62 | if command == "" { 63 | expect (" ") 64 | break 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tutorials/10-step-10.nutsh: -------------------------------------------------------------------------------- 1 | lesson_name("Templates") 2 | make_and_go_ws 3 | clear_ws 4 | run(`cp -r /tutorials/files/step-10/* .`) 5 | 6 | "`Templates`" 7 | 8 | "We'll use the `haproxy` as loadbalancer. Of course, install is just like we did for apache. But now configuration is a bit more tricky since we need to list all web servers in haproxy's configuration. How can we do that?" 9 | 10 | "`HAProxy configuration template`" 11 | 12 | "Ansible uses `Jinja2` ( `http://jinja.pocoo.org/docs/2.9/` ), a templating engine for Python. When you write Jinja2 templates, you can use any variable defined by Ansible." 13 | 14 | "For instance, if you want to output the `inventory_name` of the host the template is currently built for, you just can write `{{ inventory_hostname }}` in the Jinja template." 15 | 16 | "Or if you need the IP of the first ethernet interface (which ansible knows thanks to the `setup` module), you just write: `{{ ansible_eth0['ipv4']['address'] }}` in your template." 17 | 18 | "Jinja2 templates also support conditionals, for-loops, etc..." 19 | 20 | "I've just created a directory called `templates`, and created a Jinja template with name `haproxy.cfg.j2` inside. We use the `.j2` extension by convention, to make it obvious that this is a Jinja2 template, but this is not mandatory." 21 | 22 | "Inspect the template file by running the following command:" 23 | 24 | "*cat templates/haproxy.cfg.j2*" 25 | 26 | prompt { 27 | if success && command == "cat templates/haproxy.cfg.j2" { 28 | expect ("cat templates/haproxy.cfg.j2") 29 | "" 30 | "We have many new things going on here." 31 | break 32 | } 33 | } 34 | 35 | "First, `{{ ansible_eth0['ipv4']['address'] }}` will be replaced by the IP of the load balancer on eth0." 36 | 37 | "Then, we have a loop. This loop is used to build the backend servers list. It will loop over every host listed in the `[web]` group (and put this host in the `backend` variable). For each of the hosts it will render a line using host's facts. All hosts' facts are exposed in the `hostvars` variable, so it's easy to access another host variables (like its hostname or in this case IP)." 38 | 39 | "We could have written the host list by hand, since we have only 2 of them. But we're hoping that the server will be very successful, and that we'll need a hundred of them. Thus, adding servers to the configuration or swapping some out boils down to adding or removing hosts from the `[web]` group." 40 | 41 | "Please press the \"`Enter`\" key to continue!" 42 | prompt { 43 | if command == "" { 44 | expect (" ") 45 | break 46 | } 47 | } 48 | 49 | "`HAProxy playbook`" 50 | 51 | "We've done the most difficult part of the job. Writing a playbook to install and configure HAproxy is a breeze." 52 | 53 | "Display the playbook for haproxy by running the following command:" 54 | 55 | "*cat haproxy.yml*" 56 | 57 | prompt { 58 | if success && command == "cat haproxy.yml" { 59 | expect ("cat haproxy.yml") 60 | "" 61 | break 62 | } 63 | } 64 | 65 | "Looks familiar, isn't it? The only new module here is `template`, which has the same arguments as `copy`. We also restrict this playbook to the group `haproxy`." 66 | 67 | "And now... let's try this out. Since our inventory contains only hosts necessary for the cluster, we don't need to limit the host list and can even run both playbooks. Well, to tell the truth, we must run both of them at the same time, since the haproxy playbook requires facts from the two webservers. In the next lesson we'll show how to avoid this." 68 | 69 | "Run the following command to execute our playbooks:" 70 | 71 | "*ansible-playbook -i hosts apache.yml haproxy.yml*" 72 | 73 | prompt { 74 | if success && command == "ansible-playbook -i hosts apache.yml haproxy.yml" { 75 | expect ("ansible-playbook -i hosts apache.yml haproxy.yml") 76 | "" 77 | "Looks good." 78 | 79 | say("Now head to `http://127.0.0.1:" + run (`echo $HOSTPORT_BASE`) + "` and see the result. Your cluster is deployed!") 80 | 81 | say("You can even peek at HAProxy's statistics at `http://127.0.0.1:" + run (`echo $HOSTPORT_BASE`) + "/haproxy?stats`") 82 | 83 | say("! Remember our hosts are docker containers and `port 80 of host0.example.org` is exposed to `port " + run (`echo $HOSTPORT_BASE`) + " of your local machine`") 84 | break 85 | } 86 | } 87 | 88 | "Now on to the next lesson about \"Variables again\"." 89 | 90 | "This is the end of this lesson!" 91 | 92 | "Please press the \"`Enter`\" key to continue!" 93 | prompt { 94 | if command == "" { 95 | expect (" ") 96 | break 97 | } 98 | } -------------------------------------------------------------------------------- /tutorials/11-step-11.nutsh: -------------------------------------------------------------------------------- 1 | lesson_name("Variables again") 2 | make_and_go_ws 3 | clear_ws 4 | run(`cp -r /tutorials/files/step-11/* .`) 5 | 6 | "`Variables again`" 7 | 8 | "So we've setup our loadbalancer, and it works quite well. We grabbed variables from facts and used them to build the configuration. But Ansible also supports other kinds of variables. We already saw `ansible_host` in inventory, but now we'll use variables defined in `host_vars` and `group_vars` files." 9 | 10 | "`Fine tuning our HAProxy configuration`" 11 | 12 | "HAProxy usually checks if the backends are alive. When a backend seems dead, it is removed from the backend pool and HAproxy doesn't send requests to it anymore." 13 | 14 | "Backends can also have different weights (between 0 and 256). The higher the weight, the higher number of connections the backend will receive compared to other backends. It's useful to spread traffic more appropriately if nodes are not equally powerful." 15 | 16 | "We'll use variables to configure all these parameters." 17 | 18 | "Please press the \"`Enter`\" key to continue!" 19 | prompt { 20 | if command == "" { 21 | expect (" ") 22 | break 23 | } 24 | } 25 | 26 | "`Group vars`" 27 | 28 | "The check interval will be set in a group_vars file for haproxy. This will ensure all haproxies will inherit from it." 29 | 30 | "We just need to create the file `group_vars/haproxy` below the inventory directory. The file has to be named after the group you want to define the variables for. If we wanted to define variables for the web group, the file would be named `group_vars/web`." 31 | 32 | print("\t 33 | haproxy_check_interval: 3000\n\t 34 | haproxy_stats_socket: /tmp/sock\n\t 35 | ") 36 | 37 | "The name is arbitrary. Meaningful names are recommended of course, but there is no required syntax. You could even use complex variables (a.k.a. Python dict) like this:" 38 | 39 | print("\t 40 | haproxy:\n\t 41 | check_interval: 3000\n\t 42 | stats_socket: /tmp/sock\n\t 43 | ") 44 | 45 | "This is just a matter of taste. Complex vars can help group stuff logically. They can also, under some circumstances, merge subsequently defined keys (note however that this is not the default ansible behaviour). For now we'll just use simple variables." 46 | 47 | "Please press the \"`Enter`\" key to continue!" 48 | prompt { 49 | if command == "" { 50 | expect (" ") 51 | break 52 | } 53 | } 54 | 55 | "`Hosts vars`" 56 | 57 | "Hosts vars follow exactly the same rules, but live in files under `host_vars` directory." 58 | 59 | "Let's define weights for our backends in `host_vars/host1.example.com:`" 60 | 61 | print("\t 62 | haproxy_backend_weight: 100\n\t 63 | ") 64 | 65 | "and `host_vars/host2.example.com:`" 66 | 67 | print("\t 68 | haproxy_backend_weight: 150\n\t 69 | ") 70 | 71 | "If we'd define `haproxy_backend_weight` in `group_vars/web`, it would be used as a 'default': variables defined in `host_vars` files overrides varibles defined in `group_vars`." 72 | 73 | "Now, you can analyse the workspace: `group_vars`, `host_vars` directories and files inside" 74 | 75 | "Once you're done, simply press the \"`Enter`\" key to continue!" 76 | 77 | prompt { 78 | if command == "" { 79 | expect (" ") 80 | break 81 | } 82 | } 83 | 84 | "`Updating the template`" 85 | 86 | "The template must be updated to use these variables." 87 | 88 | "Inspect the template file by running the following command:" 89 | 90 | "*cat templates/haproxy.cfg.j2*" 91 | 92 | prompt { 93 | if success && command == "cat templates/haproxy.cfg.j2" { 94 | expect ("cat templates/haproxy.cfg.j2") 95 | "" 96 | "Note that we also introduced an `{% if ...` block. This block enclosed will only be rendered if the test is true. So if we define `haproxy_stats_socket` somewhere for our loadbalancer (we might even use the `--extra-vars=\"haproxy_stats_sockets=/tmp/sock\"` at the command line), the enclosed line will appear in the generated configuration file (note that the suggested setup is highly insecure!)." 97 | break 98 | } 99 | } 100 | 101 | "Note that, while we could, it's not necessary to run the apache playbook since nothing changed, but we had to cheat a bit for that." 102 | 103 | "See the updated haproxy playbook by running:" 104 | 105 | "*cat haproxy.yml*" 106 | 107 | prompt { 108 | if success && command == "cat haproxy.yml" { 109 | expect ("cat haproxy.yml") 110 | "" 111 | "See? We added an empty play for web hosts at the top. It does nothing. But it's here because it will trigger facts gathering on hosts in group `web`. This is required because the haproxy playbook needs to pick facts from hosts in this group. If we don't do this, ansible will complain saying that `ansible_eth0` key doesn't exist." 112 | break 113 | } 114 | } 115 | 116 | "Let's go!" 117 | "Run the following command to execute the playbook:" 118 | 119 | "*ansible-playbook -i hosts haproxy.yml*" 120 | 121 | prompt { 122 | if success && command == "ansible-playbook -i hosts haproxy.yml" { 123 | expect ("ansible-playbook -i hosts haproxy.yml") 124 | "" 125 | "Great!" 126 | break 127 | } 128 | } 129 | 130 | 131 | "Now on to the next lesson about \"Migrating to Roles!\"." 132 | 133 | "This is the end of this lesson!" 134 | 135 | "Please press the \"`Enter`\" key to continue!" 136 | prompt { 137 | if command == "" { 138 | expect (" ") 139 | break 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /tutorials/12-step-12.nutsh: -------------------------------------------------------------------------------- 1 | lesson_name("Migrating to roles!") 2 | make_and_go_ws 3 | clear_ws 4 | run(`cp -r /tutorials/files/step-12/* .`) 5 | 6 | "`Migrating to roles!`" 7 | 8 | "Now that our playbook is done, let's refactor everything! We'll replace our plays with roles." 9 | 10 | "Roles are just a new way of organizing files but bring interesting features. I won't go into great lengths here, since they're listed in Ansible's documentation ( `http://docs.ansible.com/ansible/latest/playbooks.html` ), but my favorite is probably role dependencies: role B can depend on another role A. Thus, when applying role B, role A will automatically be applied too." 11 | 12 | "`Roles structures`" 13 | 14 | "Roles add a bit of \"magic\" to Ansible: they assume a specific file organization. While there is a suggested layout regarding roles, you can organize things the way you want using includes. However, role's conventions help building modular playbooks, and housekeeping will be much simpler. Rubyists would call this \"convention over configuration\"." 15 | 16 | "Please press the \"`Enter`\" key to see the file layout!" 17 | prompt { 18 | if command == "" { 19 | expect (" ") 20 | break 21 | } 22 | } 23 | 24 | "The file layout for roles looks like this:" 25 | 26 | print("\t 27 | roles\n\t 28 | |\n\t 29 | |_some_role\n\t 30 | |\n\t 31 | |_defaults\n\t 32 | | |\n\t 33 | | |_main.yml\n\t 34 | | |_...\n\t 35 | |\n\t 36 | |_files\n\t 37 | | |\n\t 38 | | |_file1\n\t 39 | | |_...\n\t 40 | |\n\t 41 | |_handlers\n\t 42 | | |\n\t 43 | | |_main.yml\n\t 44 | | |_some_other_file.yml\n\t 45 | | |_ ...\n\t 46 | |\n\t 47 | |_meta\n\t 48 | | |\n\t 49 | | |_main.yml\n\t 50 | | |_some_other_file.yml\n\t 51 | | |_ ...\n\t 52 | |\n\t 53 | |_tasks\n\t 54 | | |\n\t 55 | | |_main.yml\n\t 56 | | |_some_other_file.yml\n\t 57 | | |_ ...\n\t 58 | |\n\t 59 | |_templates\n\t 60 | | |\n\t 61 | | |_template1.j2\n\t 62 | | |_...\n\t 63 | |\n\t 64 | |_vars\n\t 65 | |\n\t 66 | |_main.yml\n\t 67 | |_some_other_file.yml\n\t 68 | |_ ...\n\t 69 | ") 70 | 71 | "Quite simple. The files named `main.yml` are not mandatory. However, when they exist, roles will add them to the play automatically. You can use this file to include other tasks, handlers, ... in the play. We'll see that in a minute." 72 | 73 | "Please press the \"`Enter`\" key to continue!" 74 | prompt { 75 | if command == "" { 76 | expect (" ") 77 | break 78 | } 79 | } 80 | 81 | "Note that there is also a `vars` and a `meta` directory. `vars` is used when you want to put a bunch of variables regarding the roles. However, I don't like setting vars in roles (or plays) directly. I think variables belong to configuration, while plays are the structure. In other words, I see plays and roles as a factory, and data as inputs to this factory. So I really prefer to have \"data\" (e.g. variables) outside roles and play. This way, I can share my roles more easily, without worrying about exposing too much about my servers. But that's just a personal preference. Ansible just lets you do it the way you want." 82 | 83 | "But you have some vars that you hardly want to change. For instance, if you have a role for nginx that pulls the .deb package from a PPA, you might want to add the PPA address in `vars/main.yml`. It is something that you can configure, but that will be mostly static 99% of the time. Using `vars` will let you pull out this information out of your role, making it more generic. But really, this is a matter of taste." 84 | 85 | "However, for real vars (e.g. things you would like to use in a configuration file generated by a template), you can set defaults for roles, and this is a recommended practice. Using sane defaults ensures your role always work. For instance, you could set the number of pre-forked servers for your apache server. The best place to put the defaults is... you guessed it, the `defaults` directory." 86 | 87 | "The `meta` directory is where you can add dependencies, and it's really a neat feature. We'll see that later." 88 | 89 | "Note that roles sit in the `roles` directory, which is also cool since it will reduce top level ansible playbook clutter. But you can configure Ansible to use an alternate directory to store roles (see roles_path variable in ansible.cfg: `http://docs.ansible.com/ansible/latest/intro_configuration.html#roles-path`). This way you can setup a 'central place' for all your roles, and use them in all your playbooks." 90 | 91 | "Please press the \"`Enter`\" key to continue!" 92 | prompt { 93 | if command == "" { 94 | expect (" ") 95 | break 96 | } 97 | } 98 | 99 | "`Creating the Apache role`" 100 | 101 | "Ok, now that we know the required layout, we can create our apache role from our apache playbook." 102 | 103 | "The steps required are really simple:" 104 | 105 | print("\t 106 | - create the roles directory and apache role layout\n\t 107 | - extract the apache handler into roles/apache/handlers/main.yml\n\t 108 | - move the apache configuration file awesome-app into roles/apache/files/\n\t 109 | - create a role playbook\n\t 110 | ", "cyan") 111 | 112 | "Lets start!" 113 | 114 | "`Creating the role layout`" 115 | 116 | "First start with creating required folder structure for `apache` role. Run the following command:" 117 | 118 | "*mkdir -p roles/apache/{tasks,handlers,files}*" 119 | 120 | prompt { 121 | if success && command == "mkdir -p roles/apache/{tasks,handlers,files}" { 122 | expect ("mkdir -p roles/apache/{tasks,handlers,files}") 123 | "" 124 | break 125 | } 126 | } 127 | 128 | "Now we need to copy the tasks from `apache.yml` as `roles/apache/tasks/main.yml`" 129 | 130 | "First check the `apache_tasks.yml` file that I prepared for you" 131 | 132 | "*cat apache_tasks.yml*" 133 | 134 | prompt { 135 | if success && command == "cat apache_tasks.yml" { 136 | expect ("cat apache_tasks.yml") 137 | "" 138 | "The file is not fully reproduced, but it is exactly the content of `apache.yml` between `tasks:` and `handlers:`." 139 | break 140 | } 141 | } 142 | 143 | "Now move this file to the correct place with correct name:" 144 | 145 | "*mv apache_tasks.yml roles/apache/tasks/main.yml*" 146 | 147 | prompt { 148 | if success && command == "mv apache_tasks.yml roles/apache/tasks/main.yml" { 149 | expect ("mv apache_tasks.yml roles/apache/tasks/main.yml") 150 | "" 151 | 152 | "Note that we also have to remove references to `files/` and `templates/` directories in tasks. Since we're using the roles structure, Ansible will look for them in the right directories." 153 | 154 | "Ok, let's move on!" 155 | break 156 | } 157 | } 158 | 159 | "Please press the \"`Enter`\" key to continue!" 160 | prompt { 161 | if command == "" { 162 | expect (" ") 163 | break 164 | } 165 | } 166 | 167 | "`Extracting the handler`" 168 | 169 | "I've extracted the handlers part into the file `apache_handlers.yml`. Check its content:" 170 | 171 | "*cat apache_handlers.yml*" 172 | 173 | prompt { 174 | if success && command == "cat apache_handlers.yml" { 175 | expect ("cat apache_handlers.yml") 176 | "" 177 | break 178 | } 179 | } 180 | 181 | 182 | "Now move this file as roles/apache/handlers/main.yml" 183 | 184 | "*mv apache_handlers.yml roles/apache/handlers/main.yml*" 185 | 186 | prompt { 187 | if success && command == "mv apache_handlers.yml roles/apache/handlers/main.yml" { 188 | expect ("mv apache_handlers.yml roles/apache/handlers/main.yml") 189 | "" 190 | 191 | break 192 | } 193 | } 194 | 195 | "`Moving the configuration file`" 196 | 197 | "As simple as:" 198 | 199 | "*mv files/awesome-app roles/apache/files/*" 200 | 201 | prompt { 202 | if success && command == "mv files/awesome-app roles/apache/files/" { 203 | expect ("mv files/awesome-app roles/apache/files/") 204 | "" 205 | break 206 | } 207 | } 208 | 209 | "We no longer need the `files` folder at root, delete it:" 210 | 211 | "*rm -rf files*" 212 | 213 | prompt { 214 | if success && command =~ "rm -rf files\." { 215 | expect ("rm -rf files") 216 | "" 217 | break 218 | } 219 | } 220 | 221 | "At this point, the apache role is fully working, but we need a way to invoke it." 222 | 223 | "`Create a role playbook`" 224 | 225 | "I've just created a top level playbook that we'll use to map hosts and host groups to roles and called it `site.yml`, since our goal is to have our site-wide configuration in it." 226 | 227 | "Check the top level playbook by running:" 228 | 229 | "*cat site.yml*" 230 | 231 | prompt { 232 | if success && command == "cat site.yml" { 233 | expect ("cat site.yml") 234 | "" 235 | "That wasn't too hard." 236 | break 237 | } 238 | } 239 | 240 | "Now let's create the haproxy role." 241 | 242 | "We could create the haproxy role directory structure using mkdir as we did for apache role:" 243 | 244 | print("\t 245 | mkdir -p roles/haproxy/{tasks,handlers,templates}\n\t 246 | ") 247 | 248 | "But, we have another option which is using `ansible-galaxy`. Let's do it so:" 249 | 250 | "*ansible-galaxy --offline init roles/haproxy*" 251 | 252 | prompt { 253 | if success && command == "ansible-galaxy --offline init roles/haproxy" { 254 | expect ("ansible-galaxy --offline init roles/haproxy") 255 | "" 256 | "You can check the directory structure with `ls -la roles/haproxy`." 257 | break 258 | } 259 | } 260 | 261 | "Now move the haproxy_tasks.yml to the correct place with correct name:" 262 | 263 | "*mv haproxy_tasks.yml roles/haproxy/tasks/main.yml*" 264 | 265 | prompt { 266 | if success && command == "mv haproxy_tasks.yml roles/haproxy/tasks/main.yml" { 267 | expect ("mv haproxy_tasks.yml roles/haproxy/tasks/main.yml") 268 | "" 269 | 270 | break 271 | } 272 | } 273 | 274 | "Move `haproxy_handlers.yml` to roles/haproxy/handlers/main.yml" 275 | 276 | "*mv haproxy_handlers.yml roles/haproxy/handlers/main.yml*" 277 | 278 | prompt { 279 | if success && command == "mv haproxy_handlers.yml roles/haproxy/handlers/main.yml" { 280 | expect ("mv haproxy_handlers.yml roles/haproxy/handlers/main.yml") 281 | "" 282 | 283 | "Almost done!" 284 | break 285 | } 286 | } 287 | 288 | "Finally move `templates folder` (which only contains the haproxy template file) to the correct place:" 289 | 290 | "*mv templates roles/haproxy/*" 291 | 292 | prompt { 293 | if success && command == "mv templates roles/haproxy/" { 294 | expect ("mv templates roles/haproxy/") 295 | "" 296 | break 297 | } 298 | } 299 | 300 | "Great, `haproxy` role is ready as well!" 301 | 302 | "Now we can run our new playbook. Run as follows:" 303 | 304 | "*ansible-playbook -i hosts site.yml*" 305 | 306 | prompt { 307 | if success && command == "ansible-playbook -i hosts site.yml" { 308 | expect ("ansible-playbook -i hosts site.yml") 309 | "" 310 | 311 | "Perfect!" 312 | 313 | say("Now head to `http://127.0.0.1:" + run (`echo $HOSTPORT_BASE`) + "` and see the result. Your cluster is deployed!") 314 | 315 | say("You can even peek at HAProxy's statistics at `http://127.0.0.1:" + run (`echo $HOSTPORT_BASE`) + "/haproxy?stats`") 316 | 317 | say("! Remember our hosts are docker containers and `port 80 of host0.example.org` is exposed to `port " + run (`echo $HOSTPORT_BASE`) + " of your local machine`") 318 | 319 | break 320 | } 321 | } 322 | 323 | "You may have noticed that running all roles in site.yml can take a long time. What if you only wanted to push changes to web? This is also easy, with the limit flag. Run the following:" 324 | 325 | "*ansible-playbook -i hosts -l web site.yml*" 326 | 327 | prompt { 328 | if success && command == "ansible-playbook -i hosts -l web site.yml" { 329 | expect ("ansible-playbook -i hosts -l web site.yml") 330 | "" 331 | 332 | "This concludes our migration to roles. It was quite easy, and adds a bunch of features to our playbook that we can use later." 333 | 334 | break 335 | } 336 | } 337 | 338 | "This is the end of this lesson!" 339 | 340 | "Please press the \"`Enter`\" key to continue!" 341 | prompt { 342 | if command == "" { 343 | expect (" ") 344 | break 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /tutorials/13-step-13.nutsh: -------------------------------------------------------------------------------- 1 | lesson_name("Using roles from Ansible Galaxy - Install a Jenkins server") 2 | make_and_go_ws 3 | clear_ws 4 | run(`cp -r /tutorials/files/step-13/* .`) 5 | 6 | "Ansible Galaxy ( `http://docs.ansible.com/ansible/latest/galaxy.html` ) refers to the Galaxy website where users can share roles, and to a command line tool for installing, creating and managing roles." 7 | 8 | "In this lesson, we are going to use a role from Ansible Galaxy to install Jenkins on one of our nodes." 9 | 10 | "You can browse available roles here: https://galaxy.ansible.com/search?keywords=&order_by=-relevance&deprecated=false&type=role&page=1" 11 | 12 | "Run the following command to install the required role" 13 | 14 | "*ansible-galaxy install geerlingguy.jenkins geerlingguy.java*" 15 | 16 | prompt { 17 | if success && command == "ansible-galaxy install geerlingguy.jenkins geerlingguy.java" { 18 | expect ("ansible-galaxy install geerlingguy.jenkins geerlingguy.java") 19 | "" 20 | "OK!" 21 | break 22 | } 23 | } 24 | 25 | "Let's continue with an inventory file. We will install on `host0.example.org`" 26 | 27 | "Check the inventory file I've prepared for you, by running:" 28 | 29 | "*cat hosts*" 30 | 31 | prompt { 32 | if success && command == "cat hosts" { 33 | expect ("cat hosts") 34 | "" 35 | "See? We created a new group named `jenkins`. This is not mandatory but a good practice." 36 | break 37 | } 38 | } 39 | 40 | "Now we need a playbook and we will use the one provided as example in github repo of the role: `https://github.com/geerlingguy/ansible-role-jenkins#example-playbook`" 41 | 42 | "Inspect our playbook, by running:" 43 | 44 | "*cat jenkins.yaml*" 45 | 46 | prompt { 47 | if success && command == "cat jenkins.yaml" { 48 | expect ("cat jenkins.yaml") 49 | "" 50 | 51 | "The playbook includes another role which installs `java` (a prerequisite for jenkins). This role has already been installed with our first `ansible-galaxy install ...` command as `geerlingguy.jenkins` has a dependency on `geerlingguy.java`." 52 | 53 | "That's it, we are ready to go!" 54 | 55 | break 56 | } 57 | } 58 | 59 | "Run the following command to run the playbook:" 60 | 61 | "*ansible-playbook -i hosts jenkins.yaml*" 62 | 63 | prompt { 64 | if success && command == "ansible-playbook -i hosts jenkins.yaml" { 65 | expect ("ansible-playbook -i hosts jenkins.yaml") 66 | "" 67 | 68 | "Perfect!" 69 | 70 | say("Now go to `http://127.0.0.1:" + run (`echo $(($HOSTPORT_BASE+3))`) + "` and see the result!") 71 | 72 | say("You can login with `admin/admin` :)") 73 | 74 | say("! Remember our hosts are docker containers and `port 8080 of host0.example.org` is exposed to `port " + run (`echo $(($HOSTPORT_BASE+3))`) + " of your local machine`") 75 | 76 | break 77 | } 78 | } 79 | 80 | 81 | "This is the end of this lesson!" 82 | 83 | "Please press the \"`Enter`\" key to continue!" 84 | prompt { 85 | if command == "" { 86 | expect (" ") 87 | break 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tutorials/14-freeplay.nutsh: -------------------------------------------------------------------------------- 1 | lesson_name("Free play") 2 | make_and_go_ws 3 | 4 | "This is not a lesson, rather an option to let you play with ansible!" 5 | 6 | "Remember you can quit by typing `exit` or `done` once you're done!" 7 | 8 | prompt { 9 | if command == "done" { 10 | expect ("done") 11 | break 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tutorials/2-step-02.nutsh: -------------------------------------------------------------------------------- 1 | lesson_name("First modules and facts") 2 | make_and_go_ws 3 | clear_ws 4 | run(`cp /tutorials/files/step-1-2/hosts hosts`) 5 | 6 | "`Talking with nodes`" 7 | 8 | "Now we're good to go. " 9 | 10 | "Let's play with the command we saw in the previous chapter: `ansible`. This command is the first one of three that ansible provides which interact with nodes." 11 | 12 | "Please press the \"`Enter`\" key to continue!" 13 | prompt { 14 | if command == "" { 15 | expect (" ") 16 | break 17 | } 18 | } 19 | 20 | "`Doing something useful`" 21 | 22 | "In the previous command, `-m ping` means `use module ping`. This module is one of many available with ansible. " 23 | 24 | "`ping module` is really simple, it doesn't need any arguments. Modules that take arguments pass them via `-a` switch. Let's see a few other modules." 25 | 26 | "Please press the \"`Enter`\" key to continue!" 27 | prompt { 28 | if command == "" { 29 | expect (" ") 30 | break 31 | } 32 | } 33 | 34 | "`Shell module`" 35 | 36 | "This module lets you execute a shell command on the remote host" 37 | 38 | "Please run *ansible -i hosts -m shell -a 'uname -a' host0.example.org*" 39 | 40 | prompt { 41 | if success && command == "ansible -i hosts -m shell -a 'uname -a' host0.example.org" { 42 | expect ("ansible -i hosts -m shell -a 'uname -a' host0.example.org") 43 | "Easy!" 44 | "Next one is the `copy module`" 45 | break 46 | } 47 | } 48 | 49 | "Please press the \"`Enter`\" key to continue!" 50 | prompt { 51 | if command == "" { 52 | expect (" ") 53 | break 54 | } 55 | } 56 | 57 | "`Copy module`" 58 | 59 | "No surprise, with this module you can copy a file from the controlling machine to the node. Lets say we want to copy our `/etc/motd` to `/tmp` of our target node. Please run the following command:" 60 | 61 | "*ansible -i hosts -m copy -a 'src=/etc/motd dest=/tmp/' host0.example.org*" 62 | 63 | prompt { 64 | if success && command == "ansible -i hosts -m copy -a 'src=/etc/motd dest=/tmp/' host0.example.org" { 65 | expect ("ansible -i hosts -m copy -a 'src=/etc/motd dest=/tmp/' host0.example.org") 66 | "Please check the output above." 67 | break 68 | } 69 | } 70 | 71 | "Ansible (more accurately `copy module` executed on the node) replied back a bunch of useful information in JSON format. We'll see how that can be used later." 72 | 73 | "We'll see other useful modules below. Ansible has a huge module list that covers almost anything you can do on a system." 74 | 75 | "Please see the following page: `https://docs.ansible.com/ansible/latest/collections/index_module.html`" 76 | 77 | "If you can't find the right module, writing one is pretty easy (it doesn't even have to be Python, it just needs to speak JSON)." 78 | 79 | "Please press the \"`Enter`\" key to continue!" 80 | prompt { 81 | if command == "" { 82 | expect (" ") 83 | break 84 | } 85 | } 86 | 87 | "`Many hosts, same command`" 88 | 89 | "Ok, the above stuff is fun, but we have many nodes to manage. Let's try that on other hosts too." 90 | 91 | "Lets say we want to get some facts about the node, and, for instance, know which Ubuntu version we have deployed on nodes, it's pretty easy. Please run the following:" 92 | 93 | "*ansible -i hosts -m shell -a 'grep DISTRIB_RELEASE /etc/lsb-release' all*" 94 | 95 | prompt { 96 | if success && command == "ansible -i hosts -m shell -a 'grep DISTRIB_RELEASE /etc/lsb-release' all" { 97 | expect ("ansible -i hosts -m shell -a 'grep DISTRIB_RELEASE /etc/lsb-release' all") 98 | "`all` is a shortcut meaning `all hosts found in inventory file`" 99 | break 100 | } 101 | } 102 | 103 | "Please press the \"`Enter`\" key to continue!" 104 | prompt { 105 | if command == "" { 106 | expect (" ") 107 | break 108 | } 109 | } 110 | 111 | "`Many more facts`" 112 | 113 | "That was easy. However, It would quickly become cumbersome if we wanted more information (ip addresses, RAM size, etc...). The solution comes from another really handy module (weirdly) called `setup`: it specializes in node's `facts` gathering." 114 | 115 | "Try it out:" 116 | 117 | "*ansible -i hosts -m setup host0.example.org*" 118 | 119 | prompt { 120 | if success && command == "ansible -i hosts -m setup host0.example.org" { 121 | expect ("ansible -i hosts -m setup host0.example.org") 122 | "Replied with lots of information" 123 | break 124 | } 125 | } 126 | 127 | "You may also filter returned keys, in case you're looking for something specific." 128 | 129 | "For instance, let's say you want to know how much memory you have on all your hosts, easy. Please run the following:" 130 | 131 | "*ansible -i hosts -m setup -a 'filter=ansible_memtotal_mb' all*" 132 | 133 | prompt { 134 | if success && command == "ansible -i hosts -m setup -a 'filter=ansible_memtotal_mb' all" { 135 | expect ("ansible -i hosts -m setup -a 'filter=ansible_memtotal_mb' all") 136 | "Notice that hosts may reply in different order compared to the previous output. This is because ansible parallelizes communications with hosts!" 137 | break 138 | } 139 | } 140 | 141 | "BTW, when using the setup module, you can use `*` in the `filter=` expression. It will act like a shell glob" 142 | 143 | "Please press the \"`Enter`\" key to continue!" 144 | prompt { 145 | if command == "" { 146 | expect (" ") 147 | break 148 | } 149 | } 150 | 151 | "`Selecting hosts`" 152 | 153 | "We saw that `all` means 'all hosts', but ansible provides a lot of other ways to select hosts: `http://docs.ansible.com/ansible/latest/intro_patterns.html`" 154 | 155 | "- `host0.example.org:host1.example.org` would run on host0.example.org and host1.example.org" 156 | 157 | "- `host*.example.org` would run on all hosts starting with 'host' and ending with '.example.org' (just like a shell glob too)" 158 | 159 | "There are other ways that involve groups, we'll see that in the next lesson." 160 | 161 | "This is the end of this lesson!" 162 | 163 | "Please press the \"`Enter`\" key to continue!" 164 | prompt { 165 | if command == "" { 166 | expect (" ") 167 | break 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /tutorials/3-step-03.nutsh: -------------------------------------------------------------------------------- 1 | lesson_name("Groups and variables") 2 | make_and_go_ws 3 | clear_ws 4 | 5 | "`Grouping hosts`" 6 | 7 | "Hosts in inventory can be grouped arbitrarily. For instance, you could have a `debian` group, a `web-servers` group, a `production` group, etc..." 8 | 9 | print("\t 10 | [debian]\n\t 11 | host0.example.org\n\t 12 | host1.example.org\n\t 13 | host2.example.org\n\t") 14 | 15 | "This can even be expressed shorter:" 16 | 17 | print("\t 18 | [debian]\n\t 19 | host[0:2].example.org\n\t") 20 | 21 | "If you wish to use child groups, just define a `[groupname:children]` and add child groups in it. For instance, let's say we have various flavors of linux running, we could organize our inventory like this:" 22 | 23 | print("\t 24 | [ubuntu]\n\t 25 | host0.example.org\n\t 26 | \n\t 27 | [debian]\n\t 28 | host[1:2].example.org\n\t 29 | \n\t 30 | [linux:children]\n\t 31 | ubuntu\n\t 32 | debian\n\t") 33 | 34 | "Grouping of course, leverages configuration mutualization." 35 | 36 | "Please press the \"`Enter`\" key to continue!" 37 | prompt { 38 | if command == "" { 39 | expect (" ") 40 | break 41 | } 42 | } 43 | 44 | "`Setting variables`" 45 | 46 | "You can assign variables to hosts in several places: inventory file, host vars files, group vars files, etc..." 47 | 48 | "I usually set most of my variables in group/host vars files (more on that later). However, I often use some variables directly in the inventory file, such as `ansible_host` which sets the IP address for the host. Ansible by default resolves hosts' name when it attempts to connect via SSH. But when you're bootstrapping a host, it might not have its definitive ip address yet. `ansible_host` comes in handy here." 49 | 50 | "When using `ansible-playbook` command (not the regular `ansible` command), variables can also be set with `--extra-vars` (or `-e`) command line switch. `ansible-playbook` command will be covered in the next step." 51 | 52 | "`ansible_port`, as you can guess, has the same function regarding the ssh port ansible will try to connect at." 53 | 54 | print("\t 55 | [ubuntu]\n\t 56 | host0.example.org ansible_host=192.168.0.12 ansible_port=2222\n\t") 57 | 58 | "Please press the \"`Enter`\" key to continue!" 59 | prompt { 60 | if command == "" { 61 | expect (" ") 62 | break 63 | } 64 | } 65 | 66 | "Ansible will look for additional variables definitions in group and host variable files. These files will be searched in directories `group_vars` and `host_vars`, below the directory where the main inventory file is located." 67 | 68 | "The files will be searched by name. For instance, using the previously mentioned inventory file, `host0.example.org` variables will be searched in those files:" 69 | 70 | print("\t 71 | - group_vars/linux\n\t 72 | - group_vars/ubuntu\n\t 73 | - host_vars/host0.example.org\n", "cyan") 74 | 75 | "It doesn't matter if those files do not exist, but if they do, ansible will use them." 76 | 77 | "Now that we know the basics of modules, inventories and variables, let's explore the real power of Ansible with playbooks." 78 | 79 | "This is the end of this lesson!" 80 | 81 | "Please press the \"`Enter`\" key to continue!" 82 | prompt { 83 | if command == "" { 84 | expect (" ") 85 | break 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tutorials/4-step-04.nutsh: -------------------------------------------------------------------------------- 1 | lesson_name("Playbooks") 2 | make_and_go_ws 3 | clear_ws 4 | run(`cp /tutorials/files/step-4/hosts hosts`) 5 | 6 | "`Ansible playbooks`" 7 | 8 | "Playbook concept is very simple: it's just a series of ansible commands (tasks), like the ones we used with the `ansible` CLI tool. These tasks are targeted at a specific set of hosts/groups." 9 | 10 | "The necessary files for this step should have appeared magically and you don't even have to type them." 11 | 12 | "`Apache example (a.k.a. Ansible's \"Hello World!\")`" 13 | 14 | "Inspect the inventory file that we prepared for this lesson by running the following command:" 15 | 16 | "*cat hosts*" 17 | 18 | prompt { 19 | if success && command == "cat hosts" { 20 | expect ("cat hosts") 21 | "" 22 | "Note: remember you can (and in our exercise we do) use `ansible_host` to set the real IP of the host. In the real hosts file, we also have `ansible_user=root` to cope with potential different ansible default configurations." 23 | break 24 | } 25 | } 26 | 27 | run(`cp /tutorials/files/step-4/apache.yml apache.yml`) 28 | 29 | "Lets build a playbook that will install apache on machines in the `web` group." 30 | 31 | "I've just copied a playbook file into the current directory, run the following to see the content:" 32 | 33 | "*cat apache.yml*" 34 | 35 | prompt { 36 | if success && command == "cat apache.yml" { 37 | expect ("cat apache.yml") 38 | "" 39 | "We just need to say what we want to do using the right ansible modules. Here, we're using the `apt` module (http://docs.ansible.com/ansible/latest/apt_module.html) that can install debian packages. We also ask this module to update the package cache." 40 | 41 | "We also added a name for this task. While this is not necessary, it's very informative when the playbook runs, so it's highly recommended." 42 | 43 | "All in all, this was quite easy!" 44 | break 45 | } 46 | } 47 | 48 | "We will now continue with the command below. Here, `hosts` is the inventory file, `-l` limits the run only to `host1.example.org` and `apache.yml` is our playbook." 49 | 50 | "Now run the playbook with the following command: " 51 | 52 | "*ansible-playbook -i hosts -l host1.example.org apache.yml*" 53 | 54 | prompt { 55 | if success && command == "ansible-playbook -i hosts -l host1.example.org apache.yml" { 56 | expect ("ansible-playbook -i hosts -l host1.example.org apache.yml") 57 | 58 | "Great! Let's analyse the output one line at a time." 59 | 60 | break 61 | } 62 | } 63 | 64 | "Please press the \"`Enter`\" key to continue!" 65 | prompt { 66 | if command == "" { 67 | expect (" ") 68 | break 69 | } 70 | } 71 | 72 | print ("\t 73 | PLAY [web] ********************* \n 74 | ") 75 | 76 | "Ansible tells us it's running the play on hosts `web`. A play is a suite of ansible instructions related to a host. If we'd have another `-host: blah` line in our playbook, it would show up too (but after the first play has completed)." 77 | 78 | print ("\t 79 | TASK [Gathering Facts] ********************* \n\t 80 | ok: [host1.example.org]\n\t 81 | \n") 82 | 83 | "Remember when we used the `setup` module? Before each play, ansible runs it on necessary hosts to gather facts. If this is not required because you don't need any info from the host, you can just add `gather_facts: no` below the host entry (same level as `tasks:`)." 84 | 85 | print ("\t 86 | TASK [Installs apache web server] ********************* \n\t 87 | changed: [host1.example.org]\n\t 88 | \n") 89 | 90 | "Next, the real stuff: our (first and only) task is run, and because it says `changed`, we know that it changed something on `host1.example.org`." 91 | 92 | print ("\t 93 | PLAY RECAP ********************* \n\t 94 | host1.example.org : ok=2 changed=1 unreachable=0 failed=0 \n\t 95 | \n") 96 | 97 | "Finally, ansible outputs a recap of what happened: two tasks have been run and one of them changed something on the host (our apache task, setup module doesn't change anything)." 98 | 99 | "Now let's try to run it again and see what happens:" 100 | 101 | "*ansible-playbook -i hosts -l host1.example.org apache.yml*" 102 | 103 | prompt { 104 | if success && command == "ansible-playbook -i hosts -l host1.example.org apache.yml" { 105 | expect ("ansible-playbook -i hosts -l host1.example.org apache.yml") 106 | 107 | "Note the changed field, it is now `0`" 108 | 109 | break 110 | } 111 | } 112 | 113 | "This is absolutely normal and is one of the core feature of ansible: the playbook will act only if there is something to do. It's called `idempotency`, and means that you can run your playbook as many times as you want, you will always end up in the same state (well, unless you do crazy things with the `shell` module of course, but this is beyond ansible's control)." 114 | 115 | "`Refining things`" 116 | 117 | "Sure our playbook can install apache server, but it could be a bit more complete. It could add a virtualhost, ensure apache is restarted. It could even deploy our web site from a git repository. Let's make it so." 118 | 119 | "This is the end of this lesson!" 120 | 121 | "Please press the \"`Enter`\" key to continue!" 122 | prompt { 123 | if command == "" { 124 | expect (" ") 125 | break 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tutorials/5-step-05.nutsh: -------------------------------------------------------------------------------- 1 | lesson_name("Playbooks, pushing files on nodes") 2 | make_and_go_ws 3 | clear_ws 4 | run(`cp /tutorials/files/step-5/hosts hosts`) 5 | 6 | "`Refining apache setup`" 7 | 8 | "We've installed apache, now lets set up our virtualhost." 9 | 10 | "`Refining the playbook`" 11 | 12 | "We need just one virtualhost on our server, but we want to replace the default one with something more specific. So we'll have to remove the current (presumably `default`) virtualhost, send our virtualhost, activate it and restart apache." 13 | 14 | run(`cp -r /tutorials/files/step-5/files files`) 15 | 16 | "I've just created a directory called `files`, and added our virtualhost configuration for host1.example.org, which we'll call `awesome-app`" 17 | 18 | "Check the configuration file by running the following command:" 19 | 20 | "*cat files/awesome-app*" 21 | 22 | prompt { 23 | if success && command == "cat files/awesome-app" { 24 | expect ("cat files/awesome-app") 25 | "" 26 | break 27 | } 28 | } 29 | 30 | run(`cp /tutorials/files/step-5/apache.yml apache.yml`) 31 | 32 | "Now, a quick update to our apache playbook and we're set. Inspect our playbook by running:" 33 | 34 | "*cat apache.yml*" 35 | 36 | prompt { 37 | if success && command == "cat apache.yml" { 38 | expect ("cat apache.yml") 39 | "" 40 | break 41 | } 42 | } 43 | 44 | "Here we go! Run the following command to execute our playbook:" 45 | 46 | "*ansible-playbook -i hosts -l host1.example.org apache.yml*" 47 | 48 | prompt { 49 | if success && command == "ansible-playbook -i hosts -l host1.example.org apache.yml" { 50 | expect ("ansible-playbook -i hosts -l host1.example.org apache.yml") 51 | "" 52 | "Pretty cool!" 53 | break 54 | } 55 | } 56 | 57 | "Well, thinking about it, we're getting ahead of ourselves here. Shouldn't we check that the config is ok before restarting apache? This way we won't end up interrupting the service if our configuration file is incorrect." 58 | 59 | "Lets do that in the next lesson." 60 | 61 | "This is the end of this lesson!" 62 | 63 | "Please press the \"`Enter`\" key to continue!" 64 | prompt { 65 | if command == "" { 66 | expect (" ") 67 | break 68 | } 69 | } -------------------------------------------------------------------------------- /tutorials/6-step-06.nutsh: -------------------------------------------------------------------------------- 1 | lesson_name("Playbooks and failures") 2 | make_and_go_ws 3 | clear_ws 4 | run(`cp /tutorials/files/step-6/hosts hosts`) 5 | 6 | "`Restarting when config is correct`" 7 | 8 | "We've installed apache, pushed our virtualhost and restarted the server. But what if we wanted the playbook to restart the server only if the config is correct? Let's do that." 9 | 10 | "`Bailing out when things go wrong`" 11 | 12 | "Ansible has a nifty feature: it will stop all processing if something goes wrong. We'll take advantage of this feature to stop our playbook if the config file is not valid." 13 | 14 | run(`cp -r /tutorials/files/step-6/files files`) 15 | 16 | "I've just changed our awesome-app virtual host configuration file and broken it." 17 | 18 | "See the broken config by running the following command:" 19 | 20 | "*cat files/awesome-app*" 21 | 22 | prompt { 23 | if success && command == "cat files/awesome-app" { 24 | expect ("cat files/awesome-app") 25 | "" 26 | "RocumentDoot instead of DocumentRoot :)" 27 | break 28 | } 29 | } 30 | 31 | "As said, when a task fails, processing stops. So we'll ensure that the configuration is valid before restarting the server. We also start by adding our virtualhost `before` removing the default virtualhost, so a subsequent restart (possibly done directly on the server) won't break apache." 32 | 33 | "Note that we should have done this in the first place. Since we ran our playbook already, the default virtualhost is already deactivated. Nevermind: this playbook might be used on other innocent hosts, so let's protect them." 34 | 35 | run(`cp /tutorials/files/step-6/apache.yml apache.yml`) 36 | 37 | "Inspect our playbook by running:" 38 | 39 | "*cat apache.yml*" 40 | 41 | prompt { 42 | if success && command == "cat apache.yml" { 43 | expect ("cat apache.yml") 44 | "" 45 | break 46 | } 47 | } 48 | 49 | "Here we go! Run the following command to execute our playbook:" 50 | 51 | "*ansible-playbook -i hosts -l host1.example.org apache.yml*" 52 | 53 | prompt { 54 | if ! success && command == "ansible-playbook -i hosts -l host1.example.org apache.yml" { 55 | expect ("ansible-playbook -i hosts -l host1.example.org apache.yml") 56 | "" 57 | "As you can see since `apache2ctl` returns with an exit code of 1 when it fails, ansible is aware of it and stops processing. Great!" 58 | break 59 | } 60 | } 61 | 62 | "Mmmh, not so great in fact... Our virtual host has been added anyway. Any subsequent apache restart will complain about our config and bail out. So we need a way to catch failures and revert back." 63 | 64 | "Lets do that in the next lesson." 65 | 66 | "This is the end of this lesson!" 67 | 68 | "Please press the \"`Enter`\" key to continue!" 69 | prompt { 70 | if command == "" { 71 | expect (" ") 72 | break 73 | } 74 | } -------------------------------------------------------------------------------- /tutorials/7-step-07.nutsh: -------------------------------------------------------------------------------- 1 | lesson_name("Playbook conditionals") 2 | make_and_go_ws 3 | clear_ws 4 | run(`cp /tutorials/files/step-7/hosts hosts`) 5 | run(`cp -r /tutorials/files/step-7/files files`) 6 | 7 | "`Using conditionals`" 8 | 9 | "We've installed apache, pushed our virtualhost and restarted the server. But we want to revert things to a stable state if something goes wrong." 10 | 11 | "`Reverting when things go wrong`" 12 | 13 | "A word of warning: there's no magic here. The previous error was not ansible's fault. It's not a backup system, and it can't rollback all things. It's your job to make sure your playbooks are safe. Ansible just doesn't know how to revert the effects of `a2ensite awesome-app`." 14 | 15 | "But if we care to do it, it's well within our reach." 16 | 17 | "As said, when a task fails, processing stops... unless we accept failure (and we should). This is what we'll do: continue processing if there is a failure but only to revert what we've done." 18 | 19 | run(`cp /tutorials/files/step-7/apache.yml apache.yml`) 20 | 21 | "First inspect our playbook by running:" 22 | 23 | "*cat apache.yml*" 24 | 25 | prompt { 26 | if success && command == "cat apache.yml" { 27 | expect ("cat apache.yml") 28 | "" 29 | break 30 | } 31 | } 32 | 33 | "The `register` keyword records output from the `apache2ctl configtest` command (exit status, stdout, stderr, ...), and `when: result|failed` checks if the registered variable (`result`) contains a failed status." 34 | 35 | "Here we go! Run the following command to execute our playbook:" 36 | 37 | "*ansible-playbook -i hosts -l host1.example.org apache.yml*" 38 | 39 | prompt { 40 | if ! success && command == "ansible-playbook -i hosts -l host1.example.org apache.yml" { 41 | expect ("ansible-playbook -i hosts -l host1.example.org apache.yml") 42 | "" 43 | "Seemed to work as expected." 44 | break 45 | } 46 | } 47 | 48 | "Let's try to restart apache to see if it really worked, but how? Of course with ansible :)" 49 | 50 | "Run the following command:" 51 | 52 | "*ansible -i hosts -m service -a 'name=apache2 state=restarted' host1.example.org*" 53 | 54 | prompt { 55 | if success && command == "ansible -i hosts -m service -a 'name=apache2 state=restarted' host1.example.org" { 56 | expect ("ansible -i hosts -m service -a 'name=apache2 state=restarted' host1.example.org") 57 | "" 58 | "Ok, now our apache is safe from misconfiguration here." 59 | break 60 | } 61 | } 62 | 63 | "While this sounds like a lot of work, it isn't. Remember you can use variables almost everywhere, so it's easy to make this a general playbook for apache, and use it everywhere to deploy your virtualhosts. Write it once, use it everywhere." 64 | 65 | "We'll do that in lesson 10 but for now, let's deploy our web site using git in the next lesson." 66 | 67 | "This is the end of this lesson!" 68 | 69 | "Please press the \"`Enter`\" key to continue!" 70 | prompt { 71 | if command == "" { 72 | expect (" ") 73 | break 74 | } 75 | } -------------------------------------------------------------------------------- /tutorials/8-step-08.nutsh: -------------------------------------------------------------------------------- 1 | lesson_name("Git module") 2 | make_and_go_ws 3 | clear_ws 4 | run(`cp /tutorials/files/step-8/hosts hosts`) 5 | run(`cp -r /tutorials/files/step-8/files files`) 6 | 7 | "`Deploying our website from git`" 8 | 9 | "We've installed apache, pushed our virtualhost and restarted the server safely. Now we'll use the git module to deploy our application." 10 | 11 | "`The git module`" 12 | 13 | "Well, this is a kind of break. Nothing necessarily new here. The `git` module is just another module. But we'll try it out just for fun. And we'll be familiar with it when it comes to `ansible-pull` later on." 14 | 15 | "Our virtualhost is set, but we need a few changes to finish our deployment. First, we're deploying a PHP application. So we need to install the `libapache2-mod-php5` package. Second, we have to install `git` since the git module (used to clone our application's git repository) uses it." 16 | 17 | "We could do it like this:" 18 | 19 | print("\t 20 | ...\n\t 21 | - name: Installs apache web server\n\t 22 | apt: pkg=apache2 state=present update_cache=true\n\t 23 | \n\t 24 | - name: Installs php module\n\t 25 | apt: pkg=libapache2-mod-php state=present\n\t 26 | \n\t 27 | - name: Installs git\n\t 28 | apt: pkg=git state=present\n\t 29 | ...\n\t 30 | ") 31 | 32 | "Please press the \"`Enter`\" key to continue!" 33 | prompt { 34 | if command == "" { 35 | expect (" ") 36 | break 37 | } 38 | } 39 | 40 | "but Ansible provides a more readable way to write this. Ansible can loop over a series of items, and use each item in an action like this:" 41 | 42 | print("\t 43 | ...\n\t 44 | - name: Installs necessary packages\n\t 45 | apt: pkg={{ item }} state=latest\n\t 46 | with_items:\n\t 47 | - apache2\n\t 48 | - libapache2-mod-php\n\t 49 | - git\n\t 50 | ...\n\t 51 | ") 52 | 53 | run(`cp /tutorials/files/step-8/apache.yml apache.yml`) 54 | 55 | "Now, first inspect our playbook by running:" 56 | 57 | "*cat apache.yml*" 58 | 59 | prompt { 60 | if success && command == "cat apache.yml" { 61 | expect ("cat apache.yml") 62 | "" 63 | break 64 | } 65 | } 66 | 67 | "Here we go! Run the following command to execute our playbook:" 68 | 69 | "*ansible-playbook -i hosts -l host1.example.org apache.yml*" 70 | 71 | prompt { 72 | if success && command == "ansible-playbook -i hosts -l host1.example.org apache.yml" { 73 | expect ("ansible-playbook -i hosts -l host1.example.org apache.yml") 74 | "" 75 | say("You can now browse to `http://127.0.0.1:" + run (`echo $(($HOSTPORT_BASE+1))`) + "`, and it should display a Bill Murray, and the server hostname.") 76 | say("! Remember our hosts are docker containers and `port 80 of host1.example.org` is exposed to `port " + run (`echo $(($HOSTPORT_BASE+1))`) + " of your local machine`") 77 | break 78 | } 79 | } 80 | 81 | "Note the `tags:` deploy line allows you to execute just a part of the playbook. Let's say you push a new version for your site. You want to speed up and execute only the part that takes care of deployment. Tags allows you to do it. Of course, \"deploy\" is just a string, it doesn't have any specific meaning and can be anything." 82 | 83 | "Let's see how to use it, run the following command:" 84 | 85 | "*ansible-playbook -i hosts -l host1.example.org apache.yml -t deploy*" 86 | 87 | prompt { 88 | if success && command == "ansible-playbook -i hosts -l host1.example.org apache.yml -t deploy" { 89 | expect ("ansible-playbook -i hosts -l host1.example.org apache.yml -t deploy") 90 | "" 91 | "Cool!" 92 | break 93 | } 94 | } 95 | 96 | "Ok, let's deploy another web server in the next lesson." 97 | 98 | "This is the end of this lesson!" 99 | 100 | "Please press the \"`Enter`\" key to continue!" 101 | prompt { 102 | if command == "" { 103 | expect (" ") 104 | break 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tutorials/9-step-09.nutsh: -------------------------------------------------------------------------------- 1 | lesson_name("Extending to several hosts") 2 | make_and_go_ws 3 | clear_ws 4 | run(`cp /tutorials/files/step-9/hosts hosts`) 5 | run(`cp -r /tutorials/files/step-9/files files`) 6 | run(`cp /tutorials/files/step-9/apache.yml apache.yml`) 7 | 8 | "`Adding another Webserver`" 9 | 10 | "We have one web server. Now we want two." 11 | 12 | "`Updating the inventory`" 13 | 14 | "Since we have big expectations, we'll add another web server and a load balancer we'll configure in the next lesson. But let's complete the inventory now." 15 | 16 | "Inspect the inventory file that we prepared for this lesson by running the following command:" 17 | 18 | "*cat hosts*" 19 | 20 | prompt { 21 | if success && command == "cat hosts" { 22 | expect ("cat hosts") 23 | "" 24 | break 25 | } 26 | } 27 | 28 | "`Building another web server`" 29 | 30 | "We didn't do all this work for nothing. Deploying another web server is dead simple" 31 | 32 | "All we had to do was remove `-l host1.example.org` from our command line. Remember `-l` is a switch that limits the playbook run on specific hosts. Now that we don't limit anymore, it will run on all hosts where the playbook is intended to run on (i.e. `web`)." 33 | 34 | "If we had other servers in group web but wanted to limit the playbook to a subset, we could have used, for instance: `-l firsthost:secondhost:....`" 35 | 36 | "Run the following command to execute our playbook:" 37 | 38 | "*ansible-playbook -i hosts apache.yml*" 39 | 40 | prompt { 41 | if success && command == "ansible-playbook -i hosts apache.yml" { 42 | expect ("ansible-playbook -i hosts apache.yml") 43 | "" 44 | break 45 | } 46 | } 47 | 48 | "Now that we have this nice farm of web servers. Let's turn it into a cluster by putting a load balancer in front of them in the next lesson." 49 | 50 | "This is the end of this lesson!" 51 | 52 | "Please press the \"`Enter`\" key to continue!" 53 | prompt { 54 | if command == "" { 55 | expect (" ") 56 | break 57 | } 58 | } -------------------------------------------------------------------------------- /tutorials/common.nutsh: -------------------------------------------------------------------------------- 1 | def exit_code { 2 | return(run("echo $?")) 3 | } 4 | 5 | def success { 6 | return(exit_code == "0") 7 | } 8 | 9 | def make_and_go_ws { 10 | run(`WS="/root/workspace"`) 11 | run(`mkdir -p $WS`) 12 | run(`cd "$WS"`) 13 | } 14 | 15 | def clear_ws { 16 | run(`WS="/root/workspace"`) 17 | run(`rm -rf $WS/*`) 18 | } 19 | 20 | def enter_to_continue { 21 | "Please press the \"`Enter`\" key to continue!" 22 | prompt { 23 | if command == "" { 24 | break 25 | } 26 | } 27 | } 28 | 29 | def common_mistakes { 30 | if command == "1s" { 31 | "Das ist ein kleines L, keine Eins! Probieren Sie's nochmal!" 32 | } 33 | if command == ".." { 34 | "`..` ist der Name des Verzeichnisses, Sie müssen noch dazusagen, was 35 | Sie damit machen möchten. Um \"hinzugehen\", schreiben Sie `cd` davor." 36 | } 37 | if command == "cd.." { 38 | "Da fehlt noch ein Leerzeichen zwischen `cd` und `..`!" 39 | } 40 | } 41 | 42 | def stayin(d) { 43 | if !(run("pwd") =~ run(`echo "`+d+`"`)) { 44 | run(`cd "`+d+`"`) 45 | "Bleiben Sie bitte erstmal in diesem Ordner." 46 | } 47 | } 48 | 49 | def stayinroot { 50 | stayin("$ROOT") 51 | } 52 | 53 | def runs(cmd) { 54 | run(cmd) 55 | return(exit_code == "0") 56 | } 57 | 58 | def test(condition) { 59 | return(runs("[[ "+condition+" ]]")) 60 | } 61 | 62 | def dir(d) { 63 | return(test("-d \""+d+"\"")) 64 | } 65 | 66 | def file(f) { 67 | return(test("-f \""+f+"\"")) 68 | } 69 | 70 | def help { 71 | return(command =~ `echo\s+(help|hilfe)`) 72 | } 73 | 74 | def ready { 75 | return(command =~ `echo\s+(fertig|ready)`) 76 | } 77 | 78 | def in(d) { 79 | return(run("pwd") == run(`echo "` + d + `"`)) 80 | } 81 | 82 | def user { 83 | return(run("whoami")) 84 | } 85 | 86 | -------------------------------------------------------------------------------- /tutorials/files/step-1-2/hosts: -------------------------------------------------------------------------------- 1 | host0.example.org ansible_host=172.21.0.2 ansible_user=root 2 | host1.example.org ansible_host=172.21.0.3 ansible_user=root 3 | host2.example.org ansible_host=172.21.0.4 ansible_user=root 4 | -------------------------------------------------------------------------------- /tutorials/files/step-10/apache.yml: -------------------------------------------------------------------------------- 1 | - hosts: web 2 | tasks: 3 | - name: Updates apt cache 4 | apt: update_cache=true 5 | 6 | - name: Installs necessary packages 7 | apt: pkg={{ item }} state=latest 8 | with_items: 9 | - apache2 10 | - libapache2-mod-php 11 | - git 12 | 13 | - name: Push future default virtual host configuration 14 | copy: src=files/awesome-app dest=/etc/apache2/sites-available/awesome-app.conf mode=0640 15 | 16 | - name: Activates our virtualhost 17 | command: a2ensite awesome-app 18 | 19 | - name: Check that our config is valid 20 | command: apache2ctl configtest 21 | register: result 22 | ignore_errors: True 23 | 24 | - name: Rolling back - Restoring old default virtualhost 25 | command: a2ensite 000-default 26 | when: result|failed 27 | 28 | - name: Rolling back - Removing our virtualhost 29 | command: a2dissite awesome-app 30 | when: result|failed 31 | 32 | - name: Rolling back - Ending playbook 33 | fail: msg="Configuration file is not valid. Please check that before re-running the playbook." 34 | when: result|failed 35 | 36 | - name: Deploy our awesome application 37 | git: repo=https://github.com/leucos/ansible-tuto-demosite.git dest=/var/www/awesome-app 38 | tags: deploy 39 | 40 | - name: Deactivates the default virtualhost 41 | command: a2dissite 000-default 42 | 43 | - name: Deactivates the default ssl virtualhost 44 | command: a2dissite default-ssl 45 | notify: 46 | - restart apache 47 | 48 | handlers: 49 | - name: restart apache 50 | service: name=apache2 state=restarted -------------------------------------------------------------------------------- /tutorials/files/step-10/files/awesome-app: -------------------------------------------------------------------------------- 1 | 2 | DocumentRoot /var/www/awesome-app 3 | 4 | Options -Indexes 5 | 6 | ErrorLog /var/log/apache2/error.log 7 | TransferLog /var/log/apache2/access.log 8 | 9 | -------------------------------------------------------------------------------- /tutorials/files/step-10/haproxy.yml: -------------------------------------------------------------------------------- 1 | - hosts: haproxy 2 | tasks: 3 | - name: Installs haproxy load balancer 4 | apt: pkg=haproxy state=present update_cache=yes 5 | 6 | - name: Pushes configuration 7 | template: src=templates/haproxy.cfg.j2 dest=/etc/haproxy/haproxy.cfg mode=0640 owner=root group=root 8 | notify: 9 | - restart haproxy 10 | 11 | - name: Sets default starting flag to 1 12 | lineinfile: dest=/etc/default/haproxy regexp="^ENABLED" line="ENABLED=1" 13 | notify: 14 | - restart haproxy 15 | 16 | handlers: 17 | - name: restart haproxy 18 | service: name=haproxy state=restarted 19 | -------------------------------------------------------------------------------- /tutorials/files/step-10/hosts: -------------------------------------------------------------------------------- 1 | [web] 2 | host1.example.org 3 | host2.example.org 4 | 5 | [haproxy] 6 | host0.example.org -------------------------------------------------------------------------------- /tutorials/files/step-10/templates/haproxy.cfg.j2: -------------------------------------------------------------------------------- 1 | global 2 | daemon 3 | maxconn 256 4 | 5 | defaults 6 | mode http 7 | timeout connect 5000ms 8 | timeout client 50000ms 9 | timeout server 50000ms 10 | 11 | listen cluster 12 | bind {{ ansible_eth0['ipv4']['address'] }}:80 13 | mode http 14 | stats enable 15 | balance roundrobin 16 | {% for backend in groups['web'] %} 17 | server {{ hostvars[backend]['ansible_hostname'] }} {{ hostvars[backend]['ansible_eth0']['ipv4']['address'] }} check port 80 18 | {% endfor %} 19 | option httpchk HEAD /index.php HTTP/1.0 -------------------------------------------------------------------------------- /tutorials/files/step-11/apache.yml: -------------------------------------------------------------------------------- 1 | - hosts: web 2 | tasks: 3 | - name: Updates apt cache 4 | apt: update_cache=true 5 | 6 | - name: Installs necessary packages 7 | apt: pkg={{ item }} state=latest 8 | with_items: 9 | - apache2 10 | - libapache2-mod-php 11 | - git 12 | 13 | - name: Push future default virtual host configuration 14 | copy: src=files/awesome-app dest=/etc/apache2/sites-available/awesome-app.conf mode=0640 15 | 16 | - name: Activates our virtualhost 17 | command: a2ensite awesome-app 18 | 19 | - name: Check that our config is valid 20 | command: apache2ctl configtest 21 | register: result 22 | ignore_errors: True 23 | 24 | - name: Rolling back - Restoring old default virtualhost 25 | command: a2ensite 000-default 26 | when: result|failed 27 | 28 | - name: Rolling back - Removing our virtualhost 29 | command: a2dissite awesome-app 30 | when: result|failed 31 | 32 | - name: Rolling back - Ending playbook 33 | fail: msg="Configuration file is not valid. Please check that before re-running the playbook." 34 | when: result|failed 35 | 36 | - name: Deploy our awesome application 37 | git: repo=https://github.com/leucos/ansible-tuto-demosite.git dest=/var/www/awesome-app 38 | tags: deploy 39 | 40 | - name: Deactivates the default virtualhost 41 | command: a2dissite 000-default 42 | 43 | - name: Deactivates the default ssl virtualhost 44 | command: a2dissite default-ssl 45 | notify: 46 | - restart apache 47 | 48 | handlers: 49 | - name: restart apache 50 | service: name=apache2 state=restarted 51 | -------------------------------------------------------------------------------- /tutorials/files/step-11/files/awesome-app: -------------------------------------------------------------------------------- 1 | 2 | DocumentRoot /var/www/awesome-app 3 | 4 | Options -Indexes 5 | 6 | ErrorLog /var/log/apache2/error.log 7 | TransferLog /var/log/apache2/access.log 8 | 9 | 10 | -------------------------------------------------------------------------------- /tutorials/files/step-11/group_vars/haproxy: -------------------------------------------------------------------------------- 1 | haproxy_check_interval: 3000 2 | -------------------------------------------------------------------------------- /tutorials/files/step-11/haproxy.yml: -------------------------------------------------------------------------------- 1 | - hosts: web 2 | 3 | - hosts: haproxy 4 | tasks: 5 | - name: Installs haproxy load balancer 6 | apt: pkg=haproxy state=present update_cache=yes 7 | 8 | - name: Pushes configuration 9 | template: src=templates/haproxy.cfg.j2 dest=/etc/haproxy/haproxy.cfg mode=0640 owner=root group=root 10 | notify: 11 | - restart haproxy 12 | 13 | - name: Sets default starting flag to 1 14 | lineinfile: dest=/etc/default/haproxy regexp="^ENABLED" line="ENABLED=1" 15 | notify: 16 | - restart haproxy 17 | 18 | handlers: 19 | - name: restart haproxy 20 | service: name=haproxy state=restarted 21 | -------------------------------------------------------------------------------- /tutorials/files/step-11/host_vars/host0.example.org: -------------------------------------------------------------------------------- 1 | haproxy_backend_weight: 150 2 | haproxy_stats_socket: /tmp/sock 3 | 4 | -------------------------------------------------------------------------------- /tutorials/files/step-11/host_vars/host1.example.org: -------------------------------------------------------------------------------- 1 | haproxy_backend_weight: 100 2 | -------------------------------------------------------------------------------- /tutorials/files/step-11/host_vars/host2.example.org: -------------------------------------------------------------------------------- 1 | haproxy_backend_weight: 150 2 | -------------------------------------------------------------------------------- /tutorials/files/step-11/hosts: -------------------------------------------------------------------------------- 1 | [web] 2 | host1.example.org 3 | host2.example.org 4 | 5 | [haproxy] 6 | host0.example.org 7 | -------------------------------------------------------------------------------- /tutorials/files/step-11/templates/haproxy.cfg.j2: -------------------------------------------------------------------------------- 1 | global 2 | daemon 3 | maxconn 256 4 | {% if haproxy_stats_socket %} 5 | stats socket {{ haproxy_stats_socket }} 6 | {% endif %} 7 | 8 | defaults 9 | mode http 10 | timeout connect 5000ms 11 | timeout client 50000ms 12 | timeout server 50000ms 13 | 14 | listen cluster 15 | bind {{ ansible_eth0['ipv4']['address'] }}:80 16 | mode http 17 | stats enable 18 | balance roundrobin 19 | {% for backend in groups['web'] %} 20 | server {{ hostvars[backend]['ansible_hostname'] }} {{ hostvars[backend]['ansible_eth0']['ipv4']['address'] }} check inter {{ haproxy_check_interval }} weight {{ hostvars[backend]['haproxy_backend_weight'] }} port 80 21 | {% endfor %} 22 | option httpchk HEAD /index.php HTTP/1.0 23 | -------------------------------------------------------------------------------- /tutorials/files/step-12/apache_handlers.yml: -------------------------------------------------------------------------------- 1 | - name: restart apache 2 | service: name=apache2 state=restarted -------------------------------------------------------------------------------- /tutorials/files/step-12/apache_tasks.yml: -------------------------------------------------------------------------------- 1 | - name: Updates apt cache 2 | apt: update_cache=true 3 | 4 | - name: Installs necessary packages 5 | apt: pkg={{ item }} state=latest 6 | with_items: 7 | - apache2 8 | - libapache2-mod-php 9 | - git 10 | 11 | - name: Push future default virtual host configuration 12 | copy: src=awesome-app dest=/etc/apache2/sites-available/awesome-app.conf mode=0640 13 | 14 | - name: Activates our virtualhost 15 | command: a2ensite awesome-app 16 | 17 | - name: Check that our config is valid 18 | command: apache2ctl configtest 19 | register: result 20 | ignore_errors: True 21 | 22 | - name: Rolling back - Restoring old default virtualhost 23 | command: a2ensite 000-default 24 | when: result|failed 25 | 26 | - name: Rolling back - Removing our virtualhost 27 | command: a2dissite awesome-app 28 | when: result|failed 29 | 30 | - name: Rolling back - Ending playbook 31 | fail: msg="Configuration file is not valid. Please check that before re-running the playbook." 32 | when: result|failed 33 | 34 | - name: Deploy our awesome application 35 | git: repo=https://github.com/leucos/ansible-tuto-demosite.git dest=/var/www/awesome-app 36 | tags: deploy 37 | 38 | - name: Deactivates the default virtualhost 39 | command: a2dissite 000-default 40 | 41 | - name: Deactivates the default ssl virtualhost 42 | command: a2dissite default-ssl 43 | notify: 44 | - restart apache -------------------------------------------------------------------------------- /tutorials/files/step-12/files/awesome-app: -------------------------------------------------------------------------------- 1 | 2 | DocumentRoot /var/www/awesome-app 3 | 4 | Options -Indexes 5 | 6 | ErrorLog /var/log/apache2/error.log 7 | TransferLog /var/log/apache2/access.log 8 | 9 | 10 | -------------------------------------------------------------------------------- /tutorials/files/step-12/group_vars/haproxy: -------------------------------------------------------------------------------- 1 | haproxy_check_interval: 3000 2 | -------------------------------------------------------------------------------- /tutorials/files/step-12/haproxy_handlers.yml: -------------------------------------------------------------------------------- 1 | - name: restart haproxy 2 | service: name=haproxy state=restarted -------------------------------------------------------------------------------- /tutorials/files/step-12/haproxy_tasks.yml: -------------------------------------------------------------------------------- 1 | - name: Installs haproxy load balancer 2 | apt: pkg=haproxy state=present update_cache=yes 3 | 4 | - name: Pushes configuration 5 | template: src=haproxy.cfg.j2 dest=/etc/haproxy/haproxy.cfg mode=0640 owner=root group=root 6 | notify: 7 | - restart haproxy 8 | 9 | - name: Sets default starting flag to 1 10 | lineinfile: dest=/etc/default/haproxy regexp="^ENABLED" line="ENABLED=1" 11 | notify: 12 | - restart haproxy 13 | -------------------------------------------------------------------------------- /tutorials/files/step-12/host_vars/host0.example.org: -------------------------------------------------------------------------------- 1 | haproxy_backend_weight: 150 2 | haproxy_stats_socket: /tmp/sock 3 | 4 | -------------------------------------------------------------------------------- /tutorials/files/step-12/host_vars/host1.example.org: -------------------------------------------------------------------------------- 1 | haproxy_backend_weight: 100 2 | -------------------------------------------------------------------------------- /tutorials/files/step-12/host_vars/host2.example.org: -------------------------------------------------------------------------------- 1 | haproxy_backend_weight: 150 2 | -------------------------------------------------------------------------------- /tutorials/files/step-12/hosts: -------------------------------------------------------------------------------- 1 | [web] 2 | host1.example.org 3 | host2.example.org 4 | 5 | [haproxy] 6 | host0.example.org 7 | -------------------------------------------------------------------------------- /tutorials/files/step-12/site.yml: -------------------------------------------------------------------------------- 1 | - hosts: web 2 | roles: 3 | - { role: apache } 4 | 5 | - hosts: haproxy 6 | roles: 7 | - { role: haproxy } 8 | 9 | -------------------------------------------------------------------------------- /tutorials/files/step-12/templates/haproxy.cfg.j2: -------------------------------------------------------------------------------- 1 | global 2 | daemon 3 | maxconn 256 4 | {% if haproxy_stats_socket %} 5 | stats socket {{ haproxy_stats_socket }} 6 | {% endif %} 7 | 8 | defaults 9 | mode http 10 | timeout connect 5000ms 11 | timeout client 50000ms 12 | timeout server 50000ms 13 | 14 | listen cluster 15 | bind {{ ansible_eth0['ipv4']['address'] }}:80 16 | mode http 17 | stats enable 18 | balance roundrobin 19 | {% for backend in groups['web'] %} 20 | server {{ hostvars[backend]['ansible_hostname'] }} {{ hostvars[backend]['ansible_eth0']['ipv4']['address'] }} check inter {{ haproxy_check_interval }} weight {{ hostvars[backend]['haproxy_backend_weight'] }} port 80 21 | {% endfor %} 22 | option httpchk HEAD /index.php HTTP/1.0 23 | -------------------------------------------------------------------------------- /tutorials/files/step-13/hosts: -------------------------------------------------------------------------------- 1 | [jenkins] 2 | host0.example.org -------------------------------------------------------------------------------- /tutorials/files/step-13/jenkins.yaml: -------------------------------------------------------------------------------- 1 | - hosts: jenkins 2 | vars: 3 | jenkins_hostname: host0.example.org 4 | roles: 5 | - role: geerlingguy.java 6 | - role: geerlingguy.jenkins 7 | become: true -------------------------------------------------------------------------------- /tutorials/files/step-4/apache.yml: -------------------------------------------------------------------------------- 1 | - hosts: web 2 | tasks: 3 | - name: Installs apache web server 4 | apt: pkg=apache2 state=present update_cache=true 5 | -------------------------------------------------------------------------------- /tutorials/files/step-4/hosts: -------------------------------------------------------------------------------- 1 | [web] 2 | host1.example.org -------------------------------------------------------------------------------- /tutorials/files/step-5/apache.yml: -------------------------------------------------------------------------------- 1 | - hosts: web 2 | tasks: 3 | - name: Installs apache web server 4 | apt: pkg=apache2 state=present update_cache=true 5 | 6 | - name: Push default virtual host configuration 7 | copy: src=files/awesome-app dest=/etc/apache2/sites-available/awesome-app.conf mode=0640 8 | 9 | - name: Disable the default virtualhost 10 | file: dest=/etc/apache2/sites-enabled/000-default.conf state=absent 11 | notify: 12 | - restart apache 13 | 14 | - name: Disable the default ssl virtualhost 15 | file: dest=/etc/apache2/sites-enabled/default-ssl.conf state=absent 16 | notify: 17 | - restart apache 18 | 19 | - name: Activates our virtualhost 20 | file: src=/etc/apache2/sites-available/awesome-app.conf dest=/etc/apache2/sites-enabled/awesome-app.conf state=link 21 | notify: 22 | - restart apache 23 | 24 | handlers: 25 | - name: restart apache 26 | service: name=apache2 state=restarted 27 | -------------------------------------------------------------------------------- /tutorials/files/step-5/files/awesome-app: -------------------------------------------------------------------------------- 1 | 2 | DocumentRoot /var/www/awesome-app 3 | 4 | Options -Indexes 5 | 6 | ErrorLog /var/log/apache2/error.log 7 | TransferLog /var/log/apache2/access.log 8 | 9 | -------------------------------------------------------------------------------- /tutorials/files/step-5/hosts: -------------------------------------------------------------------------------- 1 | [web] 2 | host1.example.org -------------------------------------------------------------------------------- /tutorials/files/step-6/apache.yml: -------------------------------------------------------------------------------- 1 | - hosts: web 2 | tasks: 3 | - name: Installs apache web server 4 | apt: pkg=apache2 state=present update_cache=true 5 | 6 | - name: Push future default virtual host configuration 7 | copy: src=files/awesome-app dest=/etc/apache2/sites-available/awesome-app.conf mode=0640 8 | 9 | - name: Activates our virtualhost 10 | command: a2ensite awesome-app 11 | 12 | - name: Check that our config is valid 13 | command: apache2ctl configtest 14 | 15 | - name: Deactivates the default virtualhost 16 | command: a2dissite 000-default 17 | 18 | - name: Deactivates the default ssl virtualhost 19 | command: a2dissite default-ssl 20 | notify: 21 | - restart apache 22 | 23 | handlers: 24 | - name: restart apache 25 | service: name=apache2 state=restarted 26 | -------------------------------------------------------------------------------- /tutorials/files/step-6/files/awesome-app: -------------------------------------------------------------------------------- 1 | 2 | RocumentDoot /var/www/awesome-app 3 | 4 | Options -Indexes 5 | 6 | ErrorLog /var/log/apache2/error.log 7 | TransferLog /var/log/apache2/access.log 8 | 9 | -------------------------------------------------------------------------------- /tutorials/files/step-6/hosts: -------------------------------------------------------------------------------- 1 | [web] 2 | host1.example.org -------------------------------------------------------------------------------- /tutorials/files/step-7/apache.yml: -------------------------------------------------------------------------------- 1 | - hosts: web 2 | tasks: 3 | - name: Installs apache web server 4 | apt: pkg=apache2 state=present update_cache=true 5 | 6 | - name: Push future default virtual host configuration 7 | copy: src=files/awesome-app dest=/etc/apache2/sites-available/awesome-app.conf mode=0640 8 | 9 | - name: Deactivates the default virtualhost 10 | command: a2dissite 000-default 11 | 12 | - name: Activates our virtualhost 13 | command: a2ensite awesome-app 14 | 15 | - name: Check that our config is valid 16 | command: apache2ctl configtest 17 | register: result 18 | ignore_errors: True 19 | 20 | - name: Rolling back - Restoring old default virtualhost 21 | command: a2ensite 000-default 22 | when: result|failed 23 | 24 | - name: Rolling back - Removing our virtualhost 25 | command: a2dissite awesome-app 26 | when: result|failed 27 | 28 | - name: Rolling back - Ending playbook 29 | fail: msg="Configuration file is not valid. Please check that before re-running the playbook." 30 | when: result|failed 31 | 32 | - name: Deactivates the default ssl virtualhost 33 | command: a2dissite default-ssl 34 | 35 | notify: 36 | - restart apache 37 | 38 | handlers: 39 | - name: restart apache 40 | service: name=apache2 state=restarted 41 | -------------------------------------------------------------------------------- /tutorials/files/step-7/files/awesome-app: -------------------------------------------------------------------------------- 1 | 2 | RocumentDoot /var/www/awesome-app 3 | 4 | Options -Indexes 5 | 6 | ErrorLog /var/log/apache2/error.log 7 | TransferLog /var/log/apache2/access.log 8 | 9 | -------------------------------------------------------------------------------- /tutorials/files/step-7/hosts: -------------------------------------------------------------------------------- 1 | [web] 2 | host1.example.org -------------------------------------------------------------------------------- /tutorials/files/step-8/apache.yml: -------------------------------------------------------------------------------- 1 | - hosts: web 2 | tasks: 3 | - name: Updates apt cache 4 | apt: update_cache=true 5 | 6 | - name: Installs necessary packages 7 | apt: pkg={{ item }} state=latest 8 | with_items: 9 | - apache2 10 | - libapache2-mod-php 11 | - git 12 | 13 | - name: Push future default virtual host configuration 14 | copy: src=files/awesome-app dest=/etc/apache2/sites-available/awesome-app.conf mode=0640 15 | 16 | - name: Activates our virtualhost 17 | command: a2ensite awesome-app 18 | 19 | - name: Check that our config is valid 20 | command: apache2ctl configtest 21 | register: result 22 | ignore_errors: True 23 | 24 | - name: Rolling back - Restoring old default virtualhost 25 | command: a2ensite 000-default 26 | when: result|failed 27 | 28 | - name: Rolling back - Removing our virtualhost 29 | command: a2dissite awesome-app 30 | when: result|failed 31 | 32 | - name: Rolling back - Ending playbook 33 | fail: msg="Configuration file is not valid. Please check that before re-running the playbook." 34 | when: result|failed 35 | 36 | - name: Deploy our awesome application 37 | git: repo=https://github.com/leucos/ansible-tuto-demosite.git dest=/var/www/awesome-app 38 | tags: deploy 39 | 40 | - name: Deactivates the default virtualhost 41 | command: a2dissite 000-default 42 | 43 | - name: Deactivates the default ssl virtualhost 44 | command: a2dissite default-ssl 45 | notify: 46 | - restart apache 47 | 48 | handlers: 49 | - name: restart apache 50 | service: name=apache2 state=restarted -------------------------------------------------------------------------------- /tutorials/files/step-8/files/awesome-app: -------------------------------------------------------------------------------- 1 | 2 | DocumentRoot /var/www/awesome-app 3 | 4 | Options -Indexes 5 | 6 | ErrorLog /var/log/apache2/error.log 7 | TransferLog /var/log/apache2/access.log 8 | 9 | -------------------------------------------------------------------------------- /tutorials/files/step-8/hosts: -------------------------------------------------------------------------------- 1 | [web] 2 | host1.example.org -------------------------------------------------------------------------------- /tutorials/files/step-9/apache.yml: -------------------------------------------------------------------------------- 1 | - hosts: web 2 | tasks: 3 | - name: Updates apt cache 4 | apt: update_cache=true 5 | 6 | - name: Installs necessary packages 7 | apt: pkg={{ item }} state=latest 8 | with_items: 9 | - apache2 10 | - libapache2-mod-php 11 | - git 12 | 13 | - name: Push future default virtual host configuration 14 | copy: src=files/awesome-app dest=/etc/apache2/sites-available/awesome-app.conf mode=0640 15 | 16 | - name: Activates our virtualhost 17 | command: a2ensite awesome-app 18 | 19 | - name: Check that our config is valid 20 | command: apache2ctl configtest 21 | register: result 22 | ignore_errors: True 23 | 24 | - name: Rolling back - Restoring old default virtualhost 25 | command: a2ensite 000-default 26 | when: result|failed 27 | 28 | - name: Rolling back - Removing our virtualhost 29 | command: a2dissite awesome-app 30 | when: result|failed 31 | 32 | - name: Rolling back - Ending playbook 33 | fail: msg="Configuration file is not valid. Please check that before re-running the playbook." 34 | when: result|failed 35 | 36 | - name: Deploy our awesome application 37 | git: repo=https://github.com/leucos/ansible-tuto-demosite.git dest=/var/www/awesome-app 38 | tags: deploy 39 | 40 | - name: Deactivates the default virtualhost 41 | command: a2dissite 000-default 42 | 43 | - name: Deactivates the default ssl virtualhost 44 | command: a2dissite default-ssl 45 | notify: 46 | - restart apache 47 | 48 | handlers: 49 | - name: restart apache 50 | service: name=apache2 state=restarted -------------------------------------------------------------------------------- /tutorials/files/step-9/files/awesome-app: -------------------------------------------------------------------------------- 1 | 2 | DocumentRoot /var/www/awesome-app 3 | 4 | Options -Indexes 5 | 6 | ErrorLog /var/log/apache2/error.log 7 | TransferLog /var/log/apache2/access.log 8 | 9 | -------------------------------------------------------------------------------- /tutorials/files/step-9/hosts: -------------------------------------------------------------------------------- 1 | [web] 2 | host1.example.org 3 | host2.example.org 4 | 5 | [haproxy] 6 | host0.example.org -------------------------------------------------------------------------------- /tutorials/info.yaml: -------------------------------------------------------------------------------- 1 | name: "Ansible Interactive Tutorial" 2 | target: bash 3 | version: 1 4 | days: 5 | 2000-01-01: 15 6 | --------------------------------------------------------------------------------