L’objectif du projet est de mettre en place un site de monitoring afin de superviser l’ensemble des services web de la société.
Pour cela le service Cabot a été choisit. Pour répondre au besoin le service dispose de différents types de vérifications qui s’assurent de la communication avec les sites de manière automatisée. Pour notre besoin nous mettons en place les vérifications HTTP voir ICMP si nécessaire. Ces checks sont regroupés dans des instances qui correspondent chacune à un site , l’instance est elle même intégrée à un groupe d’instances appelé service. Les services ont été disposés en différentes catégories de sites, les sites internes à Nautile, les sites clients, et autres.
Le service sera déployé dans un Docker, une technologie qui permet le déploiement efficace et optimisé de plusieurs service de manière isolée.
L’ensemble des services et sites web à monitorer se trouve dans un dépôt Salt qui contient les configurations des Vhost Nginx, des pare-feu et du BGP ainsi que la liste des services.
Lorsque l’un des checks échoue une erreur est envoyée dans le canal dédié à cet effet dans le logiciel de discussion de l’entreprise.
La première partie était d’installer Cabot une fois réalisée, il a fallut comprendre l’API de cabot, c’est elle qui interprète les requêtes que nous lui enverrons afin de configurer de manière autonome Cabot. Le langage de l’API est JSON et elle interprétera un script Python.
DOCKER: Docker est une technologie de containers permettant d’héberger un à plusieurs services minimisant les ressources utilisées par le serveur car il ne déploie pas entièrement l’OS.
Caddy: Est utilisé en tant que reverse proxy, proxy installé sur le serveur limitant les accès extérieur.
L’installation de Cabot:
- Installation de Docker Engine
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli docker.io
2. Installation de cabot: dans docker-cabot récupération du docker Cabot:
git clone git@github.com:cabotapp/docker-cabot.git
3. Copie des fichiers de configuration:
cp conf/production.env.example conf/production.env
cp conf/Caddyfile.example conf/Caddyfile
4. Modifier dans le fichier de configuration
restart : always
5. Modification des droits
docker-compose -f docker-compose.yml -f docker-compose-caddy.yml up -d
6. Application des changements
docker-compose up -d
7. Vérification des containers
docker ps
L’installation de Cabot est terminé, l’étape suivante est la rédaction du script en Python afin d’automatiser le système. La liste des différents services web à monitorer se situe dans différents fichiers de configuration en YAML que nous devrons récupérer dans notre script.
#!/usr/bin/env python3 # pylint: disable=missing-module-docstring
# Import de bibliothèques
import json
import requests
import yaml
# Variables
FILES_YAML = {
'RP-Infra':'rpinfra.sls',
'RP-Core':'rpcore.sls',
'RP-External':'rpexternal.sls',
}
FREQUENCY = '5'
VERIFY_SSL_CERTIFICATE = 'true'
TIMEOUT = '500'
ACTIVE = 'true'
IMPORTANCE = 'ERROR'
ALERTS_ENABLED = 'true'
URL = "https://@Cabot/api"
HEADERS = {
'Content-Type': 'application/json',
'authorization': 'Basic YWRtaW46TmF1dGlsZTEyMw==',
}
# - Récupérations des Données existantes
# - Récupération des noms et ID des Services
DICTSERVICE = {}
SERVICE_NAME_ID = {}
try:
RESPONSE_SERVICE = requests.get(URL + '/services/', headers=HEADERS)
RESPONSE_SERVICE = json.loads(RESPONSE_SERVICE.text)
except: #pylint: disable=W0702
print('La récupération des Services a échoué')
for DICTSERVICE in RESPONSE_SERVICE:
SERVICE_NAME_ID[DICTSERVICE['name']] = DICTSERVICE['id']
# - Récupération ICMP
DICT_ICMP = {}
ICMP_NAME_ID = {}
try:
ICMP_GET = requests.get(URL + '/icmp_checks/', headers=HEADERS)
ICMP_GET = json.loads(ICMP_GET.text)
except: #pylint: disable=W0702
print('La récupération des checks ICMP a échoué')
for DICT_ICMP in ICMP_GET:
ICMP_NAME_ID[DICT_ICMP['name']] = DICT_ICMP['id']
# - Récupération HTTP
DICT_HTTP = {}
HTTP_NAME_ID = {}
try:
HTTP_GET = requests.get(URL + '/http_checks/', headers=HEADERS)
HTTP_GET = json.loads(HTTP_GET.text)
except: #pylint: disable=W0702
print('La récupération des checks HTTP a échoué')
for DICT_HTTP in HTTP_GET:
HTTP_NAME_ID[DICT_HTTP['name']] = DICT_HTTP['id']
# - Récupération des Instances
DICT_INSTANCE = {}
INSTANCE_NAME_ID = {}
try:
RESPONSE_INSTANCE = requests.get(URL + '/instances/', headers=HEADERS)
RESPONSE_INSTANCE = json.loads(RESPONSE_INSTANCE.text)
except: #pylint: disable=W0702
print('La récupération des instances a échoué')
for DICT_INSTANCE in RESPONSE_INSTANCE:
INSTANCE_NAME_ID[DICT_INSTANCE['name']] = DICT_INSTANCE['id']
# - Fonction de récupération
def get_ressources(endpoint): # pylint: disable=missing-function-docstring
response = requests.get(URL + endpoint, headers=HEADERS).json()
return response
# - Fonction de création
def create_ressources(endpoint, data): # pylint: disable=missing-function-docstring
response = requests.post(URL + endpoint, headers=HEADERS, data=data).json()
return response
# - Fonction de mise à jour
def patch_ressource(endpoint, data): # pylint: disable=missing-function-docstring
response = requests.patch(URL + endpoint, headers=HEADERS, data=data).json()
return response
for service in FILES_YAML:
FullStatusChecks = []
InstanceFull = []
ServiceYaml = FILES_YAML[service]
DataServiceCreate = '{\
"name": "'+str(service)+'",\
"users_to_notify": [1],\
"alerts_enabled": true,\
"status_checks": [],\
"alerts": [5],\
"hackpad_id": "",\
"url": "",\
"instances": [],\
"overall_status": "PASSING"\
}'
# - Conditions de créations ou de récupérations
if service in SERVICE_NAME_ID.keys():
get_ressources('/services/'+ str(SERVICE_NAME_ID[service]) + '/')
print('===> ', service, SERVICE_NAME_ID[service], ': OK')
else:
creat_service = create_ressources('/services/', DataServiceCreate)
SERVICE_NAME_ID[creat_service['name']] = creat_service['id']
print('===> ', service, SERVICE_NAME_ID[service], ': Created')
# - Exceptions status code
DictStatusCode = {}
with open("genCabotconfig.yml", 'r') as f:
conf = yaml.load(f, Loader=yaml.FullLoader)
for liste in conf['exceptions']['status_code']:
DictStatusCode.update(liste)
# - Récupération des sites
with open(ServiceYaml, 'r') as f:
site = yaml.load(f, Loader=yaml.FullLoader)
for site in site['nginx-proxy']['vhosts']:
name = site['name']
print('===> ', name)
if name in DictStatusCode:
StatusCode = DictStatusCode[name]
else:
StatusCode = 200
# - Data des Checks
#DataIcmp = '{\
# "name": "PING '+name+'",\
# "active": '+ACTIVE+',\
# "importance": "'+IMPORTANCE+'",\
# "frequency": '+str(FREQUENCY)+',\
# "debounce": 0,\
# "calculated_status":"passing"\
# }'
DataHttp = '{\
"name": "'+name+'",\
"active": '+ACTIVE+',\
"importance": "'+IMPORTANCE+'",\
"frequency":'+str(FREQUENCY)+',\
"debounce": 0,\
"calculated_status": "passing",\
"endpoint": "https://'+name+'",\
"username": "null",\
"password": "null",\
"text_match": "",\
"status_code": "'+str(StatusCode)+'",\
"timeout": '+str(TIMEOUT)+',\
"verify_ssl_certificate": '+VERIFY_SSL_CERTIFICATE+'\
}'
#if 'PING ' + name in ICMP_NAME_ID.keys():
# IdIcmp = ICMP_NAME_ID['PING ' + name]
# print(' - HTTP Check: OK ')
#else:
# CreateIcmp = create_ressources('/icmp_checks/', DataIcmp)
# ICMP_NAME_ID[CreateIcmp['name']] = CreateIcmp['id']
# IdIcmp = CreateIcmp['id']
# print(' - ICMP Check: Created ')
if name in HTTP_NAME_ID.keys():
IdHttp = HTTP_NAME_ID[name]
print(' - HTTP Check: OK ')
else:
CreateHttp = create_ressources('/http_checks/', DataHttp)
HTTP_NAME_ID[CreateHttp['name']] = CreateHttp['id']
IdHttp = CreateHttp['id']
print(' - HTTP Check: Created ')
# Rajouter la variable IdIcmp au DataInstance dans status_checks
DataInstance = '{\
"name": "'+str(name)+'",\
"users_to_notify": [1],\
"alerts_enabled": true,\
"status_checks": ['+str(IdHttp)+'],\
"alerts": [],\
"hackpad_id": null,\
"address": "'+str(name)+'",\
"overall_status": "PASSING"\
}'
if name in INSTANCE_NAME_ID.keys():
print(' - Instance: OK ')
else:
CreateInstance = create_ressources('/instances/', DataInstance)
INSTANCE_NAME_ID[CreateInstance['name']] = CreateInstance['id']
print(' - Instance: Created ')
# - Patch
PatchInst = patch_ressource('/instances/'+str(INSTANCE_NAME_ID[name])+'/', DataInstance)
InstanceFull.append(PatchInst['id'])
FullStatusChecks.extend(PatchInst['status_checks'])
DataServicePatch = '{\
"name": "'+str(service)+'",\
"users_to_notify": [1],\
"alerts_enabled": true,\
"status_checks": '+str(FullStatusChecks)+',\
"alerts": [5],\
"hackpad_id": "",\
"url": "",\
"instances": '+str(InstanceFull)+',\
"overall_status": "PASSING"\
}'
PatchService = patch_ressource('/services/'+str(SRVICE_NAME_ID[service])+'/', DataServicePatch)
Voici notre script terminé et nettoyé. La mise en place du service est terminée.
Les exceptions des statuts code sont listés dans un fichier YAML
exceptions:
status_code:
- mailer.nautile.tech: 403
- nautile.tech: 404
- api.q20.sarl: 404
- dev.api.nautile.tech: 502
- dev.api.q20.nc: 404
- doli.nautile.co.nautile.dev: 404
- doli.nautile.co.nautile.site: 404
- grafana.nautile.tech: 401
- kibana.nautile.tech: 401
- netdata.nautile.tech: 403
- s3.nautile.tech: 400
Pour l’intégration continue nous créons un fichier de configuration .gitlab-ci.yml
Generate-Cabot:
stage: deploy
script:
- /usr/local/bin/python -m pip install --upgrade pip
- pip3 install requests pyyaml
- cd pillar/ && ./genCabot.py
tags:
- docker
#only:
#- master
A présent à chaque ajout d’un service web dans les fichiers de configuration gitlab exécutera automatique les ligne de commande du fichier .gitlab-ci.yml.