Cet article est le deuxième jour du calendrier de l'avent Python 2016. http://qiita.com/advent-calendar/2016/python
Je m'appelle Matsunaga de LOGICA Co., Ltd. Nous voulons vous faciliter les déplacements et nous développons un service de recherche croisée plus simple pour les hôtels.
Actuellement, nous développons deux produits (crawler et media) en interne avec Django, mais comme nous développons actuellement par une seule personne et qu'il y a de nombreux serveurs, nous aimerions terminer le travail de déploiement avec une seule commande. Était là.
Cependant, bien que je souhaitais automatiser le déploiement, je n'ai pas touché à Ansible et Chef n'a pas bien compris après avoir fait le tutoriel, j'ai donc entendu dire que le coût d'apprentissage semblait faible j'ai décidé de faire un script de déploiement en utilisant fabric. Fait. la cuisine est pour assurer l'homosexualité (ne devrait pas être parfaite) Et comme j'ai utilisé Pyenv, Django, Nginx et gunicorn à la fois pour le robot d'exploration et les médias, j'ai décidé de créer quelque chose comme la recette de Chef et de l'utiliser.
Je pense que l'article suivant est facile à comprendre pour une brève explication du tissu et de la cuisine. http://qiita.com/pika_shi/items/802e9de8cb1401745caa
Les liens vers la documentation sont ci-dessous. documentation du fabric documentation cuisine
Les répertoires sont chaque projet (projet1, projet2), recettes et ssh_keys.
Sous le répertoire templates de chaque projet, placez les fichiers dont vous souhaitez mélanger les points de terminaison et les paramètres pour la production, tels que le fichier settings.py
de django et le fichier de paramètres de Nginx. Pour chacun de ces fichiers de modèle, utilisez Jinja2 pour placer les variables décrites dans secrets.yml, puis téléchargez-les sur le serveur.
Placez les fichiers et binaires que vous souhaitez télécharger dans project1 / files
.
Les recettes contiennent des scripts réutilisables.
ssh_keys est pour extraire à distance le contenu du référentiel sur github.
Ceux-ci sont gérés par github. Bien sûr, ajoutez les répertoires secrets.yml et ssh_keys à gitignore.
├── project1
│ ├── fabfile.py
│ ├── files
│ │ └── phantomjs-2.1.1-linux-x86_64.tar.bz2
│ ├── secrets.yml
│ ├── secrets.yml.example
│ └── templates
│ ├── gunicorn_conf.py
│ ├── nginx.conf
│ └── settings.py
├── project2
│ ├── fabfile.py
│ ├── secrets.yml
│ ├── secrets.yml.example
│ └── templates
│ ├── gunicorn_conf.py
│ ├── nginx.conf
│ └── settings.py
├── recipes
│ ├── __init__.py
│ ├── django.py
│ ├── git.py
│ ├── gunicorn.py
│ ├── httpd_tools.py
│ ├── nginx.py
│ ├── phantomjs.py
│ ├── pyenv.py
│ ├── redis.py
│ ├── service_base.py
├── requirements.txt
└── ssh_keys
└── github
└── id_rsa
Depuis que j'utilise amazon linux, j'utilise yum pour la gestion des paquets, mais pour tout ce qui démarre ou s'arrête comme sudo service ◯◯ start
, faites du script suivant la classe parent. L'installation elle-même peut sûrement être effectuée en utilisant package_ensure
de cuisine, mais je voulais la conserver avec un nom descriptif comme méthode.
recipes/service_base.py
# -*- coding: utf-8 -*-
from fabric.api import sudo
from fabric.utils import puts
from fabric.colors import green
from cuisine import package_ensure, select_package
select_package('yum')
class ServiceBase(object):
def __init__(self, package_name, service_name):
self.package_name = package_name
self.service_name = service_name
def install(self):
package_ensure(self.package_name)
def start(self):
puts(green('Starting {}'.format(self.package_name)))
sudo('service {} start'.format(self.service_name))
def stop(self):
puts(green('Stopping {}'.format(self.package_name)))
sudo('service {} stop'.format(self.service_name))
def restart(self):
puts(green('Restarting {}'.format(self.package_name)))
sudo('service {} restart'.format(self.service_name))
En utilisant ceci, créez le script d'installation / démarrage / arrêt de nginx comme suit. Nginx
recipes/nginx.py
from service_base import ServiceBase
class Nginx(ServiceBase):
def __init__(self):
super(Nginx, self).__init__('nginx', 'nginx')
self.remote_nginx_conf_path = '/etc/nginx/nginx.conf'
J'écrirai le téléchargement du fichier de configuration Nginx plus tard.
En plus de ceux gérés par yum, par exemple, Pyenv, Django (car la commande est exécutée par python manage.py ~~
) et celery font aussi des scripts communs. Je mettrai seulement pyenv.
Pyenv
recipes/pyenv.py
class Pyenv(object):
def __init__(self):
pass
def install(self):
"""Installer pyenv et les outils associés"""
pyenv_dir = '~/.pyenv'
#Confirmation de l'installation de pyenv
if not dir_exists(pyenv_dir):
run('curl -L https://raw.githubusercontent.com/yyuu/pyenv-installer/master/bin/pyenv-installer | bash')
text = """
# settings for pyenv
export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
"""
files.append('~/.bashrc', text)
run('source ~/.bashrc')
def install_python(self, py_version):
"""Installez la version de python spécifiée sur Pyenv"""
#Si pyenv n'est pas installé, installez-le.
if not dir_exists('~/.pyenv'):
self.install()
#Assurez-vous que les packages nécessaires à la construction de Python sont installés
packages = ['gcc', 'python-devel', 'bzip2-devel', 'zlib-devel', 'openssl-devel', 'sqlite-devel', 'readline-devel', 'patch']
for package in packages:
package_ensure(package)
if not dir_exists('~/.pyenv/versions/{}'.format(py_version)):
run('pyenv install {}'.format(py_version))
run('pyenv rehash')
def make_virtualenv(self, py_version, env_name):
"""Créer un environnement avec le nom spécifié"""
self.install_python(py_version)
if not dir_exists('~/.pyenv/versions/{}'.format(env_name)):
run('pyenv virtualenv {} {}'.format(py_version, env_name))
run('pyenv rehash')
run('pyenv global {}'.format(env_name))
else:
run('pyenv global {}'.format(env_name))
def change_env(self, env_name):
run('pyenv global {}'.format(env_name))
J'utilise la fonction suivante. Puisque le tissu et la cuisine ne prennent pas en charge Python3, je changerai la version distante de Python avant et après le téléchargement avec recettes / pyenv.py
que j'ai écrit plus tôt w (c'est-à-dire que j'ai deux versions de Python installées sur la télécommande Je vais.)
Si je n'ai pas téléchargé le fichier, je pourrais utiliser la télécommande réglée sur 3, mais quand j'ai fait file_write, la télécommande est également tombée avec une erreur à moins qu'elle ne soit 2, donc je fais ce genre de problème.
def upload_template(remote_path, local_template_path, variables={}, sudo=None):
"""
Télécharger en mettant des variables dans le modèle jinja2
"""
#Changer Python distant en un environnement système 2
pyenv = Pyenv()
pyenv.change_env(VIRTUALENV_NAME_FOR_FABRIC)
local_template_name = local_template_path.split('/')[-1]
local_template_dir = local_template_path.replace(local_template_name, '')
jinja2_env = Environment(loader=FileSystemLoader(local_template_dir))
content = jinja2_env.get_template(local_template_name).render(variables)
file_write(remote_path, content.encode('utf-8'), sudo=sudo)
#Revenir à l'environnement Python d'origine
pyenv.change_env(VIRTUALENV_NAME)
Utilisez-le pour télécharger les fichiers de configuration Nginx, etc. variables contient les données lues à partir de secrets.yml.
upload_template(nginx.remote_nginx_conf_path, 'templates/nginx.conf', variables, sudo=sudo)
Par exemple, écrivez ce qui suit dans le nom de serveur de nginx.conf
.
server_name {{ end_point }};
Ce n'est pas grave si vous mettez le point de terminaison que vous voulez spécifier dans nom_serveur dans variables [" point de terminaison "]
. Je pense que c'est une description familière pour ceux qui utilisent habituellement Jinja ou Django.
Les paramètres de base de données dans settings.py de Django sont les suivants.
secrets.yml
django:
settings:
production:
secret_key:Clef secrète
databases:
default:
engine: django.db.backends.mysql
name:Nom de la base de données
user:Nom d'utilisateur DB
password:Mot de passe DB
host:Point de terminaison DB
port:Port DB
project1/templates/settings.py
DATABASES = {
'default': {
'ENGINE': '{{ databases.default.engine }}',
'NAME': '{{ databases.default.name }}',
'USER': '{{ databases.default.user }}',
'PASSWORD': '{{ databases.default.password }}',
'HOST': '{{ databases.default.host }}',
'PORT': '{{ databases.default.port }}',
},
}
project1/fabfile.py
variables = secrets['django']['settings']['production']
upload_template(settings_file_path, 'templates/settings.py', variables)
Puisqu'il est dangereux de mettre le brut, j'ai créé un script qui installe uniquement nginx et construit l'environnement Python avec Pyenv (je n'ai pas confirmé l'opération car il s'agit d'une copie partielle de celle réellement utilisée)
project1/fabfile.py
# -*- coding: utf-8 -*-
import os
import sys
sys.path.append(os.pardir)
import yaml
from jinja2 import Environment, FileSystemLoader
from fabric.api import env, run, sudo, settings, cd
from fabric.decorators import task
from cuisine import package_ensure, select_package, file_write
from recipes.nginx import Nginx
from recipes.pyenv import Pyenv
#Informations Python
PYTHON_VERSION = "La version que vous souhaitez utiliser en production"
VIRTUALENV_NAME = "Nom de l'environnement utilisé en production"
#Environnement Python distant lors du téléchargement de fichiers
PYTHON_VERSION_FOR_FABRIC = "Dans 2 système"
VIRTUALENV_NAME_FOR_FABRIC = "Nom de l'environnement pour la structure distante"
#Sélection de la méthode de gestion des colis
select_package('yum')
#Charger les informations à intégrer dans le modèle
secrets = yaml.load(file('secrets.yml'))
#Paramètres d'environnement Informations utilisées pour se connecter au serveur de destination
env.user = "Nom d'utilisateur"
env.group = "nom de groupe"
env.key_filename = "Chemin d'accès clé utilisé pour se connecter au serveur"
env.use_ssh_config = True
def upload_template(remote_path, local_template_path, variables={}, sudo=None):
pyenv = Pyenv()
pyenv.change_env(VIRTUALENV_NAME_FOR_FABRIC)
local_template_name = local_template_path.split('/')[-1]
local_template_dir = local_template_path.replace(local_template_name, '')
jinja2_env = Environment(loader=FileSystemLoader(local_template_dir))
content = jinja2_env.get_template(local_template_name).render(variables)
file_write(remote_path, content.encode('utf-8'), sudo=sudo)
#Revenir à l'environnement Python d'origine
pyenv.change_env(VIRTUALENV_NAME)
@task
def deploy():
#Créer un environnement Python pour le téléchargement de modèles (le système distant n'est pas 2)
pyenv = Pyenv()
pyenv.install_python(PYTHON_VERSION_FOR_FABRIC)
pyenv.make_virtualenv(PYTHON_VERSION_FOR_FABRIC, VIRTUALENV_NAME_FOR_FABRIC)
#Construire un environnement Python pour la production
pyenv.install_python(PYTHON_VERSION)
pyenv.make_virtualenv(PYTHON_VERSION, VIRTUALENV_NAME)
#construction d'environnement nginx
nginx = Nginx()
nginx.install()
variables = {
'end_point': END_POINT,
}
upload_template(nginx.remote_nginx_conf_path, 'templates/nginx.conf', variables, sudo=sudo)
nginx.stop()
nginx.start()
En tant qu'impression de l'utiliser réellement, il n'y a presque aucun coût d'apprentissage (car il ne s'agit que d'un wrapper shell) Cependant, lorsqu'il s'agit de réutiliser et de télécharger des fichiers de modèle, je pense qu'Ansible va bien. (Je ne sais pas parce que je n'y ai pas touché)
Puisque nous sommes une startup, je craignais au départ que l'écriture d'un script de déploiement ne soit un effort supplémentaire, mais en raison de la pesée du coût et de la facilité de création de ces scripts de déploiement, je suis maintenant très satisfait. Nous avons beaucoup de serveurs pour la balance, donc c'était bien d'avoir un script de déploiement. C'est vraiment simple.
Si vous avez un meilleur moyen, ou si vous avez quelque chose comme ça, veuillez nous le faire savoir dans les commentaires.