CI / CD usando Jenkins no Kubernetes

Boa tarde


Já existem vários artigos sobre Habré sobre jenkins, ci / cd e kubernetes, mas nisto não quero me concentrar em analisar os recursos dessas tecnologias, mas em sua configuração mais simples para a construção do pipeline ci / cd.


Quero dizer que o leitor tem um entendimento básico do docker e não vou me debruçar sobre os tópicos de instalação e configuração do kubernetes. Todos os exemplos serão mostrados no minikube, mas também podem ser aplicados no EKS, GKE ou similares sem alterações significativas.



Meio ambiente


Sugiro usar os seguintes ambientes:


  • test - para implantação manual e teste de ramificação
  • teste - um ambiente em que todas as alterações que se enquadram no mestre são implantadas automaticamente
  • produção - o ambiente usado por usuários reais, onde as mudanças só serão efetuadas após a confirmação de sua operacionalidade no preparo

Os ambientes serão organizados usando namespaces do kubernetes em um único cluster. Essa abordagem é o mais simples e rápida possível no início, mas também tem suas desvantagens: os espaços para nome não são completamente isolados um do outro nos kubernetes.


Neste exemplo, cada espaço para nome terá o mesmo conjunto de ConfigMaps com as configurações deste ambiente:


apiVersion: v1 kind: Namespace metadata: name: production --- apiVersion: v1 kind: ConfigMap metadata: name: environment.properties namespace: production data: environment.properties: | env=production 

Elmo


Helm é um aplicativo que ajuda a gerenciar recursos instalados nos kubernetes.
As instruções de instalação podem ser encontradas aqui .
Para começar, você deve inicializar o pod do leme para usar o leme com o cluster:


 helm init 

Jenkins


Usarei o Jenkins, pois é uma plataforma bastante simples, flexível e popular para projetos de construção. Ele será instalado em um espaço para nome separado para se isolar de outros ambientes. Como pretendo usar o leme no futuro, posso simplificar a instalação do Jenkins usando gráficos de código aberto existentes:


 helm install --name jenkins --namespace jenkins -f jenkins/demo-values.yaml stable/jenkins 

demo-values.yaml contém a versão Jenkins, um conjunto de plug-ins pré-instalados, um nome de domínio e outras configurações


demo-values.yaml
 Master: Name: jenkins-master Image: "jenkins/jenkins" ImageTag: "2.163-slim" OverwriteConfig: true AdminUser: admin AdminPassword: admin InstallPlugins: - kubernetes:1.14.3 - workflow-aggregator:2.6 - workflow-job:2.31 - credentials-binding:1.17 - git:3.9.3 - greenballs:1.15 - google-login:1.4 - role-strategy:2.9.0 - locale:1.4 ServicePort: 8080 ServiceType: NodePort HostName: jenkins.192.168.99.100.nip.io Ingress: Path: / Agent: Enabled: true Image: "jenkins/jnlp-slave" ImageTag: "3.27-1" #autoadjust agent resources limits resources: requests: cpu: null memory: null limits: cpu: null memory: null #to allow jenkins create slave pods rbac: install: true 

Essa configuração usa admin / admin como nome de usuário e senha para login, e pode ser reconfigurada posteriormente. Uma das opções possíveis é o SSO do google (o plugin google-login é necessário para isso, suas configurações estão localizadas em Jenkins> Gerenciar Jenkins> Configurar segurança global> Controle de acesso> Domínio de segurança> Efetuar login no Google).


O Jenkins será configurado imediatamente para criar automaticamente um escravo único para cada build. Graças a isso, a equipe não esperará mais um agente gratuito para montagem, e os negócios poderão economizar no número de servidores necessários.



Também pronto para uso, o PersistenceVolume está configurado para salvar pipelines ao reiniciar ou atualizar.


Para que os scripts de implantação automática funcionem corretamente, você precisa dar permissão de administrador de cluster para Jenkins obter uma lista de recursos no kubernetes e manipulá-los.


 kubectl create clusterrolebinding jenkins --clusterrole cluster-admin --serviceaccount=jenkins:default 

