Background Image
TECNOLOGÍA

Infraestructura como código real: Introducción al modelo de programación constructiva

El modelo de programación constructiva
Headshot - Will Wermager
Will Wermager
Consultor principal

April 25, 2023 | 10 Minuto(s) de lectura

La infraestructura como código es una parte no negociable de la construcción de cualquier tipo de plataforma mantenible. Lo primero que suele venir a la mente cuando se oye hablar de infraestructura como código (IaC) son herramientas como Terraform de Hashicrorp y AWS CloudFormation, en las que la infraestructura se define explícitamente en JSON, YAML o un lenguaje específico de la herramienta, como HCL de Terraform. Aunque todas estas son formas viables de definir nuestra infraestructura, hay algunas deficiencias notables. En concreto, todos estos lenguajes son declarativos y, por ello, debemos definir explícitamente toda nuestra infraestructura y las piezas que la unen. Con ellos, tiende a haber muchas plantillas, y hay poca o ninguna abstracción a este nivel, lo que resulta en definiciones de infraestructura difíciles de manejar para sistemas grandes o complejos.

Introduzca AWS Cloud Development Kit (AWS CDK) y el modelo de programación constructiva (CPM) en 2019. Después de luchar internamente con la falta de modularidad y tener la necesidad de abstraer los detalles de los patrones de arquitectura comunes, Amazon creó el AWS CDK dándonos una forma de definir la infraestructura como código utilizando un lenguaje de programación imperativo. Aunque inicialmente comenzó como un proyecto interno de Java, se convirtió en un proyecto de código abierto de Typescript. La compatibilidad con otros lenguajes se proporciona a través de otra herramienta de código abierto, JSII, que crea enlaces en esos lenguajes que les permiten interactuar con clases de JavaScript. La lista de lenguajes compatibles en el momento de escribir esto incluye Typescript, JS, Python, Java, C# y Go (vista previa). Desde su lanzamiento como código abierto, ha habido múltiples implementaciones del RPC derivadas de la solución de Amazon (AWS CDK). En el momento de escribir esto, esas implementaciones incluyen CDKtf (Terraform), CDK8s (Kubernetes), y Projen una herramienta para gestionar archivos de configuración de proyectos como package.json, tsconfig.json, etc. Este último demuestra que el uso del RPC no tiene por qué estar aislado sólo a la infraestructura, ya que es una metodología viable para cualquier cosa que se configure mediante lenguaje declarativo.

Aunque los ejemplos en este post serán escritos para AWS CDK, debe tenerse en cuenta que los beneficios de aprovechar el CPM no son exclusivos de la oferta de Amazon. Independientemente de la implementación que elijas, tienes todo el poder del lenguaje de programación de tu elección en la construcción de tu infraestructura, no importa lo simple o complejo que pueda ser. Si una característica es específica de AWS CDK, la mencionaré directamente.

Antes de continuar hay algunos términos clave que debemos conocer y su relación entre sí: app, construct y stack (tabla para CDK8s).

Constructo: El bloque de construcción central de cualquier app CDK. Un constructo corresponde a uno o más recursos sintetizados, que pueden ser una VPC, un pod Kubernetes, o incluso todos los recursos necesarios para un cluster de base de datos y un host bastión. Hablaremos de la abstracción de constructos en la siguiente sección de este post.

Pila (Gráfico para CDK8s): Una pila es el vehículo de despliegue de una aplicación CDK, y puede haber múltiples en una aplicación. Los stacks pueden ser vistos como una agrupación lógica de piezas relacionadas de infraestructura representadas por Constructs. Cada Stack/Chart cuando se sintetiza va a producir un archivo de configuración declarativo que corresponde al CDK de elección: una plantilla CloudFormation para AWS CDK, un archivo de configuración Terraform para CDKtf, un Manifiesto Kubernetes para CDK8s, etc.

App: Una aplicación CDK es un árbol de construcciones: el nodo raíz de este árbol es la construcción App, desde donde puede ramificarse a una o más construcciones de pila (o gráfico), las pilas tienen una o más construcciones que a su vez pueden abarcar una o más construcciones.

El siguiente diagrama proporciona una vista de alto nivel de lo que podría ser la estructura de una aplicación CDK

Infrastructure as Actual Code: An Introduction to the Construct Programming Model Blog - Graphic #1

