Quantcast
Channel: Planeta Código
Viewing all 2715 articles
Browse latest View live

Variable not found: MVP*10

$
0
0
MVP*10Es una alegría, un honor y un auténtico lujo poder compartir con todos vosotros que, por décimo año consecutivo, he sido reconocido por Microsoft como Most Valuable Professional (MVP) en la categoría Developer technologies.

Cuando fui nombrado MVP por primera vez, jamás pensé que este privilegio fuera a durar tanto. Un par de añitos quizás, lo suficiente como para poder visitar Redmond alguna vez y poder contar a los nietos que "yo estuve allí" ;) Pero diez años más tarde, aquí estamos todavía, con las mismas ganas y entusiasmo de poder seguir formando parte de este club de amigos a los que sigo y admiro tanto. ¡Impresionante!

Esta vez la notificación me ha pillado en una videollamada con el gran Jorge Turrado, amigo y compañero MVP, que me ha sacado del fragor de la batalla diaria para inyectarme la dosis de adrenalina que supone darse cuenta de pronto de que hoy era el día de nombramientos y todavía no me había llegado el famoso email :D Por cierto, Jorge ¡felicidades, por tu merecida renovación!

Muchas gracias a todos los que hacéis posible que me lleve estos momentazos. A los amigos y amigas de blog, porque sin su apoyo no habría sido posible llegar hasta tan lejos; al equipo del programa MVP, por su incansable labor y exquisito trato con todos, y, por supuesto, a mis tres niñas, por consentírmelo todo :)

Finalmente, no me gustaría cerrar este post sin enviar mi más sincera enhorabuena a los nuevos MVP; disfrutad de ese momento tan bonito. Si renováis en el futuro os llevaréis grandes alegrías, pero la primera vez nunca se olvida. Y también enviar un fuerte abrazo (con distancia social, eso sí ;)) a los que repetís galardón: como ya he dicho alguna vez, lo difícil no es sólo llegar, sino también mantenerse.

Publicado en Variable not found.


Blog Bitix: Tareas programadas de forma periódica con Quartz y Spring en Java

$
0
0

Java

En las aplicaciones web basadas en el protocolo HTTP la petición al servidor es el desencadenante de la ejecución de la acción que le da respuesta. Algunas acciones no dependen de la solicitud de un usuario o de la recepción de un mensaje sino que se han de ejecutar de forma periódica cada cierto tiempo o de forma planificada en tiempos determinados. Por ejemplo, una tarea que necesite ejecutarse todos los días a las 3 de la mañana o cada 5 minutos.

Quartz es una de las librerías en la plataforma Java que proporciona la funcionalidad de planificador de tareas, permite ejecutar tareas de forma periódica o de forma planificada en determinados tiempos.

Spring también integra una solución sencilla para ejecutar tareas de forma programada disponible para las aplicaciones que usen Spring Boot sin necesidad de dependencias adicionales.

El propio JDK desde la versión 5 incorpora varias clases para ejecutar tareas programadas sin ninguna dependencia en elproyecto ni siquera de Spring.

La programación de las tareas también se puede realizar a nivel de sistema operativo. En GNU/Linux con la utilidad cron y con systemd se puede programar tareas. Sin embargo, realizar la programación a nivel de sistema operativo se crean nuevos procesos y la configuración está externalizada de la aplicación. Utilizar Quartz, Spring o las clases del JDK tiene la ventaja de que la configuración de la programación de las tareas está más en el ámbito de la programación que en la de configuración de sistemas, el primer caso los cambios los hace el programador, en el segundo los hace la persona a cargo de los sistemas.

Tareas programadas con Quartz y Spring Boot

Entre las muchas itegraciones que ofrece Spring una de ellas es para Quartz. Las clases importantes que ofrece Quartz son:

  • Job: es la tarea a ejecutar.
  • JobDetail: es una instancia de una tarea.
  • Trigger: es el disparador que determina los momentos de ejecución de los Jobs, cuando una tarea se ha de ejecutar se crea una instancia de JobDetail de la tarea a ejecutar.
  • JobListener: recibe eventos sobre las ejecuciones de las tareas.
  • JobBuilder: una clase que implementa el patrón factoría que facilita la definición de las clases anteriores.
  • JobStore: una clase que permite guardar las definiciones de las tareas en la base de datos.
  • JobDetailFactoryBean, SimpleTriggerFactoryBean: clases alternativas proporcionadas por Spring para la configuración de las tareas, por ejemplo para añadir listeners. Los listeners permiten recibir notificaciones de los eventos de ejecución de las tareas, por ejemplo cuando una tarea se va a ejecutar y cuando se ha ejecutado. En el ejemplo e listeners emite unas trazas en la salida.

Esta es la configuración para definir los jobs con Spring, los triggers que disparan los jobs cada cierto tiempo con una expresión cron y los listeners que reciben los eventos de ejecución de los jobs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
packageio.github.picodotdec.blogbitix.quartzspring;importorg.apache.logging.log4j.LogManager;importorg.apache.logging.log4j.Logger;importorg.quartz.CronScheduleBuilder;importorg.quartz.JobDetail;importorg.quartz.JobListener;importorg.quartz.SchedulerException;importorg.quartz.SimpleScheduleBuilder;importorg.quartz.Trigger;importorg.quartz.TriggerBuilder;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.boot.CommandLineRunner;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;importorg.springframework.boot.context.event.ApplicationStartedEvent;importorg.springframework.context.ApplicationContext;importorg.springframework.context.ApplicationEvent;importorg.springframework.context.ApplicationListener;importorg.springframework.context.annotation.Bean;importorg.quartz.JobBuilder;importorg.springframework.context.event.ContextRefreshedEvent;importorg.springframework.context.event.ContextStartedEvent;importorg.springframework.scheduling.annotation.EnableScheduling;importjava.util.concurrent.ExecutionException;importjava.util.concurrent.Executors;importjava.util.concurrent.ScheduledExecutorService;importjava.util.concurrent.TimeUnit;@SpringBootApplication@EnableSchedulingpublicclassMainimplementsApplicationListener{    privatestaticfinalLoggerlogger=LogManager.getLogger(Main.class);    ...    @Bean(name="QuartzJob")    publicJobDetailquartzJob(){        returnJobBuilder.newJob(QuartzJob.class)                .withIdentity("QuartzJob","QuartzJobs")                .storeDurably()                .build();    }    @Bean    publicTriggerquartzTrigger(@Qualifier("QuartzJob")JobDetailjob){        returnTriggerBuilder.newTrigger().forJob(job)            .withIdentity("QuartzTrigger","QuartzJobs")            .withDescription("Quartz trigger")            .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(10))            .build();    }    @Bean    publicTriggercronQuartzTrigger(@Qualifier("QuartzJob")JobDetailjob){        returnTriggerBuilder.newTrigger().forJob(job)                .withIdentity("CronQuartzTrigger","QuartzJobs")                .withDescription("Cron Quartz trigger")                .withSchedule(CronScheduleBuilder.cronSchedule("0 * * * * ?"))                .build();    }    @Bean(name="QuartzJobListener")    publicJobListenerquartzListener(){        returnnewQuartzJobListener();    }    @Bean    publicSchedulerFactoryBeanCustomizerschedulerConfiguration(@Qualifier("QuartzJobListener")JobListenerlistener){        returnbean->{            bean.setGlobalJobListeners(listener);        };    }    ...    publicstaticvoidmain(String[]args){        SpringApplication.run(Main.class,args);    }}
Main-1.java

Para usar Quartz con Spring Boot hay que incluir su dependencia en el archivo de construcción del proyecto.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
plugins{id'java'id'application'}group='io.github.picodotdec.blogbitix.quartzspring'version='1.0'java{    sourceCompatibility=JavaVersion.VERSION_11}application{    mainClass='io.github.picodotdec.blogbitix.quartzspring.Main'}repositories{    mavenCentral()}dependencies{    defexcludeSpringBootStarterLogging={exclude(group:'org.springframework.boot',module:'spring-boot-starter-logging')}    implementationplatform('org.springframework.boot:spring-boot-dependencies:2.3.1.RELEASE')    implementation('org.springframework.boot:spring-boot-starter',excludeSpringBootStarterLogging)    implementation('org.springframework.boot:spring-boot-starter-quartz',excludeSpringBootStarterLogging)    implementation'org.springframework.boot:spring-boot-starter-log4j2'    implementation'org.apache.logging.log4j:log4j-api:2.13.3'    implementation'org.apache.logging.log4j:log4j:2.13.3'    implementation'org.apache.logging.log4j:log4j-core:2.13.3'    runtimeOnly'com.fasterxml.jackson.core:jackson-databind:2.11.1'    runtimeOnly'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.1'}
build.gradle

Con Quartz no existe la opción de planificar una tarea pasado un tiempo desde la última ejecución. Solo se pueden planificar los Jobs en tiempos regulares, esto provoca que si la tarea tarda en ejecutarse más que el intervalo entre ejecuciones haya dos instancias de la tarea en ejecución de forma paralela. Hay dos opciones para evitar ejecuciones paralelas: una de ellas es utilizar la anotación DisallowConcurrentExecution, la otra forma es planificar otra ejecución de la tarea cuando la anterior ejecución haya terminando proporcionando una implementación de JobListener.

En las clases de las tareas se pueden inyectar beans de Spring con la anotación @Autowired.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
packageio.github.picodotdec.blogbitix.quartzspring;importorg.apache.logging.log4j.LogManager;importorg.apache.logging.log4j.Logger;importorg.quartz.Job;importorg.quartz.JobExecutionContext;importorg.quartz.JobExecutionException;publicclassQuartzJobimplementsJob{    privatestaticfinalLoggerlogger=LogManager.getLogger(QuartzJob.class);    @Override    publicvoidexecute(JobExecutionContextcontext)throwsJobExecutionException{        logger.info("Job: QuartzJob");    }}
QuartzJob.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
packageio.github.picodotdec.blogbitix.quartzspring;importorg.apache.logging.log4j.LogManager;importorg.apache.logging.log4j.Logger;importorg.quartz.JobExecutionContext;importorg.quartz.JobExecutionException;importorg.quartz.JobListener;publicclassQuartzJobListenerimplementsJobListener{    privatestaticfinalLoggerlogger=LogManager.getLogger(QuartzJobListener.class);    @Override    publicStringgetName(){        return"QuartzJobListener";    }    @Override    publicvoidjobToBeExecuted(JobExecutionContextcontext){        logger.info("QuartzJobListener: jobToBeExecuted. Job: {}, Trigger: {}",context.getJobDetail().getKey().getName(),context.getTrigger().getKey().getName());    }    @Override    publicvoidjobExecutionVetoed(JobExecutionContextcontext){        logger.info("QuartzJobListener: jobExecutionVetoed. Job: {}, Trigger: {}",context.getJobDetail().getKey().getName(),context.getTrigger().getKey().getName());    }    @Override    publicvoidjobWasExecuted(JobExecutionContextcontext,JobExecutionExceptionjobException){        logger.info("QuartzJobListener: jobWasExecuted. Job: {}, Trigger: {}",context.getJobDetail().getKey().getName(),context.getTrigger().getKey().getName());    }}
QuartzJobListener.java

Tareas programadas con Spring

Las tareas programadas con Spring son una opción sencilla, basta con anotar un método con la anotación @Scheduled e indicar los parámetros de la anotación el mecanismo que dispara la tarea y los periodos de tiempo. Las planificaciones pueden ser:

  • initialDelay: las tareas se ejecutan con un retraso desde el inicio de la aplicación.
  • fixedRate: ejecución cierto tiempo fijo e independiente de la duración de la tarea.
  • fixedDelay: con una diferencia de tiempo desde la última ejecución.
  • cron: con una expresión cron que permite planificar los periodos de ejecución de la tarea.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
packageio.github.picodotdec.blogbitix.quartzspring;importorg.apache.logging.log4j.LogManager;importorg.apache.logging.log4j.Logger;importorg.springframework.scheduling.annotation.Scheduled;importorg.springframework.stereotype.Component;@ComponentpublicclassSpringJobs{    privatestaticfinalLoggerlogger=LogManager.getLogger(SpringJobs.class);    @Scheduled(fixedRate=2000)    publicvoidscheduleJobWithFixedRate(){        logger.info("SpringJob: scheduleJobWithFixedRate");    }    @Scheduled(fixedDelay=2000)    publicvoidscheduleJobWithDelay(){        try{            Thread.sleep(2000);        }catch(Exceptione){            logger.error(e.getMessage(),e);        }        logger.info("SpringJob: scheduleJobWithDelay");    }    @Scheduled(cron="0 * * * * ?")    publicvoidscheduleJobWithCron(){        logger.info("SpringJob: scheduleJobWithCron");    }}
springJobs.java

