paint-brush
Cómo optimizar CI/CD con Octopus y TeamCitypor@socialdiscoverygroup
4,027 lecturas
4,027 lecturas

Cómo optimizar CI/CD con Octopus y TeamCity

por Social Discovery Group13m2024/05/14
Read on Terminal Reader

Demasiado Largo; Para Leer

No es ningún secreto que sin procesos claros y organizados, los desarrolladores pueden tener dificultades para colaborar de manera efectiva, lo que genera retrasos en la entrega de actualizaciones de software. En este artículo, el equipo de Social Discovery Group comparte cómo construir un canal de CI/CD conveniente y flexible con una combinación de TeamCity y Octopus.
featured image - Cómo optimizar CI/CD con Octopus y TeamCity
Social Discovery Group HackerNoon profile picture
0-item


No es ningún secreto que sin procesos claros y organizados, los desarrolladores pueden tener dificultades para colaborar de manera efectiva, lo que genera retrasos en la entrega de actualizaciones de software. Hace unos años, el equipo de Social Discovery Group enfrentó el desafío de un proceso de CI/CD subóptimo. En ese momento, el equipo utilizó TeamCity y Octopus, cada uno con sus puntos fuertes. Por ejemplo, Octopus es conveniente para implementaciones, mientras que TeamCity es bueno para pruebas automatizadas y suficientemente conveniente para la construcción de proyectos. Para construir un canal de CI/CD completo y visualmente atractivo que tenga una configuración máximamente conveniente y flexible, es necesario utilizar una combinación de herramientas. El código se almacenó en un repositorio local en Bitbucket para varios proyectos. El equipo de ODS estudió el tema y decidió optimizar el proceso utilizando las herramientas existentes.


Objetivos clave de optimización:

  1. Creación e implementación automáticas desde TeamCity en entornos específicos.
  2. Nombrar compilaciones: se agrega "lanzamiento" a la rama maestra para diferenciar las compilaciones por nombres.
  3. Construcción e implementación automáticas al enviar a las ramas correspondientes en los respectivos entornos de prueba de los respectivos servicios.
  4. Establecer un proceso en el que se debe completar la implementación en los entornos de prueba y desarrollo antes de la implementación en el ensayo y luego en la producción. Esto se implementó en Octopus.


El equipo de SDG decidió utilizar TeamCity para compilaciones y pruebas automatizadas, y Octopus para implementaciones.


Qué se implementó en TeamCity:

  1. TeamCity permite el uso de tres agentes en la versión gratuita, lo que fue suficiente para el equipo de SDG. Instalaron un nuevo agente, lo agregaron al grupo y lo aplicaron a sus plantillas.

  2. En el momento de utilizar la última versión de TeamCity, el equipo estaba trabajando en Ubuntu Server. La captura de pantalla muestra los complementos adicionales utilizados por el equipo:



  1. A partir de las herramientas, el equipo agregó complementos, como Allure 2.14.0 para la creación de informes y Nuget 5.5.1.
  2. Para simplificar la ejecución de tareas similares, el equipo de SDG creó varias plantillas para diferentes tipos de implementaciones: NuGet y servicios.
  3. Cada una de estas plantillas incluía varios pasos, reflejados en las capturas de pantalla a continuación.


La implementación de NuGet fue la siguiente:




Vale la pena señalar que dependiendo de si la rama es maestra o no, se agregó "-release" a la versión (pasos 3, 4).


El despliegue de servicios se puede ver a continuación:


Para cada servicio, las variables correspondientes se sustituyeron en función de las variables del sistema (nombre del servicio, %build.number% y otras).


En la captura de pantalla se presenta un ejemplo del paso Docker Build:


Cada repositorio de proyecto contenía el Dockerfile correspondiente.


Las diferencias entre los pasos 4 y 5, como se mencionó anteriormente, fueron las siguientes:



La variable %deploymentTarget% sirvió como parámetro de entorno(s), al cual se pasaron las etapas correspondientes en Octopus durante la implementación (por ejemplo, Prueba, Desarrollo). Cuando los cambios se enviaron a las respectivas ramas (configuradas) de los equipos de desarrollo, se realizaron automáticamente compilaciones e implementaciones de software en los entornos de prueba correspondientes. La configuración es visible en la captura de pantalla siguiente. Para conectarse con Octopus, era necesario agregar dos parámetros globales: octopus.apiKey y octopus.url



Además, el equipo de SDG conectó un repositorio NuGet común y un Registro de contenedores para todos los proyectos en la sección Conexiones.


Además, SDG recomienda configurar notificaciones por correo electrónico en la sección Notificador de correo electrónico, configurar copias de seguridad en la sección Copia de seguridad, crear los grupos necesarios, asignar roles adecuados y agregar usuarios a los grupos requeridos. La configuración principal está completa y, en conclusión, el equipo recomienda buscar actualizaciones periódicamente y actualizar TeamCity una vez al mes.


