Como descrever um trabalho de 100 gitlab em 100 linhas no Jsonnet

Na continuação do artigo anterior sobre ferramentas de implantação no Kubernetes, quero falar sobre como você pode usar o Jsonnet para simplificar a descrição do trabalho em seu arquivo .gitlab-ci.yml



Dado


Existe um monorepa no qual:


  • 10 dockerfiles
  • 30 implantações descritas
  • 3 ambientes: desenvolvimento , preparação e produção

Desafio


Configure um pipeline:


  • As imagens do Docker de construção devem ser feitas adicionando uma tag git com uma versão.
  • Cada operação de implantação deve ser executada ao enviar para a ramificação do ambiente e apenas alterando arquivos em um diretório específico
  • Cada ambiente possui seu próprio gitlab-runner com uma tag separada que somente executa a implantação em seu ambiente.
  • Nem todos os aplicativos devem ser implantados em cada um dos ambientes; precisamos descrever o pipeline para poder fazer exceções.
  • Algumas implementações usam o submódulo git e devem ser iniciadas com a variável definida GIT_SUBMODULE_STRATEGY=normal

Descrever tudo isso pode parecer um inferno, mas não nos desesperamos e, armados com o Jsonnet, faremos isso com facilidade e naturalidade.


Solução


O gitlab-ci.yml possui recursos internos para reduzir a descrição de tarefas repetidas, por exemplo, você pode usar extends ou include , mas não fornece modelos completos, o que não permite descrever o trabalho mais conciso e eficiente.


Para resolver esse problema, sugiro usar o jsonnet, que permite livrar-se quase completamente da repetição de código ao descrever qualquer estrutura de dados.


Ao trabalhar com o jsonnet, eu recomendo instalar um plugin para o seu editor

Por exemplo, para o vim, existe um plug-in vim-jsonnet que ativa o destaque de sintaxe e executa automaticamente o jsonnet fmt cada vez que é salvo (requer o jsonnet instalado).

Vamos dar uma olhada na estrutura do nosso repositório:


 . ├── deploy │  ├── analyse │  ├── basin │  ├── brush │  ├── copper │  ├── dinner │  ├── dirty │  ├── drab │  ├── drunk │  ├── education │  ├── fanatical │  ├── faulty │  ├── guarantee │  ├── guitar │  ├── hall │  ├── harmonious │  ├── history │  ├── iron │  ├── maniacal │  ├── mist │  ├── nine │  ├── pleasant │  ├── polish │  ├── receipt │  ├── shop │  ├── smelly │  ├── solid │  ├── stroke │  ├── thunder │  ├── ultra │  └── yarn └── dockerfiles ├── dinner ├── drunk ├── fanatical ├── guarantee ├── guitar ├── harmonious ├── shop ├── smelly ├── thunder └── yarn 

As imagens do Docker serão construídas usando o kaniko


A implantação de aplicativos no cluster será feita usando o qbec . Cada aplicativo é descrito para três ambientes diferentes. Para aplicar alterações no cluster, basta executar:


 qbec apply <environment> --root deploy/<app> --yes 

onde:


  • <app> - o nome do nosso aplicativo
  • <environment> é um dos nossos ambientes: desenvolvimento , estágio ou prod .

No final, nossos trabalhos devem ficar assim:


Montagem:


 build:{{ image }}: stage: build tags: - build image: name: gcr.io/kaniko-project/executor:debug entrypoint: [""] script: - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dockerfiles/{{ image }}/Dockerfile --destination $CI_REGISTRY_IMAGE/{{ image }}:$CI_COMMIT_TAG only: refs: - tags 

Onde, em vez de {{ image }} , o nome do diretório dos dockerfiles será substituído


Implantar:


 deploy:{{ environment }}:{{ app }}: stage: deploy tags: - {{ environment }} script: - qbec apply {{ environment }} --root deploy/{{ app }} --force:k8s-context __incluster__ --wait --yes only: changes: - deploy/{{ app }}/**/* refs: - {{ environment }} 

Onde, em vez de {{ app }} , o nome do diretório de deploy será substituído,
e em vez de {{ environment }} - o nome do ambiente no qual você deseja implantar.


