jueves, 21 de julio de 2016

Secuencia de arranque con systemctl

systemctl ha llegado a Linux envuelto en polémica. En la evolución de este sistema operativo ya hemos vivido episodios similares. Yo creo que como suele ocurrir, ha venido para quedarse.
Ya que tenemos que lidiar con él vamos a sacarle algún partido extra que justifique el "trago".

Ubicación de la configuración
Lo que antes buscabas en tu /etc/rcX.d ya no está. Tampoco te funciona "/etc/init.d/servicioX start". Eso ha cambiado y hay una nueva ubicación y un sistema de dependencias en el arranque diferente. Si eres de los míos y quieres saber donde se esconde y qué forma tiene la configuración aquí tienes algunas indicaciones:

# ls -al /etc/systemd/system/
total 12
drwxr-xr-x. 10 root root 4096 jul 21 10:40 .
drwxr-xr-x.  4 root root 4096 abr 25 13:35 ..
drwxr-xr-x.  2 root root   30 jul 21 10:40 basic.target.wants
lrwxrwxrwx.  1 root root   46 abr 25 13:28 dbus-org.freedesktop.NetworkManager.service -> /usr/lib/systemd/system/NetworkManager.service
lrwxrwxrwx.  1 root root   57 abr 25 13:28 dbus-org.freedesktop.nm-dispatcher.service -> /usr/lib/systemd/system/NetworkManager-dispatcher.service
lrwxrwxrwx.  1 root root   37 abr 25 13:29 default.target -> /lib/systemd/system/multi-user.target
drwxr-xr-x.  2 root root   85 abr 25 13:28 default.target.wants
drwxr-xr-x.  2 root root   31 abr 25 13:28 getty.target.wants
drwxr-xr-x.  2 root root 4096 may 18 13:59 multi-user.target.wants
drwxr-xr-x.  2 root root   30 may 18 13:59 remote-fs.target.wants
drwxr-xr-x.  2 root root   75 may 18 13:59 sockets.target.wants
drwxr-xr-x.  2 root root   86 abr 25 13:35 sysinit.target.wants
drwxr-xr-x.  2 root root   43 abr 25 13:28 system-update.target.wants


# ls /etc/systemd/system/multi-user.target.wants
atop.service      crond.service       kdump.service           nfs-client.target  
postfix.service   rsyslog.service     sshd.service            vmtoolsd.service
auditd.service    irqbalance.service  NetworkManager.service  ntpd.service       
remote-fs.target  snmpd.service       tuned.service


# cat /etc/systemd/system/multi-user.target.wants/atop.service
[Unit]
Description=advanced interactive monitor
After=syslog.target

[Service]
Type=forking
PIDFile=/var/run/atop.pid
ExecStart=/usr/bin/atopd
ExecStop=/bin/rm -f /var/run/atop.pid

[Install]
WantedBy=multi-user.target

Secuencia de arranque
Seguro que todos tenemos más o menos una idea de cómo arranca nuestro equipo con Linux. Me refiero a la secuencia en las que las cosas ocurren. Podemos saber con precisión qué depende de qué con el comando

# systemd-analyze critical-chain
The time after the unit is active or started is printed after the "@" character.
The time the unit takes to start is printed after the "+" character.

multi-user.target @22.077s
└─tuned.service @17.251s +4.824s
  └─network.target @17.233s
    └─network.service @15.516s +1.715s
      └─NetworkManager.service @7.451s +1.904s
        └─basic.target @7.265s
          └─sockets.target @7.265s
            └─dbus.socket @7.264s
              └─sysinit.target @7.253s
                └─systemd-update-utmp.service @7.201s +50ms
                  └─auditd.service @6.744s +452ms
                    └─systemd-tmpfiles-setup.service @6.647s +94ms
                      └─rhel-import-state.service @6.536s +108ms
                        └─local-fs.target @6.505s
                          └─boot.mount @5.125s +1.375s
                            └─systemd-fsck@dev-disk-by\x2duuid-0a013536\x2d8d16\x2d4bde\x2db03a\x2da664620130fe.service @4.737s +386ms
                              └─dev-disk-by\x2duuid-0a013536\x2d8d16\x2d4bde\x2db03a\x2da664620130fe.device @4.736s

Diagrama de Gant y tiempos de puesta en marcha
Podemos obtener más detalle en el informe generando un diagrama de Gant con marcas de tiempo. Para ello podemos usar el comando
systemd-analyze plot > boot-gant.svg

Esto nos va a generar un svg que puedes abrir sin problemas con cualquier explorador de internet (por ejemplo). Verás algo asi como el gráfico siguiente. Si lo analizas verás que te indice el tiempo en el que tu sistema operativo está completamente arriba desde cero.

La leyenda (abajo) te indica el estado de cada proceso a lo largo del tiempo.

martes, 29 de marzo de 2016

autofs

El demonio autofs es un gran desconocido en los sistemas Unix. No es que no lo conozca nadie, lo que pasa es que no se suele poner en funcionamiento por miedo a que un recurso no esté montado cuando se necesita. Vamos a aclarar algunas cuestiones a ver si gana adeptos. Yo más que recomendarlo, lo practico.