Tareas programadas con las clases del JDK

Otra tercera forma de ejecutar tareas periódicas es con las clases Executors y ScheduledExecutorService que están disponibles desde la versión 5 de Java. Proporcionan una funcionalidad similar a las tareas programadas de Spring sin la funcionalidad de expresiones cron.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
packageio.github.picodotdec.blogbitix.quartzspring;...@SpringBootApplication@EnableSchedulingpublicclassMainimplementsApplicationListener{    privatestaticfinalLoggerlogger=LogManager.getLogger(Main.class);    @Autowired    privateJavaJobjavaJob;    ...    @Bean    publicJavaJobjavaJob(){        returnnewJavaJob();    }    @Override    publicvoidonApplicationEvent(ApplicationEventevent){        if(eventinstanceofApplicationStartedEvent){            ScheduledExecutorServicescheduler=Executors.newScheduledThreadPool(5);            scheduler.scheduleAtFixedRate(javaJob::jobWithFixedRate,0,2,TimeUnit.SECONDS);            scheduler.scheduleWithFixedDelay(javaJob::jobWithDelay,0,2,TimeUnit.SECONDS);        }    }    publicstaticvoidmain(String[]args){        SpringApplication.run(Main.class,args);    }}
Main-2.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
packageio.github.picodotdec.blogbitix.quartzspring;importorg.apache.logging.log4j.LogManager;importorg.apache.logging.log4j.Logger;publicclassJavaJob{    privatestaticfinalLoggerlogger=LogManager.getLogger(JavaJob.class);    publicvoidjobWithFixedRate(){        logger.info("JavaJob: jobWithFixedRate");    }    publicvoidjobWithDelay(){        try{            Thread.sleep(2000);        }catch(Exceptione){            logger.error(e.getMessage(),e);        }        logger.info("JavaJob: jobWithDelay");    }}
JavaJob.java

Elegir entre usar Quartz, usar Spring o usar las clases del JDK

La ventaja de usar las clases del JDK es que ya están incluidas en el JDK y no se necesita incluir ninguna dependencia en el proyecto. Si se usa Spring tampoco se necesitan dependencias adicionales y además proporciona la funcionalidad de expresiones cron que no tienen las clases del JDK. La desventaja de usar el JDK y Spring está en que no tienen todas las opciones de Quartz como la persistencia en la base de datos, la ejecución de tareas que reciban parámetros a modo de contexto o la característica de los listeners que en Spring habría que implementar con alguna otra solución como usar Guava para publicar y suscribirse a eventos.

Dependiendo de las nacesidades de la aplicación será más adecuado usar las clases del JDK, Spring o Quartz.

Ejemplo de tareas programadas con Quartz, Spring y las clases del JDK

El ejemplo incluye varias tareas definidas con Quartz y con Spring. En las trazas se observan los tiempos de ejecución de cada tarea. La tarea de Quartz tiene dos triggers, uno que se ejecuta cada 10 segundos y otro cada minuto. Los jobs de Spring scheduleJobWithFixedRate se ejecuta cada dos segundos, scheduleJobWithDelay se ejecuta cada dos segundos después de haber terminado la anterior ejecución que como tarda dos segundos en ejecutarse se ejecuta en realidad cada cuatro segundos y finalmente scheduleJobWithCron se ejecuta cada minuto. Las tareas planificadas con las clases del JDK se ejecutan igual que las tareas de Spring cada dos y cada cuatro segundos.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
...
2020-07-03 14:41:00,004  INFO  ...QuartzJobListener QuartzJobListener: jobToBeExecuted. Job: QuartzJob, Trigger: CronQuartzTrigger
2020-07-03 14:41:00,005  INFO          ...QuartzJob Job: QuartzJob
2020-07-03 14:41:00,005  INFO  ...QuartzJobListener QuartzJobListener: jobWasExecuted. Job: QuartzJob, Trigger: CronQuartzTrigger
2020-07-03 14:41:01,069  INFO         ...SpringJobs SpringJob: scheduleJobWithDelay
2020-07-03 14:41:01,069  INFO         ...SpringJobs SpringJob: scheduleJobWithCron
2020-07-03 14:41:01,070  INFO         ...SpringJobs SpringJob: scheduleJobWithFixedRate
2020-07-03 14:41:01,075  INFO            ...JavaJob JavaJob: jobWithDelay
2020-07-03 14:41:01,077  INFO            ...JavaJob JavaJob: jobWithFixedRate
2020-07-03 14:41:03,068  INFO         ...SpringJobs SpringJob: scheduleJobWithFixedRate
2020-07-03 14:41:03,075  INFO            ...JavaJob JavaJob: jobWithFixedRate
2020-07-03 14:41:05,070  INFO         ...SpringJobs SpringJob: scheduleJobWithDelay
2020-07-03 14:41:05,070  INFO         ...SpringJobs SpringJob: scheduleJobWithFixedRate
2020-07-03 14:41:05,074  INFO            ...JavaJob JavaJob: jobWithFixedRate
2020-07-03 14:41:05,077  INFO            ...JavaJob JavaJob: jobWithDelay
2020-07-03 14:41:07,068  INFO         ...SpringJobs SpringJob: scheduleJobWithFixedRate
2020-07-03 14:41:07,074  INFO            ...JavaJob JavaJob: jobWithFixedRate
...
2020-07-03 14:42:00,003  INFO  ...QuartzJobListener QuartzJobListener: jobToBeExecuted. Job: QuartzJob, Trigger: CronQuartzTrigger
2020-07-03 14:42:00,004  INFO          ...QuartzJob Job: QuartzJob
2020-07-03 14:42:00,005  INFO  ...QuartzJobListener QuartzJobListener: jobWasExecuted. Job: QuartzJob, Trigger: CronQuartzTrigger
2020-07-03 14:42:01,074  INFO            ...JavaJob JavaJob: jobWithFixedRate
2020-07-03 14:42:01,085  INFO         ...SpringJobs SpringJob: scheduleJobWithDelay
2020-07-03 14:42:01,086  INFO         ...SpringJobs SpringJob: scheduleJobWithCron
2020-07-03 14:42:01,087  INFO         ...SpringJobs SpringJob: scheduleJobWithFixedRate
...
System.out
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run

Blog Bitix: Las diferencias entre GNU/Linux, BSD y sus distribuciones

$
0
0

Las distribuciones BSD tienen aún menor cuota de uso que las distribuciones GNU/Linux pero al igual que estas tiene una licencia de software libre aún más permisiva que las GNU/Linux y también se basan en la filosofía UNIX. Pero también tienen diferencias importantes en varios aspectos.

BSD

De los sistemas operativos Windows de Microsoft es el más popular en el uso en empresas y en los ordenadores personales con una cuota de uso del 90% prácticamente de monopolio. La siguiente opción más popular a nivel de usuario es macOS de Apple como única opción de sus equipos portátiles y de escritorio, macOS tiene una cuota de uso de un 7%. Con una cota de uso en el escritorio de apenas el el 3% la siguiente opción es alguna de las distribuciones GNU/Linux, tiene una cuota de uso tan pequeña porque prácticamente todos los equipos vendidos en grandes superficies incorporan Windows preinstalado, para usar GNU/Linux son los usuarios los que de forma expresa han de instalar GNU/Linux reemplazando a Windows aunque en los últimos años hay varias marcas que ofrecen GNU/Linux como opción entre ellas Slimbook y Vant. En el lado del servidor GNU/Linux si tiene una mucha mayor cuota de uso por encima incluso de Windows, muchos de los servicios ofrecidos por las compañías más importantes usan alguna distribución de GNU/Linux como sistema operativo. Las distribuciones empresariales GNU/Linux más populares son RHEL de RedHat, Ubuntu de Canonical o SLES de SUSE.

Otra familia de sistemas operativos basados en UNIX al igual que GNU/Linux son las distribuciones BSD. Si la cuota de uso a nivel personal de GNU/Linux es muy reducida la de los sistemas operativos BSD es aún mucho más reducida, no llega al 0,5%, teniendo algo de mayor presencia como sistema de servidores con una comunuidad de usuarios pequeña pero al igual que la de GNU/Linux activa que comparte y colabora en el desarrollo.

Aún estando las distribuciones GNU/Linux y BSD ambas basadas en UNIX e implementar el estándar POSIX que define una interfaz con el sistema operativo para permitir que las aplicaciones sean portables entre sistemas tienen notables diferencias en varios apartados importantes. Las diferencias entre las distribuciones GNU/Linux y BSD están en la licencia que usan, en su modelo de desarrollo, en la ya comentada cuota de uso o en aspectos importantes para los usuarios como el soporte del hardware.

Aunque solo para representar a los proyectos una diferencia está en su mascota, en Linux es Tux, en BSD es Beastie y en GNU es un ñu.

Tux la mascota de LinuxBestie la mascota de BSDGNU la mascota de GNU

Mascotas de Linux, BSD y GNU

Diferencias en el modelo de desarrollo

Lo que conocemos como Linux es simplemente el núcleo del sistema operativo de las distribuciones GNU/Linux. las distribuciones GNU/Linux están compuestas por otra multitud de programas de usuario que forman el sistema operativo completo y lo hacen usable, estos programas son muchos de los programados bajo los proyectos de la fundación GNU. Este es el motivo de que las distribuciones GNU/Linux incluyan los términos de ambos proyectos. Las distribuciones GNU/Linux son una recopilación de programas de terceras partes, todos estos programas GNU y no GNU incluidos en los sistemas GNU/Linux están desarrollados por diferentes programadores ajenos a Linux y los creadores de la distribución. Los creadores de las distribuciones GNU/Linux son los encargados de juntar los diferentes programas de cada fuente y empaquetarlas como un sistema usable, estable y libre de errores.

Las distribuciones BSD son muy diferentes en este aspecto ya que los desarrolladores del núcleo son los mismos que desarrollan los programas de usuario que hacen el sistema operativo completo.

Por otro lado, la arquitectura del sistema base y las aplicaciones de usuario tienen diferentes enfoques. En GNU/Linux hay dos variaciones usar una colección fija de paquetes donde solo se incorporan modificaciones de seguridad o un modelo en constante actualización o rolling release. Ubuntu y Debian son ejemplos de distribuciones de colecciones fijas en los ciclos de vida de las versiones y Arch Linux y Void Linux son ejemplos que están en constante actualización.

Las distribuciones BSD siguen un enfoque en el que el sistema base es fijo pero las aplicaciones de usuario se actualizan con nuevas versiones. El sistema base es pequeño, estable y solo incorpora cambios siguiendo un calendario de publicaciones. Los programas de usuario como Firefox, LibreOffice o el entorno de escritorio utilizan la última versión disponible de cada proyecto. Esto permite a las distribuciones BSD usar las últimas versiones sin comprometer la estabilidad del sistema al haber errores en el núcleo del sistema. Algunas distribuciones GNU/Linux proporcionan un modelo similar a las BSD como Fedora Silverblue con una base estable y aplicaciones de usuario actualizadas.

Esta diferencia en el modelo de desarrollo del sistema base e independiente de las aplicaciones haace que en BSD las aplicaciones se instalen de forma independiente a los paquetes del sistema. Los ports son la colección de paquetes con las aplicaciones instalables en los sistemas BSD.

Diferencias en la licencia

Otro aspecto importante con diferencias está en la licencia del código fuente. Mientras que Linux usa una licencia GNU General Public License o GPL los sistemas BSD usan una de las diferentes licencias BSD que tiene diferentes versiones según su número de clausulas de cuatro a cero en cuyo caso es considerado software de dominio público.Ambas son licencias consideradas como software libre.

La diferencia entre la licencia GPL y BSD está en que la BSD es más permisiva, la licencia GPL impone restricciones a los programas distribuidos requiriendo que el código fuente del software también sea entregado. Por el contrario, la licencia BSD no impone si se solicita el requerimiento a los programas de que el código fuente sea entregado en su forma original del programa compilado en formato binario. En algunos casos la licencia BSD es más atractiva para algunas empresas que pueden usar su software sin necesidad de tener que distribuir el código fuente del software en el que se basan ni sus modificaciones si han hecho alguna lo que les permite mantener su propiedad intelectual oculta a sus competidores.

Las licencias BSD no imponen unos requerimientos éticos y morales, la licencia GPL usada por Linux tiene un mayor activismo para promover y proteger el software libre. En BSD se centran más en el desarrollo que en el activismo del software libre y de código abierto.

