Weblogs Código

Variable not found

Implicit usings en C#10

octubre 19, 2021 06:05

.NET Core

Hace unos días hablábamos de las directivas using globales, un interesante añadido a C# 10 que permite importar espacios de nombres en todos los archivos de código del proyecto, sin necesidad de repetir cientos de veces las mismas líneas en sus encabezados. Simplemente, si un namespace es interesante para nuestro proyecto, lo declaramos como global en algún punto y será lo mismo que si lo hubiéramos hecho en cada uno de los archivos .cs:

global using System;
global using System.Text;
global using System.Text.Json;
global using MyProject.Model;
...

Bien podían haberlo dejado aquí porque ya es una mejora sustancial respecto a lo que tenemos, pero no, el equipo de diseño de C# sigue introduciendo detalles que pueden hacernos la vida más sencilla. Es el caso de los implicit usings que, de la misma forma, acompañan a .NET 6 y C# 10.

Importación implícita de namespaces

El hecho que hay detrás de esta característica es bien simple: hay ciertos espacios de nombres cuya importación que son casi obligatorios en función del tipo de proyecto que estemos desarrollando. Por ejemplo, en casi todos los proyectos encontraremos referencias a System, System.Collection.Generics o System.Linq. O si programamos una aplicación web ASP.NET Core, es habitual que acabemos importando namespaces como Microsoft.AspNetCore.Builder, Microsoft.AspNetCore.Routing o System.Net.Http.Json.

.NET 6 importará automáticamente ciertos espacios de nombres en función del tipo de proyecto, que viene dado por el SDK definido en la primera línea del archivo .csproj, como el siguiente, correspondiente a un proyecto Web:

<Project Sdk="Microsoft.NET.Sdk.Web">
...
</Project>

En función de este SDK, se importarán los siguientes namespaces:

SDKNamespaces importados
Microsoft.NET.SdkSystem
System.Collections.Generic
System.IO
System.Linq
System.Net.Http
System.Threading
System.Threading.Tasks
Microsoft.NET.Sdk.WebSystem.Net.Http.Json
Microsoft.AspNetCore.Builder
Microsoft.AspNetCore.Hosting
Microsoft.AspNetCore.Http
Microsoft.AspNetCore.Routing
Microsoft.Extensions.Configuration
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Hosting
Microsoft.Extensions.Logging
(+ todos los de Microsoft.NET.Sdk)
Microsoft.NET.Sdk.Worker Microsoft.Extensions.Configuration
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Hosting
Microsoft.Extensions.Logging
(+ todos los de Microsoft.NET.Sdk)

Para evitar conflictos con clases existentes, esta característica está desactivada por defecto, de forma que si migramos un proyecto de .NET 5 a .NET 6 seguro que no se romperá nada. Pero para proyectos nuevos, ya habilita de serie en las nuevas plantillas, usando la siguiente configuración en el .csproj:

<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
...
</PropertyGroup>

Internamente, cuando los implicit usings están habilitados, se generará durante la compilación un archivo llamado [ProjectName].GlobalUsings.g.cs en la carpeta obj. Por ejemplo, este es el contenido que podemos encontrar en una aplicación web:

// <auto-generated/>
global using global::Microsoft.AspNetCore.Builder;
global using global::Microsoft.AspNetCore.Hosting;
global using global::Microsoft.AspNetCore.Http;
global using global::Microsoft.AspNetCore.Routing;
global using global::Microsoft.Extensions.Configuration;
global using global::Microsoft.Extensions.DependencyInjection;
global using global::Microsoft.Extensions.Hosting;
global using global::Microsoft.Extensions.Logging;
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Net.Http.Json;
global using global::System.Threading;
global using global::System.Threading.Tasks;

Aunque este proceso sea automático, podemos utilizar el .csproj para ajustar los espacios de nombre incluidos en este archivo, como ya vimos en el post sobre los usings globales. En el siguiente ejemplo vemos cómo podríamos eliminar System y añadir MyProject.Model de forma muy sencilla:

<ItemGroup>
<Using Remove="System" />
<Using Include="MyProject.Model" />
</ItemGroup>

En definitiva, la idea no está nada mal y es una de las piezas que permite conseguir algunas cosas realmente espectaculares, como las que podemos ver al crear nuevos proyectos de consola, cuyo código veremos que consiste en una única línea, o las cuatro o cinco líneas que costará poner en marcha un servidor ASP.NET Core. A cambio, eso sí, tendremos que aceptar el cierto grado de "magia" que este tipo de comportamientos  implícitos añaden a nuestras aplicaciones.

Publicado en Variable not found.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 458

octubre 18, 2021 06:05

Enlaces interesantes

Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramienta

Xamarin / .NET Maui

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Blog Bitix

Análisis del maravilloso juego horrible The Last of Us

octubre 16, 2021 09:30

Algunos juegos están enfocados a la parte de habilidad y acción con la única intención de proporcionar diversion. Otros independiente de la calidad gráfica que es muy notable en la actualidad pueden ser vistos como una forma de obra de arte al mismo nivel que una novela, una película, una canción o música, un cómic, un cuadro u otras formas de arte. Los juegos tienen varios de estas formas de arte como una historia, imágenes, secuencias cinemáticas o una banda sonora que en conjunto forman la obra. El juego The Last of Us sin lugar a dudas entra perfectamente en la categoría de obra de arte siendo uno de los mejores juegos no solo de la generación para la PS3 y PS4. Un juego con una maravillosa historia al mismo tiempo que horrible.

Aún no he jugado a muchos juegos de la PlayStation 4 que compré casi al final de su ciclo de vida, me quedan por jugar un montón de ellos de los más reconocidos de la amplia y excepcional generación de esta consola. Uno de ellos al que tenía muchas ganas era The Last of Us de Naughty Dog desarrollado para la PS3 en el año 2013 y remasterizado en el año 2014 para la PS4. Tiene una segunda parte con The Last of Us 2 publicado para la PS4 en el año 2020 que continúa la historia.

Es un juego con muy buenas críticas que ya sabía antes de jugarlo que me iba a gustar y me apetecía jugar por su estilo de juego de historia lineal para descansar de juegos largos y de mundo abierto. A veces un juego tiene buenas críticas pero por diferentes circunstancias no siempre a uno le gusta lo que a la mayoría y a veces uno está deseando un juego y también por lo que sea al final no le resulta tan interesante o entretenido como deseaba.

Ya terminado, a falta de la precuela que incluye la versión remasterizada, para mí es una maravilla de juego como ya esperaba. A pesar de la antigüedad del juego no conocía ningún detalle importante de la historia, por ejemplo no conocía como empezaba ni cómo se iba a desarrollar el juego ni ningún otro personaje, solo sabía un poco de su ambientación general y los dos personajes principales del juego que aparecen en la portada del juego, Joel y Ellie. No conocía mucho del juego porque también he tratado de evitar conocer detalles del mismo, en un juego lineal en el que la historia al mismo nivel que las partes de acción conocer un detalle importante de la trama puede cambiar mucho la percepción del juego por la falta de sorpresa.

En este artículo se comenta ningún detalle de la historia aunque sí incluye vídeos con el argumento e historia completa entre otros detalles del juego como la ubicación de los artefactos que muestran secuencias del juego, así que si se va a jugar este juego recomiendo solo ver las partes de vídeos incluidos una vez superados esos puntos del juego. El artículo solo comenta algunos detalles de cómo es la jugabilidad del juego, en que mecánicas de juego se basa y algunos pequeños consejos de estrategia que he aprendido para completarlo más fácilmente.

A veces me cuesta encender la Play y ponerme a jugar pero este al igual o más que el juego de mundo abierto Horizon Zero Dawn lo he disfrutado mucho, me ha ido atrapando cada vez más la historia y conocer cómo finaliza.

Pantalla inicial Menú

Pantalla inicial y menú del juego The Last of Us

Portada Portada

Portada

Anticipación del juego

Este artículo contiene información de estrategias para completar más fácilmente el juego, parte de la diversión de un juego es descubrir y superar los retos que se plantean por uno mismo. Sin embargo, algunos juegos son difíciles sin una pequeña ayuda que obliga a tener que dedicarles mucho más tiempo o a recomenzarlos.

En algunos juegos el argumento es una de las partes más importantes. El texto del artículo no contiene información acerca del argumento del juego, de la mitad o del final, ni hace ningún spoiler por lo que lo puedes leer sin riesgo de conocer alguna parte del argumento de forma anticipada. Sin embargo, algunos enlaces del artículo a otras páginas y vídeos sí pueden contener información del argumento de modo que recomiendo consultar solo las partes del juego una vez superadas.

Contenido del artículo

El juego

El juego se desarrolla en un mundo apocalíptico que muestra la historia de principalmente dos personajes principales Joel y Ellie junto a otros personajes en momentos puntuales. The Last of Us cuenta una maravillosa historia emocional pero al mismo tiempo horrible, dramática, violenta y cruel en la que no solo se vive en tercera persona como espectador sino que el juego te obliga a participar en ella en varias ocasiones. La historia no tiene complejos giros de guión ni detalles incomprensibles o de interpretación libre aunque no explica las cosas de forma explícita permite comprender lo que sucede con un poco de atención.

Hay una mezcla de parte jugable de exploración y acción con varias secuencias cinemáticas que guían la historia con siempre el mismo desarrollo que no depende de ninguna de las acciones del jugador. Posee varios niveles de dificultad, algunos usuarios prefieren la parte de habilidad y acción de los juegos como diversión y juegan en niveles más difíciles para que el juego sea un reto de mayor habilidad, otros usuarios la parte de los juegos que más les gusta es disfrutar de la historia y prefieren jugar en un nivel fácil o normal. También tiene un modo plus que permite volver a empezar la historia sin perder las mejoras de nivel de los personajes.

El juego para la PS4 es una versión remasterizada del original para la PS3, consta de la historia principal y una precuela Left Behind con una duración de unas 30 horas en tiempo de juego. Es un juego de historia lineal en el que no se puede volver a atrás como ocurre en los juegos de mundo abierto, cada zona es única y nueva por la que solo se transita una sola vez. Los escenarios aunque lineales con un único destino tienen pequeñas zonas que explorar y permiten afrontar las misiones de varias alternativas en el mapa según como se quieran resolver los combates. Al avanzar siempre a nuevos escenarios motiva a explorar más concienzudamente para encontrar todos los objetos y coleccionables. Una vez completada la historia por primera vez el juego muestra el número de coleccionables y artefactos que no se han encontrado para repetir el capítulo si se desea encontrarlos todos.

The Last of Us se cataloga en el género de survival horror en el que los recursos son limitados que obliga a racionarlos. Los combates han de afrontarse con una mezcla de táctica, sigilo y en ocasiones con armas de fuego. No se proporciona ningún mapa pero los escenarios no son muy amplios que simplifica su exploración necesaria para ir obteniendo recursos que se van gastando del equipo y encontrar diferentes artefactos que proporcionan mejoras. Aunque no es un juego de rol tiene varios componentes del género como la fabricación de objetos a partir de recursos, varios tipos de armas y mejora de las mismas así como una pequeña serie de mejoras en la ficha del personaje que dan cierta libertad al jugador de elegir. Aparte de acción y exploración en algunos momentos hay pequeñas mecánicas de puzzle para superar obstáculos.

Aloy en Horizon Zero Down es un personaje que siempre está dispuesto a ayudar a todos haciendo el bien en cada cosa que hace, en Last of Us los personajes son muy distintos como requiere el transfondo de la historia en la que están, a veces realizan acciones violentas sin demasiados remordimientos. Otra diferencia y detalle notable del juego es que en The Last of Us a lo largo de la historia se observa cómo evolucionan los sentimientos de los personajes. En el juego principalmente se encarna a Joel aunque avanzado el juego en algunos momentos el jugador encarna a Ellie, a veces de forma intercalada a uno u otro en una línea de tiempo paralela.

Las voces de los diálogos de los personajes están doblados al español con la posibilidad de poner subtítulos. Los diálogos son buenos y en ocasiones al llegar a un determinado sitio relevante los personajes generan alguna conversación que ambientan el juego esto les dota de cierta vida propia y más realismo. Tiene una calidad gráfica muy buena y la banda sonora está al mismo nivel. El único punto que podría mejorar es indicar el título de cada tramo de las misiones que solo aparece en el menú.

Tenía la preferencia de los juegos de mundo abierto por la libertad de movimiento pero en estos te pasas más tiempo viajando por el mapa y con una cantidad abrumadora de de misiones principales, secundarias y recados, en The Last of Us el mapa es reducido y lineal lo que permite seguir perfectamente el desarrollo de la historia que en los juegos de mundo abierto a veces se pierde. En The Last of Us nunca he perdido el hilo de por donde iba la historia por tener que realizar misiones secundarias o viajar en busca de coleccionables lo que me ha permitido disfrutar más ni olvidar detalles que en el juego de mundo abierto Horizon Zero Dawn.

En The Last of Us aunque como todo videojuego tiene una parte de acción interactiva la parte más importante del juego es la historia por eso es muy recomendable no conocer ningún detalle previo a jugarlo para mantener esa sorpresa del guión. El juego deja algunos puntos de la historia en vilo que seguramente se desarrollen y resuelvan en la segunda parte de The Last of Us 2. Como juego e historia es una obra de arte a la altura de una buena novela o película, sin dudar está entre los mejores juegos de la generación de la PS3 y PS4.

Juego Juego Juego

Juego Juego

Juego

Combates

A lo largo del juego se alterna entre pequeñas escenas cinemáticas, travesías con momentos de acción y combate sin ser tan frenético como la de un shooter. Los combates en The Last of Us no son para afrontarlos como un juego de disparos o shooter puro aunque se dispongan de varias armas de fuego, por la limitada munición y recursos, y porque en cuanto a combate los personajes no tienen la agilidad suficiente para afrontar una lucha contra varios enemigos al mismo tiempo, los personajes son torpes de movimientos. Se ha de emplear una mezcla de distracción y sigilo combinado con las armas adecuadas, las armas de fuego no es posible utilizarlas de forma continuada únicamente cuando el grupo de enemigos ya ha sufrido alguna baja con sigilo. Los combates suelen acabar con todos los enemigos muertos.