Empecemos por el principio. "autofs" es un servicio Unix/Linux que permite que un recurso se monte justo cuando se necesita. Pasado un tiempo de ociosidad (configurable, luego lo vemos), el recurso se desmonta.

Esto presenta ventajas desde varios puntos de vista:
  • Durante el arranque, el host no tiene que montar todos los sistemas de ficheros que pueden ser necesarios. Se montarán justo cuando empiecen a necesitarse. Se evitan los posibles sobresaltos y esperas durante el arranque.
  • El servidor NFS, FTP o del tipo que sea tendrá menos clientes conectados.
  • Durante el tiempo en el que el hots está encendido lo hará siempre con la menor cantidad de recursos montados.



Configuración básica

El fichero /etc/auto.master es el origen de la configuración. Los ficheros incluidos en él se incorporan a la configuración del demonio. Un contenido mínimo típico (quitando los comentarios):
/misc   /etc/auto.misc


La lectura que debemos hacer es que en el punto de montaje "/misc" se montarán los recursos declarados en el fichero "/etc/auto.misc".

El fichero /etc/auto.misc podría contener las siguientes 2 lineas:

cd              -fstype=iso9660,ro,nosuid,nodev :/dev/cdrom
recupera        -fstype=nfs,nolock servidor_nfs:/data/recuperaciones
Todo ello debe interpretarse como que virtualmente existen /misc/cd y /misc/recupera. Si sacamos un listado del directorio "/misc" puede que aparezca vacío, pero si accedemos a cualquiera de los 2 directorios virtuales se montará el correspondiente recurso y se mostrará el contenido.

Es muy importante que sepamos que el directorio /misc no debe contener nada. De hecho si contiene algo, al arrancar el demonio "autofs" desaparecerá virtualmente ya que el demonio gestiona por completo ese punto de montaje base. Esto es un pequeño inconveniente que solemos solventar con enlaces, como comento abajo.



Uso

Una vez configurado adecuadamente, usar el recurso es tan sencillo como acceder al recurso como si estuviera montado. En ese justo instante se monta si no lo está ya.

Un simple "ls" del punto de montaje servirá
ls /misc/recupera

La dificultad estriba, si cabe decirlo así, en saber donde está el recurso. Una operativa habitual consiste en crear un enlace simbólico del punto de montaje automático en el lugar donde lo querríamos tener. Supongamos para el ejemplo, que queremos tener el directorio de recuperaciones en el $HOME del root. Como hemos dicho, no es posible que el montaje automático ocurra en un directorio que usamos para otra cosa, pero si podemos hacer el enlace:

ln -s /misc/recupera /root/RECUPERA



Monitorización

  • Una vez montado se puede ver con el comando "mount", df, etc.., es decir, con cualquier comando que se nos ocurra.
  • El log del sistema almacena todos los hitos de montaje y desmontaje de los recursos del autofs.



Configuración avanzada

Una linea típica de configuración en el /etc/auto.misc puede contener cualquier opción que acepta el correspondiente comando "mount" para ese recurso. Una lectura atenta de la sintaxis nos permite adivinar dónde va el tipo de recurso y dónde las opciones. Un ejemplo más completo para el recurso NFS puede ser:
/recupera -fstype=nfs,rw,nosuid,nodev,hard,nolock,rsize=32768,wsize=32768,noatime,nodiratime servidor_nfs:/data/recuperaciones



La otra cuestión caliente es el tiempo que debe estar un recurso montado para evitar rebotes o que esté montado demasiado tiempo. En el fichero "auto.master" se puede modificar el timeout por defecto (suele ser 300 segundos). Por ejemplo:
/misc   /etc/auto.misc --timeout=60

En el ejemplo hemos rebajado a 60 segundos la permanencia del montaje. Cuanto más prolongado o frecuente sea el uso mayor tiene que ser el timeout. Cuanto mas esporádico sea el uso del recurso menor debería ser el timeout. Eso requiere de tu propio ajuste fino.

Finalmente decir que es posible tener montado el mismo recurso en distintos puntos de montaje. Esto es extensible a autofs. Puedes tener un recurso montado manualmente y también tenerlo configurado por autofs o en varios puntos de montaje autofs. La imaginación al poder.

Hay muchísimas posibilidades y detalles que escapan a la intención de primera aproximación de este post, por eso te recomiendo que mires documentación oficial similar a esta.

miércoles, 23 de marzo de 2016

Docker: acceso interactivo a consola


Un contenedor docker puede haber sido arrancado sin mapeo de puertos y sin sesión interactiva. En esos casos podemos albergar dudas sobre qué está ocurriendo en él. Otras veces queremos descubrir qué directorio interno debemos mapear con un volumen para conservar los cambios. Todo eso es posible de averiguar abriendo una sesión interactiva de shell con nuestro contenedor sin alterar su estado.

Cogemos de nuevo el post anterior y los contenedores que se crearon:
$ sudo docker ps -a
CONTAINER ID        IMAGE               [...]   NAMES
87a11363c32f        wordpress:latest    [...]   WPwordpress         
1eda9845dc71        mysql:latest        [...]   WPmysql             

Vamos a hacer una incursión con sesión interactiva para administrar algo de ambos.

Acceder por shell a un contenedor en ejecución

