mar 23, 2015
Con el auge de los servidores baratos, ya sea VPS o dedicados, cada vez es
mas común que cualquier pequeña página tenga que administrar su propio
servidor, un aspecto que es fácilmente ignorado o incluso olvidado es el
espacio swap, y puede tener una grandes repercusiones en el rendimiento.
¿Que es el espacio swap?
Swap es usado cuando Linux llena toda la memoria RAM física disponible. Si el
sistema tiene swap habilitado, datos almacenados en RAM que no están siendo
usados son movidos a swap temporalmente para liberar espacio. Con esto se
consigue que en momentos puntuales en los que la carga del sistema es alta,
exista espacio extra hasta que pase la congestión.
El espacio swap tiene sus limitaciones, al estar normalmente localizado en
algún medio de almacenamiento masivo, ya sea un disco duro o SSD, tienen
un tiempo de acceso y velocidad de transferencia ordenes de magnitud peores
que la memoria RAM. Por eso un sistema que usa swap de manera continuada
tendrá un mal rendimiento, e indica que no tiene suficiente memoria RAM y
es momento de una ampliación.
Configurar swap en Linux
El primer paso es comprobar si swap ya está activado en el sistema.
$ free -m
total used free shared buffers cached
Mem: 7709 5009 2699 312 55 1198
-/+ buffers/cache: 756 3953
Swap: 0 0 0
Con free podemos ver la memoria disponible en el sistema, en caso de que swap
no esté activado deberá aparecer ceros en la linea de swap.
Si no esta activado el siguiente paso es crear el archivo que se usara como swap
$ sudo dd if=/dev/zero of=/swapfile bs=8G count=4
$ sudo chmod 600 /swapfile
Esto crea un archivo de 8GB (recomendado que su tamaño sea el doble que la RAM),
y cambia los permisos para que no pueda ser leído mas que por el root. Tras esto
hay que crear el espacio swap en el archivo.
$ sudo mkswap /swapfile
Configurando espacio de intercambio versión 1, tamaño = 4194300 kiB
sin etiqueta, UUID=7f2020e5-0a09-4f1b-b0af-3053e94f17e2
Para activar swap
Antes de continuar es recomendable usar free para comprobar que todo ha funcionado correctamente.
$ free -m
total used free shared buffers cached
Mem: 7709 6626 1082 463 189 2464
-/+ buffers/cache: 350 3736
Swap: 7980 1029 6951
Por último es necesario configurar fstab para que swap se active al
arrancar el sistema. Con tu editor favorito añade lo siguiente a fstab.
/swapfile none swap sw 0 0
Y eso es todo, un tutorial MUY básico de como activar swap.
Click to read and post comments
mar 23, 2015
Aviso: Este es un listado de comandos que constantemente olvido y
busco, asi que los he reunido en una lista para tener acceso rápido.
Compresión de directorios
Comprimir:
$ tar -zcvf backup-2013-05-03.tar.gz /home/backup/directory
Descomprimir:
$ tar -zxvf backup-2013-05-03.tar.gz
Vim
Encontrar cada ocurrencia de la cadena 'foo' en el texto, y substituirla por la
cadena 'bar'.
Encontrar cada ocurrencia de la cadena 'foo' en la línea actual, y substituirla
por 'bar'.
Substituir cada ocurrencia de la cadena 'foo' por 'bar', pero pidiendo confirmación
primero.
Grep
Buscar archivos que contengan determinado texto.
$ grep 'nombre' *.txt
$ grep '#include<example.h>' *.c
Debug
Resumen de llamadas al systema realizadas por un comand
$ strace -c ls >/dev/null
Trafico de red excepto ssh
Click to read and post comments
ene 01, 2015
Recientemente he tenido que crear un panel de control para una aplicación
django, su función era mostrar estadísticas de los pedidos. Como en
casi todos los casos el primer problema fue crear un sistema de login, esta
es una situación muy común que se presenta en la mayoría de aplicaciones,
la diferencia es que en este caso me decidí por usar Class Based Views,
y me he quedado sorprendido con la simplicidad y limpieza del código:
# views.py
from django.views.generic import FormView, TemplateView, RedirectView
# Authentication imports
from django.contrib.auth import login, logout
from django.contrib.auth.forms import AuthenticationForm
from django.core.urlresolvers import reverse_lazy
from cesta.models import Pedido
class LoginView(FormView):
form_class = AuthenticationForm
template_name = "control_panel/login.html"
success_url = reverse_lazy("panel-dashboard")
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated():
return HttpResponseRedirect(self.get_success_url())
else:
return super(LoginView, self).dispatch(request, *args, **kwargs)
def form_valid(self, form):
login(self.request, form.get_user())
return super(LoginView, self).form_valid(form)
class LogoutView(RedirectView):
pattern_name = 'panel-login'
def get(self, request, *args, **kwargs):
logout(request)
return super(LogoutView, self).get(request, *args, **kwargs)
class LoginRequiredMixin(object):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('panel-login'))
else:
return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)
class ControlPanelView(LoginRequiredMixin, TemplateView):
template_name = 'control_panel/panel.html'
def get_context_data(self, **kwargs):
context = super(ControlPanelView, self).get_context_data(**kwargs)
context['pedidos_pendientes'] = Pedido.objects.pendientes()
# ....
# Recopilar resto de la informacion
# ....
return context
Descargar views.py
# urls.py
from django.conf.urls import patterns, url
from .views import ControlPanelView, LoginView, LogoutView
urlpatterns = patterns('',
url(
regex = r'^$',
view = ControlPanelView.as_view(),
name = "panel-dashboard"),
url(
regex = r'^login/$',
view = LoginView.as_view(),
name = "panel-login"),
url(
regex = r'^logout/$',
view = LogoutView.as_view(),
name = "panel-logout"),
)
Descargar urls.py
Por último un ejemplo para el template login.html:
{% extends 'base.html' %}
{% block content %}
<div class="login-panel">
<form method="post" role="form">{% csrf_token %}
{{ form.username }}
{{ form.password }}
<input type="submit" value="Login"/>
</form>
</div>
{% endblock %}
Click to read and post comments
ene 01, 2015
Recientemente he tenido que crear un panel de control para una aplicación
django, su función era mostrar estadísticas de los pedidos. Como en
casi todos los casos el primer problema fue crear un sistema de login, esta
es una situación muy común que se presenta en la mayoría de aplicaciones,
la diferencia es que en este caso me decidí por usar Class Based Views,
y me he quedado sorprendido con la simplicidad y limpieza del código:
# views.py
from django.views.generic import FormView, TemplateView, RedirectView
# Authentication imports
from django.contrib.auth import login, logout
from django.contrib.auth.forms import AuthenticationForm
from django.core.urlresolvers import reverse_lazy
from cesta.models import Pedido
class LoginView(FormView):
form_class = AuthenticationForm
template_name = "control_panel/login.html"
success_url = reverse_lazy("panel-dashboard")
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated():
return HttpResponseRedirect(self.get_success_url())
else:
return super(LoginView, self).dispatch(request, *args, **kwargs)
def form_valid(self, form):
login(self.request, form.get_user())
return super(LoginView, self).form_valid(form)
class LogoutView(RedirectView):
pattern_name = 'panel-login'
def get(self, request, *args, **kwargs):
logout(request)
return super(LogoutView, self).get(request, *args, **kwargs)
class LoginRequiredMixin(object):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('panel-login'))
else:
return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)
class ControlPanelView(LoginRequiredMixin, TemplateView):
template_name = 'control_panel/panel.html'
def get_context_data(self, **kwargs):
context = super(ControlPanelView, self).get_context_data(**kwargs)
context['pedidos_pendientes'] = Pedido.objects.pendientes()
# ....
# Recopilar resto de la informacion
# ....
return context
Descargar views.py
# urls.py
from django.conf.urls import patterns, url
from .views import ControlPanelView, LoginView, LogoutView
urlpatterns = patterns('',
url(
regex = r'^$',
view = ControlPanelView.as_view(),
name = "panel-dashboard"),
url(
regex = r'^login/$',
view = LoginView.as_view(),
name = "panel-login"),
url(
regex = r'^logout/$',
view = LogoutView.as_view(),
name = "panel-logout"),
)
Descargar urls.py
Por último un ejemplo para el template login.html:
{% extends 'base.html' %}
{% block content %}
<div class="login-panel">
<form method="post" role="form">{% csrf_token %}
{{ form.username }}
{{ form.password }}
<input type="submit" value="Login"/>
</form>
</div>
{% endblock %}
Click to read and post comments
sep 18, 2014
Hace unos meses publiqué un pequeño tutorial sobre el
despliegue de aplicaciones Django, en
el que mencioné Docker, pero no expliqué ni su
funcionamiento ni sus ventajas, principalmente porque pensaba escribir un
tutorial que por falta de tiempo se quedó en el tintero. Desde entonces han
aparecido algunos buenos manuales, así que en su lugar he escrito este tutorial
sobre como desplegar django usando Docker.
El tutorial asume que el lector posee conocimientos sobre la configuración
de nginx y gunicorn, así como del funcionamiento de docker.
Si no es así recomiendo leer mi tutorial
despliegue de aplicaciones Django,
y algún tutorial de docker, antes de continuar.
Introducción
En esencia el proceso de crear una imagen Docker es el mismo que hay que
seguir para configurar cualquier servidor manualmente, cada uno de los pasos
en el proceso se plasma en un archivo Dockerfile que es usado por Docker
para construir la imagen.
Por ejemplo para un contenedor de una aplicación django, será necesario:
- Copiar la aplicación django al contenedor, e instalar sus dependencias.
- Instalar y configurar gunicorn.
- Instalar y configurar nginx.
- Instalar supervisor y configurarlo para que arranque nginx y gunicorn.
Para conseguir todo esto, además del archivo Dockerfile, necesitamos los
archivos de configuración de nginx, gunicorn, supervisor, y la aplicación
django que se copiarán a la imagen. Todos estos archivos del proyecto deben
estar en el mismo directorio que Dockerfile, por ejemplo la estructura usada
en este tutorial es:
Docker/
|--Dockerfile
|--gunicorn-config.py
|--nginx-default
|--supervisor.conf
|--django_app/
| |--manage.py
| |--requirements.txt
| |--static/
| |--app1/
| | |--models.py
| | |--....
| |--app2/
| | |--....
Descargar
El Dockerfile en este tutorial asume que la aplicación django esta en el
subdirectorio django_app, que a su vez contiene el archivo
requirements.txt con las dependencias del mismo, y el directorio static
con todos los archivos estáticos.
Dockerfile
Toda la magia en la creación de una imagen está en el archivo Dockerfile,
su sintaxis es muy clara:
FROM ubuntu:14.04
MAINTAINER secnot <secnot@secnot.com>
# Actualizacion de los 'sources' a la ultima version
RUN apt-get update
# Instalar los paquetes del sistema necesarios para python
RUN apt-get install -qy python \
python-dev \
python-pip \
python-setuptools \
build-essential
# Instalar algunas utilidades extras (opcional)
RUN apt-get install -qy vim \
wget \
net-tools \
git
# Instalamos resto aplicaciones
RUN apt-get install -qy nginx \
supervisor
###############################
#
# Nginx
#
###############################
# Copiar la configuracion de nginx de la aplicion
ADD nginx-default /etc/nginx/sites-available/default
# Desactiva el modo demonio para arrancar el proceso con supervisor
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf
# Cambiar los permisos de nginx para poder ejecutar nginx como www-data
RUN chown -R www-data:www-data /var/lib/nginx
# Permitir el acceso al puerto 80 del contenedor
EXPOSE 80
##################################
#
# Gunicorn y Django
#
################################
# Copiar aplicacion del subdirectorio django_app/ al directorio
# /django_app en el contenedor
ADD django_app /django_app
RUN chown -R www-data:www-data /django_app
# Si la aplicacion tiene dependencias de paquetes del del sistema
# este es un buen sitio para instalarlas, por ejemplo para PIL:
# RUN apt-get install -qy python-dev libjpeg-dev zlib1g-dev
# Usamos requirements.txt para instalar las dependencias de la
# aplicacion.
RUN pip install -r /django_app/requirements.txt
# Tambien se pueden instalar individualmente, por ejemplo:
# RUN pip install Django
# RUN pip install bleach
# ...
# Una buena medida de seguridad es alamacenar claves usuarios y
# otras credenciales de seguridad en variables de entorno.
# Se importan desde settings.py con:
# PAYPAL_CLIENT_ID = os.environ['PAYPAL_CLIENT_ID']
# PAYPAL_CLIENT_SECRET = os.environ['PAYPAL_CLIENT_SECRET']
ENV PAYPAL_CLIENT_ID sdfasFASDRwefasFqasdfAsdfAsdFAsdfsDFaSDfWERtSDFg
ENV PAYPAL_CLIENT_SECRET ASAsdfarasDFaRasdFaSsdfghJdfGHDGsdTRSDfGErtAFSD
# Como precaucion se instala gunicorn, aunque deberia estar en
# requirements.txt
RUN pip install gunicorn
# Por ultimo se copia la configuracion de gunicorn.
ADD gunicorn-config.py /etc/gunicorn/config.py
#############################
#
# Supervisor
#
############################
# Copiar la configuracion
ADD supervisor.conf /etc/supervisor/conf.d/django_app.conf
# Instalamos supervisor-stdout que permite que los logs, sean impresos
# en stdout. (ver supervisor.conf)
RUN pip install supervisor-stdout
# Establecer el directorio de trabajo
WORKDIR /django_app
# Comando por defecto que se ejecutara al arranque del contenedor,
# supervisor se encarga de gestionar nginx y gunicorn.
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf"]
Supervisor y Logging
El archivo supervisor.conf, contiene la configuración necesaria para
que supervisord arranque nginx y gunicorn, y los reinicie en caso de que mueran:
[program:gunicorn]
command = /usr/local/bin/gunicorn -c /etc/gunicorn/config.py yourapp.wsgi:application
directory = /django_app
user = www-data
autostart = True
autorestart = True
[program:nginx]
command = /usr/sbin/nginx
autostart = True
autorestart = True
Pero esa no es la única función de supervisor, sino que también se encarga de
guardar logs de las salidas stdout y stderr de cada programa que
gestiona. Con django se puede aprovechar esta característica para
que supervisor se encargue de gestionar los logs, solo es necesario
imprimirlos en stdout en lugar de guardarlos en un archivo, editando
settings.py de tu aplicación:
# settings.py
# .....
import sys
LOGGING = {
'version': 1,
'dissable_existing_loggers': False,
'formatters': {
'verbose': {
'format': "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s",
'datefmt': "%Y-%m-%d %H:%M:%S",
},
'simple': {
'format': "%(levelname)s %(message)s",
},
},
'handlers': {
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'stream': sys.stdout,
'formatter': 'simple',
},
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'include_html': True,
},
},
'loggers': {
'': {
'handlers': ['mail_admins', 'console'],
'level': 'DEBUG',
}
},
}
El manejador StreamHandler redirige todos los logs de nivel INFO, o superior
a la salida estándar, cuando supervisor recibe estos logs tiene dos opciones:
Almacenar los logs en un archivo, que a su vez puede o no estar montado
mediante un volumen desde el host, para que los datos sean persistente:
[program:gunicorn]
command = /usr/local/bin/gunicorn -c /etc/gunicorn/config.py yourapp.wsgi:application
directory = /django_app
user = www-data
autostart = True
autorestart = True
redirect_stderr=true
stdout_logfile=/var/log/django.log
Para usar un volumen, se debe iniciar el contenedor con la opción -v:
$ docker run -v /var/log/django.log:/var/log/django.log -p 80:80 yourapp:nginx
Redirigir los logs a stdout para que sean gestionados por el host
que inicio el contenedor. Para esto es necesario usar el manejador de eventos
supervisor-stdout.
Y la configuración de supervisor seria:
[supervisord]
nodaemon = true
[program:gunicorn]
command = /usr/local/bin/gunicorn -c /etc/gunicorn/config.py testsite.wsgi:application
directory = /django_app
user = www-data
autostart = True
autorestart = True
stdout_events_enabled = true
stderr_events_enabled = true
[program:nginx]
command = /usr/sbin/nginx
autostart = True
autorestart = True
stdout_events_enabled = true
stderr_events_enabled = true
[eventlistener:stdout]
command = supervisor_stdout
buffer_size = 100
events = PROCESS_LOG
result_handler = supervisor_stdout:event_handler
Para arrancar el contenedor, hay que añadir la opción -a stdout para que
imprima la salida en stdout:
$ sudo docker run --rm -a stdout -p 80:80 yourapp:nginx
En el host se puede configurar supervisor para que arranque el contenedor, y
al tiempo guardar los logs
Seguridad
Los contenedores docker NO son seguros, a pesar de lo que hayas leído es
posible salir de un contenedor.
Por diseño los contenedores comparten el mismo kernel que el host, y pueden
hacer llamadas de sistema al mismo, de manera que cualquier vulnerabilidad
del interfaz del kernel, es explotable desde un contenedor.
La mejor política es tratar docker como una herramienta para desplegar
aplicaciones de forma sencilla, que además proporciona una capa extra de
seguridad. Dicho esto, es posible mejorar la seguridad con medidas sencillas:
NO ejecutes como root los procesos dentro del contenedor, usa
un usuario sin privilegios:
$ docker run -u=www-data yourapp:nginx
con -u hay que indicar un usuario o uid existente en el contenedor.
Limita la memoria disponible para los procesos del contenedor con la
opción -m, por ejemplo 200MB:
$ docker run -m=200m yourapp:nginx
Limita el uso de cpu, permitiendo la ejecución únicamente en los
nucleos/cpus especificados con --cpuset:
$ docker run --cpuset=0,1 yourapp:nginx
NO uses volúmenes para montar el sistema de archivos del host en el
contenedor, es cómodo pero es difícil de configurar correctamente
para que sea seguro. Si no tiene mas remedio que usarlos, móntalos en modo
lectura:
$ docker run -v /host/static:/container/static:ro
Utiliza versiones reciente del kernel, como he comentado anteriormente
todas las vulnerabilidades del kernel son explotables desde el contenedor,
instala versiones recientes y esta atento a la aparición de nuevos exploits.
Usa una máquina virtual en la que ejecutar docker, de esta manera tienes
lo mejor de los dos mundos, la seguridad de una VM, con la facilidad
para desplegar aplicaciones de docker.
Comandos útiles
Una vez la aplicación esté correctamente configurada, puedes construir el
contenedor ejecutando el siguiente comando desde el directorio Docker:
$ sudo docker build --rm:True -t yourapp:nginx .
y deberia aparecer al listar las imágenes disponibles:
$ sudo docker.io images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
yourapp nginx fb3367f96602 4 minutes ago 544.4 MB
ubuntu 14.04 826633116fdc 5 days ago 194.2 MB
y por ultimo se ejecuta el contenedor con:
$ sudo docker run --rm -a stdout -p 80:80 yourapp:nginx
El parámetro -p 80:80 le indica a docker que publique el puerto 80 del
contenedor en el puerto 80 local, de forma que el contenedor sea accesible
desde el exterior. -rm indica que el contenedor debe ser eliminado
una vez finalice.
Por defecto para crear y ejecutar contenedores es necesario, tener privilegios
de root, esto es debido a que el demonio docker se ejecuta como root,
y solo es posible comunicarse con el a través de de un socket unix, propiedad
del usuario root y el grupo docker.
Para que un usuario sea capaz de manejar contenedores, solo es necesario
añadirlo al grupo docker, y reiniciar el demonio:
$ sudo usermod -a -G docker gowen
$ sudo service docker restart
Click to read and post comments
jun 25, 2014
Un problema muy común en cualquier aplicación django es la necesidad de
almacenar algún dato confidencial para su funcionamiento, por ejemplo
una clave, nombre de usuario, o identificador de un API, esto se suele hacer en el archivo de configuración settings.py, pero es una mala practica de
seguridad.
La solución es definir variables de entorno en el shell, e importarlas desde
settings.py, de manera que sea mas difícil que un fallo exponga la
información.
#!/bin/bash
#.bashrc
export EMAIL_HOST_USER="tuemail@gmail.com"
export EMAIL_HOST_PASSWORD="tuclave"
#settings.py
import os
EMAIL_HOST_USER=os.environ['EMAIL_HOST_USER']
EMAIL_HOST_PASSWORD=os.environ['EMAIL_HOST_PASSWORD']
La limitación de este sistema cuando se está usando virtualenv, es que no
permite tener distintos valores de una variable para cada entorno. Esto se
puede solucionar usando los hooks .virtualenvs/app_env/bin/postactivate
para establecer las variables al entrar en el entorno:
#!/bin/bash
# This hook is run after this virtualenv is activated.
export EMAIL_HOST_USER="tuemail@gmail.com"
export EMAIL_HOST_PASSWORD="tuclave"
y .virtualenvs/app_env/bin/predeactivate para limpiarlas al salir:
#!/bin/bash
# This hook is run before this virtualenv is deactivated.
unset EMAIL_HOST_USER
unset EMAIL_HOST_PASSWORD
Click to read and post comments