Category Archives: Tips

Elastest & Amazon Dash Button

Here, at Universidad Rey Juan Carlos, where we’re working hard to deploy Elastest, we’ve the need to run instances on AWS all the time. We, of course, have defined CloudFormation Templates to do the work but we can go further with just push a button (a physical one, indeed) so every time a team member needs to test something on Elastest, push the button!

Let’s see how it works

First of all, you need an Amazon Dash Button, I chose Optimum Nutrition a brand for gym diet, don’t ask why.

To configure the device, we follow the steps from Amazon’ guide till we have to choose a product. We avoid that because we don’t want to order products each time we push the button. So we’ve the device attached to our network and we need to know the MAC address.

We’re gonna use a Python project Amazon Dash to do that. First, we install it:

$ sudo pip install amazon-dash  # and after:
$ sudo python -m amazon_dash.install

Scan the network to find the device:

$ sudo amazon-dash discovery

Launch the command and wait like 10 seconds, then push the button and you’ll see the MAC address. Copy it for the next step.

Dash Button is based on systemd (which means that you must stop and start it on each change) and it has a yaml file for configuration (in /etc/amazon-dash.yml). It’s quite simple for out needs, should look like:

# amazon-dash.yml
# ---------------
settings:
  # On seconds. Minimum time that must pass between pulsations.
  delay: 10
devices:
  fc:65:de:1b:2e:8a:
    name: Elastest-dashbutton
    user: nordri
    cmd: PATH/TO/deploy-elastest.sh

For each device we have, we can define a stanza with a command to run. We set the user that will be the user who run the command. As you can imagine, the script is an AWS Cli command for CloudFormation like this one:

#!/bin/bash

DATE_SUFFIX=$(date +%s)

aws cloudformation create-stack \
  --stack-name Elastest-dashbutton-$DATE_SUFFIX \
  --template-url https://s3-eu-west-1.amazonaws.com/aws.elastest.io/cloud-formation-latest.json \
  --parameters '[{ "ParameterKey": "KeyName", "ParameterValue": "kms-aws-share-key" }]' \
  --profile naeva

Feel free to use the script on its own. Just remember to change the RSA key you use to access your instances on AWS and the auth profile.

That’s it, from now on, every push will result in an Elastest instance running on AWS.

Here, one could say

OK, mate, that’s pretty cool but deploying Elastest takes like 400 seconds and pushing a button won’t tell me when the instance is ready and, where should I connect to use the platform

Fair enough! Let’s configure the notifications.

Amazon Dash has confirmations to actually confirm that the button was pushed, but as our friend said before, we should notify when the instance is ready, to do that, we’re using a Telegram Bot. It’s quite simple to ask the Telegram’ @botfather for an Auth Token and start using it. So, out script to deploy Elastest now is like:

#!/bin/bash

DATE_SUFFIX=$(date +%s)

# Telegram data
USERID="...."
KEY="...."
TIMEOUT="10"
URL="https://api.telegram.org/bot$KEY/sendMessage"

aws cloudformation create-stack \
  --stack-name Elastest-dashbutton-$DATE_SUFFIX \
  --template-url https://s3-eu-west-1.amazonaws.com/aws.elastest.io/cloud-formation-latest.json \
  --parameters '[{ "ParameterKey": "KeyName", "ParameterValue": "kms-aws-share-key" }]' \
  --profile naeva

aws cloudformation wait stack-create-complete --stack-name Elastest-dashbutton-$DATE_SUFFIX --profile naeva
ELASTEST_URL=$(aws cloudformation describe-stacks --stack-name Elastest-dashbutton-$DATE_SUFFIX --profile naeva | jq --raw-output '.Stacks[0] | .Outputs[0] | .OutputValue')

TEXT="New Elastest deployed on $ELASTEST_URL"
curl -s --max-time $TIMEOUT -d "chat_id=$USERID&disable_web_page_preview=1&text=$TEXT" $URL > /dev/null

And, anyway, let’s configure the confirmation, so we’ll know when the button is pushed. To do so, see this Amazon Dash’ configuration file:

devices:
  fc:65:de:1b:2e:8a:
    name: Elastest-dashbutton
    user: nordri
    cmd: PATH/TO/deploy-elastest.sh
    confirmation: Telegram
confirmations:
  Telegram:
    service: telegram
    token: '...'
    to: ...
    is_default: true

Now, every time one of the team member needs an AWS instance to test Elastest, we’ll receive a notification at the beginning and another as soon as it’s ready.

Dash button cost 5€ and keep me busy for a day, not bad investment at all 😉

Docker: Mapear usuarios dentro del contenedor

En ocasiones que trabajamos con Docker necesitamos generar ficheros. Los ficheros que se generan en el contenedor por defecto pertenecen a root. Vamos a ver una forma para mapear usuarios del sistema dentro del contenedor. En concreto nos interesa mapear nuestro propio usuario deforma que el propietario de un fichero sea el mismo dentro y fuera del contenedor.