"docker attach" solo permite una conexión simultánea. Es incómodo y además attach detiene la ejecución del contenedor cuando salimos de la sesión interactiva. Usaremos la fórmula siguiente para abrir tantos shell como deseemos y no afectar al estado del contenedor.
$ sudo docker exec -i -t 87a11363c32f bash

Para salir simplemente "exit"
root@87a11363c32f:/var/www/html# exit

Gestionamos algo del contenedor Wordpress
Al entrar vemos donde están los contenidos estáticos y los PHP de Wordpress. Deberíamos hacer un mapeo a un volumen externo.
root@87a11363c32f:/var/www/html# ls
index.php    readme.html wp-admin wp-comments-post.php  wp-config.php  
wp-cron.php  wp-links-opml.php  wp-login.php  wp-settings.php  wp-trackback.php
license.txt  wp-activate.php  wp-blog-header.php  wp-config-sample.php  wp-content     
wp-includes  wp-load.php   wp-mail.php   wp-signup.php    xmlrpc.php


Conexión a una base de datos dentro del contenedor
Vamos a conectarnos a la base de datos con el cliente propio del contenedor:
root@goku:~# sudo docker exec -i -t 1eda9845dc71 bash
root@1eda9845dc71:/# mysql -u root -p
Enter password: 
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
root@1eda9845dc71:/# mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 29
Server version: 5.7.11 MySQL Community Server (GPL)

Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show schemas;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| wordpress          |
+--------------------+
5 rows in set (0.00 sec)

Hemos podido acceder al contenido, previa entrada a la consola del contenedor.

martes, 22 de marzo de 2016

Wordpress con docker y mysql

Uniendo los conceptos de docker de entradas anteriores del blog voy a proponerte una forma sencilla de desplegar un wordpress con contenedores docker.

La receta es tan sencilla como los 2 comandos siguientes y el paso final:
1.- Creamos la base de datos.
Le hacemos un mapeo del puerto (-p) por si queremos entrar a administrar o curiosear algo. No será necesario.
$ sudo docker run --name WPmysql -p 13307:3306 \
         -v /home/volumenes/WPmysql:/var/lib/mysql \
         -e MYSQL_ROOT_PASSWORD=mi-secreto -d mysql

30160004622edb51ef290fca4cf232b61ca519a674994c26a98127e6c424abc7

2.- Creamos el contenedor wordpress
$ sudo docker run --name WPwordpress --link WPmysql:mysql \
      -v /home/volumenes/WPwordpress/html:/var/www/html \
      -p 10080:80 -d wordpress
626c5706de2c062ecaa3479bd4f6abbcf96703d7353151dd2111d4e063bf3bf6

Debemos tener algo así:
$ sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                     NAMES
626c5706de2c        wordpress:latest    "/entrypoint.sh apac   5 seconds ago       Up 4 seconds        0.0.0.0:10080->80/tcp     WPwordpress         
30160004622e        mysql:latest        "/entrypoint.sh mysq   6 seconds ago       Up 5 seconds        0.0.0.0:13307->3306/tcp   WPmysql             
3.- Navegamos para terminar de configurar
Recupero un viejo clásico de la informática de administración. Se trata del comando "lynx" que muestra por consola su "best-effort" de la página en cuestión. Es un browser en consola de texto.
$ lynx http://localhost:10080
Lógicamente no tienes porqué usar "lynx". Puedes usar cualquier navegador o acceder desde tu escritorio si tienes accesible el anfitrión del contenedor por su IP y al puerto 10080.

Comentario final
Tal vez te hayas dado cuenta de que hay algo de magia en la poca configuración de Wordpress y sin embargo parece que llega a la base de datos. El secreto está en el switch "--link". Lo ampliaremos en otro post. De momento disfruta de tu wordpress reluciente.

lunes, 21 de marzo de 2016

Deshabilitar IPv6

Normalmente uno no es consciente de si utiliza ipv6 o solamente ipv4. Lo normal en entornos pequeños o heredados es que lo que usamos sea solamente ipv4. Si esto es así, podemos desactivar ipv6 de nuestros sistemas para aligerar la carga del sistema y para aclarar diagnósticos relacionados con la configuración de red.

El ejemplo que te muestro aquí es de un sistema Ubuntu 14.04.04 LTS

Comprobamos si tenemos IPv6 habilitado
# sysctl -a | grep "ipv6 ="
net.ipv6.conf.all.disable_ipv6 = 0
net.ipv6.conf.default.disable_ipv6 = 0
net.ipv6.conf.docker0.disable_ipv6 = 0
net.ipv6.conf.eth0.disable_ipv6 = 0
net.ipv6.conf.lo.disable_ipv6 = 0

Como podrás observar las entradas de configuración dicen que "disable_ipv6 = 0". De forma resumida lo podíamos haber preguntado con el siguiente comando:
# cat /proc/sys/net/ipv6/conf/all/disable_ipv6
0


