paint-brush
Como simplificar CI/CD com Octopus e TeamCitypor@socialdiscoverygroup
4,199 leituras
4,199 leituras

Como simplificar CI/CD com Octopus e TeamCity

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

Muito longo; Para ler

Não é nenhum segredo que, sem processos claros e organizados, os desenvolvedores podem ter dificuldades para colaborar de forma eficaz, levando a atrasos na entrega de atualizações de software. Neste artigo, a equipe do Social Discovery Group compartilha como construir um pipeline de CI/CD conveniente e flexível com uma combinação de TeamCity e Octopus.
featured image - Como simplificar CI/CD com Octopus e TeamCity
Social Discovery Group HackerNoon profile picture
0-item


Não é nenhum segredo que, sem processos claros e organizados, os desenvolvedores podem ter dificuldades para colaborar de forma eficaz, levando a atrasos na entrega de atualizações de software. Há alguns anos, a equipe do Social Discovery Group enfrentou o desafio de um processo de CI/CD abaixo do ideal. Naquela época, a equipe utilizava TeamCity e Octopus, cada um com seus pontos fortes. Por exemplo, o Octopus é conveniente para implantações, enquanto o TeamCity é bom para testes automatizados e suficientemente conveniente para construção de projetos. Para construir um pipeline de CI/CD abrangente e visualmente atraente que seja extremamente conveniente e flexível na configuração, é necessário usar uma combinação de ferramentas. O código foi armazenado em um repositório local no Bitbucket para diversos projetos. A equipe do ODS estudou o assunto e decidiu otimizar o processo utilizando as ferramentas existentes.


Principais objetivos de otimização:

  1. Construção e implantação automáticas do TeamCity em ambientes especificados.
  2. Nomenclatura de compilações: "release" é adicionado ao branch master para diferenciar compilações por nomes.
  3. Construção e implantação automáticas ao enviar para filiais correspondentes nos respectivos ambientes de teste dos respectivos serviços.
  4. Estabelecer um processo em que a implantação em ambientes de teste e desenvolvimento deve ser concluída antes da implantação na preparação e depois na produção. Isso foi implementado no Octopus.


A equipe do SDG decidiu usar o TeamCity para compilações e testes automatizados, e o Octopus para implantações.


O que foi implementado no TeamCity:

  1. O TeamCity permite a utilização de três agentes na versão gratuita, o que foi suficiente para a equipe do ODS. Eles instalaram um novo agente, adicionaram-no ao pool e aplicaram-no aos seus modelos.

  2. Na hora de usar a versão mais recente do TeamCity, a equipe estava trabalhando no Ubuntu Server. A captura de tela mostra os plugins adicionais usados pela equipe:



  1. A partir das ferramentas, a equipe adicionou plugins, como Allure 2.14.0 para criação de relatórios e Nuget 5.5.1.
  2. Para simplificar a execução de tarefas semelhantes, a equipe do SDG criou diversos modelos para diferentes tipos de implantações: NuGet e serviços.
  3. Cada um desses modelos incluía várias etapas, refletidas nas capturas de tela abaixo.


A implantação do NuGet foi a seguinte:




Vale ressaltar que dependendo se o branch é master ou não, "-release" foi adicionado ao release (etapas 3, 4).


A implantação dos serviços pode ser vista abaixo:


Para cada serviço, as variáveis correspondentes foram substituídas com base nas variáveis do sistema (nome do serviço, %build.number% e outras).


Um exemplo da etapa Docker Build é apresentado na captura de tela:


Cada repositório do projeto continha o Dockerfile correspondente.


As diferenças entre as etapas 4 e 5, conforme mencionado anteriormente, foram as seguintes:



A variável %deploymentTarget% serviu como parâmetro de Ambiente(s), para o(s) qual(is) estágio(s) correspondente(s) no Octopus foram passados durante a implantação (por exemplo, Teste, Dev). Quando as alterações eram enviadas para as respectivas filiais (configuradas) das equipes de desenvolvimento, as compilações e implantações de software nos ambientes de teste correspondentes eram executadas automaticamente. As configurações estão visíveis na imagem abaixo. Para conectar-se ao Octopus, dois parâmetros globais precisaram ser adicionados: octopus.apiKey e octopus.url



Além disso, a equipe do SDG conectou um repositório NuGet comum e um Container Registry para todos os projetos na seção Conexões.


Além disso, o SDG recomenda configurar notificações por email na seção Email Notifier, configurar backups na seção Backup, criar grupos necessários, atribuir funções apropriadas e adicionar usuários aos grupos necessários. A configuração principal está concluída e, para concluir, a equipe recomenda verificar regularmente se há atualizações e atualizar o TeamCity uma vez por mês.


