Preparándose para listar una versión paga de nuestro Software Qumulo File Fabric (QF2) en AWS Marketplace significa que nuestro equipo de ingeniería en la nube tuvo que resolver algunos desafíos únicos.
Esta publicación de blog es la primera de una serie en la que discutimos algunos de esos desafíos y mostramos cómo los hemos resuelto inicialmente. Esto no quiere decir que esta es la solución final o incluso la mejor solución, pero una forma para nosotros aquí en Qumulo es compartir lo que hemos aprendido.
El objetivo principal de obtener su producto en AWS Marketplace es permitir que sus clientes compren, implementen y administren directamente su infraestructura. Para los proveedores de software, es conveniente porque Amazon maneja toda la facturación y la licencia.
AWS Marketplace: lo hace más fácil para los clientes, conveniente para los proveedores
Para nosotros, el mayor desafío es facilitar a los clientes la implementación del producto en el proceso más libre de fricción posible. Todos sabemos que esos primeros segundos con cualquier producto realmente importan, por lo que la experiencia lista para usar es realmente importante.
Para entregar software a través del Marketplace, AWS alienta a los proveedores a utilizar CloudFormation, el mecanismo de Infraestructura como código de Amazon, que generalmente se entrega en un archivo con formato JSON o YAML.
En CloudFormation faltan constructos de lenguaje simples que pueden complicar un poco las cosas. Tome el bucle, por ejemplo. No existe la posibilidad de crear fácilmente múltiples partes idénticas de infraestructura sin recurrir a AutoScaling, que puede no ser una buena opción para su software. En un lenguaje como Python, simplemente establecería mi límite superior y bombearía una matriz de Objetos y seguiría adelante. Con CloudFormation, terminas repitiendo el código para crear infraestructura.
Por ejemplo, aquí en Qumulo, admitimos clusters de nodos 4 y superiores. Ir con el número más bajo significa que tendría cuatro copias del código de creación de Instancia EC2. Esto se vuelve muy difícil de manejar una vez que alcanzamos los conteos de nodos de dos dígitos.
"Resources": { "QF2Node1": { "Properties": { "ImageId": { "Fn::FindInMap": [ "RegionMap", { "Ref": "AWS::Region" }, "AMI" ] }, "InstanceType": { "Ref": "InstanceType" }, "KeyName": { "Ref": "KeyName" }, "NetworkInterfaces": [ { "AssociatePublicIpAddress": "false", "DeleteOnTermination": "true", "DeviceIndex": 0, "GroupSet": [ { "Ref": "QumuloSecurityGroup" } ], "SubnetId": { "Ref": "SubnetId" } } ] }, "Type": "AWS::EC2::Instance" }, "QF2Node2": { "Properties": { "ImageId": { "Fn::FindInMap": [ "RegionMap", { "Ref": "AWS::Region" }, "AMI" ] }, "InstanceType": { "Ref": "InstanceType" }, "KeyName": { "Ref": "KeyName" }, "NetworkInterfaces": [ { "AssociatePublicIpAddress": "false", "DeleteOnTermination": "true", "DeviceIndex": 0, "GroupSet": [ { "Ref": "QumuloSecurityGroup" } ], "SubnetId": { "Ref": "SubnetId" } } ] }, "Type": "AWS::EC2::Instance" }, "QF2Node3": { "Properties": { "ImageId": { "Fn::FindInMap": [ "RegionMap", { "Ref": "AWS::Region" }, "AMI" ] }, "InstanceType": { "Ref": "InstanceType" }, "KeyName": { "Ref": "KeyName" }, "NetworkInterfaces": [ { "AssociatePublicIpAddress": "false", "DeleteOnTermination": "true", "DeviceIndex": 0, "GroupSet": [ { "Ref": "QumuloSecurityGroup" } ], "SubnetId": { "Ref": "SubnetId" } } ] }, "Type": "AWS::EC2::Instance" }, "QF2Node4": { "Properties": { "ImageId": { "Fn::FindInMap": [ "RegionMap", { "Ref": "AWS::Region" }, "AMI" ] }, "InstanceType": { "Ref": "InstanceType" }, "KeyName": { "Ref": "KeyName" }, "NetworkInterfaces": [ { "AssociatePublicIpAddress": "false", "DeleteOnTermination": "true", "DeviceIndex": 0, "GroupSet": [ { "Ref": "QumuloSecurityGroup" } ], "SubnetId": { "Ref": "SubnetId" } } ] }, "Type": "AWS::EC2::Instance" },
Obtener la entrada del usuario sobre el tamaño del clúster
Otro problema es que, sin la intervención del usuario, ¿cómo puedo saber qué tamaño de clúster desearía crear el cliente? Ahora, CloudFormation puede tomar entradas, pero el uso de la lógica condicional limitada disponible en el lenguaje significa que cada permutación de entrada posible debe tenerse en cuenta en la plantilla de CloudFormation. Idealmente, podríamos generar automáticamente una plantilla de CloudFormation por usuario, y luego simplemente iniciar al usuario en un clúster que se personalice según sus necesidades.
Para resolver este problema, comenzamos a crear un formulario simple que un cliente podría utilizar para ingresar el tamaño del clúster que desea, recibir comentarios inmediatos sobre la capacidad a la que tendrá acceso y luego iniciarlo directamente en CloudFormation. En el back-end, este formulario enviaría el recuento de nodos del clúster, el tipo de instancia y la región AWS a un script de back-end. Este script de back-end se encuentra actualmente en nuestra página GitHub en la carpeta Cloud-Deployment-Samples. Aprovecha la Troposfera, que le permite construir de forma programada plantillas a medida de CloudFormation. Por ejemplo, el mismo clúster de nodo 4 que se muestra anteriormente se puede crear utilizando esta función:
# add_nodes () toma un objeto de plantilla dado, un recuento de nodos para crear y # un nombre para prefijar todas las instancias EC2. Las instancias EC2 se crearán con la estructura de nomenclatura # Prefix + Node + NodeNumber. def add_nodes (t, nodos, prefijo): lista_nodos = [] para x en rango (0, nodos): nombre_nodo = prefijo + "Nodo" + str ((x + 1)) t.add_resource (ec2.Instance (nombre_nodo, ImageId = FindInMap ("RegionMap", Ref ("AWS :: Region"), "AMI"), InstanceType = Ref ("InstanceType"), KeyName = Ref ("KeyName"), NetworkInterfaces = [ec2.NetworkInterfaceProperty (AssociatePublicIpAddress = False, GroupSet = [Ref ("QumuloSecurityGroup")], DeviceIndex = 0, DeleteOnTermination = True, SubnetId = Ref ("SubnetId"),)])) nodes_list.append (node_name) # Crea una lista que contenga las IP privadas de todos nodos. output_ips = [] for i in nodes_list: output_ips.append (GetAtt (i, "PrivateIp")) t.add_output (Output ("ClusterPrivateIPs", Description = "Copiar y pegar esta lista en la pantalla de creación de clústeres QF2", Valor = Join (",", output_ips),)) t.add_output (Output ("LinkToManagement", Description = "Haga clic para iniciar la consola de administración de QF2", Value = Join ("", ["https: //", GetAtt ( node_list [0], "PrivateIp")]),)) t.add_output (Output ("InstanceId", Description = "Copie y pegue este ID de instancia en la pantalla de creación de clúster QF2.", Valor = Ref (prefijo + "Node1 "),))
A primera vista, esto no parece mucho más corto, pero analicemos lo que está sucediendo aquí. Esta función crea cualquier número de nodos utilizando la configuración que el cliente selecciona a través de otras partes del script. Todos los nodos se han creado en las líneas de código 18 en el ciclo for. Todo después de eso realmente proporciona algunas sutilezas de experiencia del usuario.
# Cree una lista que contenga las IP privadas de todos los nodos. output_ips = [] para i en la lista de nodos: output_ips.append (GetAtt (i, "PrivateIp"))
En esta sección, tomamos cada dirección IP privada de la instancia EC2 y la agregamos a una lista para más adelante. Las siguientes secciones de Salida realmente colocan texto y enlaces en la pestaña de Salida de CloudFormation para que el cliente pueda completar la configuración y acceder a su nuevo clúster. Actualmente, después de que se crea un clúster, el cliente navega a la página de administración de cualquier nodo, responde una pregunta de desafío sobre su cuenta AWS (en este caso solicitamos el ID de instancia del nodo) y proporciona una lista delimitada por comas. de todos los nodos IP privadas. Las salidas aquí ponen un enlace a la dirección IP de gestión del primer nodo, su ID de instancia y una lista delimitada por comas de todas las direcciones IP privadas. Todo lo que necesita para obtener el clúster, todo en un solo lugar.