El comando netstat suele mostrar las filas correspondientes a tcp6 y udp6 de forma separada. Puedes ver en el ejemplo que las conexiones se duplican para ipv6 e ipv4.
# netstat -l
Conexiones activas de Internet (solo servidores)
Proto  Recib Enviad Dirección local         Dirección remota       Estado      
tcp        0      0 *:sunrpc                *:*                     ESCUCHAR   
tcp        0      0 *:ssh                   *:*                     ESCUCHAR   
tcp        0      0 localhost:ipp           *:*                     ESCUCHAR   
tcp6       0      0 [::]:sunrpc             [::]:*                  ESCUCHAR   
tcp6       0      0 [::]:ssh                [::]:*                  ESCUCHAR   
tcp6       0      0 [::]:43446              [::]:*                  ESCUCHAR   
tcp6       0      0 ip6-localhost:ipp       [::]:*                  ESCUCHAR   
udp        0      0 *:1008                  *:*                                
udp        0      0 *:mdns                  *:*                                
udp        0      0 *:sunrpc                *:*                                
udp6       0      0 [::]:1008               [::]:*                             
udp6       0      0 [::]:mdns               [::]:*                             
udp6       0      0 [::]:sunrpc             [::]:*


¿ Y esto porqué es ?. Por que está fijado por defecto que se habilite, con cada nueva interfaz. Vamos a verlo.
# cat /proc/sys/net/ipv6/conf/default/disable_ipv6
0
Es decir, por defecto cualquier nueva interfaz también tendría ipv6 activado.

Deshabilitar ipv6 de forma temporal
Antes de deshabilitar ipv6 de nuestro sistema de forma permanente vamos a ver que pasa si lo quitamos sin afectar la configuración. Lo desconectamos un momento.
La forma más rápida es
# echo "1" > /proc/sys/net/ipv6/conf/all/disable_ipv6

Comprobamos que cada una de las interfaces ya lo tiene "disable".
# sysctl -a | grep "ipv6 ="
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.docker0.disable_ipv6 = 1
net.ipv6.conf.eth0.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1

Si trabajamos con normalidad, hacemos pruebas y todo va ok, te puedes plantear pasar al siguiente punto y dejarlo deshabilitado de forma permanente.

Deshabilitar ipv6 de forma permanente
Para hacerlo de forma permanente editaremos el fichero "/etc/sysctl.conf" con un editor de textos. Es posible que todo lo que contenga esté comentado, de forma que solo los valores por defecto se incorporan para que sean tenidos en cuenta en cada reinicio. Nosotros vamos a aportar uno de los bloques que hemos visto arriba.

Ten en cuenta que puedes ser selectivo, por ejemplo dejando ipv6 habilitado solo para la interfaz localhost
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.docker0.disable_ipv6 = 1
net.ipv6.conf.eth0.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 0

O quitar todo ipv6 de forma permanente.
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.all.disable_ipv6 = 1
Tras el reinicio del sistema, las preferencias de ipv6 se mantendrán como las hayamos indicado.
Otra forma de hacer permanente la configuración del fichero "/etc/sysctl.conf" es usar "sysctl -p".

miércoles, 16 de marzo de 2016

Docker: crear una base de datos (2/2)

Este post es la continuación de este otro. Ahora vamos a mejorar un poco el planteamiento a la vez que introducimos un concepto nuevo.

Como ya hemos dicho el contenedor no debe contener estado alguno que lo convierta en una elemento valioso e irremplazable con datos. Tenemos varios mecanismos para hacer esto, pero el que vamos a ver hoy en acción es el de los volúmenes de docker. Se trata de mapear un directorio del host anfitrión para que sea accesible dentro del contenedor. Lógicamente lo que dejemos ahí desde cualquier extremo (host o contenedor) es visible para el contrario.

Siguiendo con el tema de la base de datos, supongamos que tenemos la carpeta /home/usuario/db y que es donde vamos a ubicar los schemas de nuestra mysql.
Creamos el contenedor desde cero indicando este volumen. La novedad es el switch -v:

$ sudo docker run --name mimysql2 -p 13307:3306 \
         -v /home/usuario/db:/var/lib/mysql \
         -e MYSQL_ROOT_PASSWORD=mi-secreto -d mysql
ebe6d5b6c4854a86a781e48386c5d6107b05ec6e10b16654feb5282028254257
Nos conectamos a ella y alteramos el estado.

$ mysql --host=127.0.0.1 --port 13307 -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.11 MySQL Community Server (GPL)
 
Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
 
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
 
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
 
mysql> show schemas;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.00 sec)
 
mysql> create schema prueba;
Query OK, 1 row affected (0.00 sec)
 
mysql> use prueba
Database changed

mysql> create table tabla_datos(id int(4),  nota varchar(20));
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into tabla_datos values (1,'hola mundo');
Query OK, 1 row affected (0.01 sec)
 
mysql> select * from tabla_datos;
+------+------------+
| id   | nota       |
+------+------------+
|    1 | hola mundo |
+------+------------+
1 row in set (0.01 sec)


Eliminamos el contenedor “mimysql”

$ sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED              STATUS              PORTS                     NAMES
ebe6d5b6c485        mysql:latest        "/entrypoint.sh mysq   About a minute ago   Up About a minute   0.0.0.0:13307->3306/tcp   mimysql             
89a8e4f3e3d4        mysql:latest        "/entrypoint.sh mysq   About an hour ago    Up 51 minutes       0.0.0.0:13306->3306/tcp   some-mysql          
$ sudo docker rm -f ebe6d5b6c485
ebe6d5b6c485


Volvemos a crear un nuevo contenedor y le montamos el volumen donde está la base de datos.
$ sudo docker run --name mimysql -p 13307:3306 \
         -v /home/usuario/db:/var/lib/mysql \
         -e MYSQL_ROOT_PASSWORD=mi-secreto -d mysql
