Ce lab poursuit uniquement objectif pédagogique, intellectuel et démonstratif qui démarre d’une “solution d’infrastructure” parmi beaucoup d’autres possiblités. Aussi, une mise en production exigerait une autre approche probablement orientée sur l’application et les services qu’elle rendra aux clients.
Source originale : https://github.com/geerlingguy/ansible-for-devops/tree/master/lamp-infrastructure.
Livres de jeu adaptés : https://github.com/goffinet/ansible-for-devops/, dans le dossier lamp-infrastructure
.
Voici l’architecture de départ constituée de six serveurs Centos 7 :
On propose dans ce lab de provisionner et de configurer avec Ansible cette infrastructure LAMP uniquement basée sur le service Amazon AWS EC2.
Cette architecture offre plusieurs niveaux de mise en cache et une haute disponibilité/redondance à presque tous les niveaux, bien que pour simplifier les choses, il existe des points de défaillance uniques. Toutes les données persistantes stockées dans la base de données sont stockées dans un serveur esclave, et l’une des parties les plus lentes et les plus contraignantes de la pile (les serveurs web, dans ce cas, exécutant une application PHP via Apache) est facile à dimensionner horizontalement, derrière Varnish, qui agit comme couche de “cache” (reverse proxy) et “load balancer”.
Pour les besoins de la démonstration, la mise en cache de Varnish est complètement désactivée, vous pouvez donc rafraîchir et voir les deux serveurs Apache (avec la mise en cache activée, Varnish mettra en cache la première réponse puis continuera à la servir sans toucher au reste de la pile). Vous pouvez voir la configuration de mise en cache et d’équilibrage de charge dans playbooks/varnish/templates/default.vcl
).
Aussi, la page de l’application expose les adresses IP des serveurs de base données (à des fins de démonstration).
On se posera immédiatement la question de la sécurité quant à l’accès aux adresses IPv4 publiques ou privées, sur l’accès aux ports utilisés, du support de l’HTTPS. Certaines de ces questions pourraient trouver dans ce document des pistes de réfléxion pour des réponses avec de nombreuses alternatives en remplacement ou en complément de ce qui est proposé ici.
Notons enfin que l’approvisionnement et la configuration de l’architecture sécurisée avec son application pourrait être représentée en seul modèle “CloudFormation”.
Il est préférable de disposer d’une station de gestion avec Ansible installé, de préférence au plus proche des cibles, chez AWS.
Par exemple, ce script Bash pourrait vous aider à démarrer. Attention, les clés montrées ici sont factices, veuillez remplacer la valeur des variables AWS_ACCESS_KEY_ID
et AWS_SECRET_ACCESS_KEY
.
#!/bin/bash
export DEBIAN_FRONTEND="noninteractive"
sudo apt update
sudo apt-get upgrade --yes --force-yes -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold"
sudo apt -y install python-pip
pip install boto boto3 ansible --upgrade --user
export AWS_ACCESS_KEY_ID="XXXXXXXXXXXXXXXXXXXX"
export AWS_SECRET_ACCESS_KEY="nqkh7zp7X/JSVfM3DFKQRNbw5x5D1Qzq0xeLKl9F"
export AWS_DEFAULT_REGION="eu-west-3"
export ANSIBLE_HOST_KEY_CHECKING=False
echo "export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX" >> ~/.profile
echo "export AWS_SECRET_ACCESS_KEY=nqkh7zp7X/JSVfM3DFKQRNbw5x5D1Qzq0xeLKl9F" >> ~/.profile
echo "export AWS_DEFAULT_REGION=eu-west-3" >> ~/.profile
echo "export ANSIBLE_HOST_KEY_CHECKING=False" >> ~/.profile
ssh-keygen -b 4096 -t rsa -f ~/.ssh/id_rsa -q -N ""
Il est préférable de récupérer le livre de jeu de ce lab :
git clone https://github.com/goffinet/ansible-for-devops
cd ansible-for-devops/lamp-infrastructure
Le livre de jeu “Infrastructure LAMP” peut être déployé avec :
Pour faire fonctionner ce lab sur AWS EC2, le livre de jeu ansible-for-devops/lamp-infrastructure/provision.yml
a été adapté par rapport à son original, déjà très fonctionnel pour que les tâches d’approvisionnement sur AWS soient activées.
---
# Uncomment the provisioner you would like to use. For for Vagrant provisioning,
# use `vagrant up` instead of this playbook.
#- provisioners/digitalocean.yml
- provisioners/aws.yml
# Configure provisioned servers.
- configure.yml
Ensuite, il a été nécessaire de paraméter le livre de jeu ansible-for-devops/lamp-infrastructure/provisioners/aws.yml
comme ceci :
---
- hosts: localhost
connection: local
gather_facts: false
vars:
ssh_key: "a4d-key"
region: "eu-west-1" # Dublin
image: "ami-0ff760d16d9497662" #Centos7
instances:
- name: a4d.lamp.varnish
group: "lamp_varnish"
security_group: ["default", "a4d_lamp_http"]
type: "t2.micro"
image: "{{ image }}"
region: "{{ region }}"
- name: a4d.lamp.www.1
group: "lamp_www"
security_group: ["default", "a4d_lamp_http"]
type: "t2.micro"
image: "{{ image }}"
region: "{{ region }}"
- name: a4d.lamp.www.2
group: "lamp_www"
security_group: ["default", "a4d_lamp_http"]
type: "t2.micro"
image: "{{ image }}"
region: "{{ region }}"
# - name: a4d.lamp.www.3
# group: "lamp_www"
# security_group: ["default", "a4d_lamp_http"]
# type: "t2.micro"
# image: "{{ image }}"
# region: "{{ region }}"
- name: a4d.lamp.db.1
group: "lamp_db"
security_group: ["default", "a4d_lamp_db"]
type: t2.micro
image: "{{ image }}"
region: "{{ region }}"
- name: a4d.lamp.db.2
group: "lamp_db"
security_group: ["default", "a4d_lamp_db"]
type: "t2.micro"
image: "{{ image }}"
region: "{{ region }}"
- name: a4d.lamp.memcached
group: "lamp_memcached"
security_group: ["default", "a4d_lamp_memcached"]
type: "t2.micro"
image: "{{ image }}"
region: "{{ region }}"
security_groups:
- name: a4d_lamp_http
rules:
- proto: tcp
from_port: 80
to_port: 80
cidr_ip: 0.0.0.0/0
- proto: tcp
from_port: 22
to_port: 22
cidr_ip: 0.0.0.0/0
rules_egress: []
- name: a4d_lamp_db
rules:
- proto: tcp
from_port: 3306
to_port: 3306
cidr_ip: 0.0.0.0/0
- proto: tcp
from_port: 22
to_port: 22
cidr_ip: 0.0.0.0/0
rules_egress: []
- name: a4d_lamp_memcached
rules:
- proto: tcp
from_port: 11211
to_port: 11211
cidr_ip: 0.0.0.0/0
- proto: tcp
from_port: 22
to_port: 22
cidr_ip: 0.0.0.0/0
rules_egress: []
pre_tasks:
- name: "Ensure key pair is present"
ec2_key:
name: "{{ ssh_key | default('lamp_aws') }}"
key_material: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
region: "{{ region | default('eu-west-1') }}" # Dublin
tasks:
- name: Configure EC2 Security Groups.
ec2_group:
name: "{{ item.name }}"
description: Example EC2 security group for A4D.
region: "{{ item.region | default('eu-west-1') }}" # Dublin
state: present
rules: "{{ item.rules }}"
rules_egress: "{{ item.rules_egress }}"
with_items: "{{ security_groups }}"
- name: Provision EC2 instances.
ec2:
key_name: "{{ ssh_key | default('lamp_aws') }}"
instance_tags:
inventory_group: "{{ item.group | default('') }}"
inventory_host: "{{ item.name | default('') }}"
group: "{{ item.security_group | default('') }}"
instance_type: "{{ item.type | default('t2.micro')}}" # Free with agreement
image: "{{ item.image | default('ami-0ff760d16d9497662') }}" # CentOS 7
region: "{{ item.region | default('eu-west-1') }}" # Dublin
wait: yes
wait_timeout: 500
exact_count: 1
count_tag:
inventory_group: "{{ item.group | default('') }}"
inventory_host: "{{ item.name | default('') }}"
register: created_instances
with_items: "{{ instances }}"
- name: Add EC2 instances to inventory groups.
add_host:
name: "{{ item.1.tagged_instances.0.public_ip }}"
#name: "{{ item.1.tagged_instances.0.private_ip }}"
groups: "aws,{{ item.1.item.group }},{{ item.1.item.name }}"
# You can dynamically add inventory variables per-host.
ansible_ssh_user: centos
host_key_checking: false
mysql_replication_role: >
{{ 'master' if (item.1.item.name == 'a4d.lamp.db.1')
else 'slave' }}
mysql_server_id: "{{ item.0 }}"
when: item.1.instances is defined
with_indexed_items: "{{ created_instances.results }}"
# Run some general configuration on all AWS hosts.
- hosts: aws
gather_facts: false
tasks:
- name: Wait for port 22 to become available.
local_action: "wait_for port=22 host={{ inventory_hostname }}"
- name: Set selinux into 'permissive' mode.
selinux: policy=targeted state=permissive
become: yes
Il est adapté de la manière suivante :
ansible_ssh_user: centos
est adapté à la tâche “Add EC2 instances to inventory groups”.pre_tasks
s’assure que la clé nommée existe “lamp_aws”, dans le cas contraire votre clé publique par défaut sera importée sous ce nom.instances.[].type
, instances.[].image
, instances.[].region
pour une image dans la région “eu-west-3” (Paris) voir https://wiki.centos.org/Cloud/AWS.On sera attentif à la manière dont l’inventaire est nourri dynamiquement :
- name: Add EC2 instances to inventory groups.
add_host:
name: "{{ item.1.tagged_instances.0.public_ip }}"
groups: "aws,{{ item.1.item.group }},{{ item.1.item.name }}"
# You can dynamically add inventory variables per-host.
ansible_ssh_user: centos
host_key_checking: false
mysql_replication_role: >
{{ 'master' if (item.1.item.name == 'a4d.lamp.db.1')
else 'slave' }}
mysql_server_id: "{{ item.0 }}"
when: item.1.instances is defined
with_indexed_items: "{{ created_instances.results }}"
Enfin afin de rendre ce lab unique, il sera aussi nécessaire d’adapter le terme a4d
qui identifie le projet dans le livre de jeu. Voici la commande qui permet de remplacer cette occurence a4d
par lab0
à partir du dossier courant ansible-for-devops/lamp-infrastructure
:
find ./ -type f -print0 | xargs -0 sed -i 's/a4d/lab0/g'
Il est nécessaire d’importer tous les rôles du livre de jeu à partir du fichier requirements.yml
:
ansible-galaxy install -r requirements.yml
Et puis on peut lancer le livre de jeu avec ansible-playbook
.
ansible-playbook provision.yml
Il est nécessaire d’être en mesure de désigner ses éléments d’infrastructure (fichier inventories/aws/inventory
) :
# Varnish
[lamp_varnish]
[a4d.lamp.varnish]
[lamp_varnish:children]
#a4d.lamp.varnish
# Apache
[lamp_www]
[a4d.lamp.www.1]
[a4d.lamp.www.2]
[lamp_www:children]
a4d.lamp.www.1
a4d.lamp.www.2
# MySQL
[lamp_db]
[a4d.lamp.db.1]
[a4d.lamp.db.2]
[lamp_db:children]
#a4d.lamp.db.1
#a4d.lamp.db.2
# Memcached
[lamp_memcached]
[a4d.lamp.memcached]
[lamp_memcached:children]
#a4d.lamp.memcached
On est invité à lire les livres de jeux suivants :
playbooks/varnish/main.yml
, playbooks/varnish/vars.yml
et playbooks/varnish/templates/default.vcl.j2
playbooks/www/main.yml
, playbooks/www/vars.yml
et playbooks/www/templates/index.php.j2
playbooks/db/main.yml
et playbooks/db/vars.yml
playbooks/memcached/main.yml
et playbooks/memcached/vars.yml
Il est essentiel de porter attention :
hosts:
)vars.yml
Les machines au moment de leur création sont identifiées par leurs “tags” qui correspondent à des groupes d’inventaire ou des hôtes d’inventaire dynamique. Si on fait commencer les livres de jeu ultérieurs par un jeu - provisioners/aws.yml
, il récupérera les balises des machines pour en faire un inventaire dynamique.
Pour des tâches ultérieures sur l’infrastructure, il serait probablement plus judicieux d’utiliser le script d’inventaire ec2.py
inclus :
inventories/aws/ec2.py --list
C’est le moment d’exploiter le programme ansible-inventory
avec le script d’inventaire ec2.py
:
ansible-inventory --inventory-file inventories/aws/ec2.py --graph
ansible-inventory -i inventories/aws/ec2.py --list --yaml
ansible -m ping -i inventories/aws/ec2.py -u centos tag_inventory_group_lamp_db
inventories/aws/ec2.py --list | jq .tag_inventory_host_a4d_lamp_varnish
Mise en variable de l’URL du serveur Varnish :
varnish="http://$(inventories/aws/ec2.py --list | jq -r '.tag_inventory_host_a4d_lamp_varnish[]')"
curl $varnish
<!DOCTYPE html>
<html>
<head>
<title>Host 52.215.204.61</title>
<style>* { font-family: Helvetica, Arial, sans-serif }</style>
</head>
<body>
<h1>Host 52.215.204.61</h1>
<p>MySQL Connection (34.249.244.102):
<span style="color: green;">PASS</span> (slave)</p>
<p>MySQL Connection (34.252.226.132):
<span style="color: green;">PASS</span> (slave)</p>
<p>Memcached Connection: <span style="color: green;">PASS</span></p>
</body>
</html>
while true ; do curl -s $varnish | grep 'h1' ; done
<h1>Host 52.215.204.61</h1>
<h1>Host 52.215.204.61</h1>
<h1>Host 34.253.203.217</h1>
<h1>Host 52.215.204.61</h1>
<h1>Host 34.253.203.217</h1>
<h1>Host 52.215.204.61</h1>
<h1>Host 52.215.204.61</h1>
<h1>Host 52.215.204.61</h1>
Comment faire évoluer horizontalement des serveurs Web ou des serveurs de base de données ?
On ajoutant un serveur dans la liste de la variable instances
, par exemple :
- name: a4d.lamp.www.3
group: "lamp_www"
security_group: ["default", "a4d_lamp_http"]
type: "t2.micro"
image: "{{ image }}"
region: "{{ region }}"
Il sera aussi nécessaire d’adapter le fichier de variables lamp-infrastructure/playbooks/memcached/vars.yml
du livre de jeu memcached
qui dispose de règles iptables vers chaque membre défini du groupe lamp_www
, de cette manière :
---
firewall_allowed_tcp_ports:
- "22"
firewall_additional_rules:
- "iptables -A INPUT -p tcp -d {{ ansible_default_ipv4.address }} --dport 11211 -s {{ groups['lamp_www'][0] }} -j ACCEPT"
- "iptables -A INPUT -p tcp -d {{ ansible_default_ipv4.address }} --dport 11211 -s {{ groups['lamp_www'][1] }} -j ACCEPT"
- "iptables -A INPUT -p tcp -d {{ ansible_default_ipv4.address }} --dport 11211 -s {{ groups['lamp_www'][2] }} -j ACCEPT"
memcached_listen_ip: "{{ ansible_default_ipv4.address }}"
…
Les adresses IP publiques sont utilisées pour toutes les communications inter-instances (par exemple, communication PHP vers MySQL/Memcached, réplication maître/esclave MySQL). Pour une meilleure sécurité et potentiellement une petite amélioration des performances, vous pouvez utiliser la variable private_ip
d’instances pour cette communication inter-instances au lieu de public_ip
dans le livre de jeu provisioners/aws.yml
.
Il alors sera nécessaire de placer le contrôlleur dans le même VPC que les hôtes cibles à créer et à configurer.1
sed -i 's/public_ip/private_ip/' provisioners/aws.yml
Il pourrait être judicieux d’adapter la liste des règles de trafic entrant sur les hôtes autorisés uniquement (la clé cidr_ip:
) dans les variables de définition des groupes de sécurité. Plus encore, on peut aussi proposer de n’autoriser l’accès SSH aux serveurs uniquement à partir de la station de gestion. A cet égard, une installation du logiciel faib2ban pourrait trouver son utilité ici …
Alors une adaptation des règles de pare-feu sur base de l’origine du trafic en autorisation sur les bons ports serait-elle une solution acceptable ?
Outre le trafic de gestion SSH (TCP22), on trouve quatre flux entrants à controler dans l’infrastructure :
Il s’agit d’adapter les règles de pare-feu iptables.
Pour le group lamp_db
, on autorise le trafic TCP 3306 venant des groupes d’hôtes lamp_www
et lamp_memcached
dans le fichier lamp-infrastructure/playbooks/db/vars.yml
:
firewall_allowed_tcp_ports:
- "22"
# - "3306"
firewall_additional_rules:
- "iptables -A INPUT -p tcp -d {{ ansible_default_ipv4.address }} --dport 3306 -s {{ groups['lamp_memcached'][0] }} -j ACCEPT"
- "iptables -A INPUT -p tcp -d {{ ansible_default_ipv4.address }} --dport 3306 -s {{ groups['lamp_www'][0] }} -j ACCEPT"
- "iptables -A INPUT -p tcp -d {{ ansible_default_ipv4.address }} --dport 3306 -s {{ groups['lamp_www'][1] }} -j ACCEPT"
# - "iptables -A INPUT -p tcp -d {{ ansible_default_ipv4.address }} --dport 3306 -s {{ groups['lamp_www'][2] }} -j ACCEPT"
Pour le groupe lamp_www
, on autorise le trafic TCP 80 venant du groupe d’hôtes lamp_varnish
dans le fichier lamp-infrastructure/playbooks/www/vars.yml
:
firewall_allowed_tcp_ports:
- "22"
# - "3306"
firewall_additional_rules:
- "iptables -A INPUT -p tcp -d {{ ansible_default_ipv4.address }} --dport 80 -s {{ groups['lamp_varnish'][0] }} -j ACCEPT"
Le serveur cache / load balancer HTTP Varnish accepte pour l’instant uniquement le trafic sur son port TCP 80 venant de partout.
Acteurs d’un scénario Reverse Proxy HTTPS :
Comment Placer un Proxy qui sert les requêtes en HTTPS en HTTP vers les serveurs de Backend ?
{{ ansible_inventory }}.xip.io
…
ec2
avec l’argument assign_public_ip: false
mais un VPC spécifique sera nécessaire avec une solution d’infrastructure réseau propre à AWS.
[return]