Unha guía de CI/CD en GitLab para o (case) principiante absoluto
Ou como conseguir fermosos distintivos para o teu proxecto nunha noite de codificación relaxada
Probablemente, todos os desenvolvedores que teñan polo menos un proxecto de mascota nalgún momento teñan picazón por fermosos distintivos con estados, cobertura de código, versións de paquetes en nuget... E esta comezón levoume a escribir este artigo. No proceso de prepararme para escribilo, adquirei esta beleza nun dos meus proxectos:
Este artigo abordará a configuración básica da integración e entrega continuas dun proxecto de biblioteca de clases .Net Core en GitLab, publicando documentación en GitLab Pages e enviando paquetes recollidos a unha fonte privada en Azure DevOps.
Utilizouse VS Code coa extensión como ambiente de desenvolvemento Fluxo de traballo de GitLab (para validar o ficheiro de configuración directamente desde o contorno de desenvolvemento).
Breve introdución
CD é cando só empurraches, pero o cliente xa perdeu todo?
Que é CI/CD e por que é necesario? Podes buscalo facilmente en Google. Atopa documentación completa sobre a configuración de canalizacións en GitLab tamén fácil. Aquí describirei brevemente e, se é posible, sen fallos, o proceso do sistema desde a vista de paxaro:
o desenvolvedor envía unha confirmación ao repositorio, crea unha solicitude de combinación a través do sitio web, ou dalgún outro xeito explícita ou implícitamente lanza o pipeline,
desde a configuración, selecciónanse todas as tarefas cuxas condicións permiten que se lancen nun contexto determinado,
as tarefas organízanse segundo as súas etapas,
as etapas execútanse á súa vez - é dicir. paralelo todas as tarefas desta etapa están completadas,
se unha etapa falla (é dicir, polo menos unha das tarefas da etapa falla) - o gasoduto detense (case sempre),
se todas as etapas se completan con éxito, o gasoduto considérase exitoso.
Así temos:
pipeline é un conxunto de tarefas organizadas en etapas nas que podes montar, probar, empaquetar código, implementar a montaxe rematada nun servizo na nube, etc.
etapa (etapa) — unidade de organización da canalización, contén 1+ tarefa,
tarefa (traballo) é unha unidade de traballo nunha canalización. Consiste nun script (obrigatorio), condicións de lanzamento, configuración para publicar/cachear artefactos e moito máis.
En consecuencia, a tarefa á hora de configurar CI/CD redúcese a crear un conxunto de tarefas que implementen todas as accións necesarias para montar, probar e publicar código e artefactos.
Antes de comezar: por que?
Por que GitLab?
Porque cando xurdiu a necesidade de crear repositorios privados para proxectos de mascotas, pagábanse en GitHub, e eu era cobizoso. Os repositorios fixéronse gratuítos, pero de momento esta non é unha razón suficiente para que me pase a GitHub.
Por que non Azure DevOps Pipelines?
Porque a configuración é sinxela: nin sequera necesitas coñecementos sobre a liña de comandos. A integración con provedores externos de git (nun par de clics, importación de claves SSH para enviar commits ao repositorio) tamén se configura facilmente a canalización incluso sen un modelo.
Posición inicial: o que tes e o que queres
Temos:
repositorio en GitLab.
Queremos:
montaxe e probas automáticas para cada solicitude de combinación,
construír paquetes para cada solicitude de combinación e enviar ao mestre, sempre que exista unha liña determinada na mensaxe de confirmación,
enviar paquetes recollidos a un feed privado en Azure DevOps,
montaxe de documentación e publicación en GitLab Pages,
distintivos!11
Os requisitos descritos encaixan naturalmente no seguinte modelo de canalización:
Fase 1 - montaxe
Recollemos o código, publicamos os ficheiros de saída como artefactos
Fase 2 - proba
Recibimos artefactos da fase de construción, realizamos probas, recompilamos datos de cobertura de código
Fase 3 - envío
Tarefa 1: recolle o paquete nuget e envíao a Azure DevOps
Tarefa 2: montamos o sitio desde xmldoc no código fonte e publicamos en GitLab Pages
Cando premes no botón Crear, crearase o proxecto e dirixirase á súa páxina. Nesta páxina podes desactivar funcións innecesarias accedendo á configuración do proxecto (ligazón inferior na lista da esquerda -> Visión xeral -> Bloque Azure DevOps Services)
Vaia a Atrifacts, fai clic en Crear feed
Introduza o nome da fonte
Seleccione visibilidade
Desmarque a caixa Incluír paquetes de fontes públicas comúnspara que a fonte non se converta nun vertedoiro dun clon nuget
Fai clic en Conectar para alimentar, selecciona Visual Studio, copia a fonte desde o bloque Configuración da máquina
Vaia á configuración da conta, seleccione Token de acceso persoal
Crea un novo token de acceso
Nome - arbitrario
Organización - actual
Prazo de vixencia: máximo 1 ano
Ámbito: embalaxe/lectura e escritura
Copia o token creado - despois de pechar a xanela modal o valor non estará dispoñible
Vaia á configuración do repositorio en GitLab, seleccione Configuración de CI/CD
Expande o bloque Variables e engade un novo
Nome: calquera sen espazos (estará dispoñible no shell de comandos)
O valor é o token de acceso do paso 9
Seleccione a variable Máscara
Isto completa a configuración preliminar.
Preparación do marco de configuración
Por defecto, o ficheiro usado para configurar CI/CD en GitLab é .gitlab-ci.yml desde a raíz do repositorio. Podes configurar un camiño personalizado a este ficheiro na configuración do repositorio, pero neste caso non é necesario.
Como se pode ver na extensión, o ficheiro contén configuración no formato YAML. A documentación describe en detalle que chaves se poden conter no nivel superior da configuración e en cada un dos niveis aniñados.
En primeiro lugar, imos engadir ao ficheiro de configuración unha ligazón á imaxe do docker na que se executarán as tarefas. Para iso atopamos Páxina de imaxes .Net Core en Docker Hub. En GitHub Hai unha guía detallada sobre que imaxe escoller para diferentes tarefas. Unha imaxe con .Net Core 3.1 é adecuada para que poidamos construír, así que non dubide en engadila como primeira liña á configuración
image: mcr.microsoft.com/dotnet/core/sdk:3.1
Agora, cando inicie a canalización, a imaxe especificada descargarase do repositorio de imaxes de Microsoft, no que se executarán todas as tarefas da configuración.
O seguinte paso é engadir etapa's. Por defecto, GitLab define 5 etapas:
.pre - realizado en todas as fases,
.post - realizado despois de todas as etapas,
build - primeiro despois .pre etapa,
test - segunda fase,
deploy - terceira etapa.
Non obstante, nada lle impide declaralos explícitamente. A orde na que se enumeran os pasos afecta á orde en que se completan. Para completar a configuración, agreguemos:
stages:
- build
- test
- deploy
Para a depuración, ten sentido obter información sobre o ambiente no que se executan as tarefas. Engademos un conxunto global de comandos que se executarán antes de usar cada tarefa before_script:
before_script:
- $PSVersionTable.PSVersion
- dotnet --version
- nuget help | select-string Version
Queda por engadir polo menos unha tarefa para que cando se envíen as confirmacións, o pipeline se inicie. De momento, engadimos unha tarefa baleira para a demostración:
dummy job:
script:
- echo ok
Comezamos a validación, recibimos unha mensaxe de que todo está ben, commit, push, miramos os resultados no sitio... E recibimos un erro de script - bash: .PSVersion: command not found. WTF?
Todo é lóxico: por defecto, os corredores (responsables de executar scripts de tarefas e proporcionados por GitLab) usan bash para executar comandos. Podes corrixir isto indicando explícitamente na descrición da tarefa que etiquetas debe ter o executador de pipeline:
dummy job on windows:
script:
- echo ok
tags:
- windows
Genial! Agora o gasoduto está funcionando.
Un lector atento, repetindo estes pasos, notará que a tarefa está completada na etapa test, aínda que non especificamos o escenario. Como podes adiviñar, test é o paso predeterminado.
Continuemos creando o esqueleto de configuración engadindo todas as tarefas descritas anteriormente:
build job:
script:
- echo "building..."
tags:
- windows
stage: build
test and cover job:
script:
- echo "running tests and coverage analysis..."
tags:
- windows
stage: test
pack and deploy job:
script:
- echo "packing and pushing to nuget..."
tags:
- windows
stage: deploy
pages:
script:
- echo "creating docs..."
tags:
- windows
stage: deploy
Temos unha canalización non especialmente funcional, pero non obstante correcta.
Configurando disparadores
Debido ao feito de que non se especifican filtros de activación para ningunha das tarefas, a canalización farase totalmente execútase cada vez que se envían commits ao repositorio. Dado que este non é o comportamento desexado en xeral, configuraremos filtros de activación para as tarefas.
Os filtros pódense configurar en dous formatos: só/excepto и regras. En resumo, only/except permite configurar filtros por disparadores (merge_request, por exemplo: configura a tarefa para que se execute cada vez que se crea unha solicitude de combinación e cada vez que se envían confirmacións á rama que é a orixe da solicitude de combinación) e os nomes das ramas (incluído o uso de expresións regulares); rules permite personalizar un conxunto de condicións e, opcionalmente, cambiar a condición de execución da tarefa dependendo do éxito das tarefas anteriores (when en GitLab CI/CD).
Lembremos o conxunto de requisitos: montaxe e probas só para solicitudes de fusión, empaquetado e envío a Azure DevOps, para solicitudes de fusión e envíos ao mestre, xeración de documentación, aos envíos ao mestre.
En primeiro lugar, imos configurar unha tarefa de ensamblaxe de código engadindo unha regra de activación que só desencadea unha solicitude de combinación:
build job:
# snip
only:
- merge_request
Agora imos configurar a tarefa de empaquetado para activar unha solicitude de combinación e engadir commits ao mestre:
En condicións pódese usar variables listadas aquí; regras rules non compatible coas normas only/except.
Configurando o aforro de artefactos
Mentres se executa a tarefa build job teremos artefactos de construción creados que poidan ser reutilizados en tarefas posteriores. Para iso, cómpre engadir camiños á configuración da tarefa, ficheiros ao longo dos cales terán que ser gardados e reutilizados en tarefas posteriores, á clave artifacts:
As rutas admiten comodíns, o que sen dúbida fai que sexan máis fáciles de establecer.
Se unha tarefa crea artefactos, cada tarefa posterior poderá acceder a eles; situaranse polos mesmos camiños en relación á raíz do repositorio ao longo da cal se recolleron da tarefa orixinal. Os artefactos tamén están dispoñibles para descargar no sitio web.
Agora que temos un marco de configuración listo (e verificado), podemos pasar a escribir scripts para tarefas.
Escribimos guións
Quizais, noutrora, nunha galaxia moi, moi lonxe, construír proxectos (incluídos os .net) desde a liña de comandos era unha dor. Agora podes montar, probar e publicar o proxecto en 3 equipos:
dotnet build
dotnet test
dotnet pack
Por suposto, hai algúns matices polos que complicaremos un pouco os comandos.
Queremos unha versión, non unha depuración, compilación, polo que engadimos a cada comando -c Release
Durante a proba, queremos recoller datos sobre a cobertura do código, polo que necesitamos conectar un analizador de cobertura ás bibliotecas de proba:
O paquete debería engadirse a todas as bibliotecas de proba coverlet.msbuild: dotnet add package coverlet.msbuild dende o cartafol do proxecto
Engadir ao comando de inicio de proba /p:CollectCoverage=true
Engademos unha clave á configuración da tarefa de proba para obter resultados de cobertura (ver a continuación)
Ao empaquetar o código en paquetes nuget, estableceremos o directorio de saída para os paquetes: -o .
Recopilación de datos de cobertura de código
Despois de executar as probas, Coverlet mostra as estatísticas de inicio na consola:
GitLab permítelle especificar unha expresión regular para obter estatísticas, que logo se poden obter en forma de distintivo. A expresión regular especifícase na configuración da tarefa coa tecla coverage; a expresión debe conter un grupo de captura, cuxo valor se transferirá ao distintivo:
test and cover job:
# snip
coverage: /|s*Totals*|s*(d+[,.]d+%)/
Aquí obtemos estatísticas dunha liña cunha cobertura total por liñas.
Publicamos paquetes e documentación
Temos ambas accións asignadas á última etapa do gasoduto: xa que pasaron a montaxe e as probas, podemos compartir os nosos desenvolvementos co mundo.
En primeiro lugar, vexamos a publicación na fonte do paquete:
Se o proxecto non ten un ficheiro de configuración nuget (nuget.config), crea un novo: dotnet new nugetconfig
Para qué: É posible que a imaxe non teña acceso de escritura ás configuracións globais (usuario e máquina). Para non detectar erros, simplemente imos crear unha nova configuración local e traballar con ela.
Engademos unha nova fonte de paquete á configuración local: nuget sources add -name <name> -source <url> -username <organization> -password <gitlab variable> -configfile nuget.config -StorePasswordInClearText
name - nome da fonte local, non importante
url — URL da fonte da fase "Preparación de contas", paso 6
organization - nome da organización en Azure DevOps
gitlab variable — o nome da variable co token de acceso engadido a GitLab (“Preparación de contas”, p. 11). Por suposto, no formato $variableName
En caso de erros pode ser útil engadir -verbosity detailed
Enviamos o paquete á fonte: nuget push -source <name> -skipduplicate -apikey <key> *.nupkg
Enviamos todos os paquetes desde o directorio actual, polo tanto *.nupkg.
name - do paso anterior.
key - Calquera liña. En Azure DevOps, na xanela Conectar ao feed, a liña de exemplo está sempre az.
-skipduplicate — se tenta enviar un paquete xa existente sen esta chave, a fonte devolverá un erro 409 Conflict; coa chave, o envío será omitido.
Agora imos configurar a creación de documentación:
Para comezar, no repositorio, na rama mestra, inicializamos o proxecto docfx. Para iso, cómpre executar o comando desde a raíz docfx init e establecer de forma interactiva os parámetros clave para a montaxe da documentación. Descrición detallada da configuración mínima do proxecto aquí.
Ao configurar, é importante especificar o directorio de saída ..public - GitLab toma de forma predeterminada o contido do cartafol público na raíz do repositorio como fonte de Páxinas. Porque o proxecto estará situado nun cartafol anidado no repositorio - engade unha saída ao seguinte nivel ao camiño.
Impulsemos os cambios en GitLab.
Engade unha tarefa á configuración da canalización pages (palabra reservada para tarefas de publicación de sitios en páxinas de GitLab):
Guión:
nuget install docfx.console -version 2.51.0 - instalar docfx; A versión ofrécese para garantir que as rutas de instalación do paquete son correctas.
.docfx.console.2.51.0toolsdocfx.exe .docfx_projectdocfx.json - Recollida de documentación
Nodo de artefactos:
pages:
# snip
artifacts:
paths:
- public
Digresión lírica sobre docfx
Anteriormente, ao configurar un proxecto, especificaba a fonte do código para a documentación como ficheiro de solución. A principal desvantaxe é que tamén se crea documentación para proxectos de proba. Se isto non é necesario, pode definir este valor para o nodo metadata.src:
metadata.src.src: "../" - subir un nivel en relación á localización docfx.json, porque A busca na árbore de directorios non funciona en patróns.
metadata.src.files: ["**/*.csproj"] — un patrón global, recollemos todos os proxectos C# de todos os directorios.
metadata.src.exclude: ["*.tests*/**"] - patrón global, excluír todo dos cartafoles con .tests No título
Subtotal
Esta configuración sinxela pódese crear en literalmente media hora e un par de cuncas de café, o que lle permitirá comprobar con cada solicitude de fusión e enviarlle ao mestre que o código se está a construír e as probas están a pasar, montando un novo paquete, actualizando a documentación e agradando a vista con fermosos distintivos no proxecto README.
.gitlab-ci.yml final
image: mcr.microsoft.com/dotnet/core/sdk:3.1
before_script:
- $PSVersionTable.PSVersion
- dotnet --version
- nuget help | select-string Version
stages:
- build
- test
- deploy
build job:
stage: build
script:
- dotnet build -c Release
tags:
- windows
only:
- merge_requests
- master
artifacts:
paths:
- your/path/to/binaries
test and cover job:
stage: test
tags:
- windows
script:
- dotnet test -c Release /p:CollectCoverage=true
coverage: /|s*Totals*|s*(d+[,.]d+%)/
only:
- merge_requests
- master
pack and deploy job:
stage: deploy
tags:
- windows
script:
- dotnet pack -c Release -o .
- dotnet new nugetconfig
- nuget sources add -name feedName -source https://pkgs.dev.azure.com/your-organization/_packaging/your-feed/nuget/v3/index.json -username your-organization -password $nugetFeedToken -configfile nuget.config -StorePasswordInClearText
- nuget push -source feedName -skipduplicate -apikey az *.nupkg
only:
- master
pages:
tags:
- windows
stage: deploy
script:
- nuget install docfx.console -version 2.51.0
- $env:path = "$env:path;$($(get-location).Path)"
- .docfx.console.2.51.0toolsdocfx.exe .docfxdocfx.json
artifacts:
paths:
- public
only:
- master
Falando de distintivos
Foi polo seu ben que todo comezou!
As insignias con estados de canalización e cobertura de código están dispoñibles en GitLab na configuración de CI/CD no bloque de canalizacións Gtntral:
Creei un distintivo cunha ligazón á documentación da plataforma escudos.io — todo é moi sinxelo alí, podes crear o teu propio distintivo e recibilo mediante unha solicitude.
![Пример с Shields.io](https://img.shields.io/badge/custom-badge-blue)
Azure DevOps Artifacts tamén che permite crear distintivos para paquetes que indican a versión máis recente. Para iso, na fonte do sitio web de Azure DevOps, cómpre facer clic en Crear distintivo para o paquete seleccionado e copiar a marca de rebaixa:
Engadindo beleza
Destacamos fragmentos de configuración comúns
Mentres escribía a configuración e buscaba a documentación, atopeime cunha función YAML interesante: reutilizar fragmentos.
Como podes ver na configuración de tarefas, todos requiren unha etiqueta windows desde o corredor, e se activan cando se envía unha solicitude de combinación ao mestre/creado (excepto para a documentación). Engadimos isto ao fragmento que imos reutilizar:
E agora podemos inserir o fragmento anunciado anteriormente na descrición da tarefa:
build job:
<<: *common_tags
<<: *common_only
Os nomes dos fragmentos deben comezar cun punto para evitar ser interpretados como unha tarefa.
Versionado de paquetes
Ao crear un paquete, o compilador verifica os interruptores da liña de comandos e, na súa ausencia, os ficheiros do proxecto; Despois de atopar o nodo Versión, toma o seu valor como a versión do paquete que se está a construír. Resulta que para construír un paquete cunha nova versión, cómpre actualizalo no ficheiro do proxecto ou pasalo como argumento de liña de comandos.
Engademos un desexo máis: deixe que os dous números máis baixos da versión sexan o ano e a data de compilación do paquete, e engade versións previas. Por suposto, podes engadir estes datos ao ficheiro do proxecto e comprobalos antes de cada envío, pero tamén podes facelo no proceso, recollendo a versión do paquete do contexto e pasándoa a través dun argumento da liña de comandos.
Aceptemos que se a mensaxe de confirmación contén unha liña como release (v./ver./version) <version number> (rev./revision <revision>)?, entón colleremos a versión do paquete desta liña, completaremos coa data actual e pasaremos como argumento ao comando dotnet pack. A falta dunha liña, simplemente non montaremos o paquete.
O seguinte script resolve este problema:
# регулярное выражение для поиска строки с версией
$rx = "releases+(v.?|ver.?|version)s*(?<maj>d+)(?<min>.d+)?(?<rel>.d+)?s*((rev.?|revision)?s+(?<rev>[a-zA-Z0-9-_]+))?"
# ищем строку в сообщении коммита, передаваемом в одной из предопределяемых GitLab'ом переменных
$found = $env:CI_COMMIT_MESSAGE -match $rx
# совпадений нет - выходим
if (!$found) { Write-Output "no release info found, aborting"; exit }
# извлекаем мажорную и минорную версии
$maj = $matches['maj']
$min = $matches['min']
# если строка содержит номер релиза - используем его, иначе - текущий год
if ($matches.ContainsKey('rel')) { $rel = $matches['rel'] } else { $rel = ".$(get-date -format "yyyy")" }
# в качестве номера сборки - текущие месяц и день
$bld = $(get-date -format "MMdd")
# если есть данные по пререлизной версии - включаем их в версию
if ($matches.ContainsKey('rev')) { $rev = "-$($matches['rev'])" } else { $rev = '' }
# собираем единую строку версии
$version = "$maj$min$rel.$bld$rev"
# собираем пакеты
dotnet pack -c Release -o . /p:Version=$version
Engadir un script a unha tarefa pack and deploy job e observe estrictamente a montaxe de paquetes se a liña especificada está presente na mensaxe de confirmación.
En total
Despois de pasar entre media hora e unha hora escribindo a configuración, depurando no Powershell local e, posiblemente, un par de lanzamentos sen éxito, recibimos unha configuración sinxela para automatizar tarefas rutineiras.
Por suposto, GitLab CI/CD é moito máis extenso e multifacético do que podería parecer despois de ler esta guía. iso non é certo en absoluto. Incluso hai Auto DevOps sipermitindo
detectar, construír, probar, implantar e supervisar as súas aplicacións automaticamente
Agora os plans son configurar unha canalización para a implantación de aplicacións en Azure, usando Pulumi e detectando automaticamente o ambiente de destino, que se tratará no seguinte artigo.