miércoles, 20 de agosto de 2014

Capistrano y Monit

Últimamente me ha pasado un par de veces que al hacer una subida a producción, Capistrano y Monit no se llevan muy bien.

El problema es que en el deploy se reinician algunos servicios, como Sidekiq, y si coincide con el chequeo de Monit, se lanza otro reinicio, con el resultado de tener 2 Sidekiq arrancados en producción.

Tambien puede pasar que quieras parar algún servicio manualmente, por ejemplo Unicorn desde una tarea de Capistrano y Monit lo levante automáticamente.

Afortunadamente, la solución es simple, consiste en añadir unas tareas en Capistrano y dar permisos al usuario de la aplicación para ejecutar Monit.

Para ejecutar los comandos de Monit, se necesitan permisos de sudo, pero por seguridad, no queremos dar acceso sudo sin más. Tampoco interesa dar acceso a toda la funcionalidad de Monit, ya que se podría parar cualquier servicio monitorizado con Monit externo a la aplicación.

Así que solo daremos permiso para ver el estado de la monitorización (monit summary) y parar o iniciar la monitorización (monit monitor y monit unmonitor)

La forma de conseguirlo es tan simple como añadir un nuevo fichero /etc/sudoers.d/monit con esos permisos
my_user ALL = NOPASSWD: /usr/bin/monit summary, /usr/bin/monit unmonitor *, /usr/bin/monit monitor *
No le pedimos la contraseña al usuario para ejecutar estos comandos, ya que el usuario no tiene contraseña asignada (solo le permitimos acceder por medio de clave pública).

Y ese mismo fichero creado desde ansible

- name: Allow execute monit commands to users
  action: 'lineinfile dest=/etc/sudoers.d/monit state=present create=yes regexp="{{item}}" line="{{item}} ALL = NOPASSWD: /usr/bin/monit summary, /usr/bin/monit unmonitor *, /usr/bin/monit monitor *" validate="visudo -cf %s"'
  with_items:
    - my_user
Una vez que tenemos permisos, creamos un módulo de capistrano con las diferentes tareas a ejecutar en lib/capistrano/tasks


namespace :monit do
  desc 'Monit summary'
  task :summary do
    on roles :app do
      puts capture :sudo, :monit, :summary
    end
  end

  desc 'Monit unmonitor'
  task :unmonitor do
    on roles :app do
      puts capture :sudo, :monit, :unmonitor, :sidekiq
    end
  end

  desc 'Monit monitor'
  task :monitor do
    on roles :app do
      puts capture :sudo, :monit, :monitor, :sidekiq
    end
  end
end

Y invocamos esas tareas al empezar y al acabar el deploy

after 'deploy:starting',  'monit:unmonitor'
after 'deploy:finished',  'monit:monitor'
Además, la tarea summary permite al usuario monitorizar el estado del servidor desde línea de comandos
$ cap production monit summary
...
System 'teowaki.com'                Running
Process 'sidekiq'                   Running
Process 'sshd'                      Running
Filesystem 'rootfs'                 Accessible
...
Por último, comentar que la gema capistrano-sidekiq incluye algunas tareas similares, pero no me cuadraba su uso en mi caso, ya que me obliga a cambiar el nombre del servicio monitorizado.

lunes, 30 de junio de 2014

Ansible y Digital Ocean

Justo hoy tenía prevista hacer una subida a producción del API de Teowaki con bastantes cambios de configuración que llevo probando un tiempo en local.

La subida incluye la actualización a Rails 4.1.2, y bastantes gemas, entre las que está Capistrano 3, que ha supuesto reescribir los ficheros de deploy. Para acabar de completarlo, tambien quiero actualizar a Ruby 2.1.2.

Está claro que esta subida tiene todos los números para que algo falle y dejar caído producción un rato...

Así que he optado por la opción de crear un nuevo servidor donde probar todos esos cambios y aprovecho para ir actualizando a Ubuntu Trusty. Una vez creado el servidor y comprobado que todo está bien, simplemente cambiaré el balanceador para que apunte al nuevo servidor y apagaré el servidor antiguo.

Aprovechando estos cambios le he dado un nuevo empujón a la configuración de ansible, creando los servidores desde los playbook en lugar de crearlos desde la consola de Digital Ocean, como hacía hasta ahora.

Continuando la entrada del mes pasado, donde conté como hacer Bootstrap con Ansible, aquí cuento como crear los servidores.