No futuro, você pode atualizar o Jenkins usando o leme no caso de novas versões de plug-ins ou alterações na configuração.


 helm upgrade jenkins stable/jenkins -f jenkins/demo-values.yaml 

Isso pode ser feito através da interface do próprio Jenkins, mas com o helm você terá a oportunidade de reverter para as revisões anteriores usando:


 helm history jenkins helm rollback jenkins ${revision} 

Compilação de aplicativos


Como exemplo, vou construir e implantar o aplicativo de inicialização mais simples da primavera. Da mesma forma com Jenkins, usarei o leme.


A montagem ocorrerá na seguinte sequência:


  • checkout
  • compilação
  • teste de unidade
  • teste de integração
  • montagem de artefato
  • Implementação de Artefato no Registro do Docker
  • implementando artefato na preparação (apenas para ramificação principal)

Para isso, eu uso o arquivo Jenkins . Na minha opinião, essa é uma maneira muito flexível (mas, infelizmente, não é a mais fácil) de configurar a montagem do projeto. Uma de suas vantagens é a capacidade de manter a configuração do conjunto do projeto no repositório com o próprio projeto.


checkout



No caso da organização bitbucket ou github, você pode configurar o Jenkins para verificar periodicamente a conta inteira quanto à presença de repositórios com o Jenkinsfile e criar assemblies automaticamente para eles. Jenkins coletará o mestre e os ramos. Os pedidos de recebimento serão exibidos em uma guia separada. Existe uma opção mais simples - adicione um repositório git separado, independentemente de onde ele esteja hospedado. Neste exemplo, farei exatamente isso. Tudo o que é necessário está no menu Jenkins> Novo item> Multibranch Pipeline, selecione o nome do assembly e ligue o repositório git.


Compilação


Como o Jenkins cria um novo pod para cada montagem, no caso de usar coletores maven ou similares, as dependências serão baixadas novamente a cada vez. Para evitar isso, você pode alocar PersistenceVolume para caches .m2 ou similares e montá-lo no pod que cria o projeto.


 apiVersion: "v1" kind: "PersistentVolumeClaim" metadata: name: "repository" namespace: "jenkins" spec: accessModes: - ReadWriteMany resources: requests: storage: 10Gi 

No meu caso, isso permitiu acelerar o pipeline de cerca de 4 para o 1º minuto.


Versionamento


Para que o CI / CD funcione corretamente, cada compilação precisa de uma versão exclusiva.


Uma opção muito boa seria usar o controle de versão semântico . Isso permitirá que você rastreie alterações compatíveis e incompatíveis com versões anteriores, mas esse controle de versão é mais difícil de automatizar.


Neste exemplo, gerarei uma versão do id e a data da confirmação, bem como o nome da ramificação, se não for mestre. Por exemplo 56e0fbdc-201802231623 ou b3d3c143-201802231548-PR-18 .


Vantagens dessa abordagem:


  • facilidade de automação
  • é fácil obter o código fonte e o tempo de criação da versão
  • visualmente, é possível distinguir a versão de lançamento do candidato (do assistente) ou experimental (do ramo)
    mas:
  • esta versão é mais difícil de usar na comunicação oral
  • não está claro se houve alterações incompatíveis.

Como a imagem do docker pode ter várias tags ao mesmo tempo, as abordagens podem ser combinadas: todos os lançamentos usam as versões geradas e as que caem na produção são adicionalmente marcadas (manualmente) com o controle de versão semântico. Isso, por sua vez, está associado a uma complexidade de implementação ainda maior e à ambiguidade de qual versão o aplicativo deve mostrar.


Artefatos


O resultado da montagem será:


  • imagem do Docker com um aplicativo que será armazenado e carregado no registro do Docker. O exemplo usará o registro interno do minikube, que pode ser substituído por um hub docker ou um registro privado da amazon (ecr) ou google (não esqueça de fornecer credenciais a eles usando a construção withCredentials).
  • gráficos helm com uma descrição da implantação do aplicativo (implantação, serviço, etc.) no diretório helm. Idealmente, eles devem ser armazenados em um repositório separado de artefatos, mas, para simplificação, eles podem ser usados ​​verificando o commit necessário do git.

Jenkinsfile


