Cómo construir una canalización de implementación inicial eficaz
Publicado: 2022-03-11Me encanta construir cosas, ¿qué desarrollador no? Me encanta pensar en soluciones a problemas interesantes, escribir implementaciones y crear código hermoso. Sin embargo, lo que no me gusta son las operaciones . Las operaciones son todo lo que no está relacionado con la creación de un gran software, desde configurar servidores hasta enviar su código a producción.
Esto es interesante porque, como desarrollador independiente de Ruby on Rails, con frecuencia tengo que crear nuevas aplicaciones web y repetir el proceso de descubrir el lado DevOps de las cosas. Afortunadamente, después de crear docenas de aplicaciones, finalmente me decidí por una canalización de implementación inicial perfecta. Desafortunadamente, no todo el mundo lo entendió como yo; eventualmente, este conocimiento me llevó a dar el paso y documentar mi proceso.
En este artículo, lo guiaré a través de mi tubería perfecta para usar al comienzo de su proyecto. Con mi canalización, cada impulso se prueba, la rama maestra se implementa en la etapa de pruebas con un volcado de base de datos nuevo de producción, y las etiquetas versionadas se implementan en producción con copias de seguridad y migraciones que se realizan automáticamente.
Tenga en cuenta que, dado que es mi tubería, también es obstinado y se adapta a mis necesidades; sin embargo, puede cambiar cualquier cosa que no le guste y reemplazarla con lo que le apetezca. Para mi tubería, usaremos:
- GitLab para alojar el código.
- Por qué: Mis clientes prefieren que su código permanezca en secreto, y el nivel gratuito de GitLab es maravilloso. Además, el CI gratuito integrado es increíble. ¡Gracias GitLab!
- Alternativas: GitHub, BitBucket, AWS CodeCommit y muchas más.
- GitLab CI para compilar, probar e implementar nuestro código.
- Por qué: ¡Se integra con GitLab y es gratis!
- Alternativas: TravisCI, Codeship, CircleCI, DIY con Fabric8 y muchas más.
- Heroku para alojar nuestra aplicación.
- Por qué: funciona de inmediato y es la plataforma perfecta para comenzar. Puede cambiar esto en el futuro, pero no todas las aplicaciones nuevas deben ejecutarse en un clúster de Kubernetes especialmente diseñado. Incluso Coinbase comenzó con Heroku.
- Alternativas: AWS, DigitalOcean, Vultr, DIY con Kubernetes y muchas más.
De la vieja escuela: cree una aplicación básica e impleméntela en Heroku
En primer lugar, vamos a recrear una aplicación típica para alguien que no utiliza canalizaciones de CI/CD sofisticadas y solo quiere implementar su aplicación.
No importa qué tipo de aplicación estés creando, pero necesitarás Yarn o npm. Para mi ejemplo, estoy creando una aplicación Ruby on Rails porque viene con migraciones y una CLI, y ya tengo la configuración escrita para ella. Puede usar cualquier marco o lenguaje que prefiera, pero necesitará Yarn para hacer el control de versiones que hago más adelante. Estoy creando una aplicación CRUD simple usando solo unos pocos comandos y sin autenticación.
Y probemos si nuestra aplicación funciona como se esperaba. Seguí adelante y creé algunas publicaciones, solo para asegurarme.
Y vamos a implementarlo en Heroku empujando nuestro código y ejecutando migraciones
$ heroku create toptal-pipeline Creating ⬢ toptal-pipeline... done https://toptal-pipeline.herokuapp.com/ | https://git.heroku.com/toptal-pipeline.git $ git push heroku master Counting objects: 132, done. ... To https://git.heroku.com/toptal-pipeline.git * [new branch] master -> master $ heroku run rails db:migrate Running rails db:migrate on ⬢ toptal-pipeline... up, run.9653 (Free) ...
Finalmente, vamos a probarlo en producción.
¡Y eso es! Por lo general, aquí es donde la mayoría de los desarrolladores dejan sus operaciones. En el futuro, si realiza cambios, tendrá que repetir los pasos de implementación y migración anteriores. Incluso puede realizar pruebas si no llega tarde a la cena. Esto es excelente como punto de partida, pero pensemos un poco más en este método.
ventajas
- Rápido de configurar.
- Las implementaciones son fáciles.
Contras
- No SECO: Requiere repetir los mismos pasos en cada cambio.
- No versionado: "Revertiré la implementación de ayer a la de la semana pasada" no es muy específico dentro de tres semanas.
- No a prueba de código incorrecto: sabe que se supone que debe ejecutar pruebas, pero nadie está mirando, por lo que puede presionarlo a pesar de la prueba fallida ocasional.
- No es a prueba de malos actores: ¿Qué pasa si un desarrollador descontento decide romper su aplicación presionando un código con un mensaje sobre cómo no ordena suficientes pizzas para su equipo?
- No escala: Permitir que cada desarrollador tenga la capacidad de implementar les daría acceso de nivel de producción a la aplicación, violando el Principio de privilegio mínimo.
- Sin entorno de prueba: los errores específicos del entorno de producción no aparecerán hasta la producción.
La tubería de implementación inicial perfecta
Voy a probar algo diferente hoy: tengamos una conversación hipotética. Voy a darle una voz a “usted”, y hablaremos sobre cómo podemos mejorar este flujo actual. Adelante, di algo.
¿Que qué? Espera, ¿puedo hablar?
Sí, eso es lo que quise decir sobre darte una voz. ¿Cómo estás?
Estoy bien. esto se siente raro
Lo entiendo, pero sigue adelante. Ahora, hablemos de nuestra canalización. ¿Cuál es la parte más molesta de ejecutar implementaciones?
Oh, eso es fácil. La cantidad de tiempo que pierdo. ¿Alguna vez has intentado empujar a Heroku?
¡Sí, ver cómo se descargan las dependencias y cómo se crea la aplicación como parte de git push
es horrible!
¿Yo se, verdad? Es una locura. Desearía no tener que hacer eso. También está el hecho de que tengo que ejecutar migraciones *después* de la implementación, así que tengo que ver el programa y verificar que mi implementación se ejecute
De acuerdo, podrías resolver ese último problema encadenando los dos comandos con &&
, como git push heroku master && heroku run rails db:migrate
, o simplemente creando un script bash y colocándolo en tu código, pero aún así, gran respuesta, el el tiempo y la repetición es un verdadero dolor.
Sí, realmente apesta
¿Qué pasaría si le dijera que puede arreglar ese bit de inmediato con una canalización de CI/CD?
¿Y ahora qué? ¿Qué es eso?
CI/CD significa integración continua (CI) y entrega/implementación continua (CD). Fue bastante difícil para mí entender exactamente qué era cuando estaba comenzando porque todos usaban términos vagos como "fusión de desarrollo y operaciones", pero en pocas palabras:
- Integración continua: asegurarse de que todo su código se fusione en un solo lugar. Haz que tu equipo use Git y estarás usando CI.
- Entrega continua: asegurándose de que su código esté continuamente listo para ser enviado. Lo que significa producir rápidamente una versión de lectura para distribuir de su producto.
- Implementación continua: tomar el producto de la entrega continua sin problemas y simplemente implementarlo en sus servidores.
Oh Ahora lo entiendo. ¡Se trata de hacer que mi aplicación se despliegue mágicamente en el mundo!
Mi artículo favorito que explica CI/CD es de Atlassian aquí. Esto debería aclarar cualquier duda que tengas. De todos modos, volviendo al problema.
Sí, de vuelta a eso. ¿Cómo evito las implementaciones manuales?
Configuración de una canalización de CI/CD para implementar en Push to master
¿Qué pasaría si te dijera que puedes arreglar ese bit inmediatamente con un CI/CD? Puede presionar su control remoto GitLab ( origin
) y se generará una computadora para simplemente enviar ese código suyo a Heroku.
¡De ningún modo!
¡Sí, camino! Volvamos al código de nuevo.
Cree un .gitlab-ci.yml
con los siguientes contenidos, intercambiando toptal-pipeline
por el nombre de su aplicación Heroku:
image: ruby:2.4 before_script: - > : "${HEROKU_EMAIL:?Please set HEROKU_EMAIL in your CI/CD config vars}" - > : "${HEROKU_AUTH_TOKEN:?Please set HEROKU_AUTH_TOKEN in your CI/CD config vars}" - curl https://cli-assets.heroku.com/install-standalone.sh | sh - | cat >~/.netrc <<EOF machine api.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN machine git.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN EOF - chmod 600 ~/.netrc - git config --global user.email "[email protected]" - git config --global user.name "CI/CD" variables: APPNAME_PRODUCTION: toptal-pipeline deploy_to_production: stage: deploy environment: name: production url: https://$APPNAME_PRODUCTION.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_PRODUCTION.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku run rails db:migrate --app $APPNAME_PRODUCTION only: - master
Empuje esto hacia arriba y observe cómo falla en la página Pipelines de su proyecto. Eso es porque faltan las claves de autenticación de su cuenta de Heroku. Sin embargo, arreglar eso es bastante sencillo. Primero necesitará su clave API de Heroku. Obtenlo de la página Administrar cuenta y luego agrega las siguientes variables secretas en la configuración de CI/CD de tu repositorio de GitLab:
-
HEROKU_EMAIL
: la dirección de correo electrónico que usas para iniciar sesión en Heroku -
HEROKU_AUTH_KEY
: La clave que obtuviste de Heroku
Esto debería dar como resultado una implementación de GitLab para Heroku en cada impulso. En cuanto a lo que está pasando:
- Al presionar para dominar
- La CLI de Heroku está instalada y autenticada en un contenedor.
- Su código se envía a Heroku.
- Se captura una copia de seguridad de su base de datos en Heroku.
- Se ejecutan las migraciones.
Ya puede ver que no solo está ahorrando tiempo al automatizar todo en un git push
, ¡también está creando una copia de seguridad de su base de datos en cada implementación! Si algo sale mal, tendrá una copia de su base de datos para volver.
Creación de un entorno de ensayo
Pero espere, una pregunta rápida, ¿qué sucede con sus problemas específicos de producción? ¿Qué pasa si te encuentras con un error extraño porque tu entorno de desarrollo es muy diferente al de producción? Una vez me encontré con algunos problemas extraños de SQLite 3 y PostgreSQL cuando ejecuté una migración. Los detalles se me escapan, pero es muy posible.