Según la documentación, el primer paso es instalar las dependencias, en este caso sólo dopy, un wrapper de la API de Digital Ocean en Python.
$ sudo pip install dopy
Para facilitar la configuración, nos bajamos los ficheros de inventorio
$ wget https://raw.githubusercontent.com/ansible/ansible/devel/plugins/inventory/digital_ocean.py
$ chmod +x digital_ocean.py
$ wget https://raw.githubusercontent.com/ansible/ansible/devel/plugins/inventory/digital_ocean.ini
Creamos un par de variables de entorno con la API_KEY y el CLIENT_ID, que los consultamos en la configuración de Digital Ocean
export DO_API_KEY=MI_API_KEY
export DO_CLIENT_ID=MI_CLIENT_ID
Y chequeamos si la configuración es correcta listando los droplets que tenemos creados (con -h o --help puedes ver las opciones disponibles)
$./digital_ocean.py --droplets --pretty
Para crear un droplet, necesitamos 4 datos principales, la imagen que usaremos para crearlo, el tamaño, la region donde lo creamos y la clave SSH que usaremos para acceder. Todos esos datos tienen un ID único en Digital Ocean, que podemos consultar con el script anterior (con los parámetros images, sizes, regions y ssh-keys)
$ ./digital_ocean.py --sizes --pretty
{
  "sizes": [
    {
      "cost_per_hour": "0.00744", 
      "cost_per_month": "5.0", 
      "cpu": "1", 
      "disk": "20", 
      "id": "66", 
      "memory": "512", 
      "name": "512MB", 
      "slug": "512mb"
    }, 
...
Para simplificar la creación de nuevos droplets, he añadido en el fichero hosts una sección local (el droplet se crea desde localhost, no desde un servidor remoto)
[local]
localhost
Y un fichero de variables de grupo  
$ more group_vars/local.yml 
---
# SIZES:
#   "slug": "512mb", "name": "512MB", "id": "66", "disk": "20", "cpu": "1", "cost_per_month": "5.0"
#   "slug": "1gb", "name": "1GB", "id": "63", "disk": "30", "cpu": "1", "cost_per_month": "10.0"
#   "slug": "2gb", "name": "2GB", "id": "62", "disk": "40", "cpu": "2", "cost_per_month": "20.0",
do_size_512mb: 66
do_size_1gb: 63
do_size_2gb: 62
# IMAGES:
#   "slug": "ubuntu-14-04-x64", "distribution": "Ubuntu", "id": "3240036", "name": "Ubuntu_14.04_x64",
do_image_ubuntu_14_04_x64: 3240036
# REGIONS;
#   "slug": "nyc2", "id": "4", "name": "New_York_2",
do_region_nyc2: 4
# KEYS:
#   "id": "xxxx", "name": "diego"
do_diego_key:  xxxx

Y ya solo nos queda decidir la configuración y el nombre del servidor (SERVER_NAME en el playbook)
---
- hosts: local
  connection: local
  tasks:
  - name: Create new Digital Ocean Droplet
    digital_ocean: >
      state=present
      command=droplet
      name=SERVER_NAME
      size_id={{ do_size_1gb }}
      region_id={{ do_region_nyc2 }}
      image_id={{ do_image_ubuntu_14_04_x64 }}
      private_networking=yes
      ssh_key_ids={{ do_diego_key }}

Una vez creado, ejecutamos el bootstrap de mi post anterior y los roles del servidor.

Existen soluciones mucho más completas para provisionar los servidores como ansible-provisioner, pero en mi caso con hacer la creación manualmente modificando este playbook es más que suficiente.





miércoles, 28 de mayo de 2014

Bootstrap con Ansible

La semana pasada en el grupo DevOps de Madrid, hubo una magnífica charla introductoria de Maykel Moya sobre Ansible. Parte de lo que explico en este post lo contó con una demo en directo.

Estuve hablando con Javier Vidal sobre el tema y al día siguiente me comento en twitter



Y en lugar de compartirselo sólo a Javi, he creado esta entrada en el blog :)

Para ello, he separado el role de bootstraping de nuestra instalación de Teowaki en un proyecto independiente y lo he configurado con Vagrant, para poder probarlo en local sin problemas.

El proyecto está disponible en github.

Los pasos para probarlo son:

Instalar Vagrant en local (previamente debe estar instalado VirtualBox). He optado por instalarlo a partir del .deb en lugar de a partir de apt porque la opción apt me instalaba los paquetes de ruby 1.9, y ruby ya lo tengo instalado en local con RVM.

$ wget https://dl.bintray.com/mitchellh/vagrant/vagrant_1.6.2_x86_64.deb
$ sudo dpkg -i vagrant_1.6.2_x86_64.deb
$ vagrant -v
Vagrant 1.6.2
$ vagrant init ubuntu/trusty64
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

Una vez instalado, simplemente con vagrant up levantamos el servidor, que escucha por defecto por ssh en el puerto 2222 en localhost

Más información sobre la instalación en las páginas de Vagrant y Ansible.

El siguiente paso es configurar el servidor en Ansible, que es tan simple como definir en un fichero hosts los datos de conexión al servidor. En mi caso he definido un grupo de servidores 'development' que incluye al servidor dev

[development]
dev ansible_ssh_host=127.0.0.1 ansible_ssh_port=2222

Por otra parte configuro la conexión a vagrant en ansible.cfg, de forma que no hay que escribirla en cada conexión desde ansible

[defaults]
hostfile = hosts
private_key_file=~/.vagrant.d/insecure_private_key
remote_user = vagrant

Y probamos que la conexión al servidor desde ansible funciona

$ ansible dev -m ping
dev | success >> {
    "changed": false, 
    "ping": "pong"
}

Sin el fichero de configuración la instrucción habría sido:

$ ansible dev -i hosts --private-key=~/.vagrant.d/insecure_private_key -u vagrant -m ping

Una vez comprobado que conectamos, cambiamos el fichero development.yml con la configuración local y ya podemos instalar el servidor


$ ansible-playbook bootstrap.yml

Por último, las tareas incluídas en el role son:
  • Instalación y actualización de paquetes de ubuntu
  • Copia de ficheros de configuración, en este caso solo /etc/environment
  • Configuración del locale
  • Creación de usuarios, asignando grupos y clave pública
  • Configuración de seguridad, instalando Fail2ban y cambiando ssh para que no permita ni acceso SSH de root ni acceso con contraseña (sólo por clave pública)
  • Instalación y configuración de Nullmailer y Mandrill. El motivo de uso lo explique en mi anterior post
  • Instalación de la monitorización de servidores de NewRelic


  • Para que funcione correctamente la instalación se debe actualizar el fichero development.yml con la configuración local (usuarios y sus claves públicas, emails, dominio, claves de acceso a Mandrill y NewRelic, ...) tal y como cuento en el README del proyecto.


    jueves, 24 de abril de 2014

    Nullmailer y Mandrill como alternativa a un servidor de mail local

    Nunca me ha gustado la idea de tener un servidor de mail local instalado en producción, por dos motivos principales:

    El primero es que según el uso (o mal uso) que le des al mail desde tu aplicación, puede que los usuarios te marquen como spam y que la IP de tu servidor se añada a una lista negra. En ese caso, tus mails salientes quedarán bloqueados y no se enviarán, y conseguir que te borren de una lista negra no es tarea fácil.

    El segundo es que hay que configurarlo, he escuchado muchas historias de miedo de lo doloroso que es administrar un servidor de correo y prefiero no pasar por eso.

    Hace un tiempo, la opción habitual era utilizar un servicio externo para las newsletters, como MailChimp y el resto de mails enviarlos desde la aplicación.

    El siguiente paso natural fue enviar tambien el mail transaccional de la aplicación (bienvenida, olvido de password, ...) desde una plataforma externa, usando su SMTP o su API. Un ejemplo sería Mandrill y las diferentes integraciones que tiene.

    Pero en nuestro caso queríamos ir un paso más y directamente no tener un servidor local para envío de los mails de sistema (p.e. los cron). Y aquí es donde aparece Nullmailer.

    Nullmailer es un MTA que usa el mismo formato que sendmail y permite redirigir el mail saliente a un servidor externo, de forma que desde tu servidor nunca se enviará el mail.

    En Ubuntu Trusty está disponible como paquete. La versión en Ubuntu Precise no soporta SSL, pero hay un backport que si lo soporta.

    La configuración es bastante simple, solo hay que cambiar 4 ficheros en el directorio /etc/nullmailer:

    • adminaddr con el email del administrador
    • me con el nombre del servidor
    • defaultdomain con el dominio de tu servidor
    • remotes con la configuración del servidor de correo

    La configuración completa la podeis encontrar perfectamente explicada en este post de OpenSourceHacker , escrito por el mismo que hizo el backport de Nullmailer a Precise


    Por último, destacar que Nullmailer no escucha en el puerto 25 como cualquier SMTP, así que si algún servicio depende de SMTP, se debe configurar por separado.

    Un ejemplo de este último caso es monit, donde se debe configurar un servidor para enviar las alertas. Así que lo que haremos, será que apunte directamente al SMTP de mandrill

      server {
      set mailserver smtp.mandrillapp.com port 587
        username "youremail@domain.com" password "your_password"
        using tlsv1
      }
    


    lunes, 24 de marzo de 2014

    Configurando SSL en nginx

    Informe SSL de teowaki.com por sslabs.com


    La configuración básica de SSL en nginx es muy sencilla. Lo primero es tener una versión de nginx compilada con el módulo ngx_http_ssl_module. Si usas la versión estandar de tu distribución linux lo más seguro es que ya tenga soporte. Puedes comprobarlo con

    $ nginx -V
    ...
    configure arguments: --prefix=/usr/share/nginx ...--with-http_ssl_module ...
    

    En el virtual host indicaremos que vamos a usar SSL y el path de los ficheros de certificados. El proveedor donde contrates el certificado te dirá los pasos para generar estos ficheros. Como ejemplo las instrucciones de Comodo y Linode

    server {
      listen        443 ssl;
    
      ssl_certificate      /path_to/cert.crt;
      ssl_certificate_key  /path_to/cert.key;
    
    }
    

    Simplemente con estas tres líneas ya tenemos nuestro servidor con soporte SSL funcionando. Pero para que tu configuración sea óptima, hay cuantos parámetros más a tener en cuenta.


    SPDY

    SPDY es un protocolo creado por google para reducir el tiempo de carga de las páginas web. Está soportado en Firefox y Chrome desde hace tiempo y en IE desde la version 11.

    Al igual que para tener soporte de SSL, se debe compilar nginx con el módulo correspondiente, ngx_http_spdy_module.

    Como me da mucha pereza compilar nginx cada vez que lanzan una nueva versión, tenemos dos alternativas, usar binarios precompilados por Passenger o usar un ppa que incluya ese módulo

    He optado por la segunda opción, usando los paquetes de Chris Lea. Están compilados usando la versión de desarrollo de nginx. Aunque es la versión de desarrollo es una versión estable que tambien usan en la web de nginx tal como explican en las FAQ.

    La configuración de nuevo es tan simple como añadir spdy en la directiva listen

      listen        443 ssl spdy;
    


    TLS

    La configuración por defecto de nginx soporta SSL v3 y TLS versiones 1.0, 1.1 y 1.2.

    Todos los navegadores modernos soportan TLS, y SSL v3 es un protocolo antiguo y tiene algunos problemas de seguridad, así que he decidido deshabilitarlo

      # enables TLS protocols, but not SSL which is weak and should no longer be used.
      ssl_protocols TLSv1 TLSv1.1 TLSv1.2;


    Strict Transport Security (HSTS)

    Si vamos a usar siempre conexión segura con nuestro servidor, añadiendo la cabecera Strict Transport Security, indicamos al navegador que aunque el usuario escriba la URL con http, siempre debe conectarse de forma segura usando https.

    Es una buena práctica de seguridad y muchos servicios como Twitter o Paypal añaden esta cabecera en sus respuestas.

      # Remember this setting for 365 days
      add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
    

    Ciphers

    Hay mucha literatura sobre los algoritmos de cifrado que se deberían permitir, en nuestro caso he optado por seguir las recomendaciones de Comodo.

    Actualización 23-04-2014: Basado en este post sobre SSL Ciphers, he actualizado la configuración, desactivando RC4.

    Tambien he añadido otra directiva para que sea el servidor el que decida que protocolo usar entre los disponibles en lugar de dejar la elección al cliente.

      # Disables all weak ciphers
      ssl_prefer_server_ciphers on;
      ssl_ciphers ALL:!aNULL:!ADH:!eNULL:!LOW:!EXP:RC4+RSA:+HIGH:+MEDIUM;
      ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS;
    




    OCSP Stapling

    Al conectarse el navegador a una web segura, hace una petición adicional a la autoridad certificadora para asegurarse de que el certificado es válido usando OCSP (Online Certificate Status Protocol).

    Por medio de OCSP Stapling, se evita esa petición adicional incluyendo la respuesta OCSP en la misma conexión SSL, con lo que reducimos el tiempo de conexión.

    Puedes leer más en Cloudfare, en la Wikipedia, y comprobar si tu servidor lo soporta siguiendo las instrucciones de Unmitigated Risk.

      # enable ocsp stapling
      ssl_stapling on;
    


    SSL Session

    Por último, siguiendo las recomendaciones de nginx para reducir la carga del procesador habilitamos la caché de sesiones SSL y aumentamos el timeout

      ssl_session_cache    shared:SSL:10m;
      ssl_session_timeout  10m;
    



    Con esta configuración conseguimos una implementación SSL robusta y eficiente. Si conoces algún truco o mejora, por favor, dejalo en los comentarios, gracias!