A continuación, el equipo de Social Discovery Group pasó a configurar Octopus. Este artículo no describirá los detalles de instalación, la configuración básica de los derechos de usuario y otros aspectos, porque usted mismo puede hacerlos fácilmente. El equipo abordó de inmediato el ciclo de vida, que se configura en la sección Biblioteca. En la siguiente captura de pantalla, puede ver un ejemplo de flujo del equipo de ODS:



Luego, el equipo creó todos los grupos de variables necesarios por temas en Conjuntos de variables. Para cada variable, se establecieron valores y se establecieron dependencias con el entorno, objetivos y roles de objetivos (etiquetas). En la siguiente captura de pantalla se muestra un ejemplo:



Los clústeres en Kubernetes servían como objetivos y los roles de destino eran etiquetas adjuntas a los clústeres o entornos informáticos correspondientes. Todo esto se puede configurar en el apartado de Infraestructura.


También se pueden agrupar proyectos y configurar un panel conveniente para mostrar los servicios, las etapas y las versiones implementadas en ellos.

El proceso de implementación de SDG fue el siguiente: todas las etapas de prueba se combinaron en un solo paso y se creó una plantilla común para ellas, de manera similar para la etapa y las etapas en vivo.


La siguiente captura de pantalla muestra cómo se vio esto para el equipo de ODS:

A la derecha se seleccionó el ciclo de vida descrito anteriormente. La etapa Implementar un paquete incluía configuraciones predeterminadas bastante simples.

Para la etapa Implementar Raw Kubernetes Yaml, el equipo de SDG utilizó plantillas universales de Yaml autoescritas. En este ejemplo, Kubernetes Script, se explica con más detalle a continuación. También se sustituyeron los parámetros correspondientes marcados en rojo. Vale la pena señalar que los grupos de variables globales necesarios se conectaron en el menú Variables->Conjuntos de variables, y las variables específicas del proyecto se configuraron en el menú Variables->Proyecto, que tenía mayor prioridad.


En este artículo, el equipo de ODS decidió omitir detalles como agregar un logotipo al proyecto, configurar activadores u otros detalles menores. Centrémonos en dos elementos de menú importantes: 1 - Lanzamientos, donde siempre puede ver la versión y la fecha de creación de un lanzamiento en particular; Esta información también se muestra en el panel del proyecto, 2 - Variables->Vista previa, donde puede ver qué variables se sustituirán para la etapa correspondiente.





Pasando a la parte más importante: la implementación de plantillas de Yaml en clústeres de Kubernetes. Fueron creados en la sección Biblioteca->Plantillas de pasos. A continuación, el equipo de ODS presentó una captura de pantalla utilizando sus parámetros. Para cada parámetro, puede elegir una etiqueta, un tipo y un valor predeterminado, además de agregar una descripción, lo cual es muy recomendable.