Durante la aventura aparecen varios grupos de enemigos, muchos son los propios humanos. Dentro de los diferentes grupos de los humanos en el aspecto de la historia se muestra como cada uno tiene roles diferentes, entre los diferente tipos de enemigos los humanos son casi los peores desde el punto de vista de la historia. Como ocurre en muchos juegos la inteligencia de los enemigos es limitada, solo tienen unas pocas reglas de inteligencia que conocidas permite acabar con muchos de ellos de la misma forma. Una estrategia de combate es tratar de acabar primero con algunos miembros del grupo de enemigos de forma sigilosa y ya después si se desea utilizar armas de fuego para terminar antes. Es posible recoger botellas de vídeo y ladrillos para aturdirlos temporalmente o llamar la atención de los enemigos que se acercan al lugar donde cae el objeto, si hay varios cerca todos se acercan al mismo punto y con un arma de área como un cóctel molotov o una bomba explosiva permite acabar con todos ellos de una vez, rápido y gastando pocos recursos.

Otra estrategia es saber que recursos no se han podido recoger por tener ya el inventario lleno y si hay que gastar algún recurso es mejor hacerlo de aquellos que se pueden reaprovisionar con esos que no se han podido recoger. En mi caso durante el juego me han matado muchas veces y me he dejado matar muchas veces por no gastar muchos recursos o vida, en vez de agotar todos los recursos que llevaba. En caso de perder la vida el castigo que aplica el juego no es muy severo ya que se vuelve al último punto de control con los mismos recursos que se tenían en él, además los puntos de control son numerosos con lo que no se pierde mucho progreso.

Armas, recursos, mejoras y equipo

La combinación de survival horror promueve a explorar el mapa por completo aunque sea un mapa sin demasiadas alternativas, esto encaja perfectamente con un juego lineal en el que se sabe que una vez pasado un punto ya no se puede volver a atrás al contrario de los juegos de mundo abierto.

Por una parte está que los recursos que se recogen son limitados, al mismo tiempo otra parte es que la capacidad del inventario de los personajes también es limitado con lo que a veces ocurre que no es posible recoger recursos por tener el inventario completo. Los personajes, principalmente Joel, y las armas tienen la opción de ser mejorados con píldoras y componentes dotando al juego de una pequeña forma de personalizar los personajes según las preferencias que habitualmente es una faceta que gusta en los juegos. No hay píldoras y componentes para mejorar todas las armas por completo al menos en la primera finalización del juego aún encontrando todos los recursos.

Los recursos más importantes son las píldoras para mejorar habilidades de Joel, los componentes para mejorar las armas en los bancos de trabajo que se encuentran en ocasiones en varias ubicaciones en la aventura. Otro recurso importante son los manuales de combate que permite mejorar algunas habilidades como mayor capacidad de curación o mayor área de acción en las armas. También importantes son las cajas de herramientas ya que mejorar las armas requieren tener la caja de herramientas de ese nivel para realizar la mejora.

En cuanto a recursos de equipo hay varios elementos básicos como tijeras, sujeción o cinta, trapos, alcohol, explosivos y azúcar. Con estos recursos básicos es posible fabricar diferentes objetos como dagas, botiquines de curación, cócteles molotov, explosivos o mejorar el arma cuerpo a cuerpo. Fabricar una unidad de cada uno de los objetos requiere una combinación de dos recursos básicos.

En cuanto a armas hay varios tipos cuerpo a cuerpo, armas blancas, silenciosas, de corto alcance que permiten ser usadas para combatir de forma sigilosa y por otro lado están las armas de fuego que usadas anulan el sigilo pero permiten acabar con los enemigos más rápido y a más largas distancia. Las armas blancas tienen un cierto número de usos, una vez utilizados se rompen.

Fabricar Habilidades Coleccionables

Fabricar, habilidades y coleccionables

Manuales de entrenamiento Manuales de entrenamiento Manuales de entrenamiento

Manuales de entrenamiento

Banco de trabajo Banco de trabajo

Banco de trabajo

Coleccionables

Los coleccionables son objetos que se pueden recoger a lo largo de la aventura, en este caso no suelen estar muy escondidos pero obliga a explorar cada zona del mapa de forma completa y prestar cierta atención para verlos. Lo más escondidos son algunos de los colgantes de los luciérnagas enganchados en las ramas de los árboles, es fácil no verlos ya que se suele ir mirando al suelo porque es donde están los recursos. Los coleccionables no suponen ningún beneficio o perjuicio encontrarlos o no encontrarlos más que obtener el trofeo de encontrarlos todos.

Algunos coleccionables son los colgantes de los luciérnagas, notas, abrir las puertas cerradas que suelen tener una cantidad importante de recursos. Otros coleccionables son escuchar todas las conversaciones opcionales en determinados lugares, los chistes de Ellie y los comics que cuentan una pequeña historia de superhéroes.

Los coleccionables no están especialmente escondidos, basta con explorar un poco todo el mapa aunque no sea esencial para completar la misión, esto combina muy bien con la necesidad de hacerlo para equiparse con todos los recursos posibles.

Comics Comics Comics

Comics

Colgantes de luciérnaga Colgantes de luciérnaga Colgantes de luciérnaga

Colgantes de luciérnaga

Banda sonora original

La música y banda sonora del juego The Last of Us está a la altura del juego, si el juego es una obra de arte la música es otra. La música en cierta medida suena descompasada y estridente que le da una mala sensación para acompañar a la historia horrible del juego, también es música triste y melancólica. Una vez terminado el juego escuchar una de estas canciones los traen los recuerdos y sentimientos de la historia. Estos dos primeros temas son únicos.

Película de la historia

En YouTube se encuentran vídeos de la historia de los juegos como si de una película se tratara. También, sirven para volver a ver ciertas escenas sin tener que rejugar el juego. Estos vídeos contienen detalles importantes de la historia del juego de modo que solo recomiendo ver aquellas partes que hayan sido superadas, más en un juego como este en el que la historia es casi lo principal.

https://www.youtube.com/watch?v=GBitxFoYNoE

Ubicación de los coleccionables y artefactos

Los coleccionables y artefactos no son difíciles de encontrar porque no están muy escondidos, sin embargo, hay que ser muy meticuloso para fijarse en todo y explorar todo el mapa, no es raro que algún coleccionable o artefacto pase desapercibido aún pasando no muy lejos de él. En YouTube también hay vídeos de donde se encuentran todos en un mismo vídeo o los coleccionables separados por capítulos. Al igual que la película de la historia es recomendable revisar estos vídeos únicamente superado ese punto de la historia ya que muestran detalles de los escenarios.

Referencia:

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 457

octubre 11, 2021 06:05

Enlaces interesantes

Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Blog Bitix

Razones para cambiar a GNU/Linux en vez usar Windows y macOS

octubre 10, 2021 09:00

GNU/Linux no te obliga a actualizaciones forzosas aplicadas en un momento inapropiado, tiene una licencia libre mayoritariamente gratuito al igual que sus aplicaciones disponibles para cualquier propósito y finalidad, los juegos triple A son jugables a través de Steam, es tan o más seguro que cualquier otra opción y respeta mejor la privacidad de los usuarios, es apto para ordenadores que se convierten incompatibles con otros sistemas operativos por sus nuevas versiones, altamente personalizable según las preferencias del usuario, es una buena opción para los desarrolladores y profesionales, la mayoría del hardware común no requiere instalar controladores adicionales y tiene una comunidad que aunque pequeña parece más grande por ser muy activa y colaborativa. Finalmente, es posible probarlo antes de instalarlo para estar convencido del cambio.

GNU

Linux

Para las computadoras personales hay cuatro sistemas operativos destacados, los comerciales desarrollados por empresas que son Windows de Microsoft y macOS de Apple, por otro lado están los alternativos con licencia libres como GNU/Linux formado por multitud de distribuciones desarrolladas por los propios usuarios algunas con el respaldo de una compañía, finalmente está FreeBSD siendo una opción elegida de forma más minoritaria. Estos sistemas operativos no son iguales se diferencian en el modelo de desarrollo y licencia comercial o libre, en la interfaz gráfica y coste entre otras características.

En el caso de Windows la razón de muchos usuarios de usarlo es sencillamente por ser el sistema operativo preinstalado en la computadora al comprarla, en el caso de los ordenadores mac por ser la única opción ofrecida por Apple para sus equipos. GNU/Linux domina el software de servidor por su flexibilidad y de los smartphones con Android, con el paso de los años GNU/Linux se ha convertido también en una alternativa perfectamente viable para la mayoría de usuarios tambien en el escritorio que simplemente necesitan un uso ofimático como para los más avanzados de desarrollo y de forma profesional. Hace varios lustros faltaba soporte hardware, era difícil de instalar o requería acceder a la línea de comandos para tareas básicas, hoy en día GNU/Linux es tán fácil de instalar como Windows y sus varios entornos de escritorio tan amigables como los de Windows o macOS.

En octubre del 2021 se ha lanzado al mercado Windows 11 que equiparán los equipos comerciales vendidos a partir de este momento. Como cada nueva versión los requisitos mínimos son más exigentes y dejan a muchos equipos vendidos con anterioridad sin poder actualizarse a la última versión de Windows. En Windows 11 muchos ordenadores no serán compatibles por el requerimiento del módulo TPM requerido en aras de la seguridad, por lo menos hasta que vean que mucha gente no se actualiza y se mantiene en Windows 10, Windows sufra de fragmentación y Microsoft decida relajar este u otros requerimientos.

Ubuntu openSUSE Debian

Arch Linux Fedora elementary OS

Trisquel

Logotipos de varias de las distribuciones GNU/Linux más importantes y usadas

Estas son algunas razones por las que usar GNU/Linux en vez de Windows y macOS.

Contenido del artículo

Razones para cambiar a GNU/Linux

Linux es libre y gratuito

Las licencias de Windows y macOS que deben ser aceptadas antes de realizar la instalación en realidad únicamente dan el derecho al uso del software. Un usuario que ha comprado una licencia de Windows, una computadora con Windows preinstalado o un mac con macOS no tiene acceso al código fuente lo que impide examinarlo para conocer que funciones implementa.

GNU/Linux y muchas aplicaciones para este sistema operativo tienen una licencia de software libre según la Free Software Foundation como aquel que proporciona a los usuarios las 4 libertades básicas que define la filosofía del software libre:

  • La libertad de ejecutar el programa como se desee, con cualquier propósito (libertad 0).
  • La libertad de estudiar cómo funciona el programa, y cambiarlo para que haga lo que se desee (libertad 1). El acceso al código fuente es una condición necesaria para ello.
  • La libertad de redistribuir copias para ayudar a otros (libertad 2).
  • La libertad de distribuir copias de sus versiones modificadas a terceros (libertad 3). Esto le permite ofrecer a toda la comunidad la oportunidad de beneficiarse de las modificaciones. El acceso al código fuente es una condición necesaria para ello.

Aunque software libre no es sinónimo de gratuito, mucho del software libre es gratuito. La licencia de Windows cuesta unos 150€ comprada a Microsoft y unos 15€ en páginas alternativas completamente legales, en el caso de estar preinstalado en la computadora como ocurre generalmente el precio de Windows no está separado del de la computadora, en los mac el sistema operativo macOS es parte del producto y está incluido en sus precios pero son significativamente más caros que productos equivalentes.

Linux cubre las necesidades esenciales

Muchas de las aplicaciones están disponibles tanto en Windows, macOS como en GNU/Linux siendo exactamente las mismas en cuanto a funcionalidad para el usuario como el navegador web Firefox entre muchas otras. Otras aplicaciones disponibles en Windows y macOS tiene su equivalente en GNU/Linux que cubren las necesidades de la mayoría de usuarios como el programa para manipular imágenes GIMP. Desde programas de línea de comandos como aplicaciones de interfaz gráfica.

Los entornos de escritorio integran una serie de aplicaciones sencillas sin funcionalidades avanzadas pero que cubren las necesidades básicas de los usuarios. Otras aplicaciones sirven para propósitos específicos e incluyen funcionalidades más avanzadas como ofimática y documentos, editor de texto avanzado, internet y comunicaciones, fotos y gráficos, multimedia, vídeo y audio, programación y desarrollo o seguridad y privacidad.

Los juegos mejor que nunca

Uno de los motivos por los que muchos usuarios continúan con Windows y no migran a GNU/Linux es porque una de sus necesidades es poder jugar a juegos los cuales por cuota de mercado de los sistemas operativos son lanzados para Windows, esto hace que la situación se perpetúe. El soporte para juegos en GNU/Linux ha mejorado mucho en los últimos años y sigue mejorando notablemente cada pocos meses, actualmente es posible jugar a muchos juegos triple A perfectamente y con el mismo rendimiento que en Windows.

El soporte de Steam como plataforma de juegos ha contribuido a que la mayoría de juegos de Windows estén disponibles en GNU/Linux, incluso las últimas novedades. También es posible jugar a juegos retro y arcade míticos de máquinas recreativas y consolas antiguas junto a otros de culto como los Monkey Island, Day of Tentacle y Maniac Mansion.

Sin actualizaciones forzosas

Mantener el sistema operativo y aplicaciones actualizadas a las últimas versiones es importante ya que corrigen errores de seguridad, incorporan nuevas funcionalidades o mejoran las existentes. El problema surge cuando estás actualizaciones aún con sus riesgos no son opcionales ni se tiene el control de cuando se aplican.

En los sistemas operativos comerciales Windows y macOS tanto Microsoft como Apple tienen el poder de aplicar parches de seguridad y otras actualizaciones que no son de seguridad cuando deseen sin el consentimiento del usuario. Algunas de esas actualizaciones son notificadas que deben ser aplicadas en un momento a conveniencia del usuario pero con una fecha límite en la que se instalan de forma forzosa. Algunas de las actualizaciones requieren que Windows y macOS tomen por completo el control del equipo con el único propósito de aplicar la actualización, tiempo en el que el usuario no puede utilizar la computadora que es un gran problema para muchos usuarios que los utilizan como herramienta de trabajo ya que es tiempo laboral en el que no pueden hacer sus tareas.

En las distribuciones de GNU/Linux es el usuario el que elige el momento en el que aplicar las actualizaciones de seguridad y de las aplicaciones, estas se aplican sin tomar el control del equipo lo que permite seguir trabajando, salvo algunas críticas que involucran la actualización del firmware. Algunas actualizaciones requieren reiniciar la computadora para que surtan efecto, en GNU/Linux nuevamente es el usuario el que decide el momento de hacerlo, en Windows y macOS algunas requieren reiniciar de forma inmediata haciendo que el usuario deba interrumpir y guardar las tareas que esté haciendo y documentos que esté editando para continuarlas una vez realizado el reinicio.