83f848a88181a04f4c55b782bfe8bea988fa6d7a5c5b7936ae3f112db246f01a
Nos conectamos a la base de datos y vemos si existe el schema que creamos con el otro contenedor.
$ mysql --host=127.0.0.1 --port 13307 -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.11 MySQL Community Server (GPL)
 
Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
 
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
 
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
 
mysql> show schemas;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| prueba             |
| sys                |
+--------------------+
5 rows in set (0.00 sec)
 
mysql> use prueba;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select * from tabla_datos;
+------+------------+
| id   | nota       |
+------+------------+
|    1 | hola mundo |
+------+------------+
1 row in set (0.00 sec)

Si te has quedado con ganas de mucho más, aquí tienes algunos enlaces interesantes:

martes, 15 de marzo de 2016

Docker: obtener los atributos con inspect

Se pueden conocer los detalles de un contenedor o imagen con el subcomando "inspect".

$ sudo docker run --name mimysql -p 13306:3306 \
            -e MYSQL_ROOT_PASSWORD=mi-secreto -d mysql

$ sudo docker inspect mimysql
[{
    "AppArmorProfile": "",
    "Args": [
        "mysqld"
    ],
    "Config": {
        "AttachStderr": false,
        "AttachStdin": false,
        "AttachStdout": false,
        "Cmd": [
            "mysqld"
        ],
        "CpuShares": 0,
        "Cpuset": "",
        "Domainname": "",
        "Entrypoint": [
            "/entrypoint.sh"
        ],
        "Env": [
            "MYSQL_ROOT_PASSWORD=mi-secreto",
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
            "MYSQL_MAJOR=5.7",
            "MYSQL_VERSION=5.7.11-1debian8"
        ],
        "ExposedPorts": {
            "3306/tcp": {}
        },
        "Hostname": "a7ba98d86780",
        "Image": "mysql",
        "Labels": {},
        "MacAddress": "",
        "Memory": 0,
        "MemorySwap": 0,
        "NetworkDisabled": false,
        "OnBuild": null,
        "OpenStdin": false,
        "PortSpecs": null,
        "StdinOnce": false,
        "Tty": false,
        "User": "",
        "Volumes": {
            "/var/lib/mysql": {}
        },
        "WorkingDir": ""
    },
    "Created": "2016-03-15T14:02:35.975162208Z",
    "Driver": "aufs",
    "ExecDriver": "native-0.2",
    "ExecIDs": null,
    "HostConfig": {
        "Binds": null,
        "CapAdd": null,
        "CapDrop": null,
        "CgroupParent": "",
        "ContainerIDFile": "",
        "CpuShares": 0,
        "CpusetCpus": "",
        "Devices": [],
        "Dns": null,
        "DnsSearch": null,
        "ExtraHosts": null,
        "IpcMode": "",
        "Links": null,
        "LogConfig": {
            "Config": null,
            "Type": "json-file"
        },
        "LxcConf": [],
        "Memory": 0,
        "MemorySwap": 0,
        "NetworkMode": "bridge",
        "PidMode": "",
        "PortBindings": {
            "3306/tcp": [
                {
                    "HostIp": "",
                    "HostPort": "13306"
                }
            ]
        },
        "Privileged": false,
        "PublishAllPorts": false,
        "ReadonlyRootfs": false,
        "RestartPolicy": {
            "MaximumRetryCount": 0,
            "Name": "no"
        },
        "SecurityOpt": null,
        "Ulimits": null,
        "VolumesFrom": null
    },
    "HostnamePath": "/var/lib/docker/containers/a7ba98d867804d74a2a2f9b749bd796ed9e54127356aa9e78e9a7a17161c8ade/hostname",
    "HostsPath": "/var/lib/docker/containers/a7ba98d867804d74a2a2f9b749bd796ed9e54127356aa9e78e9a7a17161c8ade/hosts",
    "Id": "a7ba98d867804d74a2a2f9b749bd796ed9e54127356aa9e78e9a7a17161c8ade",
    "Image": "50806c71cd84eb5b3bc15060d3aa60c8963c7df6cc6ceff9a7cb5c27b62a01f4",
    "LogPath": "/var/lib/docker/containers/a7ba98d867804d74a2a2f9b749bd796ed9e54127356aa9e78e9a7a17161c8ade/a7ba98d867804d74a2a2f9b749bd796ed9e54127356aa9e78e9a7a17161c8ade-json.log",
    "MountLabel": "",
    "Name": "/mimysql",
    "NetworkSettings": {
        "Bridge": "docker0",
        "Gateway": "172.17.42.1",
        "GlobalIPv6Address": "",
        "GlobalIPv6PrefixLen": 0,
        "IPAddress": "172.17.0.16",
        "IPPrefixLen": 16,
        "IPv6Gateway": "",
        "LinkLocalIPv6Address": "fe80::42:acff:fe11:10",
        "LinkLocalIPv6PrefixLen": 64,
        "MacAddress": "02:42:ac:11:00:10",
        "PortMapping": null,
        "Ports": {
            "3306/tcp": [
                {
                    "HostIp": "0.0.0.0",
                    "HostPort": "13306"
                }
            ]
        }
    },
    "Path": "/entrypoint.sh",
    "ProcessLabel": "",
    "ResolvConfPath": "/var/lib/docker/containers/a7ba98d867804d74a2a2f9b749bd796ed9e54127356aa9e78e9a7a17161c8ade/resolv.conf",
    "RestartCount": 0,
    "State": {
        "Dead": false,
        "Error": "",
        "ExitCode": 0,
        "FinishedAt": "0001-01-01T00:00:00Z",
        "OOMKilled": false,
        "Paused": false,
        "Pid": 19910,
        "Restarting": false,
        "Running": true,
        "StartedAt": "2016-03-15T14:02:36.105333233Z"
    },
    "Volumes": {
        "/var/lib/mysql": "/var/lib/docker/vfs/dir/842864eff1fdcb81c55f455925e72cbec425569585a99e60619178ed13e00ed9"
    },
    "VolumesRW": {
        "/var/lib/mysql": true
    }
}
]