En los últimos años el soporte de hardware en Linux ha mejorado notablemente y salvo hardware muy específico o raro lo normal es que todo funcione sin problemas importantes. El soporte de los fabricantes se centra más hacer compatible su hardware con Windows o macOS que en Linux y es mas raro aún que ofrezcan soporte para algún BSD. De modo que el soporte de hardware en los BSD es más limitado.

Distribuciones más importantes BSD

Al igual que en GNU/Linux ha varias distribuciones BSD con algunos propósitos más prioritarios en cada una de ellas. Soportan múltiples arquitecturas de procesadores incluyendo x86, ARM, MIPS, PowerPC y UltraSPARC. Estas son algunas de las distribuciones BSD más conocidas:

  • FreeBSD: es el BSD más popular, con el objetivo de proporcionar alto rendimiento y facilidad de uso. Soporta múltiples plataformas de 32 y 64 bits.
  • OpenBSD: esta distribución está diseñada para la máxima seguridad realizando modificaciones de forma proactiva que otros vendedores no son capaces de hacer e integrando criptografía.
  • NetBSD: uno de sus principios es hacer el sistema operativo altamente portable, valores por defecto seguros, calidad de código, corrección y adhesión a estándares.
  • MidnightBSD: es una distribución BSD orientada al escritorio que incluye las aplicaciones de usuario básicas como navegador, correo electrónico, procesador de textos, juegos y muchos más.

Hay otros sistemas BSD notables:

  • DragonFly BSD: sigue un camino diferente al de las anteriores BSD. Algunas características diferenciadoras son su sistema de archivos de alto rendimiento HAMMER, kernels virtuales, alto rendimiento en múltiples procesadores con SMP con poca contención.
  • Darwin / Mac OS X: los sistema de Apple con Mac OS X están basados en BSD.
  • PlayStation 4: el sistema operativo Orbis de esta consola está basado en FreeBSD 9.

FreeBSDOpenBSDNetBSD

MidnightBSDDragonFly

Varias de las distribuciones más importantes de BSD

La publicación digital BSD Magazine gratuita bajo suscripción se publica de forma mensual con varios artículos técnicos dedicados a las diferentes distribuciones BSD. Permite adentrarse en el mundo de la distribuciones BSD. Más información sobre las distibuciones en la Wikipedia.

Razones por las que usar y no usar una distribución BSD

Entre las razones para usar BSD están:

  • Posibilidad de usar características de Solaris como el avanzado sistema de archivos ZFS o DTrace.
  • La comunidad BSD se centra más en la tecnología que en los aspectos de licencia y políticos del software libre.
  • Estabilidad y sistema bien estructurado.
  • Alto rendimiento y gran desempeño en tareas de red.
  • Menos fragmentación en distribuciones como ocurren en Linux donde algunas no tiene objetivos claros diferenciadores.
  • La licencia BSD.

Algunas de las razones por las que no usar BSD están:

  • Falta de soporte hardware.
  • No hay soporte para suspender y continuar en portátiles.
  • Ausencia en móviles y algunas funcionalidades inexistentes.
  • Más fácil de usa Mac OS X que FreeBSD.
  • ports rotos.
  • Menos información y ayuda en internet que de GNU/Linux.

Distribuciones más importantes de GNU/Linux

Las distribuciones GNU/Linux son más numerosas si bien es cierto que muchas de ellas no tienen un objetivo que las diferencie claramente de otras. Algunas importantes son:

UbuntuopenSUSEDebian

Arch LinuxFedoraelementary OS

He escrito algunos artículos para elegir la mejor distribución GNU/Linux según las necesidades del usuario o uso. Cómo descargar e instalar Ubuntu paso paso desde cero, una de las distribuciones más populares y recomendadas para los usuarios recién llegados a GNU/Linux y que muchos usuarios con experiencia siguen usando. Las aplicaciones del entorno de escritorio GNOME con las que los usuarios interaccionan con la computadora o la distribución Fedora Silverblue con una aproximación diferente a la mayoría de las distribuciones con un sistema inmutable y basado en contenedores que le dota de mayor fiabilidad al realizar actualizaciones del sistema que en ciertos aspectos se parece a las distribuciones BSD al separar el sistema base de las aplicaciones.

Fixed Buffer: ¡¡Renovado como MVP 2020!!

$
0
0
Tiempo de lectura:2minutos
MVP Award

El pasado 1 de julio tuve la suerte de renovar como Microsoft MVP en la categoría ‘Developer Technologies’. Me hubiese gustado publicar algo en el mismo momento más allá de un mensaje en Twitter, pero la situación familiar no me lo ha permitido hasta ahora.

Para mí es todo que me hayan dado este reconocimiento por segunda vez consecutiva ya que me ha permitido conocer y colaborar con grandes profesionales, algunos convertido ya en amigos.

¿Cómo ha sido mi experiencia este año? Pues me encontraba yo hablando con Jose M. Aguilar de VariableNotFound mientras me explicaba las bondades de Blazor, cuando recibí el mail. Poco después de mí, Jose también recibió el mail (¡¡¡felicidades!!!) dando el cierre a un día muy interesante :).

Como no podría ser de otra manera, el mérito no es solo mío. De hecho, creo que es más de mi novia que mío. Ella es la que me aguanta tantas horas delante del ordenador investigando, probando y documentando, además de escribiendo las entradas que finalmente acaban publicadas. Desde aquí gracias por estar día a día aguantando este estilo de vida de ordenador y charlas.

¿Y qué es de un blog sin lectores? Pues que no sería un blog… Es por eso que también quiero agradecer a todos los lectores que día a día haces que este pequeño rincón de internet que es FixedBuffer siga adelante. Este reconocimiento también os pertenece a todos y cada uno de vosotros que estáis ahí semana tras semana y mes tras mes.

Ya para terminar, una mención especial para Cristina Herrero e Irene Otero, managers del programa MVP en la región por el trabajo que hacen y las facilidades que ofrecen para hacer crecer más y más a la comunidad.

**La entrada ¡¡Renovado como MVP 2020!! se publicó primero en Fixed Buffer.**

Variable not found: Enlaces interesantes 411

$
0
0
Enlaces interesantes

El código de estado HTTP 411, length required, es enviado al cliente para indicar que la petición no puede ser aceptada si no incluye el encabezado content-length indicando el tamaño en bytes del cuerpo de la misma.