Vamos descrever os protótipos de nossos trabalhos como objetos em uma lib / jobs.jsonnet separada


 { //    docker- dockerImage(name):: { tags: ['build'], stage: 'build', image: { name: 'gcr.io/kaniko-project/executor:debug-v0.15.0', entrypoint: [''], }, script: [ 'echo "{\\"auths\\":{\\"$CI_REGISTRY\\":{\\"username\\":\\"$CI_REGISTRY_USER\\",\\"password\\":\\"$CI_REGISTRY_PASSWORD\\"}}}" > /kaniko/.docker/config.json', '/kaniko/executor --cache --context $CI_PROJECT_DIR/dockerfiles/' + name + ' --dockerfile $CI_PROJECT_DIR/dockerfiles/' + name + '/Dockerfile --destination $CI_REGISTRY_IMAGE/' + name + ':$CI_COMMIT_TAG --build-arg VERSION=$CI_COMMIT_TAG', ], }, //    qbec- qbecApp(name): { stage: 'deploy', script: [ 'qbec apply $CI_COMMIT_REF_NAME --root deploy/' + name + ' --force:k8s-context __incluster__ --wait --yes', ], only: { changes: [ 'deploy/' + name + '/**/*', ], }, }, } 

Observe que eu deliberadamente não especifiquei refs e tags para tornar nossa biblioteca mais flexível e demonstrar totalmente os recursos do jsonnet para você; nós os adicionaremos posteriormente a partir do arquivo principal.


Agora vamos descrever o nosso .gitlab-ci.jsonnet :


 //    local jobs = import 'lib/jobs.libsonnet'; //    local ref(x) = { only+: { refs: [x] } }; local tag(x) = { tags: [x] }; local submodule(x) = { variables+: { GIT_SUBMODULE_STRATEGY: x } }; { // C docker- ['build:' + x]: jobs.dockerImage(x) + tag('build') + ref('tags') for x in [ 'dinner', 'drunk', 'fanatical', 'guarantee', 'guitar', 'harmonious', 'shop', 'smelly', 'thunder', 'yarn', ] } + { //         'prod' ['deploy:prod:' + x]: jobs.qbecApp(x) + tag('prod') + ref('prod') for x in [ 'dinner', 'hall', ] } + { //   git-submodule ['deploy:' + env + ':' + app]: jobs.qbecApp(app) + tag(env) + ref(env) + submodule('normal') for env in ['devel', 'stage', 'prod'] for app in [ 'brush', 'fanatical', 'history', 'shop', ] } + { //    ['deploy:' + env + ':' + app]: jobs.qbecApp(app) + tag(env) + ref(env) for env in ['devel', 'stage', 'prod'] for app in [ 'analyse', 'basin', 'copper', 'dirty', 'drab', 'drunk', 'education', 'faulty', 'guarantee', 'guitar', 'harmonious', 'iron', 'maniacal', 'mist', 'nine', 'pleasant', 'polish', 'receipt', 'smelly', 'solid', 'stroke', 'thunder', 'ultra', 'yarn', ] } 

Preste atenção às funções ref , tag e submodule no início do arquivo, pois elas permitem criar um objeto de substituição.


Uma pequena explicação: usar " +: " em vez de " : " para substituir objetos permite adicionar um valor a um objeto ou lista existente.


Por exemplo " : " para refs :


 local job = { script: ['echo 123'], only: { refs: ['tags'] }, }; local ref(x) = { only+: { refs: [x] } }; job + ref('prod') 

retornará:


 { "only": { "refs": [ "prod" ] }, "script": [ "echo 123" ] } 

E aqui está o " +: " para refs :


 local job = { script: ['echo 123'], only: { refs: ['tags'] }, }; local ref(x) = { only+: { refs+: [x] } }; job + ref('prod') 

retornará:


 { "only": { "refs": [ "prod", "tags" ] }, "script": [ "echo 123" ] } 

Como você pode ver, o uso do Jsonnet permite que você descreva e mescle com eficiência seus objetos, a saída é sempre um JSON pronto, que podemos gravar imediatamente em nosso arquivo .gitlab-ci.yml :


 jsonnet .gitlab-ci.jsonnet > .gitlab-ci.yml 

Verifique o número de linhas:


 # wc -l .gitlab-ci.jsonnet lib/jobs.libsonnet .gitlab-ci.yml 77 .gitlab-ci.jsonnet 24 lib/jobs.libsonnet 1710 .gitlab-ci.yml 

Na minha opinião é muito bom!


Você pode ver mais exemplos e sentir o Jsonnet diretamente no site oficial: jsonnet.org
Se você, como eu, gosta da Jsonnet, junte-se ao nosso grupo no telegrama t.me/jsonnet_ru

Source: https://habr.com/ru/post/pt483626/


All Articles