que modelado microservicios diseño diseñar desarrollo crear basado deployment configuration architecture docker microservices

deployment - modelado - ¿Cómo gestiona los datos por entorno en microservicios basados en Docker?



modelado y diseño de microservicios (1)

Visión general

Post largo!

  • ENTRYPOINT es tu amigo
  • Construir microservicios por Sam Newman es genial
  • Sugerencia de seguridad entre servicios: el TLS bidireccional puede funcionar, pero puede presentar problemas de latencia
  • Entraré en un ejemplo real de mi equipo. No pudimos usar un servidor de configuración, y las cosas se han puesto ... interesantes. Manejable por ahora. Pero no puede escalar ya que la empresa tiene más servicios.
  • Los servidores de configuración parecen una mejor idea.

Actualización : Casi dos años más tarde, podríamos mudarnos a Kubernetes y comenzar a usar la función etcd-powered ConfigMaps que viene con ella. Mencionaré esto nuevamente en la sección de servidores de configuración. La publicación aún puede valer la pena si está interesado en estos temas. Seguiremos usando ENTRYPOINT y algunos de los mismos conceptos, solo algunas herramientas diferentes.

ENTRYPOINT

Sugiero que ENTRYPOINT es la clave para administrar la configuración específica del entorno para sus contenedores Docker.

En resumen: cree un script para iniciar su servicio antes de comenzar, y use ENTRYPOINT para ejecutar este script.

Entraré en detalle al contextualizar esto y también explicaré cómo lo hacemos sin un servidor de configuración. Se vuelve un poco profundo, pero no es inmanejable. Luego, termino con detalles sobre los servidores de configuración, una mejor solución para muchos equipos.

Microservicios de construcción

Tienes razón en que estas son preocupaciones comunes, pero simplemente no hay soluciones únicas para todos. La solución más general es un servidor de configuración. (El más general, pero aún no es de talla única). Pero quizás no pueda usar uno de estos: el equipo de Seguridad nos prohibió usar un servidor de configuración.

Le recomiendo leer Building Microservices de Sam Newman, si aún no lo ha hecho. Examina todos los desafíos comunes y analiza muchas soluciones posibles, al tiempo que brinda una perspectiva útil de un arquitecto experimentado. (Nota: no se preocupe por una solución perfecta para la administración de su configuración; comience con una solución "suficientemente buena" para su conjunto actual de microservicios y entornos. Puede iterar y mejorar, por lo que debe intentar obtener un software útil para su clientes lo antes posible , luego mejorar en versiones posteriores.)

¿Cuento con moraleja?

Al releer esto otra vez ... me estremezco un poco por lo que se necesita para explicar esto completamente. Desde el Zen de Python :

If the implementation is hard to explain, it''s a bad idea. If the implementation is easy to explain, it may be a good idea.

No estoy encantado con la solución que tenemos. Sin embargo, es una solución viable, dado que no pudimos usar un servidor de configuración. También es un ejemplo del mundo real.

Si lo lees y piensas: "Oh Dios no, ¿por qué querría todo eso?" entonces sabes, necesitas mirar detenidamente los servidores de configuración.

Seguridad entre servicios

Parece que a usted también le preocupa cómo diferentes microservicios se autentican entre sí.

Para los artefactos y la configuración relacionados con esta autenticación ... trátelos como cualquier otro artefacto de configuración.

¿Cuáles son sus requisitos en cuanto a la seguridad entre servicios? En tu publicación, parece que estás describiendo la autenticación de nivel de aplicación, nombre de usuario / contraseña. Tal vez eso tenga sentido para los servicios que tiene en mente. Pero también debe considerar el TLS bidireccional : "esta configuración requiere que el cliente proporcione su certificado al servidor, además del servidor que proporciona el suyo al cliente". La generación y administración de estos certificados puede complicarse ... pero como quiera que lo haga, se barajarán las configuraciones / artefactos como cualquier otra configuración / artefactos.

Tenga en cuenta que el TLS bidireccional puede introducir problemas de latencia en volúmenes altos. Todavía no estamos allí. Estamos utilizando otras medidas además del TLS de 2 vías y podemos deshacernos del TLS de 2 vías una vez que se hayan comprobado, con el tiempo.

Ejemplo del mundo real de mi equipo.

Mi equipo actual está haciendo algo que combina dos de los enfoques que mencionó (parafraseado):

  • Configuración de horneado en tiempo de construcción
  • Configuración de extracción en tiempo de ejecución