Como se puede ver se trata de un JSON, lo cual lo convierte en muy apropiado para su tratamiento automatizado con paneles y scripts.

También se puede emplear para preguntar por atributos de la imagen. En el ejemplo "mysql" es la imagen. Si le preguntamos por los detalles nos ofrece los valores de fabricación de la misma.

$ docker inspect mysql


Si lo ejecutas verás que hay valores que no tienen sentido para una imagen pero si para el contenedor, como son los parámetros de red, configuración de host, etc..

Si solo estás interesado en un atributo en particular podemos preguntar por él directamente usando --format. En el siguiente ejemplo vamos a preguntar por el gateway del contenedor mimysql.

$ sudo docker inspect --format='{{.NetworkSettings.Gateway}}' mimysql
172.17.42.1


Un nuevo ejemplo pero ahora con 2 consultas simultáneas.
$ sudo docker inspect --format='{{.NetworkSettings.IPAddress}}, {{.NetworkSettings.Gateway}}' mimysql
172.17.0.16, 172.17.42.1


lunes, 14 de marzo de 2016

Docker: crear una base de datos mysql (1/2)

Vamos con un ejemplo concreto de uso de contenedores docker. En esta entrada vamos a crear una base de datos de usar y tirar sin necesidad de enturbiar nuestro sistema host.

Concretamente vamos a crear un contenedor docker que:
  • El contenedor se llamará "mimysql". Recuerda que el nombre es único. Si repites el experimento te toca eliminar cada vez el contenedor o usar otro nombre.
  • Si no tenemos ninguna imagen mysql ya descargada, la imagen es la del registry y la versión será la última disponible (no la hemos especificado).
  • El demonio mysqld escuchará internamente (en el contenedor) en el puerto 3306 interno pero se mapeará al puerto 13306 del localhost (externo).
  • La contraseña del root de mysql será "mi-secreto".


Creación del contenedor

$ sudo docker run --name mimysql -p 13306:3306 \
           -e MYSQL_ROOT_PASSWORD=mi-secreto -d mysql
89a8e4f3e3d4673a8fd1b34ace7ac9a60dd90265dea6b9f92fe5fcdcd355cc54



Comprobamos que se ha creado

$ sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                     NAMES
89a8e4f3e3d4        mysql:latest        "/entrypoint.sh mysq   9 seconds ago       Up 8 seconds        0.0.0.0:13306->3306/tcp   mimysql


Conexión a la base de datos

$ mysql --host 127.0.0.1 --port=13306 -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.11 MySQL Community Server (GPL)

Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
Y vemos que es funcional
mysql> show schemas;
+--------------------+                                                              
| Database           |                                         
+--------------------+                                         
| information_schema |                                         
| mysql              |                                           
| performance_schema |                                         
| sys                |
+--------------------+
4 rows in set (0.00 sec)

mysql> create schema prueba;
Query OK, 1 row affected (0.00 sec)

mysql> show schemas;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| prueba             |
| sys                |
+--------------------+
5 rows in set (0.00 sec)



Como se puede ver hemos alterado el estado de la base de datos creando un schema y por tanto del contenedor. Después de pararla de forma abrupta (kill) y de arrancarla (start), el contenido de la base de datos se mantiene.

Aunque el ejemplo demuestra que es posible alterar el estado del contenedor, eso va contra la naturaleza de los mismos. Los contenedores tienen que ser reemplazables y por tanto no contener datos ni las modificaciones de los mismos. En un próximo post voy a mostrar como hacerlo, pero si lo que quieres es una base de datos de prueba o test donde importar un backup y que puedes borrar sin problemas tal vez este post te sirva tal cual.

viernes, 11 de marzo de 2016

Estados de un contenedor docker

Estados de un contenedor docker

El ciclo de vida de un contenedor docker es algo que debemos conocer en detalle. Los contenedores están concebidos como un servicio en ejecución y como tal tiene un estado que en parte lo provocamos nosotros con el despliegue, cada vez que usamos un comando que lo altera o con herramientas de orquestación.

He visto esta imagen en la web de docker que creo que debes conocer en detalle para comprender el alcance de cada uno de los subcomandos.