Ahí van los enlaces recopilados durante la semana pasada, la última entrega antes de comenzar las vacaciones. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

    Data

    Machine learning / IA / Bots

    Web / HTML / CSS / Javascript

    Visual Studio / Complementos / Herramientas

    Xamarin

    Windows calculator

    Otros

    Publicado en: www.variablenotfound.com.

    Bitácora de Javier Gutiérrez Chamorro (Guti): PowerArchiver en español

    $
    0
    0


    No recuerdo en cuantos proyectos de traducción y/o localización de software he participado, pero muchos. Haciendo un repaso rápido, y además del propio FileOptimizer en español, recuerdo Avira Antivir del que además me encargaba de la parte del soporte a usuarios; CubicExplorer; Simple PHP Blog (SPHPBlog); GreatNews; … La traducción de software es un proceso …

    PowerArchiver en español Leer más »



    Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

    Variable not found: Cómo invocar métodos de instancia C# desde Javascript con Blazor (interop 3/3)

    $
    0
    0
    Blazor
    En esta serie sobre interoperación Javascript-Blazor hemos ido viendo cómo desde Blazor Server o WebAssembly podíamos llamar a funciones Javascript disponibles en el browser, y también cómo conseguir invocar métodos estáticos .NET desde Javascript.

    Como recordaréis del post anterior, la invocación de métodos estáticos era bastante sencilla, porque básicamente desde Javascript sólo teníamos que conocer el nombre del ensamblado donde se encontraba el código y el nombre del método a ejecutar. El hecho de que el método fuera estático es una ventaja, pues no hay "piezas móviles" en el puzzle.

    Sin embargo, si desde Javascript queremos invocar un método de instancia, la cosa se complica un poco porque tendremos que ayudar a Blazor a determinar de qué instancia se trata, y esto a priori no suena sencillo porque Javascript y Blazor viven en dos mundos diferentes (de hecho, en el caso de Blazor Server incluso están físicamente separados). Sin embargo, veremos que las herramientas que ofrece el framework son suficientes para llevarlo a cabo sin liarnos demasiado.

    Esta última entrega la dedicaremos precisamente a esto: aprender cómo podemos llamar desde Javascript a métodos de instancia escritos en C#.

    ¿Cómo invocar métodos de instancia C# desde Javascript?

    Como hemos dejado entrever, si queremos llamar desde Javascript a un método de instancia escrito en C#, tanto en Blazor Server como en WebAssembly, lo que debemos hacer es:
    1. Desde C#, obtener una referencia hacia la instancia sobre la que se ejecutará el método que queremos invocar desde Javascript.
       
    2. Hacer llegar a Javascript la referencia obtenida anteriormente. Lo habitual será enviársela mediante una llamada (interop) desde C# a Javascript.
       
    3. Ya desde el lado Javascript, utilizar la referencia para invocar al método deseado, que debe ser público y estar decorado con el atributo [JSInvokable] que ya conocemos.
       
    4. Cuando ya no sea necesaria, liberar la referencia utilizada anteriormente llamando a su método Dispose() para evitar fugas de memoria.
    El proceso es exactamente igual para Blazor Server que para Blazor WebAssembly, por lo que no distinguiremos entre ambos.

    Veamos cada uno de estos puntos.

    1. Obtener la referencia hacia la instancia

    La referencia a la instancia debemos crearla desde C# utilizando el método DotNetObjectReference.Create(), suministrándole la instancia que queremos referenciar. Por ejemplo, el siguiente código en el interior de un componente crearía una referencia hacia el mismo:
    var reference = DotNetObjectReference.Create(this);
    Pero en realidad, la referencia podríamos crearla de cualquier objeto que esté en memoria en ese momento. Puede ser el propio componente en el que estamos, o bien un helper (de hecho, en la documentación oficial de Microsoft es el ejemplo que aparece), o cualquier otra cosa. La cuestión es que esa variable reference contendrá una instancia de DotNetObjectReference<T>, siendo T el tipo de objeto referenciado.

    Como más adelante tendremos que liberar la referencia, el ideal es dejarla almacenada en un campo para que luego podamos liberarla llamando a su método Dispose().
    @code {
    DotNetObjectReference<MyClass> _reference;
    ...
    // En el interior de algún método asignamos el campo:
    _reference = DotNetObjectReference.Create(myObject);
    ...
    }

    2. Hacer llegar a Javascript la referencia

    Para que el lado Javascript pueda invocar nuestro método, debemos hacerle llegar la referencia obtenida anteriormente, es decir, el objeto DotNetObjectReference<T>. Como hemos comentado, lo habitual será enviarla mediante interop desde C# a Javascript, algo que ya sabemos hacer :)

    Por ejemplo, en el siguiente código invocamos una función personalizada interopDemo.initialize() de Javascript suministrándole la referencia al componente actual. Ya en el lado cliente, esa función podría emplear la referencia para invocar al método C# directamente o bien dejarla almacenada para usos posteriores:
    @* File: Test.razor *@

    @page "/test"
    @inject IJSRuntime JsRuntime
    ...
    @code {
    DotNetObjectReference<Test> _reference;

    async Task CreateAndSendReferenceAsync()
    {
    // Creamos la referencia al componente actual...
    _reference = DotNetObjectReference.Create(this);

    // ... y se la pasamos a una función Javascript
    await JsRuntime.InvokeVoidAsync("interopDemo.initialize", _reference);
    }
    }
    El envío de la referencia podemos hacerlo en el momento que más nos interese, que dependerá del escenario o funcionalidad que vayamos a implementar. Por ejemplo, podría realizarse justo al cargar la página para que la referencia se encuentre disponible en el browser en todo momento, o bien hacerlo cuando ocurra alguna condición determinada. A nuestro antojo.

    Por ejemplo, si quisiéramos hacerlo cuando la página haya cargado totalmente, podríamos sobrescribir OnAfterRenderAsync() como se muestra a continuación:
    @code {
    DotNetObjectReference<Test> _reference;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
    if (firstRender)
    {
    await CreateAndSendReferenceAsync();
    }
    }

    async Task CreateAndSendReferenceAsync() { ... } // Omitido por brevedad
    }

    3. Utilizar la referencia para invocar al método

    Desde el código Javascript podríamos invocar muy fácilmente al método de la instancia referenciada ejecutando sobre ella invokeMethodAsync(), pasándole como parámetro el nombre del método de instancia a ejecutar.

    El siguiente código script, que podría encontrarse dentro de un archivo .js incluido en la página, define un objeto llamado interopDemo en cuyo interior encontramos dos funciones. La primera de ella, initialize() es la que anteriormente hemos utilizado para enviar la referencia, que dejamos guardada en una variable local; la segunda, increment() es donde utilizamos dicha referencia para invocar al código C# suministrándole un parámetro:
    window.interopDemo = (function () {
    var reference = null;
    return {
    initialize: function (ref) { reference = ref; },
    increment: function() {
    reference.invokeMethodAsync("Increment", 1);
    }
    };
    })();
    El método de instancia Increment() debe encontrarse en el objeto apuntado por la referencia, ser público y estar decorado con [JSInvokable], como el que sigue a continuación:
    @code {
    int counter = 0;
    ... // Otros miembros omitidos

    [JSInvokable]
    public async Task Increment(int num)
    {
    counter += num;
    StateHaschanges(); // Fuerza el renderizado si hay cambios
    }
    }
    Un ejemplo de uso de la función Javascript increment() podría ser el siguiente HTML, donde vemos que introducimos la llamada en el evento onclick de un botón:
    <h2>Interop demo</h2>
    <button onclick="interopDemo.increment()">Increment</button>
    <p>Counter: @counter</p>
    Fijaos que, a diferencia de la página de ejemplo Counter.razor que encontramos en la plantilla por defecto, en este caso no estamos utilizando el sistema de bindings de Blazor para incrementar el contador: estamos usando Javascript puro en la página, e invocando a un método C# para realizar la operación y refrescar la interfaz.

    4. Liberar la instancia

    Es importante tener en cuenta que, para evitar fugas de memoria, esta referencia debe ser liberada llamando a su método Dispose() cuando deje de tener sentido.

    Por ejemplo, si estamos en el contexto de un componente Blazor, un buen momento para hacerlo podría ser el propio Dispose() del componente. Para ello debemos hacerlo IDisposable usando la directiva @implements como en el siguiente ejemplo:
    @page "/test"
    @inject IJSRuntime JsRuntime
    @implements IDisposable

    ...
    @code {
    DotNetObjectReference<MyClass> _reference;
    int counter = 0;

    // Omitidos por brevedad
    protected override async Task OnAfterRenderAsync(bool firstRender) { ... }
    async Task CreateAndSendReferenceAsync() { ... }
    [JSInvokable] async Task Increment(int num) { ... }

    public void Dispose()
    {
    _reference?.Dispose();
    }
    }
    Como alternativa, se puede también liberar la referencia desde el lado Javascript, llamando al método dispose() de la misma.

    El ejemplo completo

    El siguiente bloque de código es el componente "Test.razor" que contiene la interfaz de usuario y el método Increment() que será consumido desde el lado cliente:
    @page "/test"
    @inject IJSRuntime JsRuntime
    @implements IDisposable

    <h1>Interop demo</h1>

    <button onclick="interopDemo.increment()">Increment</button>
    <p>Counter: @counter</p>

    @code {
    int counter = 0;
    DotNetObjectReference<Test> _reference;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
    if (firstRender)
    {
    await CreateAndSendReferenceAsync();
    }
    }

    async Task CreateAndSendReferenceAsync()
    {
    _reference = DotNetObjectReference.Create(this);
    await JsRuntime.InvokeVoidAsync("interopDemo.initialize", _reference);
    }

    [JSInvokable]
    public async Task Increment(int num)
    {
    counter += num;
    StateHasChanged();
    }

    public void Dispose()
    {
    _reference?.Dispose();
    }
    }
    La parte Javascript que realiza el registo de la referencia y la invocación al método ya la hemos visto anteriormente. Obviamente, este script debe encontrarse en la página o en algún archivo .js referenciado por ella:
    window.interopDemo = (function () {
    var reference = null;
    return {
    initialize: function (ref) { reference = ref; },
    increment: function() {
    reference.invokeMethodAsync("Increment", 1);
    }
    };
    })();
    Y con esto, creo que hemos terminado con la Blazor interop y, por tanto, esta mini-serie de tres capítulos en la que llevamos algunas semanas. Por supuesto, si véis que falta algo no dudéis en comentarlo :)

    Publicado en Variable not found.

    Variable not found: await Task.Delay(3888000000); // ¡Vacaciones!

    $
    0
    0

    La palabra vacaciones, derivada del latín vacans, participio del verbo vacare (estar libre, desocupado), está definida por la RAE como "Período del año en el que los trabajadores descansan temporalmente del trabajo". Y creo que ha llegado la hora de aplicarla, aunque no sea en toda su extensión :)

    Como es habitual por estas fechas, os informo que la semana que viene iniciaré mi descanso veraniego, y el blog quedará en modo de bajo consumo hasta septiembre. Durante este tiempo seguiré trabajando en otras cosas (tengo grandes proyectos en curso, ya os iré contando ;)) pero al menos podré hacer jornadas más cortas y descansar un poco los fines de semana, que falta hace.

    ¡Nos vemos a la vuelta!

    Playa
    Playa de Costa Ballena, Rota (Cádiz). Imagen: Hotel Elba

    Publicado en Variable not found.


    Blog Bitix: Qué son, para qué sirven, formato y ejemplos de las expresiones cron

    $
    0
    0

    Las expresiones cron son el equivalente de expresiones regulares para seleccionar fechas, instantes de tiempo o periodos. Normalmente se utilizan para planificar la ejecución de tareas automatizadas en librerías como Spring o Quartz o cron de GNU/Linux en los momentos seleccionados llegando a la precisión del segundo. Algunas expresiones de fechas pueden ser sencillas como a las 10:15 AM de todos los días o tan complejas como a las 10:15 de cada tercer viernes de cada mes.

    Los propósitos de planificar tareas con expresiones cron son ejecutar procesos automatizados, por ejemplo enviar un correo electrónico a un determinada hora de cada día o generar un informe a las 3:00 de la noche de cada viernes.

    Formato y posibles valores en cada campo

    El formato de las expresiones cron se compone de varios campos separados por un espacios.

    1
    
    second minute hour day-of-month month day-of-week [year]
    formato-cron.txt

    Según el campo hay varios valores posibles. En los valores numéricos de day-of-week los días empiezan por domingo, el valor 1 es domingo, el lunes es 2 y el 7 es el sábado.

    CampoRequeridoValores permitidosCaracteres especiales permitidos
    Seconds0-59, - * /
    Minutes0-59, - * /
    Hours0-23, - * /
    Day of month1-31, - * / L W
    Month0-11 o JAN-DEC, - * /
    Day of week1-7 o SUN-SAT, - * ? / L #
    YearNovacío o 1970-2099, - * /

    Los significados de los valores son:

    • * (todos): es usado para seleccionar cada unidad de tiempo. Por ejemplo, * en el campo minuto significa cada minuto.
    • ? (cualquiera): es utilizando en los campos day-of-month y day-of -week para denotar un valor arbitrario. Por ejemplo, si se quiere seleccionar las fechas de el quinto día de cada mes independientemente del día de la semana, entonces se especifica un ? en el campo day-of-week.
    • (rango): es usado para seleccionar rangos de valores. Por ejemplo, 7-11 en el campo hour significa entre las 7 y las 11.
    • , (valores): es usado para especificar varios valores. Por ejemplo, MON,WED,FRI en el campo day-of-week significa el lunes, miércoles y viernes.
    • / (incrementos): es usado para especificar valores en incrementos. Por ejemplo, el valor 5/15 en el campo minute significa en el minuto 5 y con incrementos de 15 minutos siendo los minutos de cada hora seleccionados 5, 20, 35 y 50.
    • L (último): tiene diferentes significados según el campo en el que se usa. Por ejemplo, si es aplicado al campo day-of-month, entonces significa el último día de cada mes, el día 31 para enero o anterior en otros meses con menos días. Puede usarse con un desplazamiento como L-3, esto significa 3 días antes del último día del mes. En el campo day-of-week significa el último día de la semana. También puede usarse con otro valor en el campo day-of-week, como 6L lo que significa el último viernes del mes.
    • W (día entre semana): es usado para especificar en día entre semana (de lunes a viernes) más cercano dado un día del mes. Por ejemplo, si se especifica 10W en el campo day-of-month significa el día entre semana más cercano al 10 de cada mes. De modo que si el día 10 del més es sábado se selecciona el viernes 9 y si el día 10 es domingo se selecciona el lunes 11. Si se especifica 1W en en day-of-month y el día 1 es sábado, entonces se selecciona el dia lunes 3, esto es, no se salta al mes anterior.
    • # (ocurrencia): es usado para especificar el cardinal de la ocurrencia de una semana del mes. Por ejemplo, el tercer viernes del mes se indica con 6#3 en el campo day-of-week.

    Los valores /, L, W y # son caracteres no estándares, para comprobar si están soportados hay que consultar la documentación de la implementación de las expresiones cron, varía según la herramienta.

    Ejemplos y generador de expresiones cron

    Las expresiones cron complejas son dífíciles de crear, para asegurar que la expresión cron está bien construida o para generarlas de forma sencilla hay alguna utilidad en internet, una de ellas es este generador y explicador de expresiones cron.

    Algunos ejemplos de expresiones con su explicación son los siguientes.

    Expresión cronExplicación
    0 0 12 * * ?Cada día a las 12:00 PM (12 del medio día)
    0 15 10 * * *Cada día a las 10:15 AM
    0 15 10 * * ? 2005Cada día a las 10:15 AM durante el año 2005
    0 * 14 * * ?Cada minuto de la hora 14, de cada día
    0 0/5 14 * * ?Cada 5 minutos de la hora 14 empezando en el minuto 0, de cada día
    0 0/5 14,18 * * ?Cada 5 minutos de las horas 14 y 18 empezando en el minuto 0, de cada día
    0 0-5 14 * * ?Cada minuto entre 0 y 5 de la hora 14, de cada día
    0 10,44 14 ? 3 WEDA las 14:10 y 14:44 de cada miércoles de marzo
    0 15 10 ? * MON-FRIA las 10:15 AM de cada lunes, martes, miércoles, jueves y viernes
    0 15 10 15 * ? A las 10:15 AM del día 15 de cada mes
    0 15 10 L * ?A las 10:15 AM del último día del mes
    0 15 10 ? * 6LA las 10:15 AM del último viernes del mes
    0 15 10 ? * 6L 2002-2005A las 10:15 AM de cada viernes del mes de los años 2002, 2003, 2004 y 2005
    0 15 10 ? * 6#3A las 10:15 AM del tercer viernes de cada mes
    0 0 12 1/5 * ?A las 12 PM (12 del medio día) cada cinco días del mes, empezando desde el primer día del mes
    0 11 11 11 11 ?Cada 11 de noviembre a las 11:11 AM

    Israel Perales: Comprar una alberca.

    $
    0
    0
    Comprar una alberca.

    Este es un post que no tiene nada que ver con tecnología, pero he visto muchas personas en grupos de Facebook que son estafadas en estas temporadas al comprar una alberca, espero esta lista pueda servirles para hacer sus compras mas seguras y darse una idea del precio en pesos mexicanos:

    Alberca

    Comprar una alberca.

    Alberca Avenli 4383 Litros
    Precio: $2,299.00

    https://www.coppel.com/alberca-avenli-4383-litros-im-5706313-0


    Bomba filtro de agua

    Comprar una alberca.

    Bomba de Filtración 530Gph 110-120V -Intex
    Precio: $955.00

    https://www.juliocepeda.com/bomba-de-filtracion-530gph-110-120v-intex.html


    Cubierta para alberca

    Comprar una alberca.

    Cubierta Para Alberca -Intex
    Precio: $399.00

    https://www.juliocepeda.com/cubierta-para-alberca-intex.html


    Kit analizador de PH

    Comprar una alberca.

    Juego Analiza Cloro Y Ph De Alberca Por Goteros Kit
    Precio: $328

    https://articulo.mercadolibre.com.mx/MLM-631159268-juego-analiza-cloro-y-ph-de-alberca-por-goteros-kit-_JM


    Inflable salvavidas

    Comprar una alberca.

    Inflable Salvavidas Flotador Para Bebé Con Techo Andadera
    Precio: $249

    https://articulo.mercadolibre.com.mx/MLM-668198000-inflable-salvavidas-andadera-con-techo-para-bebe-intex-_JM


    Dosificador de cloro

    Comprar una alberca.

    Dosificador Flotante Para Tabletas De Cloro Humboldt- Blanco con Azul
    Precio: $95.00
    Precio con envió: $134.00

    Aun y con el envió fue mucho mas barato comprarlo en Linio que en otras tiendas, también revise directamente en el proveedor principal pero el costo del envió era mucho mayor.

    https://www.linio.com.mx/p/dosificador-flotante-para-tabletas-de-cloro-humboldt-blanco-con-azul-ykdslq


    Tabletas de cloro

    Comprar una alberca.

    TABLETAS DE CLORO 1 KG AQUA POOL
    Precio: $219

    Este articulo no lo compre en linea ya que Home Depot tiene costos muy elevados en sus envíos y me queda a 15 min de mi casa y decidí ir por el.

    https://www.homedepot.com.mx/jardin/albercas/mantenimiento-de-albercas/tableta-de-tricloro-estabilizado-3-1-kg-244192


    Acido muriatico

    Comprar una alberca.

    Alamo · Ácido muriático
    Precio: $24.00

    De este producto compre 2 litros, es necesario para bajar el PH del agua que de forma natural va subiendo.

    https://web.cornershopapp.com/store/25/search/acido muriatico/product/336915


    Conclusión

    En total fue un gasto de $4631 pesos repartidos en 6 tiendas:

    Estos son algunos de los principales articulos para una alberca desmontable pequeña, sin contar los filtros de la bomba que no he comprado por que pienso hacer uno casero.

    Israel Perales: Liberar espacio en Nexus Repository

    $
    0
    0
    Liberar espacio en Nexus Repository

    Actualmente me encuentro trabajando(Ya no paso mucho tiempo desde que escribí el borrador) en un ambiente de integración continua que involucra Gitlab, Jenkins, SonarQube y Nexus (3), todo esto esta en un solo servidor y digamos que cada uno consume su porción de los recursos.

    Los que mas disco duro consumen, son Jenkins y Nexus, en esta ocasión voy a explicar como libere casi 40GB en una instancia que pesaba 92GB.

    Primero que nada decir que Nexus tiene una interfaz basada en Extjs, que en esta época la hace horrible a los ojos, mas aun la funcionalidad, en la cual no es posible eliminar imágenes de docker usando la multi selección, al menos por la interfaz de usuario es una tarea casi imposible.

    Después de tratar de eliminar todas esas imagenes viejas por la interfaz durante 5 minutos, recordé que Nexus tiene una API REST y me decidí a leerla y jugar con los endpoints que tiene.

    También pensé que alguien mas paso por lo mismo y googleando un poco encontré un post y un repositorio de Github en el que se tiene un utilidad de linea de comandos que me podría ayudar.

    Descargamos lo necesario siguiendo las instrucciones del repositorio de github

    wget https://s3.eu-west-2.amazonaws.com/nexus-cli/1.0.0-beta/linux/nexus-cli
    
    ./nexus-cli configure
    
    ./nexus-cli image ls
    
    ./nexus-cli image delete -keep 1 -name imagen
    

    Después de esto NO se debería ver espacio liberado, ya que Nexus solo marca los archivos como candidatos a removerse, se debe entrar a la sección de administrador y crear una tarea de compactación del blob y ejecutarla.

    Fuentes:

    Fixed Buffer: ¡A descansar!

    $
    0
    0
    Tiempo de lectura:< 1minuto

    Un año más llega el veranito y con él las vacaciones. Es momento de coger aire, descansar y recuperar fuerzas para seguir adelante. Este año debido a los problemas de movilidad por esta pandemia mundial me ha pillado el toro y repito foto :(.

    He de decir que la casa está terminada (recordemos que a eso me dediqué el verano pasado) y a la vuelta de vacaciones prometo actualizar la foto por la de la casita terminada 🙂

    Dicho esto, es hora de decir adiós hasta septiembre, momento en el que volveremos a la carga con unas ideas a las que estoy dando forma y que espero poner en marcha antes del segundo aniversario de FixedBuffer. Además de eso, ya me conocéis, no puedo parar, así que estoy preparando contenido para algunos otros blogs con los que colaboro así que estad atentos a la sección de colaboraciones🙂

    Muchas gracias a todos los que hacéis que este proyecto siga adelante y nos vemos en septiembre

    **La entrada ¡A descansar! se publicó primero en Fixed Buffer.**

    Blog Bitix: Las 3 formas de guardar datos en el navegador con JavaScript

    $
    0
    0

    En el navegador del usuario hay varias formas de guardar información con diferentes propósitos que perdure entre las visitas realizadas en varias sesiones. Los navegadores ofrecen tres formas de guardar datos: cookies, LocalStorage y SessionStorage e IndexedDB cada una con diferentes características y utilizables con código JavaScript.

    HTML

    JavaScript

    Las aplicaciones web utilizan la arquitectura cliente/servidor comunicándose mediante la red para enviar y recibir datos. El cliente ya sea un navegador web en un ordenador de escritorio o portátil o bien sea un dispositivo móvil inicia las solicitudes al servidor y el servidor devuelve una respuesta. La respuesta del servidor puede ser contenido HTML o datos en formato JSON en el caso de recursos REST o GraphQL.

    Aún con la cada vez mayor ancho de banda de las redes cada petición al servidor con comunicación mediante la red implica una latencia en la respuesta de unas decenas de milisegundos que se aprecia en la fluidez de las aplicaciones. Para evitar estas latencias y mejorar la experiencia de uso de las aplicaciones web hay varias estrategias, reducir el número de peticiones de una página web o aplicación web, reducir la cantidad de datos transmitidos y cuando sea posible cachear los recursos y datos para no tener que solicitarlos en cada petición ni en futuras visitas al servidor.

    Los navegadores web modernos ofrecen tres formas diferentes de almacenar o persistir datos en lado del cliente cada una siendo más apropiada según las necesidades, estas tres formas de guardar datos son con las cookies, LocalStorage o SessionStorage y finalmente IndexedDB. Los datos están accesibles aún sin conexión con el servidor desde donde se descargó la página siguiendo la política de mismo origen para mantener la seguridad y que solo la página origen tenga acceso a los datos almacenados. Una aplicación puede utilizar una o varias de estas formas de guardar datos al mismo tiempo que perduran al cierre del navegador y de las páginas estando disponible en futuras sesiones.

    En los navegadores se pueden inspeccionar estos datos almacenados en el lado del cliente utilizando las herramientas para desarrolladores, en el navegador Firefox en la sección Almacenamiento de las herramientas para desarrolladores.

    Dado que los datos se almacenan en el navegador del usuario hay que tener en cuenta en que el propio usuario tiene acceso a ellos y es capaz de modificarlos, de modo que si estos datos se envían al servidor hay que tratarlos como una fuente de datos no confiable y validarlos en el lado del servidor si es necesario para evitar fallos de seguridad ni ser datos que comprometan la seguridad.

    Guardar datos en el navegador con cookies

    El protocolo HTTP es un protocolo sin estado, esto significa que cada petición al servidor es independiente y no comparten información. Las cookies son la forma de convertir el protocolo HTTP a un protocolo con estado identificando al usuario en las diferentes peticiones. Las cookies son unos pequeños datos guardados por el navegador y enviados al servidor en cada petición al servidor.

    Las cookies se utilizan para mantener la sesión, para ofrecer personalización o para realizar seguimiento de usuarios. Cuando se realiza la autenticación en un servidor la sesión se mantiene creado una cookie que contiene un identificativo de sesión y en el lado del servidor el identificativo de la sesión se mantiene en memoria o externalizada del servidor en una base de datos como Redis o relacional. El identificativo de la sesión guardado en una cookie no es más que al menos 128 bits aleatorios únicos para cada usuario.

    Las propiedades de las cookies son:

    • Se mantienen en el cliente.
    • Se envían en cada petición al servidor.
    • Su tamaño es muy reducido, no pueden superar los 4 KiB.
    • Tienen una fecha de expiración.
    • Tienen un nombre y guardan un valor.
    • Las cookies solo se envían al dominio origen.
    • Pueden crearse sin la posibilidad de que desde JavaScript sean accesibles por seguridad.
    • Se pueden crear tanto en el lado del servidor, como en el lado del cliente con JavaScript.

    Desde JavaScript se pueden crear cookies, buscar por nombre, obtener sus valores, modificar y eliminar.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    // Crear y modificar una cookie de nombre name
    document.cookie="name=value";// Comprobar si existe una cookie de nombre name
    constexists=document.cookie.split(';').some(function(item){returnitem.trim().indexOf('name=')==0;});// Obtener el valor de la cookie de nombre name
    constvalue=document.cookie.split('; ').find(row=>row.startsWith('name=')).split('=')[1];// Eliminar la cookie cookie de nombre name
    document.cookie="name=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
    Cookies.js

    Datos almacenados con cookies

    Inspección de datos almacenados con cookies

    Guardar datos en el navegador con LocalStorage y SessionStorage

    Las cookies tiene la limitación de que son pequeñas y de enviarse en cada petición al servidor incluidas las peticiones de solicitudes de recursos como imágenes y hojas de estilo lo que aumenta la cantidad de datos transmitidos en el caso de realizar por cada página solicitada con muchos recursos un gran número de peticiones. Aunque el navegador soporte la Web Storage API algunos navegadores en el modo privado y restringidos impiden su uso para proteger la privacidad y el rastreo de los usuarios.

    El sistema de persistencia LocalStorage y SessionStorage tiene las propiedades:

    • La cantidad de datos que se pueden guardar es de hasta 5 MiB.
    • Los datos almacenados no se transmiten, solo se almacenan en el navegador.
    • No tienen fecha de expiración.
    • Almacenan datos clave-valor.
    • Las claves-valor están asociadas a un dominio.

    La diferencia entre LocalStorage y SessionStorage está en que en el último caso los datos son eliminados al cerrar todas las pestañas del navegador del dominio asociado al SessionSotrage, sus datos están limitados a la sesión. Se permiten las operaciones de inserción, lectura, modificación, eliminación y búsqueda.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    // Crear una clave-valor
    localStorage.setItem('key','value');// Ontener el valor de una clave
    letvalue=localStorage.getItem('key');// Eliminar una clave
    localStorage.removeItem('key');// Eliminar todas las claves
    localStorage.clear();// Guardar un valor en formato JSON
    value=JSON.stringify({"key1":true,"key2":42,"key3":"Hello World!"});localStorage.setItem('key',value);// Obtener el valor de una cadena guardada en formato JSON
    conststring=localStorage.getItem('key');value=JSON.parse(string);// Buscar elementos
    letkeysArray=[];for(leti=0;i<localStorage.length;++i){    keysArray.push(localStorage.key(i));}console.log(keysArray);
    LocalStorageSessionStorage.js

    Otra de las características de LocalStorage y SessionStorage es que permiten establecer un manejador de evento o listener cuando se realizan cambios. La singularidad de este mecanismo es que permite comunicar varias pestañas de la misma aplicación sin necesidad de realizar la comunicación a través del servidor.

    1
    2
    3
    4
    5
    6
    7
    
    window.addEventListener('storage',function(e){    console.log("Key: "+e.key);    console.log("Old value: "+e.oldValue);    console.log("New value: "+e.newValue);    console.log("Url: "+e.url);    console.log("Storage area: "+JSON.stringify(e.storageArea));});
    StorageListener.js

    Datos almacenados con LocalStorage

    Inspección de datos almacenados con LocalStorage

    Guardar datos en el navegador con IndexedDB

    En el caso de querer grandes cantidades de datos o de poder buscar datos por varias claves la otra forma disponible es IndexedDB. Sus propiedades son:

    • Permite almacenar grandes cantidades de datos.
    • Permite almacenar datos estructurados.
    • Cada base de datos tiene múltiples espacios de almacenamiento e índices.
    • Permite búsquedas por varias claves e índices eficientemente.
    • Soporta transaccionalidad.

    Los espacios de almacenamiento de datos relacionados se denominan objectStore. Los datos se acceden por una clave primaria pero con la diferencia de que puede haber varios índices, cada uno indexando los datos con una clave diferente. Se permiten las operaciones de inserción, lectura, modificación, eliminación y búsqueda.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    
    // Funciones de utilidad para convertir la API de listeners de IndexedDB a promesas
    functiontoRequestPromise(request){    returnnewPromise((resolve,reject)=>{        constunlisten=()=>{            request.removeEventListener('success',success);            request.removeEventListener('error',error);        };        constsuccess=()=>{            resolve(request.result);            unlisten();        };        consterror=()=>{            reject(request.error);            unlisten();        };        request.addEventListener('success',success);        request.addEventListener('error',error);    });}functiontoTransactionPromise(database,transaction){    returnnewPromise((resolve,reject)=>{        constunlisten=()=>{            transaction.removeEventListener('success',success);            transaction.removeEventListener('error',error);        };        constsuccess=(e)=>{            resolve(database,e);            unlisten();        };        consterror=(e)=>{            reject(database,e);            unlisten();        };        transaction.addEventListener('complete',success);        transaction.addEventListener('error',error);    });}// Abrir una base de datos
    varrequest=indexedDB.open('database',1);// Crear los stores de la base de datos y los índices
    request.onupgradeneeded=function(e){    constdatabase=e.target.result;    // Crear un objectStore y un índice
        constobjectStore=database.createObjectStore("store",{keyPath:"id"});    objectStore.createIndex('dni','dni',{unique:false});    console.log('database created');};toRequestPromise(request).then(function(database){    // Eliminar todos los datos de un store
        vartransaction=database.transaction('store','readwrite');    varstore=transaction.objectStore('store');    varitem={id:1,name:'picodotdev',dni:'00000000A'};    store.clear();    returntoTransactionPromise(database,transaction);}).then(function(database){    // Insertar datos
        vartransaction=database.transaction('store','readwrite');    varstore=transaction.objectStore('store');    varitem={id:1,name:'picodotdev',dni:'00000000A'};    store.add(item);    returntoTransactionPromise(database,transaction);}).then(function(database,e){    // Obtener datos de un store y por índice
        vartransaction=database.transaction('store','readonly');    varstore=transaction.objectStore('store');    store.get(1);    returntoTransactionPromise(database,transaction);}).then(function(database,e){    // Obtener datos de un store y por índice
        vartransaction=database.transaction('store','readonly');    varstore=transaction.objectStore('store');    varindex=store.index('dni');    index.get('00000000A');    returntoTransactionPromise(database,transaction);}).then(function(database,e){    // Modificar datos
        vartransaction=database.transaction('store','readwrite');    varstore=transaction.objectStore('store');    varitem={id:1,name:'picodotdev',dni:'11111111A'};    store.put(item);    returntoTransactionPromise(database,transaction);}).then(function(database,e){    // Eliminar datos
        vartransaction=database.transaction('store','readwrite');    varstore=transaction.objectStore('store');    store.delete(2);    returntoTransactionPromise(database,transaction);});
    IndexedDB.js

    Datos almacenados con IdexedDB

    Inspección de datos almacenados con IndexedDB

    Blog Bitix: Cómo descargar todo el contenido de una página web

    $
    0
    0

    GNU

    La necesidad puede ser hacer una copia de seguridad periódicas para conservar el historial del sitio web, descargar el contenido para procesarlo de alguna forma, para consultarlo sin conexión a internet u offline si nuestra conexión es lenta o inestable o el servidor está caído, migrar un sitio web de un servidor web a otro o descargar los archivos de vídeo, audio, imágenes o multimedia.

    Los navegadores web con el botón derecho del ratón ofrecen la posibilidad de descargar y guardar en el sistema de archivos la página que se está visualizando, está es la opción más simple ya que no se necesitan herramientas adicionales si solo se quiere guardar una página de las muchas de un sitio web. Su desventaja es que no sirve para guardar un sitio web completo por requerir hacer la operación manualmente por cada página, de forma que para realizar la operación de forma másiva y automatizada para el sitio web completo hay que utilizar alguna herramienta especializada.

    Descargar un sitio web desde la línea de comandos con wget

    La herramienta de línea de comandos wget permite descargar un sitio web completo con un comando ejecutado desde la terminal incluyendo los archivos HTML y de recursos como imágenes, vídeos, contenido multimedia como audio o música, de estilos CSS o archivos JavaScript. El contenido que descarga wget es el referenciado a través de los enlaces presentes en el propio contenido, wget explora los archivos HTML en busca de enlaces a recursos u otras páginas HTML y de forma recursiva descarga el contenido completo al sistema de archivos local. Está disponible para los sistemas operativos Windows, GNU/Linux y macOS y es una herramienta de software libre y gratuita.

    Basta indicar el o los dominios del sitio web de los que se quiere descargar el contenido y la URL de rastreo de inicio u otras URLs adicionales que se desean descargar. Este comando con las opciones indicadas solo descarga los enlaces del sitio web puesto como argumento, los enlaces a sitios web externos no indicados expresamente en la opción –domain son ignorados. El comando wget soporta numerosas opciones para personalizar la descarga explicadas en su página de manual.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    $ wget \
        --recursive \
        --no-clobber \
        --page-requisites \
        --html-extension \
        --convert-links \
        --restrict-file-names=windows \
        --domains picodotdev.github.io \
        --no-parent \
            picodotdev.github.io/blog-bitix/
    wget-download.sh

    Terminado el proceso de descarga el contenido del sitio web se guarda en el sistema de archivos conservando la jerarquía de carpetas de las URLs de los recursos.

    Archivos del sitio web en el explorador de archivos

    Archivos del sitio web en el explorador de archivos

    Descargar un sitio web no es la única funcionalidad que ofrece wget, otra necesidad habitual es buscar los enlaces rotos de una página web que con el paso del tiempo se van generando ya sea porque los sitios web externos desaparecen o por errores en la edición y enlazado interno. Conocer los enlaces rotos permite arreglarlos para ofrecer mejor experiencia de usuario al visitante y para mejorar el SEO en el posicionamiento en los buscadores.

    Descargar un sitio web con programas de interfaz gráfica o páginas web

    Hay algunas herramientas con interfaz gráfica como HTTrack o Cyotek WebCopy que realizan la misma tarea que wget de forma más intutiva. Aún así el comando de wget es una forma sencilla y rápida de realizar la descarga de un sitio web completo.

    También hay algunas herramientas o páginas web online que realizan la descarga de sitios web como Website Downloader o Archivarix sin necesidad de instalar ningún programa en el ordenador aunque tienen unos límites muy reducidos que sobrepasados no son gratuitas y hay que pagar por utilizarlas.

    Blog Bitix: Un comando y aplicaciones gráficas para descargar todo el contenido de un sitio web

    $
    0
    0

    GNU

    La necesidad puede ser hacer una copia de seguridad periódicas para conservar el historial del sitio web, descargar el contenido para procesarlo de alguna forma, para consultarlo sin conexión a internet u offline si nuestra conexión es lenta o inestable o el servidor está caído, migrar un sitio web de un servidor web a otro o descargar los archivos de vídeo, audio, imágenes o multimedia.

    Los navegadores web con el botón derecho del ratón ofrecen la posibilidad de descargar y guardar en el sistema de archivos la página que se está visualizando, está es la opción más simple ya que no se necesitan herramientas adicionales si solo se quiere guardar una página de las muchas de un sitio web. Su desventaja es que no sirve para guardar un sitio web completo por requerir hacer la operación manualmente por cada página, de forma que para realizar la operación de forma másiva y automatizada para el sitio web completo hay que utilizar alguna herramienta especializada.

    Cómo descargar un sitio web desde la línea de comandos con wget

    La herramienta de línea de comandos wget permite descargar un sitio web completo con un comando ejecutado desde la terminal incluyendo los archivos HTML y de recursos como imágenes, vídeos, contenido multimedia como audio o música, de estilos CSS o archivos JavaScript. El contenido que descarga wget es el referenciado a través de los enlaces presentes en el propio contenido, wget explora los archivos HTML en busca de enlaces a recursos u otras páginas HTML y de forma recursiva descarga el contenido completo al sistema de archivos local. Está disponible para los sistemas operativos Windows, GNU/Linux y macOS y es una herramienta de software libre y gratuita.

    Basta indicar el o los dominios del sitio web de los que se quiere descargar el contenido y la URL de rastreo de inicio u otras URLs adicionales que se desean descargar. Este comando con las opciones indicadas solo descarga los enlaces del sitio web puesto como argumento, los enlaces a sitios web externos no indicados expresamente en la opción –domain son ignorados. El comando wget soporta numerosas opciones para personalizar la descarga explicadas en su página de manual.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    $ wget \
        --recursive \
        --no-clobber \
        --page-requisites \
        --html-extension \
        --convert-links \
        --restrict-file-names=windows \
        --domains picodotdev.github.io \
        --no-parent \
            picodotdev.github.io/blog-bitix/
    wget-download.sh

    Terminado el proceso de descarga el contenido del sitio web se guarda en el sistema de archivos conservando la jerarquía de carpetas de las URLs de los recursos.

    Archivos del sitio web en el explorador de archivos

    Archivos del sitio web en el explorador de archivos

    Descargar un sitio web no es la única funcionalidad que ofrece wget, otra necesidad habitual es buscar los enlaces rotos de una página web que con el paso del tiempo se van generando ya sea porque los sitios web externos desaparecen o por errores en la edición y enlazado interno. Conocer los enlaces rotos permite arreglarlos para ofrecer mejor experiencia de usuario al visitante y para mejorar el SEO en el posicionamiento en los buscadores.

    Cómo descargar un sitio web con programas de interfaz gráfica o páginas web

    Hay algunas herramientas con interfaz gráfica como HTTrack o Cyotek WebCopy que realizan la misma tarea que wget de forma más intutiva. Aún así el comando de wget es una forma sencilla y rápida de realizar la descarga de un sitio web completo.

    También hay algunas herramientas o páginas web online que realizan la descarga de sitios web como Website Downloader o Archivarix sin necesidad de instalar ningún programa en el ordenador aunque tienen unos límites muy reducidos que sobrepasados no son gratuitas y hay que pagar por utilizarlas.


    Arragonán: Disponible para ayudar a equipos

    $
    0
    0

    Ya sea desde el rol de mentor o de asesor, me encanta ayudar a equipos en todo el espectro que involucra el desarrollo de producto digital.

    Esta inquietud tardó en despertar en mí, pero ha aumentado con el paso de los años. Arrancó primero con la iniciativa de Senpai Devs mentorizando a varias personas. Después haciendo algunas formaciones e intervenciones para ayudar a startups y corporaciones junto a mis compañeros de Coding Stones. Y más tarde en Inditex, donde además de ejercer como techlead en mis equipos, pude aconsejar a algún otro. Incluso hace unos meses he ido dedicando algo de mi tiempo libre a hacer asesorías puntuales.

    Ahora, tras mi paso por Devengo, quiero dedicar más tiempo a ayudar a equipos a crear mejor software y producto digital. De hecho, ya estoy cerrando fechas para arrancar un par de colaboraciones.

    Haciendo una sesión de mob programming con código proyectado en la pared

    ¿Ayudar a equipos? ¿Cómo?

    Lo de ayudar a equipos quizás queda algo abstracto, en concreto las actividades que quiero hacer son principalmente mentorizar y aconsejar a equipos. Además de preparar formaciones y talleres para que adquireran las bases de ciertas prácticas cuando se identifiquen esas necesidades.

    Mi intención es ayudar a mejorar extremo a extremo en la creación de producto digital: en el trabajo en equipo, en las prácticas técnicas y en la gestión de producto.

    ¿En qué puedo ayudar a tu organización o equipo?

    Algunos dolores con los que puedo aportar valor son:

    • Habéis empezado a trabajar con procesos ágiles como scrum o kanban y los equipos siguen sin tener los resultados que esperabas. O tal vez estéis pensando en empezar con ello pero necesitáis ayuda para establecer las bases.
    • Tenéis problemas con la capacidad y calidad de entrega, porque en las revisiones o incluso en producción se encuentran excesivos bugs. Motivo por el cual queréis incorporar o mejorar en algunas prácticas técnicas. O necesitáis ayuda para identificar qué mejorar.
    • A los equipos les cuesta entregar, se eternizan las historias de usuario en doing o incluso quedan semanas bloqueadas.
    • Hay cierto desalineamiento, o incluso tensiones, a la hora de integrar a los diferentes roles del equipo. Típicamente cualquier permuta entre desarrolladores vs diseñadores vs product owners/managers vs administradores de sistemas vs…
    • Se ha acumulado demasiada deuda técnica, así que el legacy no deja avanzar al ritmo esperado por el negocio.
    • No tenéis muy claro cómo cambiar la mentalidad de trabajo a proyecto hacia producto, tanto del propio equipo como por parte de stakeholders. Hay más preocupación y presión por cumplir con estimaciones que por lograr objetivos.
    • El negocio va como un tiro y estáis creciendo, eso implica cambiar parte de la arquitectura existente y analizar si hay que reorganizar los diferentes equipos que han ido surgiendo, incluso la forma de esos equipos.
    • En la organización se ve a los equipos de producto o tecnología como un impedimento para nuevas iniciativas y oportunidades de negocio, no como un colaborador y facilitador.

    Si identificas alguno de esos problemas en tu equipo u organización, por mi parte estaría encantado de que hablemos para ver si encontramos una forma de colaborar.

    Blog Bitix: Comunicaciones seguras, autenticación mutua y autorizaciones con intenciones entre servicios usando Consul Connect y Nomad

    $
    0
    0

    La configuración de seguridad estática y basada en direcciones IP no es adecuada en un entorno en el que los recursos de computación está compartidos y no son confiables, ni para aplicaciones basadas en microservicios cuyo número de servicios e instancias cambia a lo largo del tiempo. Consul y Consul Connect ofrecen un mecanismo de comunicación segura adaptados a la computación en la nube y adecuado para aplicaciones basadas en microservicios.

    Consul

    Nomad

    HashiCorp

    El método habitual para proporcionar seguridad a varios sistemas que se comunican por red ha sido utilizar protocolos de comunicaciones cifrados, poner cortafuegos e impedir una comunicación directa con algunos sistemas como las base de datos en ocasiones en base a direcciones IP. La configuración estática cuando hay que adaptarla por añadir nuevos sistemas es difícil de cambiarla.

    En un entorno en la nube, aún con sus propias medidas de seguridad y aislamiento que implementan, la infraestructura no es confiable por ser compartida con otras organizaciones como demuestran los fallos de seguridad de meltdown y spectre de los procesadores por su ejecución especulativa. En una arquitectura dinámica de múltiples microservicios e instancias una configuración estática no es práctica para un sistema que por naturaleza es altamente dinámico y en un medio de computación compartido no confiable hay que adoptar medidas adicionales.

    La propuesta que ofrecen algunas herramientas nativas para la computación en la nube es proporcionar seguridad realizando cada comunicación servicio a servicio de forma cifrada y transparente para las aplicaciones incluso si estas no implementan comunicación cifrada de forma nativa, con autenticación mutua entre los dos servicios utilizando su identidad en vez de direcciones IP y con autorizaciones basadas en intenciones fáciles de administrar y cambiar.

    Infraestructura estáticaInfraestructura estática

    Infraestructura estática

    Infraestructura dinámicaInfraestructura dinámica

    Infraestructura dinámica

    Una de estas herramientas es Consul que proporciona la funcionalidad de registro y descubrimiento de servicios y conectividad entre esos servicios. Consul Connect proporciona comunicaciones seguras de forma transparente para las aplicaciones.

    El esquema de funcionamiento de Consul Connect es hacer que los servicios no se comuniquen directamente entre ellos sino que utilizan proxys, la creación del enlace de comunicación se delega en Consul que crea los proxys y con ellos le es posible proporcionar comunicaciones seguras, con autenticación mutua y posibilitando autorización con intenciones. Las intenciones son los permisos que establece el operador de Consul para establecer que dos servicios tiene permitido la comunicación entre ellos, cualquier intento de comunicación si no está aprobado de forma explícita por su intención no se permite.

    Comunicación servicio a servicio con Consul Connect

    Comunicación servicio a servicio con Consul Connect

    Otra de las herramientas de HashiCorp es Nomad que proporciona la funcionalidad de orquestador de servicios, creando las instancias de los servicios necesarias en los diferentes nodos de computación. Nomad se integra con Consul y Consul Connect haciendo más sencillo utilizar todas estas tecnologías.

    En el siguiente ejemplo sigo la guía de ejemplo para probar Consul Connect con Nomad utilizando Envoy para crear los proxys. Para ejecutar el ejemplo es necesario obtener los binarios de Consul, Nomad y Envoy además de las utilidades de comunicaciones de cni-plugins y Docker.

    Hay que instalar los paquetes de Consul, Nomad, Envoy y cni-plugins de la distribución GNU/Linux o la descarga directa de sus binarios accesibles en la variable PATH del sistema utilizada por los intérpretes de comandos para buscar comandos. Para obtener Envoy hay que ejecutar el siguiente comando que instala el binario de Envoy en la carpeta /usr/local/bin que incluida en la variable PATH.

    1
    2
    
    $ curl -L https://getenvoy.io/cli | bash -s -- -b /usr/local/bin
    $ getenvoy run standard:1.11.2 -- --version
    getenvoy.sh

    Nomad requiere que los cni-plugins esté ubicados en la carpeta /opt/cni/bin, en Arch Linux se instalan en la carpeta /usr/lib/cni/ por lo que hay que crear un enlace simbólico para que sean encontrados por Nomad.

    1
    2
    
    $ sudo pacman -s cni-plugins
    $ sudo ln -s /usr/lib/cni/ /opt/cni/bin
    install-cni-plugins.sh

    Con los binarios necesarios instalados hay que iniciar Consul y Nomad con los siguientes comandos que los arrancan en su configuración de desarrollo y con el soporte para Consul Connect en Nomad.

    1
    2
    
    $ consul agent -dev
    $ sudo nomad agent -dev-connect
    start-consul-nomad.sh

    El siguiente paso es enviar el Nomad la definición de los servicios para que planifique su ejecución, en este caso utilizando contenedores de Docker que ha de estar instalado previamente y con su servicio del sistema iniciado.

    La definición incluye dos servicios uno que proporciona un contador y otro que obtiene el valor de ese contador y lo muestra en una página web. En la definición del servicio count-api se define que va a ofrecer su comunicación por red en el puerto 9001 asociado a la interfaz de red localhost de modo que este puerto solo pueda ser accedido por Consul a través del proxy que crea, la definición del servicio count-dashboard ofrece su servicio en el puerto 9002 y que tiene tiene como dependencia el servicio count-api para el que Consul crea un nuevo proxy, el servicio count-dashboard se conecta al puerto 8080 para comunicarse con el servicio count-api mediante los proxys de Consul Connect. Los servicios solo se comunican con los proxys y son los proxys los que se comunican entre ellos.

    1
    
    $ nomad run connect.nomad
    nomad-run.sh

    En la definición de job las partes relativas a Consul Connect están en los bloques o stanzasconnect, en el caso de count-api simplemente para indicar que desea un proxy y en el caso de count-dashboard para indicar que desea un proxy para conectarse al servicio count-api.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    
    job"countdash" {
      datacenters=["dc1"]group"api" {
        network {
          mode="bridge"    }
    
        service {
          name="count-api"      port="9001"      connect {
            sidecar_service {}
          }
        }
    
        task"web" {
          driver="docker"      config {
            image="hashicorpnomad/counter-api:v1"      }
        }
      }
    
      group"dashboard" {
        network {
          mode="bridge"      port"http" {
            static=9002        to     =9002      }
        }
    
        service {
          name="count-dashboard"      port="9002"      connect {
            sidecar_service {
              proxy {
                upstreams {
                  destination_name="count-api"              local_bind_port=8080            }
              }
            }
          }
        }
    
        task"dashboard" {
          driver="docker"      env {
            COUNTING_SERVICE_URL="http://${NOMAD_UPSTREAM_ADDR_count_api}"      }
    
          config {
            image="hashicorpnomad/counter-dashboard:v1"      }
        }
      }
    }
    
    connect.nomad

    El resultado es esta aplicación sencilla de ejemplo que muestra un contador en una página web con el tiempo se va incrementando. En las consolas de administración de Consul se observan los servicios registrados y en Nomad la ejecución de los mismos.

    Consola de administración de ConsulConsola de administración de NomadServicio count-dashboard

    Consola de administración de Consul y Nomad y servicio count-dashboard

    Dado que el servicio count-dashboard se conecta con el servicio count-api a través de los proxys que crea Consul, Consul es capaz de permitir o denegar la comunicación con las intenciones. Las intenciones son las autorizaciones concedidas a cada servicio origen y servicio destino de comunicación. La ventaja de las intenciones es que son agnósticas de la red como redes físicas, en la nube, basadas en software o cualesquiera otras ya que están basadas en la identidades de los servicios.

    En el modo desarrollo de Consul aún sin una intención la comunicación se autorizan las comunicaciones pero si se crea una intención que deniegue la comunicación desde el servicio count-dashboard a count-api el servicio count-dashboard no es capaz de recuperar el contador y se muestra desconectado. Cambiando la intención para que autorice el tráfico la comunicación se restaura y el servicio count-dashboard vuelve a mostrar el contador.

    Intención que deniega la comunicaciónIntención que deniega la comunicación

    Intención que deniega la comunicación

    Intención que permite la comunicaciónIntención que permite la comunicación

    Intención que permite la comunicación

    Bitácora de Javier Gutiérrez Chamorro (Guti): Estadísticas de comentarios

    $
    0
    0


    En 17.000 comentarios, Fernando sugería hacer una análisis de los comentarios, una especie de ranking mostrando los más antiguos, los que más participan, etcétera. Me pareció un ejercicio curioso, y una buena excusa para lanzar unas cuantas consultas a la base de datos de WordPress. Aquí tenéis los resultados. Comentaristas más antiguos Autor Fecha del …

    Estadísticas de comentarios Leer más »



    Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

    Blog Bitix: Revertir a una versión anterior de un servicio con Nomad

    $
    0
    0

    Los errores no se planifican, se producen de forma inesperada. Además, un error en un entorno de producción es normalmente urgente e importante lo que obliga a cambiar las prioridades del equipo dejando lo que están haciendo y ocuparse de resolver el problema. En ocasiones no será posible resolver el problema y la única solución es revertir la versión de la aplicación a la anterior. Dependiendo de la automatización de los procesos incluso el volver a la versión anterior quizá sea complicado. Los errores no se planifican pero si se puede planificar estar preparado para algunos errores, una forma de estar preparado ante errores es tener un proceso y herramientas para volver a la versión anterior rápido y fácilmente.

    Nomad

    HashiCorp

    En el desarrollo de una aplicación se dedica un considerable esfuerzo a tener la mayor seguridad que los cambios no introducen errores, se realizan numerosas pruebas unitarias, pruebas de integración, si es una aplicación web pruebas funcionales simulando la interacción de un usuario. Todas estas pruebas se ejecutan además del equipo de cada desarrollador en un entorno de integración continua o CI en cada commit al repositorio de control de versiones del código fuente, el equipo de desarrollo recibe una notificación con inmediatez en caso de fallar alguna prueba. Aún se hacen mas pruebas algunas manuales en un entorno de staging que tiene una configuración similar al de producción. Finalmente, se despliega la versión validada en producción todavía haciendo más comprobaciones con estrategias de despliegue blue/green y canary, si no se ha descubierto ningún error se promociona la versión en todas las instancias.

    Y aún con todo estos procesos de pruebas en ocasiones se descubren errores en producción. Si la causa del error se descubre rápido y es fácil de solucionar se opta por corregirlo, crear una nueva versión y desplegarla. Si el error es leve y no afecta a una funcionalidad importante se puede esperar a la siguiente versión para corregirlo. Pero algunos errores impactan en una funcionalidad vital para el negocio, tanto que obligan al equipo a dejar las tareas planificadas que están haciendo aún siendo también importantes para descubrir la causa del error y corregirlo, a veces la causa y la corrección se realiza rápido. En otras ocasiones la causa es difícil de determinar o la corrección es compleja, en estas ocasiones la opción adecuada es volver a la versión anterior buena conocida haciendo un revert o rollback. Se pierden las otras nuevas funcionalidades incorporadas en la misma versión pero se evita hacer cambios que introducen deuda técnica por la presión de dar solución rápido al error y se evita al equipo trabajar en un desagradable bajo presión.

    En la operación de sistemas lo ideal es que todas las tareas estén automatizadas para evitar errores al realizar operaciones manuales y para evitar complejos procesos que impidan a cualquier miembro de equipo realizar la tarea por falta de conocimiento, complejidad o requerir formación. Dos de estas tareas a automatizar son el despliegue de una aplicación con una nueva versión y también el revertir a una versión anterior en caso de detectar que la nueva versión tiene algún tipo de error.

    Nomad es un orquestador de servicios que en gran medida automatiza el despliegue de de las aplicaciones, su actualización a nuevas versiones y revertir a versiones anteriores.

    Se inicia con el siguiente comando.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    $ nomad agent -dev
    ==> No configuration files loaded==> Starting Nomad agent...
    ==> Nomad agent configuration:
    
           Advertise Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
                Bind Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
                    Client: true             Log Level: DEBUG
                    Region: global (DC: dc1)                Server: true               Version: 0.12.0
    
    ==> Nomad agent started! Log data will stream in below:
    
    ...
    nomad-start.sh

    Para ejecutar un servicio basta con enviar su definición en su archivo de configuración con un comando.

    1
    2
    3
    4
    5
    6
    7
    
    $ nomad run service-1.nomad
    ==> Monitoring evaluation "56421393"    Evaluation triggered by job "service"    Allocation "b51101cc" created: node "5721462c", group "service"    Evaluation within deployment: "33154ac5"    Evaluation status changed: "pending" -> "complete"==> Evaluation "56421393" finished with status "complete"
    nomad-run-1.sh

    La definición del servicio en Nomad incluye la versión del artecfacto a desplegar. En este caso el servicio es simplemente un comando que emite una traza en la consola, a efectos didácticos no es relevante ya que sería lo mismo si fuera el comando de inicio de la aplicación Java que incluya la versión del jar o si el contenedor Docker estuviese versionado.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    job "service"{datacenters=["dc1"]
    
      group "service"{    task "service"{      driver="docker"      config {        image="busybox:latest"        command="ash"        args=[          "-c",
              "while true; do echo \"Version 1\"; sleep 1; done"        ]      }    }}}
    service-1.nomad

    El servicio emite en la salida el mensaje.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    $ nomad job status service
    ...
    Allocations
    ID        Node ID   Task Group  Version  Desired  Status    Created     Modified
    b51101cc  5721462c  service     0        stop     running   40m6s ago   38m31s ago
    $ nomad alloc logs b51101cc
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    ...
    service-1-logs.sh

    Al hacer cambios en el código de un servicio con nuevas características, correcciones de errores o mejoras de las funcionalidades existentes hay que generar una nueva versión del artefacto a desplegar, en el caso de una aplicación Java con Spring Boot puede ser un nuevo archivo jar con un nuevo nombre que incluye su versión, el artefacto de despligue es un contenedor Docker este también estará versionado. En la definición del servicio para Nomad hay que actualizar la referencia del artefacto a la nueva versión. El comando para realizar el despliegue es el mismo, basta con enviar la nueva definición del servicio a Nomad.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    job "service"{datacenters=["dc1"]
    
      group "service"{    task "service"{      driver="docker"      config {        image="busybox:latest"        command="ash"          args=[            "-c",
                "while true; do echo \"Version 2 (error)\"; sleep 1; done"        ]      }    }}}
    service-2.nomad

    Como una nueva versión implica cambios en el código o la configuración del servicio es posible introducir nuevos errores que aún con todos los procesos de pruebas en diferentes entornos comentados sean descubiertos solo en el entorno de producción como es el típico NullPointerException o en este caso un mensaje de traza erróneo.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    $ nomad job status service
    ...
    Allocations
    ID        Node ID   Task Group  Version  Desired  Status    Created     Modified
    f7bd853e  5721462c  service     1        stop     running   38m33s ago  34m57s ago
    b51101cc  5721462c  service     0        stop     complete  40m6s ago   38m31s ago
    $ nomad alloc logs f7bd853e
    Version 2(error)
    Version 2(error)
    Version 2(error)
    Version 2(error)
    Version 2(error)
    Version 2(error)
    Version 2(error)
    Version 2(error)
    ...
    service-2-logs.sh

    Para la tarea de volver a una versión anterior el proceso con Nomad es igualmente sencillo, Nomad recuerda las definiciones anteriores enviadas con lo que solo se necesita utilizar el comando para restaurar la definición del servicio. Con los artefactos de despliegue versionados ya sea con contenedores Docker o con versiones en archivos jar las definiciones de los servicios contienen la referencia de los artefactos que utilizan.

    1
    2
    3
    4
    5
    6
    7
    
    $ nomad job revert service 0==> Monitoring evaluation "493bcc52"    Evaluation triggered by job "service"    Evaluation within deployment: "e13303e3"    Allocation "fc4fd574" created: node "5721462c", group "service"    Evaluation status changed: "pending" -> "complete"==> Evaluation "493bcc52" finished with status "complete"
    nomad-revert.sh

    El volver a la versión anterior puede hacerse desde la consola gráfica de administración de Nomad o desde la interfaz de línea de comandos introduciendo la definición anterior del servicio. Nomad se encarga de actualizar a la versión anterior de todas las instancias de forma progresiva utilizando estrategias de despliegue blue/green y canary con Nomad que haya del servicio de forma automatizada y el servicio vuelve a la normalidad.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    $ nomad job status service
    ...
    Allocations
    ID        Node ID   Task Group  Version  Desired  Status    Created     Modified
    fc4fd574  5721462c  service     2        run      running   34m58s ago  34m43s ago
    f7bd853e  5721462c  service     1        stop     complete  38m33s ago  34m57s ago
    b51101cc  5721462c  service     0        stop     complete  40m6s ago   38m31s ago
    $ nomad alloc logs fc4fd574
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    ...
    service-3-logs.sh

    Desde la consola web de administración también se ven las definiciones de los jobs y sus versiones con sus diferencias.

    Versión 1 del servicioVersión 2 del servicioVersiones del servicio

    Versiones del servicio

    Blog Bitix: Revertir un servicio a una versión anterior con Nomad

    $
    0
    0

    Los errores no se planifican, se producen de forma inesperada. Además, un error en un entorno de producción es normalmente urgente e importante lo que obliga a cambiar las prioridades del equipo dejando lo que están haciendo y ocuparse de resolver el problema. En ocasiones no será posible resolver el problema y la única solución es revertir la versión de la aplicación a la anterior. Dependiendo de la automatización de los procesos incluso el volver a la versión anterior quizá sea complicado. Los errores no se planifican pero si se puede planificar estar preparado para algunos errores, una forma de estar preparado ante errores es tener un proceso y herramientas para volver a la versión anterior rápido y fácilmente.

    Nomad

    HashiCorp

    En el desarrollo de una aplicación se dedica un considerable esfuerzo a tener la mayor seguridad que los cambios no introducen errores, se realizan numerosas pruebas unitarias, pruebas de integración, si es una aplicación web pruebas funcionales simulando la interacción de un usuario. Todas estas pruebas se ejecutan además del equipo de cada desarrollador en un entorno de integración continua o CI en cada commit al repositorio de control de versiones del código fuente, el equipo de desarrollo recibe una notificación con inmediatez en caso de fallar alguna prueba. Aún se hacen mas pruebas algunas manuales en un entorno de staging que tiene una configuración similar al de producción. Finalmente, se despliega la versión validada en producción todavía haciendo más comprobaciones con estrategias de despliegue blue/green y canary, si no se ha descubierto ningún error se promociona la versión en todas las instancias.

    Y aún con todo estos procesos de pruebas en ocasiones se descubren errores en producción. Si la causa del error se descubre rápido y es fácil de solucionar se opta por corregirlo, crear una nueva versión y desplegarla. Si el error es leve y no afecta a una funcionalidad importante se puede esperar a la siguiente versión para corregirlo. Pero algunos errores impactan en una funcionalidad vital para el negocio, tanto que obligan al equipo a dejar las tareas planificadas que están haciendo aún siendo también importantes para descubrir la causa del error y corregirlo, a veces la causa y la corrección se realiza rápido. En otras ocasiones la causa es difícil de determinar o la corrección es compleja, en estas ocasiones la opción adecuada es volver a la versión anterior buena conocida haciendo un revert o rollback. Se pierden las otras nuevas funcionalidades incorporadas en la misma versión pero se evita hacer cambios que introducen deuda técnica por la presión de dar solución rápido al error y se evita al equipo trabajar en un desagradable bajo presión.

    En la operación de sistemas lo ideal es que todas las tareas estén automatizadas para evitar errores al realizar operaciones manuales y para evitar complejos procesos que impidan a cualquier miembro de equipo realizar la tarea por falta de conocimiento, complejidad o requerir formación. Dos de estas tareas a automatizar son el despliegue de una aplicación con una nueva versión y también el revertir a una versión anterior en caso de detectar que la nueva versión tiene algún tipo de error.

    Nomad es un orquestador de servicios que en gran medida automatiza el despliegue de de las aplicaciones, su actualización a nuevas versiones y revertir a versiones anteriores.

    Se inicia con el siguiente comando.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    $ nomad agent -dev
    ==> No configuration files loaded==> Starting Nomad agent...
    ==> Nomad agent configuration:
    
           Advertise Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
                Bind Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
                    Client: true             Log Level: DEBUG
                    Region: global (DC: dc1)                Server: true               Version: 0.12.0
    
    ==> Nomad agent started! Log data will stream in below:
    
    ...
    nomad-start.sh

    Para ejecutar un servicio basta con enviar su definición en su archivo de configuración con un comando.

    1
    2
    3
    4
    5
    6
    7
    
    $ nomad run service-1.nomad
    ==> Monitoring evaluation "56421393"    Evaluation triggered by job "service"    Allocation "b51101cc" created: node "5721462c", group "service"    Evaluation within deployment: "33154ac5"    Evaluation status changed: "pending" -> "complete"==> Evaluation "56421393" finished with status "complete"
    nomad-run-1.sh

    La definición del servicio en Nomad incluye la versión del artecfacto a desplegar. En este caso el servicio es simplemente un comando que emite una traza en la consola, a efectos didácticos no es relevante ya que sería lo mismo si fuera el comando de inicio de la aplicación Java que incluya la versión del jar o si el contenedor Docker estuviese versionado.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    job "service"{datacenters=["dc1"]
    
      group "service"{    task "service"{      driver="docker"      config {        image="busybox:latest"        command="ash"        args=[          "-c",
              "while true; do echo \"Version 1\"; sleep 1; done"        ]      }    }}}
    service-1.nomad

    El servicio emite en la salida el mensaje.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    $ nomad job status service
    ...
    Allocations
    ID        Node ID   Task Group  Version  Desired  Status    Created     Modified
    b51101cc  5721462c  service     0        stop     running   40m6s ago   38m31s ago
    $ nomad alloc logs b51101cc
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    ...
    service-1-logs.sh

    Al hacer cambios en el código de un servicio con nuevas características, correcciones de errores o mejoras de las funcionalidades existentes hay que generar una nueva versión del artefacto a desplegar, en el caso de una aplicación Java con Spring Boot puede ser un nuevo archivo jar con un nuevo nombre que incluye su versión, el artefacto de despligue es un contenedor Docker este también estará versionado. En la definición del servicio para Nomad hay que actualizar la referencia del artefacto a la nueva versión. El comando para realizar el despliegue es el mismo, basta con enviar la nueva definición del servicio a Nomad.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    job "service"{datacenters=["dc1"]
    
      group "service"{    task "service"{      driver="docker"      config {        image="busybox:latest"        command="ash"          args=[            "-c",
                "while true; do echo \"Version 2 (error)\"; sleep 1; done"        ]      }    }}}
    service-2.nomad

    Como una nueva versión implica cambios en el código o la configuración del servicio es posible introducir nuevos errores que aún con todos los procesos de pruebas en diferentes entornos comentados sean descubiertos solo en el entorno de producción como es el típico NullPointerException o en este caso un mensaje de traza erróneo.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    $ nomad job status service
    ...
    Allocations
    ID        Node ID   Task Group  Version  Desired  Status    Created     Modified
    f7bd853e  5721462c  service     1        stop     running   38m33s ago  34m57s ago
    b51101cc  5721462c  service     0        stop     complete  40m6s ago   38m31s ago
    $ nomad alloc logs f7bd853e
    Version 2(error)
    Version 2(error)
    Version 2(error)
    Version 2(error)
    Version 2(error)
    Version 2(error)
    Version 2(error)
    Version 2(error)
    ...
    service-2-logs.sh

    Para la tarea de volver a una versión anterior el proceso con Nomad es igualmente sencillo, Nomad recuerda las definiciones anteriores enviadas con lo que solo se necesita utilizar el comando para restaurar la definición del servicio. Con los artefactos de despliegue versionados ya sea con contenedores Docker o con versiones en archivos jar las definiciones de los servicios contienen la referencia de los artefactos que utilizan.

    1
    2
    3
    4
    5
    6
    7
    
    $ nomad job revert service 0==> Monitoring evaluation "493bcc52"    Evaluation triggered by job "service"    Evaluation within deployment: "e13303e3"    Allocation "fc4fd574" created: node "5721462c", group "service"    Evaluation status changed: "pending" -> "complete"==> Evaluation "493bcc52" finished with status "complete"
    nomad-revert.sh

    El volver a la versión anterior puede hacerse desde la consola gráfica de administración de Nomad o desde la interfaz de línea de comandos introduciendo la definición anterior del servicio. Nomad se encarga de actualizar a la versión anterior de todas las instancias de forma progresiva utilizando estrategias de despliegue blue/green y canary con Nomad que haya del servicio de forma automatizada y el servicio vuelve a la normalidad.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    $ nomad job status service
    ...
    Allocations
    ID        Node ID   Task Group  Version  Desired  Status    Created     Modified
    fc4fd574  5721462c  service     2        run      running   34m58s ago  34m43s ago
    f7bd853e  5721462c  service     1        stop     complete  38m33s ago  34m57s ago
    b51101cc  5721462c  service     0        stop     complete  40m6s ago   38m31s ago
    $ nomad alloc logs fc4fd574
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    Version 1
    ...
    service-3-logs.sh

    Desde la consola web de administración también se ven las definiciones de los jobs y sus versiones con sus diferencias.

    Versión 1 del servicioVersión 2 del servicioVersiones del servicio

    Versiones del servicio
    Viewing all 2715 articles
    Browse latest View live