Mi equipo está utilizando Spring Boot . Spring Boot tiene una configuración externa muy compleja con un sistema de "perfiles". El manejo de la configuración de Spring Boot es complejo y poderoso, con todos los pros / contras que vienen con eso (no lo veremos aquí).

Si bien esto no es posible con Spring Boot, las ideas son generales. Prefiero Dropwizard para microservicios de Java o Flask en Python; en ambos casos, podrías hacer algo similar a lo que Spring Boot ha estado haciendo ... Tendrás que hacer más cosas por ti mismo. Lo bueno y lo malo: estos marcos pequeños y ágiles son más flexibles que Spring, pero cuando escribes más código y haces más integraciones, tienes más responsabilidad de TI para el control de calidad y para probar tu compatibilidad de configuración compleja / flexible.

Continuaré con el ejemplo de Spring Boot debido a la experiencia de primera mano, ¡pero no porque lo recomiendo! Usa lo que es correcto para tu equipo.

En el caso de Spring Boot, puede activar varios perfiles a la vez. Eso significa que puede tener una configuración base, luego anular con una configuración más específica. Mantenemos una configuración básica, application.yml en src/main/resources . Por lo tanto, esta configuración se empaqueta con el JAR que se puede enviar, y cuando se ejecuta el JAR, esta configuración siempre se retira. Por lo tanto, incluimos todas las configuraciones predeterminadas (comunes a todos los entornos) en este archivo. Ejemplo: el bloque de configuración que dice, "Tomcat incorporado, siempre use TLS con estos conjuntos de cifrado habilitados". ( server.ssl.ciphers )

Cuando solo se debe sobrescribir una o dos variables para un entorno determinado, aprovechamos el soporte de Spring Boot para obtener la configuración de las variables de entorno . Ejemplo: establecemos la URL a nuestro descubrimiento de servicio utilizando una variable de entorno. Esto anula cualquier valor predeterminado en los archivos de configuración enviados / extraídos. Otro ejemplo: usamos una variable de entorno SPRING_PROFILES_ACTIVE para especificar qué perfiles de configuración están activos.

También queremos asegurarnos de que master contenga una configuración de trabajo probada para entornos de desarrollo. src/main/resources/application.yml tiene valores predeterminados sanos. Además, colocamos la configuración solo para el desarrollo en config/application-dev.yml , y verificamos que el directorio de config es fácil de recoger, pero no se envía en el JAR. Buena característica Los desarrolladores saben (del README y otra documentación) que en un entorno de desarrollo, todos nuestros microservicios Spring Boot requieren que se active el perfil de desarrollo.

Para entornos que no sean dev , probablemente ya pueda ver algunas opciones ... Cualquiera de estas opciones podría hacer (casi) todo lo que necesita. Puedes mezclar y combinar según lo necesites. Estas opciones se superponen con algunas ideas que mencionas en tu publicación original.

  1. Mantenga perfiles específicos del entorno, como application-stage.yml , application-prod.yml , etc., que anulan la configuración con desviaciones de los valores predeterminados (en un repositorio de git muy fuertemente bloqueado)
  2. Mantener perfiles modulares, específicos del proveedor como application-aws.yml , application-mycloudvendor.yml (donde almacene esto dependerá de si contiene secretos). Estos pueden contener valores que atraviesan el escenario, la producción, etc.
  3. Use variables de entorno para anular cualquier configuración relevante, en tiempo de ejecución; Incluyendo perfil (es) de 1 y 2
  4. Use la automatización para hornear valores codificados (plantillas) en el momento de compilación o implementación (salida en un repositorio fuertemente bloqueado de algún tipo, posiblemente distinto del repositorio de (1))

(1), (2) y (3) funcionan bien juntos. Estamos felices haciendo los tres y en realidad es bastante fácil documentar, razonar y mantener (después de obtener el bloqueo inicial).

Tu dijiste ...

Supongo que podría crear un repositorio de archivos de propiedades o scripts por entorno. Sin embargo, [...] necesitaría un montón de scripts.

Puede ser manejable. Los scripts que tiran de la configuración bake-in: estos pueden ser uniformes en todos los servicios. Quizás el script se copie cuando alguien clona su plantilla de microservicio (por cierto: ¡debería tener una plantilla de microservicio oficial!). O tal vez es un script de Python en un servidor interno de PyPI. Más sobre esto después de que hablamos de Docker.