Los posibles estados son:
  • created: el contenedor existe como personalización de la imagen base pero no ha entrado en ejecución. Ha sido dado de alta en la lista de docker y tiene un identificador asignado.
  • running: es un contenedor que podemos considerar que ha pasado por la etapa "created" y además está arrancado ejecutando la tarea encomendada. Es posible crearlo y ejecutarlo con el mismo comando docker.
  • stopped: la tarea ha concluido y el contenedor se detiene.
  • paused: la tarea se ha detenido aunque no ha concluido. Es un estado provocado en el contenedor desde fuera bien por el uso del comando pause o por la captura de una señal SIGSTOP. Está técnicamente congelado y se puede reanudar.
  • deleted: el contenedor ha sido eliminado. No existe ya como tal. Tampoco está en la lista de contenedores.

miércoles, 9 de marzo de 2016

Primeros pasos con Docker

Docker está de moda así que vamos a dar algunas nociones para que todo el mundo pueda subirse a esta tecnología tan fantástica. En pocos días no podrás dejar de pensar en ella cada vez que tengas que iniciar un proyecto o hacer una prueba. Transformará el arquitecto de sistemas que llevas dentro y enriquecerá todo lo que hagas a partir de ahora.

Empezamos.

Qué es un contenedor docker

No es una máquina virtual. Y punto. Aunque lo parezca. Es un proceso que corre directamente en tu máquina. Lo puedes ver con un "ps". Ese proceso crea un entorno que contiene todo lo necesario para dar la apariencia de que es otra máquina. Está basado en LXC que es un sistema de contenedores ligeros que emplean muchos proveedores de hosting. Cuanto antes te convenzas de que no es una máquina virtual antes empezarás a disfrutar de lo que sí es.

Otra cosa que debes saber es que las imágenes de los contenedores docker se construyen unas sobre otras. Abajo estaría la más básica y conforme vamos subiendo podemos ir perfeccionando un contenedor para que cumpla con el rol que queramos. Es como hacer plantillas.

Vamos a suponer que tienes un equipo con Linux. Ubuntu por ejemplo. Instalamos el paquete de docker. Ojo que ya existe un paquete con el nombre "docker" que es una bandena KDE. El que nos interesa se llama "docker.io" en ubuntu.

sudo apt-get install docker.io



Mi primer docker

Vamos a arrancar un primer contenedor con un ubuntu limpio. Sin pensarlo. A lo loco. Los ejemplos que pongo aquí los puedes ejecutar sin peligro en tu equipo.

$ sudo docker run --name osadmin-ubuntu ubuntu
Unable to find image 'ubuntu:latest' locally
latest: Pulling from ubuntu
454970bd163b: Pull complete 
38112156678d: Pull complete 
4e1f7c524148: Pull complete 
56063ad57855: Pull complete 
ubuntu:latest: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.
Digest: sha256:4e85ebe01d056b43955250bbac22bdb8734271122e3c78d21e55ee235fc6802d
Status: Downloaded newer image for ubuntu:latest


Analicemos qué ha pasado: hemos querido arrancar un contenedor al que llamaremos "osadmin-ubuntu" y que está basado en la imagen de contenedor docker "ubuntu". Puesto que no teníamos ninguna imagen llamada "ubuntu" se la ha descargado de un repositorio (llamado "registry"). Luego lo ha arrancado y está en algún lugar corriendo. Comprobamos.

Comprobamos que existe la instancia "osadmin-ubuntu".
$ sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
c98e2697c8fa        ubuntu:latest       "/bin/bash"         7 minutes ago       Exited (0) 7 minutes ago                       osadmin-ubuntu
Vemos que está detenida. Esto es porque docker solo tiene sentido como proceso. Es decir, si no le encargas que haga nada, no hace nada. Se muere. Vamos a eliminarla y la volvemos a crear haciendo una espera de 120 segundos.

Primero la eliminamos

$ sudo docker rm osadmin-ubuntu


¿ Qué hemos eliminado ? La instancia llamada "osadmin-ubuntu".
¿ Hemos eliminado la imagen docker ubuntu ? La imagen está en un repositorio local y debe seguir allí. Lo vemos.

$ sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu              latest              56063ad57855        5 days ago          188 MB
Observa que nuestro repositorio todavía tiene la copia de la imagen "ubuntu" creada hace 5 dias de la imagen del contenedor.

El siguiente comando creará de nuevo el contenedor pero hará un sleep de 120 segundos.

$ sudo docker run -d --name osadmin-ubuntu ubuntu /bin/bash -c "sleep 120"
da6eda60ad16cfb8e1d6843fa6030ba191cc051900947052c9f07e332fcaeaa0
$ sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS               NAMES
da6eda60ad16        ubuntu:latest       "/bin/bash -c 'sleep   6 seconds ago       Up 5 seconds                            osadmin-ubuntu  


Pasados 120 segundos se morirá.

Eliminar imágenes

Si queremos purgar nuestro depósito local de imágenes, es sencillo. El ejemplo nos permite eliminar la plantilla "ubuntu". Se supone que no hay nadie usándola. Si es así no nos deja eliminarla a no ser que hagamos un "-f".

$ sudo docker rmi ubuntu
Untagged: ubuntu:latest
Deleted: 1997914a7c23c0f2f77ba6570032f8e74292224d24b9e13228244561ac304020
Deleted: 1b1999356dda739315854eb57a482e92405660e84fd71b6c59f777377b5bd2dc
Deleted: 13a8e55f2d20aee3c842842ebadcbda69347bb657a4cde1b851719b8f896c596
Deleted: 8aa2fc7185e20bacda32d815eaae32cbc1c0457dc160ed5b3995ab79a8c7fd98


