[PYTHON] Créez un script de déploiement avec fabric et cuisine et réutilisez-le

introduction

Cet article est le deuxième jour du calendrier de l'avent Python 2016. http://qiita.com/advent-calendar/2016/python

Créez un script de déploiement avec fabric et cuisine et réutilisez-le

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.

Que sont le tissu et la cuisine

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

Structure du répertoire

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

Exemples de recettes réutilisables

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))

Utilisez Jinja2 pour télécharger un fichier de configuration qui incorpore des variables

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)

Script de déploiement réel

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()

Résumé / impression

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.

Recommended Posts

Créez un script de déploiement avec fabric et cuisine et réutilisez-le
Créez rapidement un tableau de bord d'analyse de données Python avec Streamlit et déployez-le sur AWS
Créez un fichier temporaire avec django sous forme de zip et renvoyez-le
Créez un système stellaire avec le script Blender 2.80
Créez un arbre de décision à partir de 0 avec Python et comprenez-le (5. Entropie des informations)
Créez une application graphique native avec Py2app et Tkinter
Créez un lot d'images et gonflez avec ImageDataGenerator
Créer une visionneuse de modèle 3D avec PyQt5 et PyQtGraph
[Linux] Créez un auto-certificat avec Docker et apache
Jusqu'à ce que vous créiez un environnement d'apprentissage automatique avec Python sur Windows 7 et que vous l'exécutiez
Récupérez la chaîne correspondante dans l'expression régulière et réutilisez-la lors du remplacement sur Python3
Créez une caméra de surveillance WEB avec Raspberry Pi et OpenCV
Associez Python Enum à une fonction pour la rendre appelable
[Azure] Créer, déployer et réapprendre un modèle [ML Studio classic]
Créons un script qui s'enregistre avec Ideone.com en Python.
Créer une page d'accueil avec django
Créez des applications, enregistrez des données et partagez-les avec un seul e-mail
Un script qui facilite la création de menus riches avec l'API de messagerie LINE
Lorsque je déploie une application Django sur Apache2 et qu'elle ne lit plus les fichiers statiques
Procédure pour créer un Job qui extrait une image Docker et la teste avec des actions Github
Étapes pour configurer Pipenv, créer une application CRUD avec Flask et la conteneuriser avec Docker
Créer un répertoire avec python
Créons une IA à trois voies avec Pylearn2 --Save and load model -
Créez une illusion rayée avec correction gamma pour Python3 et openCV3
Créez un thermomètre avec Raspberry Pi et rendez-le visible sur le navigateur Partie 4
Créez un DMP privé sans coût initial ni développement avec BigQuery
J'ai essayé de créer des taureaux et des vaches avec un programme shell
Je veux créer un fichier pip et le refléter dans le menu fixe
Créer et renvoyer un fichier CSV CP932 pour Excel avec Chalice
J'ai créé un chat chat bot avec Tensor2Tensor et cette fois cela a fonctionné
Déployer un script sur jboss à l'aide de fabric
Déployer l'application Django avec Docker
Créez un environnement virtuel avec Python!
Donner une égalité au tissu avec la cuisine
Créez un stepper de poisson avec numpy.random
Ecrire un script batch avec Python3.5 ~
Créer un téléchargeur de fichiers avec Django
J'ai créé un script POST pour créer un problème sur Github et l'enregistrer dans le projet
[AWS lambda] Déployer, y compris diverses bibliothèques avec lambda (générer un zip avec un mot de passe et le télécharger vers s3) @ Python
2.Faites un arbre de décision à partir de 0 avec Python et comprenez-le (2. Bases du programme Python)
[AWS] Créez un environnement Python Lambda avec CodeStar et faites Hello World
Définissez la fonction Lambda et laissez-la fonctionner avec les événements S3!
Créer une pile avec une file d'attente et une file d'attente avec une pile (à partir de LetCode / Implémenter la pile à l'aide de files d'attente, Implémenter la file d'attente à l'aide de piles)
Créer une application Todo avec Django ④ Implémenter la fonction de création de dossier et de tâche
Créez un environnement Python 3 avec pyenv sur Mac et affichez des graphiques Network X
Créez un arbre de décision à partir de 0 avec Python et comprenez-le (4. Structure des données)
Peut être fait en 5 minutes!? Créez une API de détection de visage avec Fast API et OpenCV et publiez-la sur Heroku
Histoire de créer un planétarium virtuel [jusqu'à ce que les débutants créent un modèle avec un script et parviennent à l'assembler]
Créer un décorateur de fonction Python avec Class
Créez une image factice avec Python + PIL.
[Python] Créez un environnement virtuel avec Anaconda
Créons un groupe gratuit avec Python
Créer une application graphique avec Tkinter de Python
Un mémo contenant Python2.7 et Python3 dans CentOS
Créer un gros fichier texte avec shellscript