Ya que Spring Boot tiene un buen soporte para (3), y es compatible con el uso de valores predeterminados / plantillas en archivos YML, es posible que no necesite (4). Pero aquí es donde las cosas se vuelven muy específicas para su organización. El Ingeniero de Seguridad de nuestro equipo quería que usáramos (4) para hornear algunos valores específicos para entornos más allá del desarrollo: contraseñas. Este ingeniero no quería que las contraseñas "flotaran" en las variables de entorno, principalmente porque entonces, ¿quién las establecería? La persona que llama Docker? Definición de tarea de AWS ECS (visible a través de la interfaz de usuario web de AWS) ? En esos casos, las contraseñas podrían estar expuestas a ingenieros de automatización, que no necesariamente tendrían acceso al "repositorio de git bloqueado" que contiene application-prod.yml . (4) podría no ser necesario si lo hace (1); Usted podría simplemente mantener las contraseñas, codificadas, en el repositorio estrictamente controlado. Pero tal vez haya secretos para generar en el momento de la automatización de la implementación, que no desee en el mismo repositorio que (1). Este es nuestro caso.

Más sobre (2): usamos un perfil de aws y la "configuración como código" de Spring Boot para hacer una llamada en el inicio para obtener metadatos de AWS y anular algunas configuraciones basadas en eso. Nuestras definiciones de tareas de AWS ECS activan el perfil de aws . La documentación de Spring Cloud Netflix da un ejemplo como este:

@Bean @Profile("aws") public EurekaInstanceConfigBean eurekaInstanceConfig() { EurekaInstanceConfigBean b = new EurekaInstanceConfigBean(); AmazonInfo info = AmazonInfo.Builder.newBuilder().autoBuild("eureka"); b.setDataCenterInfo(info); return b; }

A continuación, Docker. Las variables de entorno son una muy buena manera de pasar los argumentos de configuración en Docker. No usamos ningún argumento posicional o de línea de comando debido a algunos errores que encontramos con ENTRYPOINT . Es fácil pasar --env SPRING_PROFILES_ACTIVE=dev o --env SPRING_PROFILES_ACTIVE=aws,prod ... ya sea desde la línea de comandos, o desde un supervisor / programador como AWS ECS o Apache Mesosphere / Marathon . Nuestro punto de entrada entrypoint.sh también facilita el paso de indicadores JVM que no tienen nada que ver con Spring: usamos la convención común de JAVA_OPTS para esto.