No ha estado mal como aperitivo.

martes, 1 de marzo de 2016

SSH: conexión desatendida entre hosts

Quiero resumir aquí lo necesario para realizar la configuración que permite la conexión desatendida del usuario userA desde el hostA hasta la cuenta del userB del hostB.

La base del mecanismo es la criptografía asimétrica. En el ejemplo vamos a emplear cifrado RSA, pero podíamos haber escogido "cifrado DSA". El resultado es equivalente.

La criptografía asimétrica requiere de un par de claves: la pública y la privada. Un mensaje cifrado con una de ellas solo puede ser descifrado con la otra. En este sencillo mecanismo nos apoyamos para:

  • Demostrar que somos quienes somos: porque lo que nos dicen cifrado con la clave pública lo entendemos porque tenemos la privada.
  • Mantener la privacidad del canal puesto que nadie puede suplantar el extremo que posee la clave privada. Para evitar que suplanten al otro extremo, al inicio de la sesión se intercambia un secreto que permitirá cifrar las comunicaciones, ya con criptografía simétrica.


Generamos la pareja de claves
Ten en cuenta que la pareja de claves podría existir antes ya. Sigue leyendo el post y cuando lo entiendas todo, comprueba el contenido del directorio $HOME/.ssh por ver si te puedes saltar el paso de la generación.

(host A) user A # ssh-keygen -t rsa 
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
a7:34:ee:dd:cc:bb:aa:99:88:77:66:55:44:33:22:11 root@muchotest.com
The key's randomart image is:
+--[ RSA 2048]----+
|        ..=+ ..o.|
|         1 3+ +..|
|          == - = |
|         . .. e .|
|        S .  o o |
|       . +    *  |
|        .    o   |
|                 |
|                 |
+-----------------+


Si entras en el .ssh del HOME del usuario que quiere hacer las conexiones desatendidas encontrarás los 2 nuevos ficheros:
  • id_rsa.pub: puede ser enviado a cualquier host o persona que lo solicite. Es la parte pública de la pareja de claves asimétricas.
  • id_rsa: NUNCA debe salir del lugar seguro donde se supone que se ha generado. NUNCA. Es la parte privada de la clave asimétrica. Cualquiera que tenga este fichero puede suplantar la identidad del host o el usuario al que pertenece.


Habilitamos la clave en el hostB

Lo normal es que los dos hosts sean de nuestra propiedad o ámbito de gestión. Copiamos el id_rsa.pub con un scp o copy-paste por el terminal.

Para seguir con el ejemplo, vamos a suponer que ya se ha copiado el fichero id_rsa.pub en el $HOME del receptor.

(hostB) userB # cat $HOME/id_rsa.pub >> $HOME/.ssh/authorized_keys
(hostB) userB # chmod 400 $HOME/.ssh/authorized_keys


El tema de los permisos es importantísimo, por eso los aseguramos en la segunda línea. Si el fichero $HOME/.ssh/authorized_keys no existe se creará con unos permisos por defecto que pueden no ser "400". Debemos asegurarnos de que los permisos del fichero son esos porque si no va a parecer que no lo hemos hecho bien ya que no funcionará y tampoco habrá un mensaje de error claro de ello.


Probamos

La prueba es hacer un ssh desde el userA del hostA hasta el userB del hostB. Es posible que nos pida confirmación para guardar el certificado del host e incluso de userB. Solo debe ocurrir la primera vez. Lo que nunca debe ocurrir es que nos pida la contraseña.

Lo que hemos hecho solo sirve en el sentido de A hacia B. Si queremos los 2 sentidos hay que intercambiar la clave pública de B.


jueves, 25 de febrero de 2016

Acceso a Ubuntu para recuperación

Es frecuente que un administrador de sistemas tenga que recuperar una contraseña o deshabilitar un módulo del arranque porque el host se queda colgado sin presentar el prompt para login. Típicamente uno piensa en dispositivo booteable con un Linux Live para poder trastear todo lo que quiera. La idea no es mala pero toma nota de este truco porque tal vez no necesites arrancar con un Linux Live.

1.- Cuando salga el primer mensaje de grub pulsa ESC para detener la cuenta atrás.
2.- Escoge la versión que te interesa (la más reciente estará arriba por defecto) y pulsa la tecla 'e'.
3.- En la siguiente pantalla muévete con los cursores hasta el final de la linea y añade init=/bin/bash. Cambia también de "ro" a "rw" (montajes con todos los permisos de escritura)
4.- Provocamos el arranque. Según la versión será con la tecla "b" o F10.


Si además necesitas, cambiar el password porque lo has perdido usa el comando "passwd".
Cuando hayas terminado reinicia normalmente (comando reboot por ejemplo) para ver el resultado.


RECUERDA: estas operaciones son delicadas. Si tienes dudas lee, lee y lee. No te quedes con lo que yo te he dicho, sigue leyendo y forma tu propia estrategia de forma segura. Aquí tienes esta información y otras que pueden ser útiles Algunos efectos pueden ser no reversibles.