Tenemos al usuario foo que tiene una entrada en /etc/passwd como esta:

foo:x:1001:1001::/home/foo:/bin/bash

Empecemos por preparar un directorio de trabajo para foo dentro del contenedor.

$ mkdir ~/docker_home
$ cp /etc/skel/{.bash_logout,.bashrc,.profile} ~/docker_home

Ahora cuando vayamos a lanzar el contenedor, debemos mapear el usuario:

$ docker run -ti \
 -v /etc/passwd:/etc/passwd \
 -v /etc/group:/etc/group \
 -v /etc/shadow:/etc/shadow \
 -v /home/foo/docker_home:/home/foo ubuntu:16.04

La entrada al contenedor seguirá siendo con el usuario root pero ahora podemos hacer:

su - foo

Para trabajar con nuestro usuario.

Si queremos entrar directamente con nuestro usuario hacemos así:

$ docker run -ti \
 -v /etc/passwd:/etc/passwd \
 -v /etc/group:/etc/group \
 -v /etc/shadow:/etc/shadow \
 -v /home/foo/docker_home:/home/foo ubuntu:16.04 \
 su -s /bin/bash -c "/bin/bash" foo

Por último si queremos que el contenedor sea más caja negra debemos tener en cuenta que hay que modificar el Dockerfile a la hora de construir la imagen.

VirtualBox: Levantar Máquinas Desde la Consola

En el caso que necesitemos levantar alguna máquina virtual de VirtualBox desde la consola, bien porque no tengamos entorno gráfico, bien porque estemos en una sesión SSH, el procedimiento es bastante sencillo.

En primer lugar debemos identificar el ID de la máquina en cuestión, que lo hacemos así.

vboxmanage list vms

Veremos algo como esto:

"DebianApache" {627b6646-6aa3-4db7-ae86-c05cbbd204ab}
"Debian-PXE-DHCP-FTP-Server" {f4882e22-9216-4b8d-a1ac-c160cbb60fe0}
"Debian-6.0-ClonBlog" {158cf271-87b7-4af2-b1c9-248520480ee7}
"Windows" {4b9523ad-a08a-4412-83c8-bd59ac3670df}
"Debian-JMeter-Cookbook" {46a89076-e079-4135-ac95-834e1fde7df0}
"Linux Mint" {c49d2f32-da3b-4243-af8a-e5e4e04f27bb}
"Debian 7 Django" {fa63dab2-4a12-48a6-9edc-6ed412989a7f}
"Debian 7 Asterisk OpenWebinars" {5e418161-7e3e-4b72-b980-7756fceaeedd}
"Debian NGINX" {07b812af-7dad-45cb-94e7-975df59b783d}
"Debian 7 Database" {07c41d80-bd81-4d84-8718-e441c06739b9}
"Debian 7 WebServer" {11435e1c-f421-45c4-89d6-5743596d2410}
"Ubuntu-LEMP-Varnish" {b3dbfb0d-d0d0-41bf-a36b-08db8fabdafc}
"Debian GlusterFS #1" {1d6206ad-b0c0-4a35-aea0-f990f76903ce}
"Debian GlusterFS #2" {75566833-2cb6-4279-aed1-9b6b374728fd}
"Debian GlusterFS Client" {3177a875-c5b3-4c56-b456-d9646d786f72}
"CentOS Gluster #1" {e58beaf2-4dba-4fc9-ad3d-5658f62b0dea}
"CentOS Gluster #2" {da99b182-884f-442e-91f6-877eb7dd8428}
"CentOS Gluster #3" {2d89ca31-bc31-4b41-831b-fec413c5abf9}
"CentOS GlusterFS Client" {2f396eab-8a02-47bb-bc53-761dad6a3a6a}
"Zentyal 3.5" {a5f70d06-f044-4969-8b57-e4bb9f963337}
"Ubuntu" {3b7d4c85-f4d4-4a42-a072-0bd143b55785}
"Centos LAMP WordPress" {7b45f364-ef43-4198-b047-7dcad484c812}
"Debian-7-Tomcat" {397eb08a-75b7-4ebe-b98a-69a89acbe122}

Y ya sólo elegimos la máquina que queremos levantar, por ejemplo la base de datos que tenemos en Debian 7

vboxmanage startvm 07c41d80-bd81-4d84-8718-e441c06739b9 --type headless

Y listo.

OpenVZ: Habilitar Acceso A Internet Dentro De Los Containers

Tras crear containers en nuestra máquina OpenVZ puede que necesitemos habilitar acceso a internet dentro de ellos para, por ejemplo, realizar actualizaciones.

Para habilitar que los containers, que sólo tienen IP privadas, tengan acceso a Internet deberemos configurar SNAT (Source Network Address Translation, también conocido como enmascaramiento de IP) en el nodo físico. Para esto utilizaremos unas simples reglas de iptables como la que se muestran a continuación:

# iptables -t nat -A POSTROUTING -s private_ip -o eth0 -j SNAT --to public_ip