(Oh, debería mencionar ... también usamos Gradle para nuestras compilaciones. En este momento ... Gradle la construcción de la Gradle docker build , la docker run ventana docker push y la función de carga de la ventana docker push con las tareas de Gradle. Nuestro Dockerfile está Dockerfile , así que nuevamente, la opción # 4 arriba. Tenemos variables como @agentJar@ que se sobrescriben en el momento de la compilación. Realmente no me gusta esto, y creo que esto podría manejarse mejor con la configuración antigua ( -Dagent.jar.property.whatever ). Esto probablemente Dockerfile , pero solo lo menciono por completo. Algo de lo que estoy contento es que no se hace nada en el Dockerfile compilación, Dockerfile o entrypoint.sh , que está acoplado estrechamente a un determinado contexto de implementación (como AWS) .Todo funciona en entornos de desarrollo y en entornos implementados. Por lo tanto, no tenemos que implementar la imagen de Docker para probarla: es portátil, como debería ser.)

Tenemos una carpeta src/main/docker contiene Dockerfile y entrypoint.sh (la secuencia de comandos llamada por ENTRYPOINT ; esto se Dockerfile en Dockerfile ). Nuestro Dockerfile y entrypoint.sh son casi completamente uniformes en todos los microservicios. Estos se duplican cuando clonas nuestra plantilla de microservicio. Desafortunadamente, a veces hay que copiar / pegar actualizaciones. No hemos encontrado una buena manera de evitar esto todavía, pero no es terriblemente doloroso.

El Dockerfile hace lo siguiente (tiempo de compilación):

  1. Deriva de nuestro Dockerfile base "dorado" para aplicaciones Java
  2. Agarra nuestra herramienta para tirar de la configuración. (Grabs desde un servidor interno disponible para cualquier máquina Dev o Jenkins que realice una compilación). (También puede usar herramientas de Linux como wget , así como nombres de DNS / convenciones basadas en dónde obtenerlo. También puede usar AWS S3 y nomenclatura basada en convenciones.)
  3. Copie algunas cosas en el Dockerfile , como el JAR, entrypoint.sh ...
  4. ENTRYPOINT exec /app/entrypoint.sh

El entrypoint.sh hace lo siguiente (tiempo de ejecución):

  1. Utiliza nuestra herramienta para tirar de la configuración. (Es lógico comprender que si el perfil de aws no está activo, no se espera el archivo de configuración de aws ). Fallece de inmediato y en voz alta si hay algún problema.
  2. exec java $JAVA_OPTS -jar /app/app.jar (recoge todos los archivos de propiedades, variables de entorno, etc.)

Así que hemos cubierto eso en el momento de inicio de la aplicación, la configuración se extrae de algún lugar ... pero ¿dónde? Para los puntos anteriores, podrían estar en un repositorio de git. Puede desplegar todos los perfiles y luego usar SPRING_PROFILES_ACTIVE para decir cuáles están activos; pero luego puede desplegar application-prod.yml en una máquina de escenario (no es bueno). Entonces, en lugar de eso, puede mirar SPRING_PROFILES_ACTIVE (en su lógica de configuración-puller) y extraer solo lo que se necesita.

Si está utilizando AWS, podría usar un repositorio / s de S3 en lugar de un repositorio git. Esto puede permitir un mejor control de acceso. En lugar de que application-prod.yml y application-stage.yml vivan en el mismo repositorio / cubo, puede hacerlo para que application-envspecific.yml siempre tenga la configuración requerida, en el S3 bucket por algún nombre convencional en el dado Cuenta de AWS. es decir, "Obtenga la configuración de s3://ecs_config/$ENV_NAME/application-envspecific.yml " (donde $ENV_NAME proviene del script entrypoint.sh o de la Definición de tareas de ECS).

Mencioné que el Dockerfile funciona de manera portátil y no está acoplado a ciertos contextos de implementación. Esto se debe a que entrypoint.sh está definido para verificar los archivos de configuración de una manera flexible; sólo quiere los archivos de configuración. Entonces, si usa la opción --volume de Docker para montar una carpeta con config, el script estará contento y no intentará extraer nada de un servidor externo.

No entraré mucho en la automatización de la implementación ... pero solo mencione rápidamente que usamos terraform, boto3 y algunos códigos de envoltura personalizados de Python. jinja2 para plantillas (hornear en esos valores de pareja que necesitan ser horneados).

Aquí hay una seria limitación de este enfoque: el proceso de microservicio debe cancelarse / reiniciarse para volver a descargar y volver a cargar la configuración. Ahora, con un grupo de servicios sin estado, esto no necesariamente representa un tiempo de inactividad (debido a algunas cosas, como el equilibrio de carga del lado del cliente, la Ribbon opciones configurada para los reintentos y la escala horizontal, por lo que algunas instancias siempre se ejecutan en el grupo). Hasta ahora está funcionando, pero los microservicios todavía tienen una carga bastante baja. El crecimiento está llegando. Veremos.

Hay muchas más formas de resolver estos desafíos. Esperemos que este ejercicio te haga pensar en lo que funcionará para tu equipo. Solo trata de poner en marcha algunas cosas. Prototipo rápidamente y sacará los detalles a medida que avanza.

Quizás mejor: servidores de configuración.

Creo que esta es una solución más común: servidores de configuración. Usted mencionó ZooKeeper . También hay Consul . Tanto ZooKeeper como Consul ofrecen tanto Configuration Management como Service Discovery. También hay etcd .

En nuestro caso, el equipo de seguridad no se sintió cómodo con un servidor de administración de configuración centralizado. Decidimos utilizar Eureka de NetflixOSS para el descubrimiento de servicios, pero nos retrasamos en un servidor de configuración. Si terminamos por no gustarnos los métodos anteriores, podemos cambiar a Archaius para la administración de la configuración. Spring Cloud Netflix tiene como objetivo hacer que estas integraciones sean fáciles para los usuarios de Spring Boot. Aunque creo que quiere que uses Spring Cloud Config (Server / Client) en lugar de Archaius. Aún no lo he probado.

Los servidores de configuración parecen mucho más fáciles de explicar y pensar. Si puede, debe comenzar con un servidor de configuración.

If the implementation is hard to explain, it''s a bad idea. If the implementation is easy to explain, it may be a good idea.

Comparaciones de servidores de configuración.

Si decides probar un servidor de configuración, deberás hacer un pico de investigación. Aquí hay algunos buenos recursos para comenzar:

Si prueba con el Cónsul, debería ver esta charla, "Operando al Cónsul como un Adopter Temprano" . Incluso si intentas algo más además de Cónsul, la conversación tiene muchos consejos y conocimientos para ti.

16/05/11 EDITAR: El radar de tecnología de ThoughtWorks ahora ha llevado al cónsul a la categoría "Adoptar" (el historial de su evaluación está aquí ).

17/06/01 EDITAR: Estamos considerando mudarnos a Kubernetes por múltiples razones. Si lo hacemos, aprovecharemos la etcd-powered ConfigMaps que se suministra con K8S. Eso es todo por ahora en este tema :-)

