Buenas tardes
Ya hay varios artículos sobre Habré sobre jenkins, ci / cd y kubernetes, pero en esto no quiero concentrarme en analizar las capacidades de estas tecnologías, sino en su configuración más simple para construir la tubería ci / cd.
Quiero decir que el lector tiene una comprensión básica de Docker, y no me detendré en los temas de instalación y configuración de kubernetes. Todos los ejemplos se mostrarán en minikube, pero también se pueden aplicar en EKS, GKE o similares sin cambios significativos.

Medio ambiente
Sugiero usar los siguientes entornos:
- prueba: para implementación manual y pruebas de sucursal
- puesta en escena: un entorno donde todos los cambios que se incluyen en el maestro se implementan automáticamente
- producción: el entorno utilizado por usuarios reales, donde los cambios solo se realizarán después de confirmar su operatividad en la puesta en escena
Los entornos se organizarán utilizando espacios de nombres kubernetes dentro de un solo clúster. Este enfoque es lo más simple y rápido posible al principio, pero también tiene sus inconvenientes: los espacios de nombres no están completamente aislados entre sí en kubernetes.
En este ejemplo, cada espacio de nombres tendrá el mismo conjunto de ConfigMaps con las configuraciones de este entorno:
apiVersion: v1 kind: Namespace metadata: name: production --- apiVersion: v1 kind: ConfigMap metadata: name: environment.properties namespace: production data: environment.properties: | env=production
Timón
Helm es una aplicación que ayuda a administrar los recursos instalados en kubernetes.
Las instrucciones de instalación se pueden encontrar aquí .
Para comenzar, debe inicializar el timón para usar timón con el clúster:
helm init
Jenkins
Usaré Jenkins, ya que es una plataforma bastante simple, flexible y popular para construir proyectos. Se instalará en un espacio de nombres separado para aislarse de otros entornos. Como planeo usar Helm en el futuro, puedo simplificar la instalación de Jenkins usando los gráficos de código abierto existentes:
helm install --name jenkins --namespace jenkins -f jenkins/demo-values.yaml stable/jenkins
demo-values.yaml contiene la versión de Jenkins, un conjunto de complementos preinstalados, un nombre de dominio y otra configuración
valores-demo.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
Esta configuración usa admin / admin como nombre de usuario y contraseña para iniciar sesión, y puede reconfigurarse más tarde. Una de las opciones posibles es el SSO de Google (para esto, se requiere el complemento de inicio de sesión de Google, su configuración se encuentra en Jenkins> Administrar Jenkins> Configurar seguridad global> Control de acceso> Reino de seguridad> Iniciar sesión con Google).
Jenkins se configurará inmediatamente para crear automáticamente un esclavo único para cada compilación. Gracias a esto, el equipo ya no esperará un agente gratuito para el ensamblaje, y la empresa podrá ahorrar en la cantidad de servidores necesarios.

También listo para usar, PersistenceVolume está configurado para guardar canalizaciones al reiniciar o actualizar.
Para que los scripts de implementación automática funcionen correctamente, debe otorgar permiso de administrador de clúster para que Jenkins obtenga una lista de recursos en kubernetes y los manipule.
kubectl create clusterrolebinding jenkins --clusterrole cluster-admin --serviceaccount=jenkins:default
En el futuro, puede actualizar Jenkins usando el timón en caso de nuevas versiones de complementos o cambios de configuración.
helm upgrade jenkins stable/jenkins -f jenkins/demo-values.yaml
Esto se puede hacer a través de la interfaz de Jenkins, pero con Helm tendrás la oportunidad de volver a las revisiones anteriores usando:
helm history jenkins helm rollback jenkins ${revision}
Construcción de la aplicación
Como ejemplo, construiré e implementaré la aplicación de arranque de primavera más simple. De manera similar con Jenkins usaré timón.
El montaje tendrá lugar en la siguiente secuencia:
- pago
- compilación
- prueba unitaria
- prueba de integración
- montaje de artefactos
- despliegue de artefactos en el registro de docker
- desplegar artefactos en la preparación (solo para la rama maestra)
Para esto utilizo el archivo Jenkins . En mi opinión, esta es una forma muy flexible (pero, desafortunadamente, no la más fácil) de configurar el ensamblaje del proyecto. Una de sus ventajas es la capacidad de mantener la configuración del ensamblaje del proyecto en el repositorio con el proyecto mismo.
pago