Por ejemplo, si nuestra máquina física utiliza una IP pública 88.88.88.88 y queremos utilicen una IP privada 192.168.1.0/24 tendriamos que escribir:

# iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to 88.88.88.88

También tendremos que habilitar una regla para reenvio de paquetes así:

# iptables -A FORWARD -s 192.168.1.0/24 -j ACCEPT
# iptables -A FORWARD -d 192.168.1.0/24 -j ACCEPT

Con esto ya podríamos probar como desde dentro de algún container disponemos de acceso completo a Internet

vzctl exec $CTID ping debian.org

Shared Web Hosting con Varnish

Hemos decidido montar un OpenVZ sobre un servidor físico para ofrecer VPSs a nuestro clientes. Cada cliente dispondrá de un servidor virtual (container) para construir su web. ¿Cómo podemos compartir el web hosting entre todos los VPSs?

De entre todas las opciones posibles vamos a hacerlo con Varnish configurado como Proxy

Segun Wikipedia:

Varnish Cache es un acelerador de aplicaciones web, también conocido como caché de proxy HTTP inversa. Se instala delante de cualquier servidor HTTP y se configura para almacenar en caché una copia del recurso solicitado. Ideado para aumentar el rendimiento de las aplicaciones web.

Lo primero que debemos hacer es instalar varnish

# apt-get install varnish

Debemos ajustar los parámetros para iniciar el demonio:

# vim /etc/default/varnish

Incluimos la configuración necesaria:

VARNISH_RUN_USER=varnish
VARNISH_RUN_GROUP=varnish

START=yes

# Maximum number of open files (for ulimit -n)
NFILES=131072
# Locked shared memory (for ulimit -l)
# Default log size is 82MB + header
MEMLOCK=82000
# Maximum number of threads (for ulimit -u)
NPROCS="unlimited"
# Maximum size of corefile (for ulimit -c). Default in Fedora is 0
# DAEMON_COREFILE_LIMIT="unlimited"

RELOAD_VCL=1

# # Should probably change this
VARNISH_VCL_CONF=/etc/varnish/default.vcl

# # Not setting VARNISH_LISTEN_ADDRESS makes Varnish listen on all IPs on this box
# # (Both IPv4 and IPv6 if available). Set manually to override this.
# VARNISH_LISTEN_ADDRESS=
VARNISH_LISTEN_PORT=80

# # Telnet admin interface listen address and port
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1
VARNISH_ADMIN_LISTEN_PORT=6082

# # Shared secret file for admin interface
VARNISH_SECRET_FILE=/etc/varnish/secret

# # The minimum number of worker threads to start
VARNISH_MIN_THREADS=50

# # The Maximum number of worker threads to start
VARNISH_MAX_THREADS=5000

# # Idle timeout for worker threads
VARNISH_THREAD_TIMEOUT=120

# Best option is malloc if you can. malloc will make use of swap space smartly if
# you have it and need it.
VARNISH_STORAGE_TYPE=malloc

# # Cache file size: in bytes, optionally using k / M / G / T suffix,
# # or in percentage of available disk space using the % suffix.
VARNISH_STORAGE_SIZE=2G

VARNISH_STORAGE="${VARNISH_STORAGE_TYPE},${VARNISH_STORAGE_SIZE}"

# # Default TTL used when the backend does not specify one
VARNISH_TTL=60

# # DAEMON_OPTS is used by the init script.  If you add or remove options, make
# # sure you update this section, too.
DAEMON_OPTS="-a ${VARNISH_LISTEN_ADDRESS}:${VARNISH_LISTEN_PORT} 
             -f ${VARNISH_VCL_CONF} 
             -T ${VARNISH_ADMIN_LISTEN_ADDRESS}:${VARNISH_ADMIN_LISTEN_PORT} 
             -t ${VARNISH_TTL} 
             -w ${VARNISH_MIN_THREADS},${VARNISH_MAX_THREADS},${VARNISH_THREAD_TIMEOUT} 
             -u ${VARNISH_RUN_USER} -g ${VARNISH_RUN_GROUP} 
             -S ${VARNISH_SECRET_FILE} 
             -s ${VARNISH_STORAGE}"

A continuación definimos la configuración del proxy:

backend default {
  .host = "127.0.0.1";
  .port = "80";
}

backend client1 {
        .host = "192.168.1.6";
        .port = "80";
}

backend client2 {
        .host = "192.168.1.7";
        .port = "80";
}

sub vcl_recv {
    set req.grace = 10s;
    
    if (req.http.host ~ "client1.example.com") {
      set req.backend = client1;
    } else if (req.http.host ~ "client2.example.com") {
      set req.backend = client2;
    } else {
      set req.backend = default;
    }
[...]

Comprobamos que la sintaxis está correcta:

# varnishd -C -f /etc/varnish/default.vcl

Si no devuelve ningún error, podemos reiniciar el demonio:

# service varnish restart