Este post va acompañado de un proyecto de ejemplo que utiliza el CDK de AWS. He creado este proyecto principalmente para mostrar lo sencillo que es establecer estándares para las implementaciones de infraestructura ampliando las construcciones existentes, así como creando y utilizando construcciones abstractas que definen procesos o patrones de arquitectura completos. El siguiente diagrama ofrece una vista de alto nivel de la arquitectura que despliega el proyecto. Tenga en cuenta que, como he optado por un despliegue de clúster RDS multi-AZ, ese componente de la arquitectura no es elegible para la capa gratuita. Por lo tanto, si decide desplegar esta infraestructura, asegúrese de limpiarla después para evitar cargos excesivos. El proyecto se puede encontrar aquí: https://github.com/wwermager/cdk-with-custom-constructs  

 Infrastructure as Actual Code: An Introduction to the Construct Programming Model blog - Graphic #2

Abstracción de la infraestructura

Hay tres niveles de abstracción proporcionados por las construcciones: nivel 1 (L1), nivel 2 (L2) y nivel 3 (L3). Este último es el más abstracto. Las construcciones L1 son un mapeo 1:1 con su recurso subyacente en el lenguaje declarativo base. Las construcciones L2 son las que realmente hacen brillar a la RPC. En L2 ya no tenemos que lidiar con el exceso de burocracia que conlleva el uso de ese recurso en el nivel más bajo. Además, tenemos acceso a métodos de ayuda y configuraciones por defecto que nos hacen la vida mucho más fácil. Como ejemplo, un método de ayuda inmensamente útil de muchos AWS CDK Constructs es conceder que nos permite crear las políticas IAM necesarias para acceder a los recursos con una sola línea de código.

// From lib/api-stack.ts 
props.dbInfra.dbCluster.secret?.grantRead(isolatedApiFunction); 

Esta única línea se sintetiza en el siguiente recurso de CloudFormation. Esto por sí solo demuestra cuánto más sucinta y legible puede ser nuestra infraestructura como código haciendo uso de una implementación CPM.

"getlambdafunctionServiceRoleDefaultPolicyC1D2F054": { 
 "Type": "AWS::IAM::Policy", 
 "Properties": { 
  "PolicyDocument": { 
   "Statement": [ 
    { 
     "Action": [ 
      "secretsmanager:DescribeSecret", 
      "secretsmanager:GetSecretValue" 
     ], 
     "Effect": "Allow", 
     "Resource": { 
      "Fn::ImportValue": "DatabaseStack:ExportsOutputRefrdswithbastionhostrdsdatabaseclusterSecretAttachment7F0016B283030634" 
     } 
    } 
   ], 
   "Version": "2012-10-17" 
  }, 
  "PolicyName": "getlambdafunctionServiceRoleDefaultPolicyC1D2F054", 
  "Roles": [ 
   { 
    "Ref": "getlambdafunctionServiceRole4009E415" 
   } 
  ] 
 }, 
 "Metadata": { 
  "aws:cdk:path": "ApiStack/get-lambda-function/ServiceRole/DefaultPolicy/Resource" 
 } 
} 

Por último, están las construcciones L3, también llamadas comúnmente patrones. Estos son los más abstractos de los constructos y se utilizan para patrones comunes de arquitectura o alguna funcionalidad compleja. En la aplicación de ejemplo enlazada anteriormente, el constructor AuroraMysqlConBastionHost y MysqlSetup son ejemplos de construcciones L3. La primera, un patrón de arquitectura común, despliega la base de datos, la infraestructura de red y un host bastión. El segundo despliega una función lambda que instancia la base de datos con una tabla y algunos datos en nuestro primer despliegue de la pila de base de datos que nos proporciona funcionalidad compleja. Estas construcciones pueden ser desplegadas en una pila, configuradas por las propiedades expuestas por su interfaz. Las aproximadamente 20 líneas de código que aparecen a continuación sintetizan una plantilla de CloudFormation que tiene más de 2000 líneas de JSON formateado.

// From lib/api-stack.ts 
this.dbInfra = new AuroraMysqlWithBastionHost( 
  this, 
  "rds-with-bastion-host", 
  { 
    bastionHostInitScriptPath: path.join( 
      __dirname, 
      props.config.bastionHostInitScriptPath 
    ), 
    bastionhostKeyPairName: props.config.bastionhostKeyPairName, 
    dbAdminUser: props.config.dbAdminUser, 
    defaultDbName: props.config.defaultDbName, 
    dbSecretName: props.config.dbSecretName, 
    dbPort: props.config.dbPort, 
  } 
); 
 
new MysqlSetup(this, "mysql-setup", { 
  dbInfra: this.dbInfra, 
  lambdaDirectory: path.join(__dirname, props.config.lambdaApisDirectory), 
  defaultHandler: `utils/${props.config.defaultHandler}`, 
  dbTableName: props.config.dbTableName, 
}); 

Compartir patrones y establecer estándares