Utilizo estrictamente PostgreSQL en el desarrollo, nunca coincido con los motores de base de datos de esa manera, y superviso diligentemente mi pila en busca de posibles incompatibilidades.
Bueno, eso es un trabajo tedioso y aplaudo tu disciplina. Personalmente, soy demasiado perezoso para hacer eso. Sin embargo, ¿puede garantizar ese nivel de diligencia para todos los futuros desarrolladores, colaboradores o contribuyentes potenciales?
Errrr— Sí, no. Me atrapaste. Otras personas lo estropearán. ¿Cuál es tu punto, sin embargo?
Mi punto es que necesitas un entorno de ensayo. Es como la producción pero no lo es. Un entorno de prueba es donde ensaya la implementación en producción y detecta todos los errores con anticipación. Mis entornos de ensayo suelen reflejar la producción, y descargo una copia de la base de datos de producción en la implementación de ensayo para asegurarme de que ningún caso de esquina molesto arruine mis migraciones. Con un entorno de ensayo, puede dejar de tratar a sus usuarios como conejillos de indias.
¡Esto tiene sentido! Entonces, ¿cómo hago esto?
Aquí es donde se pone interesante. Me gusta implementar el master
directamente en la puesta en escena.
Espera, ¿no es ahí donde estamos implementando la producción en este momento?
Sí, lo es, pero ahora lo implementaremos en el escenario.
Pero si master
se implementa en la etapa de pruebas, ¿cómo lo implementamos en la producción?
Usando algo que deberías haber estado haciendo hace años: Versionar nuestro código y empujar las etiquetas de Git.
¿Etiquetas Git? ¿Quién usa las etiquetas de Git? Esto empieza a sonar como mucho trabajo.
Seguro que lo fue, pero afortunadamente, ya hice todo ese trabajo y puedes simplemente volcar mi código y funcionará.
Primero, agregue un bloque sobre la implementación de la etapa en su archivo .gitlab-ci.yml
, he creado una nueva aplicación de Heroku llamada toptal-pipeline-staging
:
… variables: APPNAME_PRODUCTION: toptal-pipeline APPNAME_STAGING: toptal-pipeline-staging deploy_to_staging: stage: deploy environment: name: staging url: https://$APPNAME_STAGING.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_STAGING.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku pg:backups:restore `heroku pg:backups:url --app $APPNAME_PRODUCTION` --app $APPNAME_STAGING --confirm $APPNAME_STAGING - heroku run rails db:migrate --app $APPNAME_STAGING only: - master - tags ...
Luego, cambie la última línea de su bloque de producción para que se ejecute en etiquetas Git versionadas semánticamente en lugar de la rama principal:
deploy_to_production: ... only: - /^v(?'MAJOR'(?:0|(?:[1-9]\d*)))\.(?'MINOR'(?:0|(?:[1-9]\d*)))\.(?'PATCH'(?:0|(?:[1-9]\d*)))(?:-(?'prerelease'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?(?:\+(?'build'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$/ # semver pattern above is adapted from https://github.com/semver/semver.org/issues/59#issuecomment-57884619
Ejecutar esto ahora fallará porque GitLab es lo suficientemente inteligente como para permitir que solo las ramas "protegidas" accedan a nuestras variables secretas. Para agregar etiquetas de versión, vaya a la página de configuración del repositorio de su proyecto GitLab y agregue v*
a las etiquetas protegidas.
Recapitulemos lo que está sucediendo ahora:
- Al presionar para dominar, o al presionar una confirmación etiquetada
- La CLI de Heroku está instalada y autenticada en un contenedor.
- Su código se envía a Heroku.
- Se captura una copia de seguridad de la producción de su base de datos en Heroku.
- La copia de seguridad se vuelca en su entorno de ensayo.
- Las migraciones se ejecutan en la base de datos provisional.
- Al enviar una versión etiquetada semánticamente, confirme
- La CLI de Heroku está instalada y autenticada en un contenedor.
- Su código se envía a Heroku.
- Se captura una copia de seguridad de la producción de su base de datos en Heroku.
- Las migraciones se ejecutan en la base de datos de producción.
¿Te sientes poderoso ahora? Me siento poderoso. Recuerdo, la primera vez que llegué tan lejos, llamé a mi esposa y le expliqué todo este oleoducto con detalles insoportables. Y ella ni siquiera es técnica. ¡Estaba súper impresionado conmigo mismo, y tú también deberías estarlo! ¡Gran trabajo llegando tan lejos!
Probando Cada Empuje
Pero hay más, dado que una computadora está haciendo cosas por ti de todos modos, también podría ejecutar todas las cosas que eres demasiado perezoso para hacer: pruebas, errores de linting, prácticamente cualquier cosa que quieras hacer, y si alguno de estos falla, ganaron 't pasar a la implementación.
Me encanta tener esto en mi tubería, hace que mis revisiones de código sean divertidas. Si una solicitud de fusión supera todas mis verificaciones de código, merece ser revisada.
Agregar un bloque de test
:
test: stage: test variables: POSTGRES_USER: test POSTGRES_PASSSWORD: test-password POSTGRES_DB: test DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSSWORD}@postgres/${POSTGRES_DB} RAILS_ENV: test services: - postgres:alpine before_script: - curl -sL https://deb.nodesource.com/setup_8.x | bash - apt-get update -qq && apt-get install -yqq nodejs libpq-dev - curl -o- -L https://yarnpkg.com/install.sh | bash - source ~/.bashrc - yarn - gem install bundler --no-ri --no-rdoc - bundle install -j $(nproc) --path vendor - bundle exec rake db:setup RAILS_ENV=test script: - bundle exec rake spec - bundle exec rubocop
Recapitulemos lo que está sucediendo ahora:
- En cada envío o solicitud de fusión
- Ruby y Node se configuran en un contenedor.
- Las dependencias están instaladas.
- La aplicación está probada.
- Al presionar para dominar, o al presionar una confirmación etiquetada, y solo si todas las pruebas pasan
- La CLI de Heroku está instalada y autenticada en un contenedor.
- Su código se envía a Heroku.
- Se captura una copia de seguridad de la producción de su base de datos en Heroku.
- La copia de seguridad se vuelca en su entorno de ensayo.
- Las migraciones se ejecutan en la base de datos provisional.
- Al enviar una confirmación de versión etiquetada semánticamente, y solo si todas las pruebas pasan
- La CLI de Heroku está instalada y autenticada en un contenedor.
- Su código se envía a Heroku.
- Se captura una copia de seguridad de la producción de su base de datos en Heroku.
- Las migraciones se ejecutan en la base de datos de producción.
Da un paso atrás y maravíllate con el nivel de automatización que has logrado. De ahora en adelante, todo lo que tienes que hacer es escribir código y empujar. Pruebe su aplicación manualmente en staging si lo desea, y cuando se sienta lo suficientemente seguro como para lanzarla al mundo, ¡etiquétela con versiones semánticas!
Versionado semántico automático
Sí, es perfecto, pero falta algo. No me gusta buscar la última versión de la aplicación y etiquetarla explícitamente. Eso requiere múltiples comandos y me distrae por unos segundos.
Bien, amigo, ¡detente! Eso es suficiente. Ahora lo estás sobredimensionando. Funciona, es brillante, no arruines algo bueno exagerando.
Vale, tengo una buena razón para hacer lo que voy a hacer.
Ruega, ilumíname.
Solía ser como tú. Estaba contento con esta configuración, pero luego me equivoqué. git tag
enumera las etiquetas en orden alfabético, v0.0.11
está por encima de v0.0.2
. Una vez etiqueté accidentalmente un lanzamiento y continué haciéndolo durante aproximadamente media docena de lanzamientos hasta que vi mi error. Fue entonces cuando decidí automatizar esto también.
Aquí vamos de nuevo
De acuerdo, afortunadamente, tenemos el poder de npm a nuestra disposición, así que encontré un paquete adecuado: Ejecute yarn add --dev standard-version
y agregue lo siguiente a su archivo package.json
:
"scripts": { "release": "standard-version", "major": "yarn release --release-as major", "minor": "yarn release --release-as minor", "patch": "yarn release --release-as patch" },
Ahora debe hacer una última cosa, configurar Git para insertar etiquetas de forma predeterminada. Por el momento, debe ejecutar git push --tags
para subir una etiqueta, pero hacerlo automáticamente en git push
regular es tan simple como ejecutar git config --global push.followTags true
.
Para usar su nueva canalización, cada vez que desee crear una ejecución de lanzamiento:
-
yarn patch
para lanzamientos de parches -
yarn minor
para lanzamientos menores -
yarn major
para lanzamientos importantes
Si no está seguro de lo que significan las palabras "principal", "menor" y "parche", lea más sobre esto en el sitio de versiones semánticas.
Ahora que finalmente ha completado su canalización, recapitulemos cómo usarlo.
- Escribir código.
- Comprométalo y empújelo para probarlo e implementarlo en la etapa de pruebas.
- Use un
yarn patch
para etiquetar un lanzamiento de parche. -
git push
para enviarlo a producción.
Resumen y pasos adicionales
Acabo de rascar la superficie de lo que es posible con las canalizaciones de CI/CD. Este es un ejemplo bastante simplista. Puede hacer mucho más cambiando Heroku por Kubernetes. Si decide usar GitLab CI, lea los documentos de yaml porque hay mucho más que puede hacer almacenando archivos en caché entre implementaciones o guardando artefactos.
Otro gran cambio que podría realizar en esta canalización es introducir disparadores externos para ejecutar el control de versiones y la liberación semántica. Actualmente, ChatOps es parte de su plan pago y espero que lo lancen a los planes gratuitos. ¡Pero imagine poder activar la siguiente imagen a través de un solo comando de Slack!
Eventualmente, a medida que su aplicación comienza a volverse compleja y requiere dependencias a nivel de sistema, es posible que deba usar un contenedor. Cuando eso suceda, consulte nuestra guía: Introducción a Docker: Simplificación de DevOps.
Esta aplicación de ejemplo realmente está activa y puede encontrar el código fuente aquí.