Quantcast
Channel: Adictos al trabajo
Viewing all 989 articles
Browse latest View live

Instalación de Kubernetes en Ubuntu con Ansible

$
0
0

En este tutorial comprobaremos como instalar Kubernetes de forma sencilla con Ansible.

Índice de contenidos


1. Introducción

Una de las cosas más importantes a la hora de tener una aplicación en producción es que esta esté disponible siempre para los usuarios. Kubernetes nos ofrece una buena infraestructura para aumentar la disponibilidad de nuestra aplicación.


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS El Capitán 10.11.2
  • Virtual Box
  • Vagrant 1.8.1
  • Ansible 1.9.3

3. Preparación y configuración del entorno

En tutoriales anteriores ya se ha explicado como instalar Vagrant y Ansible (ver aquí), así que partimos de que ambas herramientas ya están instaladas.

Recordamos que Vagrant es una herramienta que nos permite gestionar máquinas virtuales (usando en nuestro caso VirtualBox por debajo) y que Ansible nos permite automatizar tareas en host remotos via ssh. Vagrant y ansible están muy bien integrados, de forma que utilizaremos Vagrant para crear el entorno con un sistema operativo Ubuntu en el cual instalaremos y jugaremos con Kubernetes. Para ello nos creamos un directorio dónde se encontrará nuestro Vagrantfile, fichero de configuración de Vagrant. Para que se genere el fichero por defecto ejecutamos el comando vagrant init.

Nuestro fichero Vagrantfile debe quedar de la siguiente manera:

Vagrantfile
Vagrant.configure(2) do |config|

  config.vm.box = "ubuntu/trusty64"

  config.vm.network "forwarded_port", guest: 8080, host: 8080
  config.vm.network "forwarded_port", guest: 32066, host: 32066

  config.vm.network "private_network", ip: "192.168.90.20"

  config.vm.provider "virtualbox" do |v|
      v.memory = 4096
      v.cpus = 2
  end

  config.vm.define "prueba" do |prueba|
    prueba.vm.provision "ansible" do |ansible|
      ansible.verbose = 'vvv'
      ansible.playbook = "ansible/infraestructure.yml"
    end
  end
end

Analicemos que realizará este fichero de configuración:

  • config.vm.box = “ubuntu/trusty64” : en este apartado indicamos la box que va a utilizar vagrant para crear la máquina virtual.
  • config.vm.network “forwarded_port”, guest: 8080, host: 8080 : aquí redireccionamos el puerto de la máquina anfitrión a la máquina virtual de forma que cuando se llame al puerto 8080 de la máquina real esto será redireccionado al puerto 8080 de la máquina virtual. Esto nos sirve para ver la interfaz gráfica de Kubernetes.
  • config.vm.network “private_network”, ip: “192.168.90.20” : en este apartado indicamos la ip que tendrá la máquina virtual.
  • v.memory = 4096 : en este apartado la memoria que va a tenr la máquina virtual.
  • Por último definimos el provisionamiento que Vagrant va a realizar en la máquina una vez esté levantada. Para ello uso el ya mencionado Ansible, pero notad que no le especifico en ningún lugar el inventory. Esto está hecho a propósito ya que Vagrant te lo genera automáticamente si no lo creas tú.

Ahora tenemos que crear el directorio Ansible dónde vamos a tener la siguiente estructura:

  • infraestructure.yml: es el playbook principal que se encarga de instalar kubernetes.
  • roles/docker/tasks/docker_install.yml : aquí se encuentran las tareas propias de la instalación docker.
  • roles/docker/tasks/docker_prerequisites.yml : aquí están las tareas que necesitan ser ejecutadas para que se instale docker.
  • roles/docker/tasks/docker_setup.yml : aquí están tareas propias de la configuración.
  • roles/docker/tasks/main.yml : aquí se encuentran las tareas que se van a ejecutar en el rol de docker.
  • roles/kubernetes/files/config-default.sh : es el fichero de configuración dónde se especifican las propiedades del cluster.
  • roles/kubernetes/meta/main.yml : en este fichero se encuentran las dependencias del rol kubernetes.
  • roles/kubernetes/tasks/main.yml : aquí definimos las tareas que se van a ejecutar en el rol kubernetes.
  • roles/kubernetes/vars/main.yml : por ultimo definimos las variables propias del rol aquí.

3.1 Kubernetes

A continuación veremos el contenido del rol Kubernetes.

roles/kubernetes/files/config-default.sh
#!/bin/bash

# Copyright 2015 The Kubernetes Authors All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

## Contains configuration values for the Ubuntu cluster

# Define all your cluster nodes, MASTER node comes first"
# And separated with blank space like
export nodes=${nodes:-"root@127.0.0.1"}

# Define all your nodes role: a(master) or i(minion) or ai(both master and minion), must be the order same
role={roles:-"ai"}
# If it practically impossible to set an array as an environment variable
# from a script, so assume variable is a string then convert it to an array
export roles=($role)

# Define minion numbers
export NUM_NODES=${NUM_NODES:-1}
# define the IP range used for service cluster IPs.
# according to rfc 1918 ref: https://tools.ietf.org/html/rfc1918 choose a private ip range here.
export SERVICE_CLUSTER_IP_RANGE=${SERVICE_CLUSTER_IP_RANGE:-192.168.3.0/24}  # formerly PORTAL_NET
# define the IP range used for flannel overlay network, should not conflict with above SERVICE_CLUSTER_IP_RANGE

# The Ubuntu scripting supports two ways of networking: Flannel and
# CNI.  To use CNI: (1) put a CNI configuration file, whose basename
# is the configured network type plus ".conf", somewhere on the driver
# machine (the one running `kube-up.sh`) and set CNI_PLUGIN_CONF to a
# pathname of that file, (2) put one or more executable binaries on
# the driver machine and set CNI_PLUGIN_EXES to a space-separated list
# of their pathnames, and (3) set CNI_KUBELET_TRIGGER to identify an
# appropriate service on which to trigger the start and stop of the
# kubelet on non-master machines.  For (1) and (2) the pathnames may
# be relative, in which case they are relative to kubernetes/cluster.
# If either of CNI_PLUGIN_CONF or CNI_PLUGIN_EXES is undefined or has
# a zero length value then Flannel will be used instead of CNI.

export CNI_PLUGIN_CONF CNI_PLUGIN_EXES CNI_KUBELET_TRIGGER
CNI_PLUGIN_CONF=${CNI_PLUGIN_CONF:-""}
CNI_PLUGIN_EXES=${CNI_PLUGIN_EXES:-""}
CNI_KUBELET_TRIGGER=${CNI_KUBELET_TRIGGER:-networking}

# Flannel networking is used if CNI networking is not.  The following
# variable defines the CIDR block from which cluster addresses are
# drawn.
export FLANNEL_NET=${FLANNEL_NET:-172.16.0.0/16}

# Optionally add other contents to the Flannel configuration JSON
# object normally stored in etcd as /coreos.com/network/config.  Use
# JSON syntax suitable for insertion into a JSON object constructor
# after other field name:value pairs.  For example:
# FLANNEL_OTHER_NET_CONFIG=', "SubnetMin": "172.16.10.0", "SubnetMax": "172.16.90.0"'

export FLANNEL_OTHER_NET_CONFIG
FLANNEL_OTHER_NET_CONFIG=''

# Admission Controllers to invoke prior to persisting objects in cluster
export ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,SecurityContextDeny

# Path to the config file or directory of files of kubelet
export KUBELET_CONFIG=${KUBELET_CONFIG:-""}

# A port range to reserve for services with NodePort visibility
SERVICE_NODE_PORT_RANGE=${SERVICE_NODE_PORT_RANGE:-"30000-32767"}

# Optional: Enable node logging.
ENABLE_NODE_LOGGING=false
LOGGING_DESTINATION=${LOGGING_DESTINATION:-elasticsearch}

# Optional: When set to true, Elasticsearch and Kibana will be setup as part of the cluster bring up.
ENABLE_CLUSTER_LOGGING=false
ELASTICSEARCH_LOGGING_REPLICAS=${ELASTICSEARCH_LOGGING_REPLICAS:-1}

# Optional: When set to true, heapster, Influxdb and Grafana will be setup as part of the cluster bring up.
ENABLE_CLUSTER_MONITORING="${KUBE_ENABLE_CLUSTER_MONITORING:-true}"

# Extra options to set on the Docker command line.  This is useful for setting
# --insecure-registry for local registries.
DOCKER_OPTS=${DOCKER_OPTS:-""}

# Extra options to set on the kube-proxy command line.  This is useful
# for selecting the iptables proxy-mode, for example.
KUBE_PROXY_EXTRA_OPTS=${KUBE_PROXY_EXTRA_OPTS:-""}

# Optional: Install cluster DNS.
ENABLE_CLUSTER_DNS="${KUBE_ENABLE_CLUSTER_DNS:-true}"
# DNS_SERVER_IP must be a IP in SERVICE_CLUSTER_IP_RANGE
DNS_SERVER_IP=${DNS_SERVER_IP:-"192.168.3.10"}
DNS_DOMAIN=${DNS_DOMAIN:-"cluster.local"}
DNS_REPLICAS=${DNS_REPLICAS:-1}

# Optional: Install Kubernetes UI
ENABLE_CLUSTER_UI="${KUBE_ENABLE_CLUSTER_UI:-true}"

# Optional: Enable setting flags for kube-apiserver to turn on behavior in active-dev
#RUNTIME_CONFIG=""

# Optional: Add http or https proxy when download easy-rsa.
# Add envitonment variable separated with blank space like "http_proxy=http://10.x.x.x:8080 https_proxy=https://10.x.x.x:8443"
PROXY_SETTING=${PROXY_SETTING:-""}

DEBUG=${DEBUG:-"false"}

El fichero config-default se crea por defecto cuando descargas la versión de Kubernetes (se encuentra dentro del directorio kubernetes/cluster/ubuntu). Este fichero se encarga de establecer la configuración por defecto del cluster, como el número de nodos, sus respectivas ip y el número de minions y de maestros del cluster. Las propiedades que hemos modificado del fichero son:

  • export nodes=${nodes:-“root@127.0.0.1”}: Con esta propiedad le indicamos los nodos que va a tener nuestro cluster. Como este es un ejemplo muy sencillo, tenemos un cluster de un único nodo, pero si tuviéramos más deberían de ir las ip aquí separadas por espacios.
  • role={roles:-“ai”}: Aquí se separan los nodos por espacio y van en el mismo orden en el que hemos definido las ips en la propiedad anterior. a significa master, mientras que i significa minion de forma que estamos indicando que nuestro único nodo es master y minion a la vez.
  • export NUM_NODES=${NUM_NODES:-1}: con esta propiedad indicamos que el número de minions que tiene nuestro cluster es 1.

En el siguiente fichero están descritas las tareas que se van a realizar en el playbook Kubernetes:

roles/kubernetes/tasks/main.yml
---

# file: roles/kubernetes/tasks/main.yml

- name: Create ssh key for this machine (Necesary to start kubernetes)
  user: name=root generate_ssh_key=yes
  register: user_var

- name: Add key to authorized keys
  shell: "cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys"

- name: Download Kubernetes release bundle
  get_url: >
    url="https://github.com/GoogleCloudPlatform/kubernetes/releases/download/v1.2.0/kubernetes.tar.gz"
    dest="{{kubernetes.temporal_path}}/kubernetes.tar.gz"

- name: Untar the kubernetes bundle
  unarchive: >
    src="{{kubernetes.temporal_path}}/kubernetes.tar.gz"
    dest={{kubernetes.path}} copy=no

- name: Unarchive salbase tar to configure the cluster.
  unarchive: >-
    src={{kubernetes.path}}/kubernetes/server/kubernetes-salt.tar.gz
    dest={{kubernetes.path}}/kubernetes/cluster
    copy=no

- name: move saltbase directory outside the unarchive directory
  shell: mv {{kubernetes.path}}/kubernetes/cluster/kubernetes/saltbase {{kubernetes.path}}/kubernetes/cluster
  register: result
  failed_when: "'Directory not empty' not in result.stderr and 'true' in result.failed"

- name: Delete empty directory
  file: >-
    path={{kubernetes.path}}/kubernetes/cluster/kubernetes
    state=absent

- name: Setup default configuration
  copy: >
    src=config-default.sh
    dest={{kubernetes.path}}/kubernetes/cluster/ubuntu
    mode="0755"
    force=yes

- name: Start cluster
  shell: KUBERNETES_PROVIDER=ubuntu ./kube-up.sh chdir={{kubernetes.path}}/kubernetes/cluster
  register: result
  failed_when: "'Text file busy' not in result.stdout and 'true' in result.failed"

Los pasos que sigue este playbook son:

  • Las dos primeras tareas son necesarias para arrancar el servicio de Kubernetes. Este servicio necesita de acceso por ssh, y como lo estamos automatizando con Ansible, no queremos que se detenga la ejecución. Para ello creamos una par clave pública/privada que identifica a la máquina, y añadimos la clave pública al fichero authorized_keys.
  • Nos descargamos la última release de Kubernetes y la descomprimimos en la variable {{kubernetes.path}}, que se encuentra en el fichero roles/kubernetes/defaults/main.yml.
  • Una vez hecho eso, para el arranque del cluster necesitamos un certificado para que el master del cluster pueda actuar como un servidor https. Para ello generamos un certificado general y lo usamos para firmar los certifcicados de la máquina con el script make-ca-cert.sh que se encuentra en el directorio saltabse, que está comprimido por defecto. Lo descomprimimos y lo movemos al directorio correspondiente.
  • Ya estaríamos listos para levantar el cluster, pero necesitamos la configuración que tenemos en el fichero config-default.sh dónde indicamos que solo se nos cree un nodo en la máquina virtual que sea master y minion a la vez.
  • Dejando lo mejor para el final, ejecutamos el comando para levantar el cluster. Comprobamos como antes del comando tenemos que indicarle previamente el provider a utilizar, ubuntu en nuestro caso. En esta tarea usamos la gestión de errores que nos ofrece Ansible. Esta característica está explicada en este tutorial.

Ahora vemos el fichero defaults/main.yml que contiene las variables por defecto que se van a utilizar en el rol:

roles/kubernetes/defaults/main.yml
kubernetes:
    path: /opt
    temporal_path: /tmp

Por último, en el fichero main.yml del directorio meta dentro del rol, tenemos las dependencias que tiene el este rol. En el tutorial mencionado anteriormente también hablo sobre la gestión de dependencias de los roles de ansible.

roles/kubernetes/meta/main.yml
---

# file: roles/kubernetes/kubernetes/meta/main.yml

dependencies:
    - {role: docker}

3.2. Docker

Nos queda crear las tareas pertenecientes al rol docker, que instalarán la última versión de Docker.

roles/docker/tasks/main.yml
---

# file: roles/docker/tasks/main.yml

- include: docker_prerequisites.yml
- include: docker_setup.yml
- include: docker_install.yml

Como bien dice, estos ficheros contienen las tareas encargadas de los prerequisitos de la instalacción, la configuración y la instalación propiamente dicha.

roles/docker/tasks/docker_prerequisites.yml
---

# file: roles/docker/tasks/docker_prerequisites.yml

- name: install Ensure that apt works with https method and CA certificates are installed
  apt: >
    name="{{ item }}"
    state=latest
    update_cache=yes
  with_items:
      - apt-transport-https
      - ca-certificates

- name: Add GPG key
  command: sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D

- name: Copy docker.list
  copy: >
    src=docker.list
    dest=/etc/apt/sources.list.d
    force=yes

- name: Install appsrmor package
  apt: >
    name=apparmor
    update_cache=yes
roles/docker/tasks/docker_setup.yml
---

# file: roles/docker/tasks/docker_setup.yml

- name: create docker group
  group: name=docker state=present

- name: create docker user
  user: name=docker group=docker state=present
roles/docker/tasks/docker_install.yml
---

# file: roles/docker/tasks/docker_install.yml

- name: install docker
  apt: >
    name="docker-engine"
    state=latest
    update_cache=yes


- name: ensure docker is running and enabled as service
  become_user: docker
  service: name=docker state=started enabled=yes

Con esto ya tendríamos preparado el entorno y la configuración.


4. Comprobación

Una vez realizada la configuración, basta con ejecutar el comando vagrant up para comprobar como se despliega. Una vez termine vagrant, comprobamos como accediendo a la url localhost:8080 tenemos la indicaciones del api rest. El dashboard viene deshabilitado por defecto, pero veremos como habilitarlo en el próximo tutorial.


5. Conclusiones

Comprobamos una vez más como gracias a Ansible crear una infraestructura compleja como instalar Kubernetes en ubuntu puede ser hecho con un solo comando. Puedes ver los ficheros creados en el tutorial en mi repositorio de github.


6. Referencias


Primeros pasos con Kubernetes

$
0
0

En este tutorial veremos una pequeña introducción a la gestión de servicios que nos ofrece la herramienta Kubernetes.

Índice de contenidos


1. Introducción

Kubernetes es una herramienta muy potente para manejar los servicios y que nos ofrece a tiro de piedra la experiencia de google a la hora de gestionar múltiples host remotos para el despliegue y mantenimiento de aplicaciones. En este tutorial aprenderemos los conceptos básicos al trabajar con kubernetes así como la exposición de las aplicaciones fuera del cluster.


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS El Capitán 10.11.2
  • Virtual Box
  • Vagrant 1.8.1
  • Ansible 1.9.3

3. Conceptos básicos

Kubernetes es una plataforma opensource para el automatizar el despliegue, escalado de las aplicaciones, así como las operaciones con los contenedores de aplicaciones. Antes de ponernos manos a la obra, tenemos que tener una serie de conceptos claros:

  • Cluster: Conjunto de másquinas físicas o virtuales y otros recursos utilizados por kubernetes.
  • Nodo: Una máquina física o virtual ejecutándose en kubernetes donde pods pueden ser programados.
  • Pod: Son la unidad más pequeña desplegable que puede ser creada, programada y manejada por kubernetes.
  • Replication Controller: Se asegura de que el número específicado de réplicas del pod estén ejecutándose. Permite escalar de forma facil los sistemas y maneja la re-creación de un pod cuando ocurre un fallo.
  • Service: Es una abstracción que define un conjunto de pods y la lógica para acceder a los mismos.

4. Instalación de kubernetes

Para realizar la instalación de kubernetes seguiremos el tutorial de instalación de Kubernetes en ubuntu. A partir de este punto entendemos que tienes Kubernetes instalado en una máquina virtual de Ubuntu.


5. Operaciones básicas

A continuación se indican las operaciones básicas que se pueden realizar con Kubernetes como crear y eliminar pods, crear y eliminar replication controllers y gestionar estos con los servicios, exponiéndolos dentro y fuera del cluster.


5.1. Pods


5.1.1 ¿Qué es un pod?

Como hemos definido antes, un pod es la unidad mínima que es manejada por kubernetes. Este pod es un grupo de uno o más contenedores (normalmente de Docker), con almacenamiento compartido entre ellos y las opciones específicas de cada uno para ejecutarlos. Un modelo de pods específico de una aplicación contiene uno o más contenedores que normalmente irían en la misma máquina.

Varios contenedores que pertenezcan al mismo pod son visibles unos de otros vía localhost. Los contenedores que se encuentran en distintos pods no pueden comunicarse de esta manera.

En términos de Docker, un pod es un conjunto de contenedores de Docker con namespace y volúmenes compartidos.

Hay que tener en cuenta que los pods son entidades efímeras. En el ciclo de vida de un pod estos se crean y se les asigna un UID hasta que terminen o se borren. Si un nodo que contiene un pod es eliminado, todos los pods que contenía ese nodo se pierden. Este pod puede ser reemplazado en otro nodo, aunque el UID será diferente. Esto es importante porque un pod no debería de tener información almacenada que pueda ser utilizada después por otro pod en caso de que a este le pasara algo. Para compartir información entre pods están los volúmenes (no hablamos de ellos en este tutorial, pero si tienes curiosidad puedes mirarlo en este enlace).


5.1.2 Usos de un pod

Los pods pueden utilizarse para realizar escalado horizontal, aunque fomentan el trabajo con microservicios puestos en contenedores diferentes para crear un sistema distribuido mucho más robusto. Puedes encontrar más información sobre los patrones utilizados aquí.


5.1.3 Creación de un pod

Los pods se pueden crear de dos maneras: directamente por línea de comandos o a través de un fichero de tipo YAML.

NOTA: Para ejecutar los comandos tal y como se muestran en el tutorial tenemos el comando que contiene el comando kubectl añadido al path. Para realizarlo, solo hay que introducir al final del fichero .bashrc la siguiente línea: “export PATH=$PATH:/opt/kubernetes/cluster/ubuntu/binaries” (teniendo en cuenta que se siga la instalación del tutorial previo).

Para crearlo directamente por línea de comandos ejecutamos lo siguiente:

Creación de replication controller por línea de comandos:
kubectl run my-nginx --image=nginx --port=80

Kubectl es el programa que vamos a utilizar para interactuar con el api de kubernetes.

  • El primer parámetro indica la acción, que sirve para arrancar un pod.
  • Después el nombre que va a recibir, en este caso my-nginx.
  • Después el nombre que va a recibir, en este caso my-nginx.
  • Luego la imagen a partir de la que se va a construir el pod (la imagens e llama nginx).
  • Por último el puerto en el que escucha.

Una vez ejecutado este comando obtendremos:

Lo que estamos viendo por pantalla no es el pod, sino el replication controller que se ha creado que se encarga de gestionarlo (y veremos más adelante). Para ver tanto el pod como el replication controller ejecutamos los siguientes comandos:

kubectl get pods
kubectl get rc

Comprobamos como el nombre del pod es igual al nombre del replication controller nombrado en el comando (my-nginx) seguido de un identificador único para cada pod.

Creación de pod a través de un fichero YAML

Para crearlo directamente por fichero de tipo YAML nos crearemos el archivo nginx-pod.yaml dentro del directorio /opt/kubernetes/example/nginx:

/opt/kubernetes/examples/nginx/nginx-pod.yaml
# Número de versión del api que se quiere utilizar
apiVersion: v1
# Tipo de fichero que se va a crear.
kind: Pod
# Aquí van los datos propios del pod como el nombre y los labels que tiene asociados para seleccionarlo
metadata:
    name: my-nginx
    # Especificamos que el pod tenga un label con clave "app" y valor "nginx"
    labels:
        app: nginx
# Contiene la especificación del pod
spec:
    # Aquí se nombran los contenedores que forman parte de este pod. Todos estos contenedores serían visibles por localhost
    containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80
    # Aquí se define la política de restauració en caso de que el pod se detenga o deje de ejecutarse debido a un fallo interno.
    restartPolicy: Always

Antes de ejecutar este pod vamos a eliminar el pod creado anteriormente. para ello usamos el comando:

kubectl delete rc my-nginx

¿Por qué borramos un replication controller en lugar del pod específico? Cuando creas un pod desde línea de comando implícitamente se crea un replication controller que se encarga de restaurar el pod cuando este es borrado ya que su política de restauración por defecto siempre es Always. Por tanto ahora procedemos a crear el mismo pod pero desde el fichero que acabamos de crear:

kubectl create -f /opt/kubernetes/examples/nginx/nginx-pod.yaml

Como estamos creando el pod directamente, vemos como ahora no se crea un replication controller.

Y ¿qué es un replication controller? vamos a verlo justo a continuación 😀


5.2. Replication Controllers


5.2.1 ¿Qué es un replication controller?

Un replication controller se asegura de que grupo de uno o más pods esté siempre disponible. Si hay muchos pods, eliminará algunos. Si hay pocos, creará nuevos. Por este motivo, se recomienda siempre crear un replication controller aunque solo tengas un único pod (este es el motivo por el cual cuando creamos un pod por comando automáticamente se crea un replication controller para el pod que acabamos de crear). Un replication controller es al fin y al cabo un supervisor de un grupo de uno o más pods a través de un conjunto de nodos.


5.2.2 Creación de un replication controller

A continuación crearemos un replication contorller llamado nginx-rc.yaml en el directorio /opt/kubernetes/examples/nginx a partir de la siguiente plantilla, encargado de levantar un servidor nginx:

/opt/kubernetes/examples/nginx/nginx-rc.yaml
# Número de versión del api que se quiere utilizar
apiVersion: v1
# Tipo de fichero que se va a crear.
kind: ReplicationController
# Datos propios del replication controller
metadata:
    # Nombre del Replication Controller
    name: my-nginx
# La especificación del estado deseado que queremos que tenga el pod.
spec:
    # Número de réplicas que queremos que se encargue de mantener el rc. (Esto creará un pod)
    replicas: 1
    # En esta propiedad se indican todos los pods que se va a encargar de gestionar este replication controller. En este caso, se va a encargar de todos los que tengan el valor "nginx" en el label "app"
    selector:
        app: nginx
    # Esta propipedad tiene exactamente el mismo esquema interno que un pod , excepto que como está anidado no necesita ni un "apiVersion" ni un "kind"
    template:
        metadata:
            name: nginx
            labels:
                app: nginx
        spec:
            containers:
                - name: nginx
                  image: nginx
                  ports:
                    - containerPort: 80

Antes de ejecutar este pod vamos a eliminar el pod creado en el paso anterior. para ello usamos el comando:

kubectl delete pod my-nginx

Ahora creamos el replication controller con el comando:

kubectl create -f /opt/kubernetes/examples/nginx/nginx-rc.yaml

Vemos como ahora se nos ha creado un pod con un UID. Para ver para que sirve cada línea del replication controller miramos los comentarios de código.

También podemos comprobar el estado de nuestro replication controller (nombre, número de pods y sus respectivos estados,..) a través del comando:

kubectl describe rc my-nginx


5.2.3 Trabajando con replication controllers

Una vez creado un rc te permite:

  • Escalarlo: puedes escoger el número de réplicas que tiene un pod de forma dinámica.
  • Borrar el replication controller: puedes borrar solo el replication controller o borrarlo junto a todos los pods de los que se encarga
  • Aislar al Pod del replication controller:Los pods pueden no pertenecer a un replication controller cambiando los labels. El pod que ha sido removido de está manera será reemplazado por un pod nuevo, que será creado por le replication controller.

Para añadir un pod que sea controlado por el replication controller ejecutamos:

kubectl scale rc my-nginx --replicas=2

Tras listar el número de pods comprobaríamos como se ha añadido uno nuevo:

Para borrar un replication controller ejecutaríamos el comando:

kubectl delete rc my-nginx

Si después listamos los pods comprobaremos que han desaparecido todos.


5.3. Services


5.3.1 ¿Qué es un service?

Como ya sabemos, los pods son volátiles. Son creados y destruidos, de forma que no pueden recuperarse. De hecho, los replication controllers son los encargados de manejar su ciclo de vida, y de definir sus políticas de restauración. Como decíamos anteriormente, cada pod tiene su propia dirección IP (que podría incluso no ser constante en el mismo pod a lo largo del tiempo). Esto nos supone un problema en caso de que un pod necesite comunicarse con otro pod. ¿Qué manera tienen de comunicarse ambos, si las ip de cada pod son variables, o si uno de los dos se cae y lo sustituye otro? De esto justo se encargan los services.

Un service es una abstracción que define un grupo lógico de pods y una política de acceso a los mismos. Los pods apuntan a un servicio normalmente por la propiedad label. Pongamos como ejemplo nuestro caso anterior, dónde tenemos un replication controller encargado de ejecutar un pod con un contenedor nginx. Si algo causara la destrucción de este pod, el replication controller crearía uno nuevo con una ip diferente, de forma que el resto de la infraestructura que dependiera de ese pod por esa ip fija dejaría de funcionar. El servicio lo que hace es que ese pod siempre sea accesible de la misma manera, de forma que aunque el pod se destruya o se modifique siempre sea accesible por la abstracción. A continuación crearemos un servicio y también comprobaremos como podemos exponerlo desde fuera del cluster.


5.3.2 Creación de un Service

A continuación vamos a crear un service:

/opt/kubernetes/examples/nginx/nginx-svc.yaml
# Número de versión del api que se quiere utilizar
apiVersion: v1
# Tipo de fichero que se va a crear.
kind: Service
# Aquí van los datos propios del pod como el nombre y los labels que tiene asociados para seleccionarlo
metadata:
    name: my-nginx-service
# Contiene la especificación del pod
spec:
    # En esta propiedad se indican todos los pods que apuntan a este servkice. En este caso, se va a encargar de todos los que tengan el valor "nginx" en el label "app"
    selector:
        app: nginx
    ports:
      # Indica el puerto en el que se debería de servir este servicio
      - port: 80