El código en este caso era el siguiente:


 apiVersion: apps/v1 kind: Deployment metadata: name: '#{Octopus.Project.Name | ToLower}' namespace: #{Octopus.Environment.Name | ToLower} labels: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' spec: replicas: #{Replicas} strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 25% revisionHistoryLimit: 10 progressDeadlineSeconds: 600 selector: matchLabels: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' template: metadata: labels: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' spec: volumes: #{if usesidecar} - name: dump-storage persistentVolumeClaim: claimName: dumps-#{Octopus.Environment.Name | ToLower} #{/if} #{if MountFolders} #{each folder in MountFolders} - name: volume-#{folder | ToBase64 | Replace "\W" X | ToLower} hostPath: path: #{folder} type: DirectoryOrCreate #{/each} #{/if} - name: logs-volume hostPath: path: #{LogsDir} type: DirectoryOrCreate - name: appsettings secret: secretName: #{Octopus.Project.Name | ToLower} #{if Secrets} #{each secret in Secrets} - name: #{secret.name} secret: secretName: #{secret.name} #{/each} #{/if} #{if usesidecar} - name: diagnostics emptyDir: {} - name: dumps configMap: name: dumps defaultMode: 511 #{/if} containers: - name: #{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}-container image: #{DockerRegistry}/projectname.#{Octopus.Project.Name | ToLower}:#{Octopus.Release.Notes} #{if resources} resources: #{each resource in resources} #{resource.Key}: #{each entry in resource.Value} #{entry.Key}: #{entry.Value} #{/each} #{/each} #{/if} ports: - name: http containerPort: 80 protocol: TCP env: - value: "Development" name: "ASPNETCORE_ENVIRONMENT" - name: DD_ENV value: "#{Octopus.Environment.Name | ToLower}" - name: DD_SERVICE value: "#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}" - name: DD_VERSION value: "1.0.0" - name: DD_AGENT_HOST value: "#{DatadogAgentHost}" - name: DD_TRACE_ROUTE_TEMPLATE_RESOURCE_NAMES_ENABLED value: "true" - name: DD_RUNTIME_METRICS_ENABLED value: "true" volumeMounts: #{if usesidecar} - name: dump-storage mountPath: /tmp/dumps #{/if} #{if MountFolders} #{each folder in MountFolders} - mountPath: #{folder} name: volume-#{folder | ToBase64 | Replace "\W" X | ToLower} #{/each} #{/if} - mountPath: #{LogsDir} name: logs-volume #{if usesidecar} - name: diagnostics mountPath: /tmp #{/if} - name: appsettings readOnly: true mountPath: /app/appsettings.json subPath: appsettings.json #{if Secrets} #{each secret in Secrets} - name: #{secret.name} readOnly: true mountPath: #{secret.mountPath} subPath: #{secret.subPath} #{/each} #{/if} readinessProbe: httpGet: path: hc port: http scheme: HTTP initialDelaySeconds: #{InitialDelaySeconds} imagePullPolicy: IfNotPresent securityContext: {} #{if usesidecar} - name: sidecar image: '#{DockerRegistry}/monitor:3' command: - /bin/sh args: - '-c' - while true; do . /app/init.sh; sleep 1m;done env: - name: USE_MEMORY value: '2048' - name: PROJECT value: "#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}" resources: {} volumeMounts: - name: diagnostics mountPath: /tmp - name: dump-storage mountPath: /tmp/dumps - name: dumps mountPath: /app/init.sh subPath: init.sh shareProcessNamespace: true #{/if} affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: environment operator: In values: - "#{Node}" --- apiVersion: v1 kind: Service metadata: name: #{Octopus.Project.Name | ToLower} namespace: #{Octopus.Environment.Name | ToLower} labels: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' spec: type: ClusterIP selector: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' ports: - name: http port: 80 targetPort: http protocol: TCP --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: ingress.kubernetes.io/ssl-redirect: 'false' nginx.ingress.kubernetes.io/ssl-redirect: 'false' cert-manager.io/cluster-issuer: "letsencrypt-cluster-issuer" cert-manager.io/renew-before: '#{LetsencryptRenewBefore}' kubernetes.io/ingress.class: nginx #{if IngressAnnotations} #{each annotation in IngressAnnotations} #{annotation.Key}: #{annotation.Value} #{/each} #{/if} name: #{Octopus.Project.Name | ToLower} namespace: #{Octopus.Environment.Name | ToLower} labels: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' spec: tls: #{if ExternalHost} #{each host in ExternalHost} - hosts: - #{host} secretName: #{Octopus.Project.Name | ToLower}-#{host | ToBase64 | Replace "\W" X | ToLower}-tls #{/each} #{/if} rules: #{if ExternalHost} #{each host in ExternalHost} - host: '#{host}' http: paths: - path: / pathType: ImplementationSpecific backend: service: name: #{Octopus.Project.Name | ToLower} port: name: http #{/each} #{/if} #{if usesidecar} --- apiVersion: v1 kind: ConfigMap metadata: name: dumps namespace: #{Octopus.Environment.Name | ToLower} data: init.sh: |- #!/usr/bin/env bash mem=$(ps aux | awk '{print $6}' | sort -rn | head -1) mb=$(($mem/1024)) archiveDumpPath="/tmp/dumps/$PROJECT-$(date +"%Y%m%d%H%M%S").zip" fullPathGc="/tmp/$PROJECT-$(date +"%Y%m%d%H%M%S").dump" echo "mem:" $mb" project:" $PROJECT "use:" $USE_MEMORY if [ "$mb" -gt "$USE_MEMORY" ]; then export USE_MEMORY=$(($USE_MEMORY*2)) pid=$(dotnet-dump ps | awk '{print $1}') dotnet-dump collect -p $pid -o $fullPathGc zip $fullPathGc.zip $fullPathGc mv $fullPathGc.zip $archiveDumpPath rm $fullPathGc fi #{/if}


Todas las variables en Octopus se especificaron en el siguiente formato en el código: '#{Octopus.Project.Name | ToLower}' , donde la última parte indica la conversión a minúsculas.


El último archivo de configuración se creó para guardar automáticamente el estado de los servicios .NET cuando alcanzaron un cierto límite de uso de memoria. Esto ayudó significativamente a identificar pérdidas de memoria durante el desarrollo y reparar rápidamente los servicios.


Finalmente, el panel de servicio quedó de la siguiente manera:


A los equipos de desarrollo y pruebas les resultó muy conveniente trabajar con este panel.


Resultados de optimización:


  1. El equipo de ODS creó un proceso CI/CD eficiente que mejoró significativamente la velocidad y la conveniencia del desarrollo. El equipo trabajó bastante tiempo dentro de este marco de proceso.
  2. SDG también introdujo pruebas automatizadas en la conveniente herramienta TeamCity e implementaciones de servicios automatizadas.
  3. A través del panel Octopus fácil de usar, el equipo configuró derechos de acceso y pudo administrar implementaciones y entornos.

Posteriormente, SDG implementó muchas otras funciones en Octopus. Por ejemplo, el apagado automático de grupos por la noche según un horario.


Sin embargo, la búsqueda de la perfección no conoce límites. El equipo de Social Discovery Group avanzó en su desarrollo al dominar Azure DevOps. Configuraron un proceso en Azure DevOps dentro de un ecosistema en Helm, aún más completo y eficiente. Esto se tratará en el próximo artículo.


Nos encantaría conocer su experiencia en la configuración y optimización de CI/CD utilizando Octopus y TeamCity. ¡Comparte tus ideas y consejos!