En la plantilla AuroraMysqlWithBastionHost mencionado anteriormente, estoy aprovechando una construcción L3 disponible públicamente que se encuentra aquí para crear automáticamente un par de claves para conectarse al host bastión EC2. Destaco esto porque muestra lo fácil que es compartir/consumir construcciones personalizadas, una ventaja clave del RPC. Simplemente añadiendo la librería a las dependencias de nuestro proyecto (package.json en este caso) podemos hacer uso de ella en cualquier construcción o pila que creemos. Cabe señalar que las construcciones escritas en Typescript ofrecen un poco más de flexibilidad en la forma en que se pueden compartir. Puesto que con JSII, se pueden crear enlaces de lenguaje para cualquiera de los lenguajes soportados, sin embargo, no hay nada que te impida escribir y compartir construcciones específicamente para el lenguaje de tu elección.

Esta facilidad de compartir y consumir construcciones es un gran punto de venta en el uso de una implementación CPM. Con la herencia de nuestro lado, se vuelve trivial extender una construcción existente y establecer o requerir ciertas propiedades. En el proyecto de ejemplo, he hecho esto tanto para la construcción Vpc y Función . Aquí hay un ejemplo de envolver la base Vpc con una configuración de subred explícita.

// From lib/common/network/database-vpc.ts 
export class DatabaseVpc extends ec2.Vpc { 
  constructor(scope: Construct, id: string, props?: VpcProps) { 
    super(scope, id, { 
      ...props, 
      subnetConfiguration: [ 
        { 
          cidrMask: 28, 
          name: "public-subnet", 
          subnetType: ec2.SubnetType.PUBLIC, 
        }, 
        { 
          cidrMask: 28, 
          name: "private-with-egress", 
          subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, 
        }, 
        { 
          cidrMask: 28, 
          name: "rds-private-subnet", 
          subnetType: ec2.SubnetType.PRIVATE_ISOLATED, 
        }, 
      ], 
    }); 
  } 
} 

A continuación se muestra un ejemplo de ampliación de la base Función base para que requiera que la función se coloque en una VPC que sea específicamente del tipo DatabaseVpc. 

// From lib/common/lambda/isolated-function.ts 
export interface IsolatedFunctionProps extends lambda.FunctionProps { 
  readonly vpc: DatabaseVpc; 
} 
 
export class IsolatedFunction extends lambda.Function { 
  constructor(scope: Construct, id: string, props: IsolatedFunctionProps) { 
    super(scope, id, { 
      ...props, 
      vpc: props.vpc, 
    }); 
  } 
} 

La capacidad de anular los valores predeterminados de esta manera hace que el CDK sea una poderosa herramienta en su caja de herramientas de gobierno y estandarización de la infraestructura para los requisitos específicos de la empresa. Para dar un ejemplo del mundo real, en un proyecto anterior en el que estuve había un equipo de infraestructura dedicado a definir y ampliar las construcciones de forma que cumplieran los requisitos de seguridad de la empresa y las mejores prácticas internas. A continuación, agruparon estas construcciones y las pusieron a disposición de todos los equipos internos a través de repositorios internos. Los equipos de aplicación eran libres de crear la infraestructura que necesitaran para sus aplicaciones utilizando estos constructos. Sólo en el caso de que un equipo decidiera no utilizar estas construcciones curadas, se le pedirían aprobaciones o excepciones específicas para cada caso de uso por parte de los equipos de seguridad de la información. Estas prácticas en torno al CDK nos ahorraron una cantidad de tiempo considerable que normalmente habríamos invertido en reuniones para conseguir la aprobación de la infraestructura, esperando a que la infraestructura fuera desplegada por algún otro equipo o aprendiendo algún lenguaje esotérico de IaC.

Cuál es el truco

Aunque puedo ser un poco parcial sobre el uso de una implementación CPM cuando esté disponible debido a mi experiencia previa en proyectos, hay definitivamente algunas cosas a tener en cuenta cuando se utiliza uno. Aunque el CPM permite a los desarrolladores y equipos de infraestructura moverse más rápido en la construcción de sistemas complejos, las abstracciones y la flexibilidad que nos da esa velocidad no es un sustituto para el conocimiento fundacional sólido de los recursos subyacentes que estamos construyendo y las mejores prácticas que los rodean. Un equipo de aplicaciones con poca o ninguna experiencia en la nube no debería desplegar infraestructura de nube sin cierta supervisión. Hacer uso de construcciones curadas internamente para sus necesidades empresariales puede ayudar a aliviar esta preocupación.