Linux es más seguro

No hay ningún sistema operativo exento de problemas de seguridad pero dado que la cuota de usuarios de GNU/Linux es minoritaria comparada con Windows o macOS la mayoría de ataques se dirigen a estos últimos sistemas. Por otro lado, como cada distribución GNU/Linux es diferente y con diferentes versiones de los paquetes es más difícil explotar la misma vulnerabilidad en todos ellos.

Linux protege la privacidad

Tanto Windows como macOS incluyen funcionalidades que con el consentimiento del usuario recopilan información de la computadora, de su uso, del usuario como el patrón de voz con la funcionalidad de reconocimiento los asistentes de voz y que la envían a Microsoft y Apple que la utilizan para sus propósitos o la venden a terceras partes aún siendo de forma anónima.

GNU/Linux es un sistema que no incluye este tipo de funcionalidades y que da mucha importancia a la privacidad de los usuarios y a la información que terceras partes pueden recopilar del usuario por su actividad. GNU/Linux no obtiene ningún beneficio ni está interesada en recopilar datos de sus usuarios.

Linux revive ordenadores viejos

Cada nueva versión de Windows impone unos requerimientos más restrictivos, algunos muy ajustados a las especificaciones incluso de computadoras nuevas. Cada nueva versión deja a muchas computadoras sin cumplir los requisitos mínimos para realizar la actualización, Windows 11 por ejemplo requiere un módulo de seguridad TPM que incluso computadoras con dos años de antigüedad y en el momento de salida no lo cumplen, impidiendo su actualización a la última versión. Tanto Apple como Microsoft suelen ofrecer amplios tiempos de soporte de parches de seguridad y corrección de errores, en cualquier caso el periodo de soporte lo deciden ellos con sus propios intereses y no el de sus usuarios. En GNU/Linux hay distribuciones específicas con requerimientos más modestos para computadoras antiguas.

Algunos fabricantes de computadoras con Windows incluyen numerosos programas adicionales que se mantienen en ejecución en segundo plano consumiendo recursos, reciben el calificativo de blotware y hacen que incluso una computadora nueva ofrezca un rendimiento inferior al que debería.

Linux es altamente personalizable

No hay un único GNU/Linux, los desarrolladores de cada distribución eligen los principios en los que se basa, el entorno de escritorio, los componentes, paquetes de software y aplicaciones que selecciona en la instalación por defecto.

Algunas distribuciones están orientadas a tener gran estabilidad durante largos periodos de tiempo en los que se solo se actualizan para corregir fallos de seguridad y errores importantes pero generalmente no actualizando a nuevas versiones de los paquetes, un ejemplo es Debian y otro las versiones LTS de Ubuntu.

Otras distribuciones están orientadas a usuarios que desean obtener las últimas versiones de cada paquete cuando son publicadas que incorporan nuevas funcionalidades, mejoras de rendmiento o mejor compatibilidad con hardware nuevo. Aunque también son estables a veces requieren algunos cambios o incompatibilidades, un ejemplo de estas distribuciones en constante cambio denominadas rolling release es Arch Linux.

openSUSE tiene una versión para los usuarios que buscan estabilidad con Leap y usuarios que buscan actualizaciones con Tumbleweed. Otras distribuciones están destinadas a equipos antiguos con recursos más modestos que no requieren compatibilidad con el último hardware o versiones destinadas a no incluir ningún componente privativo, un ejemplo de este tipo de distribuciones es Triquel.

Como distribuciones hay muchas los usuarios de GNU/Linux eligen la que más se adapta a sus necesidades, aparte de las categorías anteriores hay muchas otras por ejemplo aquellas destinadas a los usuarios que proporcionan un primer contacto con GNU/Linux con una experiencia de usuario tán fácil como la de Windows y macOS. Que haya muchas distribuciones no quiere decir que haya una para cada necesidad, algunas distribuciones son adecuadas para usuarios distintos, por ejemplo Ubuntu es adecuada tanto para ser la primera distribución de GNU/Linux usada como para usuarios con grandes conocimientos de GNU/Linux y llevan usándolo muchos años. Elegir una distribución no es algo definitivo, si en algún momento las necesidades del usuario cambian muchos cambian a otra que se ajuste mejor a sus nuevas necesidades.

Windows y macOS ofrecen un único entorno de escritorio para todos los usuarios, el escritorio único evita la fragmentación y que los usuarios no tengan que aprender uno nuevo pero obliga a hacer concesiones para intentar satisfacer a mayoría aunque no a todos, solo admiten algunas pocas opciones de personalización cosméticas.

Linux es una buena opción para desarrolladores

Una categoría adicional a las anteriores de tipos de usuarios es el de los desarrolladores de software. Aunque GNU/Linux en el escritorio tiene una pequeña cuota de uso ocurre exactamente lo contrario en los servidores de las compañías que proporcionan servicios en internet. La mayor parte de los servidores están basados en GNU/Linux por su flexibilidad, seguridad y ahorro en costes de licencias.

Cualquier persona que desee aprender sobre desarrollo tiene a su disposición gran cantidad de software libre y recursos de documentación para el aprendizaje sin necesidad de pagar costosas licencias de software, el mismo software que usan muchas de las grandes tecnológicas. El uso de GNU/Linux en las empresas permite optar a trabajos de tecnología altamente cualificados y en muchas empresas bien remunerados con oportunidades importantes para la carrera profesional.

GNU/Linux es el sistema operativo de software libre con más éxito y utilizado por muchos desarrolladores que crean herramientas de desarrollo de software libre para otros desarrolladores desde lenguajes de programación, compiladores y entornos integrados de desarrollo hasta servidores. Esto hace que sea fácil encontrar herramientas para desarrolladores en este sistema operativo.

Incluso otros profesionales dedicados a la tecnología o que utilizan tecnología para su trabajo tiene programas equivalentes a los propietarios en otras plataformas, GIMP para la edición de imágenes, Inkscape y Blender para para el diseño artístico de imágenes vectoriales y modelado en tres dimensiones, programas para la composición musical para los que PipeWire resuelve varios de los problemas que tenían los artistas musicales en GNU/Linux, varias suites ofimáticas alternativas como LibreOffice, OnlyOffice y WPS Office alternativas a Microsoft Office.

La comunidad de Linux

Aunque la cuota de uso de GNU/Linux es pequeña la comunidad da la sensación de ser muy grande por ser muy activa, comparte información y tutoriales en numerosos artículos, vídeos, podcast y foros. Hay muchas personas dispuestas a ayudar GNU/Linux como este blog y otros varios que incluyo en la sección Enlaces a otros blogs y webs, aún así seguramente otro usuario haya tenido el mismo problema antes y la respuesta con la solución haya sido contestada que se puede encontrar con una sencilla búsqueda en internet.

Por dónde empezar

Si estos motivos te convencen o estás dispuesto a investigar más los siguientes artículos son de ayuda para saber por dónde empezar.

Arch Linux con el entorno de escritorio GNOME

Arch Linux con el entorno de escritorio GNOME

» Leer más, comentarios, etc...

Blog Bitix

Sincronización de hora automática con el protocolo NTP en GNU/Linux

octubre 07, 2021 06:00

Muchos dispositivos electrónicos entre ellos las computadoras tienen un reloj interno que continúa funcionando aún estando apagados y desconectados de la red electrica gracias a una batería en forma de pila de botón. La fecha y la hora permiten conocer cuando se modifica un documento pero también otros procesos más importantes que también dependen del tiempo como el segundo factor de autenticación y los códigos temporales basados en el tiempo. Los relojes internos usados en las computadoras son bastante imprecisos y se adelantan o retrasan con respecto a la hora oficial, algunos dispositivos electrónicos ni siquiera tienen el hardware de un reloj interno. El protocolo Network Time Protocol o NTP permite a las computadoras obtener la hora de un servidor que ofrezca este servicio de forma regular para evitar diferencias significativas, algunos de estos servidores están respaldados por una organización gubernamental que ofrecen la medida del tiempo oficial de un país y que poseen relojes atómicos mucho más precisos.

Linux

Conocer la hora y fecha actual es importante también en las computadoras y programas para algo tan simple como conocer cuál fue la fecha de creación o modificación de un archivo o algo más importante como conocer la fecha de una compra o transacción bancaria. Las computadoras son capaces de mantener la hora y fecha gracias a una pequeña batería como fuente de energía como una pila de botón habitualmente una CR2032 y un reloj que sigue funcionando cuando la computadora está apagada y desconectada de toda fuente de energía. La pila de botón es suficiente para mantener el reloj interno de la computadora y otros dispositivos electrónicos funcionando durante más de 10 años.

Otra funcionalidad importante que depende del tiempo es el segundo factor de autenticación o 2FA. Para mayor seguridad es muy recomendable configurar el segundo factor de autenticación en los principales servicios como correo electrónico junto a otros servicios que tratan con información personal o datos bancarios. El segundo factor de autenticación o TOTP es un código adicional a la contraseña a introducir al autenticarse en un servicio. El TOTP normalmente está formado por unos seis dígitos que solo es válido durante un pequeño espacio de tiempo de unos 30 segundos, pasado este tiempo deja de ser válido y otro código pasa a ser válido. En caso de que el dispositivo que genera el código no posea el tiempo actual genera códigos inválidos.

Sin embargo, algunos dispositivos como la Raspberry Pi no posee un reloj interno con lo que no son capaces de mantener la hora cuando están apagados. Por otro lado, los relojes de las computadoras no son totalmente precisos y durante periodos de tiempo prolongados cada uno mide el paso del tiempo de forma diferente, el tiempo proporcionado por cada uno varía en unos pocos milisegundos o segundos. Estas variaciones de tiempo aunque pequeñas son suficientes para no ser válidas en ciertos propósitos que necesitan marcas de tiempo totalmente precisas. Enviar un correo electrónico de respuesta con una fecha anterior al original es una paradoja temporal, posiblemente inocua en correo electrónico pero un problema en una transacción financiera. Los relojes más precisos son los atómicos que tardan en desfasarse un segundo al cabo de 52 millones de años. El motivo por el que se utilizan relojes de cuarzo en los ordenadores en vez de atómicos más precisos es que los primeros son más baratos, más pequeños y con menor consumo, los relojes de cuarzo ofrecen una precisión suficiente durante más o menos un pequeño periodo de tiempo.

Los dispositivos que no tienen un reloj interno mantiene la hora actual utilizando el protocolo Network Time Protocol o NTP, los dispositivos que tiene reloj interno pero se quieren mantener mayor precisión pueden utilizar este protocolo para mantener yna medida del tiempo más precisa. Hay servidores que ofrecen la hora a través del protocolo NTP tomando como fuente el tiempo proporcionado por un organismo gubernamental siendo la autoridad encargada de proporcionar esta medida considerada la oficial. En España es el Real Instituto y Observatorio de la Armada ubicado en Cádiz el que mantiene la hora oficial, este organismo posee un reloj atómico y ofrece el servicio de NTP, en su página web en la Sección de Hora proporciona más detalles.

Hace unos días intenté entrar en mi cuenta de PayPal en la que tengo configurada el segundo factor de autenticación, resulta que mi ordenador que no lo tenía configurado con NTP tenía una diferencia de tiempo de dos minutos con la real. Esta diferencia de tiempo era suficiente para que el código no fuera válido utilizando el cliente KeePassXC para guardar las contraseñas y que también utilizo para generar los códigos TOTP del segundo factor de autenticación. El resultado fué que la cuenta de PayPal se me bloqueó de forma temporal como medida de seguridad para evitar ataques de fuerza bruta después de varios intentos.

No tenía activado el servicio NTP proporcionado por el gestor de servicios systemd, lo activé de forma fácil en el panel de configuración de GNOME.

Contenido del artículo

Sincronización de hora en GNOME

Para activar el servicio NTP en GNOME basta con acceder al panel de configuración en la sección de Fecha y hora y activar la opción Fecha y hora automáticas. Esta opción hace lo mismo que desde la línea de comandos.

Configuración de fecha y hora en GNOME

Configuración de fecha y hora en GNOME

Sincronización de hora desde línea de comandos