Una vez creado el fichero levantamos el service con el comando:

kubectl create -f /opt/kubernetes/examples/nginx/nginx-svc.yaml

Ahora habilitamos el replication controller de este servicio, que es el que hemos creado anteriormente:

kubectl create -f /opt/kubernetes/examples/nginx/nginx-rc.yaml

Ahora vamos a acceder a la aplicación del pod desde dentro del cluster. Para ello necesitamos saber la ip que tiene actualmente el pod, para lo que usamos el comando:

kubectl get -o template pod my-nginx-m5mni --template={{.status.podIP}}

Ahora accedemos al servicio por medio de esa ip usando el comando curl.

Actualmente este pod solo es accesible dentro del cluster. El service nos sirve para abstraernos del pod en cuestión que estamos utilizando. Este tipo de servicios dentro del cluster se suele utilizar para tener múltiples aplicaciones de un backend detrás del mismo frontend.


5.3.3 Acceder a un servicio desde fuera del cluster

Por último vamos a acceder al servicio desde fuera de cluster, de modo que accederemos al servidor nginx de nuestra máquina virtual desde el navegador. Para ello necesitamos modificar el servicio que estamos utilizando al que le añadimos la propiedad type: NodePort, que lo que hace es exponer el servicio en cada nodo del cluster de forma que serás capaz de contactar con el servicio desde cualquier ip de los nodos. Nuestro servicio qeudaría de la siguiente manera:

/opt/kubernetes/examples/nginx/nginx-svc.yaml
# Número de versión del api que se quiere utilizar
apiVersion: v1
# Tipo de fichero que se va a crear.
kind: Service
# Aquí van los datos propios del pod como el nombre y los labels que tiene asociados para seleccionarlo
metadata:
    name: my-nginx-service
# Contiene la especificación del pod
spec:
    type: NodePort
    # En esta propiedad se indican todos los pods que apuntan a este servkice. En este caso, se va a encargar de todos los que tengan el valor "nginx" en el label "app"
    selector:
        app: nginx
    ports:
      # Indica el puerto en el que se debería de servir este servicio
      - port: 80

Eliminamos el anterior service y añadimos el nuevo con los comandos que ya sabemos y comprobamos en que puerto externo está mapeeada la conexión:

Vemos que tenemos la conexión mapeada en el puerto 32138 (que es el puerto que se elije de forma automática). Ahora tenemos que cambiar la configuración de vagrant para acceder a este puerto desde localhost:

/opt/kubernetes/examples/nginx/nginx-svc.yaml
Vagrant.configure(2) do |config|

  config.vm.box = "ubuntu/trusty64"

  config.vm.network "forwarded_port", guest: 8080, host: 8080
  config.vm.network "forwarded_port", guest: 32138, host: 32138

  config.vm.network "private_network", ip: "192.168.90.20"

  config.vm.define "prueba" do |prueba|
    prueba.vm.provision "ansible" do |ansible|
      ansible.verbose = 'vvv'
      ansible.playbook = "ansible/infraestructure.yml"
    end
  end
end

Ahora reiniciamos la máquina virtual para que aplique los nuevos cambios con el comando vagrant reload. Accedemos a la máquina con el comando vagrant ssh y una vez dentro, arrancamos el cluster de kubernetes con el comando:

/opt/kubernetes/examples/nginx/nginx-svc.yaml
cd /opt/kubernetes/cluster
KUBERNETES_PROVIDER=ubuntu ./kube-up.sh

NOTA: Esto implica que has seguido el tutorial de instalación de kubernetes en ubuntu para crear un cluster de kubernetes.

Por último accedemos desde el navegador a la url localhost:32138 (tarda un poco en mostrarse debido a la configuración de la máquina virtual, asíque se puede aumentar desde el fichero Vagrantfile)


6. Acceder a la interfaz gráfica desde el navegador

Kubernetes cuenta con una serie de add-ons, qué son un conjunto de Services y Replication Controller (con sus correspondientes pods) que son considerados una parte interna del cluster de Kubernetes. Estos add-ons son visibles a través del API. Vamos a habilitar la interfaz que nos ofrece kubernetes: Para ello comenzamos habilitando el namespace kube-system, que viene en el fichero /opt/kubernetes/cluster/ubuntu/namespace.yaml para lo que ejecutamos el comando:

/opt/kubernetes/cluster/ubuntu/namespace.yaml
kubectl create -f /opt/kubernetes/cluster/ubuntu/namespace.yaml

Ahora habilitamos la interfaz gráfica, creando el Replication controller y el Service que vienen en el directorio /opt/kubernetes/cluster/addons/dashboard pero antes de eso, en el servicio exponemos el servicio fuera del cluster como hicimos anteriormente para poder acceder desde el navegador. Para ello simplemente añadimos type: NodePort justo después del spec.

/opt/kubernetes/cluster/addons/dashboard/dashboard-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: kubernetes-dashboard
  namespace: kube-system
  labels:
    k8s-app: kubernetes-dashboard
    kubernetes.io/cluster-service: "true"
spec:
  type: NodePort
  selector:
    k8s-app: kubernetes-dashboard
  ports:
  - port: 80
    targetPort: 9090

Ahora ya estamos listos para levantar la interfaz gráfica, para lo que usamos los siguientes comandos:

shell
kubectl create -f dashboard-controller.yaml
kubectl create -f dashboard-service.yaml

Al igual que antes, para poder acceder desde fuera tenemos que mapear el puerto en el fichero de configuración de Vagrant y hacer el comando vagrant reload. ¡Después accedemos por el navegador, y ya tenemos el dashboard preparado!.


7. Conclusiones

Espero que hayamos cubierto algunas de las características principales de la gestión de kubernetes, así como que seamos capaces de crear nuestros propios pods, replication controllers y services para ser capaces una buena infraestructura que nos permita manejar nuestros servicios de una forma amigable.


8. Referencias

Primeros pasos con Gulp

$
0
0
Este tutorial pretende ser una iniciación a la herramienta Gulp, que sirve para automatizar tareas en proyectos Javascript. En él veremos como instalarlo y como hacer una primera aplicación de ejemplo en Javascript que utilice Gulp para realizar unas sencillas tareas.

Índice de contenidos.

  1. Introducción.
  2. Entorno.
  3. Instalación.
  4. Funcionamiento.
  5. Gulp API.
  6. El gulpfile.
  7. Ejemplo de uso.
  8. Plugins.
  9. Conclusiones.
  10. Referencias.

1. Introducción.

Con el auge de los nuevos frameworks javascript como AngularJs, Backbone, EmberJs, React, Polymer, etc. surge la necesidad de herramientas para gestionar los proyectos.
Las arquitecturas basadas en estos frameworks necesitan un build system, es decir, es una colección de tareas que automatizan un trabajo repetitivo. De manera análoga en Java tendríamos Maven o en Groovy tendríamos Gradle.
Los componentes más comunes en un flujo típico de front-end son:
  • Gestores de dependencias
  • Preprocesadores (opcionalmente)
  • Herramientas para la construcción de tareas
Gulp es una herramienta para la gestión y automatización de tareas en Javascript. Estas tareas serán de uso común para los desarrolladores. El único requisito para utilizar Gulp es tener instalado Node.js
Algunas de las tareas más frecuentes son:
  • Compilación de CSS y Javascript preprocesado
  • Concatenación
  • Minificación
  • Lanzar un servidor para la recarga automática en el browser
  • Creación de una build para despliegue
  • Ejecución de tests unitarios
  • Ejecución de herramientas para detectar errores en el código
  • Gestionar el código en un repositorio

2. Entorno. Este tutorial está escrito usando el siguiente entorno:
  • Hardware: Portátil Mac Book Pro 15″ (2,3 Ghz Intel Core i7, 16 GB DDR3)
  • Sistema Operativo: Mac OS X El Capitan
  • NodeJS v4.4.3
  • npm 3.8.3
  • Atom 1.7.1
  • Gulp 3.9.0

3. Instalación.

Abrimos un terminal e instalamos Gulp de manera global con el siguiente comando.
sudo npm install --global gulp-cli

4. Funcionamiento.

A diferencia de otras herramientas de este tipo, como Grunt, Gulp utiliza streams para la ejecución de tareas. Un stream es un flujo de datos para el intercambio de información. Gulp utiliza el módulo Stream de Node.js. Internamente utiliza la librería vinyl-fs para leer y escribir en un stream.
Además se utilizan tuberías(pipes) para realizar operaciones sobre un stream. Las tuberías son un mecanismo para la comunicación y sincronización entre procesos.
  • Basados en el patrón productor/consumidor
  • Están implementadas de forma muy eficiente en los sistemas operativos
  • Inician todos los procesos al mismo tiempo
  • Atienden automáticamente los requerimientos de lectura de datos para cada proceso cuando los datos son escritos por el proceso anterior

5. Gulp API.