Incluso con un conocimiento profundo de la plataforma de su elección, el nivel extremo de abstracción que es posible, especialmente cuando se utilizan construcciones L3 (patrones), podría ser motivo de preocupación. Esta preocupación puede ser mitigada en su mayoría por el hecho de que al final del día, todavía estamos desplegando la misma plantilla CloudFomration, configuración Terraform, o manifiesto Kubernetes que habríamos tenido que construir de todos modos. Por ejemplo, en el ejemplo de proyecto AWS CDK que he proporcionado si lo construyes puedes encontrar las plantillas CloudFormation generadas en el archivo cdk.out directorio. Incluso mejor que mirar la salida sintetizada sería escribir pruebas unitarias minuciosas. AWS CDK, CDKtf, y CDK8s proporcionan un conjunto de utilidades para las pruebas. Podemos utilizar estas herramientas para asegurarnos de que sólo se están creando los recursos que esperamos y se están aplicando las configuraciones que hemos dado. A continuación se muestra un ejemplo de prueba que verifica que la VPC que la pila de base de datos despliega en la aplicación de ejemplo tiene 3 subredes (véase la construcción Vpc personalizada más arriba) para cada zona de disponibilidad en nuestra región de despliegue.

// From test/database-stack.ts 
const ec2Client = new EC2Client({ region: process.env.AWS_REGION }); 
const app = new cdk.App(); 
const stack = new DatabaseStack.DatabaseStack(app, "database-stack", { 
  env, 
  config, 
}); 
const template = Template.fromStack(stack); 
// TEST CASE 
it("should deploy 3 subnets for each availability zone in our region.", async () => { 
  const azs = await ec2Client.send(new DescribeAvailabilityZonesCommand({})); 
  template.resourceCountIs( 
    "AWS::EC2::Subnet", 
    azs.AvailabilityZones.length * 3 
  ); 
}); 

Por último, aunque no es tanto una preocupación como un reto potencial en el uso, estos proyectos se encuentran en varios estados de finalización. Cuando se añade o actualiza un constructo a AWS CDK, el constructo L1 está disponible inmediatamente debido a esa asignación 1:1; sin embargo, los constructos L2 y L3 pueden tender a retrasarse a medida que los recursos subyacentes reciben actualizaciones. Por otro lado, CDKtf aún no está disponible de forma generalizada, por lo que existe cierto riesgo inherente al elegir utilizarlo, ya que existe la posibilidad de que se produzcan cambios en la API con cada nueva versión. Otro desafío con CDKtf es que la mayoría de las construcciones para los proveedores disponibles parecen ser L1, por lo que no hay mucho en la forma de métodos de ayuda como AWS CDK's grant de AWS CDK. Terraform no tiene la culpa de esto, ya que proporcionar un único método que funcione con todas las APIs de los proveedores relevantes es una tarea mucho más compleja. Ten en cuenta que si estás utilizando el proveedor de AWS a través de CDKtf, es posible que puedas hacer uso de Terraform's Adaptador AWS de Terraform, que permite utilizar las construcciones de AWS CDK directamente dentro de CDKtf, aunque para cualquier otro proveedor tendrás que crear permisos explícitamente. Cabe señalar que este adaptador de AWS también está sólo en la vista previa técnica.

Reflexiones finales

El RPC y sus implementaciones son una forma excelente de acelerar el proceso de pasar de un diagrama de arquitectura a una solución robusta. La capacidad de personalizar y crear constructos fácilmente nos permite crear recursos que satisfacen necesidades empresariales específicas o incluso proporcionar patrones de arquitectura completos que se pueden distribuir y reutilizar fácilmente de forma pública o solo en nuestra organización. La creación de estas construcciones, nuestra infraestructura, en un lenguaje de programación completo nos proporciona mucho más poder del que tenemos con la naturaleza declarativa de la mayoría de las soluciones de IaC. Además, podemos asegurarnos de que escribimos pruebas unitarias sólidas contra nuestras pilas para protegernos de cambios accidentales y asegurarnos de que estamos desplegando exactamente lo que esperamos desplegar. Aunque algunas de las implementaciones de CPM aún están en fase de desarrollo, te recomiendo encarecidamente que pruebes una de ellas en tu próximo proyecto. Personalmente, estoy muy ansioso por empezar a trabajar con la oferta CDK de Terraform a pesar de su relativa infancia. A menos que un proyecto sea totalmente nativo de AWS, es casi seguro que Terraform será la opción preferida para IaC. Recursos útiles

Tecnología
Ingeniería de plataformas

¿Necesita ayuda para crear su próxima aplicación Go?

Reflexiones más recientes

Explore las entradas de nuestro blog e inspírese con los líderes de opinión de todas nuestras empresas.
Tomorrow Technology Today Blog Thumbnail
DATOS

Cómo saber si su plataforma de datos es realmente moderna

Exploremos qué define una plataforma de datos moderna y cómo liberar todo su potencial para su empresa.