Em seguida, a equipe do Social Discovery Group passou a configurar o Octopus. Este artigo não descreverá os detalhes da instalação, configurações básicas de direitos do usuário e outros aspectos, porque você pode fazer isso facilmente sozinho. A equipe abordou imediatamente o ciclo de vida, que está configurado na seção Biblioteca. Na captura de tela abaixo, você pode ver um exemplo de fluxo da equipe ODS:



Em seguida, a equipe criou todos os grupos de variáveis necessários por temas em Conjuntos de Variáveis. Para cada variável foram definidos valores e estabelecidas dependências do ambiente, alvos e funções alvo (tags). Um exemplo é mostrado na captura de tela abaixo:



Os clusters no Kubernetes serviam como alvos, e as funções de destino eram tags anexadas aos clusters ou ambientes de computador correspondentes. Tudo isso pode ser configurado na seção Infraestrutura.


Os projetos também podem ser agrupados e um painel conveniente pode ser configurado para exibir serviços, estágios e versões implantadas neles.

O processo de implantação do ODS foi o seguinte: todos os estágios de teste foram combinados em uma única etapa, e um modelo comum foi criado para eles, da mesma forma para os estágios de estágio e de produção.


A captura de tela abaixo mostra como isso foi para a equipe do ODS:

À direita, foi selecionado o Ciclo de Vida descrito anteriormente. O estágio Implantar um Pacote incluía configurações padrão bastante simples.

Para o estágio Deploy Raw Kubernetes Yaml, a equipe SDG usou modelos Yaml universais auto-escritos. Neste exemplo - Kubernetes Script, é explicado com mais detalhes abaixo. Os parâmetros correspondentes marcados em vermelho também foram substituídos. Vale a pena notar que os grupos de variáveis globais necessários foram conectados no menu Variáveis->Conjuntos de variáveis, e as variáveis específicas do projeto foram definidas no menu Variáveis->Projeto, que tinha maior prioridade.


Neste artigo, a equipe do ODS decidiu pular detalhes como adicionar um logotipo ao projeto, configurar gatilhos ou outros detalhes menores. Vamos nos concentrar em dois itens importantes do menu: 1 - Lançamentos, onde você sempre pode ver a versão e a data de criação de um determinado lançamento; essas informações também são exibidas no dashboard do projeto, 2 - Variáveis->Pré-visualização, onde é possível visualizar quais variáveis serão substituídas pela etapa correspondente.





Passando para a parte mais importante: implantação de modelos Yaml em clusters Kubernetes. Eles foram criados na seção Biblioteca->Step Templates. Abaixo, a equipe do ODS apresentou uma captura de tela utilizando seus parâmetros. Para cada parâmetro, você pode escolher uma tag, tipo e valor padrão, bem como adicionar uma descrição, o que é altamente recomendado.




O código neste caso ficou assim:


 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 as variáveis no Octopus foram especificadas no seguinte formato no código: '#{Octopus.Project.Name | ToLower}' , onde a última parte indica a conversão para minúsculas.


O último arquivo de configuração foi criado para salvar automaticamente o estado dos serviços .NET quando atingirem um determinado limite de uso de memória. Isso ajudou significativamente a identificar vazamentos de memória durante o desenvolvimento e a corrigir prontamente os serviços.


Por fim, o painel de serviço ficou assim:


As equipes de desenvolvimento e teste acharam muito conveniente trabalhar com este painel.


Resultados de otimização:


  1. A equipe do SDG construiu um processo eficiente de CI/CD que melhorou significativamente a velocidade e a conveniência do desenvolvimento. A equipe trabalhou bastante tempo dentro dessa estrutura de processo.
  2. A SDG também introduziu testes automatizados na conveniente ferramenta TeamCity e implantações automatizadas de serviços.
  3. Por meio do painel fácil de usar do Octopus, a equipe configurou direitos de acesso e pôde gerenciar implantações e ambientes.

Posteriormente, o SDG implementou muitos outros recursos no Octopus. Por exemplo, desligamento automático de clusters à noite de acordo com uma programação.


No entanto, a busca pela perfeição não tem limites. A equipe do Social Discovery Group avançou em seu desenvolvimento dominando o Azure DevOps. Eles configuraram um processo no Azure DevOps dentro de um ecossistema no Helm, ainda mais abrangente e eficiente. Isso será abordado no próximo artigo.


Adoraríamos ouvir sobre sua experiência na configuração e otimização de CI/CD usando Octopus e TeamCity. Compartilhe seus insights e dicas!