Más recursos

En una arquitectura de microservicio, me cuesta mucho entender cómo se puede administrar la configuración específica del entorno (por ejemplo, la dirección IP y las credenciales para la base de datos o el intermediario de mensajes).

Digamos que usted tiene tres microservicios ("A", "B" y "C"), cada uno de ellos pertenece y es mantenido por un equipo diferente. Cada equipo necesitará un entorno de integración de equipo ... donde trabajen con la última instantánea de su microservicio, junto con versiones estables de todos los microservicios de dependencia. Por supuesto, también necesitarás entornos de control de calidad / organización / producción. Una vista simplificada del panorama general se vería así:

"Microservice A" Team Environment

  • Microservicio A ( SNAPSHOT )
  • Microservicio B (ESTABLE)
  • Microservicio C (ESTABLE)

"Microservice B" Team Environment

  • Microservicio A (ESTABLE)
  • Microservicio B ( FOTO )
  • Microservicio C (ESTABLE)

"Microservice C" Team Environment

  • Microservicio A (ESTABLE)
  • Microservicio B (ESTABLE)
  • Microservicio C ( SNAPSHOT )

QA / Puesta en escena / Producción

  • Microservicio A (ESTABLE, LIBERACIÓN, etc.)
  • Microservicio B (ESTABLE, LIBERACIÓN, etc.)
  • Microservicio C (ESTABLE, LIBERACIÓN, etc.)

Eso es un montón de implementaciones, pero ese problema puede resolverse mediante un servidor de integración continua y quizás algo como Chef / Puppet / etc. La parte realmente difícil es que cada microservicio necesitaría algunos datos del entorno específicos de cada lugar en el que se implementa.

Por ejemplo, en el entorno de equipo "A", "A" necesita una dirección y un conjunto de credenciales para interactuar con "B". Sin embargo, en el entorno del equipo "B", esa implementación de "A" necesita una dirección y credenciales diferentes para interactuar con esa implementación de "B".

Además, a medida que se acerca la producción, la información de configuración ambiental como esta probablemente necesite restricciones de seguridad (es decir, solo ciertas personas pueden modificarla o incluso verla).

Entonces, con una arquitectura de microservicio, ¿cómo mantener la información de configuración específica del entorno y ponerla a disposición de las aplicaciones? Algunos enfoques vienen a la mente, aunque todos parecen problemáticos:

  • Haga que el servidor de compilación los incorpore en la aplicación en el momento de la compilación . Supongo que podría crear un repositorio de archivos de propiedades o guiones por entorno, y hacer que el proceso de compilación de cada microservicio se extienda y obtenga el guión apropiado (también tener un repositorio separado de acceso limitado para el material de producción). Aunque necesitarías un montón de guiones. Básicamente, uno separado para cada microservicio en cada lugar donde se pueda implementar el microservicio.
  • Conviértalos en imágenes base de Docker para cada entorno : si el servidor de compilación está poniendo sus aplicaciones de microservicio en los contenedores de Docker como último paso del proceso de construcción, puede crear imágenes base personalizadas para cada entorno. La imagen base contendría un script de shell que establece todas las variables de entorno que necesita. Su Dockerfile se configuraría para invocar este script antes de iniciar su aplicación. Esto tiene desafíos similares al punto de bala anterior, ya que ahora está administrando una tonelada de imágenes Docker.
  • Obtenga la información del entorno en tiempo de ejecución desde algún tipo de registro : por último, puede almacenar su configuración por entorno dentro de algo como Apache ZooKeeper (o incluso solo una base de datos), y hacer que su código de aplicación lo introduzca en el tiempo de ejecución cuando se pone en marcha Cada aplicación de microservicio necesitaría una forma de indicar en qué entorno se encuentra (por ejemplo, un parámetro de inicio), para que sepa qué conjunto de variables debe obtener del registro. La ventaja de este enfoque es que ahora puede utilizar exactamente el mismo artefacto de compilación (es decir, la aplicación o el contenedor Docker) desde el entorno del equipo hasta la producción. Por otro lado, ahora tendría otra dependencia de tiempo de ejecución y, de todos modos, tendría que administrar todos esos datos en su registro.

¿Cómo aborda la gente este problema en una arquitectura de microservicio? Parece que esto sería algo común de lo que hablar.