El gestor de servicios systemd proporciona un servicio para mantener sincronizada la fecha y hora con el protocolo NTP. Los siguientes comandos permiten conocer el estado del servicio y la información de la fecha y hora del sistema.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ systemctl status systemd-timesyncd.service
$ systemctl status systemd-timesyncd.service
● systemd-timesyncd.service - Network Time Synchronization
     Loaded: loaded (/usr/lib/systemd/system/systemd-timesyncd.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2021-10-07 19:09:48 CEST; 51min ago
       Docs: man:systemd-timesyncd.service(8)
   Main PID: 355 (systemd-timesyn)
     Status: "Initial synchronization to time server 193.182.111.142:123 (2.arch.pool.ntp.org)."
      Tasks: 2 (limit: 38345)
     Memory: 2.1M
        CPU: 65ms
     CGroup: /system.slice/systemd-timesyncd.service
             └─355 /usr/lib/systemd/systemd-timesyncd

oct 07 19:09:48 archlinux systemd[1]: Starting Network Time Synchronization...
oct 07 19:09:48 archlinux systemd[1]: Started Network Time Synchronization.
oct 07 19:10:18 archlinux systemd-timesyncd[355]: Initial synchronization to time server 193.182.111.142:123 (2.arch.pool.ntp.org).
systemctl-timesyncd-status.sh
1
2
3
4
5
6
7
8
$ timedatectl
               Local time: jue 2021-10-07 20:01:59 CEST
           Universal time: jue 2021-10-07 18:01:59 UTC
                 RTC time: jue 2021-10-07 18:01:59
                Time zone: Europe/Madrid (CEST, +0200)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no
timedatectl.sh

El siguiente comando ofrece información más detallada, incluye el servidor NTP utilizado y el intervalo de tiempo entre sincronizaciones.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ timedatectl timesync-status
       Server: 193.182.111.142 (2.arch.pool.ntp.org)
Poll interval: 34min 8s (min: 32s; max 34min 8s)
         Leap: normal
      Version: 4
      Stratum: 2
    Reference: C0248F96
    Precision: 1us (-20)
Root distance: 39.024ms (max: 5s)
       Offset: -1.968ms
        Delay: 76.516ms
       Jitter: 10.264ms
 Packet count: 7
    Frequency: +23,154ppm
timesyncd-timesync-status.sh

Habilitar el servicio indica que el servicio se inicie como parte del inicio del sistema, si el servicio está parado se puede iniciar de forma explícita.

1
2
$ systemctl enable systemd-timesyncd.service
$ systemctl start systemd-timesyncd.service
systemctl-timesyncd.sh

El servidor NTP utilizado para realizar la sincronización de la fecha y la hora usa la configuración personalizada del archivo /etc/systemd/timesyncd.conf en el que se incluye el dominio del servicio. Sin haber realizado cambios en el archivo todos los valores de está configuración están comentados. Los valores por defecto de esta configuración se definen en tiempo de compilación, en el caso de los paquetes de Arch Linux en el archivo PKGBUILD de systemd.

En este archivo se pone el servidor NTP deseado si es otro distinto del por defecto como el proporcionado por el Real Instituto y Observatorio de la Armada, hora.roa.es y minuto.roa.es, u otro de los proporcionados por la organización de voluntarios NTP Pool Project que utilizan muchas distribuciones incluida Arch Linux y dispositivos en internet como routers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it under the
#  terms of the GNU Lesser General Public License as published by the Free
#  Software Foundation; either version 2.1 of the License, or (at your option)
#  any later version.
#
# Entries in this file show the compile time defaults. Local configuration
# should be created by either modifying this file, or by creating "drop-ins" in
# the timesyncd.conf.d/ subdirectory. The latter is generally recommended.
# Defaults can be restored by simply deleting this file and all drop-ins.
#
# See timesyncd.conf(5) for details.

[Time]
#NTP=
#FallbackNTP=0.arch.pool.ntp.org 1.arch.pool.ntp.org 2.arch.pool.ntp.org 3.arch.pool.ntp.org
#RootDistanceMaxSec=5
#PollIntervalMinSec=32
#PollIntervalMaxSec=2048
timesyncd.conf

» Leer más, comentarios, etc...

Variable not found

Directivas using globales en C# 10

octubre 05, 2021 06:05

.NET CoreSeguro que estáis acostumbrados a ver y escribir las, a veces extensas, listas de directivas using encabezando vuestros archivos de código fuente C#. Aunque ciertamente los entornos de desarrollo actuales son de bastante ayuda a la hora de introducirlos e incluso a veces nos permiten colapsarlos, son unos grandes consumidores del valioso espacio vertical de nuestros monitores:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// ... (muchos using más)

namespace MyApplication
{
public class MyClass
{
...
}
}

Pero si habéis trabajado con Razor, ya sea para crear vistas MVC/Razor Pages o bien desde Blazor, seguro que os habrán encantado los archivos tipo _ViewImports.cshtml o _Imports.razor, pues permiten introducir directivas que se apliquen a todos los archivos Razor del proyecto. 

¿No sería buena idea llevar esto mismo a nivel del lenguaje?

Pues sí que lo es, y de hecho es una de las útiles características que han sido introducidas en C# 10, disponible a partir de la llegada de .NET 6. A partir de esta versión, es posible utilizar la directiva global using para referenciar namespaces en un único punto del código, evitando así su introducción en el resto del proyecto.

Para utilizar esta característica, basta con insertar en el encabezado de cualquier archivo C# los using que nos interesen que estén presentes en el resto de archivos .cs del proyecto:

global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Text;
global using System.Threading.Tasks;
...

Obviamente, en lugar de usar la directiva global using de forma descontrolada, podríamos unificarlas todas en un archivo llamado, por ejemplo, GlobalUsings.cs en la carpeta raíz del proyecto, centralizando así su declaración.

En la práctica, la introducción de esas directivas globales será equivalente a haber introducido esas líneas en cada uno de los archivos .cs del proyecto. De hecho, si en algún archivo individual repitiésemos uno de los using, Visual Studio nos avisaría y nos facilitaría la eliminación de la redundancia:

El IDE avisando que la directiva using no es necesaria

Por último, es interesante saber que, además de definir using globales desde código, podemos hacerlo usando el archivo de configuración .csproj:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
...
</PropertyGroup>
<ItemGroup>
<Using Include="System" />
<Using Include="System.Text" />
<Using Include="System.Threading.Tasks" />
</ItemGroup>
</Project>

En definitiva, sin duda una buena opción para ahorrarnos pulsaciones de teclas y dejar nuestro código fuente cada vez más ligero.

Publicado en Variable not found.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 456

octubre 04, 2021 06:05

Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

    Machine learning / IA / Bots

    Web / HTML / CSS / Javascript

    Visual Studio / Complementos / Herramientas

    Xamarin / .NET Maui

    Otros

    Publicado en Variable not found.

    » Leer más, comentarios, etc...

    Blog Bitix

    Escapar símbolos especiales en una expresión regular en Java

    septiembre 30, 2021 09:00

    Java

    Las expresiones regulares son cadenas formados por una serie de caracteres y combinación de ellos que se interpretan de forma especial. Las expresiones regulares permiten expresar mediante un patrón ocurrencias en una cadena, permiten encontrar coincidencias y validar que una cadena cumple el patrón de la expresión regular. El patrón de las expresiones regulares .

    Así el caracter ^ en una expresión regular indica el inicio de la cadena, $ el final, el . cualquier caracter, un grupo seguido + una o más ocurrencias y un grupo seguido de * cero o más ocurrencias. Hay muchos otros caracteres especiales que puede contener una expresión regular.

    En el caso de incluir alguno de los caracteres especiales en una expresión regular estos son interpretados de forma especial, para que un caracter especial o grupo de caracteres no sea interpretado sino tratado como un literal este debe ser escapado. Para escapar una palabra en una expresión regular en Java basta con utilizar el método Pattern.quote().

    La expresión regular (^1+$) cumple con todas las cadenas que estén formadas con al menos un caracter del número 1. En caso de querer buscar las cadenas que contengan el grupo de caracteres (^1+$) que empiecen por a y acaben por b el código sería el siguiente.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    import java.util.regex.Pattern;
    
    public class Main {
    
        public static void main(String[] args) {
        	String string = "aaa(1+)bbb";
        	String regexpQuoted = String.format("a+%sb+", Pattern.quote("(1+)"));
            System.out.printf("Quoted (regexp: \"%s\", string: \"%s\"): %s%n", regexpQuoted, string, Pattern.matches(regexpQuoted, string));
        }
    }
    Main-1.java

    En la cadena de la expresión regular se observa que el método quote ha insertado varios caracteres antes y después de la cadena escapada (1+).

    1
    2
    
    Quoted (regexp: "a+\Q(1+)\Eb+", string: "aaa(1+)bbb"): true
    
    
    System.out-1

    En caso de no escapar el grupo de caracteres (^1+$) no se encontraría la coincidencia porque los caracteres ( ) especiales de grupo y el + son interpretados por la expresión regular.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    import java.util.regex.Pattern;
    
    public class Main {
    
        public static void main(String[] args) {
        	String string = "aaa(1+)bbb";
        	String regexpUnquoted = "a+(1+)b+";
            System.out.printf("Unquoted (regexp: \"%s\", string: \"%s\"): %s%n", regexpUnquoted, string, Pattern.matches(regexpUnquoted, string));
        }
    }
    Main-2.java
    1
    2
    
    Unquoted (regexp: "a+(1+)b+", string: "aaa(1+)bbb"): false
    
    
    System.out-2

    Escapar una palabra es importante si con ella se construye una expresión regular y esta proviene de un campo de entrada del usuario o de otro sistema. No hacerlo posibilita una forma de inyectar en la expresión regular un patrón que sea interpretado y podría ser un problema de seguridad.

    El ejemplo se puede probar con una de las novedades introducidas en Java 11 para ejecutar un programa desde el código fuente sin requerir compilación.

    » Leer más, comentarios, etc...

    Bitácora de Javier Gutiérrez Chamorro (Guti)

    Los orígenes de Borland

    septiembre 30, 2021 10:58


    Me sorprende, de forma agradable, como esto de la retro-informática se ha ido popularizando tanto, que ahora hasta en la Wikipedia aparece la historia antigua del software. Borland Ltd. fue fundada en agosto de 1981 por tres ciudadanos de Dinamarca (Niels Jensen, Ole Henriksen y Mogens Glad), con el objetivo de desarrollar productos como Word …

    Los orígenes de Borland Leer más »



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

    » Leer más, comentarios, etc...

    Variable not found

    ¿Por qué llamamos "uppercase" y "lowercase" a mayúsculas y minúsculas?

    septiembre 28, 2021 08:00

    Uppercases y lowercases, esos grandes desconocidos :D

    Como en otras ocasiones, seguro que debía ser de los pocos que quedaban en este planeta sin saber esto, pero me lo he encontrado mientras pululaba por internet y me ha parecido de lo más curioso. Y como con toda probabilidad habrá por ahí algún despistado más, me ha parecido interesante comentar por aquí mi último gran descubrimiento ;)

    La cuestión es que en nuestro mundillo solemos utilizar frecuentemente los términos uppercase y lowercase para referirnos a mayúsculas y minúsculas. Pensaba, erróneamente, que los prefijos upper y lower tenían que ver con el tamaño de los caracteres, y jamás me había dado por preguntarme el por qué de este sufijo "case" tan caprichoso que estas palabras comparten.

    Revisor comprobando planchas de páginas listas para ser impresas sobre un rollo de papelPues bien, resulta que estos términos vienen desde los tiempos de la prensa impresa, donde los caracteres o letras era pequeñas piececillas con relieve, normalmente de metal, que se usaban para componer los "moldes" que, convenientemente entintados, eran aplicados sobre la superficie deseada para imprimir su contenido.

    Estas piececillas eran guardadas en un par de cajones, que se colocaban uno encima de otro. Cada cajón estaba a su vez dividido en pequeños compartimientos, uno para cada carácter, cuyo tamaño y disposición dependían de su frecuencia de uso. Los caracteres más utilizados usaban compartimentos mayores y se colocaban en la parte inferior, más accesible para las personas que los manejaban.

    La siguiente fotografía muestra una distribución frecuente, y justamente después vemos un par de cajones utilizándola:

    Organización de los caracteres en los cajones
    Organización de los caracteres en los cajones. Imagen: Wikipedia

    Cajones de caracteres siguiendo la organización anterior
    Cajones de caracteres siguiendo la organización anterior. Imagen: Wikipedia

    Como veis en las imágenes anteriores, las letras mayúsculas estaban siempre situadas lejos del usuario, en el cajón de arriba, porque eran utilizadas con menor frecuencia, mientras que las minúsculas en el de abajo, para tenerlas más a mano.

    Seguro que podréis deducir el resto, porque aquí es donde acaba nuestra historia: los términos uppercase y lowercase hacen referencia a la ubicación de los compartimentos o cajitas (cases) donde se encontraban las letras, en el cajón superior (upper) o inferior (lower).

    Qué cosas, ¿verdad?

    Referencias:

    Publicado en Variable not found.

    » Leer más, comentarios, etc...

    Blog Bitix

    Contenedores en macOS con Lima y minikube

    septiembre 25, 2021 09:00

    Para ejecutar contenedores en macOS hay dos opciones entre las posibles, una usar minikube que aunque su objetivo es ejecutar un cluster de Kubernetes permite usarlo para ejecutar contenedores de Docker directamente, la segunda opción es lime que es una opción similar que usa containerd en vez de Docker. La empresa encargada del desarrollo de Docker ha anunciado que el producto Docker Desktop pasa a requerir una licencia de pago para organizaciones empresariales grandes. Docker Desktop es usado tanto en Windows como en macOS para ejecutar contenedores de Docker con la opción también de ejecutar un cluster de Kubernetes, minikube y lima para macOS son una alternativa a Docker Desktop.

    minikube

    containerd

    Docker

    Docker es una tecnología para ejecutar procesos en contenedores nativa de GNU/Linux, los contenedores en Linux son simplemente un proceso más del sistema no utilizan virtualización con lo que requieren menos recursos, se inicia mucho más rápido que una máquina virtual y están aislados de otros procesos de sistema. Al usar tecnologías específicas de Linux no es posible ejecutarlo de forma nativa ni en Windows ni en macOS, en estos para usar contenedores hay que recurrir a la virtualización. Aún siendo una tecnología de GNUL/Linux hay necesidad de ejecutarlo en otros sistemas operativos para obtener los mismos beneficios aún siendo con virtualización.

    La empresa propietaria y encargada del desarrollo de Docker ha anunciado que Docker Desktop usada para ejecutar Docker en Windows y macOS va a ser una opción de pago si se usa en un ámbito profesional en grandes organizaciones, para uso personal continúa siendo gratuito en su versión Docker Personal.

    Docker Desktop is free to use, as part of the Docker Personal subscription, for individuals, non-commercial open source developers, students and educators, and small businesses of less than 250 employees AND less than $10 million in revenue. Commercial use of Docker Desktop at a company of more than 250 employees OR more than $10 million in annual revenue requires a paid subscription (Pro, Team, or Business) to use Docker Desktop.

    Mucho del software de código abierto y libre es gratuito, sin embargo, es mantenido en algunos casos por personas en su propio tiempo personal con poco soporte económico. En el caso de Docker Desktop la empresa propietaria seguramente ha tomado esta medida como una vía para monetizar y financiar el desarrollo de Docker. Dada la popularidad que Docker ha adquirido en los últimos años muchas empresas basan de forma importante sus entornos de producción o desarrollo en Docker.

    Docker Desktop permite ejecutar contenedores de Docker en sistemas distintos a GNU/Linux como macOS y Windows, incluye otras herramientas como Docker Compose y se integra con Docker Hub para almacenar imágenes, también integra una versión de Kuberntes para orquestar contenedores.

    El cambio en el acuerdo de uso de Docker Desktop obliga a los usuarios de Apple macOS que no cumplan los criterios para usar la suscripción Personal han de pagar por su uso o buscar una alternativa. Para uso personal uso la distribución Arch Linux pero por política de empresa en el trabajo estoy obligado a usar macOS con lo que he tenido que buscar una alternativa para seguir usando Docker. En el caso de macOS dos de las opciones como alternativa a Docker Desktop son minikube y Lima.

    Contenido del artículo

    Contenedores de Docker y Kuberntes con Minikube en macOS

    minikube tiene el objetivo de crear un cluster de Kubernetes rápidamente en GNU/Linux, Windows y macOS, pero dado que usa Docker el comando docker puede interaccionar con la instancia de minikube. Docker es una herramienta nativa de GNU/Linux en el caso de macOS y Windows usa virtualización, en estos dos sistemas operativos Docker se ejecuta en una máquina virtual con GNU/Linux y se proporcionan una serie de integraciones para el reenvío de puertos entre la máquina anfitrión y la máquina virtual con los contenedores.

    El gestor de paquetes homebrew para macOS permite instalar fácilmente minikube simplemente con un comando. minikube y Kubernetes utilizan Docker para la ejecución de contenedores, aunque el propósito de minikube es ejecutar aplicaciones en contenedores permite el uso de las herramientas de línea de comandos docker y docker-compose.

    1
    2
    
    $ brew install minikube
    $ brew install docker docker-compose
    
    minikube-install.sh

    Una vez instalado minikube hay que iniciar la máquina virtual con el siguiente comando.

    1
    2
    
    $ minikube start
    
    
    minikube-start.sh

    Para que la herramienta de línea de comandos interaccione con el servidor de Docker en la máquina virtual de minikube el siguiente comando permite establecer varias variables de entorno.

    1
    2
    
    $ eval $(minikube docker-env)
    $ docker images
    
    minikube-docker-env.sh

    Hecha la configuración previa iniciar un contenedor de Docker es exactamente igual que en GNU/Linux de forma nativa, en realidad el comando docker interacciona con el servidor de Docker en la máquina virtual. Por otro lado dado que los contenedores de Docker se ejecutan dentro de la máquina virtual y los puertos se exponen en ella es necesario acceder a la máquina virtual es con su dirección IP, dirección IP proporcionada por el siguiente comando y si se desea añadirla al archivo /etc/hosts con un nombre de dominio.

    1
    2
    
    $ docker run --rm -p 8080:80 nginx:latest
    
    
    minikube-docker-run.sh
    1
    2
    
    $ minikube ip
    $ echo "`minikube ip` docker.local" | sudo tee -a /etc/hosts > /dev/null
    
    minikube-ip.sh

    Para destruir la máquina virtual que usa minikube hay otro comando.

    1
    2
    
    $ minikube stop
    $ minikube delete
    
    minikube-delete.sh

    Contenedores de Docker con Lima en macOS

    Aunque minikube permite ejecutar contenedores de Docker directamente su propósito es ejecutar recursos de Kubernetes como pods en vez de directamente contenedores. Para el caso de simplemente contenedores de Docker minikube no ofrece reenvío de puertos y compartición de archivos entre la máquina virtual y local que requiere hacer el paso adicional del comando minikube-ip.sh.

    Otra opción en macOS alternativa a Docker en macOS es Lima, denominada a sí misma como macOS subsystem for Linux parafraseando el Windows subsystem for Linux o WSL de Windows. El funcionamiento de Lima es también a través de una máquina virtual en la que se ejecutan los contenedores, la ventaja de Lima es que ofrece compartición de archivos entre la máquina local y la virtual y reenvío de puertos de forma automática de modo que la experiencia de uso es similar a Docker en GNU/Linux.

    Lima como entorno de ejecución de contenedores utiliza containerd, pero dado que las imágenes de los contenedores de Docker de DockerHub siguen el estándar OCI son compatibles con containerd lo que permite usar exactamente las mismas imágenes.

    Lima se instala también con un comando usando homebrew.

    1
    2
    
    $ brew install lima
    
    
    lima-install.sh

    La máquina virtual con GNU/Linux y docker en la que se ejecutan los contenedores con el siguiente comando.

    1
    2
    3
    
    $ limactl start
    $ lima uname -a
    Linux lima-default 5.11.0-34-generic #36-Ubuntu SMP Thu Aug 26 19:22:09 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
    lima-start.sh

    Para ejecutar un contenedor el comando a usar es lima nerdctl, el resto de opciones y parámetros son los mismos a los habituales a los del comando docker están en la documentación de comandos de nerdctl. En este caso se inicia un contenedor del servidor web Nginx que se se accede con la dirección http://localhost:8080.

    1
    2
    
    $ lima nerdctl run --rm -p 8080:80 nginx:latest
    
    
    lima-run.sh

    Para destruir la máquina virtual que usa lima hay otro comando.

    1
    2
    
    $ limactl stop
    $ limactl delete
    
    lima-delete.sh

    nerdctl soporta muchos de los subcomandos de docker tanto para la ejecución de contenedores, gestión, construcción, gestión de imágenes, gestión de volúmenes y de Docker Compose.

    Imágenes de contenedores con lima en macOS Ejecución de un contenedor con lima en macOS

    Ejecución de un contenedor con lima en macOS

    » Leer más, comentarios, etc...

    Bitácora de Javier Gutiérrez Chamorro (Guti)

    WordPress y el problema con wp_options

    septiembre 24, 2021 07:24


    Con un tráfico en crecimiento la página funcionaba cada vez con más lentitud y de vez en cuando daba errores. Así que pagando más, he migrado a un nuevo alojamiento en forma de VPS (Virtual Private Server), aunque probablemente no lo recordéis, fue unos meses atrás cuando realicé todo el proceso y que hubo algunas …

    WordPress y el problema con wp_options Leer más »



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

    » Leer más, comentarios, etc...

    Blog Bitix

    Planificar procesos periódicos y scripts con Nomad

    septiembre 23, 2021 05:00

    Los pequeños scripts aún con su pequeña función específica, sus pocas líneas de código y breves instantes de ejecución limitados a segundos, minutos u pocas horas al día o a la semana realizan tareas importantes dentro de todas las tares de las que se compone un sistema computacional completo. Al igual que cualquier otro proceso requieren de un entorno de ejecución pero dada su naturaleza breve hace que dedicar una máquina reservada que estará infrautilizada en exclusiva para ellos sea ineficiente además de un coste en la infraestructura. En vez de dedicar una máquina en exclusiva para uno o unos pocos scripts el orquestador de procesos Nomad permite planificarlos en alguna de las instancias de computación existentes del cluster ya sea como procesos del sistema o dentro de contenedores.

    HashiCorp Nomad

    Con mucha seguridad surge la necesidad de ejecutar de forma periódica un pequeño proceso o script completamente autónomo con el que no interacciona ningún usuario o administrador pero realiza una función esencial para el funcionamiento de una aplicación u organización. Las funciones o tareas de los scripts son muy diversas desde generar informes en formato CSV o generador documentos PDF a enviar correos electrónicos, para ello quizá se conecten a bases de datos relacionales o NoSQL o realizar llamadas usando REST a APIs de aplicaciones propias o de otras organizaciones, incluso un script con la complejidad suficiente varias cosas de todo esto.

    Los lenguajes habitualmente usados para realizar tareas de scripting son dinámicos e interpretados por no requerir ser compilados y ejecutables desde el código fuente. En la plataforma Java una opción como lenguaje de scripting es Groovy y en otros lenguajes son propio intérprete de comandos Bash o Python, aunque el lenguaje Java con JBang o Gradle es una opción para realizar tareas de scripting con las ventajas de ser un lenguaje compilado con la asistencia de código de los entornos integrados de desarrollo o IDE.

    Una vez creado un script este ha de ser ejecutado en un sistema, dependiendo de las dependencias como fuentes de datos que tenga el script la ejecución se puede realizar de forma manual en la propia computadora de un usuario o desarrollador. Por el contrario, como cualquier otra aplicación requiere su propio entorno de ejecución en producción, la forma más simple con una expresión cron como un proceso del sistema habiendo aprovisionado previamente el entorno y las dependencias necesarias del mismo. Otra propiedad de muchos scripts es que su tiempo de ejecución no es continuo durando unos pocos minutos u alguna hora realizan su atarea y terminan hasta la siguiente ejecución, al contrario que las aplicaciones con funciones de servidor que están continuamente funcionado por si algún cliente se conecta. En función de las necesidades del script su ejecución se planifica con una expresión cron cada minuto, hora, día o una vez a la semana.

    Ejemplo de planificar procesos o scripts de forma periódica con Nomad

    Una de las ventajas de la computación en la nube es poder crear máquinas virtuales cuando se necesitan y destruirlas cuando ya no son necesarias, de forma rápida y bajo demanda. Los contenedores permiten ejecutar procesos en cualquier máquina que soporte para la ejecución de contenedores, esto permite que un proceso no dependa de una máquina física o virtual en concreto.

    Nomad de HashiCorp es un orquestador de procesos y contenedores, en lo relativo al tema de este artículo permite planificar no solo tareas para mantenerse en ejecución de forma constante e indefinida sino también planificar una tarea de forma periódica y temporal. Una ventaja de Nomad sobre una opción alternativa como Kuberntes es que permite planificar no solo contenedores de Docker sino también contenedores Podman o procesos del sistema a partir de comandos existentes en el mismo. Otra ventaja de Nomad es que es simplemente un orquestador de procesos de fácil instalación y uso al constar de únicamente un binario, se integra con otras herramientas como Consul para el registro y descubrimiento y conectividad y Vault para seguridad.

    Desde luego crear una infraestructura basada en un cluster de servidores Nomad para planificar un script es un esfuerzo innecesario teniendo en cuenta que la opción de una máquina con un cron es más sencillo. Sin embargo, a poco complejo que sea un entorno para prestar un servicio que conste de más de una decena de instancias computacionales, la opción del cluster de Nomad permite utilizar la misma infraestructura para cualquier tipo de carga de trabajo, además siendo una infraestructura con la opción de proporcionar autoservicio permite conseguir algunos de los ideales propuestos de la cultura DevOps y detallados en The three ways y The five ideals de los libros The DevOps Handbook, The Phoenix Project y The Unicorn Project.

    En este ejemplo se muestran la definición de dos tareas o jobs para Nomad, una como definida como un proceso del sistema a partir de los comandos disponibles en la máquina en la que se ejecuta y otra tarea como un contenedor de Docker. Ambas tareas son lo más simple posible emitiendo únicamente en la salida estándar un mensaje, en el caso del proceso del sistema con el comando echo y en el caso del contenedor con el mismo comando de una imagen de contenedor con BusyBox. En la definición de las tareas de Nomad se planifica su ejecución con una expresión cron cada un minuto.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    job "echo-busybox" {
        datacenters = ["dc1"]
        type = "batch"
    
        periodic {
            cron = "*/1 * * * * *"
            prohibit_overlap = false
        }
    
        group "echo" {
            task "echo" {
                driver = "docker"
                config {
    	        image = "busybox:latest"
                    command = "/bin/echo"
                    args    = ["Hello Word! (busybox)"]
                }
            }
        }
    }
    
    echo-busybox.nomad
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    job "echo-bash" {
        datacenters = ["dc1"]
        type = "batch"
    
        periodic {
            cron = "*/1 * * * * *"
            prohibit_overlap = false
        }
    
        group "echo" {
            task "echo" {
                driver = "raw_exec"
                config {
                    command = "/bin/echo"
                    args    = ["Hello Word! (bash)"]
                }
            }
        }
    }
    
    echo-bash.nomad

    Nomad y Docker se inician con los siguientes comandos.

    1
    2
    
    $ nomad agent -dev
    
    
    nomad.sh
    1
    2
    
    $ sudo systemctl start docker.service
    
    
    docker.sh

    Antes de enviar a Nomad las tareas el panel de administración integrado o dashboard permite ver el estado, está disponible en la dirección http://127.0.0.1:4646/ui/, inicialmente sin ninguna tarea planificada, una vez se envían las tareas aparecen en el panel.

    Dashboard de Nomad

    Dashboard de Nomad

    Dashboard de Nomad Dashboard de Nomad

    Dashboard de Nomad

    Las tareas se envían a Nomad para su planificación con el siguiente comando, otros comandos permiten ver el estado de las tareas y las trazas que ha generado. Nomad planifica una ejecución de cada tarea según la expresión cron indicada en la definició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
    
    $ nomad job run echo-busybox.nomad
    Job registration successful
    Approximate next launch time: 2021-09-23T17:38:00Z (37s from now)
    
    $ nomad job status echo-busybox
    ID                   = echo-busybox
    Name                 = echo-busybox
    Submit Date          = 2021-09-23T19:37:23+02:00
    Type                 = batch
    Priority             = 50
    Datacenters          = dc1
    Namespace            = default
    Status               = running
    Periodic             = true
    Parameterized        = false
    Next Periodic Launch = 2021-09-23T17:39:00Z (1s from now)
    
    Children Job Summary
    Pending  Running  Dead
    1        0        1
    
    Previously Launched Jobs
    ID                                Status
    echo-busybox/periodic-1632418680  dead
    
    $ nomad job status echo-busybox/periodic-1632418680
    ID            = echo-busybox/periodic-1632418680
    Name          = echo-busybox/periodic-1632418680
    Submit Date   = 2021-09-23T19:38:00+02:00
    Type          = batch
    Priority      = 50
    Datacenters   = dc1
    Namespace     = default
    Status        = dead
    Periodic      = false
    Parameterized = false
    
    Summary
    Task Group  Queued  Starting  Running  Failed  Complete  Lost
    echo        0       0         0        0       1         0
    
    Allocations
    ID        Node ID   Task Group  Version  Desired  Status    Created    Modified
    be8e648a  26b275d9  echo        0        run      complete  1m20s ago  1m16s ago
    
    $ nomad alloc logs be8e648a
    Hello Word! (busybox)
    nomad-job-run-echo-busybox.sh
     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
    
    $ nomad job run echo-bash.nomad
    Job registration successful
    Approximate next launch time: 2021-09-23T17:44:00Z (4s from now)
    
    $ nomad job status echo-bash
    ID                   = echo-bash
    Name                 = echo-bash
    Submit Date          = 2021-09-23T19:43:56+02:00
    Type                 = batch
    Priority             = 50
    Datacenters          = dc1
    Namespace            = default
    Status               = running
    Periodic             = true
    Parameterized        = false
    Next Periodic Launch = 2021-09-23T17:45:00Z (31s from now)
    
    Children Job Summary
    Pending  Running  Dead
    1        0        1
    
    Previously Launched Jobs
    ID                             Status
    echo-bash/periodic-1632419040  dead
    
    $ nomad job status echo-bash/periodic-1632419040
    ID            = echo-bash/periodic-1632419040
    Name          = echo-bash/periodic-1632419040
    Submit Date   = 2021-09-23T19:44:00+02:00
    Type          = batch
    Priority      = 50
    Datacenters   = dc1
    Namespace     = default
    Status        = dead
    Periodic      = false
    Parameterized = false
    
    Summary
    Task Group  Queued  Starting  Running  Failed  Complete  Lost
    echo        0       0         0        0       1         0
    
    Allocations
    ID        Node ID   Task Group  Version  Desired  Status    Created  Modified
    db6b9f1e  26b275d9  echo        0        run      complete  55s ago  55s ago
    
    $ nomad alloc logs db6b9f1e
    Hello Word! (bash)
    nomad-job-run-echo-bash.sh
    Terminal

    El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.

    » Leer más, comentarios, etc...

    Bitácora de Javier Gutiérrez Chamorro (Guti)

    FileOptimizer premiado en el Building Good with C++ Builder Contest 2021

    septiembre 23, 2021 04:36


    Aunque la última vez que escribí sobre FileOptimizer fue en 2018 con el artículo Análisis estático de FileOptimizer, su desarrollo no se ha detenido, y regularmente he ido lanzando nuevas versiones, la última es la 15.10 de hace un par de semanas. Ciertamente el desarrollo se ha ido frenando poco a poco, en un software …

    FileOptimizer premiado en el Building Good with C++ Builder Contest 2021 Leer más »



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

    » Leer más, comentarios, etc...

    Una sinfonía en C#

    Ejecutar Wordpress + MySQL en Kubernetes paso a paso 3, configuración

    septiembre 21, 2021 12:00

    En el post anterior Agregamos Persisten Volumens y Persisten Volume Claims para tener un persistencia más allá del ciclo de vida de los Pods, en este Post vamos a mover la configuración a ConfigMaps y Secrets.

    Crear config maps

    El siguiente paso es pasar las environment variables a configuración, de modo de poder modificarlas y que sea escalable. De momento no vamos a crear secrets, solo pasar los valores a un configmap

    Primero creamos el configmap, de momento con los secrets y el mismo para ambas aplicaciones

    apiVersion: v1
    kind: ConfigMap
    metadata:
      labels:
        app: my-wordpress
      name: mywordpress-config
    data:
      MYSQL_RANDOM_ROOT_PASSWORD: '1'
      MYSQL_DATABASE: password 
      MYSQL_USER: readWrite
      MYSQL_PASSWORD: password
      MYSQL_HOST: mysql
    

    Y modificamos los deployment para cargar sus valores en las variables de entorno

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-db
      labels:
        app: my-db
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: my-db
      template:
        metadata:
          labels:
            app: my-db
        spec:
          containers:
          - name: my-db
            image: mysql:5.7
            ports:
            - containerPort: 80
            volumeMounts:
            - mountPath: /var/lib/mysql
              name: my-db-pv
            env:
            - name: MYSQL_RANDOM_ROOT_PASSWORD
              valueFrom:
                configMapKeyRef:
                  name: mywordpress-config
                  key: MYSQL_RANDOM_ROOT_PASSWORD
            - name: MYSQL_DATABASE
              valueFrom:
                configMapKeyRef:
                  name: mywordpress-config
                  key: MYSQL_DATABASE
            - name: MYSQL_USER
              valueFrom:
                configMapKeyRef:
                  name: mywordpress-config
                  key: MYSQL_USER
            - name: MYSQL_PASSWORD
              valueFrom:
                configMapKeyRef:
                  name: mywordpress-config
                  key: MYSQL_PASSWORD
          volumes:
          - name: my-db-pv
            persistentVolumeClaim:
              claimName: mysql-pvc
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-wordpress
      labels:
        app: my-wordpress
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: my-wordpress
      template:
        metadata:
          labels:
            app: my-wordpress
        spec:
          containers:
          - name: my-wordpress
            image: wordpress:latest
            volumeMounts:
            - name: my-wp-pv
              mountPath: /var/www/html
            ports:
            - containerPort: 80
            env:
            - name: WORDPRESS_DB_PASSWORD
              valueFrom:
                configMapKeyRef:
                  name: mywordpress-config
                  key: MYSQL_PASSWORD
            - name: WORDPRESS_DB_USER
              valueFrom:
                configMapKeyRef:
                  name: mywordpress-config
                  key: MYSQL_USER
            - name: WORDPRESS_DB_NAME
              valueFrom:
                configMapKeyRef:
                  name: mywordpress-config
                  key: MYSQL_DATABASE
            - name: WORDPRESS_DB_HOST
              valueFrom:
                configMapKeyRef:
                  name: mywordpress-config
                  key: MYSQL_HOST
          volumes:
          - name: my-wp-pv
            persistentVolumeClaim:
              claimName: mywordpress-pvc
    

    Proteger secrets

    Los secrets o passwords de la base de datos en el yaml del config map no son una buena idea, podemos hacer dos cosas:

    • Crear dos objetos secret en yaml
    • Crear los secrets por línea de comandos

    Cada uno tiene sus ventajas y desventajas.

    Crear dos objectos secret Ya que los secrets se guardan en base64 primero hay que encodearlos

    apiVersion: v1
    kind: Secret
    metadata:
      name: mysecrets
    type: Opaque
    data:
      MYSQL_PASSWORD: bXlwYXNzd29yZA==
    

    Modificamos el configmap para quitar el password y modificamos los deployments para leer el secret

    apiVersion: v1
    kind: ConfigMap
    metadata:
      labels:
        app: my-wordpress
      name: mywordpress-config
    data:
      MYSQL_DATABASE: password 
      MYSQL_USER: readWrite
      MYSQL_HOST: mysql
      MYSQL_RANDOM_ROOT_PASSWORD: '1'
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-db
      labels:
        app: my-db
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: my-db
      template:
        metadata:
          labels:
            app: my-db
        spec:
          containers:
          - name: my-db
            image: mysql:5.7
            ports:
            - containerPort: 80
            volumeMounts:
            - mountPath: /var/lib/mysql
              name: my-db-pv
            env:
            - name: MYSQL_RANDOM_ROOT_PASSWORD
              valueFrom:
                configMapKeyRef:
                  name: mywordpress-config
                  key: MYSQL_RANDOM_ROOT_PASSWORD
            - name: MYSQL_DATABASE
              valueFrom:
                configMapKeyRef:
                  name: mywordpress-config
                  key: MYSQL_DATABASE
            - name: MYSQL_USER
              valueFrom:
                configMapKeyRef:
                  name: mywordpress-config
                  key: MYSQL_USER
            - name: MYSQL_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysecrets
                  key: MYSQL_PASSWORD
          volumes:
          - name: my-db-pv
            persistentVolumeClaim:
              claimName: mysql-pvc
          - name: my-db-config
            configMap:
              name: mywordpress-config
    
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-wordpress
      labels:
        app: my-wordpress
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: my-wordpress
      template:
        metadata:
          labels:
            app: my-wordpress
        spec:
          containers:
          - name: my-wordpress
            image: wordpress:latest
            volumeMounts:
            - name: my-wp-pv
              mountPath: /var/www/html
            ports:
            - containerPort: 80
            env:
            - name: WORDPRESS_DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysecrets
                  key: MYSQL_PASSWORD
            - name: WORDPRESS_DB_USER
              valueFrom:
                configMapKeyRef:
                  name: mywordpress-config
                  key: MYSQL_USER
            - name: WORDPRESS_DB_NAME
              valueFrom:
                configMapKeyRef:
                  name: mywordpress-config
                  key: MYSQL_DATABASE
            - name: WORDPRESS_DB_HOST
              valueFrom:
                configMapKeyRef:
                  name: mywordpress-config
                  key: MYSQL_HOST
          volumes:
          - name: my-wp-pv
            persistentVolumeClaim:
              claimName: mywordpress-pvc
    
    

    Genial, el último paso sería para no tener el secret en un archivo (aunque se puede restringir su acceso) es crearlo por línea de comandos Borramos el existente y lo creamos por línea de comandos

    kubectl delete secret mysecrets
    kubectl create secret generic mysecrets --from-literal=MYSQL_PASSWORD='bXlwYXNzd29yZA=='
    kubectl get secret mysecrets -o yaml
    

    Alternativamente podemos crearlo por línea de comandos a partir de un file (por ejemplo si es un texto largo o un certificado, etc.)

    kubectl create secret generic db-user-pass --from-file=username=./username.txt --from-file=password=./password.txt
    

    Nos leemos.

    » Leer más, comentarios, etc...

    Una sinfonía en C#

    Ejecutar Wordpress + MySQL en Kubernetes paso a paso 2, agregando persistencia

    septiembre 12, 2021 12:00

    En el post anterior vimos cómo ejecutar Wordpress (Wordpress + MySQL) en Kubernetes, del modo más básico, solo con dos Pods separados. En este post vamos a poner la persistencia en elementos externos (volúmenes) para que los datos tengan un ciclo de vida separado de los Pods.

    Mejorando la persistencia

    Evidentemente la persistencia se hace dentro de los containers y esto no es ideal. Vamos a agregar algo de storage. Primero creamos el Persisten volumen, como yo estoy en Docker en Windows será del tipo local-storage En este caso este tipo no permite Dynamic Provisioning, así que no hace falta StorageClass Entonces creamos dos PV

    kind: PersistentVolume
    metadata:
      name: wp-pv
    spec:
      capacity:
        storage: 1Gi
      volumeMode: Filesystem
      accessModes:
      - ReadWriteOnce
      storageClassName: local-storage
      local:
        path: /run/desktop/mnt/host/c/k8svolume/wp #windows path
      nodeAffinity:
        required:
          nodeSelectorTerms:
          - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
              - docker-desktop
    
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: mysql-pv
    spec:
      capacity:
        storage: 1Gi
      volumeMode: Filesystem
      accessModes:
      - ReadWriteOnce
      storageClassName: local-storage
      local:
        path: /run/desktop/mnt/host/c/k8svolume/mysql
      nodeAffinity:
        required:
          nodeSelectorTerms:
          - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
              - docker-desktop
    

    Creamos un persisten volume claim para cada deployment

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: mysql-pvc
    spec:
      accessModes:
      - ReadWriteOnce
      storageClassName: local-storage
      resources:
        requests:
          storage: 1Gi
    
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: mywordpress-pvc
    spec:
      accessModes:
      - ReadWriteOnce
      storageClassName: local-storage
      resources:
        requests:
          storage: 1Gi
    

    Y por último modificamos los deployments de Wordpress y MySQL para declarar y montar el los claims

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-db
      labels:
        app: my-db
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: my-db
      template:
        metadata:
          labels:
            app: my-db
        spec:
          containers:
          - name: my-db
            image: mysql:5.7
            ports:
            - containerPort: 80
            volumeMounts:
            - mountPath: /var/lib/mysql
              name: my-db-pv
            env:
            - name: MYSQL_ROOT_PASSWORD
              value: "my-secret-pw"
            - name: MYSQL_DATABASE
              value: "my-db"
            - name: MYSQL_USER
              value: "my-user"
            - name: MYSQL_PASSWORD
              value: "my-secret-pw"
          volumes:
          - name: my-db-pv
            persistentVolumeClaim:
              claimName: mysql-pvc
    
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-wordpress
      labels:
        app: my-wordpress
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: my-wordpress
      template:
        metadata:
          labels:
            app: my-wordpress
        spec:
          containers:
          - name: my-wordpress
            image: wordpress:latest
            volumeMounts:
            - name: my-wp-pv
              mountPath: /var/www/html
            ports:
            - containerPort: 80
            env:
            - name: WORDPRESS_DB_PASSWORD
              value: "my-secret-pw"
            - name: WORDPRESS_DB_USER
              value: "my-user"
            - name: WORDPRESS_DB_NAME
              value: "my-db"
            - name: WORDPRESS_DB_HOST
              value: "mysql"
          volumes:
          - name: my-wp-pv
            persistentVolumeClaim:
              claimName: mywordpress-pvc
    

    Y ya está, en el siguiente post mejoraremos la configuración. Nos leemos en la próxima

    » Leer más, comentarios, etc...

    Fixed Buffer

    ¡¡FixedBuffer ha cumplido su tercer año!!

    septiembre 11, 2021 10:04

    Tiempo de lectura: 2 minutos

    Otro año más llega la vuelta de vacaciones y con ello FixedBuffer cumple un año más. Tres años hace desde que empece esta andadura donde voy publicando mis notas sobre nuevas tecnologías y sobre cosas que en general me parecen interesantes.

    Es cierto que este último año he estado sin escribir desde febrero, la muerte de mi padre, el COVID, cambios de trabajo y demás cosas me han alejado de escribir en el blog (principalmente por no tener ganas o simplemente no creer que tenía nada interesante para contar). La parte buena es que aunque no he escrito activamente si he seguido participando activamente en la comunidad así que al menos si alguien ha querido saber de mí (no alcanzo a imaginar por qué… xD), no se ha quedado sin la oportunidad.

    También voy a aclarar que no todo ha sido malo, y que he cumplido con otras metas personales: he comprado mi casita, sigo siendo MVP y mi mujer todavía no se ha cansado de mí y de todo el tiempo que dedico a estas cosas (toda una santa he de añadir).

    Dicho esto, ¿qué va a ser de FixedBuffer de aquí en adelante? Pues la verdad no tengo una idea clara… Me gustaría y voy a intentar escribir una entrada nueva cada 2-3 semanas, el verano me ha servido para recuperar fuerzas y llevo lo suficiente en mi nuevo trabajo como para haber descubierto varias tecnologías muy interesantes de las que escribir, o de cosas que si bien son básicas, no todo el mundo las conoce. Veremos en que se traduce y si no acabaré solo con las charlas (‘traccionando’ que diría alguno 😉 )

    Y ya como despedida, solo 2 cosas más. La primera es agradecer a la gente que seguís aquí pese a que no haya escrito demasiado últimamente, la verdad es que ver que el contenido sigue siendo útil es una de las principales razones para seguir tirando líneas 🙂
    La segunda, como ya empieza a ser tradición, es el top 5 de las entradas del último año:

    1. Haciendo fácil el acceso a datos con Entity Framework Core (Parte 2)
    2. Inyección de Dependencias en .Net Framework
    3. Worker Service: Cómo crear un servicio .Net Core 3 multiplataforma
    4. Cómo medir tiempos en C# y .Net con precisión
    5. Generación de ficheros «Excel» (xlsx) con ClosedXML

    **La entrada ¡¡FixedBuffer ha cumplido su tercer año!! se publicó primero en Fixed Buffer.**

    » Leer más, comentarios, etc...

    Fixed Buffer

    Generando y publicando imágenes Docker con GitHub Actions

    septiembre 07, 2021 08:00

    Tiempo de lectura: 5 minutos

    ¡Después de muchos meses sin escribir, por fin volvemos a la carga! Han pasado muchas cosas durante estos meses (de las que supongo que hablaré en la entrada de los 3 años), pero entre ellas hay una que ha empujado el tema de esta entrada:
    Docker está moviendo ficha en lo referente a sus políticas de precios.

    Lo que nos lleva a una pregunta obvia, ¿en qué me afecta eso a mí? Pues seguramente en nada, pero tal vez seas una de esas personas que estaban utilizando utilidades como las «builds automáticas» de DockerHub.

    Esta es precisamente la parte que más me ha impactado a mí, tengo algunas imágenes Docker que hasta hace poco generaba directamente usando DockerHub, pero ese tiempo se acabó, y ha tocado buscarse otra solución. Esta solución, en la mayoría de los casos va a pasar por utilizar los propios sistemas de CI/CD que ofrecen muchos proveedores, y ya que estamos en GitHub, ¿por qué no usar GitHub Actions para construir la imagen Docker?

    Generando una imagen Docker con GitHub

    No es la primera entrada en este blog sobre qué son y cómo utilizar GitHub Actions, así que podemos ir al turrón y centrarnos en como generar nuestras imágenes Docker. Esto en sí mismo ya es algo muy útil que podemos utilizar como parte de nuestro proceso de integración continua, para validar que no se está rompiendo nada con un cambio.

    Para este pequeño ejemplo que estamos haciendo, vamos a utilizar un Dockerfile muy sencillo, ya que solo queremos ver como generar la imagen Docker con Github Actions, no recrearnos en Docker 🙂 Algo como esto por ejemplo:

    FROM alpine
    ENTRYPOINT ["echo", "Hello World!"]
    

    Simplemente usando una imagen ‘alpine’, imprimimos por consola ‘Hello World!’.

    El primer paso, como toda GitHub Action, es crear el yam correspondiente dentro de la carpata ‘.github/workflows’, en este caso, algo tan simple como esto:

    name: Docker GitHub
    
    on: [push, pull_request,workflow_dispatch]
    
    jobs:
      build-and-push-images:
        runs-on: ubuntu-latest
    
        steps:
          - name: Checkout repository
            uses: actions/checkout@v2
    
    

    Sobre esta Action que de momento no hace nada, vamos a añadir el paso que generará la imagen. Para ello vamos a utilizar la acción build-push-action, que es su modo más simple, basta con añadir el paso a nuestro ‘yaml’:

          - name: Build and push Docker image
            uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
            with:
              context: .  //Contexto para el comando Docker
              push: false //Indicamos si queremos hacer push de la imagen
    

    Con esto ya tenemos todo listo, nuestra GitHub Action va a generar una imagen Docker automáticamente.

    Publicando una imagen Docker en DockerHub con GitHub

    Muy bien, tenemos lista nuestra Action y ya genera la imagen Docker, pero esto aún no resuelve el problema inicial, de que no tengo las auto builds de DockerHub para mantener las imágenes actualizadas. Precisamente por eso, hay que ir un paso más allá y subir la imagen al registro.

    Lo primero que vamos a hacer, es añadir una nueva GitHub Action más a nuestro ‘yaml’ (antes de hacer el build & push, obviamente), con la que haremos un login en DockerHub:

          - name: Login to DockerHub
            if: github.ref == 'refs/heads/master'
            uses: docker/login-action@v1 
            with:
              username: ${{ secrets.DOCKERHUB_USERNAME }}
              password: ${{ secrets.DOCKERHUB_TOKEN }}
    
    

    Ten en cuenta que es necesario crear un token en DockerHub y añadirlo junto al usuario como secreto para el repositorio.

    Una vez hecho esto, basta con un par de cositas más, editar el paso donde hacemos el build, para indicarle que queremos también hacer un push por un lado, y por otro lado, darle un tag a la imagen:

          - name: Build and push Docker image
            uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
            with:
              context: .
              push: true
              tags: 'fixedbuffer/hello'
    

    Con esto, ya tenemos lista nuestra imagen Docker, y subida a DockerHub. Pero… ¿Y si quiero más de un tag? Es muy habitual que cuando creamos una nueva imagen, le demos un tag especifico y además ‘latest’.

    Gestionando múltiples nombres y etiquetas

    Vale, ya tenemos el proceso listo, pero como hemos comentado con el tema de los tags, es mejorable para simplificarnos la vida. Es por eso que mi propuesta es utilizar otra GitHub Action que nos va a generar diferentes tags según la configuremos. Esta Action es metadata-action.

    Simplemente vamos a tener que configurar la Action y editar un poco la Action con la que generamos la imagen Docker, de modo que utilice como entrada la salida de esta. Lo primero será configurar los metadatos, para lo que añadimos esto a nuestro ‘yaml’:

          - name: Docker meta
            id: meta
            uses: docker/metadata-action@v3
            with:
              images: |
                fixedbuffer/hello
              tags: |
                latest
                type=sha
    

    Como resultado de esta Action, se van a generar las etiquetas ‘fixedbuffer/hello:latest’ y ‘fixedbuffer/hello:sha-COMMIT-SHA’. Esta solo es una configuración muy simple para poder probar que funciona, pero realmente ofrece una gran cantidad de opciones, que puedes consultar en el mismo repositorio.

    Ahora, vamos a usar esas etiquetas. La Action que genera y publica la imagen se verá así:

          - name: Build and push Docker image
            uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
            with:
              context: .
              push: true
              tags: ${{ steps.meta.outputs.tags }}
              labels: ${{ steps.meta.outputs.labels }}
    

    Listo, ya tenemos solucionado el problema y se va a generar y subir la imagen con ambos tags.

    ¿Y si quiero usar otro regristry? (El de GitHub por ejemplo)

    Buena pregunta, y la verdad es que tal cual hemos planteado nuestro ‘yaml’, es algo muy muy sencillo. Bastaría con hacer login en el regritry que queramos usar (ghcr, acr, ecs…) y añadir el nombre. Por ejemplo, con DockerHub Y el registry de GitHub nos quedaría una cosa así:

    name: Github Registry
    
    on: [push, pull_request, workflow_dispatch]
    
    env:
      IMAGE_NAME: fixedbuffer/hello
    
    jobs:
      build-and-push-images:
        runs-on: ubuntu-latest
        # Asignamos los permisos sobre los recursos de GitHub
        permissions:
          contents: read
          packages: write
    
        steps:
          - name: Checkout repository
            uses: actions/checkout@v2
    
          - name: Docker meta
            id: meta
            uses: docker/metadata-action@v3
            with:
              images: |
                ${{ env.IMAGE_NAME}}
                ghcr.io/${{ env.IMAGE_NAME}}
              tags: |
                latest
                type=sha
    
          - name: Login to DockerHub
            if: github.ref == 'refs/heads/master' # Solo master
            uses: docker/login-action@v1 
            with:
              username: ${{ secrets.DOCKERHUB_USERNAME }}
              password: ${{ secrets.DOCKERHUB_TOKEN }}
    
          - name: Login to GHCR
            if: github.ref == 'refs/heads/master' # Solo master
            uses: docker/login-action@v1
            with:
              registry: ghcr.io
              username: ${{ github.repository_owner }}
              password: ${{ secrets.GITHUB_TOKEN }}
    
          - name: Build and push Docker image
            uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
            with:
              context: .
              push: github.ref == 'refs/heads/master' # Solo master
              tags: ${{ steps.meta.outputs.tags }}
              labels: ${{ steps.meta.outputs.labels }}
    

    Conclusión

    La verdad es que en mi caso concreto, perder las auto builds de DockerHub fue un problema, ya que tuve que hacer un trabajo extra para añadir la generación de imágenes directamente desde GitHub.

    De todos modos, como puedes comprobar es muy muy sencillo utilizar GitHub Actions para generar imágenes Docker. En este artículo hemos hecho una pequeña review de alto nivel, pero usando las dos Actions principales (docker/build-push-action y docker/metadata-action) podemos configurar incluso las plataformas de destino, usar buildx, …

    Puedes verlo en marcha en este repo de GitHub (obviamente xD)

    **La entrada Generando y publicando imágenes Docker con GitHub Actions se publicó primero en Fixed Buffer.**

    » Leer más, comentarios, etc...

    Una sinfonía en C#

    Ejecutar Wordpress + MySQL en Kubernetes paso a paso

    septiembre 04, 2021 12:00

    En las siguientes semanas voy a publicar una serie de post sobre cómo ejecutar Wordpress en Kubernetes paso a paso, mayormente para comprender los principales elementos de Kubernetes y cómo interactúan.

    En esta primera publicación vamos a hacer una aproximación bien simple. Tomaremos un docker-compose como base y convertirlo a menos en los diferentes yaml para que funcione en Kubernetes.

    En mi caso voy a usar Docker Desktop sobre Windows.

    Convertir Wordpress desde docker-compose a Kubernetes

    A modo de ejercicio y para asentar conocimientos la idea es, a partir de un docker-compose, generar los yaml para desplegar Wordpress en Kubernetes. Lo iremos haciendo paso a paso, desde el enfoque más simple y llegar a un buen nivel de complejidad para cubrir gran parte de los conceptos de Kubernetes.

    docker-compose actual

    Este docker-compose es el que aparece en Docker hub de Wordpress y lo usaremos como base.

    version: '3.1'
    
    services:
    
      wordpress:
        image: wordpress
        restart: always
        ports:
          - 8080:80
        environment:
          WORDPRESS_DB_HOST: db
          WORDPRESS_DB_USER: exampleuser
          WORDPRESS_DB_PASSWORD: examplepass
          WORDPRESS_DB_NAME: exampledb
        volumes:
          - wordpress:/var/www/html
    
      db:
        image: mysql:5.7
        restart: always
        environment:
          MYSQL_DATABASE: exampledb
          MYSQL_USER: exampleuser
          MYSQL_PASSWORD: examplepass
          MYSQL_RANDOM_ROOT_PASSWORD: '1'
        volumes:
          - db:/var/lib/mysql
    
    volumes:
      wordpress:
      db:
    

    Tiene dos servicios, Wordpress y MySQL, un par de volúmenes, uno para la base de datos y otro para los html de Wordpress, unas variables de entorno y algunos secrets (passwords)

    Primer approach

    Vamos a crear la versión más simple, para ello vamos a necesitar lo mínimo para que esto funcione en Kubernetes:

    • Dos deployments:
      • Worpress: for wordpress app
      • MySQL: for mysql app
    • Dos service:
      • CluterIP: para exponer MySQL dentro del cluster y que Wordpress lo puede alcanzar
      • Loadbalancer: para exponer Wordpress al mundo exterior y poder usarlo.

    De momento no vamos a poner storage externo, ni configuraciones ni nada.

    Deployment de wordpress

    Lo único particular es que le pasamos las variables de entorno y el host de MySQL es mysql y el label app=my-wordpress

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-wordpress
      labels:
        app: my-wordpress
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: my-wordpress
      template:
        metadata:
          labels:
            app: my-wordpress
        spec:
          containers:
          - name: my-wordpress
            image: wordpress:latest
            ports:
            - containerPort: 80
            env:
            - name: WORDPRESS_DB_PASSWORD
              value: "my-secret-pw"
            - name: WORDPRESS_DB_USER
              value: "my-user"
            - name: WORDPRESS_DB_NAME
              value: "my-db"
            - name: WORDPRESS_DB_HOST
              value: "mysql"
    

    Deployment MySQL

    Similar a Wordpress, un deployment, le pasamos las variables de entorno y en este caso el label app=my-db

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-db
      labels:
        app: my-db
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: my-db
      template:
        metadata:
          labels:
            app: my-db
        spec:
          containers:
          - name: my-db
            image: mysql:5.7
            ports:
            - containerPort: 80
            env:
            - name: MYSQL_ROOT_PASSWORD
              value: "my-secret-pw"
            - name: MYSQL_DATABASE
              value: "my-db"
            - name: MYSQL_USER
              value: "my-user"
            - name: MYSQL_PASSWORD
              value: "my-secret-pw"
    

    Servicios

    Tenemos Wordpress y MySQL funcionando, pero necesitamos crear dos servicios como ya dijimos Uno para que Wordpress puede alcanzar a MySQL (un ClusterIP porque sería un ciente interno del cluster) Y otro para acceder a Wordpress desde el exterior, un LoadBalancer.

    apiVersion: v1
    kind: Service
    metadata:
      name: mysql ## DNS name (cluster internal)
      labels:
        app: my-db
    spec:
      type: ClusterIP
      selector:
        app: my-db # busca el deployment que tenga esa label
      ports: # puertos que va a escuchar
      - name: http
        port: 3306
        targetPort: 3306
      - name: mysql
        port: 33060
        targetPort: 33060
    

    Con este ClusterIP ya podemos utilizar un port-forward para probar Wordpress

    kubectl port-forward deployment/my-wordpress 8080:80
    

    Con portforward funciona, vamos a crear el LoadBalancer

    apiVersion: v1
    kind: Service
    metadata:
     name: wordpress-loadbalancer
    spec:
     type: LoadBalancer
     selector:
        app: my-wordpress # Busca a partir del laberl app: my-wordpres
     ports:
      - name: "80"
        port: 8080
        targetPort: 80
    

    Y listo, con esto tenemos Wordpress en el puerto 8080 conectado a MySQL

    Nos leemos la próxima para mejorar la persistencia

    » Leer más, comentarios, etc...

    Arragonán

    Un año ayudando a equipos

    septiembre 03, 2021 12:00

    Hace poco más de un año hice público que empezaba a dar servicios de consultoría para ayudar equipos, sin mucha más pretensión que por un lado ordenar mis ideas sobre los servicios que quería empezar a dar y por otro que la gente que conozco supiera de ello.

    Al publicar el post tuvo muy buena acogida, infinitamente mejor de lo que hubiera imaginado nunca. Me surgieron bastantes leads de ahí, incluso un par de tentadoras ofertas para incorporarme a startups muy prometedoras que preferí declinar en ese momento.

    Como es normal, de esos leads muchos no se concretaron. Ya fuera por mi parte, por la de las compañías interesadas o por ambas; no terminamos de encontrar encaje. Pero ese post sirvió perfectamente para el propósito de que gran parte de la gente que conozco me tuviera en mente.

    Tipos de clientes

    Durante este año he podido trabajar con algo más de una decena de empresas de distintos tamaños, que podrían agruparse en estos sectores:

    • Impresión industrial
    • Logística
    • Consultoría tecnológica
    • Alquiler de vehículos
    • Moda
    • Certificación de autenticidad
    • Medios digitales

    Con contextos de lo más variopintos, desde fundadores de startups a la búsqueda de su market fit aún sin equipo técnico, hasta equipos dentro de multinacionales involucrados en grandes cambios tecnológicos y organizativos.

    Formatos de las colaboraciones

    En este año en algunas ocasiones aunque a veces pudiera haber buen feeling inicial sobre posibles colaboraciones, no encontraba un formato de colaboración más allá de incrustarme en los equipos unos meses. Cosa que no podía hacer tras incorporarme en Sigma Rail actuando como tech lead la mayor parte de mi tiempo.

    Descartada la opción de incrustarme en equipos, tenía pensados dos formatos: Mentorías y formaciones a equipos, son dos tipos de colaboraciones que ya había hecho en el pasado varias ocasiones y con las que me sentía cómodo. A ese tipo de colaboraciones se le unió pronto el hacer asesorías, que a diferencia de las mentorías los objetivos tienden a tener un impacto más cercano al nivel organizacional, normalmente para apoyar a roles con respondabilidades de gestión.

    Las formaciones no tienen mucho misterio. Procuro que sean de no más de 2 o 3 días y que no duren mucho más de media jornada, la gente tiene otras cosas que hacer aparte de estar en tus sesiones. También lo que hago es adaptar cada formación a la realidad de cada cliente, incluso las diseño de cero si es necesario. Si es para un equipo que trabaja conjuntamente procuro que buena parte del trabajo sea sobre su propio código, para facilitar llevar a su día a día lo que hayamos estado tratando.

    En cuanto a las mentorías normalmente tendemos a cerrar una bolsa de sesiones inicial que puede ir renovándose si estamos todos a gusto con ello. Combinamos hacer revisiones de arquitectura y de código con programar en pair/mob. Las primeras sesiones suelen ser muy de aterrizaje por mi parte, tienden a ser revisiones de arquitectura de alguna aplicación sobre la que vayamos a trabajar en las que busco detectar de dónde vienen los principales dolores del equipo. En esas sesiones bombardeo a preguntas para conocer qué componentes tiene y cómo interactúan entre ellos, suelo hacer mucho foco en conocer los porqués de las decisiones que se tomaron en el pasado, ya que me ayuda a entender mucho mejor la situación actual. Lo malo es que a veces ya no queda nadie en el equipo que formó parte de esas decisiones y toca tirar de hipótesis.

    Respecto a las asesorías técnicas, más allá de también resevar bolsas de sesiones o de horas bastante más reducidas, no tengo un formato establecido. Cada colaboración me ha resultado una situación totalmente diferente a la otra. Las que he hecho es a gente que ha llegado a mi porque ya me conocen o alguien que se fía de mi les ha pasado mi contacto, así que empezamos con un escenario de cierta confianza inicial.

    Temáticas de las colaboraciones

    En las formaciones, adaptando contenidos a las necesidades de cada cliente, los temas centrales a trabajar han sido en esencia tres: Testing automático, diseño de software y DevOps, pero también me pidieron una específica de Python usando buenas prácticas. En otras de esas formaciones utilizamos Java y C#, ya que el lenguaje es parte de la adaptación.

    Las formaciones relacionadas con DevOps no era algo que tuviera en mente, pero llegaron un par de peticiones de empresas interesadas, así que le propuse a Néstor hacerlo conjuntamente porque creo que lo complementamos muy bien. De ahí diseñamos una formación de tres días sobre Principios y Prácticas Devops, donde nos focalizamos en los fundamentos entrando en algunas herramientas para ilustrar algunas de las prácticas, bastante cañera en mi humilde opinión ;).

    De momento se me ha quedado fuera el hacer formaciones de Specification by Example (aka BDD) y de Clean Architecture, que eran temas que me apetecían pero de momento no ha surgido la posibilidad de hacerlo. En cambio, aunque fuera sólo en formato charla, sí tuve oportuniad de hablar sobre las partes exploratoria y estratégica de Domain-Driven Design.

    En cuanto a mentorías básicamente hice tres. Acompañé a un equipo en el desarrollo de una aplicación móvil desde cero, colaboré en pequeñas evoluciones de un MVP aún en la búsqueda de su market fit y ayudé en poner al día componentes que a nivel técnico se habían quedado algo estancados en un ecosistema de microservicios; en esto último aún seguimos colaborando. En estos casos fui intentando aportar en temas de testing, arquitectura, pipelines de integración y entrega continua… vamos, temas habituales. Respecto a lenguajes Kotlin (que no había tenido oportunidad de usar medianamente en serio y me ha despertado aún más curiosidad), Javascript/Node y Java respectivamente.

    Dentro del cajón de las asesorías como decía he hecho cosas bastante variadas, e incluso inesperadas para mi. Algunas cosas más o menos normales; como apoyar en temas de product owning, hacer auditorías o dar feedback sobre código/arquitectura de software. Pero no esperaba hacer cosas como evaluar para contratar (o no) proveedores y servicios o hacer poco más que de patito de goma para ayudar a tomar decisiones, son cosas que no me hubiera contratado nadie sin un escenario confianza previo.

    ¿Y ahora qué?

    Cuando arranqué el año pasado no tenía muy claro qué tal iba a funcionar, tenía dudas de si iba a encontrar un interés suficiente en el tipo de servicios que quería ofrecer para hacerlo sostenible. Pero a los meses me vi en un momento en el que tuve que pisar un poco el freno y quitarme algo de trabajo para equilibrar.

    De momento hay un par de clientes con los que vengo trabajando desde hace unos meses con los que vamos a seguir haciéndolo, uno en formato mentoría y otro en asesoría.

    Cuento con que prácticamente hasta el último trimestre del año no voy a arrancar ninguna nueva colaboración. Hay alguna posibilidad de colaboración a modo de asesoría que quedó pendiente de concretar tras las vacaciones de verano que debería confirmarse (o no) en cosa de un par de semanas. Y sin haber nada cerrado, quizá de cara a final del año hagamos alguna formación. Más allá de eso, una incógnita.

    En fin, que como es de suponer, por mi encantado de explorar posibles nuevas colaboraciones. Así que sea para eso o para cualquier duda o tipo de cuestión vía mi email estoy disponible :). Que aunque luego no cuajen siempre me resulta muy interesante conocer equipos y contextos nuevos.

    » Leer más, comentarios, etc...

    Header Files

    Ajuste dinámico de imágenes en QLabels

    agosto 27, 2021 12:30

    El control para mostrar etiquetas de texto en Qt se llama QLabel. Además de texto, puede mostrar una imagen mediante el método QLabel::setPixmap, aunque de una forma bastante limitada. Me explico: la imagen se mostrará con una relación 1:1, por lo que estará recortada si es mayor que el widget, mostrándose más o menos dependiendo del tamaño del mismo.

    QLabel cuenta con una propiedad para paliar el problema descrito arriba (aunque yo creo que lo que logra es confundir más aún): scaledContents. Al usar QLabel::setScaledContents(true) el pixmap se redimensiona dinámicamente al tamaño de la etiqueta, pero sin respetar la relación de aspecto (adquiere la relación de aspecto del widget).

    Si por el contrario lo que queremos es que la imagen se escale dinámicamente respetando la relación de aspecto, entonces este artículo es para ti. La técnica consiste básicamente en crear una segunda versión escalada y centrada de la imagen original cada vez que el widget cambie de tamaño.

    Escalado y posicionamiento

    La parte del escalado es sencilla, ya que Qt la incorpora en QPixmap::scale. En este caso, usaremos aspectMode = Qt::KeepAspectRatio para que la imagen quede dentro de la nueva área. Si por el contrario quisiésemos que la imagen cubriese todo usaríamos Qt::KeepAspectRatioByExpanding. Además, usaremos el modo Qt::SmoothTransformation para obtener el mejor resultado posible. Es importante recordar que siempre debemos hacer el escalo sobre una copia de la imagen original para evitar degradaciones en la calidad de la misma.

    Ahora toca centrar la imagen, ya que el escalado únicamente nos ajusta el tamaño, el posicionamiento es un asunto del renderizado. Para ello crearemos una imagen secundaria con el tamaño de la etiqueta, sobre la que pintaremos centrada la imagen escalada en el paso anterior:

    auto image = QImage{label->size(), QImage::Format_ARGB32_Premultiplied}; // transparency required to prevent 'black' strips to appear
    image.fill(Qt::transparent); // cannot use QPainter::eraseGeometry when working with a QImage as painting device
    
    // Scale image
    const auto scaled_pixmap = original_pixmap.scaled(label->size(), aspect_mode, Qt::SmoothTransformation);
    
    // Center image by computing the new origin
    const auto size_diff = label->size() - scaled_pixmap.size();
    const auto top_left = QPoint{size_diff.width() / 2, size_diff.height() / 2};
    
    // Render the new image
    QPainter painter{&image};
    painter.drawPixmap(top_left, scaled_pixmap);
    painter.end();
    

    Ajuste dinámico

    Por último sólo nos toca cambiar el pixmap cada vez que la etiqueta cambie de tamaño. Si bien la herencia es una opción, la forma más sencilla de realizar este procedimiento es mediante un filtro de eventos que capture el evento de cambio de tamaño (QEvent::Resize). Una sencilla clase nos puede dar esta funcionalidad:

    class QLabelPixmapScaler : public QObject
    {
    public:
      explicit QLabelPixmapScaler(QLabel *label, Qt::AspectRatioMode aspect_mode)
        : QObject{label}, m_pixmap{*label->pixmap()}, m_aspect_mode{aspect_mode}
      {
        label->installEventFilter(this);
      }
    
      bool eventFilter(QObject *obj, QEvent *event) override {
        if (obj == parent() && event->type() == QEvent::Resize) {
          auto label = (QLabel *)parent();
    
          // Scale original pixmap and save on 'image'
    
          label->setPixmap(QPixmap::fromImage(image));
        }
    
        return false;
      }
    
    private:
      const QPixmap m_pixmap;
      const Qt::AspectRatioMode m_aspect_mode;
    };
    

    Ahora únicamente nos queda instalar el escalador al QLabel. Nótese que el escalador se asocia a la jerarquía de objetos de la etiqueta, por lo que no es necesario preocuparse de eliminarlo; se hará cuando se borre la etiqueta.

    new QLabelPixmapScaler{label, Qt::KeepAspectRatio};
    

    Retoques finales

    La solución anterior es válida si la etiqueta siempre tiene la misma imagen, pero si ha de cambiar entonces podemos vernos en el caso de tener múltiples escaladores instalados. Para solucionarlo bastaría con borrar los anteriores cada vez que se cree uno nuevo:

    setObjectName("QLabelPixmapScaler");
    
    for (auto prev_scaler : label->findChildren<QLabelPixmapScaler *>(objectName())) {
      label->removeEventFilter(prev_scaler);
    }
    

    Un ejemplo de cambio de imagen sería algo así como

    new QLabelPixmapScaler{label, Qt::KeepAspectRatio};
    
    // ...
    
    label->setPixmap(/* ... */);
    new QLabelPixmapScaler{label, Qt::KeepAspectRatio};
    

    Ejemplo

    En este ejemplo (disponible en GitHub) se comparan los cuatro modos de escalado mencionados: sin escala, usando scaledContents, escalado dinámico por dentro, y escalado dinámico por fuera.

    resize_image_qlabel

    » Leer más, comentarios, etc...

    Bitácora de Javier Gutiérrez Chamorro (Guti)

    Modula-2 (Fitted y TopSpeed)

    agosto 23, 2021 03:10


    Hace casi 10 años, que se dice pronto, escribía Mi relación con Pascal, mucho antes de haber sido nombrado MVP de Embarcadero. En aquel capítulo no hablé sobre Modula-2, el lenguaje diseñado también por Niklaus Wirth y que era el sustituto de Pascal. El tema es que yo, que estaba acostumbrado a Turbo Pascal, ya …

    Modula-2 (Fitted y TopSpeed) Leer más »



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

    » Leer más, comentarios, etc...

    Picando Código

    Documental – Two Cartoon Foxes: Remembering Why The Lucky Stiff

    agosto 19, 2021 11:00

    El 19 de agosto de 2009, Why the Lucky Stiff desapareció de la comunidad Ruby. Hoy se celebra el Whyday, donde la comunidad Ruby recuerda las contribuciones de _why al ecosistema y la cultura que la identifica.

    Para más información pueden visitar el sitio Whyday.org o la cuenta en Twitter @celebratewhyday o seguir el hashtag #whyday.

    El post Documental – Two Cartoon Foxes: Remembering Why The Lucky Stiff fue publicado originalmente en Picando Código.

    » Leer más, comentarios, etc...

    Bitácora de Javier Gutiérrez Chamorro (Guti)

    El misterio con Borland Turbo BASIC 2.1

    agosto 15, 2021 11:34


    Hace unas semanas me encontré en Old-DOS.ru con un hecho retro que me pareció digno de ser investigado. Ofrecían para descargar una copia de Borland Turbo Basic en su versión 2.1 y fechada en 1989. Aquello me dejó intrigado, fui un gran amante y usuario de Turbo Basic primero con la «buggy» 1.0 de 1987 …

    El misterio con Borland Turbo BASIC 2.1 Leer más »



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

    » Leer más, comentarios, etc...

    Meta-Info

    ¿Que es?

    Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

    rss subscripción

    Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
    planetacodigo

    planetacodigo

    Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

    Idea: Juanjo Navarro

    Diseño: Albin