En el caso de la organización bitbucket o github, puede configurar Jenkins para escanear periódicamente la cuenta completa en busca de repositorios con Jenkinsfile y crear automáticamente ensamblajes para ellos. Jenkins recogerá tanto el maestro como las ramas. Las solicitudes de extracción se mostrarán en una pestaña separada. Hay una opción más simple: agregar un repositorio git separado, independientemente de dónde esté alojado. En este ejemplo, haré exactamente eso. Todo lo que se necesita está en el menú Jenkins> Nuevo elemento> Canalización de múltiples ramas, seleccione el nombre del ensamblaje y enlace el repositorio git.
Compilación
Dado que Jenkins crea un nuevo pod para cada ensamblaje, en el caso de usar maven o colectores similares, las dependencias se descargarán nuevamente cada vez. Para evitar esto, puede asignar PersistenceVolume para .m2 o cachés similares y montarlo en el pod que construye el proyecto.
apiVersion: "v1" kind: "PersistentVolumeClaim" metadata: name: "repository" namespace: "jenkins" spec: accessModes: - ReadWriteMany resources: requests: storage: 10Gi
En mi caso, esto permitió acelerar la tubería de aproximadamente 4 al primer minuto.
Versionado
Para que CI / CD funcione correctamente, cada compilación necesita una versión única.
Una muy buena opción sería usar versiones semánticas . Esto le permitirá realizar un seguimiento de los cambios compatibles e incompatibles con versiones anteriores, pero este control de versiones es más difícil de automatizar.
En este ejemplo, generaré una versión de id y la fecha de la confirmación, así como el nombre de la rama, si no es master. Por ejemplo 56e0fbdc-201802231623 o b3d3c143-201802231548-PR-18 .
Ventajas de este enfoque:
- facilidad de automatización
- es fácil obtener el código fuente y su tiempo de creación a partir de la versión
- visualmente, puede distinguir la versión de lanzamiento del candidato (del asistente) o experimental (de la rama)
pero: - esta versión es más difícil de usar en la comunicación oral
- No está claro si hubo cambios incompatibles.
Dado que la imagen de Docker puede tener varias etiquetas al mismo tiempo, los enfoques se pueden combinar: todas las versiones usan las versiones generadas, y las que se incluyen en la producción se etiquetan adicionalmente (manualmente) con versiones semánticas. Esto, a su vez, está asociado con una complejidad de implementación aún mayor y la ambigüedad de qué versión debe mostrar la aplicación.
Artefactos
El resultado de la asamblea será:
- imagen de docker con una aplicación que se almacenará y cargará desde el registro de docker. El ejemplo utilizará el registro integrado de minikube, que puede reemplazarse por un docker hub o un registro privado de amazon (ecr) o google (no olvide proporcionarles credenciales utilizando la construcción withCredentials).
- gráficos de timón con una descripción de la implementación de la aplicación (implementación, servicio, etc.) en el directorio de timón. Idealmente, deberían almacenarse en un repositorio separado de artefactos, pero, para simplificar, pueden usarse al verificar la confirmación necesaria de git.
Jenkinsfile
Como resultado, la aplicación se creará utilizando el siguiente archivo 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}" } } } } }
Tuberías adicionales de Jenkins para la gestión del ciclo de vida de la aplicación
Suponga que los repositorios están organizados de manera que:
- contener una aplicación separada en forma de imagen acoplable
- se puede implementar utilizando archivos helm ubicados en el directorio helm
- se versionan con el mismo enfoque y tienen un archivo helm / setVersion.sh para configurar las revisiones en los gráficos de timones
Luego podemos construir varias tuberías de Jenkinsfile para administrar el ciclo de vida de la aplicación, a saber:
En el archivo Jenkins de cada proyecto, puede agregar una llamada de canalización de implementación que se ejecutará cada vez que la rama maestra se compila correctamente o cuando la rama de implementación solicita explícitamente el entorno de prueba.
Archivo Jenkins desplegar llamada de canalización ... 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 } } ...
Aquí puede encontrar Jenkinsfile con todos los pasos.
Por lo tanto, es posible construir una implementación continua en el entorno de prueba o combate seleccionado, también utilizando jenkins o sus notificaciones por correo electrónico / holgura / etc., tener una auditoría de qué aplicación, qué versión, por quién, cuándo y dónde se instaló.
Conclusión
Usando Jenkinsfile y helm, simplemente puede construir ci / cd para su aplicación. Este método puede ser más relevante para equipos pequeños que recientemente han comenzado a usar kubernetes y no pueden (independientemente de la razón) usar servicios que puedan proporcionar tal funcionalidad de forma inmediata.
Puede encontrar ejemplos de configuración para entornos, Jenkins y canalización para administrar el ciclo de vida de la aplicación aquí y una aplicación de ejemplo con Jenkinsfile aquí .