El API de Gulp es muy sencillo y consta 4 instrucciones básicas: gulp.task(), gulp.src(), gulp.dest() y gulp.watch(). image04
  1. gulp.task()
  • Define una tarea
  • Tiene 3 argumentos:
    • el nombre de la tarea
    • dependencias de otras tareas
    • la función a ejecutar
  • gulp.src()
    • Toma como parámetro una cadena que coincida con uno o más archivos
    • Utiliza patrones que usa el intérprete de comandos de unix(shell)
    • Retorna un stream que puede ser “pipeado” a un plugin adicional ó hacia el método gulp.dest()
  • gulp.dest()
    • Canaliza y escribe archivos desde un stream
      • Puede canalizar a varias carpetas
      • Creará las carpetas que no existan
      • Retornará el stream, por si deseamos realizar alguna acción más
    • Sirve para escribir los datos actuales de un stream
  • gulp.watch()
    • Observa archivos y realiza una acción cuando se modifica un archivo
    • Esto siempre devuelve un EventEmitter que emite los eventos de cambio
    • Tiene 2 formas de uso:
      • gulp.watch(glob, [tasks])
      • gulp.watch(glob, callback)
    • El primer parámetro es un glob con la ruta o un patrón con uno o varios ficheros
    • image01
    • El segundo puede ser una lista de tareas o una función a ejecutar
    • image00
    A partir de gulp 4 se incluyen algunas instrucciones nuevas. Las más relevantes son gulp.series() y gulp.parallel() para facilitar la definición de flujos de tareas. Aunque esto se podía realizar en la versión anterior de Gulp, de esta manera el código queda más claro y flexible.

  • gulp.series()
    • Realiza ejecución de tareas de manera secuencial
    • Tiene de 2 argumentos:
      • El nombre de la/s tarea/s a ejecutar
      • Una función a ejecutar (opcional)
  • gulp.parallel()
    • Realiza ejecución de tareas en paralelo
    • Tiene de 2 argumentos:
      • El nombre de la/s tarea/s a ejecutar
      • Una función a ejecutar (opcional)

    6. El gulpfile.

    En primer lugar debemos de crear un directorio para el proyecto y dentro crear un módulo de node de la siguiente manera.
    mkdir <nombre_proyecto>
    cd <nombre_proyecto>
    npm init
    A continuación completamos los valores que se solicitan (para el ejemplo basta con el nombre, versión y el entry point) para rellenar el package.json image02 Después ejecutamos el siguiente comando de npm para instalar gulp en el proyecto y guardarlo en el package.json.
    npm install gulp --save-dev
    Cuando se bajen dependencias de npm se guardarán por defecto en el directorio node_modules en la raíz de nuestro proyecto, y se incluirán dentro del package.json.

    Posteriormente debemos crear un fichero llamado gulpfile.js, que debe de estar en la raíz del proyecto (y estar referenciado en la propiedad main del package.json de nuestro proyecto).
    • El gulpfile debe de contener una estructura con los siguientes elementos:
      • La importación de otros módulos
      • La importación de un fichero de configuración del proyecto (opcional)
      • La definición de las tareas
      • Observadores que se ejecutan en función de ciertos cambios (opcional)
      • Una tarea por defecto a ejecutar
    • Si el gulpfile del proyecto es muy grande conviene modularizarlo en varios ficheros más pequeños por funcionalidad e importarlos
    • En una arquitectura donde tengamos muchos módulos similares conviene externalizar las tareas comunes en un módulo de Node.js para no repetir las mismas tareas en muchos puntos y que éstas puedan divergir.

    • 7. Ejemplo de uso.

      Una vez que tenemos creados el package.js y el gulpfile.js podemos definir nuestra primera tarea (la tarea por defecto). image06
    var gulp = require('gulp');
    
    gulp.task('default', function() {
        console.log('Hello world!');
    });
    image05
    A continuación vamos a ver cómo podemos crear una tarea para minificar código en javascript. Para empezar, vamos a crear un código de prueba simulando el que sería en código real de nuestra aplicación. Creamos un directorio src/js para guardar ahí nuestros ficheros de código. Los llamaremos file1.js y file2.js y contendrán lo siguiente:

    file1.js

    function add(paramA, paramB) {
      console.log('This is a sum');
      return paramA + paramB;
    }

    file2.js

    function substract(arg0, arg1) {
      console.log('This is a substract');
      return arg0 - arg1;
    }
    A continuación modificaremos nuestro gulpfile para añadir la tarea minify, que deberá concatenar y ofuscar el código. Para ello haremos uso de dos plugins de gulp, que son gulp-concat y gulp-uglify, que se importan en el gulpfile mediante require.
    npm install --save-dev gulp-concat
    npm install --save-dev gulp-uglify

    var gulp = require('gulp');
    var concat = require('gulp-concat');
    var uglify = require('gulp-uglify');
    
    gulp.task('minify', function() {
      console.log('minifying js ...');
    
      return gulp.src('src/js/*.js')
        .pipe(concat('all.js'))
        .pipe(gulp.dest('build/js/'));
    });
    
    gulp.task('default', function() {
        console.log('Hello world!');
    });
    Para empezar concatenamos el código tomando como fuente todos los ficheros javascript del directorio src/js, escribiendo el resultado en el fichero all.js en el directorio build. image07 Ahora modificamos la tarea para además de concatenar, ofuscar el código.
    gulp.task('minify', function() {
      console.log('minifying js ...');
    
      return gulp.src('src/js/*.js')
        .pipe(concat('all.js'))
        .pipe(uglify())
        .pipe(gulp.dest('build/js/'));
    });
    image03
    En este caso además de ofuscar el código queremos que se eliminen todos los comentarios que se generan con el console.log(). Para ello hay una propiedad en el plugin uglify que permite hacerlo. En general, cuando utilicemos plugins podemos buscar su documentación, como veremos en el punto siguiente(https://www.npmjs.com/package/gulp-uglify)

    Finalmente añadimos la opción drop_console en el plugin uglify para quitar los comentarios y aprovechamos para introducir un watcher que ejecute la tarea minify y añadimos la dependencia del watcher en la tarea por defecto. De esta manera cuando ejecutemos la tarea gulp se ejecutará el watcher y cuando se modifique algún fichero del código fuente se aplicará la tarea de minificación.
    var gulp = require('gulp');
    var concat = require('gulp-concat');
    var uglify = require('gulp-uglify');
    
    gulp.task('minify', function() {
      console.log('minifying js ...');
    
      return gulp.src('src/js/*.js')
        .pipe(concat('all.js'))
        .pipe(uglify({
          compress: {
            drop_console: true
          }
        }))
        .pipe(gulp.dest('build/js/'));
    });
    
    gulp.task('watch-js', function() {
      gulp.watch('src/js/*.js', ['minify'], function() {
        console.log('watching js changes...');
      });
    });
    
    gulp.task('default', ['watch-js'], function() {
      console.log('Executing gulp...');
    });

    Y el resultado final es este..
    image08

    8. Plugins.

    Gulp tiene alrededor de ~2346 plugins “oficiales” y otros no-oficiales. Los plugins disponibles permiten realizar tareas muy versátiles como:
    • Concatenar ficheros
    • Minimizar js
    • Minimizar imágenes
    • Inyectar dependencias dinámicamente
    • Ejecutar tests
    • Gestionar el código en un repositorio (svn, git)
    • Empaquetar un directorio
    • Ejecutar jshint, htmlhint
    • Generar css ejecutando saas, less, etc.
    • Mostrar ayuda
    En el ejemplo anterior hemos visto los plugins gulp-concat y gulp-uglify. A continuación vamos a mostrar algunos más.
    • gulp-zip
      • Para comprimir directorios con ficheros
      • Permite generar una distribución front-end
    var gulp = require('gulp');
    var zip = require('gulp-zip');
    
    gulp.task('default', function() {
      return gulp.src('src/*')
        .pipe(zip('archive.zip'))
        .pipe(gulp.dest('dist'));
    });
  • gulp-if
    • Permite ejecutar una tarea de manera condicional
    var gulpif = require('gulp-if');
    var uglify = require('gulp-uglify');
    
    var condition = true; // TODO: add business logic
    
    gulp.task('task', function() {
      gulp.src('./src/*.js')
        .pipe(gulpif(condition, uglify()))
        .pipe(gulp.dest('./dist/'));
    });
  • gulp-git
    • Para ejecutar comandos sobre un repositorio de código GIT
    • Commit, add, push, clone, etc
    var gulp = require('gulp');
    var git = require('gulp-git');
    
    // Run git commit without checking for a
    // message using raw arguments
    gulp.task('commit', function(){
      return gulp.src('./git-test/*')
        .pipe(git.commit(undefined, {
          args: '-m "initial commit"',
          disableMessageRequirement: true
        }));
    });
    
    // Run git push
    gulp.task('push', function(){
      git.push('origin', 'master', function (err) {
        if (err) throw err;
      });
    });

    Podemos encontrar documentación de los mismos en:

    9. Conclusiones.

    En los nuevos desarrollos con los frameworks javascript este tipo de herramientas son muy útiles para ayudarnos a ser productivos. Este tipo de herramientas junto con otros elementos del ecosistema javascript, como pueden ser la gestión de dependencias (con npm, bower, jspm, etc.), el uso de frameworks js (AngularJs, Flux, Marionette, etc.) la realización de tests (Jasmine, Protractor) un IDE amigable y ligero (Sublime, Atom, Webstorm, etc.) hacen que el desarrollo en javascript sea algo muy distinto a lo que era hace unos años.

    En particular Gulp (2014) no fue la primera herramienta relevante de este tipo en aparecer, fue Grunt (2012). Aunque Grunt sigue gozando de una gran popularidad, una gran cantidad de plugins desarrollados y una amplia comunidad de desarrolladores, Gulp es superior a Grunt en varios aspectos, lo cual hace que ya en este momento y en el futuro Gulp sea la herramienta de automatización de tareas predominante.

    En primer lugar Grunt tiene un enfoque de configuración sobre código, mientras que Gulp tiene un enfoque de código sobre configuración. Esto hace que Gulp sea mucho más conciso y claro, mientras que Grunt es más verboso. En segundo lugar Gulp es mucho más eficiente, por su concepción basado en el uso de streams implica operaciones de lectura y escritura en memoria. En el caso de Grunt, para encadenar varias operaciones en una tarea se realizan varias lecturas y escrituras en disco.

    Gulp es muy sencillo de aprender, pero en proyectos grandes debemos de tener cuidado en la definición y organización de nuestras tareas y las dependencias entre ellas.

    10. Referencias.

    Un entorno de programación en la nube: un vistazo a Cloud 9 IDE

    $
    0
    0

    La nube sigue en auge. En este artículo echaremos un vistazo al Cloud 9 IDE, un entorno de programación completamente funcional en la nube.

    Índice de contenidos


    1. Introducción

    portada de la web

    Se trata de un IDE multilenguaje en la nube al que accedemos desde nuestro navegador. Los beneficios de la nube son obvios: no necesitamos un maquinón, compartimos ese IDE en todos nuestros equipos, por tanto se puede comenzar a programar en uno, y continuar en otro únicamente iniciando sesión en el servicio, etc.

    Se puede conectar con GitHub, Google Cloud Platform o Bitbucket. Aun habiendome registrado en el servicio mediante mi cuenta de GitHub, he de decir que intimida el hecho de que tengas que autorizar al servicio a ver todo lo que hay en tus repositorios, ya sean públicos o privados. Igualmente tiene sentido porque de alguna forma tendrás que subir el código a tus repositorios.


    2. Entorno

    El tutorial está escrito usando el siguiente entorno:

    • Hardware: Portátil MacBook Pro 15′ (2 Ghz Intel Core I7, 8GB DDR3).
    • Sistema Operativo: Mac OS El Capitan 10.11

    3. Primer vistazo

    dashboard

    Lo primero que vemos una vez nos registramos en el servicio es nuestro dashboard, con un proyecto demo ya creado para mostrarnos las principales posibilidades del servicio, así como las características de la máquina que va a mover ese proyecto, en nuestro caso 1CPU, con 1GB de RAM y 5GB de HDD para la opción gratuita. Según indican en la página de precios, el rendimiento de las máquinas aumenta en cualquier modalidad de pago. Nosotros, por ahora, nos ceñiremos a la gratuita para tratar de medir su potencial.

    Dicho esto, nos corroe la curiosidad, por lo que navegamos al proyecto de ejemplo con rapidez.

    pantalla de bienvenida del proyecto

    Nos encontramos ante un IDE en toda regla. Incluso con un terminal en la parte de abajo ya situado en el directorio de nuestro, o el gráfico de consumo de recursos de la máquina que estamos usando, en la parte superior derecha: consumo de memoria, CPU y disco.

    Este proyecto tiene a su vez cinco diferentes partes, cinco aplicaciones en distintos lenguajes: html, node.js, php, python y ruby.

    Echando un vistazo a las opciones del IDE, no tiene nada que envidiar a un IDE de escritorio.

    settings

    Antes de empezar a cacharrear, una última prueba. Ejecutamos la parte en html, que arranca un apache, como vemos en el terminal y nos proporciona una URL a la que acceder desde nuestro navegador desde la que ver el resultado.

    despliegue apache

    No es más que un hello world.

    hello world html

    4. Debug

    Ahora vamos a ver cómo se comporta el debugger. Para ello, creamos un programita sencillo con una función suma que muestra por pantalla el resultado de la suma. Para poner un punto de ruptura, como en otros IDEs, hay que hacer click en la parte inmediatamente a la izquierda del número de línea en el editor. Después, ejecutar el programa. Durante la ejecución se parará en el punto que hemos marcado y mostrará un panel a la derecha como el que vemos en la imagen.

    debug

    En la parte de arriba del panel de debug tenemos las instrucciones típicas para dirigir la ejecución según nos convenga: reanudar ejecución, step over, step into, etc. Además, podemos observar los valores de las variables en cada momento o de las expresiones que declaremos, así como la pila.


    5. Editor

    El editor es bastante potente. Configurable incluso en modo vim, emacs o sublime. Se dispone de varios temas para la gente como yo, que gusta de editores de colores oscuros.

    Para los que disponen de una pantalla grande, es posible partir el editor en varias ventanas, tanto horizontal como verticalmente de varias formas distintas. De esta forma se aprovecha más la pantalla y se nos permite ver varios ficheros a la vez.

    editor partido

    Por supuesto, dispone de autocompletado con el habitual Ctrl + Espacio

    autocompletado

    6. Limitaciones

    Quizá a alguno le preocupe el rendimiento. Es sólo una opinión personal, pero el rendimiento es muy bueno, incluso usando una cuenta gratuita que en principio tiene menos recursos que una de pago. El único problema de latencia que he tenido ha sido por mi propia red. Aún así, no lo he probado con un proyecto grande, sólo pequeños programas de prueba. Para una carga más elevada quizá sí se resienta un poco. En cualquier caso tenemos en todo momento visible el consumo de la máquina en la parte superior derecha como hemos indicado con anterioridad.

    Otro de los problemas que nos podemos encontrar con las cuentas gratuitas es que, si no se usa durante un tiempo, acaban por hibernar la máquina virtual que aloja tu entorno. Por tanto, en el momento de arrancar tras un tiempo el entorno tardará más en arrancar.

    Limitación

    7. Conclusiones

    En cualquier caso, la nube no deja de crecer y proporcionar servicios con los que antes soñábamos. Cloud 9 IDE provee un entorno de programación con muchísima funcionalidad en tu navegador de forma sencilla sin apenas configuración. No dudéis en echarle un vistazo y comentar vuestras conclusiones.


    8. Referencias

    Pruebas multiplataforma con Selenium GRID

    $
    0
    0
    Cómo configurar Selenium GRID para realizar pruebas de aplicaciones web sobre distintos entornos

    Índice de contenidos

    1. Introducción

    En adictos hay disponibles múltiples tutoriales que enseñan a utilizar y configurar Selenium. Es fácil que a menudo se nos presente como requisito ejecutar la aplicación web en un sistema diferente al que utilizamos para desarrollarla. En este tutorial vamos a ver cómo configurar una máquina virtual Windows 7, con Internet Explorer 11 para ejecutar nuestros test.

    2. Entorno

    El tutorial está escrito usando el siguiente entorno:

    • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core i7, 16GB DDR3)
    • Sistema Operativo: Mac OS El Capitan 10.11
    • VirtualBox 5.0.16 r105871
    • Windows 7, Internet Explorer 11 (máquina virtual)
    • Selenium Server 2.53.0
    • IntelliJ IDEA 2016.1

    3. Configuración de VirtualBox y descarga de la máquina virtual

    Existen multitud de gestores de máquinas virtuales, en nuestro caso utilizaremos VirtualBox, gratuito y descargable aquí.

    Además, necesitaremos una máquina virtual. Microsoft pone a nuestra disposición máquinas virtuales ya creadas, gratuitas, para el uso por desarrolladores. Si bien están pensadas para pruebas, nos permiten instalar software como Java, necesario para nuestras pruebas. Podemos descargarlas desde aquí.

    La configuración por defecto de la máquina es correcta para nuestros propósitos, sólo hay que modificar la configuración de los interfaces de red para que ambas máquinas puedan comunicarse. En nuestro caso, configuramos dos interfaces de red:

    • NAT para disponer de acceso a internet para, por ejemplo, instalar java y acceder a la página web de Google en los test.
    • Adaptador sólo anfitrión, para que pueda existir comunicación entre ambas máquinas.

    4. Configuración de Selenium GRID

    Para realizar las pruebas en remoto necesitamos dos componentes básicos, un servidor que atienda las peticiones de nuestros test (HUB), y uno o más nodos para resolverlas. En función del tipo de nodo, contará con una serie de características, y eso mismo es lo que utiliza el servidor para enrutar dichas peticiones.

    Cuando realizamos una petición a Selenium HUB lo hacemos con unas DesiredCapabilities concretas, por lo que buscará en los nodos que tenga registrados y dirigirá la petición en consonancia.

    4.1. Servidor autónomo local

    Tendremos que ejecutar el siguiente comando en nuestro equipo para levantar la instancia de Selenium HUB que funcionará como servidor:

    java -jar selenium-server-standalone-2.53.0.jar -role hub

    Anotamos la dirección y el puerto en el que se ha levantado, ya que nos hará falta para indicarle al nodo a que hub debe registrarse.

    4.2. Nodo remoto

    En la máquina virtual, tendremos que levantar el mismo servidor para que funcione como nodo, indicando la dirección de nuestro hub, la ip con la que lo levantamos y el puerto si fuera necesario. Para evitar posteriores quebraderos de cabeza y registrar correctamente el nodo, mediante ipconfig obtenemos la dirección asignada al adaptador “sólo anfitrión”, y mediante ping desde el servidor, verificamos la conectividad.

    Ejecutaremos el siguiente comando para que levante el servidor como nodo y se registre en el hub:

    java -jar selenium-server-standalone-2.53.0.jar -role node -hub http://<ip_hub>:<puerto_hub> -host <ip_máquina_virtual> -port <puerto_máquina_virtual>

    5. Lanzar las pruebas

    Cuando estemos seguros de que el entorno está configurado, y que ambas máquinas se ven, lanzaremos las pruebas utilizando la clase RemoteWebDriver. En el ejemplo únicamente vamos a comprobar el funcionamiento del entorno. Lo primero es añadir las dependencias en Maven:

    pom.xml
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>2.52.0</version>
    </dependency>
    Y pasamos a comprobar si pasa un test sencillo en la máquina remota:
    TestSeleniumHub.java
    @Test
    public void remoteHubTest() {
        try {
             DesiredCapabilities desiredCapabilities = DesiredCapabilities.internetExplorer();
             desiredCapabilities.setPlatform(Platform.WINDOWS);
    
             URL hubURL = new URL("http://localhost:4444/wd/hub");
             WebDriver webDriver = new RemoteWebDriver(hubURL, desiredCapabilities);
             webDriver.manage().window().maximize();
             webDriver.get("http://www.google.es");
    
             Assert.assertEquals("Google", webDriver.getTitle());
             webDriver.close();
        } catch (MalformedURLException e) {
             fail(e.getMessage());
        }
    }

    Si no conseguimos que se ejecute, lo más probable es que tengamos algún problema de comunicación entre nodos y servidor. Comprobar que efectivamente la ip con la que se ha levantado el nodo es visible desde la máquina servidor, y que el nodo puede comunicar con el hub.

    6. Conclusiones

    Siempre que nos interese desarrollar en una plataforma diferente a la que se va a utilizar como entorno de producción, o bien si nos interesa lanzar determinados test sobre una máquina diferente, tenemos la posibilidad de realizarlo fácilmente mediante el uso de Selenium HUB.

    También podemos configurar un HUB común a todos los desarrolladores, de manera que todo el equipo realice los test bajo las mismas condiciones, permitiendo homogeneidad en los desarrollos.

    7. Referencias

    Inyección de dependencias en Android con Dagger 2

    $
    0
    0
    En este tutorial vamos a ver cómo empezar a usar Dagger2 en nuestras aplicaciones Android.

    Índice de contenidos

    • Introducción
    • Módulos y Componentes
    • Inyección de dependencias en Android
    • Dependencias
    • Creando nuestro primer módulo de Dagger
    • Creando un componente de aplicación
    • Inyectando dependencias en una Activity con Dagger
    • Sobre los Scopes
    • Conclusiones

    Dagger2 es un contenedor de dependencias desarrollado con la idea de solucionar algunos de los problemas que tenían otros contenedores de dependencias usados en Java.

    Introducción

    El principal cambio de Dagger es que mueve una gran parte del proceso de creación del grafo de dependencias a tiempo de compilación, consiguiendo así descubrir muchos de los errores que suelen ocurrir en tiempo de ejecución(runtime) antes de arrancar la aplicación.

    Además esto permite un arranque de la aplicación mucho más rápido, ya que no necesita generar el grafo cada vez que se arranca la aplicación, esto es especialmente útil en aplicaciones móviles y en servicios que son desplegados continuamente, ya que este tipo de aplicaciones es especialmente sensible al tiempo de arranque.

    Dagger consigue esto usando apt, lo que permite en tiempo de compilación analizar las diferentes anotaciones y generar el grafo de objetos y las clases necesarías para hacer la inyección de dependencias, capturando una buena parte de los errores asociados a los contenedores de dependencias (dependencias no satisfechas, dependencias circulares..).

    Módulos y Componentes

    Dagger se compone principalmente de módulos y componentes:

    • Los módulos contienen las diferentes dependencias, mediante una serie de métodos proveedores, se encargan de crear aquellas dependencias que requieran algún tipo de proceso especial a la hora de ser creadas. Para algunas clases no es necesario hacer ningún tipo de procesamiento antes de ser creadas, para esas basta con usar la etiqueta @Inject en el constructor. Un módulo puede estar asociado a uno o más componentes.
    • Los componentes son los encargados de inyectar las dependencias y pueden asociarse a distintos ciclos de vida de la aplicación.

    Inyección de dependencias en Android

    Android tiene varias particularidades que hacen más dificil usar contenedores de dependencias en nuestras aplicaciones, cómo por ejemplo:

    • No tenemos control sobre la creación de nuestras clases de entrada (Actividades), ni podemos sobreescribir los constructores, ya que Android las crea a partir de reflexión y no servirá de nada. El punto de entrada suele ser el método onCreate y onResume, por lo que es necesario adaptarse a esta circunstancia.
    • Aunque hoy en día la memoria ya no sea un problema en los dispositivos móviles, conviene no tener en memoria demasiados objetos en desuso, por lo que tener un solo grafo de objetos en aplicaciones muy grandes es perjudicial.
    • El sistema puede decidir matar la aplicación en cualquier momento para recuperar memoria, pero cuando el usuario pulse en la aplicación espera que esta se abra rápidamente, por lo que el arranque de esta ha de ser lo más rápido posible.

    A continuación vamos a crear un proyecto de prueba en el que vamos a ver los primeros pasos para incluir Dagger en un proyecto Android. Tódo el código lo puedes encontrar en GitHub.

    Dependencias

    El primer paso es añadir las dependencias necesarias en Gradle, por un lado tenemos que añadir el plugin de apt en nuestro fichero build.gradle exterior.

    dependencies {
        classpath 'com.android.tools.build:gradle:2.0.0-rc1'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }

    Este nos permitirá usar el procesador de anotaciones de Java en la build de Gradle. El siguiente paso será aplicar este plugin en el fichero gradle del módulo donde vayamos a usar Dagger (en este caso el módulo app).

    apply plugin: 'com.neenbedankt.android-apt'
    ....
    ....
    
    dependencies {
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:appcompat-v7:23.2.1'
        compile 'com.google.dagger:dagger:2.0.2'
        apt 'com.google.dagger:dagger-compiler:2.0.2'
        provided 'javax.annotation:jsr250-api:1.0'
    }

    Esto incluirá Dagger cómo una dependencia normal y el compilador de Dagger cómo una dependencia apt, que será la encargada de hacer todo el pre-procesado de las anotaciones.

    Con esto ya tendremos todas las dependencias necesarias para empezar a usar Dagger en nuestra aplicación.

    Creando nuestro primer módulo de Dagger

    Nuestro primer paso con Dagger será crear un módulo que encapsule algunas de las dependencias que nos proporciona el SDK de Android.

    Un módulo se define cómo una clase Java anotada con @Module :

    @Module
    public class SystemModule {
    
        private final Application application;
    
        public SystemModule(Application application) {
            this.application = application;
        }
    
        @Provides
        Context provideContext(){
            return application;
        }
    
    }

    En este caso el módulo tiene la dependencia directa con la clase Application, la cual nos permitirá acceder al contexto de Android para sacar las dependencias necesarias del SDK.

    Además tenemos el método anotado provideContext el cual provee de un contexto a todas aquellas clases que requieran el contexto de Android cómo dependencia.

    Creando un componente de aplicación.

    El siguiente paso será el de crear un componente que incluya el módulo de Sistema que acabamos de crear. Un componente se define cómo una interfaz anotada con @Component.

    @Singleton
    @Component(modules = {SystemModule.class})
    public interface SystemComponent {
        void inject(MainActivity activity);
    }

    La anotación @Singleton define el ciclo de vida de este componente, en este caso el ciclo de vida es toda la aplicación, más adelante ahondaremos sobre el tema de los ciclos de vida de los componentes.

    El compilador de Dagger será el encargado de implementar esta clase y generar el método inject el cual inyectará todas las dependencias en la clase deseada. Esto solo es necesario para clases que no cree Dagger, solucionando uno de los problemas de la inyección de dependencias en Android. Por ejemplo:

    class ClassThatUsesContext {
        @Inject
        public ClassThatUsesContext(Context context) {
            // do something with the context
        }
    }

    Esta clase la creará Dagger en el momento en el que la marquemos cómo una dependencia de otra clase, pasando al constructor todas las dependencias que este tenga(siempre que estas se encuentren en el contenedor, ya sea teniendo un constructor con @Inject o un método que las (@Provides) provea en algún módulo).

    Cómo he dicho anteriormente, es Dagger el encargado de crear la implementación de la interfaz de nuestro componente, esto lo haremos al crear la aplicación

    public class Dagger2Application extends Application {
    
        private SystemComponent systemComponent;
    
        @Override
        public void onCreate() {
            super.onCreate();
            this.systemComponent = DaggerSystemComponent.builder()
                    .systemModule(new SystemModule(this))
                    .build();
        }
    
        public SystemComponent getSystemComponent() {
            return this.systemComponent;
        }
    }

    Dagger nos habrá generado la clase DaggerSystemComponent, la cual es un Builder que nos permitirá proveer los módulos que tiene cómo dependencias nuestro Component. Por último proveemos de un método para obtener el componente, el cual nos permitirá tener acceso al método inject en las clases deseadas.

    Inyectando dependencias en una Activity con Dagger

    Una vez completados los pasos anteriores ya estamos listos para inyectar nuestras dependencias en el Activity, para ello tenemos que hacernos con una referencia a nuestro SystemComponent.

    public class BaseActivity extends AppCompatActivity {
        public SystemComponent getSystemComponent() {
            return ((Dagger2Application) getApplication()).getSystemComponent();
        }
    }

    Con este sencillo método en nuestra BaseActivity podemos disponer del SystemComponent en todas las activities que dependen de ella.

    public class MainActivity extends BaseActivity {
    
        @Inject
        ConnectivityManager connectivityManager;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            getSystemComponent().inject(this);
    
            boolean activeNetworkMetered = connectivityManager.isActiveNetworkMetered();
            logger.log("Network is metered? " + activeNetworkMetered);
        }
    
    }

    Y tan solo nos quedaría llamar al método inject en el método onCreate, a partir de este método ya podríamos usar cualquiera de las dependencias inyectadas por Dagger, cómo puede verse en el ejemplo.

    *El método inject en Dagger es síncrono, por lo que no hay problema en usarlo justo después

    Manejo de Scopes y ciclos de vida de los componentes en Dagger2

    Los Scopes permiten indicar el ciclo de vida de una dependencia, por ejemplo:

    "Queremos que la dependencia UserPreferencesManager se inicie en el momento en el que el usuario hace login(o al inicio de la app si este está logado) y se destruya en el momento en el que el usuario hace logout."

    Para conseguir esto con Dagger2 debemos crear un Scope personalizado, el Scope es una anotación con la que anotaremos las diferentes dependencias indicando el Scope que tiene esa dependencia. Un ejemplo de Scope sería @Singleton.

    A continuación vamos a ver cómo crear un Scope personalizado en Dagger.

    El primer paso es crear una anotación UserScope

    @Scope
    @Retention(RetentionPolicy.RUNTIME)
    public @interface UserScope {
    }

    En este caso queremos que nuestro componente de Usuario extienda del de sistema, es decir, que todas las dependencias declaradas por los módulos del componente de sistema estén disponibles para este componente. Para ello, el componente de Usuario se declarará cómo @Subcomponent

    @UserScope
    @Subcomponent(
            modules = {
                    UserModule.class
            }
    )
    public interface UserComponent {
        void inject(UserActivity userActivity);
    }

    Además lo anotaremos con la anotación UserScope y le indicaremos que depende del módulo UserModule. Esta clase también tiene que tener un método inject por cada clase en la que sea inyectado.

    Nuestro UserModule se creará a partir de un objeto User y existirá mientras el usuario está logado.

    @Module
    public class UserModule {
    
        private final User user;
    
        public UserModule(User user) {
            this.user = user;
        }
    
        @Provides
        User provideUser() {
            return this.user;
        }
    }

    Cómo hemos dicho con antelación, queremos que nuestro UserModule tenga también acceso a todas las clases que están en los módulos del SystemComponent, cómo UserComponent es un subcomponente de este, el método de creación es un poco distinto.

    El primer paso es añadir el siguiente método al interfaz de SystemComponent:

    UserComponent plus(UserModule userModule);

    Y añadir un método a nuestra clase Application que permitirá crear el UserComponent:

    public UserComponent createUserComponent(User user) {
        userComponent = systemComponent.plus(new UserModule(user));
        return userComponent;
    }

    En nuestra UserActivity creamos el componente, que podrá ser reutilizado hasta que el usuario realice logout:

    public class UserActivity extends BaseActivity implements View.OnClickListener {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            User user = (User) getIntent().getSerializableExtra("User");
            UserComponent userComponent = getApp().createUserComponent(user);
            userComponent.inject(this);
        }
    
        @Override
        public void onClick(View v) {
            // onLogoutClick
            getApp().releaseUserComponent();
            finish();
        }
    }

    Sobre los Scopes

    Cómo hemos podido comprobar las Scopes no hacen automáticamente el trabajo por nosotros, en última instancia somos nosotros quienes tenemos que indicar el momento en el que empieza un Scope y en el momento en el que acaba. Un lector podría pensar, entonces, de qué nos sirven los Scopes. La respuesta es sencilla, el compilador nos protegerá de meter la pata, cómo por ejemplo en el siguiente ejemplo:

    @Singleton
    @Provides
    ConnectivityManager provideConnectivityManager(UserPreferencesManager userPreferencesManager) {
        return (ConnectivityManager) application.getSystemService(Context.CONNECTIVITY_SERVICE);
    }

    La dependencia ConnectivityManager está declarada con un Scope de Singleton, es decir, que está pensada para que dure el mismo tiempo que la aplicación, en cambio UserPreferencesManager está declarada con un Scope de User, el compilador nos avisará de que esto no se puede hacer, y devolverá un error al intentar compilar el proyecto:

    dagger_error_1

    Por un lado nos avisa que el Scope de UserPreferencesManager es distinto y además que no encuentra ningún objeto User que poder inyectar, ya que este objecto sólo existe en el UserComponent.

    Conclusiones

    Cómo hemos podido ver Dagger requiere un poco más de trabajo que otros contenedores de dependencias cómo pueden ser Spring y Guice, y más cuando solemos usarlos dentro de nuestro framework web, que hace que se integre sin casi notarlo en nuestras clases. Android en este sentido presenta más dificultades, ya que el SDK de Android no fue pensado de cara a facilitar la inyección de dependencias. Las ventajas de tener un contenedor de dependencias son claras, desde el punto de vista del testing y la mantenibilidad del código, y ahora gracias a Dagger2 no hay excusa de no usar uno en Android :).

    Complejidad computacional de BigInteger.multiply() en Java 8

    $
    0
    0
    En Java8, BigInteger incluye algoritmos con una menor complejidad computacional para la multiplicación y división de cifras grandes.  Aún podría mejorarse más paralelizando el método multiply() con Fork/Join.

    Éste artículo es una traducción al castellano de la entrada original publicada, en inglés, por Dr. Heinz Kabutz en su número 236 del JavaSpecialists newsletter. Puedes consultar el texto original en Javaspecialists’ Newsletter #236: Computational Complexity of BigInteger.multiply() in Java 8

    Este artículo se publica traducido en adictos, con permiso del autor, por David Gómez García, (@dgomezg)consultor tecnológico en Autentia, colaborador de JavaSpecialists e instructor certificado para impartir los cursos de JavaSpecialists.

    Complejidad computacional de BigInteger.multiply() en Java 8

    Curioseando por el código de Java de forma aleatoria, hace unos meses, encontré que BigInteger había sido mejorado. En versiones anteriores, el método multiply implementaba el mismo mecanismo que utilizaba tu profesor de matemáticas en primaria para torturarte: O(n2). En un número anterior de Javaspecialists’ Newsletter, ya comenté que el algoritmo de Karatsuba es mejor cuando tenemos números grandes, ya que proporciona O(n lg 3) o O(n 1,585). Otro algoritmo es el algoritmo de Toom-Cook (también conocido como Toom-3), que tiene O(n 1,465). Aunque ambos parecen muy similares, la diferencia es grande cuando n crece. Aquí tenemos un pequeño código que lo demuestra:

    public class BigOComparison {
      public static void main(String... args) {
        for (int n = 1; n <= 1_000_000_000; n *= 10) {
          double n_2 = Math.pow(n, 2);
          double n_1_585 = Math.pow(n, 1.585);
          double n_1_465 = Math.pow(n, 1.465);
    
          double karatsuba_faster_than_quadratic = n_2 / n_1_585;
          double toom_cook_faster_than_karatsuba = n_1_585 / n_1_465;
    
          System.out.printf("%d\t%.2fx\t%.2fx%n",
              n, karatsuba_faster_than_quadratic,
              toom_cook_faster_than_karatsuba);
        }
      }
    }

    Podemos comprobar que, a medida que n incrementa su valor, la diferencia en la complejidad computacional se hace más evidente. Con n = 1_000_000_000, n1,585 resulta 5.000 veces más rápido que n2. Pero n1,465 todavía es 12 veces más rápido:

    1              1.00x   1.00x
    	10             2.60x   1.32x
    	100            6.76x   1.74x
    	1000          17.58x   2.29x
    	10000         45.71x   3.02x
    	100000       118.85x   3.98x
    	1000000      309.03x   5.25x
    	10000000     803.53x   6.92x
    	100000000   2089.30x   9.12x
    	1000000000  5432.50x  12.02x

    Naturalmente, hay costes de inicialización que no se tienen en cuenta en la notación de Landau (O grande). Por este motivo, sólo deberíamos utilizar Karatsuba para cifras grandes, y Toom Cook para cifras aún mayores.

    Java 8 incluye ambos algoritmos en la implementación de BigInteger. Para comprobar la mejora en el rendimiento, aquí vemos el cálculo de Fibonacci con el Algoritmo de la suma de los cuadrados de Dijkstra:

    public class Fibonacci {
      public BigInteger f(int n) {
        if (n == 0) return BigInteger.ZERO;
        if (n == 1) return BigInteger.ONE;
        if (n % 2 == 1) { // F(2n-1) = F(n-1)^2 + F(n)^2
          n = (n + 1) / 2;
          BigInteger fn_1 = f(n - 1);
          BigInteger fn = f(n);
          return fn_1.multiply(fn_1).add(fn.multiply(fn));
        } else { // F(2n) = ( 2 F(n-1) + F(n) ) F(n)
          n = n / 2;
          BigInteger fn_1 = f(n - 1);
          BigInteger fn = f(n);
          return fn_1.shiftLeft(1).add(fn).multiply(fn);
        }
      }
    
      public BigInteger f_slow(int n) {
        if (n == 0) return BigInteger.ZERO;
        if (n == 1) return BigInteger.ONE;
        return f_slow(n - 1).add(f_slow(n - 2));
      }
    }

    El método f_slow() está preparado para permitirnos probar nuestro algoritmo rápido, pero no será útil por encima de n=30.

    Aquí tenemos una clase de test que podemos ejecutar con Java 7 y con Java 8 para comprobar cómo la reducción de la complejidad computacional en el algoritmo de multiply() acelera la ejecución en Java 8.

    public class FibonacciTest {
      public static void main(String... args) {
        Fibonacci fib = new Fibonacci();
    
        for (int i = 0; i < 10; i++) {
          if (!fib.f(i).equals(fib.f_slow(i))) {
            throw new AssertionError("Mismatch at i=" + i);
          }
        }
    
        for (int n = 10000; n < 50_000_000; n *= 2) {
          timeTest(fib, n);
        }
      }
    
      private static void timeTest(Fibonacci fib, int n) {
        System.out.printf("fib(%,d)%n", n);
        long time = System.currentTimeMillis();
        System.out.println(fib.f(n).bitLength());
        time = System.currentTimeMillis() - time;
        System.out.println("time = " + time);
      }
    }

    Y aquí tenemos la salida de su ejecución con Java 7:

    heinz$ java -showversion FibonacciTest
    java version "1.7.0_80"
    Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
    Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)
    fib(10,000)
    6942
    time = 14
    fib(20,000)
    13884
    time = 10
    fib(40,000)
    27769
    time = 11
    fib(80,000)
    55539
    time = 23
    fib(160,000)
    111078
    time = 51
    fib(320,000)
    222157
    time = 108
    fib(640,000)
    444314
    time = 181
    fib(1,280,000)
    888629
    time = 590
    fib(2,560,000)
    1777259
    time = 2530
    fib(5,120,000)
    3554518
    time = 8785
    fib(10,240,000)
    7109037
    time = 34603
    fib(20,480,000)
    14218074
    time = 142635
    fib(40,960,000)
    28436148
    time = 586950

    Podemos comprobar como, a medida que el valor de n se duplica, el tiempo de ejecución básicamente se cuadruplica. También se puede ver, en el numero de bits, que los resultados son relativamente grandes.

    Y ahora con Java 8. Para numeros pequeños, no observamos mucha diferencia respecto a Java 7, pero empezamos a ver divergencia aproximadamente en fib(1.280.000). Java 8 calcula fib(40.960.000) alrededor de 50 veces más rápido. No he tenido paciencia suficiente para hacer el cálculo con cifras mayores, porque tenía un vuelo que coger :-). No obstante, esperaría que el siguiente punto de cálculo fuese más o menos 75 veces más rápido.

    heinz$ java -showversion FibonacciTest
    java version "1.8.0_74"
    Java(TM) SE Runtime Environment (build 1.8.0_74-b02)
    Java HotSpot(TM) 64-Bit Server VM (build 25.74-b02, mixed mode)
    fib(10,000)
    6942
    time = 6
    fib(20,000)
    13884
    time = 3
    fib(40,000)
    27769
    time = 7
    fib(80,000)
    55539
    time = 16
    fib(160,000)
    111078
    time = 27
    fib(320,000)
    222157
    time = 40
    fib(640,000)
    444314
    time = 58
    fib(1,280,000)
    888629
    time = 155
    fib(2,560,000)
    1777259
    time = 324
    fib(5,120,000)
    3554518
    time = 734
    fib(10,240,000)
    7109037
    time = 1661
    fib(20,480,000)
    14218074
    time = 4412
    fib(40,960,000)
    28436148
    time = 11870

    Así pues, ahora ya hemos visto que Java 8 ha mejorado al menos en un punto. De todas formas, no ha sido suficiente, en mi opinión. Tanto Karatsuba como Toom-Cook pueden mejorarse todavía más con una división recursiva. Si realmente necesitas trabajar con cifras realmente grandes, probablemente querrás poner la carga en el hardware. Yo lo he probado modificando BigInteger para añadirle una pequeña MultiplyTask:

    private static final class MultiplyTask
    	    extends RecursiveTask {
    	  private final BigInteger b1, b2;
    
    	  public MultiplyTask(BigInteger b1, BigInteger b2) {
    	    this.b1 = b1;
    	    this.b2 = b2;
    	  }
    
    	  protected BigInteger compute() {
    	    return b1.multiply(b2);
    	  }
    	}

    para después, cambiar el siguiente fragmento método multiplyKaratsuba():

    BigInteger p1 = xh.multiply(yh);  // p1 = xh*yh
      BigInteger p2 = xl.multiply(yl);  // p2 = xl*yl
    por el siguiente código
    MultiplyTask mt1 = new MultiplyTask(xh, yh);
    	mt1.fork();
    	BigInteger p2 = xl.multiply(yl);  // p2 = xl*yl
    	BigInteger p1 = mt1.join();//xh.multiply(yh);  // p1 = xh*yh

    Por defecto, este nuevo código utiliza el Pool común de Fork/Join, pero sería genial añadir un nuevo método a BigInteger que nos permitiese multiplicar en paralelo, por ejemplo: BigInteger.multiply(BigInteger, ForkJoinPool) o más explicitamente BigInteger.multiplyParallel(BigInteger, ForkJoinPool).

    Mis modificaciónes a BigInteger han funcionado bastante bien. También he utilizado ManagedBlocker para implementar un “esquema de caché reservada” en Fibonacci que evite calcular el mismo número dos veces. ManagedBlocker funciona muy bien y mantiene los cores de mi máquina más ocupados.

    Aquí teneis un tweet donde se ve en funcionamiento mi solución en paralelo sin utilizar ManagedBlocker. Observa que los cores están ociosos, especialmente al principio de la ejecución, cuando las cifras que se están multiplicando son pequeñas. En este segundo tweet, con el mismo código, pero con el “esquema de cache reservada” que usa ManagedBlocker para mantener el ForkJoinPool más activo. fib(1_000_000_000) acaba un 8% más rápido, según se ve en el gráfico de las CPUs, utilizando el hardware disponible mucho mejor. Si tienes curiosidad, este es el tipo de conceptos sobre los que hablamos en el curso de “Extreme Java – Concurrency and Performance for Java 8”, que también puedes recibir en Español.

    Espero que hayas disfrutado del artículo y que te haya resultado útil.

    Saludos desde la soleada y acogedora Grecia.

    Heinz.

    Cuando un producto fracasa antes de empezar

    $
    0
    0
    En este artículo nos centraremos en los pasos necesarios para que un producto no fracase antes de empezar, de los cuales nos ha hablado hoy nuestro director Roberto Canales Mora.

    Antes de hablar con Roberto, nuestro director, enfoqué este artículo en cómo tener un producto de éxito. Sin embargo, después de comentarle la existencia de este, Roberto me dijo que el éxito no está asegurado, que realmente la charla estuvo enfocada a crear las bases para que tu producto no haya fracasado antes de empezar. Por lo tanto, nos debemos plantear dejar de programar algunos días y dedicar tiempo de todo el equipo a atender los pasos que rodean la comercialización del producto. Debemos ver el producto como un todo y no solo como el desarrollo de un software que creemos buena idea y nos apetece construir. Esto es algo que no todo el mundo se plantea y prueba de ello son artículos como este que nos dicen que el 80% de los nuevos productos fracasan. Esto no significa que tengamos el fracaso asegurado, si no que debemos aprender cuáles son las razones por las que un producto puede fracasar y poder solucionarlo, o al menos tratar de usar todas las herramientas a nuestra disposición para ello.

    Captura de pantalla 2016-04-21 a las 9.14.20

    A continuación voy a enumerar los pasos y más adelante los detallaré uno a uno.
    1. Elevator Pitch, presentar tu producto brevemente pero con eficiencia.
    2. Conoce tu producto, documéntalo y envíaselo a tu potencial cliente.
    3. Reúnete con tu cliente y explícale tu producto.
    4. Oferta tu producto de la mejor forma posible.
    5. Construye la plataforma para que tu cliente use el producto que ha adquirido.
    6. El cliente usará el producto durante un tiempo.
    7. Realiza informes que animen a tu cliente y refuerza el uso de tu producto.
    8. Mejora e innova tu producto, soluciona los errores y crece.

    1. Elevator Pitch (discurso de ascensor)
    Este término, que proviene del inglés, hace referencia a un discurso o presentación que debe convencer a alguien en menos de lo que dura un viaje en ascensor. Esto, en nuestro contexto, se traduce a que debes ser breve y conciso a la hora de hablar de tu producto. Aprovecha todo este tiempo para contar la información más relevante, qué es lo que te diferencia de otros productos similares (en el caso de que los haya), despertar curiosidad al cliente en potencia… En resumen, hay que saber qué vendes, a quién se lo vendes y cómo se lo vendes.

    2. Conoce tu producto
    Los clientes que hayas captado durante el elevator pitch van a necesitar información extra que les exponga de manera un poco más extensa el producto por el que han demostrado interés. Realiza transparencias, vídeos o demostraciones de tu producto y dáselas a conocer a tu potencial cliente para que se haga una idea más concreta de qué es lo que vendes. Correo web, Whatsapp, teléfono… cualquier vía de contacto que tengamos de nuestro cliente será lo que usemos para enviar toda la información que vaya a necesitar.

    3. Reunión con los clientes
    En este punto debemos tener claro que el proceso puede alargarse en el tiempo, pero esto no debe de ser un problema. Planea reuniones con tus clientes de una duración máxima de 1 hora en la que poder presentar tu producto de manera extensa. Existen infinidad de estudios que hablan sobre las formas de tratar con clientes, pero este artículo no va enfocado a ese tema. No obstante, os dejo una serie de links al final del artículo que hablan sobre ello.

    4. Una oferta que no podrá rechazar
    Saber hacer ofertas de tu producto está relacionado con ser realista con las capacidades de este. Pongamos un ejemplo: ¿Qué precio crees que tendría un producto que es capaz de generar veinte mil euros anuales? Ahora piensa en su precio de nuevo si fuera capaz de generar veinte millones de euros anuales.
    Hay que ser lo más realista posible con tu producto antes de ofertarlo. Una vez que tengamos una estimación del impacto de tu producto, podrás ofertarlo de manera que le resulte atractivo al cliente. Además incluirémos siempre las condiciones de uso, contrato y toda la documentación necesaria para cerrar un trato.

    5. Construye la plataforma
    Una vez el cliente haya aceptado el trato debes enseñarle lo necesario para que use tu producto, es decir, construir la plataforma que tu producto necesita para funcionar y que el cliente aprenda a usarla. Es en este punto cuando el cliente va a poder usar realmente tu producto. Se trata de un punto importante que debemos haber preparado a conciencia porque, ¿cuánto tiempo tiene que invertir para aprender a usar tu producto? Es una de las preguntas que el cliente responderá en este momento. Debemos tener claro que el cliente percibe un valor que le aporta tu producto y para calcularlo tendrá en cuenta todas las variables.

    6. El tiempo de uso
    Nuestro cliente ya tiene el producto, ya le hemos enseñado a usarlo, ahora solo queda que lo use durante un tiempo. En esta fase el feedback es importante. Tu deber como proveedor del producto es atender a las peticiones y errores que vayan surgiendo y el deber del cliente es hacernos llegar dichas peticiones o fallos. Para entender los conceptos de los dos próximos puntos hay que entender el siguiente gráfico que muestra cómo funciona el interés del cliente.

    Gráfico
    Al principio el interés es muy alto y, cuanto más usa el producto, cuanto más avanza el tiempo, el interés desciende. Por ejemplo, si compras un iPad, al principio estarás emocionado con él, pero poco a poco empezarás a preguntarte si compraste el mejor iPad, si no habría sido mejor esperar a que salieran los nuevos modelos, etc… Es decir, cuanto más usas un producto, más normal lo ves y menos expectativas pones en él.

    El gráfico se ha basado en un gráfico dibujado por Roberto Canales Mora y ha sido creado en un Generador de gráficos online

    7. Anima al cliente
    Como ya explicamos en el anterior punto, el cliente empezará a dudar sobre si lo que ha comprado es lo mejor. Es en este momento cuando debes mostrarle datos al cliente de tu producto y los beneficios que le está reportando. Un informe donde se le muestre claramente que, después de haber comprado tu producto, por ejemplo, su impacto sobre las redes sociales ha aumentado un 70%. Al igual que yo he remarcado en negrita el dato anterior, debes remarcar y dejar claros los mejores beneficios que le ha reportado tu producto. Esto animará al cliente a usar más tu producto porque ha visto el valor de su compra.

    8. Mejora tu producto
    Como ya expliqué, debe haber un feedback bidireccional con el cliente. En este feedback recibiremos peticiones de mejora y errores que solucionar. Pues bien, hazlo. Mejora tu producto, innova, cambia lo que sea necesario para la comodidad del cliente. Un producto debe mejorar día a día o quedará atrás y desaparecerá con el tiempo.

    Conclusión
    Han quedado claros varios aspectos a lo largo del artículo pero, ¿qué conclusión hay que sacar de todo ello? Como nos comentaba Roberto, nadie tiene el éxito asegurado, pero si que puede construir una base sólida que evite que su producto fracase antes de haber empezado. Hay que entender que la elaboración del producto tan solo es una parte del total. Se debe incluir un modelo de negocio entre nuestras tareas, labor que debe cumplir y entender el equipo responsable del producto en su totalidad.

    Para acabar citaré una frase célebre:
    “No hay secretos para el éxito. Este se alcanza preparándose, trabajando arduamente y aprendiendo del fracaso”. Colin Powell.

    Links de interés:
    Tips para sacar provecho de una reunión con clientes
    Consejos para una reunión efectiva

    Gestiona tus filtros con Spring usando DelegatingFilterProxy

    $
    0
    0

    Los filtros son uno de los pilares básicos de Java en aplicaciones Web, pero su configuración es muy primaria. En este tutorial te enseñamos cómo emplear Spring para enriquecer tus filtros.

    0. Índice de Contenidos

    1. Introducción

    Los mecanismos básicos que emplea Java para poder dar servicio en aplicaciones web están especificados en javax.servlet. Estos mecanismos son empleados por todos los frameworks utilizados hoy en día, aunque de una forma más o menos oculta para el desarrollador, dependiendo de la evolución y orientación del framework. Básicamente son 3:

    • Servlets: que contienen la lógica que se aplica al recibir una petición HTTP.
    • Listeners: que son unas clases que el contenedor de servlet invoca cuando sucenden los eventos de arranque y parada del contenedor.
    • Filtros: que se aplican a peticiones HTTP antes o después de que hayan sido atendidas por los servlets, sin que estos sean conscientes de su aplicacion (AOP o programación orientada a aspectos)

    Estos elementos son configurados dentro del fichero WEB-INF/web.xml de descripción del contexto de la aplicación según se especifica en el estandard de Java para las aplicaciones Web en los Web Archive (.war), y suponen los pilares básicos sobre los que se implementan los frameworks Web actuales, como pueden ser Spring MVC o Struts2 entre otros muchos.

    En este tutorial vamos a centrarnos en los filtros y en cómo poder aprovechar la potencia del framework Spring para su configuración, tanto de comportamiento como de dependencias.

    2. Entorno

    Para realizar este tutorial se ha empleado el siguiente entorno de desarrollo:

    • Hardware: Acer Aspire One 753 (2 Ghz Intel Celeron, 4GB DDR2)
    • Sistema Operativo: Ubuntu 15.10
    • Java 7, Maven 3, Tomcat 7, Eclipse Mars 2.

    El código fuente resultante puede ser descargado en este repositorio de GitHub:
    https://github.com/4lberto/simpleWebAppWithDelegatingFilterProxy

    3. Filtros

    Los filtros son clases de Java que implementan la interfaz javax.servlet.Filter, y cuya misión es interceptar las peticiones antes de que lleguen a los servlets y después, para realizar ciertas operaciones.

    00

    ¿Para qué se pueden utilizar los filtros? Para un buen número de tareas:

    • Control de Seguridad.
    • Log de acceso.
    • Compresión.
    • Preparación de datos para los servlets.
    • Encriptación.

    La implementacion de la interfaz javax.servlet.Filter tiene mucho que ver con su ciclo de vida. Tiene tres métodos:

    1. init: cuando se recibe la primera petición para el filtro, se llama a este método y se le pasan los parámetros de configuración y el contexto del servlet. Aquí es donde podemos cambiar el comportamiento del filtro.
    2. doFilter: cuando se reciben peticiones y ya se ha pasado por init, se invoca directamente a doFilter, que es el que contiene la lógica de lo que hace el filtro. Recibe por parámetro el request y el response, además de la cadena de filtros para pasar la cadena al siguiente filtro.
    3. destroy: cuando se decide “apagar” el filtro se llama a este método. Sucede en el cierre de la aplicación al hacer un shutdown por ejemplo.

    Así, que implementar un Filtro no será otra cosa que implementar estos métodos de la interfaz javax.servlet.Filter

    Una de las cosas más interesantes de los filtros es que los servlets no son conscientes de ellos: cuando una petición llega a un Servlet, éste no sabe si previamente esa petición ha sido interceptada por un filtro o no. Esto permite tener muy desacoplado el comportamiento y por tanto favorece la reutilización y el diseño es más claro.

    Otro aspecto importante es que los filtros son encadenables, es decir, si la configuración así lo dicta, después de aplicar un filtro, se puede aplicar otro, y así sucesivamente hasta que llega al servlet final o se cancela la petición.

    Vamos a ver un ejemplo de Filtro y cómo podemos manejarlo desde Spring con DelegatingFilterProxy.

    4. Filtro de Ejemplo

    Como hemos visto, los filtros se pueden utilizar para multitud de tareas. Para ilustrar con un ejemplo este tutorial vamos a hacer un filtro muy sencillo de “autenticación” basada en token. Quizá ni siquiera sea eso en realidad :). Te explico el comportamiento:

    • Comprueba a ver si la petición (request) contiene un parámetro llamado “token” con un valor determinado:
      • Si no tiene el token o lo tiene pero no coincide, cancela la petición antes de llegar al servlet y muestra un mensaje de no autorizado al usuario.
      • Si contiene el token y coincide con el configurado, pasa la ejecución al token.

    Es bastante sencillo, pero a la vez ilustrativo de la utilidad de los filtros y la independencia de los Servlet. El Servlet no sabe nada de la existencia del filtro. Sólo sabe que no tiene que ocuparse de autenticar nada. De hecho, si no usásemos filtros, esta comprobación de token la tendríamos que haber incluído en cada Servlet, lo cual haría el código más engorroso y pondría en duda el principio de single responsability de los patrones SOLID.

    ¿Cómo implementamos un filtro así? Pues tendremos que:

    • Crear una clase que implemente javax.servlet.Filter-
    • Que en su método init reciba, en la configuración, un parámetro con el valor del token que esperamos.
    • Que en el doFilter compruebe a ver si en el request está el parámetro y lo compare con el valor configurado.
    • Que si es todo correcto, que continúe la cadena de llamadas al siguiente filtro/servlet.
    • Que si hay fallo, cancele la request y le informe al usuario a través de un mensaje en el response.

    Más o menos esto es lo que hace la siguiente implementación:

    package com.adictos.filters;
    
    import java.io.IOException;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    
    public class SimpleAuthenticationFilter implements Filter {
    
    	private static final String TOKEN_PARAMETER_KEY = "token";
    	private static final String TOKEN_CONFIG_KEY = "expectedToken";
    	private String expectedToken;
    
    	public void destroy() {
    	}
    
    	public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
    			throws IOException, ServletException {
    
    		final String tokenFromRequest = request.getParameter(TOKEN_PARAMETER_KEY);
    
    		if (expectedToken.equalsIgnoreCase(tokenFromRequest)) {
    			chain.doFilter(request, response);
    		} else {
    			response.reset();
    			response.getWriter().println("Error en la autentiación: token no presente");
    			response.getWriter().flush();
    			return;
    		}
    	}
    
    	public void init(final FilterConfig config) throws ServletException {
    		this.expectedToken = config.getInitParameter(TOKEN_CONFIG_KEY);
    		System.out.println("Filtro " + config.getFilterName() + " configurado con token:" + this.expectedToken);
    	}
    
    }

    Si nos fijamos en el método init, el token se configura a través de los parámetros que se reciben en el FilterConfig, que se establece en el web.xml al declarar el filtro:

    <filter>
    	<filter-name>SimpleAuthenticationFilter</filter-name>
    	<filter-class>com.adictos.filters.SimpleAuthenticationFilter</filter-class>
    	<init-param>
    		<param-name>expectedToken</param-name>
    		<param-value>123456</param-value>
    	</init-param>
    </filter>

    Le estamos indicando que el token que usamos para validar es “123456”. Vale, es bastante burdo, pero únicamente es un ejemplo de qué puede hacer un filtro :).

    Adicionalmente podemos (y debemos) indicar que el filtro se aplicará cuando se hagan peticiones a un servlet:

    <filter-mapping>
    	<filter-name>SimpleAuthenticationFilter</filter-name>
    	<servlet-name>servlet1</servlet-name>
    </filter-mapping>

    Una vez tenemos todo configurado (incluyendo el servlet), podemos ver el resultado corriendo la aplicación Web en un contenedor de servlets. En este caso usaré Tomcat 7 y lo lanzaré desde Eclipse.

    Si accedemos a la URL: http://localhost:8080/simpleWebAppWithFilter/servlet1 nos dará este resultado por faltar el token:

    2

    Si incluimos en la URL el token en un parámetro obtendremos el resultado deseado: http://localhost:8080/simpleWebAppWithFilter/servlet1?token=123456

    1

    5. DelegatingFilterProxy de Spring

    Si tienes algo de experiencia con Spring, o al menos has escuchado algo de sus proyectos, sabrás que cuentan con soluciones prácticamente para cualquier casuística que nos podamos encontrar en el desarrollo de una aplicación un poco seria. Y los filtros no son menos.

    La declaración del filtro en el web.xml es el mecanismo estándard de configuración del filtro, pero desde luego que no permite tantas posiblidades como podríamos desear: ¿Por qué no poder configurar el filtro como cualquier otro bean de Spring?

    Para ello está el DelegatingFilterProxy, que permite traspasar la gestión de los filtros a Spring, gozando así de la potencia de este framework.

    DelegatingFilterProxy no es más que un filtro que provee Spring y que le permite hacer de nexo de unión entre el clásico web.xml y la configuración de contexto de Spring en donde podremos declarar los filtros como beans cualquiera. Cuando DelegatingFilterProxy recibe una petición (init, doFilter,destroy), la traspasa al filtro relacionado en Spring directamente.

    ¿Qué ventaja tiene? Una muy grande: que el filtro destino está en el contexto de Spring y por tanto puede disfrutar de todas las ventajas, como el control y la configuración de dependencias, mucho más potente que la que se puede hacer en el web.xml.

    Si alguna vez has trabajado por ejemplo con Spring Security te sonará seguro DelegatinFilterProxy, porque lo utiliza para crear el filtro de control de acceso. Aquí te lo explica mi compañero David Gómez. El código del filtro de Spring Security en el web.xml es el siguiente:

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>
            org.springframework.web.filter.DelegatingFilterProxy
        </filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    Como puedes ver, DelegatingFilterProxy implementa la interfaz Filter (entre otras más específicas de Spring), ya que es un filtro como otro cualquiera, sólo que se basa en el nombre que le hemos dado para buscar un bean con ese nombre que será un filtro declarado en Spring. De este modo, en la configuración de Spring, al emplear Spring Security, hay un bean llamado springSecurityFilterChain que es al que al final pasa el control del DelegatingFilterProxy.

    Así, el contenedor de Servlets (Tomcat en nuestro ejemplo), se comunicará con el el DelegatingFilterProxy, que se ocupará de traspasar estas llamadas al filtro que está en el contexto de Spring. Sería algo así:

    4

    6. Llamando a nuestro Filtro a través de DelegatingFilterProxy de Spring

    Ya sabemos más o menos la teoría de DelegatingFilterProxy:

    • Se introduce en el web.xml y se le da un nombre similar al que le daremos al bean de Spring de nuestro filtro original en el applicationContext.xml.
    • Tenemos que declarar nuestro filtro como un bean de Spring.
    • Las llamadas que reciba Delegatin Filter Proxy pasarán a nuestro filtro.

    Vamos a comenzar por el segundo paso, que condiciona al primero: configurar en Spring nuestro filtro como un bean.

    Originalmente ya tenía incluido Spring en el proyecto Web. Es muy sencillo: se introduce el “listener” en el fichero web.xml (otro elemento de la interfaz java.servlet que se encarga de activarse cuando se arranca y para el contendor de servlets). Este listener se ocupa de buscar un fichero de configuración XML que le indicamos como parámetro de contexto en el web.xml también (aunque ahora es común usar anotaciones y no XML) y así configurar el contexto de Spring con esa información.

    <context-param>
    	<param-name>contextConfigLocation</param-name>
    	<param-value>classpath:application-context.xml</param-value>
    </context-param>
    
    <listener>
    	<listener-class>
    		org.springframework.web.context.ContextLoaderListener
    	</listener-class>
    </listener>

    Con esto logramos que el contexto de spring se lea del fichero application-context.xml que he ubicado en /src/main/resources en mi proyecto Maven. El contenido es el siguiente:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans
    	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    	http://www.springframework.org/schema/context
    	http://www.springframework.org/schema/context/spring-context-2.5.xsd">
    
    	<context:component-scan base-package="com.adictos" />
    	<bean id="timingService" class="com.adictos.services.TimingService"></bean>
    
    	<bean id="simpleAuthenticationFilter" class="com.adictos.filters.SimpleAuthenticationFilter"></bean>
    
    </beans>

    Puedes ver en el último lugar la declaración del simpleAuthenticationFilter, con la “s” en minúscula para diferenciarla de la clase, ya que es un bean de Spring.

    Por tanto ya tenemos nuestro filtro, que cumple con la interfaz Filter en el contexto de Spring. Sólo queda engancharlo con la aplicación a través del DelegatinFilterProxy.

    Simplemente se debe sustituir en el web.xml el filtro anterior por el nuevo de Spring llamado org.springframework.web.filter.DelegatingFilterProxy. Pero ¡atención! Se debe dar un “filter-name” adecuado, que no es otro que el nombre del bean con el que hemos designado al bean del filtro en Spring: simpleAuthenticationFilter.

    Además continuamos configurando el parámetro inicial “expectedToken” con el valor 123456 en el web.xml.

    Pero ahora hay un nuevo parámetro inicial, que no es de nuesto filtro, sino del filtro DelegatinfFilterProxy. Tiene mucho que ver con el ciclo de vida de un filtro que hemos visto antes en la introducción.

    Si no indicamos ningún parámetro al filtro DelegatingFilterProxy, entonces Spring se ocupará de manejar el ciclo de vida del filtro al cual envía las peticones (simpleAuthenticationFilter). ¿Qué quiere decir esto? Que Spring no llama al init ni destroy en ningún momento, así que no se pueden configurar a través de init como lo estábamos haciendo hasta ahora. Hay dos soluciones:

    • 1. Hacer que sea el contenedor de servlets el que llame a init y destroy cuando proceda, indicándolo con el parámetro targetFilterLifecycle.
    • 2. Configurar el filtro a través de las inyección de dependencias de Spring y no a través de init.

    De momento vamos a seguir la opción 1, que es indicarle con el parámetro targetFilterLifecycle a Spring que el contenedor de Servlets (Tomcat en este caso) va a llamar a init y destroy, y que por tanto, hará caso a los parámetros de configuración que le indicamos en el web.xml

    Así pues el web.xml, en lo que afecta al filtro quedará de este modo:

    <filter>
    	<filter-name>simpleAuthenticationFilter</filter-name>
    	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
                <param-name>targetFilterLifecycle</param-name>
                <param-value>true</param-value>
        </init-param>
    	<init-param>
    		<param-name>expectedToken</param-name>
    		<param-value>123456</param-value>
    	</init-param>
    </filter>
    
    <filter-mapping>
    	<filter-name>simpleAuthenticationFilter</filter-name>
    	<servlet-name>servlet1</servlet-name>
    </filter-mapping>

    Si probamos la aplicación funciona correctamente

    ¿Y si queremos aprovechar la potencia de Spring para hacer una configuración más avanzada de nuestro filtro, olvidándonos del web.xml? Vamos a verlo a continuación:

    7. Alterando nuestro Filtro para configurarlo con Spring

    El valor 123456 que hemos establecido para el “expectedToken” en el web.xml se configura en el filtro a través del método init, y es Tomcat el que le pasa un objeto de configuración con esta información para que sea leída en el filtro. En código:

    public void init(final FilterConfig config) throws ServletException {
    	this.expectedToken = config.getInitParameter(TOKEN_CONFIG_KEY);
    }

    Efectivamente, en el FilterConfig están mapeados los valores del web.xml, además de otra información del contexto del Servlet que ahora no estamos utilizando.

    ¿Cómo cambiarlo por configuración de Spring?

    Se me ocurre que podemos crear un constructor a nuestra clase SimpleAuthenticationFilter donde se le pase por parámetro un String con el valor que le demos a expectedToken. Posteriormente en el fichero application-context.xml especificaremos este valor como constructor:

    public SimpleAuthenticationFilter(String expectedToken) {
    	super();
    	this.expectedToken = expectedToken;
    }

    Y en el fichero Spring indicamos por constructor este valor:

    <bean id="simpleAuthenticationFilter" class="com.adictos.filters.SimpleAuthenticationFilter">
    	<constructor-arg value="123456" type="java.lang.String"></constructor-arg>
    </bean>

    Así que ahora podemos quitar el parámetro de inicio del web.xml y como ya no tendríamos que inyectar nada, también el targetFilterLifecycle indica que el contenedor de Servlets tiene que llamar a init porque no nos hace falta (incluso podemos quitar -mejor vaciar porque lo requiere la interfaz- el método init).

    <filter>
    	<filter-name>simpleAuthenticationFilter</filter-name>
    	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    
    <filter-mapping>
    	<filter-name>simpleAuthenticationFilter</filter-name>
    	<servlet-name>servlet1</servlet-name>
    </filter-mapping>

    El funcionamiento es exactamente igual que antes, solo que ahora tenemos la flexibilidad de Spring para configurar mínimamente nuestro filtro.

    Y como ya hemos abierto la “caja de Pandora” de Spring, vamos a inyectar en nuestro filtro un servicio que nos da la hora actual formateada. Nuevamente es algo muy sencillo, pero es para ilustrar con algún ejemplo.

    El servicio es el siguiente:

    public class TimingService {
    	public String getCurrentTime() {
    		final SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yy hh:mm:ss");
    		return sdf.format(new Date());
    	}
    }

    Lo podríamos haber anotado, pero si te has fijado bien antes, ya lo teníamos configurado en el application-context.xml de Spring:

    <bean id="timingService" class="com.adictos.services.TimingService"></bean>

    Alteramos nuestro código del filtro para introducir un miembro nuevo que albergue la referencia al nuevo TimingService. Esta vez haremos uso de la anotación @Autowired en nuestro filtro:

    @Autowired
    private TimingService timingService;

    Y lo usamos para dar la fecha y hora en el mensaje si hay fallo en el filtro:

    response.getWriter().println("Error en la autentiación: token no presente. Fecha y hora:" + timingService.getCurrentTime());

    Y si probamos la aplicación, efectivamente tenemos el resultado esperado cuando no indicamos el token 123456 como parámetro. Ahora nos muestra la hora:

    3

    El restultado final lo puedes descargar en: https://github.com/4lberto/simpleWebAppWithDelegatingFilterProxy

    8. Conclusiones

    En este tutorial hemos visto el mecanismo que tiene Spring para poder integrar los filtros de javax.servlet dentro de su contexto y así poder acceder a todas las ventajas de este conocido framework de aplicaciones empresariales.

    Flyway: cómo integrar en Maven

    $
    0
    0
    Cómo integrar Flyway en Maven para realizar una migración/carga inicial de base de datos.

    Índice de contenidos

    1. Introducción

    Cuando tenemos que replicar un entorno en un equipo, o estamos realizando la configuración inicial de nuestra aplicación en un servidor en el que estamos desplegándola, tenemos que realizar una minuciosa labor de configuración de la base de datos. Mediante Flyway podemos automatizar todos estos pasos en un script SQL para homogeneizar la configuración y evitar fallos por despistes.

    2. Entorno

    El tutorial está escrito usando el siguiente entorno:
    • Hardware: Portátil MacBook Pro Retina 15″ (2.2 Ghz Intel Core I7, 16GB DDR3).
    • Sistema Operativo: Mac OS Yosemite 10.10
    • Entorno de desarrollo: IDEA IntelliJ 16.01 EAP
    • Maven 3.3.9
    • Flyway 4.0
    • PostgreSQL 9.5.1

    3. Creación del proyecto con Maven

    Primero crearemos un proyecto Maven para incluir el plugin de Flyway. También necesitaremos especificar las dependencias del mismo, así como la configuración de acceso a base de datos. Para especificar la configuración tenemos distintas opciones cuyo orden es el siguiente:
    1. A través de propiedades del sistema
    2. Utilizando un fichero externo
    3. Mediante propiedades Maven
    4. Directamente a nivel de plugin
    Estas se pueden utilizar de manera simultánea, ya que al encontrar una de mayor nivel, se ignorarán las siguientes. En nuestro caso utilizaremos un fichero externo de configuración. Se puede ampliar la información al respecto en la documentación oficial para Maven y su configuración en la goal flyway:migrate. Como opción adicional, se nos permite especificar en el fichero settings.xml de Maven la configuración de credenciales de manera independiente del proyecto.

    3.1 Configuración del plugin

    Deberemos tener creada la base de datos a la que nos vayamos a conectar para incluir la url de conexión a la misma, y en nuestro caso, la referencia al usuario que vamos a utilizar para conectarnos.
    <build>
            <plugins>
                <plugin>
                    <groupId>org.flywaydb</groupId>
                    <artifactId>flyway-maven-plugin</artifactId>
                    <version>4.0</version>
                    <configuration>
                        <url>jdbc:postgresql://localhost:5432/TutorialFlyway</url>
                        <user>flyway</user>
                    </configuration>
                    <dependencies>
                        <dependency>
                            <groupId>org.postgresql</groupId>
                            <artifactId>postgresql</artifactId>
                            <version>9.4.1208.jre7</version>
                        </dependency>
                    </dependencies>
                </plugin>
            </plugins>
        </build>
    El nombre del fichero properties que Flyway va a buscar por defecto es flyway.properties, y lo busca en el mismo directorio en que se ubica nuestro pom.xml, es decir, el directorio raíz. También se le puede indicar la ruta al mismo en la configuración del plugin. En este archivo especificaremos las credenciales para conectarnos a la base de datos, los esquemas que se van a ver afectados y el resto de propiedades que necesitemos. Como ejemplo tenemos:
    flyway.user=flyway
    flyway.password=flyway

    3.2 Creación del script SQL

    Flyway utiliza un prefijo para determinar el orden en el que se ejecutarán los scripts, por lo que podremos sin problema crear los elementos en el orden requerido previamente a rellenar los datos. La estructura de nombres está prefijada de acuerdo al siguiente patrón por defecto (configurable):
    • prefijo:
      • V: script con versionado
      • R: script repetible, independiente de la versión
    • versión: numérico, separado por guión bajo o puntos, con tantos niveles como se requieran
    • separador: por defecto configurado como dos guiones bajos (__)
    • descripción: podremos utilizar guiones bajos o espacios para separar las palabras
    • extensión: por defecto, buscará la extensión .sql
    Flyway los colocará ordenados por versión y procederá a ejecutarlos secuencialmente. En nuestro caso, creamos dos ficheros, V1__Create_Table.sql y V1.1__Insert_Records.sql bajo la carpeta /src/main/resources/db/migration.
    CREATE TABLE "flyway_test_table" (
    ID SERIAL PRIMARY KEY,
    FIRSTNAME varchar(255) default NULL,
    LASTNAME varchar(255) default NULL,
    MAIL varchar(255) default NULL
    );
    Para la creación de los datos he recurrido a una herramienta de generación automática para obtener 100 registros con datos: Generate Data
    Flyway utiliza un mecanismo de escaneado recursivo, incluyendo subcarpetas, para localizar los scripts a cargar a partir de la localización que le indiquemos por parámetro al plugin. Si no le indicamos ninguna, por defecto utilizará filesystem:src/main/resources/db/migration.

    4. Ejecución

    Ejecutar Flyway es tan sencillo como: mvn flyway:<goal>, donde <goal> en nuestro caso va a ser migrate.
    Yairs-MacBook-Pro:TutorialFlyway ysegura$ mvn flyway:migrate
    [INFO] Scanning for projects...
    [INFO]
    [INFO] ------------------------------------------------------------------------
    [INFO] Building TutorialFlyway 1.0-SNAPSHOT
    [INFO] ------------------------------------------------------------------------
    [INFO]
    [INFO] --- flyway-maven-plugin:4.0:migrate (default-cli) @ TutorialFlyway ---
    [INFO] Flyway 4.0 by Boxfuse
    [INFO] Database: jdbc:postgresql://localhost:5432/TutorialFlyway (PostgreSQL 9.5)
    [INFO] Successfully validated 2 migrations (execution time 00:00.007s)
    [INFO] Creating schema "public" ...
    [INFO] Creating Metadata table: "public"."schema_version"
    [INFO] Current version of schema "public": 0
    [INFO] Migrating schema "public" to version 1 - Create Table
    [INFO] Migrating schema "public" to version 1.1 - Insert Records
    [INFO] Successfully applied 2 migrations to schema "public" (execution time 00:00.082s).
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 0.555 s
    [INFO] Finished at: 2016-04-01T09:55:51+02:00
    [INFO] Final Memory: 10M/309M
    [INFO] ------------------------------------------------------------------------
    Yairs-MacBook-Pro:TutorialFlyway ysegura$
    Ya tenemos la base de datos creada, los registros insertados, y todas las operaciones indicadas en nuestros scripts finalizadas.

    5. Conclusiones

    Siempre que en nuestros proyectos utilicemos bases de datos, necesitaremos utilizar un script para crear nuestra base de datos, insertar datos iniciales, etc. y deberíamos pensar en incorporar Flyway, ya que es realmente sencillo, y nos permite ejecutar de manera estructurada esos scripts. Además, todos aquellos que colaboren en el proyecto pueden replicar la configuración con un mínimo coste, y sin que suponga trabajo adicional… The Concept Of One: Do It Once, Do It Right, And Use It Everywhere

    6. Referencias

    Monitorización de logs con Elasticsearch, Logstash y Kibana

    $
    0
    0

    Este es el primer tutorial de una serie de cuatro que explicará cómo instalar, configurar y explotar la plataforma Elasticsearch, Logstash y Kibana, un sistema centralizado de recogida y análisis de logs de aplicaciones.
    Por en medio se usarán las herramientas Vagrant y Ansible para la creación y provisionamiento de las máquinas virtuales.

    Índice de contenidos


    1. Introducción

    Una de las cuestiones a resolver cuando nuestra aplicación está lista para desplegar en producción es cómo monitorizar su funcionamiento para poder actuar de manera rápida y efectiva ante cualquier incidencia.

    Hay muchas soluciones en el mercado para cubrir esta necesidad, tanto de pago como libres, pero una de las más populares es la plataforma Elasticsearch, Logstash y Kibana, conocida como ELK.

    Lo que distingue a ELK sobre otras alternativas es principalmente:

    • Potencia: mucha funcionalidad que se puede aprovechar fácilmente.
      Por ejemplo, Kibana no está limitado a gráficas de series temporales y puede manejar cualquier conjunto de datos.
    • Escalabilidad: los clusters de Elasticsearch pueden manejar terabytes de datos sin ningún problema.
    • Flexibilidad: configuración muy abierta y flexible que se adapta a cualquier necesidad y entorno.
    • Apertura: plugins y APIs para extender casi cualquier aspecto de la plataforma.
    • Y por si todo esto no fuera suficiente, licencia comercial con soporte, hosting y productos relacionados.

    Para poner en marcha una plataforma de este tipo hacen falta los siguientes elementos:

    1. Un agente de recogida de logs en nuestras aplicaciones.
      De esto se encarga el demonio Logstash, aunque como se verá más adelante, hay otras opciones.
    2. Una base de datos donde almacenar, indexar y buscar los eventos de log de las aplicaciones.
      Para esto se utiliza Elasticsearch.
    3. Una aplicación frontend donde los usuarios puedan consultar los eventos más interesantes y estar informados de cualquier incidencia. Esta es la función de Kibana.
    Otros elementos opcionales pero no menos útiles son:
    1. Un sistema de monitorización y control de la propia plataforma ELK.
      Aunque hay soluciones más completas, nos limitaremos a monitorizar Elasticsearch con el plugin Kopf.
    2. Una herramienta de alertas automáticas para informar de problemas graves por múltiples canales.
      Por el momento no hay ninguna alternativa libre para esta funcionalidad, aunque Elastic ofrece el plugin de pago Watcher.
    3. Sistema de control de accesos con autenticación y autorización basada en roles y conjuntos de datos.
      Elastic vende licencias de su propio producto Shield aunque existen plugins libres alternativos y la opción básica de un proxy inverso Apache o Nginx.

    Montar este entorno es relativamente complejo. Además idealmente debe poder extenderse fácilmente a múltiples aplicaciones en un CPD y escalar a un cluster con múltiples nodos conforme vayan aumentando las necesidades de sistemas.
    Por esta razón, todas las tareas de instalación y configuración serán automatizadas con Ansible sobre dos máquinas virtuales VirtualBox generadas con Vagrant.

    Se suponen unos conocimientos mínimos de Ansible y Vagrant que puedes adquirir en este tutorial.


    2. Entorno

    El tutorial está escrito usando el siguiente entorno:

    • Hardware anfitrión: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core i7, 16GB DDR3)
    • Hardware huesped: dos servidores con 2,5 GB RAM y 2 CPUs
    • Sistema operativo anfitrión: Mac OS Yosemite 10.10
    • Sistema operativo huesped: Ubuntu Server 14.04.4 LTS 64 bits
    • Elasticsearch 2.2.1
    • Logstash 2.2.2
    • Kibana 4.4.2
    • Ansible 2.0.1
    • Vagrant 1.8.1
    • VirtualBox 5.0.14
    • Maven 3.3.9

    3. Objetivo del tutorial

    Nuestro punto de partida es una aplicación Spring Boot llamada elk-example-app que está lista para ser desplegada en nuestro servidor de “producción” appsserver.
    Para el caso que nos ocupa esta aplicación simplemente genera mensajes de log durante un tiempo de manera pseudo-aleatoria.

    El objetivo del tutorial es añadir a nuestro “CPD de producción” un servidor llamado statsserver en el que se registrarán los eventos de log de la aplicación para luego poder consultarlos y analizarlos.

    En el siguiente diagrama se puede ver cómo va a quedar el entorno al final de este tutorial:
    Entorno final con ELK

    Las máquinas virtuales appsserver y statssserver estarán accesibles por SSH en los puertos 2223 y 2224 respectivamente.
    La interfaz HTTP (REST y de administración) de Elasticsearch estará disponible en el puerto 9200 del servidor statsserver.
    La interfaz web de Kibana podrá utilizarse desde el puerto 5601 en statsserver.

    La aplicación escribe los mensajes de log en el fichero /var/log/tutorial/elk-example.log, de donde los lee Logstash para enviarlos a Elasticsearch en forma de registros JSON sobre HTTP REST.

    Por su parte, Kibana accederá a Elasticsearch para ejecutar las consultas solicitadas por el usuario.
    El propio Kibana guarda la configuración de búsquedas, visualizaciones y paneles en Elasticsearch.


    4. Creación de las máquinas virtuales


    4.1. Requisitos

    El equipo anfitrión se encarga de crear las máquinas virtuales y provisionarlas, así que en primer lugar es necesario instalar VirtualBox, Vagrant y Ansible.

    En el caso OSX se ha utilizado Homebrew tal y como se explica en este tutorial:

    Terminal
    $ brew cask install virtualbox
      $ brew cask install vagrant
      $ brew install ansible

    Por otro lado, el resto de materiales del tutorial se encuentran publicados en este repositorio de GitHub: https://github.com/dav-garcia/elk-tutorial.
    Repositorio Github

    Se pueden descargar como paquete ZIP o clonando el repositorio git con la URL que aparece en la caja de texto:

    Terminal
    $ git clone https://github.com/dav-garcia/elk-tutorial.git

    4.2. Generando las máquinas virtuales

    El fichero Vagrantfile del repositorio elk-tutorial define dos máquinas virtuales Ubuntu Server 14.04 de 64 bits.
    Ambas máquinas dispondrán de 2 CPUs y 2,5 GB de memoria RAM cada una, suficiente para este tutorial:

    Vagrantfile (fragmento)
    config.vm.provider "virtualbox" do |vb|
          vb.memory = "2560"
          vb.cpus = "2"
      end

    El resto del fichero son dos bloques de declaraciones que especifican para cada máquina virtual qué puertos se van a pasar a través de NAT y el provisionamiento de software con Ansible.

    El reenvío de puertos por NAT facilita la comunicación con las máquinas virtuales desde el anfitrión y entre ellas del siguiente modo:

    • Los puertos reenviados estarán disponibles en el localhost del anfitrión; por eso hay que asegurarse que no hay ningún servicio de la máquina real con esos mismos puertos (de ahí el uso de 2223 y 2224 para SSH).
    • En VirtualBox, una máquina virtual puede salir al anfitrión mediante la dirección 10.0.2.2 (lógicamente localhost accede a la propia máquina virtual).

    En cuanto a Ansible, todos los servidores utilizan el mismo playbook (stats.yml), así que para distinguir entre servidor de aplicaciones y servidor de estadísticas se utiliza el concepto de grupo de hosts.
    En concreto, el playbook define dos grupos:

    • stats es el modelo de los servidores de estadísticas y por tanto se le asignan los roles de Ansible java8, elasticsearch y kibana.
    • apps es el grupo de hosts que modela los servidores de aplicaciones, con lo que sus roles son java8, appdeploy y logstash.
    ansible/stats.yml
    ---
    
      - hosts: stats
        sudo: yes
        gather_facts: yes
        roles:
          - java8
          - elasticsearch
          - kibana
    
      - hosts: apps
        sudo: yes
        gather_facts: yes
        roles:
          - java8
          - logstash
          - appdeploy

    Por otro lado se ha definido un inventario con dos máquinas cuyos nombres son los que aparecen en el Vagrantfile, y cada una asignada a un grupo distinto: statsserver en el grupo stats y appsserver en apps.
    Esto está indicado en el fichero ansible/environments/tutorial/inventory:

    ansible/environments/tutorial/inventory
    [apps]
      appsserver
    
      [stats]
      statsserver

    Con todo esto en mente, para poner en marcha las máquinas virtuales, desde el directorio elk-tutorial donde se encuentra el Vagrantfile, ejecutaremos:

    Terminal
    $ vagrant up

    La primera vez que lo hagamos se crearán las máquinas virtuales y se provisionarán automáticamente, así que más vale armarse de paciencia.

    Para abrir una shell en una de las máquinas virtuales podemos ejecutar el siguiente comando, reemplazando nombre-servidor por statsserver o appsserver según deseemos:

    Terminal
    $ vagrant ssh nombre-servidor;

    Cuando terminemos de jugar con nuestras máquinas virutales y deseemos detenerlas, el comando a ejecutar será:

    Terminal
    $ vagrant halt

    Por último, para destruir las máquinas virtuales por completo debemos escribir el comando:

    Terminal
    $ vagrant destroy

    4.3. Personalizando las máquinas virtuales

    La manera correcta de cambiar la configuración de ELK es a través de las variables de los roles de Ansible.
    De esta manera podremos replicar y recrear la nueva configuración tantas veces como sea necesario.

    Las variables de roles tienen asignados valores por defecto en el fichero defaults/main.yml de cada uno de los roles de Ansible (es decir, appdeploy, elasticsearch, kibana y logstash).
    A modo de ejemplo, la configuración por defecto de Elasticsearch está en este fichero:

    ansible/roles/elasticsearch/defaults/main.yml
    ---
    
      # Define custom settings in a dictionary called "elasticsearch".
    
      _es_default:
        version: 2.2.0
        user: elasticsearch
        group: elasticsearch
        nofile: 32000
        cluster_name: ""
        node_name: "{{ ansible_fqdn }}"
        is_node_master: yes
        is_node_data: yes
        index_number_of_shards: 5
        index_number_of_replicas: 1
        bootstrap_mlockall: yes
        http_host: _local_
        network_host: _local_
        http_publish_host: ""
        http_publish_port: 0
        transport_publish_host: ""
        transport_publish_port: 0
    
        unicast_nodes: []
        minimum_master_nodes: 1
    
        java_opts: ""
        # Taken from rack-roles elasticsearch:
        #   Use 40% of memory for heap, ES will also use large amount of
        #   direct memory allocation, hopefully ending with a total around 50%
        #   of the whole available system memory.
        java_heap_size: "{{ (ansible_memtotal_mb * 0.4) | round | int }}m"
    
        path_conf: /etc/elasticsearch
        path_data: /var/lib/elasticsearch
        path_home: /usr/share/elasticsearch
        path_logs: /var/log/elasticsearch
        path_plugins: /usr/share/elasticsearch/plugins
        plugins:
          - { name: head, path: mobz/elasticsearch-head}
    
      # Combines default and custom settings.
      _es_combined: "{{ _es_default | combine(elasticsearch) }}"

    Para sobreescribir cualquier valor por defecto del diccionario _es_default se debe definir un diccionario elasticsearch en las variables del grupo de hosts stats (que es donde se despliega Elasticsearch).
    En este tutorial ese fichero es ansible/environments/tutorial/group_vars/stats y contiene lo siguiente:

    ansible/environments/tutorial/group_vars/stats (fragmento)
    # ElasticSearch
    
      elasticsearch:
        version: 2.2.0
        cluster_name: "tutorial-stats"
        index_number_of_shards: 1
        index_number_of_replicas: 1
        minimum_master_nodes: 1
        http_host: "0.0.0.0"
        network_host: "0.0.0.0"
        http_publish_host: "10.0.2.2"
        http_publish_port: "{{ es_http_port }}"
        transport_publish_host: "10.0.2.2"
        transport_publish_port: "{{ es_transport_port }}"
        unicast_nodes: "{{ es_unicast_nodes }}"
        plugins:
          - { name: "kopf", path: "lmenezes/elasticsearch-kopf/2.0" }

    Para aplicar los cambios realizados en estos ficheros, ejecutaremos el siguiente comando:

    Terminal
    $ vagrant provision

    5. Uso de la plataforma


    5.1. Lanzando la aplicación de ejemplo

    Los roles de Ansible se aseguran de que los servicios ELK estén activos y preparados para ser utilizados.
    Lo único que resta por hacer es ejecutar la aplicación de ejemplo desde la línea de comandos para que genere mensajes de log durante unos segundos, por ejemplo 10:
    Terminal logs diez segundos

    La redirección a /dev/null es para evitar que se vuelque todo el log por consola.

    El código fuente de esta aplicación está en el directorio elk-example-app del repositorio elk-tutorial.
    De todas formas, la cuestión de cómo configurar el logging en Spring Boot está desarrollada en este otro tutorial.

    Al compilar con Maven, se genera un nuevo jar en el subdirectorio elk-example-app/target que debe llevarse al directorio ansible para que Ansible lo despliegue la próxima vez que se fuerce un provisionamiento.
    Los comandos para realizar todos estos pasos son:

    Terminal
    $ cd elk-example-app
      $ mvn clean install
      $ cd ..
      $ cp elk-example-app/target/elk-example-app-1.0.0-SNAPSHOT.jar ansible
      $ vagrant provision appsserver

    5.2. Comprobando los resultados

    Ahora vamos a ver los resultados de esta primera ejecución suponiendo que todo ha ido bien.
    Más adelante explicaré cómo diagnosticar problemas en cualquiera de los puntos de la cadena.

    En primer lugar, abrir una pestaña del navegador y entrar a http://localhost:9200/_plugin/kopf.
    Este es el cuadro de mandos del plugin Kopf para monitorizar y controlar el cluster de Elasticsearch.
    Hacer clic en el check * special (1) para ver también los índices “especiales”:
    cuadro de mandos del Kopf

    Aunque ahora no es el momento de entrar en detalle, lo que se muestra aquí es una tabla donde:

    • Cada fila es un nodo del cluster, más una fila especial para shards que no están asignadas a ningún nodo.
    • Cada columna es un índice de Elasticsearch.
      Un índice es una partición de toda la base de datos, un concepto similar a las particiones en bases de datos relacionales clásicas.
    • Cada celda indica las shards de un índice en un nodo.
      Una shard es una instancia de Lucene dentro de Elasticsearch, es decir, es un motor de búsqueda autocontenido con sus datos, metadatos e índices.
      Las shards pueden ser primarias o réplicas, es decir, las que se consideran “fuente” de los datos y las que se consideran copias.

    En segundo lugar, abrir otra pestaña en el navegador para entrar a Kibana en http://localhost:5601/.
    Tras unos segundos de carga, se mostrará un formulario para configurar los índices sobre los que busca Kibana:
    Configuración índices Kibana

    Salvo casos excepcionales, se pueden aceptar los valores por defecto y pulsar el botón Create, lo que nos llevará a la pantalla de personalización de los campos de búsqueda: Personalización campos búsqueda Kibana

    Aquí tampoco es necesario cambiar nada porque Logstash ya envía los mensajes de log procesados y estructurados y Elasticsearch es capaz de inferir el tipo de los campos de tipos primitivos.
    Estos ajustes también son útiles para personalizar el formato de visualización de campos en Kibana, por ejemplo para limitar el número de decimales en los números en coma flotante.

    Pulsar el enlace Discover para continuar.
    Ajustes Kibana

    Tener en cuenta que Kibana no muestra todos los datos desde el principio de los tiempos, sino una ventana que por defecto está fijada a los últimos 15 minutos.
    Para cambiar la ventana temporal, pulsar el control Last 15 minutes y elegir el intervalo de tiempo que más convenga.

    Por descontado, se puede abrir cualquier evento para ver sus campos, añadir un valor de campo a un filtro, añadir o quitar una columna del listado, etc:
    Campos de evento

    También se puede realizar una búsqueda introduciendo un filtro en la caja de texto principal, por ejemplo para ver solamente los mensajes de nivel WARN:
    Nivel warn kibana


    5.3. Siguiendo el proceso

    Para conocer mejor el funcionamiento de la plataforma ELK, vamos a seguir todos los pasos del proceso de transmisión y transformación de un mensaje de log.
    Los detalles concretos de configuración de cada componente se explicarán en otros tutoriales de la serie.

    El evento de log elegido ha quedado registrado en el fichero /var/log/tutorial/elk-example.log de la máquina virtual appsserver como un warning de una excepción controlada por la aplicación:

    /var/log/tutorial/elk-example.log (fragmento)
    2016-03-29 15:18:48.328  WARN 4385 --- [main] c.a.e.batch.item.StaticItemReader        : Ignoring exception...
    
      java.lang.Exception: This is a random test exception because 0.012084067 < 0.10!
      	at com.autentia.elkexample.batch.item.StaticItemReader.read(StaticItemReader.java:59)
      	at com.autentia.elkexample.batch.item.StaticItemReader.read(StaticItemReader.java:15)
      	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        ... líneas omitidas por brevedad ...
      	at java.lang.reflect.Method.invoke(Method.java:498)
      	at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:54)
      	at java.lang.Thread.run(Thread.java:745)

    Logstash consiste en un pipeline con tres fases: entradas, filtros y salidas.
    En cada fase se pueden definir todos los componentes que se deseen, de modo que Logstash lee líneas de texto de todas las entradas, las pasa por los filtros de manera secuencial y envía los resultados a las salidas.

    En este tutorial Logstash se configura en el fichero ansible/environments/tutorial/group_vars/apps.
    Primero se define un canal de entrada que extrae las líneas escritas en todos los ficheros de log del directorio /var/log/tutorial.
    A continuación las pasa por los filtros grok, date y mutate.
    Y por último envía el resultado a la base de datos Elasticsearch.

    En el ejemplo que estamos siguiendo, el filtro grok extrae campos del evento de log mediante una expresión regular:

    • date = 2016-03-29 15:18:48.328
    • level = WARN
    • pid = 4385
    • Y así sucesivamente con cada elemento.
    El filtro date fija el formato de la fecha y el campo que debe utilizarse para ubicar el evento en el tiempo.
    Por último, los filtros mutate realizan un poco de limpieza y extraen el campo appid a partir del nombre del fichero de log (elk-example en este caso).

    Elasticsearch recibe un objeto JSON con todos los campos del evento de log, pero eso no es todo.
    Logstash también debe indicar en qué indice se guardará el mensaje (por meses según el patrón logstash-%{+YYYY.MM}) y cuál es el mapping al que se ajustan dichos objetos, es decir, la plantilla que determina la estructura de los objetos.

    En el extremo final de la cadena, Kibana viene configurado para extraer datos de todos los índices que cumplan el patrón glob logstash-*.
    Por ejemplo podemos buscar el evento por fecha introduciendo la búsqueda exacta: timestamp.raw:”2016-03-29 15:18:48.328″
    búsqueda evento por fecha kibana


    6. Conclusiones

    Esta introducción a la plataforma ELK servirá de base para profundizar en cada uno de sus elementos en los tres siguientes tutoriales de la serie.
    También espero que haya servido para mostrar las posibilidades y potencial como mecanismo para el registro de datos de carácter general (no sólo eventos temporales) y su posterior consulta directa o en forma de tablas y gráficos de agregados.


    7. Referencias

    Extracción de logs con Logstash

    $
    0
    0

    Después de la visión general de la plataforma ELK para extracción y análisis de logs de aplicaciones, aquí podrás conocer en profundidad las posibilidades que ofrece Logstash para extraer y transformar mensajes de log.

    Índice de contenidos


    1. Introducción

    Aunque en esta serie de tutoriales Logstash se utiliza para recoger logs, procesarlos y almacenarlos en Elasticsearch, esta aplicación es una solución flexible de agregación y normalización de información de fuentes diversas.
    elk-img11


    2. Entorno

    Este tutorial utiliza el mismo entorno que la introducción a ELK.

    • Hardware anfitrión: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core i7, 16GB DDR3)
    • Hardware huesped: dos servidores con 2,5 GB RAM y 2 CPUs
    • Sistema operativo anfitrión: Mac OS Yosemite 10.10
    • Sistema operativo huesped: Ubuntu Server 14.04.4 LTS 64 bits
    • Elasticsearch 2.2.1
    • Logstash 2.2.2
    • Kibana 4.4.2
    • Ansible 2.0.1
    • Vagrant 1.8.1
    • VirtualBox 5.0.14
    • Maven 3.3.9

    3. Instalación y configuración de Logstash


    3.1. Con Ansible

    Este tutorial utiliza Ansible para instalar y configurar Logstash, así que para personalizarlo solamente hay que preocuparse de sobreescribir los valores por defecto del rol logstash.

    Esto se consigue añadiendo los nuevos valores a un diccionario logstash en el fichero de variables del grupo apps (que es donde se instala Logstash).
    En este tutorial en concreto se redefinen las claves inputs, filters y outputs tal que así:

    ansible/environments/tutorial/group_vars/apps (fragmento)
    ---
    
      # Logstash
    
      logstash:
        inputs: |-
          file {
            type => "java"
            path => ["/var/log/tutorial/*.log"]
            codec => multiline {
              pattern => "^%{TIMESTAMP_ISO8601}"
              negate => "true"
              what => "previous"
            }
            sincedb_path => ["/var/lib/logstash/sincedb"]
            start_position => "beginning"
          }
    
        filters: |-
          grok {
            match => {
              "message" =>
              "^%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:level}\s+%{NUMBER:pid}\s+---\s+\[\s*%{USERNAME:thread}\s*\]\s+%{JAVAFILE:class}\s*:\s*%{DATA:themessage}(?:\n+(?<stacktrace>(?:.|\r|\n)+))?$"
                     }
          }
          date {
            match => [ "timestamp" , "yyyy-MM-dd HH:mm:ss.SSS" ]
          }
          mutate {
            rename => {
              "themessage" => "message"
            }
            add_field => {
              "appid" => "%{[path]}"
            }
          }
          mutate {
            gsub => [ "appid", "^.+/([^.]+)\..+$", "\1" ]
          }
    
        outputs: |-
          elasticsearch {
            index => "logstash-%{+YYYY.MM}"
            # This is the host's ip address under VirtualBox.
            hosts => ["10.0.2.2"]
          }

    Aunque las mencionadas inputs, filters y outputs son las variables más importantes, se puede consultar todos los valores por defecto en el siguiente fichero:

    ansible/roles/logstash/defaults/main.yml (fragmento)
    ---
    
      # Define custom settings in a dictionary called "logstash".
    
      _logstash_default:
        version: "{{ _logstash_version }}"
        apt_repo: "deb http://packages.elasticsearch.org/logstash/{{ _logstash_version }}/debian stable main"
        repo_key: "http://packages.elasticsearch.org/GPG-KEY-elasticsearch"
        yum_repo_dest: "/etc/yum.repos.d/logstash.repo"
    
        inputs: ""
        filters: ""
        outputs: ""
    
        conf_dir: "/etc/logstash/conf.d/"
        home_dir: "/opt/logstash/"
    
        options: ""
        user: "logstash"
        group: "logstash"
    
      # Required to avoid recursive reference within dictionary.
      _logstash_version: "{{ logstash.version | default(2.2) }}"

    3.2. Manualmente

    En caso de no utilizar Ansible, lo más cómodo es instalar el paquete desde el repositorio oficial de Elastic teniendo en cuenta que requiere Java 7 o posterior.

    De esta manera, para configurar Logstash será necesario ir a estos lugares:

    • /etc/default/logstash para cambiar las opciones de arranque del demonio en Debian y derivados.
    • /etc/sysconfig/logstash para las opciones de arranque del demonio en RedHat y derivados.
    • /etc/logstash/conf.d/ para dejar aquí ficheros de definición de entradas, filtros y salidas.
    También habrá que asegurarse de que el sistema operativo arranca el servicio Logstash automáticamente.

    3.3. Entradas, filtros y salidas

    En cualquier caso, la configuración siempre consta de una sección input y otra output.
    También es seguro que se definirá la sección filter, que es uno de los puntos fuertes de Logstash y por lo que destaca sobre otras opciones más ligeras.

    El demonio de Logstash obtiene esta información de todos los ficheros que encuentre en el directorio /etc/logstash/conf.d.

    También es posible lanzar la herramienta desde línea de comandos indicándole la ruta del fichero de configuración.
    Esto lo vamos a comprobar con el siguiente ejemplo, que lee de la entrada estándar, geolocaliza cualquier dirección IP introducida y vuelca la información en la salida estándar:

    geoip_example
    input {
      	stdin {}
      }
      filter {
      	grok {
      		match => {"message" => "%{IP:ip}"}
      	}
      	geoip {
      		source => "ip"
      	}
      }
      output {
      	stdout {
      		codec => rubydebug
      	}
      }

    Para mayor comodidad, los ejemplos se ejecutarán desde la máquina virtual appsserver definida en el primer tutorial.
    Recordar que para entrar a la máquina appsserver es necesario ejecutar estos comandos desde el directorio elk-tutorial:

    Terminal
    $ vagrant up
      $ vagrant ssh appsserver

    Y a continuación introducir lo que aparece en los cuadros rojos:
    elk-img12

    Volviendo al fichero geoip_example, en la sección input se indican las fuentes de datos, en este caso stdin sin ningún parámetro de configuración.
    Todas las entradas disponibles están documentadas aquí.

    La sección filter se ejecuta secuencialmente, primero el filtro grok y luego geoip.

    El primero extrae campos estructurados (ip en este caso) de una cadena (message) mediante una regex con grupos de captura (clase predefinida %{IP} en este ejemplo).
    Existen un montón de clases predefinidas para muchos campos típicos, todas especificadas en este repositorio de GitHub.
    Grok Debugger también es muy útil a la hora de probar patrones Grok y consultar las clases.

    Esto nos lleva a otro concepto clave de Logstash: los mensajes no son texto plano, sino objetos con campos tipados.
    En la captura de pantalla anterior se ve claro que cada objeto tiene varios campos predefinidos: message, @version, @timestamp y host.
    Sobre esos, el filtro grok ha generado el campo ip y la ejecución de geoip ha generado el campo compuesto del mismo nombre.

    Por último, de la salida no hace falta explicar mucho: se trata de stdout con un codec rubydebug que hace un pretty-print de los campos de los eventos de log.
    Todos los plugins de salida están documentados aquí.


    3.4. Detección de errores

    Cuando se intenta ejecutar Logstash con una configuración no válida, la herramienta directamente no arranca.

    Si el error se produce en tiempo de ejecución, la aplicación seguirá en marcha y lo único que hará será dejar un registro en su log, que está en la ruta /var/log/logstash/logstash.log (parece un trabalenguas).
    Aquí se puede ver un fallo de conexión con Elasticsearch:

    Terminal
    {:timestamp=>"2016-04-01T14:49:10.940000+0000", :message=>"No route to host", :class=>"Manticore::SocketException", :level=>:error}

    Por defecto, Logstash sólo registra errores en su log, lo que dificulta depurar comportamientos incorrectos.
    Para cambiar esto, se debe arrancar Logstash con la opción –verbose (algo más de información) o –debug (mucha información).

    Si estamos usando Ansible, esto se consigue modificando la variable logstash.options del grupo apps, teniendo entonces el siguiente código:

    ansible/environments/tutorial/group_vars/apps (fragmento)
    ---
    
      # Logstash
    
      logstash:
        options: "--verbose"
    
        inputs: |-
          ...
    
        filters: |-
    
        outputs: |-
          ...

    Para aplicar el cambio no olvidar ejecutar el siguiente comando desde una terminal de la máquina anfitrión:

    Terminal
    $ vagrant provision appsserver

    4. Procesando logs de Spring Boot

    Con lo explicado hasta ahora, ha llegado el momento de diseccionar la configuración que se utilizó con la aplicación elk-example-app del anterior tutorial.


    4.1. Entrada

    El plugin file lee las líneas de los ficheros que encajan en el array de globs de la propiedad path, en este caso /var/log/tutorial/*log.

    En los logs de Java no es correcto asumir que cada línea del fichero es un evento de log; las trazas de excepciones en particular generan muchas líneas.
    Por esta razón se utiliza el codec multiline con una configuración que significa:
    “Todo lo que no (negate a true) empiece por una fecha (el pattern) debe ser concatenado con el evento de log anterior (what es previous)”

    Las dos últimas propiedades consiguen un procesamiento de ficheros más robusto, sin pérdida de líneas y sin líneas duplicadas.
    Para saber qué ficheros han sido procesados y hasta qué línea, file mantiene una lista de inodos con la posición dentro del fichero hasta donde ha procesado. Esta lista se conoce como sincedb.
    Por otro lado, start_position con beginning asegura que se leen todas las líneas desde el principio.


    4.2. Filtros

    El log de las aplicaciones Spring Boot es bastante rico y contiene mucha información interesante para extraer con el plugin grok utilizando clases predefinidas y un grupo nombrado:

    • Con TIMESTAMP_ISO8601 se extrae al campo timestamp la fecha y hora en que se registró el evento.
    • La clase LOGLEVEL extrae al campo level el nivel del mensaje.
    • La clase general NUMBER en esa posición de la línea de log representa el pid del proceso de la aplicación.
    • Luego se usa la clase USERNAME, que parsea nombres de usuario, para el nombre del hilo (thread).
    • JAVAFILE es un patrón para analizar nombres completos de clases Java, que se guardan en el campo class.
    • DATA es un patrón para cualquier cosa excepto saltos de línea y representa el cuerpo del mensaje (themessage).
    • Por último, se utiliza una captura de grupo nombrado en formato (?<nombre-grupo>patrón) para extraer la traza de una excepción en caso de que exista.
      El grupo nombrado stacktrace también se añade al objeto Logstash.

    Por defecto, Logstash toma como fecha del evento de log el instante en que procesa ese evento.
    Lógicamente esto no es lo deseable porque cada mensaje de log ya lleva su marca de tiempo propia.
    Con este filtro date, Logstash utilizará como fecha el valor del campo timestamp extraído con grok.

    Los filtros mutate realizan estas tareas en este orden:

    1. rename: reemplazar el campo message original con el cuerpo del mensaje extraído en themessage.
    2. add_field: copiar al campo appid la ruta del fichero de log para extraer después el nombre de la aplicación.
    3. gsub: capturar con una regex el nombre del fichero de log sin extensión y usarlo como valor de appid.
    La razón de tener dos mutate es que dentro de un mutate las operaciones no se aplican en ningún orden determinado y en este tutorial es necesario que primero se realice la operación add_field y después el gsub.

    4.3. Salida

    Los objetos resultantes se envían a Elasticsearch para que los almacene en un índice mensual de Logstash según el patrón logstash-%{+YYYY.MM}.
    Si la aplicación o aplicaciones generan enormes cantidades de log, se puede decidir tener un índice semanal o incluso diario, aunque no es raro tener índices de cientos de gigas e incluso teras.


    5. Generando alertas por errores

    En el primer tutorial se sugirió Watcher como herramienta de alertas basada en consultas sobre los datos en Elasticsearch.
    Una alternativa gratuita a Watcher es una salida Logstash que envíe mensajes de alerta por e-mail o algún otro sistema de mensajería instantánea como Slack.


    5.1. Configurando las alertas

    Para enviar un e-mail cada vez que se reciba un evento de log de nivel ERROR, añadiremos el plugin email a la sección output de nuestra configuración Logstash.
    El plugin está envuelto en una condición (sentencia if) que evalúa si el campo level del evento es ERROR:

    if [level] == "ERROR" {
        email {
          address => "smtp.gmail.com"
          port => "587"
          use_tls => true
          username => "origen@gmail.com"
          password => "xyz"
          from => "origen@gmail.com"
          to => "destino@gmail.com"
          subject => "%{[appid]}: ERROR"
          body => "An ERROR event has been detected on %{[appid]}:\n- Timestamp: %{[timestamp]}\n- Source: %{[class]}\n- Message: %{[message]}\n- Stacktrace: %{[stacktrace]}"
        }
      }

    Con Ansible, habrá que añadir este texto a la variable logstash.outputs del fichero ansible/environments/tutorial/group_vars/apps y ejecutar el siguiente comando para actualizar la máquina virtual:

    Terminal
    $ vagrant provision appsserver

    5.2. Habilitando el acceso en Gmail

    Antes de probar las alertas, si la cuenta SMTP desde la que se va a enviar el correo pertenece a Gmail, es necesario entrar a esta web para habilitar el acceso “menos seguro” a esa cuenta de correo:
    elk-img13

    De no hacerlo, Gmail enviará este correo a la cuenta de origen:
    elk-img14


    5.3. Probando las alertas

    A continuación sólo falta ejecutar la aplicación de prueba desde la máquina virtual appsserver:

    Terminal
    $ java -jar elk-example-app-1.0.0-SNAPSHOT.jar duration=10

    Y unos segundos después la cuenta de correo de destino recibirá mensajes como este:
    elk-img15


    6. Conclusiones

    No hay muchas alternativas a Logstash que se aproximen a su potencia, flexibilidad y soporte.
    Este tutorial ha demostrado sus funciones más interesantes y cómo sacarles provecho, aunque queda mucho por descubrir navegando entre el mar de plugins disponibles, tanto oficiales como extraoficiales (sólo hay que buscar un poco en los repositorios de GitHub).

    Claro, estas posibilidades no vienen gratis e implican que Logstash puede llegar a consumir bastantes recursos en un servidor que nos interesa aprovechar al máximo para nuestras aplicaciones.
    Para mitigar este problema, Elastic ha publicado Beats y en concreto Filebeat.

    Los agentes Beats son aplicaciones ligeras que extraen información de la máquina donde se ejecutan y la envían a Logstash u otros destinos como Elasticsearch.
    Filebeat en particular lee ficheros de log y los envía a Logstash.
    De este modo, se desplegaría un Filebeat ligero en cada servidor donde se produzcan logs y un único servidor Logstash al que se envíen todos los mensajes para su procesamiento y posterior almacenamiento en Elasticsearch.

    Otra cuestión interesante es el control de flujo de mensajes hacia Elasticsearch.
    Una gran cantidad de logs puede saturar los nodos de Elasticsearch, lo que obligaría a desplegar una cola de mensajes intermedia que además evitase pérdidas de logs en caso de caídas.

    El próximo tutorial de la serie explicará cómo configurar Elasticsearch para mantenerlo en buenas condiciones.


    7. Referencias

    Strings compactos

    $
    0
    0

    Java 6 introdujo un mecanismo para almacenar caracteres ASCII en byte[] en lugar de en char[]. Esta característica se eliminó de nuevo en Java 7. Aún así, volverá en Java 9, pero en esta ocasión, por defecto está habilitada la compresión y se usa siempre byte[].

    Éste artículo es una traducción al castellano de la entrada original publicada, en inglés, por Dr. Heinz Kabutz en su número 237 del JavaSpecialists newsletter. Puedes consultar el texto original en Javaspecialists’ Newsletter #237: String compaction

    Este artículo se publica en Adictos al Trabajo, con permiso del autor, traducido por David Gómez García, (@dgomezg) consultor tecnológico en Autentia, colaborador de Javaspecialists e instructor certificado para impartir los cursos de Javaspecialists en Español.

    Strings compactos

    Estuve una semana entera instruyendo a un grupo de buenos desarrolladores Java de Provinzial Rheinland Versicherung AG sobre las sutilezas de los patrones de diseño en Java. Os sorprendería saber que mi Patterns Course es, de todos, el más popular. Ni el Advanced concurrency and performance for Java 8 (aunque también está bastante solicitado), ni el curso de introdución a Java (no he impartido ninguno en los últimos años), ni siquiera el Advanced Topics in Java. No, mi humilde curso de patrones de diseño que escribí ya en 2001 es todavía el más demandando en la actualidad.

    Normalmente, cuando imparto mi curso de patrones, vemos todo tipo de temas relacionados en Java. De esta forma, los asistentes aprenden mucho más de lo que encontrarían en cualquier libro: pueden ver dónde se utilizan los patrones en la propia JDK; aprenden buenos principios de diseño; conocen las últimas mejoras de Java 8, aunque aún estén anclados en JDK 6 o 7; incluso tocamos un poquito de concurrencia. Este es el curso que, una vez que una empresa ha inscrito a sus programadores, habitualmente siguen enviando a más y más de ellos. Esta es la razón por la que, 15 años después de escribir la primera versión, aún es popular entre las empresas.

    En una de esas jornadas, estábamos revisando el patrón Flyweight, que tiene una estructura de clases bastante extraña. No es realmente un patrón de diseño. Sin embargo, como el Facade, es un mal necesario en los patrones de diseño. Me explico. Un buen diseño orientado a objetos produce sistemas altamente configurables y reducen la duplicidad de código. Y esto es bueno, pero también implica a veces bastante trabajo para utilizar el sistema. El patrón Facade hace que un subsistema complejo sea más fácil de utilizar. ¿Por qué es complejo?. Porque normalmente tenemos muchas formas de usarlo, gracias a un uso generoso de los propios patrones de diseño. Flyweight tiene una razón de ser parecida. Normalmente, los buenos diseños en orientación a objetos tienen muchos más objetos (e instancias) que los diseños monolíticos, donde todo es un Singleton. Flyweight trata de reducir el número de objetos, compartiendo las instancias de aquellos que son iguales; lo que es posible si desde fuera no dependemos (o modificamos) el estado interno de los mismos.

    Estábamos viendo la fusión de Strings en clase, y cómo el char[] interno en String se reemplaza con un char[] compartido cuando tenemos varios String que contienen el mismo valor. Para ello hay que utilizar G1 como Garbage Collector (-XX:+UseG1GC) y también activar la característica de String deduplication (-XX:+UseStringDeduplication). Funciona muy bien en Java 8. Pero quería comprobar si estaba activo por defecto en Java 9, dado que G1 es su Garbage Collector por defecto. Me sorprendió que mi código ahora produjese un ClassCastException al intentar convertir el campo value de String a char[].

    En algún momento con Java 6, aparecieron los String comprimidos. Dicho comportamiento estaba desactivado por defecto, aunque se podía activar con -XX:+UseCompressedStrings. Al activarlo, los Strings que contienen únicamente caracteres ASCII (codificables en 7-bits) pasan automáticamente a tener un byte[] como estructura interna. Con sólo un carácter en el String que necesite más de 7 bits para codificarlo, el String utilizaba de nuevo un char[]. Más curioso es cuando el String contiene caracteres UTF-16, como los del alfabeto Hindi Devanagari, porque en ese caso se crean objetos adicionales y al final tenemos una tasa de creación de objetos aún mayor que sin la compresión de Strings. Pero para el caso de los caracteres US ASCII, es perfecto. Por alguna razón, esta característica de Java 6 se descartó en Java 7 y la opción para activarlo se eliminó definitivamente en Java 8.

    Pero con Java 9, aparece la nueva opción -XX:+CompactStrings, activa por defecto. Si examinas internamente la clase String, verás que siempre almacena los caracteres de la cadena en un byte[]. También hay un nuevo campo de tipo byte que almacena el encoding, que de momento puede ser Latin1 (0) o UTF-16 (1). En el futuro podrían añadirse otros valores. Así que, si tus caracteres son Latin1, tu String ocupará menos memoria.

    Para probarlo, he escrito un pequeño programa en Java que puede ejecutarse con Java 6, Java 7 y Java 9 para ver las diferencias:

    import java.lang.reflect.*;
    
    	public class StringCompactionTest {
    	  private static Field valueField;
    
    	  static {
    	    try {
    	      valueField = String.class.getDeclaredField("value");
    	      valueField.setAccessible(true);
    	    } catch (NoSuchFieldException e) {
    	      throw new ExceptionInInitializerError(e);
    	    }
    	  }
    
    	  public static void main(String... args)
    	      throws IllegalAccessException {
    	    showGoryDetails("hello world");
    	    showGoryDetails("hello w\u00f8rld"); // Scandinavian o
    	    showGoryDetails("he\u03bb\u03bbo wor\u03bbd"); // Greek l
    	  }
    
    	  private static void showGoryDetails(String s)
    	      throws IllegalAccessException {
    	    s = "" + s;
    	    System.out.printf("Details of String \"%s\"\n", s);
    	    System.out.printf("Identity Hash of String: 0x%x%n",
    	        System.identityHashCode(s));
    	    Object value = valueField.get(s);
    	    System.out.println("Type of value field: " +
    	        value.getClass().getSimpleName());
    	    System.out.println("Length of value field: " +
    	        Array.getLength(value));
    	    System.out.printf("Identity Hash of value: 0x%x%n",
    	        System.identityHashCode(value));
    	    System.out.println();
    	  }
    	}

    Esta es la primera ejecución, con Java 6 y el flag -XX:-UseCompressedStrings (por defecto). Observa como cada uno de los Strings contiene un char[].

    Java6 no compaction
    	java version "1.6.0_65"
    
    	Details of String "hello world"
    	Identity Hash of String: 0x7b1ddcde
    	Type of value field: char[]
    	Length of value field: 11
    	Identity Hash of value: 0x6c6e70c7
    
    	Details of String "hello wørld"
    	Identity Hash of String: 0x46ae506e
    	Type of value field: char[]
    	Length of value field: 11
    	Identity Hash of value: 0x5e228a02
    
    	Details of String "heλλo worλd"
    	Identity Hash of String: 0x2d92b996
    	Type of value field: char[]
    	Length of value field: 11
    	Identity Hash of value: 0x7bd63e39

    La segunda vez, lo ejecutamos con Java 6 y -XX:+UseCompressedStrings. La cadena “hello world” contiene un byte[] y las otras dos un char[]. Sólo se comprimen los caracteres US ASCII (de 7 bits).

    Java6 compaction
    java version "1.6.0_65"
    
    Details of String "hello world"
    Identity Hash of String: 0x46ae506e
    Type of value field: byte[]
    Length of value field: 11
    Identity Hash of value: 0x7bd63e39
    
    Details of String "hello wørld"
    Identity Hash of String: 0x42b988a6
    Type of value field: char[]
    Length of value field: 11
    Identity Hash of value: 0x22ba6c83
    
    Details of String "heλλo worλd"
    Identity Hash of String: 0x7d2a1e44
    Type of value field: char[]
    Length of value field: 11
    Identity Hash of value: 0x5829428e

    En Java 7 el flag se ignora. En Java 8 se eliminó, por tanto, la JVM con -XX:+UseCompressedStrings no arrancará. Por supuesto, todos los Strings contienen un char[].

    Java7 compaction
    Java HotSpot(TM) 64-Bit Server VM warning: ignoring option
        UseCompressedStrings; support was removed in 7.0
    java version "1.7.0_80"
    
    Details of String "hello world"
    Identity Hash of String: 0xa89848d
    Type of value field: char[]
    Length of value field: 11
    Identity Hash of value: 0x57fd54c4
    
    Details of String "hello wørld"
    Identity Hash of String: 0x38c83cfd
    Type of value field: char[]
    Length of value field: 11
    Identity Hash of value: 0x621c232a
    
    Details of String "heλλo worλd"
    Identity Hash of String: 0x2548ccb8
    Type of value field: char[]
    Length of value field: 11
    Identity Hash of value: 0x4e785727

    En Java 9 tenemos el nuevo flag -XX:+CompactStrings, activo por defecto. Los Strings ahora almacenan siempre su carga en un byte[], independientenmente del encoding. Por ejemplo, vemos que para Latin1, todos los bytes están comprimidos.

    Java9 compaction
    	java version "9-ea"
    
    	Details of String "hello world"
    	Identity Hash of String: 0x77f03bb1
    	Type of value field: byte[]
    	Length of value field: 11
    	Identity Hash of value: 0x7a92922
    
    	Details of String "hello wørld"
    	Identity Hash of String: 0x71f2a7d5
    	Type of value field: byte[]
    	Length of value field: 11
    	Identity Hash of value: 0x2cfb4a64
    
    	Details of String "heλλo worλd"
    	Identity Hash of String: 0x5474c6c
    	Type of value field: byte[]
    	Length of value field: 22
    	Identity Hash of value: 0x4b6995df

    Naturalmente, podemos desactivar esta característica de Java 9 con -XX:-CompactStrings. Sin embargo, la estructura de String ha cambiado así que, independientemente de lo que hagas, el campo value sigue siendo un byte[].

    Java9 no compaction
    	java version "9-ea"
    
    	Details of String "hello world"
    	Identity Hash of String: 0x21a06946
    	Type of value field: byte[]
    	Length of value field: 22
    	Identity Hash of value: 0x25618e91
    
    	Details of String "hello wørld"
    	Identity Hash of String: 0x7a92922
    	Type of value field: byte[]
    	Length of value field: 22
    	Identity Hash of value: 0x71f2a7d5
    
    	Details of String "heλλo worλd"
    	Identity Hash of String: 0x2cfb4a64
    	Type of value field: byte[]
    	Length of value field: 22
    	Identity Hash of value: 0x5474c6c

    Cualquiera que utilice introspección para acceder a las tripas de String, podría obtener ahora un ClassCastException. Esperemos que la cantidad de esos programadores sea infinitamente pequeño.

    Más preocupante es el rendimiento. Métodos como String.charAt(int) solían ser rápidos como la pólvora. Puedo notar una ralentización en Java 9. Si recorres habitualmente las cadenas con charAt(), será mejor que evalues algunas alternativas, ¡aunque estoy seguro de cuáles!. O quizás lo arreglen en la release final de Java 9, al fin y al cabo estoy trabajando con una Early Release (EA).

    Me contaron un truquito de Peter Lawrey en una de las JCrete Unconferences: String tiene un constructor que recibe como parámetros un char[] y un boolean. El parámetro boolean no se utiliza nunca y se supone que debes pasarlo como true, indicando que el char[] se utilizará directamente como value y no se copiará. Este es el código:

    String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;
    }

    El truco de Lawrey consiste en crear Strings de forma muy rápida desde un char[] utilizando directamente este constructor. No estoy seguro de los detalles, pero lo más probable es que utilice JavaLangAccess que nos proporciona la clase SharedSecrets. Antes de Java 9, esta clase estaba en el paquete sun.misc.package. Desde Java 9, está en jdk.internal.misc. Espero que no estés utilizando éste constructor directamente, porque tendrás que cambiar tu código cuando actualices a Java 9.

    Aquí tienes el código. Tendrás que cambiar los imports en función de la versión de Java que uses

    //import sun.misc.*; // prior to Java 9, use this
    import jdk.internal.misc.*; // since Java 9, use this instead
    
    public class StringUnsafeTest {
      private static String s;
    
      public static void main(String... args) {
        char[] chars = "hello world".toCharArray();
        JavaLangAccess javaLang = SharedSecrets.getJavaLangAccess();
        long time = System.currentTimeMillis();
        for (int i = 0; i < 100 * 1000 * 1000; i++) {
          s = javaLang.newStringUnsafe(chars);
        }
        time = System.currentTimeMillis() - time;
        System.out.println("time = " + time);
      }
    }

    En resumen, si hablas inglés, alemán, francés o español, tus Strings son ahora mucho más ligeros. Para los griegos y los chinos, siguen siendo lo mismo. Para todos, seguramente resultarán un poco más lentos.

    Saludos desde el aeropuerto de Tesalónica.

    Heinz.

    Conviértete en un Dios de la productividad en IntelliJ IDEA

    $
    0
    0

    La intención de este tutorial NO es decir absolutamente todos los atajos de teclado, sino nombrar algunos de los más útiles, además de herramientas interesantes o consideraciones a tener en cuenta. ¡Te sorprenderá todo lo que se puede hacer!

    Índice de contenidos

    Entorno

    El tutorial está escrito usando el siguiente entorno:

    • Hardware: Portátil MacBook Pro 15′ (2 Ghz Intel Core I7, 8GB DDR3).
    • Sistema Operativo: Mac OS El Capitan 10.11
    • Entorno de desarrollo: IntelliJ IDEA (Minerva) Ultimate 2016.1

    Nótese que al usar un Mac, los atajos serán distintos, sin embargo acompaño después del atajo de Mac el correspondiente en Windows.

    Introducción

    La doctrina principal del Dios de la productividad en IntelliJ es la búsqueda de acciones:

    [⌘ ⇧ A] ó [Ctrl + ⇧ + A]

    Este es el atajo de teclado que abre el camino a todos los demás atajos. Básicamente despliega un popup de búsqueda en el que puedes buscar cualquier acción. Si la acción buscada tiene asociado un atajo de teclado, éste se mostrará. Aquí podría terminar el tutorial, ya no necesitas más, pero vamos a seguir. Seguro que lo agradeces.

    La intención de este tutorial no es decir absolutamente todos los atajos de teclado, sino nombrar algunos de los más útiles, además de herramientas interesantes o consideraciones a tener en cuenta.

    Abrir preferencias con [⌘ ,] ó [Ctrl + Alt + S] en Windows. Así lo puedes tunear a tu gusto en un primer vistazo, como por ejemplo mostrando los números de línea en el editor (tengo esa costumbre).

    Navegación

    Navegar a una clase: [⌘ O] ó [Ctrl + N]. Puede hacerse uso de CamelCase, por ejemplo, para la clase AnotherClientOfAuthenticator, con poner ACOA nos debería mostrar ese resultado.


    O a un fichero: [⇧ ⌘ O] ó [Ctrl + ⇧]

    Si necesitamos navegar a un directorio, con que acabe en / nos es suficiente.


    Por supuesto, también podemos hacer uso de expresiones como * de forma que para DependentObjectArchitecture, con poner *object o *endentOb funcionaría.

    Si por lo que sea necesitamos ir a una línea específica de una clase en concreto, podemos hacerlo mediante NombreDeClase:numlinea, como por ejemplo User:24 para ir al fichero User en la linea 24.

    Para buscar un método, atributo, etc. en concreto, podemos usar la búsqueda por símbolo con [⌥ ⌘ O] o [Ctrl + ⇧ + Alt + N].


    Siguiendo con más opciones de navegación, una forma rápida para trabajar entre distintos ficheros es la acción “Ficheros recientes” mediante [⌘ E] o [Ctrl + E].


    Similar a la anterior pero de una forma más directa, a través de (⌘ + [) y (⌘ + ]) ó [Ctrl + Alt + Izquierda] y [Ctrl + Alt + Derecha] podemos ir rotando entre los últimos archivos que hemos ido editando.

    Podemos explorar las distintas ventanas como la estructura del proyecto con el botón [⌘ 1] ó [Alt + 1].


    Si nos fijamos en las distintas pestañas en los bordes de la pantalla, algunas tienen número. Ese número es el que junto al botón ⌘ ó Alt nos permite navegar hacia esa pestaña. Algunos típicos son [⌘ 4] o [⌘ 5] ó [Alt + 4 o 5] para las ventanas de Ejecución y Debug respectivamente. Para ganar agilidad y ahorrar clicks, se puede volver al editor con la tecla ESC.

    Editor

    Puedes ahorrarte unos valiosos segundos al tener un atajo de teclado para seleccionar el contexto en que nos encontramos, empezando por la palabra, en lugar de tener que desplazarte al principio o el final de una palabra para seleccionarla. ¿Cómo lo hacemos?

    [Alt + Arriba]

    Podemos encadenarlo para que nos vaya seleccionando la línea, bloque o incluso el fichero completo en el que nos encontremos. Podemos disminuir la selección con [Alt + Abajo].

    Una situación que se nos puede dar normalmente es que nos falte por cerrar un paréntesis o un punto y coma. En estos casos es útil el atajo [⌘ ⇧ Enter].

    Una función muy interesante si desarrollas aplicaciones con soporte a varios idiomas es la gestión que hace IntelliJ de ese tipo de ficheros de recursos. Ficheros de properties, como los del tipo messages_en.properties, etc. se pueden editar a la vez en todos los idiomas para así facilitar el llevar un control más exaustivo de lo que se escribe. Para hacer uso de esta funcionalidad únicamente hay que situarse en uno de estos ficheros y ponerse en la vista Resource Bundle en lugar de la vista normal en modo texto.


    Con [⇧ + Ctrl + Izquierda/Derecha] se puede alternar entre ambas pestañas.

    Live Templates

    Estoy convencido de que no se le da la importancia que necesita a este tipo de plantillas personalizables. Navegamos hasta las preferencias de IntelliJ, Editor -> Live Templates. Ahí podemos consultar las que vienen por defecto y añadir nuevas.


    Como ejemplo veremos cómo crear una plantilla para hacer un test haciendo uso de variables. Las variables se definen entre símbolos ‘$‘ de forma que podemos ir moviéndonos entre ellas con el tabulador.


    Al crear la plantilla definimos con qué atajo se desencadena: en nuestro caso al escribir test y pulsar TAB. Sin embargo, podemos usar enter o cualquier otra. Destacar también la variable $END$, una variable especial que simboliza la posición final al desplazarnos a la última variable.


    Como vemos en la imagen, al usarlo nos permite escribir en el lugar que hemos definido como variable en la parte anterior.

    Generación y transformación de código

    [⌘ N] ó [Ctrl + Enter] para crear código. Por ejemplo, los típicos constructores, getters y setters, generar los métodos con override, etc.


    Pero no siempre queremos generar código de la nada, quizá únicamente queremos transformarlo de alguna forma. Por ejemplo, quizá queramos mover un bloque de código a un método privado, o convertir un valor en una constante, incluso guardar en una variable el resultado de una determinada sentencia.

    La parte de refactoring es muy extensa. Nombraremos algunas de las opciones más comunes. Uno de los atajos esenciales que tenemos a nuestra disposición es la invocación del menú de refactor con [Ctrl + T].


    Es importante recalcar que este menú, como tantos otros, depende del contexto desde el que se le invoque. Es decir, el contenido del menú será distinto si se le invoca desde una parte del código u otra.

    En ocasiones nos interesa asignar el contenido de una sentencia a una variable. Además de a través del menú que acabamos de ver, podemos usar el atajo [⌘ ⌥ V] ó [Ctrl + Alt + V].

    Si lo que queremos es extraer un método, seleccionamos el código a extraer y pulsamos [⌘ ⌥ M] ó [Ctrl + Alt + M].

    Como os habréis dado cuenta, este tipo de atajos siguen un patrón, la letra a pulsar es la inicial de lo quieres extraer: M para método o V para variable. Siguiendo este patrón podemos deducir algunos de los siguientes atajos, como extraer parámetros con [⌘ ⌥ P] ó [Ctrl + Alt + P], campos o atributos con [⌘ ⌥ F] ó [Ctrl + Alt + F] y constantes con [⌘ ⌥ C] ó [Ctrl + Alt + C] entre otros.

    Acabando con este apartado, nos puede ser útil el Inline. Consiste en cambiar un método o una variable por su valor asignado. Para usarlo debemos usar [⌘ ⌥ N] ó [Ctrl + Alt + N].

    Control de versiones

    De forma similar al refactoring, VCS también tiene su menú rápido asociado a través de [Ctrl + V].


    Tenemos desde las operaciones típicas como Commit con [⌘ K] ó [Ctrl + K] o Push con [⌘ ⇧ K] ó [Ctrl + ⇧ + K], hasta funciones más avanzadas integradas en IntelliJ. Entre estas últimas destacan las anotaciones (la opción 5 en el menú desplegable más arriba), que permiten saber quién ha sido la última persona en modificar cada una de las líneas de un determinado fichero como vemos más abajo.


    Disponemos, por supuesto, de un historial clásico de las ramas y commits que se han ido haciendo a lo largo del tiempo con varias opciones de filtrado. Para poder verlo navegamos hacia la ventana de control de versiones con [⌘ 9] ó [Alt + 9], y posteriormente seleccionamos la pestaña de Log.


    Desde este histórico podemos realizar múltiples operaciones: Checkout, Cherry-Pick, ramas nuevas, tags, etc. Además de ver qué archivos se han cambiado en cada uno de los commits y comparar cambios.

    Integraciones con otros productos

    Merece la pena hablar de las integraciones de distintos productos en IntelliJ, ya sean nativas como Maven o el cliente integrado de bases de datos o a través de plugins, como la integración con Docker o con Findbugs.

    La integración con Maven o Gradle permite, entre otras cosas, lanzar y consultar goals directamente.


    Conexiones a bases de datos sin necesidad de clientes en la ventana de Database. En ella podemos configurar varias conexiones sobre las que ejecutar consultas a través de una consola que nos proporciona, entre otros, un autocompletado bastante potente.


    Una vez creamos una consulta, podemos ejecutarla con [⌘ Enter] ó [Ctrl + Enter]. Al usar este atajo, despliega un menú en el que podemos seleccionar la consulta a ejecutar, desde la más interna a la más externa (en caso de tener consultas complejas).


    Como podéis comprobar esto nos evade de tener que usar otros clientes de bases de datos además de que el rendimiento es muy bueno.

    La última integración nativa que vamos a ver es la terminal integrada. Mediante el atajo [Alt + F12] navegamos hasta la ventana del terminal, cuyo directorio de trabajo será el de nuestro proyecto. Siempre útil.


    Además, a través de Plugins podemos incrementar las integraciones que IntelliJ provee por defecto. Para ello, vamos a las opciones de IntelliJ, en el apartado de plugins. Desde este apartado podemos consultar todos los plugins disponibles para IntelliJ. Nombraremos un par de ellos que proveen integraciones con Docker y Findbugs, para hacernos una idea de lo completos que pueden llegar a ser.


    El plugin de integración con Docker nos permite gestionar las imágenes y contenedores de docker desde una ventana integrada en IntelliJ. Lo primero que tenemos que hacer es configurar la conexión contra la máquina de Docker.


    Una vez conectados, podemos gestionar nuestros contenedores como queramos.

    Haz click en la imagen para verla más grande.

    Haz click en la imagen para verla más grande.

    La integración con Findbugs, a través de la cual puedes analizar el código de tu proyecto en busca de vulnerabilidades o malas prácticas con explicaciones detalladas de cada tipo de problema que encuentre y cómo solucionarlo. Este tipo de herramientas merecen tutoriales a parte. Son un gran modo de aprendizaje y crecimiento como desarrollador.

    Haz click en la imagen para verla más grande.

    Haz click en la imagen para verla más grande.

    Tests

    Sobre los tests, sólo destacar una funcionalidad integrada que nos permite conocer el porcentaje de cobertura de nuestros tests. Esta opción está disponible a la hora de ejecutar los tests, aunque por defecto no tiene un atajo de teclado asociado recomiendo asignarle alguno.


    En cualquier caso, además de ejecutar los tests que le indiquemos, se crea un informe de cobertura que podemos ir explorando por paquetes o clases.



    Consideraciones

    Algo muy a tener en cuenta es hacer un backup de nuestras preferencias con un simple Export Settings, que podemos buscar con nuestro primer mandamiento: [⌘ ⇧ A]. De esta forma exportaremos nuestras preferencias a un archivo jar que podemos usar en cualquier momento para importar todas nuestras preferencias, como en el caso de una nueva instalación.

    Otra consideración a tener en cuenta es el hecho de que la versión de maven que trae el bundle de IntelliJ puede diferir con la que se tenga instalada en local y, por tanto, puede haber incongruencias en los resultados de maven en función de si su ejecución se hace a través de la integración con maven de IntelliJ o desde la propia terminal integrada en IntelliJ.

    Conclusiones y agradecimientos

    Podemos concluir que nuestra productividad con éste o con cualquier otro IDE recae al final en cuánto conocemos la herramienta. Cuando más conozcamos la herramientas más posibilidades tendremos de hacer las cosas, y más rápido.

    Mi recomendación es que siempre intentéis dedicar tiempo a aprender las herramientas que utilizáis con asiduidad. Es muy sencillo aprender alguna cosa nueva sobre la herramienta que nos facilite la vida, aunque llevemos mucho tiempo usándola.

    No dudéis en comentar cualquier problema que tengáis o cualquier cosa que creáis que falte en este artículo. Decir que me comprometo a completar el artículo con cosas que vaya descubriendo en el futuro sobre IntelliJ.

    Ante la duda, la más… [⌘ ⇧ A] ó [Ctrl + ⇧ + A] .

    Gran parte del contenido de este tutorial no sería posible sin la ayuda de mis compañeros de Autentia: Jose Luis Rodriguez, Javier Sanchez o Yair Segura. Echadle un ojo a sus tutoriales, ¡ son la %&¿#* !.

    Recursos

    En esta sección destacaré contenido útil que merece la pena ver. En concreto el vídeo es lo mejor que podéis ver si tenéis un rato libre.

    Scaffolding en Javascript con Slush

    $
    0
    0

    Este tutorial pretende mostrar como funciona la herramienta Slush, para hacer scaffolding con Javascript. En él veremos como instalarlo y como hacer un primer generador sencillo para una aplicación de ejemplo. También se compara a nivel general con Yeoman, la herramienta más importante de este tipo.

    Índice de contenidos


    1. Introducción

    En el arranque de proyectos Javascript, en aplicaciones empresariales donde se quiere que las aplicaciones sigan siempre un mismo patrón es conveniente utilizar herramientas de scaffolding.

    La traducción literal de scaffolding es “andamiaje”, es decir, la creación de los “cimientos” a partir del esqueleto o el arquetipo. El arquetipo es un punto de partida del modelo original de una aplicación para el resto de aplicaciones. Por ejemplo, análogamente en Java tendríamos maven-archetype.

    Además de Slush existen otras herramientas para hacer scaffolding, como Yeoman, CodeKit, Lineman y Fireshell. De todas estas la herramienta más extendida es Yeoman (Tutorial “Creación de un generador de Yeoman”).

    Yeoman (2012) en sus inicios utilizaba Grunt como gestor de tareas, aunque en las últimas versiones ya utiliza Gulp en al menos algunos módulos, que es más eficiente. Slush está concebido desde su creación como una herramienta basada en Gulp, y aunque es algo más reciente (2014) llama la atención su sencillez de uso.

    Para utilizar esta herramienta es necesario tener instalado Node.js y Gulp. Para poder comprender este tutorial se necesita como requisito al menos unas nociones de cómo funciona Gulp y como se definen y ejecutan tareas (Tutorial “Primeros pasos con Gulp”.).


    2. Entorno

    El tutorial está escrito usando el siguiente entorno:

    • Hardware: Portátil Mac Book Pro 15″ (2,3 Ghz Intel Core i7, 16 GB DDR3)
    • Sistema Operativo: Mac OS X El Capitan
    • NodeJS v4.4.3
    • npm 3.8.6
    • Atom 1.7.1
    • Gulp 3.9.0

    3. Funcionamiento

    Las principales características de un generador son:

    • Copiar una estructura de proyecto (con los directorios y todo lo demás).
    • Preguntar al usuario para algunas opciones del proyecto.
    • Reemplazar variables en las plantillas (archivos del proyecto) en función de las respuestas introducidas por el usuario.
    • Auto-instalación de dependencias necesarias para el proyecto generado.
    • Solicitar que ficheros se desea sobrescribir (cuando se ejecuta el generador por segunda vez).

    Todo esto se podría hacer fácilmente utilizando Gulp directamente, es decir, utilizar Gulp como un generador. Para que fuera más fácil de usar un generador de un proyecto realizado con Gulp se construyó Slush.

    Slush localiza todos los proyectos generadores construidos con Gulp, lo que se denominan “slush generators” y hace posible ejecutar un generador sin la necesidad de especificar su ubicación.


    4. Instalación

    En primer lugar debemos instalar NVM (Node Version Manager) para tener la flexibilidad de cambiar de versión Node.Js en cualquier momento. Instalamos la última versión y asegurarnos la activación de las variables de entorno.

    > curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.0/install.sh | bash
    	> nvm install stable
    	> nvm alias default stable

    A continuación instalamos Gulp de manera global

    > npm install --global gulp-cli

    Después debemos instalar Slush

    > npm i -g gulp slush slush-generator

    5. El slushfile

    Slush no viene con ninguna funcionalidad propia, lo único que ofrece es una convención y conveniencia de ejecutar gulpfiles globales. Estos gulpfiles de los generadores se denominan slushfiles. El propósito de un slushfile es realizar tareas de scaffolding del proyecto o cualquier utilidad para el mismo. Si no disponemos de alguna funcionalidad probablemente se podría incorporar utilizando un plugin de Gulp.

    Como se puede ver a continuación se dispone de una lista de prompt donde se muestran las preguntas que se le va a hacer al usuario y la variable donde se va a almacenar el resultado. Para ello se utiliza el plugin inquirer. Esta lista se podría modificar para añadir más parámetros del proyecto. Por defecto además se utilizan los plugins gulp-install, gulp-conflict, gulp-template y gulp-rename.

    image06

    6. Creación de un generador

    En primer lugar debemos de crear un directorio para el proyecto y dentro crear un módulo de node de la siguiente manera.

    > mkdir
    > cd
    > slush generator

    A continuación Slush nos pedirá una serie de preguntas sobre parámetros de nuestra aplicación.

    image01

    Si abrimos el proyecto en un editor veremos la estructura del generador que hemos creado.

    image07

    Los elementos principales son:

    • package.json: fichero con el nombre del generador, las dependencias del proyecto, etc
    • slushfile.js: fichero con las tareas relativas al generador
    • templates: directorio con todos los ficheros y directorios que se copiaran en un nuevo proyecto, incluyendo un package.json, un gulpfile.js, los fuentes javascript, test unitarios, css, imágenes, etc.

    Para nuestro ejemplo vamos a añadir en el directorio templates una estructura básica de un proyecto de ejemplo, que contendrá los siguientes ficheros: gulpfile.js, package.json, index.html, app.js, app.css y una imagen.

    image05
    package.json
    {
      "name": "",
      "version": "1.0.0",
      "devDependencies": {
        "gulp-connect": "^2.0.6",
        "gulp": "^3.8.6"
      }
    }
    gulpfile.js
    var gulp = require('gulp'),
      connect = require('gulp-connect');
    
    gulp.task('connect', function() {
      connect.server({
        root: './src',
        livereload: true
      });
    });
    
    gulp.task('html', function() {
      gulp.src('src/index.html')
        .pipe(connect.reload());
    });
    
    gulp.task('watch', function() {
      gulp.watch(['src/*.html', 'src/css/**', 'src/js/**'], ['html']);
    });
    
    gulp.task('default', ['connect', 'watch']);
    index.html
    <!DOCTYPE html>
    <html>
      <head>
        <title><%= appName %></title>
        <link rel='stylesheet' href='css/app.css' />
      </head>
      <body>
        <h1><%= appName %></h1>
        <h1>Welcome to <%= appName %></h1><br>
        <h2><%= appDescription %></h2><br>
        <h3>Made by <%= authorName %></h3>
    
        <script type="text/javascript" src="js/app.js"></script>
      </body>
    </html>
    app.css
    body {
        padding: 50px;
        background-image: url("../img/smoke.jpg");
        color: yellow;
    }

    Para probar el generador de manera local debemos ejecutar el siguiente comando en el directorio raíz para crear un enlace simbólico a nuestro generador.

    > sudo npm link

    Si estuviéramos trabajando con un repositorio público (https://npmjs.com) o privado (por ejemplo Nexus) de Node podríamos publicarlo mediante npm publish. Para el alcance de este tutorial lo haremos simplemente con npm link.

    A continuación vamos a crear un proyecto nuevo a partir del generador. Para ello nos situaremos fuera del generador y crearemos un directorio para el proyecto y lo ejecutaremos de la siguiente manera:

    > mkdir app-demo-1
    > cd app-demo-1
    > slush app-demo-generator
    image03

    Para arrancar la aplicación bastaría con hacer un gulp, abrir una pestaña del navegador con la url http://localhost:8080 y … vualá.

    image00

    Una parte interesante de Slush es que se podrían hacer generadores para determinadas partes de una aplicación es decir, sub-generadores. Por ejemplo, imaginemos creamos un generador para nuestra aplicación con AngularJs, Jasmine, etc. y ya hemos creado nuestro “esqueleto”. Hasta ahí bien, quizás hemos creado un una vista, un controlador y un test para un componente, pero ¿qué pasa cuando queremos crear más componentes (módulos) que forman parte de la aplicación, pero que tienen siempre la misma estructura?

    Pues en este caso podríamos crear un sub-generador que creará un directorio con un controlador, una vista, test, directivas, etc. para la entidad que estemos generando y parametrizando el nombre de la entidad que le dará nombre a los ficheros, variables, etc.

    Al realizar el ejemplo me encontré con un bug (https://github.com/slushjs/slush/issues/46) por el cual la imagen copiada al nuevo proyecto estaba corrupta. En la web viene como solucionarlo hasta que lo incluyan en la próxima release.


    7. Repositorio de generadores

    Slush dispone de varios generadores desarrollados con diferentes tecnologías que podrían adaptarse a las necesidades de nuestro proyecto. Por ejemplo, podríamos crear un proyecto con tecnologías como AngularJs, ReactJs, Backbone, o proyectos con frontend y backend en javascript con ExpressJs y MongoDb, etc.

    A fecha de este tutorial la web de Slush (http://slushjs.github.io/generators/#/) ofrece 310 generadores, que son pocos si los comparamos con Yeoman (http://yeoman.io/generators/), que tiene 3764 generadores. En este aspecto Yeoman ofrece un repertorio mucho más amplio, aunque también hay que tener en cuenta que Slush es mucho más reciente.

    image04

    8. Conclusiones

    La herramienta de scaffolding más extendida en Javascript sigue siendo Yeoman, aunque Slush es una alternativa muy interesante. En la web de Slush dice que pretende ser un reemplazo de Yeoman, aunque a día de hoy esto no es cierto. Lo relevante de Slush es que tiene un gran potencial si lo siguen evolucionando, pero a día de hoy Yeoman sigue siendo la herramienta predominante. Se deja a criterio de cada uno elegir la herramienta que más le convenga utilizar en su proyecto.

    Una de las ventajas de Slush es que está basado en Gulp, que se basa en el enfoque de código sobre configuración, lo cual hace que el scaffolding sea pueda hacer de una manera más sencilla. Si se realiza scaffolding mediante configuración requiere más conocimiento sobre la implementación específica además de documentación. En cambio si se realiza por código es mucho más explícito, generalmente mucho menor cantidad de líneas y requiere menos conocimiento del dominio.

    Un inconveniente de Slush es que es quizás muy reciente en comparación con Yeoman y no está tan maduro. A pesar de que tiene un buen número de generadores realizados por terceros, y de que es bastante fácil de hacer uno propio, Yeoman tiene un número mucho mayor de generadores disponibles, además de mayor cantidad de documentación en la red.


    9. Referencias


    Refactoring: Safe Unwrap

    $
    0
    0

    En este tutorial aprenderemos una técnica de refactorización para borrar una clase Wrapper sin errores de compilación ni errores en los tests.

    Índice de contenidos


    1. Introducción

    Hace unos días tuvimos la oportunidad aquí en Autentia de tener con nosotros a Carlos Blé, autor del libro Diseño Ágil con TDD. Podéis visitar en el siguiente link su página web www.carlosble.com. En el taller de refactoring nos enseñó técnicas que nos permiten deshacernos de la dependencia que tenemos con el compilador para ver los errores que aparecen después de realizar un cambio. Estas técnicas son a priori difíciles de entender, por lo que recomiendo paciencia y tratar de entender lo que se pretende realizar antes de nada.


    2. Entorno

    Para este tutorial he usado el siguiente entorno:

    • Hardware: Portátil MacBook Pro 17′ (CPU Intel Core 2 Duo 3Ghz, 8GB DDR3, GPU NVIDIA GeForce 9400M 256 MB).
    • Sistema Operativo: Mac OS El Capitán 10.11
    • Entorno de desarrollo: IntelliJ Idea 2016.1
    • Gradle 2.13

    3. Conceptos básicos

    Antes de comenzar con el código del ejemplo, voy a explicar las técnicas de refactoring que vamos a usar. Todos los ejemplos que aparecen a continuación de las técnicas de refactoring están sacados de esta increíble web creada por Martin Fowler

    Inline method
    Realizar un inline method es poner el cuerpo de un método dentro del cuerpo de sus llamadores y removerlo de la clase en la que estaba definido. Es exactamente lo contrario que un Extract method. Captura de pantalla 2016-04-29 a las 13.15.17
    Pull up field/method
    Llamado Pull members up en IntelliJ. Lo que hace este refactor es “subir” un atributo o método al padre de una clase. En otras palabras, mueve un atributo o método de una clase a la clase superior de la que hereda. Captura de pantalla 2016-05-03 a las 14.54.09
    Replace Constructor with Factory Method
    El nombre es autoexplicativo, reemplaza el constructor de una clase por una factoría. Captura de pantalla 2016-04-29 a las 13.28.02
    Use interface where possible
    Reemplaza todas las instancias de una clase por instancias de la superclase o interfaz donde sea posible. El siguiente ejemplo, al ser un refactor más moderno, no está añadido en la web de Martin Fowler. He sacado el ejemplo de la web de IntelliJ, el cual es menos intuitivo que los otros, aunque explica el resultado que obtenemos del refactor. Captura de pantalla 2016-04-29 a las 13.53.48


    4. Ejemplo en C#

    El ejemplo en C# lo muestra en un vídeo Carlos Blé, el cual dejo a continuación.



    5. Ejemplo en Java

    Para el siguiente ejemplo usaré el material que Carlos nos proporcionó, el cual podéis descargar del siguiente enlace: Descargar ejemplo

    También dejo a continuación el código

    Model
    public class Model {
        private String Color;
        private int Amount;
    
        public Model(String color, int amount) {
            Color = color;
            Amount = amount;
        }
    
        public String getColor() {
            return Color;
        }
    
        public int getAmount() {
            return Amount;
        }
    }
    Wrapper
    public class Wrapper {
        private Model Wrapped;
    
        public Wrapper(Model wrapped) {
    
            Wrapped = wrapped;
        }
    
        public Model getWrapped() {
    
            return Wrapped;
        }
    }
    Consumer
    public class Consumer {
    
        public String getColor(Model model){
            Wrapper wrapper = new Wrapper(model);
    
            return wrapper.getWrapped().getColor();
        }
    }
    AnotherConsumer
    public class AnotherConsumer {
    
        public int getAmount(Model model){
            Wrapper wrapper = new Wrapper(model);
    
            return wrapper.getWrapped().getAmount();
        }
    }

    En este ejemplo la clase a eliminar es, como su propio nombre indica, la clase Wrapper. Se quiere eliminar para quitar de las clases Consumer y AnotherConsumer las llamadas que violan la Ley de Demeter: wrapper.getWrapped().getColor() y wrapper.getWrapped().getAmount(). Esto mejorará el entendimiento del código y su mantenimiento.

    Primer paso: Elaborar los test

    Como habréis notado, el ejemplo no contiene ninguna clase de test, asi que necesitamos crearlos. A continuación dejo el código de los que yo he realizado, aunque os animo a crearlos por vosotros mismos.

    import org.junit.Test;
    import src.safeunwrap.Consumer;
    import src.safeunwrap.Model;
    
    import static org.junit.Assert.assertEquals;
    
    public class ConsumerTest {
    
        private Consumer consumer;
        private Model model;
    
        @Test
        public void shouldReturnColorCallingConsumerGetColor(){
    
            consumer =  new Consumer();
            String color = "Green";
            int amount = 2;
            model = new Model(color, amount);
            assertEquals(color,consumer.getColor(model));
    
        }
    
    }
    import org.junit.Test;
    import src.safeunwrap.AnotherConsumer;
    import src.safeunwrap.Model;
    
    import static org.junit.Assert.assertEquals;
    
    public class AnotherConsumerTest {
    
        private AnotherConsumer anotherConsumer;
        private String color;
        private int amount;
        private Model model;
    
        @Test
        public void shouldReturnAmountCallingAnotherConsumerGetAmount(){
    
            anotherConsumer = new AnotherConsumer();
            color = "Red";
            amount = 5;
            model = new Model(color, amount);
            assertEquals(amount,anotherConsumer.getAmount(model));
    
        }
    }

    Segundo paso: Extender de Model

    La clase Wrapper debe heredar de la clase Model. Esto nos dará un error en el constructor debido a que la clase Model no contiene un constructor por defecto. Si queremos que los test no den error solo tenemos que añadir el super-constructor. Una vez hecho, si pasamos los test no nos dará ningún problema.

    Captura de pantalla 2016-05-03 a las 17.44.06

    Tercer paso: Getter return this

    En este momento necesitamos entender que el getter de la clase Wrapper devuelve un Model. Por tanto, si Wrapper extiende de Model, podemos hacer que el getter devuelva this y, por polimorfismo, el resultado del return sea un Model.

    Captura de pantalla 2016-05-04 a las 9.15.50

    Cuarto paso: Inline Getter

    Como el getter devuelve this y por polimorfismo se trata como un return de una instancia de Model, podemos hacer un inline para que en las clases en las que se usa el getter este se sustituya por una instancia de Wrapper. El getter desaparecerá mediante este refactor y podremos observar el cambio en las clases Consumer y AnotherConsumer.

    Wrapper
    Captura de pantalla 2016-05-04 a las 9.21.33
    Consumer y AnotherConsumer

    El inline ha eliminado el getWrapped() dejando la instancia de Wrapper con una sola llamada, con lo que ya cumplimos la Ley de Demeter:

    Captura de pantalla 2016-05-04 a las 9.30.42
    Captura de pantalla 2016-05-04 a las 9.27.37

    Quinto paso: Replace constructor with factory

    Ya hemos cumplido con la mitad del trabajo, hemos eliminado esa horrible cadena de métodos mejorando mucho el entendimiento del código. Ahora vamos a eliminar la clase Wrapper. Para ello lo primero que necesitamos es sustituir el constructor por una factoría. Este es probablemente el refactor más sencillo de entender de los que hay en este tutorial, por lo que no es necesario explicar nada más.

    Wrapper
    Captura de pantalla 2016-05-04 a las 9.41.07
    Consumer y AnotherConsumer
    Captura de pantalla 2016-05-04 a las 9.43.19
    Captura de pantalla 2016-05-04 a las 9.43.32

    Sexto paso: Use interface

    Ahora, debemos usar el refactor Use interface where possible, el cual puede ser el más difícil de entender. Vayamos paso a paso. Primero, seleccionamos la factoría y elegimos el refactor. Nos saldrá una ventanita en la que nos pedirá seleccionar qué clase es la que debe utilizar. Elegimos Model:

    Captura de pantalla 2016-05-04 a las 9.49.27

    En la siguiente ventana se nos muestran las instancias de Wrapper que van a ser sustituidas por instancias de Model. Si seleccionamos el checkbox nos cambiará el nombre de la variable (wrapper -> model). Esto nos dará un error ya que el parámetro de entrada de getAmount(Model model) y de getColor(Model model) se llama igual. Por tanto si queremos que renombre las variables, debemos escribirle en lugar de model otro nombre. Yo he usado modelUnwrap

    Captura de pantalla 2016-05-04 a las 9.58.44

    El resultado es el siguiente:

    Wrapper
    Captura de pantalla 2016-05-04 a las 10.15.51
    Ahora la factoría devuelve Model el lugar de Wrapper. Como ya he nombrado a lo largo del tutorial, la instancia de Wrapper por polimorfismo es tratada también como Model, ya que hereda de dicha clase.
    Consumer y AnotherConsumer
    Captura de pantalla 2016-05-04 a las 10.16.16
    Captura de pantalla 2016-05-04 a las 10.16.32

    Séptimo paso: Inline factory

    Ahora debemos parar un segundo a entender qué es lo que hace el factory en este instante:

    public static Model createWrapper(Model wrapped) {
         return new Wrapper(wrapped);
    }

    Recoge por parámetro un Model y devuelve el mismo Model pero “Wrappeado”. Esto podemos eliminarlo en dos pasos. Primero, sustituimos new Wrapper(wrapped) por wrapped. Esto deja la factoría sin sentido, ya que recoge un Model y devuelve el mismo Model.

    Captura de pantalla 2016-05-04 a las 10.37.56

    Segundo, realizamos un inline del factory, con el que sustituremos todas las llamadas de createWrapper por la instancia del objeto pasado por parámetro, wrapped. El resultado es el siguiente:

    Wrapper
    Captura de pantalla 2016-05-04 a las 10.47.01
    Consumer y AnotherConsumer
    Captura de pantalla 2016-05-04 a las 10.48.01
    Captura de pantalla 2016-05-04 a las 10.48.51

    Como podemos comprobar, las clases Consumer y AnotherConsumer ya no usan la clase Wrapper y, de hecho, el propio IntelliJ nos avisa de ello. Por último solo quedaría hacer un safe delete de la clase Wrapper y eliminar la variable modelUnwrap sustituyendo esta por el model que entra por parámetro (como es evidente). En este momento, si has entendido bien los refactor te habrás dado cuenta de que puedes hacer un inline de modelUnwrap, lo que nos hará todo automático

    Wrapper
    Captura de pantalla 2016-05-04 a las 10.53.27
    Consumer y AnotherConsumer
    Captura de pantalla 2016-05-04 a las 11.16.28
    Captura de pantalla 2016-05-04 a las 11.16.42

    Demo en vídeo

    Para acabar, me gustaría poner este vídeo donde hago todos los pasos antes nombrados. Antes de verlo, por favor, asegúrate de que has leído todo lo anterior. El objetivo de este tutorial no es replicar los pasos, si no entender lo que se pretende hacer.

    La canción de fondo es Foria – Break Away y está sacada de NoCopyrightSounds. Podéis encontrar más sobre Foria en el siguiente link: Foria


    6. Conclusiones

    Es probable que alguien piense que el resultado sería el mismo que borrando la clase desde el principio y arreglar lo que falle, es cierto, el resultado es el mismo. El problema está cuando tienes que arreglar mil clases distintas y tener que ir una por una sin poder asegurarnos de que todo funciona hasta que no terminemos. Y ahora alguien puede pensar, al igual que hice yo, “entonces esto solo es para proyectos grandes”. En realidad me he dado cuenta de que es más útil en proyectos enormes, pero es recomendable hacerlo siempre.

    Aquí entra en juego el ser un buen programador, es decir, tener una disciplina al igual que en muchas otras profesiones las tienen. Yo a un obrero que para arreglar una pequeña grieta en una pared, tira la casa entera y vuelve a construirla, no le veo mucho futuro como profesional. Igual pasa con un informático que rompe todo para levantarlo de nuevo.

    El mundo del refactoring es muy amplio y, además, se está expandiendo. Por eso, como profesionales debemos aprender a refactorizar y a hacer bien las cosas. Una vez lo hayamos hecho, descubriremos todas las ventajas que nos aportan estas herramientas.


    7. Agradecimientos

    Quería agradecer a Carlos Blé su increíble taller de refactoring y por permitirme hacer este tutorial. También a Esther Lozano Hontecillas por ayudarme a entender los conceptos más importantes.

    java.util.Optional – Un pequeño tutorial práctico

    $
    0
    0

    Java 8 introdujo la clase java.util.Optional, basada en la popular clase de Guava del mismo nombre. Se dice que nunca deberíamos llamar al método get(). En este tutorial veremos algunos ejemplos de alternativas al get().

    Éste artículo es una traducción al castellano de la entrada original publicada, en inglés, por Dr. Heinz Kabutz en su número 238 del JavaSpecialists newsletter. Puedes consultar el texto original en Javaspecialists’ Newsletter #238: java.util.Optional – Short Tutorial By Example

    Este artículo se publica en Adictos al Trabajo, con permiso del autor, traducido por David Gómez García, (@dgomezg) consultor tecnológico en Autentia, colaborador de Javaspecialists e instructor certificado para impartir los cursos de Javaspecialists en Español.

    Bienvenidos a la edición número 238 del Javatmspecialists’ Newsletter, escrita a medias entre en el viaje de vuelta de JAX Finance de Londres y otro vuelo desde Grecia a España, porque estaré unos días en Málaga y donde además, he sido invitado a dar una charla el 10 de Mayo de 2016 en el Málaga User Group. Me gusta participar en los JUGs cuando viajo, porque me permite conocer gente que de verdad siente pasión por Java y por el aprendizaje constante.

    Hace unas semanas, Sanjeev Kumar me sugirió que, como realmente disfrutaba de mis videos educativos en Vimeo, podría tratar de grabar un pequeño mensaje en cada newsletter. Me gusta la idea y he comenzado a hacerlo. Espero que os resulte interesante. Por favor, decidme si lo es y si os gustaría seguir viendo más videos.


    Echa un vistazo al nuevo curso “Extreme Java”, que combina concurrencia, un poquito de rendimiento y Java 8: Extreme Java – Concurrency & Performance for Java 8, y que también los impartimos en Español.


    java.util.Optional – Un pequeño tutorial práctico.

    En mis investigaciones, me centro principalmente en el propio Java SE. Aunque también echo un vistazo a muchos otros frameworks como Spring, Guava, Eclipse Collections o JEE, no se quedan almacenados en la memoria a largo plazo de mi cerebro. Desafortunadamente, no he dedicado mucho tiempo al Optional de Google Guava, así que cuando java.util.Optional llegó con Java 8, me resultaba bastante novedoso. La gracia de Optional y de sus primos OptionalInt, OptionalDouble y OptionalLong está en que evitan que un método devuelva null. Un noble objetivo.

    Deberíamos evitar invocar al método get() y utilizar en su lugar otros métodos que os mostraré en un segundo. Si tenemos que utilizar get(), tendríamos que comprobar primero si hay valor. Un get() que no va precedido de una llamada a isPresent() es un bug. Dado que tenemos alternativas a get() para los casos más habituales, Stuart Marks ha propuesto no hace mucho que get() sea deprecado y sustituido con el método getWhenPresent(). ¡Creo que sólo Brian Goetz está de acuerdo con él! Afortunadamente no sucederá, porque tampoco creo que getWhenPresent() sea un buen nombre. ¿Qué sucede si no está presente? ¿Qué hacemos? ¿Se bloquea?. ¡Oh! ¡Si lanza un NoSuchElementException!. Entonces, ¿por qué no llamarlo getWhenPresentOrElseThrowNoSuchElementExceptionIfYouCallIt()? Un cambio así en Java es muy pesado. Para empezar, es necesario añadir el nuevo método y deprecar el antiguo método get(). Entonces, habrá que esperar, al menos una release para eliminar get() definitivamente. Entre tanto, deberemos cambiar todo nuestro código que llama correctamente a get() después de haber comprobado que el valor existe con isPresent() para utilizar en su lugar el nuevo nombre, o alternativamente, anotar nuestro código con @SupressWarnings("unchecked"). O también podemos ignorar los avisos de usos de métodos obsoletos, como hemos estado haciendo los últimos 20 años.

    Stuart Marks me envió un artículo muy interesante, donde muestra ejemplos del JDK donde se utiliza Optional.get() y cómo podría haber sisdo reemplazado con otros mecanismos. Muy amablemente, me permitió publicar sus conclusiones en este newsletter. Espero que resulte un “tutorial práctico” para aquellos que no han usado Optional antes y quiere conocer cómo funciona.

    El primer método que debemos conocer es Optional.ifPresent(Consumer action). No isPresent(), sino ifPresent(). Debo admitir que cuando vi su referencia a este método me rasqué la cabeza pensando si realmente exisitía. Había visto isPresent antes, pero no me había percatado que también teníamos otro método con casi el mismo nombre. Al final, “s” y “f” sólo están separadas por una “d” :-). Así que, si estás tentando a escribir código como el siguiente:

    if (source.isPresent()) {
    	  doSomethingWith(source.get());
    	}

    mientras doSomethingWith() no lance ninguna checked exception, podrías transformar el código en:

    source.ifPresent(s -> doSomethingWith(s));

    O si, como a mí, no te asustan las referencias a métodos, puedes hacerlo así:

    source.ifPresent(this::doSomethingWith);

    Stuart curioseó en la JDK y encontró un puñado de ejemplos donde se podría haber utilizado esta estructura. Recuerda, se trata de los desarrolladores de la JDK, por tanto seguramente no desarrolladores totalmente novatos. El primer ejemplo es de DependencyFinder.java:

    if (source.isPresent()) {
    	    executor.runTask(source.get(), deque);
    	}

    que podía re-escribirse como

    source.ifPresent(archive -> executor.runTask(archive, deque));

    El siguiente ejemplo es de JdepsTask.java:

    Optional req = options.requires.stream()
    	  .filter(mn -> !modules.containsKey(mn))
    	  .findFirst();
    	if (req.isPresent()) {
    	  throw new BadArgs("err.module.not.found", req.get());
    	}

    que podría ser escrito como:

    options.requires.stream()
      .filter(mn -> !modules.containsKey(mn))
      .findFirst()
      .ifPresent(s -> throw new BadArgs("err.module.not.found", s));

    El siguiente es un fragmento de código en el que el programador no comprobó que el Optional contenía un valor utilizando isPresent(). Si, por alguna razón, la subList() está vacía, el método reduce() devolverá un Optional vacío y por tanto get() lanzará un NoSuchElementException. Los IDEs deberían darse cuenta y avisarnos. Pero además, tenemos una forma más breve y probablemente más eficiente de fusionar Strings con String.join(). El código procede esta vez de JShellTool.java:

    String hist = replayableHistory
    	  .subList(first + 1, replayableHistory.size())
    	  .stream()
    	  .reduce( (a, b) -> a + RECORD_SEPARATOR + b)
    	  .get();

    que podría escribirse así:

    String hist = String.join(RECORD_SEPARATOR,
      replayableHistory.subList(first+1, replayableHistory.size()))

    Otro pequeño fragmento de código extraido de Resolver.java

    if (mref.location().isPresent())
    	    trace("  (%s)", mref.location().get());

    que podía escribirse como

    mref.location().ifPresent(loc -> trace("  (%s)", loc);

    El siguiente es un poco diferente. En este caso, comprueban si un determinado valor no está presente. En lugar de hacerlo de esta forma, podemos filtrar primero y confirmar si existe algún valor que cumpla nuestros requisitos. Si no, o nunca hubo valor, lanzamos una excepción utilizando Optional.orElseThrow(). Este ejemplo es de la nueva clase Layer de Java 9 en el paquete Reflection. Layer.java:

    Optional<Configuration> oparent = cf.parent();
    if (!oparent.isPresent() || oparent.get() != this.configuration()) {
      throw new IllegalArgumentException(
        "Parent of configuration != configuration of this Layer");

    que podría escribirse como

    cf.parent()
      .filter(cfg -> cfg == this.configuration())
      .orElseThrow(() -> new IllegalArgumentException(
        "Parent of configuration != configuration of this Layer"));

    Creo que estos ejemplos son muy interesantes y de utilidad para comprender cómo debería utilizarse Optional. En cualquier caso, qué sucede si nuestra manera habitual es un pelín distinta, como por ejemplo del estilo a:

    Optional<BigInteger> prime = findPrime();
    if (prime.isPresent()) {
      System.out.println("Prime is " + prime.get());
    } else {
      System.out.println("Prime not found");
    }

    Le planteé esta pregunta a Stuart Marks y me respondió con una solución elegante que utilizaba map() y orElse(). En nuestro ejemplo, map transforma los BigInteger en String. Pero, si el Optional está vacío, simplemente retornará un Optional<String>. Podemos incluso devlover un valor por defecto si está vacío ("Prime not found") o podemos lanzar una excepcion. Éste es el código:

    System.out.println(
      findPrime()
        .map(p -> "Prime is " + p)
        .orElse("Prime not found"));

    Le sugerí a Stuart que quizá también deberíamos añadir Optional.ifPresentElse(Consumer,Runnable). Resulta que ya lo incluirá Java 9, junto con otros nuevos métodos que convierten Optional en un composite:

    public void ifPresentOrElse(Consumer action,
                                Runnable emptyAction);
    public Optional or(Supplier<Optional> supplier);
    public Stream stream();

    Os recomiendo fervientemente utilizar Optional en vuestro código, porque reduce la posibilidad de un problema muy común: NullPointerException. Y, si te tienta utilizar get(), utiliza los consejos de este artículo como guía de buenas prácticas.

    Un Saludo

    Heinz.

    Integración de Angular 2 con Polymer

    $
    0
    0

    En este tutorial vamos a ver como la integración entre Angular 2 y Polymer facilita la creación de aplicaciones multiplataforma y multidispositivo empresariales.

    Índice de contenidos


    1. Entorno

    Este tutorial está escrito usando el siguiente entorno:

    • Hardware: Portátil Mac Book Pro 15″ (2,3 Ghz Intel Core i7, 16 GB DDR3)
    • Sistema Operativo: Mac OS X El Capitan
    • Atom 1.7.3
    • @angular 2.0.0-rc.1
    • Polymer 1.4.0

    2. Introducción

    Cuando te planteas (o te plantean) hacer una aplicación del lado del cliente, que pueda aprovechar las ventajas de los web components, es muy fácil que actualmente valores dos tecnologías por encima del resto: Angular 2 y Polymer.

    Si has leído algo sobre ellas te habrás dado cuenta de que las dos son de Google y que hay una guerra fraticida entre ellas por ver quien se queda con el pastel completo del desarrollo de aplicaciones del lado del cliente.

    En mi opinión, es una guerra que las dos tienen completamente pérdida porque cada una es la mejor en su campo: Angular 2 como framework (o plataforma, como la quieren vender después de la última ng-conf) y Polymer como librería para crear componentes web reutilizables, también de forma rápida y sencilla.

    Polymer está conceptualmente por encima de Angular 2, y es la tecnología que dentro de una empresa deberían utilizar los arquitectos y diseñadores para crear librerías de componentes que poder ofrecer a sus desarrolladores, para construir cualquier tipo de aplicación web. Estos roles deberían centrarse en aportar riqueza visual y reutilidad a los componentes por lo que una de las premisas fundamentales es que deben abstraerse del negocio, trabajando solo con estructuras de datos y aplicando patrones de diseño para favorecer la reutilización de los componentes, creando etiquetas HTML que los desarrolladores puedan utilizar fácilmente para aportar riqueza visual a las aplicaciones manteniendo un estilo corporativo único. Por contra, conceptualmente es mucho más costoso (no digo que no se pueda) crear una aplicación completa donde hay que intercomunicar todos los componentes y manejar conceptos de framework como el routing de una manera mucho menos clara y mantenible que con Angular 2.

    Angular 2 como framework está mucho más orientado a desarrolladores que tienen que dar una respuesta casi inmediata a los requisitos de negocio. Con Angular 2 es muy rápido crear el prototipo de una aplicación pero no tiene ninguna característica que favorezca la reutilización de componentes y la riqueza visual de la aplicación. Por otro lado proporciona inyección de dependencias que facilita el testing general de la aplicación y maneja el routing, el data binding y las conexiones con servidores a través de HTTP de una manera mucho más sencilla que Polymer.

    Entonces el caballo ganador es dejar que los desarrolladores creen las aplicaciones sin perder tiempo en el aspecto visual y centrándose en el aspecto funcional dando solución a los requisitos de negocio; mientras los arquitectos y diseñadores facilitan componentes reutilizables que permitan a los desarrolladores, simplemente con la utilización de ciertas etiquetas, dar el estilo visual que la empresa requiera.

    Vamos a ver como podemos hacer esto en la práctica.

    Nota: aquí tenéis un enlace con una demo en directo


    3. Nos ponemos la gorra de arquitectos y diseñadores

    Como arquitectos y diseñadores solo tenemos que preocuparnos por la reutilización y el diseño visual, que no es poco. Así que tenemos que aprender a fondo el funcionamiento de Polymer.

    Si recurrimos a la documentación oficial nos damos cuenta de que está completamente orientada a la creación de aplicaciones, incluso te facilitan una herramienta que te crea una aplicación de forma rápida, pero a poco que entras en ver como está configurada te das cuenta de que no entiendes ni la mitad de las cosas que hace.

    En mi opinión esto es un error ya que lo que deberían facilitar es una herramienta orientada a la creación de librerías de componentes reutilizables; es por eso que me decidí a crear un generador de Yeoman con lo básico para la creación de una librería de componentes reutilizables con Polymer, que podéis instalar de esta forma:

    $> npm install -g generator-polymer-lib
    $> mkdir nombre-empresa-lib && cd nombre-empresa-lib
    $> yo polymer-lib

    Esto os va crear toda la configuración necesaria y un par de componentes de ejemplo, que podréis visualizar ejecutando:

    $> npm run live

    La estructuración del proyecto es muy sencilla, todos los componentes tanto propios como de Polymer que queramos compartir con nuestros desarrolladores están alojados en el fichero lib/lib.html y en el fichero index.html tendremos el banco de pruebas con la documentación de cada uno de los componentes. El proyecto está gestionado con Bower que es el gestor de dependencias que utiliza Polymer.

    Ahora para poder compartir la librería con nuestros desarrolladores necesitamos hacerlo a través de un repositorio npm privado, yo aconsejo utilizar la última versión de Nexus OSS que nos permite tener rápidamente un repositorio de npm privado y corporativo; también tiene repositorios para Bower, NuGet e incluso Docker.


    4. Nos ponemos la gorra de desarrolladores

    Como desarrolladores nos gustaría poder centrarnos en la lógica de la aplicación encomendada y no tener que pasar horas moviendo arriba y abajo el CSS porque salvo honrosas excepciones un desarrollador no tiene mucho gusto estético.

    Si vamos a la documentación oficial de Angular 2, vemos que existe ya una herramienta para la creación de aplicaciones, angular-cli , que a través de comandos nos permite ir creando la aplicación. La idea es buena pero a mi personalmente el hecho de que la hayan “copiado” del ember-cli y que tenga que utilizar BrocroliJS como automatizador de tareas teniendo otras soluciones ya probadas como Grunt, Gulp, Webpack o JSPM, me echa un poco hacia atrás a la hora de aconsejar su utilización.

    Es por ello que he creado otro generador de Yeoman que te permite arrancar tu proyecto Angular 2 en pocos minutos con la ayuda de JSPM.

    $> npm install -g generator-ng2
    $> mkdir nombre-proyecto && cd nombre-proyecto
    $> yo ng2

    Cuando finaliza el proceso de instalación tienes a tu disposición los siguientes comandos:

    • npm run live: para arrancar la aplicación en el navegador que tengas configurado por defecto.
    • npm run test: para ejecutar la batería de tests con Karma y obtener los informes de cobertura con Istanbul.
    • npm run compile: para generar los ficheros transpilados necesarios para la ejecución de los tests y la corrección de errores en tiempo de compilación. Si usas Atom está tarea ya la realiza el editor, aunque a veces, sobre todo cuando se renombran ficheros, no viene mal ejecutarla para volver a generar “limpio” el contenido de la carpeta “build”.
    • npm run pro: para generar un único bundle.js preparado para poner en producción.

    Ahora lo que queremos conseguir es poder integrar con la librería corporativa que se encuentra en un repositorio privado. Con JSPM es tan sencillo como ejecutar:

    $> jspm install npm:nombre-empresa-lib

    Nota: nos tenemos que asegurar de tener la referencia a ese repositorio privado correctamente configurada en el fichero ~/.npmrc

    De esta forma ya tendremos la librería entre nuestras dependencias de proyecto, ahora para poder hacer uso de ella, vamos a referenciar el fichero lib.html dentro del archivo “bootstrap.ts”, quedando de esta forma:

    import 'nombre-empresa-lib/lib/lib.html!'
    import 'reflect-metadata'
    import 'es6-shim'
    ...

    Te habrás percatado del signo de exclamación, esto le dice a JSPM que tiene que utilizar el plugin de HTML para poder realizar el import. Para poder hacer uso de este plugin, tenemos que instalarlo junto a la dependencia de vulcanize, que comprime todos los ficheros HTML importados en un solo llamado bundle.html:

    $> jspm install html=github:Hypercubed/systemjs-plugin-html
    $> npm install vulcanize --save-dev

    En este momento ya podemos aplicar cualquier elemento de nuestra librería de arquitectura en cualquier template de nuestra aplicación con Angular 2 e incluso en nuestro index.html (fuera del scope de la aplicación de Angular 2).

    Ahora si volvemos a obtener el bundle de producción con el comando “npm run pro” veremos que ya no solo genera el bundle.js sino que también genera el bundle.html, los cuales tenemos que referenciar en el index.html de producción (el que se encuentra dentro de la carpeta “dist”) junto con el polyfill de webcomponents y la librería de systemjs, quedando de esta forma:

    
      
        
        Ng2
      
      
        
        
        
        
        
      
    

    Ahora ya podemos utilizar esta versión para crear una aplicación móvil híbrida con Cordova, o una aplicación de escritorio con Electron, o subirlo a cualquier servidor web, o crear un portlet de Liferay, o un contenido de WordPress, … lo que se requiera desde negocio.


    5. Conclusiones

    Como veis el binomio Angular 2 + Polymer nos permite llevar una estrategia empresarial adecuada, donde cada rol se dedica a lo suyo y las aplicaciones reflejan la imagen que la empresa quiere transmitir en cada momento.

    Cualquier duda o sugerencia en la zona de comentarios.

    Mi experiencia personal grabando eventos tecnológicos

    $
    0
    0

    Soy Leti y trabajo en Autentia desde hace dos años ya. Lo que vais a leer a continuación no es un tutorial sino mi experiencia personal grabando eventos tecnológicos que hoy quiero compartir con vosotros.

    Muchos de los que leáis esto conoceréis la empresa pero los que no… Autentia es una empresa que ayuda, entre otras cosas, a grandes organizaciones a construir nuevas arquitecturas y mejorar su desarrollo de software. En la página web www.autentia.com podéis encontrar toda la información.

    Autentia Media se crea a partir del año 2014 con el fin de contribuir a la Comunidad de desarrolladores compartiendo conocimiento de forma gratuita, con la grabación, edición y difusión de distintas charlas de contenido técnico.

    Una idea muy buena, ¡porque ahora yo tengo trabajo! La tecnología de las cámaras de vídeo todavía no es tan avanzada como para poder controlarlas de forma remota desde el sofá de casa con una app. Y si lo es, en mi empresa es mejor que de momento no se enteren o no piensen en desarrollar una app como esa… 😉

    Cuando subimos un vídeo al canal de Youtube sólo se ve el resultado final pero, ¿cómo ha sido todo el proceso hasta conseguir ese resultado?. Nosotras planificamos siempre cada evento con antelación, ahora somos seis chicas en el equipo de Comunicación y no, chicos, cuando nos veis reunidas en una sala no estamos hablando de los bolsos y zapatos que se llevan en la nueva temporada sino de cómo vamos a organizar la grabación, qué contenidos vamos a grabar y cómo va a ser la campaña de Marketing correspondiente en las redes sociales.

    El día previo al evento es como ir al gimnasio a levantar pesas, tenemos que preparar todo el equipo audiovisual y el merchandising y cargarlo en el coche. Y a veces no es poco.

    Captura de pantalla 2016-05-24 a las 9.23.08

    Y luego descargar todo allí… ¡Nos dimos de baja todas en el gimnasio! Si lo pensáis sale más rentable, aquí no tenemos que pagar la cuota mensual sino que la cobramos.

    Nuestro material audiovisual se compone de tres cámaras HD Canon XA (series 10, 20 y 25), una GoPro Hero 3, dos aparatos de streaming ViDiU TERADEK, tres juegos de micrófonos inalámbricos SENNHEISER EW 100, dos pantallas LED de 44kw y otras dos de 55kw, una antorcha LED, una cámara de fotos NIKON D90, estabilizador, reflectores, croma, walkie talkies, una cabina insonorizada DemVox para grabaciones de estudio… ¡Muy completo!

    Os voy a hablar del aparato para hacer streaming. Yo siempre había visto en otros eventos equipos de streaming que eran un poco aparatosos y, cuando me enseñó mi jefe ésta maravilla no podía creerlo. Casi cabe en una mano.

    IMG_0387

    Se pone encima de la cámara y no ocupa nada. Puedes realizar streaming tanto por wifi como por cable, únicamente necesitas un buen ancho de banda y un cable HDMI que se conecta del aparato a la cámara para la señal de vídeo. Te da la posibilidad de elegir distintas plataformas para retransmitir. Nosotros siempre lo hacemos a través de Youtube.

    Es cierto que alguna vez hemos tenido algún problema con la conexión el día del evento, a pesar de haber realizado la prueba correspondiente días previos, pero si el sitio tiene buen ancho de banda, el streaming está asegurado. En unos pocos pasos estás en el aire. Si os interesa aquí tenéis su web: Vidiu Teradek

    En los eventos siempre solemos grabar un par de tracks, hacemos entrevistas a los ponentes y a la organización, además de un vídeo resumen. Y hay ocasiones en las que plantamos nuestro stand de Autentia para recordarle a los asistentes que Autentia es una #EmpresaDiferente.

    ¿Qué stand habéis visto vosotros que te ponga el juego de la rana o una canasta de basket y te dé premios por encestar? El de Autentia. ¿Y qué stand habéis visto que os haga chapas personalizadas o que tenga las mayores chapas frikis que podáis imaginar? El de Autentia. ¿Y qué stand habéis visto que te proponga hacer una gymkana y te llene de regalos de merchandising? El de Autentia también.

    Cuando hay stand es un no parar. Yo siempre estoy en cámara y muchas veces si no hemos podido ir todo el equipo a cubrir el evento, tengo que ir corriendo de un track a otro y me acuerdo de la compañera que no ha podido venir y de toda su familia, aunque paso por el stand y veo al resto de chicas con una fila más larga que la de Doña Manolita en época de Navidad y, entonces, hago un sprint porque quiero volver de vuelta a mi track. Menos mal que tenemos los walkies para comunicarnos entre nosotras si no estamos en la misma sala.

    Captura de pantalla 2016-05-24 a las 9.23.21

    Hay eventos más cortos y eventos más largos en los que, afortunadamente, nos dan de comer y tenemos descansos para café y bollos, aunque estos los vemos más bien de lejos porque aprovechamos esos ratitos para hacer las entrevistas a ponentes que nos faltan y que amablemente nos ceden cinco minutillos de su café. Algunos organizadores nos dan la sorpresa cuando nos traen al set una bandeja que han preparado aparte para “las chicas de Autentia”. ¡Qué majos!. Aunque algunas veces nos toque comer de bocata y no de selección de ibéricos y queso, dorada a la espalda o brochetas de solomillo ibérico con queso de cabra y salsa de membrillo, por ejemplo, después de estar todo el día grabando, no podemos quejarnos, porque como dijo una vez un Director de Cine con el que trabajé… “¿Aquí se viene a rodar o a engordar?”

    Me falta hablaros de la parte de edición. Yo uso Final Cut Pro X. Antes de venir aquí yo era de Avid sobre todo y un poco de Premiere. Y después de dos años trabajando cada día con este programa, no recomiendo otro. Me encanta y he aprendido mucho.

    Pero vamos al grano… Lo peor después de un evento no es la edición, es visionar todo el material para nombrar, etiquetar y guardar únicamente lo necesario y descartar lo que no sirve. Si algún día os veis en ésta situación y podéis pedir un becario para que os haga el trabajo sucio, no lo dudéis. Mi calidad de vida se ha triplicado desde que yo no renombro archivos ;-). Es broma, la verdad que agradezco mucho tener una persona que me ayude con todo el trabajo de vídeo.

    Como he dicho antes, grabamos y por tanto editamos charlas, entrevistas y vídeo resumen.

    Para las charlas, les pido a los ponentes que me envíen la presentación en formato PDF o PPTX porque son los formatos que admite FCP y voy haciendo cortes en cada slide para luego ir sincronizándolas con el momento de cambio de slide en el vídeo. Incluyo la plantilla según qué evento sea, con los logos y el rótulo correspondiente y unas imágenes finales con los patrocinadores y colaboradores. Editar una charla de unos 45 min aprox me lleva unos 30-40 min.. Luego está el famoso render, que algunos días tarda cinco minutos y otros necesito unos 20 min para que exporte el vídeo.

    Captura de pantalla 2016-05-24 a las 11.32.06

    Si vamos a eventos donde hay varias salas con charlas y cada una suele tener a lo largo del día entre ocho y diez, y cada vez los cubrimos de manera más continuada, imaginad las horas de edición sumándole a ésto todas las entrevistas que hacemos y el vídeo resumen. Pero si usáis Final Cut Pro X no os va a importar estar 100 horas editando 😉

    El mundo de los eventos audiovisuales mola y si le echas un poco de morro y te acercas a hablar con los otros técnicos que hay puedes aprender mucho. Además no todo es siempre trabajo, ¡también hay momentos para divertirse un poco!

    Captura de pantalla 2016-05-24 a las 9.23.45

    En Autentia todos nos lo pasamos genial haciendo lo que nos gusta.

    Captura de pantalla 2016-05-24 a las 9.23.55

    ElasticKube, contenedor para el manejo empresarial de Kubernetes

    $
    0
    0

    En este tutorial veremos la interfaz gráfica que ofrece ElasticBox para Kubernetes, comentando sus características principales.

    Índice de contenidos


    1. Introducción

    La interfaz que ofrece Kubernetes por defecto puede dejar mucho que desear, por lo que los chicos de ElasticBox nos traen ElasticKube, una interfaz gráfica para Kubernetes orientada a un manejo empresarial.

    Este tutorial supone que ya tienes kubernetes instalado previamente. Si no has utilizado antes Kubernetes te recomiendo que leas el tutorial de instalación de Kubernetes en Ubuntu para la instalación y el de Primeros pasos con Kubernetes para saber los principios básicos del mismo.


    2. Entorno

    El tutorial está escrito usando el siguiente entorno:

    • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core I7, 16GB DDR3).
    • Sistema Operativo: Mac OS El Capitán 10.11.2
    • Vagrant 1.8.1
    • Ansible 2.0.1.0
    • Kubernetes 1.2.0

    3. Características de ElasticKube

    ElasticKube trae las siguientes características:

    • Autenticación: Esta interfaz nos permite tener un control de acceso para Kubernetes, permitiendo establecer permisos y visibilidad sobre el cluster a usuarios específicos.
    • Catálogo de plantillas: ElasticKube nos ofrece un conjunto de plantillas de recursos comunes para facilitarnos el despliegue de nuestros servicios.
    • Reportes en tiempo real: Nos permite monitorizar las actividades que se realizan en los contenedores.
    • Colaboración: Permite que los desarrolladores puedan definir, desplegar y administrar aplicaciones y servicios en producción.
    • Rolling Updates: La trazabilidad y control sobre la aplicación nos permiten entregar valor constantemente.
    • Administración: Desde la interfaz se pueden desplegar servicios, manejar usuarios, namespaces, plantillas y recursos.

    4. Instalación de ElasticKube

    La instalación de ElasticKube es bastante sencilla. Basta con ejecutar el siguiente comando:

    Shell
    curl -s https://elastickube.com | bash

    Para que este comando funcione sin problemas debemos asegurarnos de:

    • Tener el comando kubectl disponible desde el path. Para hacer esto, basta con añadir en el fichero .bashrc la línea:
      export PATH=$PATH:/opt/kubernetes/cluster/ubuntu/binaries
    • Tener disponible el namespace kube-system. En Ubuntu no se crea el namespace cuando instalas Kubernetes, así que tienes que hacerlo de forma manual. En el tutorial de primeros pasos con Kubernetes explico como se crea.

    Una vez tengamos los prerrequisitos, instalamos ElasticKube ejecutando el comando anterior en nuestra shell.

    Ahora comprobamos a qué puerto nos mapea el servicio para que este sea accesible desde fuera del cluster. Para ello, ejecutamos el siguiente comando:

    shell
    kubectl --namespace=kube-system describe svc elastickube-server

    El puerto que está en el apartado NodePort es el que mapeamos en nuestro fichero Vagrantfile. Actualizamos nuestra máquina virtual con la configuración recién añadida usando el comando vagrant reload.

    Accedemos a ElasticKube desde el navegador con el puerto mapeado. En mi caso: localhost:30711

    Comprobamos que al acceder nos pide crear una cuenta de administración y procedemos a crearla rellenando los campos que nos piden:

    Tras realizar la creación de la cuenta administrador, ya tenemos creado nuestro primer usuario. Tras esto se nos muestra el panel general de la interfaz gráfica.


    5. Recorrido del panel de control de ElasticKube

    Hablemos de los elementos que componen la página principal, comenzando por la barra superior:


    5.1. Instances

    Aquí podemos ver de forma gráfica todas las instancias que tenemos actualmente en kubernetes. Dentro podemos filtrarlas para ver solo los Pods, Los Replication Controllers o los Services, así como utilizar los charts para crear nuevos elementos.


    5.2. Charts

    En esta pestaña tenemos todos los charts de los que nos provee la ElasticKube para realizar la creación sencilla de un servicio o replication controller sin json ni ficheros yaml; todo desde la propia interfaz 😀


    5.3. Admin

    Se divide en varias secciones y por defecto se muestra la configuración actual, que está compuesta de:

    • Cambiar tu hostname.
    • Ver los administradores del cluster.
    • Elegir el modo de autenticación en el sistema.
    • Cambiar el repositorio de charts para la creación de pods, servicios y replication controllers.
    • Definir una cuenta desde la que se envien correos electrónicos.

    En el apartado Users tenemos información más detallada de los usuarios como la hora de creación, y podemos invitar nuevos usuarios activando el envio de emails.

    En el apartado namespace tenemos el listado de los mismos, así como la posibilidad de crear nuevos. Actualmente solo hay dos namespaces: Default (que se crea por defecto cuando instalas Kubernetes) y Kube-system, que es el namespace en el que se instala ElasticKube.

    En la pestaña charts tenemos un listado de los charts que vimos en la otra pantalla en un listado.

    En el apartado instances dentro de la adminsitración tenemos un listado de todas las instancias dentro del cluster independientemente del namespace.


    6. Monitorización

    Una de las cosas que más me gustan de ElasticKube es la información que nos ofrece de las instancias, mostrándonos los logs de las mismas, así como información detallada al respecto.

    Por ejemplo: Si nos vamos al listado de instancias y pinchamos en el pod de elastickube-server que en mi caso va seguido del UID xjqxd accederemos a una pantalla en la que se muestra información de cada contenedor.

    En esta pantalla vemos que este pod está compuesto por 4 contenedores, y si vamos a la pestaña containers podemos ver el estado de los mismos, así como los logs que está dejando cada uno de ellos. Esto está realmente bien para ver de forma centralizada el estado de tu aplicación.


    7. Conclusiones

    Podemos ver como esta interfaz puede ser de gran ayuda si vas a utilizar kubernetes de forma seria, ya que te ofrece mucha comodidad para ver las instancias de forma muy gráfica, a parte de que la autenticación del dashboard te permite poner una capa de seguridad entre el público y la gestión de la infraestructura de tu aplicación en la nube.

    En el video de introducción de la página principal muestran los casos de uso de ElasticKube con muchos más datos 😉


    8. Referencias

    Viewing all 989 articles
    Browse latest View live