Como resultado, o aplicativo será criado usando o seguinte arquivo Jenkins:


Jenkinsfile
 def branch def revision def registryIp pipeline { agent { kubernetes { label 'build-service-pod' defaultContainer 'jnlp' yaml """ apiVersion: v1 kind: Pod metadata: labels: job: build-service spec: containers: - name: maven image: maven:3.6.0-jdk-11-slim command: ["cat"] tty: true volumeMounts: - name: repository mountPath: /root/.m2/repository - name: docker image: docker:18.09.2 command: ["cat"] tty: true volumeMounts: - name: docker-sock mountPath: /var/run/docker.sock volumes: - name: repository persistentVolumeClaim: claimName: repository - name: docker-sock hostPath: path: /var/run/docker.sock """ } } options { skipDefaultCheckout true } stages { stage ('checkout') { steps { script { def repo = checkout scm revision = sh(script: 'git log -1 --format=\'%h.%ad\' --date=format:%Y%m%d-%H%M | cat', returnStdout: true).trim() branch = repo.GIT_BRANCH.take(20).replaceAll('/', '_') if (branch != 'master') { revision += "-${branch}" } sh "echo 'Building revision: ${revision}'" } } } stage ('compile') { steps { container('maven') { sh 'mvn clean compile test-compile' } } } stage ('unit test') { steps { container('maven') { sh 'mvn test' } } } stage ('integration test') { steps { container ('maven') { sh 'mvn verify' } } } stage ('build artifact') { steps { container('maven') { sh "mvn package -Dmaven.test.skip -Drevision=${revision}" } container('docker') { script { registryIp = sh(script: 'getent hosts registry.kube-system | awk \'{ print $1 ; exit }\'', returnStdout: true).trim() sh "docker build . -t ${registryIp}/demo/app:${revision} --build-arg REVISION=${revision}" } } } } stage ('publish artifact') { when { expression { branch == 'master' } } steps { container('docker') { sh "docker push ${registryIp}/demo/app:${revision}" } } } } } 

Pipelines adicionais da Jenkins para gerenciamento do ciclo de vida de aplicativos


Suponha que os repositórios estejam organizados para que:


  • contém um aplicativo separado na forma de imagem de janela de encaixe
  • pode ser implementado usando arquivos helm localizados no diretório helm
  • são versionados usando a mesma abordagem e possuem um arquivo helm / setVersion.sh para definir revisões nos gráficos de helm

Em seguida, podemos construir vários pipelines Jenkinsfile para gerenciar o ciclo de vida do aplicativo, a saber:



No arquivo Jenkins de cada projeto, você pode adicionar uma chamada de pipeline de implantação que será executada toda vez que a ramificação principal for compilada com êxito ou quando a ramificação de implantação solicitar explicitamente o ambiente de teste.


Chamada de pipeline de implantação de arquivo Jenkins
 ... stage ('deploy to env') { when { expression { branch == 'master' || params.DEPLOY_BRANCH_TO_TST } } steps { build job: './../Deploy', parameters: [ [$class: 'StringParameterValue', name: 'GIT_REPO', value: 'habr-demo-app'], [$class: 'StringParameterValue', name: 'VERSION', value: revision], [$class: 'StringParameterValue', name: 'ENV', value: branch == 'master' ? 'staging' : 'test'] ], wait: false } } ... 

Aqui você pode encontrar o Jenkinsfile com todas as etapas.


Assim, é possível construir uma implantação contínua no ambiente de teste ou combate selecionado, usando também o jenkins ou suas notificações de email / slack / etc, faça uma auditoria de qual aplicativo, qual versão, por quem, quando e onde foi instalado.


Conclusão


Usando Jenkinsfile e helm, você pode simplesmente criar ci / cd para seu aplicativo. Esse método pode ser mais relevante para equipes pequenas que começaram recentemente a usar o kubernetes e não podem (independentemente do motivo) usar serviços que possam fornecer essa funcionalidade imediatamente.


Você pode encontrar exemplos de configuração para ambientes, Jenkins e pipeline para gerenciar o ciclo de vida do aplicativo aqui e um aplicativo de exemplo com o Jenkinsfile aqui .

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


All Articles