{"id":69351,"date":"2020-02-18T14:44:06","date_gmt":"2020-02-18T11:44:06","guid":{"rendered":"https:\/\/prohoster.info\/blog\/blog_prohoster\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker"},"modified":"2020-03-03T16:15:00","modified_gmt":"2020-03-03T13:15:00","slug":"sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker","status":"publish","type":"post","link":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker","title":{"rendered":"Creando una cadena CI\/CD y automatizando el trabajo con Docker","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Escrib\u00ed mis primeros sitios a finales de los 90. Entonces, ponerlos en funcionamiento era muy sencillo. Hab\u00eda un servidor Apache en alg\u00fan hosting compartido, al que se pod\u00eda acceder por FTP, escribiendo en la barra del navegador algo as\u00ed como <code>ftp:\/\/ftp.example.com<\/code>. Despu\u00e9s hab\u00eda que introducir un nombre y una contrase\u00f1a y subir los archivos al servidor. Eran otros tiempos, todo era m\u00e1s sencillo que ahora.<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/ruvds\/blog\/488668\/\"><img decoding=\"async\" alt=\"Creando una cadena CI\/CD y automatizando el trabajo con Docker\" src=\"\/wp-content\/uploads\/2020\/02\/0861cdd82770ef4695ffac7a09777c52.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/a><\/noindex><\/p>\n<p>En las dos d\u00e9cadas transcurridas desde entonces, todo ha cambiado bastante. Los sitios se han vuelto m\u00e1s complejos, y antes de lanzarlos a producci\u00f3n, requieren un proceso de ensamblaje. Un \u00fanico servidor se ha convertido en m\u00faltiples servidores, que operan detr\u00e1s de balanceadores de carga, y es com\u00fan utilizar sistemas de control de versiones.<\/p>\n<p>Para mi proyecto personal, ten\u00eda una configuraci\u00f3n especial. Y sab\u00eda que necesitaba poder desplegar el sitio en producci\u00f3n con un solo paso: enviar el c\u00f3digo a la rama <code>master<\/code> en GitHub. Adem\u00e1s, sab\u00eda que no quer\u00eda gestionar un enorme cl\u00faster de Kubernetes, ni utilizar Docker Swarm, ni mantener un parque de servidores con pods, agentes y toda esa complejidad. Para lograr el objetivo de simplificar al m\u00e1ximo el trabajo, necesitaba familiarizarme con CI\/CD.<\/p>\n<p>Si tienes un peque\u00f1o proyecto (en este caso se trata de un proyecto de Node.js) y quieres aprender a automatizar el despliegue de este proyecto, garantizando que lo que se almacena en el repositorio coincide exactamente con lo que funciona en producci\u00f3n, entonces creo que este art\u00edculo te puede interesar.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Requisitos previos<\/h2>\n<p>\nSe espera que el lector de este art\u00edculo tenga conocimientos b\u00e1sicos sobre el uso de la l\u00ednea de comandos y la escritura de scripts en Bash. Adem\u00e1s, necesitar\u00e1 cuentas <noindex><a rel=\"nofollow\" href=\"https:\/\/travis-ci.org\/\">Travis CI<\/a><\/noindex> y <noindex><a rel=\"nofollow\" href=\"https:\/\/hub.docker.com\/\">Docker Hub<\/a><\/noindex>.<\/p>\n<h2>Objetivos<\/h2>\n<p>\nNo dir\u00e9 que este art\u00edculo pueda ser considerado un \"manual de instrucciones\" sin reservas. M\u00e1s bien, es un documento en el que relato lo que he aprendido y describo el proceso de testing y despliegue de c\u00f3digo en producci\u00f3n que he desarrollado, realizado en una sola pasada automatizada.<\/p>\n<p>As\u00ed es como ha quedado mi flujo de trabajo.<\/p>\n<p>Para el c\u00f3digo enviado a cualquier rama del repositorio, excepto <code>master<\/code>, se realizan las siguientes acciones:<\/p>\n<ul>\n<li>Se inicia la construcci\u00f3n del proyecto en Travis CI.<\/li>\n<li>Se realizan todas las pruebas modulares, de integraci\u00f3n y de extremo a extremo.<\/li>\n<\/ul>\n<p>\nSolo para el c\u00f3digo que entra en <code>master<\/code>, se realiza lo siguiente:<\/p>\n<ul>\n<li>Todo lo mencionado anteriormente, m\u00e1s\u2026<\/li>\n<li>Construcci\u00f3n de la imagen de Docker en base al c\u00f3digo actual, configuraciones y entorno.<\/li>\n<li>Publicaci\u00f3n de la imagen en Docker Hub.<\/li>\n<li>Conexi\u00f3n al servidor de producci\u00f3n.<\/li>\n<li>Descarga de la imagen desde Docker Hub al servidor.<\/li>\n<li>Detenci\u00f3n del contenedor actual y lanzamiento de uno nuevo basado en la nueva imagen.<\/li>\n<\/ul>\n<p>\nSi no sabes nada sobre Docker, im\u00e1genes y contenedores, no te preocupes. Te contar\u00e9 todo sobre eso.<\/p>\n<h2>\u00bfQu\u00e9 es CI\/CD?<\/h2>\n<p>\nLa abreviatura CI\/CD se traduce como \u2018integraci\u00f3n continua\/despliegue continuo\u2019.<\/p>\n<h3>\u258dIntegraci\u00f3n continua<\/h3>\n<p>\nLa integraci\u00f3n continua es un proceso en el que los desarrolladores hacen commits en el repositorio principal del c\u00f3digo fuente del proyecto (generalmente en la rama <code>master<\/code>). La calidad del c\u00f3digo se asegura mediante pruebas automatizadas.<\/p>\n<h3>\u258dDespliegue continuo<\/h3>\n<p>\nEl despliegue continuo es un lanzamiento automatizado de c\u00f3digo en producci\u00f3n de manera frecuente. La segunda parte de la abreviatura CI\/CD a veces se entiende como \u2018entrega continua\u2019. Esto es, en general, lo mismo que el \u2018despliegue continuo\u2019, pero \u2018entrega continua\u2019 implica la necesidad de una confirmaci\u00f3n manual de los cambios antes de iniciar el proceso de despliegue del proyecto.<\/p>\n<h2>Inicio<\/h2>\n<p>\nLa aplicaci\u00f3n con la que aprend\u00ed todo esto se llama <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/taniarascia\/takenote\">TakeNote<\/a><\/noindex>. Este es un proyecto web en el que estoy trabajando, destinado a tomar notas. Al principio intent\u00e9 hacer un <noindex><a rel=\"nofollow\" href=\"https:\/\/jamstack.org\/\">proyecto JAMStack<\/a><\/noindex>, o simplemente una aplicaci\u00f3n frontend sin servidor, para aprovechar las capacidades est\u00e1ndar de alojamiento y despliegue que ofrece <noindex><a rel=\"nofollow\" href=\"https:\/\/www.netlify.com\/\">Netlify<\/a><\/noindex>. A medida que aumentaba la complejidad de la aplicaci\u00f3n, necesitaba crear su parte del servidor, lo que significaba que ten\u00eda que formular mi propia estrategia de integraci\u00f3n y despliegue automatizados del proyecto.<\/p>\n<p>En mi caso, la aplicaci\u00f3n consiste en un servidor Express, que funciona en el entorno Node.js, que sirve una aplicaci\u00f3n React de una sola p\u00e1gina y soporta una API de servidor segura. Esta arquitectura sigue una estrategia que se puede encontrar en <noindex><a rel=\"nofollow\" href=\"https:\/\/www.taniarascia.com\/full-stack-cookies-localstorage-react-express\/\">este<\/a><\/noindex> gu\u00eda sobre la autenticaci\u00f3n full stack.<\/p>\n<p>Consult\u00e9 a <noindex><a rel=\"nofollow\" href=\"http:\/\/qualitytesting.tech\/\">otra<\/a><\/noindex>, que es un experto en automatizaci\u00f3n, y le pregunt\u00e9 qu\u00e9 necesitaba hacer para que todo funcionara como yo quer\u00eda. \u00c9l me dio una idea de c\u00f3mo deber\u00eda ser el flujo de trabajo automatizado descrito en la secci\u00f3n 'Objetivos' de este art\u00edculo. El hecho de que me haya propuesto tales objetivos significaba que necesitaba entender c\u00f3mo usar Docker.<\/p>\n<h2>Docker<\/h2>\n<p>\nDocker es una herramienta que, gracias a la tecnolog\u00eda de contenedorizaci\u00f3n, permite distribuir aplicaciones f\u00e1cilmente, as\u00ed como desplegarlas y ejecutarlas en el mismo entorno, incluso si la propia plataforma Docker funciona en diferentes entornos. Primero, necesitaba tener acceso a las herramientas de l\u00ednea de comandos (CLI) de Docker. <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/install\/\">Instrucciones<\/a><\/noindex> para instalar Docker no se pueden considerar muy claras y comprensibles, pero de ellas se puede aprender que, para dar el primer paso en la instalaci\u00f3n, es necesario descargar Docker Desktop (para Mac o Windows).<\/p>\n<p>Docker Hub es algo similar a <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/\">GitHub<\/a><\/noindex> para los repositorios git, o un registro <noindex><a rel=\"nofollow\" href=\"https:\/\/www.npmjs.com\/\">npm<\/a><\/noindex> para paquetes de JavaScript. Es un repositorio en l\u00ednea para im\u00e1genes de Docker. Docker Desktop se conecta a \u00e9l.<\/p>\n<p>As\u00ed que, para empezar a trabajar con Docker, hay que hacer dos cosas:<\/p>\n<ul>\n<li>Instala <noindex><a rel=\"nofollow\" href=\"https:\/\/www.docker.com\/get-started\">Docker Desktop<\/a><\/noindex>.<\/li>\n<li>Reg\u00edstrate en <noindex><a rel=\"nofollow\" href=\"https:\/\/hub.docker.com\/\">Docker Hub<\/a><\/noindex>.<\/li>\n<\/ul>\n<p>\nDespu\u00e9s de esto, puedes comprobar el funcionamiento de Docker CLI ejecutando el siguiente comando para verificar la versi\u00f3n de Docker:<\/p>\n<pre><code class=\"plaintext\">docker -v<\/code><\/pre>\n<p>\nLuego, inicia sesi\u00f3n en Docker Hub ingresando, cuando se te pida, tu nombre de usuario y contrase\u00f1a:<\/p>\n<pre><code class=\"plaintext\">docker login<\/code><\/pre>\n<p>\nPara usar Docker, debes entender los conceptos de im\u00e1genes y contenedores.<\/p>\n<h3>\u258dIm\u00e1genes<\/h3>\n<p>\nUna imagen es como un plano que contiene instrucciones para construir un contenedor. Es una instant\u00e1nea inmutable del sistema de archivos y la configuraci\u00f3n de la aplicaci\u00f3n. Los desarrolladores pueden intercambiar im\u00e1genes con facilidad.<\/p>\n<pre><code class=\"plaintext\"># \u0412\u044b\u0432\u043e\u0434 \u0441\u0432\u0435\u0434\u0435\u043d\u0438\u0439 \u043e\u0431\u043e \u0432\u0441\u0435\u0445 \u043e\u0431\u0440\u0430\u0437\u0430\u0445\ndocker images<\/code><\/pre>\n<p>\nEste comando mostrar\u00e1 una tabla con el siguiente encabezado:<\/p>\n<pre><code class=\"plaintext\">REPOSITORY \u00a0 \u00a0 TAG     IMAGE ID \u00a0 \u00a0 CREATED     SIZE\n---<\/code><\/pre>\n<p>\nA continuaci\u00f3n, veremos algunos ejemplos de comandos en el mismo formato: primero va el comando con un comentario, y luego un ejemplo de lo que puede mostrar.<\/p>\n<h3>\u258dContenedores<\/h3>\n<p>\nUn contenedor es un paquete ejecutable que contiene todo lo necesario para ejecutar una aplicaci\u00f3n. Con este enfoque, la aplicaci\u00f3n siempre funcionar\u00e1 de la misma manera, independientemente de la infraestructura: en un entorno aislado y en el mismo tipo de entorno. Se trata de que se ejecutan instancias de la misma imagen en diferentes entornos.<\/p>\n<pre><code class=\"plaintext\"># \u041f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432\ndocker ps -a\nCONTAINER ID \u00a0 \u00a0 IMAGE     COMMAND \u00a0 \u00a0 CREATED     STATUS     PORTS \u00a0 \u00a0 NAMES\n---<\/code><\/pre>\n<p><\/p>\n<h3>\u258dEtiquetas<\/h3>\n<p>\nUna etiqueta es una referencia a una versi\u00f3n espec\u00edfica de la imagen.<\/p>\n<h3>\u258dBreve referencia de comandos de Docker<\/h3>\n<p>\nAqu\u00ed hay un resumen de algunos de los comandos de Docker m\u00e1s utilizados.<\/p>\n<p>Comando<\/p>\n<p>Contexto<\/p>\n<p>Acci\u00f3n<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/build\/\">docker build<\/a><\/noindex><\/p>\n<p>Imagen<\/p>\n<p>Construir una imagen a partir de un Dockerfile<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/tag\/\">docker tag<\/a><\/noindex><\/p>\n<p>Imagen<\/p>\n<p>Etiquetar la imagen<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/images\/\">docker images<\/a><\/noindex><\/p>\n<p>Imagen<\/p>\n<p>Mostrar lista de im\u00e1genes<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/run\/\">docker run<\/a><\/noindex><\/p>\n<p>Contenedor<\/p>\n<p>Ejecutar un contenedor basado en la imagen<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/push\/\">docker push<\/a><\/noindex><\/p>\n<p>Imagen<\/p>\n<p>Enviar la imagen al registro<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"http:\/\/about:blank\">docker pull<\/a><\/noindex><\/p>\n<p>Imagen<\/p>\n<p>Descargar la imagen del registro<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/ps\/\">docker ps<\/a><\/noindex><\/p>\n<p>Contenedor<\/p>\n<p>Mostrar lista de contenedores<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/system_prune\/\">docker system prune<\/a><\/noindex><\/p>\n<p>Imagen\/Contenedor<\/p>\n<p>Eliminar contenedores e im\u00e1genes no utilizados<\/p>\n<h3>\u258dArchivo Dockerfile<\/h3>\n<p>\nS\u00e9 c\u00f3mo ejecutar localmente una aplicaci\u00f3n para producci\u00f3n. Tengo una configuraci\u00f3n de Webpack dise\u00f1ada para construir una aplicaci\u00f3n React lista. Adem\u00e1s, tengo un comando que inicia un servidor basado en Node.js en el puerto <code>5000<\/code>. As\u00ed es como se ve:<\/p>\n<pre><code class=\"plaintext\">npm i          # instalar dependencias\nnpm run build # construir la aplicaci\u00f3n React\nnpm run start # iniciar el servidor Node<\/code><\/pre>\n<p>\nCabe destacar que no tengo una aplicaci\u00f3n de ejemplo para este material. Pero para experimentos, cualquier aplicaci\u00f3n Node sencilla funcionar\u00e1.<\/p>\n<p>Para utilizar el contenedor, necesitar\u00e1s dar instrucciones a Docker. Esto se hace a trav\u00e9s de un archivo llamado <code>Dockerfile<\/code>, que se encuentra en el directorio ra\u00edz del proyecto. Este archivo, al principio, puede parecer bastante confuso.<\/p>\n<p>Pero lo que contiene es simplemente una descripci\u00f3n, utilizando comandos espec\u00edficos, de algo similar a la configuraci\u00f3n de un entorno de trabajo. Aqu\u00ed algunos de estos comandos:<\/p>\n<ul>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#from\">FROM<\/a><\/noindex> \u2014 Este comando inicia el archivo. Se especifica la imagen base a partir de la cual se construye el contenedor.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#copy\">COPY<\/a><\/noindex> \u2014 Copiar archivos de una fuente local al contenedor.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#workdir\">WORKDIR<\/a><\/noindex> \u2014 Establecer el directorio de trabajo para los siguientes comandos.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#run\">RUN<\/a><\/noindex> \u2014 Ejecutar comandos.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#expose\">EXPOSE<\/a><\/noindex> \u2014 Configurar el puerto.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#entrypoint\">ENTRYPOINT<\/a><\/noindex> \u2014 Especificar el comando a ejecutar.<\/li>\n<\/ul>\n<p>\n<code>Dockerfile<\/code> puede verse algo as\u00ed:<\/p>\n<pre><code class=\"plaintext\"># \u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043e\u0431\u0440\u0430\u0437\nFROM node:12-alpine\n\n# \u0421\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u0438\u0437 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e app\/\nCOPY . app\/\n\n# \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c app\/ \u0432 \u0440\u043e\u043b\u0438 \u0440\u0430\u0431\u043e\u0447\u0435\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438\nWORKDIR app\/\n\n# \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 (\u043a\u043e\u043c\u0430\u043d\u0434\u0430 npm ci \u043f\u043e\u0445\u043e\u0436\u0430 npm i, \u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0441\u0431\u043e\u0440\u043e\u043a)\nRUN npm ci --only-production\n\n# \u0421\u043e\u0431\u0440\u0430\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0435 React-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043d\u0430\nRUN npm run build\n\n# \u041f\u0440\u043e\u0441\u043b\u0443\u0448\u0438\u0432\u0430\u0442\u044c \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u0440\u0442\nEXPOSE 5000\n\n# \u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c Node-\u0441\u0435\u0440\u0432\u0435\u0440\nENTRYPOINT npm run start<\/code><\/pre>\n<p>\nDependiendo de la imagen base seleccionada, puede que necesite instalar dependencias adicionales. Esto se debe a que algunas im\u00e1genes base (como Node Alpine Linux) est\u00e1n dise\u00f1adas para ser lo m\u00e1s compactas posible. Como resultado, pueden faltar algunos programas en los que conf\u00eda.<\/p>\n<h3>\u258dConstrucci\u00f3n, etiquetado y ejecuci\u00f3n del contenedor<\/h3>\n<p>\nLa construcci\u00f3n y ejecuci\u00f3n local del contenedor son, despu\u00e9s de que tengamos <code>Dockerfile<\/code>, tareas bastante sencillas. Antes de enviar la imagen a Docker Hub, debe ser probada localmente.<\/p>\n<h3>\u258dConstrucci\u00f3n<\/h3>\n<p>\nPrimero debe construirse <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/build\/\">imagen<\/a><\/noindex>, especificando un nombre y, opcionalmente, una etiqueta (si no se especifica una etiqueta, el sistema asignar\u00e1 autom\u00e1ticamente una etiqueta a la imagen <code>latest<\/code>).<\/p>\n<pre><code class=\"plaintext\"># \u0421\u0431\u043e\u0440\u043a\u0430 \u043e\u0431\u0440\u0430\u0437\u0430\ndocker build -t &lt;image&gt;:&lt;tag&gt; .<\/code><\/pre>\n<p>\nDespu\u00e9s de ejecutar este comando, puede observar c\u00f3mo Docker lleva a cabo la construcci\u00f3n de la imagen.<\/p>\n<pre><code class=\"plaintext\">Enviando contexto de construcci\u00f3n al demonio de Docker \u00a0 2.88MB\nPaso 1\/9 : FROM node:12-alpine\n\u00a0---&gt; ...ejecuci\u00f3n de las etapas de construcci\u00f3n...\nConstruido con \u00e9xito 123456789123\nEtiquetado correctamente &lt;imagen&gt;:&lt;etiqueta&gt;<\/code><\/pre>\n<p>\nLa construcci\u00f3n puede tardar un par de minutos, depende de cu\u00e1ntas dependencias tenga. Una vez finalizada la construcci\u00f3n, puede ejecutar el comando <code>docker images<\/code> y echar un vistazo a la descripci\u00f3n de su nueva imagen.<\/p>\n<pre><code class=\"plaintext\">REPOSITORIO\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 ETIQUETA \u00a0 \u00a0 \u00a0 ID DE IMAGEN\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 CREADO \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 TAMA\u00d1O\n&lt;imagen&gt; \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00faltima \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 123456789123\u00a0 \u00a0 \u00a0 \u00a0 Hace aproximadamente un minuto \u00a0 x.xxGB<\/code><\/pre>\n<p><\/p>\n<h3>\u258dEjecuci\u00f3n<\/h3>\n<p>\nLa imagen ha sido creada. Esto significa que se puede ejecutar un contenedor basado en ella. Debido a que quiero tener la capacidad de acceder a la aplicaci\u00f3n que est\u00e1 funcionando en el contenedor en la direcci\u00f3n <code>localhost:5000<\/code>, he establecido en la parte izquierda del par <code>5000:5000<\/code> en el siguiente comando. <code>5000<\/code>. En la parte derecha est\u00e1 el puerto del contenedor.<\/p>\n<pre><code class=\"plaintext\"># \u0417\u0430\u043f\u0443\u0441\u043a \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u043e\u0440\u0442\u0430 5000 \u0438 \u043f\u043e\u0440\u0442\u0430 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 5000\ndocker run -p 5000:5000 &lt;image&gt;:&lt;tag&gt;<\/code><\/pre>\n<p>\nAhora, cuando el contenedor ha sido creado y est\u00e1 en ejecuci\u00f3n, puede usar el comando <code>docker ps<\/code> para ver la informaci\u00f3n sobre este contenedor (o puede usar el comando <code>docker ps -a<\/code>, que muestra la informaci\u00f3n sobre todos los contenedores, no solo los que est\u00e1n en funcionamiento).<\/p>\n<pre><code class=\"plaintext\">ID DE CONTENEDOR      IMAGEN               COMANDO                    CREADO              ESTADO                  PUERTOS                    NOMBRES\n987654321234                       \"\/bin\/sh -c 'npm run\u2026\"   hace 6 segundos        En funcionamiento hace 6 segundos    0.0.0.0:5000-&gt;5000\/tcp   stoic_darwin<\/code><\/pre>\n<p>\nSi ahora accede a la direcci\u00f3n <code>localhost:5000<\/code> \u2014 puede ver la p\u00e1gina de la aplicaci\u00f3n en funcionamiento, que se ve exactamente como la p\u00e1gina de la aplicaci\u00f3n funcionando en un entorno de producci\u00f3n.<\/p>\n<h3>\u258dAsignaci\u00f3n de etiqueta y publicaci\u00f3n<\/h3>\n<p>\nPara utilizar una de las im\u00e1genes creadas en el servidor de producci\u00f3n, necesitamos tener la capacidad de cargar esta imagen desde Docker Hub. Esto significa que primero hay que crear un repositorio en Docker Hub para el proyecto. Despu\u00e9s de esto, tendremos un lugar al que podemos enviar la imagen. La imagen debe ser renombrada de tal manera que su nombre comience con nuestro nombre de usuario en Docker Hub. Despu\u00e9s de esto deber\u00eda ir el nombre del repositorio. Al final del nombre puede haber cualquier etiqueta. A continuaci\u00f3n se muestra un ejemplo de la nomenclatura de las im\u00e1genes seg\u00fan este esquema.<\/p>\n<p>Ahora podemos construir la imagen asign\u00e1ndole un nuevo nombre y ejecutar el comando <code>docker push<\/code> para enviarla al repositorio de Docker Hub.<\/p>\n<pre><code class=\"plaintext\">docker build -t \\\/ : .\ndocker tag \\\/ : \\\/ :latest\ndocker push \\\/ :\n\n# En pr\u00e1ctica, esto podr\u00eda verse as\u00ed:\ndocker build -t user\\\/app:v1.0.0 .\ndocker tag user\\\/app:v1.0.0 user\\\/app:latest\ndocker push user\\\/app:v1.0.0<\/code><\/pre>\n<p>\nSi todo va bien, la imagen estar\u00e1 disponible en Docker Hub y ser\u00e1 f\u00e1cil de cargar en el servidor o de pasar a otros desarrolladores.<\/p>\n<h2>Pr\u00f3ximos pasos<\/h2>\n<p>\nHasta este momento, nos hemos asegurado de que la aplicaci\u00f3n, en forma de contenedor Docker, funciona localmente. Hemos cargado el contenedor en Docker Hub. Todo esto significa que ya hemos avanzado bastante hacia nuestro objetivo. Ahora debemos resolver dos preguntas m\u00e1s:<\/p>\n<ul>\n<li>Configuraci\u00f3n de la herramienta CI para pruebas y despliegue del c\u00f3digo.<\/li>\n<li>Configuraci\u00f3n del servidor de producci\u00f3n para que pueda cargar y ejecutar nuestro c\u00f3digo.<\/li>\n<\/ul>\n<p>\nEn nuestro caso, estamos utilizando como soluci\u00f3n CI\/CD <noindex><a rel=\"nofollow\" href=\"https:\/\/travis-ci.org\/\">Travis CI<\/a><\/noindex>. Como servidor \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/www.digitalocean.com\/\">DigitalOcean<\/a><\/noindex>.<\/p>\n<p>Es importante mencionar que aqu\u00ed se puede utilizar otra combinaci\u00f3n de servicios. Por ejemplo, en lugar de Travis CI, se puede utilizar CircleCI o Github Actions. Y en lugar de DigitalOcean, AWS o Linode.<\/p>\n<p>Decidimos trabajar con Travis CI, y en este servicio ya tengo algunas configuraciones hechas. As\u00ed que ahora brevemente contar\u00e9 c\u00f3mo prepararlo para que funcione.<\/p>\n<h2>Travis CI<\/h2>\n<p>\nTravis CI es una herramienta para pruebas y despliegue de c\u00f3digo. No me gustar\u00eda entrar en los detalles de la configuraci\u00f3n de Travis CI, ya que cada proyecto es \u00fanico y esto no ofrecer\u00eda un beneficio particular. Pero hablar\u00e9 sobre lo b\u00e1sico que te permitir\u00e1 comenzar a trabajar en caso de que decidas utilizar Travis CI. Independientemente de lo que elijas \u2014 Travis CI, CircleCI, Jenkins, o algo m\u00e1s \u2014 se aplicar\u00e1n m\u00e9todos de configuraci\u00f3n similares.<\/p>\n<p>Para empezar a trabajar con Travis CI, dir\u00edgete a <noindex><a rel=\"nofollow\" href=\"https:\/\/travis-ci.org\/\">el sitio del proyecto<\/a><\/noindex> y crea una cuenta. Luego, integra Travis CI con tu cuenta de GitHub. Durante la configuraci\u00f3n del sistema, deber\u00e1s especificar el repositorio con el que deseas automatizar el trabajo y habilitar el acceso a \u00e9l. (Utilizo GitHub, pero estoy segura de que Travis CI tambi\u00e9n puede integrarse con BitBucket, GitLab y otros servicios similares).<\/p>\n<p>Cada vez que Travis CI comienza a trabajar, se inicia un servidor que ejecuta los comandos especificados en el archivo de configuraci\u00f3n, incluyendo el despliegue de las ramas correspondientes del repositorio.<\/p>\n<h3>\u258dCiclo de vida del trabajo<\/h3>\n<p>\nEl archivo de configuraci\u00f3n de Travis CI, llamado <code>.travis.yml<\/code> y que se almacena en el directorio ra\u00edz del proyecto, soporta la concepci\u00f3n de eventos <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.travis-ci.com\/user\/job-lifecycle\/\">de las distribuciones RHEL 6 y 7, y para RHEL 8 se generar\u00e1n actualizaciones de paquetes en Application Stream hasta junio de 2024.<\/a><\/noindex> del trabajo. Estos son los eventos, presentados en el orden en que ocurren:<\/p>\n<ul>\n<li><code>apt addons<\/code><\/li>\n<li><code>cache components<\/code><\/li>\n<li><code>before_install<\/code><\/li>\n<li><code>install<\/code><\/li>\n<li><code>before_script<\/code><\/li>\n<li><code>script<\/code><\/li>\n<li><code>before_cache<\/code><\/li>\n<li><code>after_success o after_failure<\/code><\/li>\n<li><code>before_deploy<\/code><\/li>\n<li><code>deploy<\/code><\/li>\n<li><code>after_deploy<\/code><\/li>\n<li><code>after_script<\/code><\/li>\n<\/ul>\n<p><\/p>\n<h3>\u258dPruebas<\/h3>\n<p>\nEn el archivo de configuraci\u00f3n, voy a configurar un servidor local de Travis CI. He elegido Node en versi\u00f3n 12 y le he indicado al sistema que debe instalar las dependencias necesarias para usar Docker.<\/p>\n<p>Todo lo que se enumera en <code>.travis.yml<\/code>, se ejecutar\u00e1 para cada pull request a todas las ramas del repositorio, a menos que se indique lo contrario. Esta es una caracter\u00edstica \u00fatil, ya que significa que podemos probar todo el c\u00f3digo que entra al repositorio. Esto permite saber si el c\u00f3digo est\u00e1 listo para ser escrito en la rama <code>master<\/code>, y si no romper\u00e1 el proceso de construcci\u00f3n del proyecto. En esta configuraci\u00f3n global, instalo todo localmente, inicio el servidor de desarrollo de Webpack en segundo plano (esta es una caracter\u00edstica de mi flujo de trabajo) y ejecuto las pruebas.<\/p>\n<p>Si deseas que se muestren insignias en tu repositorio con informaci\u00f3n sobre la cobertura de c\u00f3digo, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.taniarascia.com\/display-build-status-and-test-coverage\/\">aqu\u00ed<\/a><\/noindex> puedes encontrar una breve gu\u00eda sobre c\u00f3mo usar Jest, Travis CI y Coveralls para recopilar y mostrar esta informaci\u00f3n.<\/p>\n<p>As\u00ed que aqu\u00ed est\u00e1 el contenido del archivo <code>.travis.yml<\/code>:<\/p>\n<pre><code class=\"plaintext\"># \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u044f\u0437\u044b\u043a\nlanguage: node_js\n\n# \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0432\u0435\u0440\u0441\u0438\u044e Node.js\nnode_js:\n\u00a0\u00a0- '12'\n\nservices:\n\u00a0\u00a0# \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443 Docker\n\u00a0\u00a0- docker\n\ninstall:\n\u00a0\u00a0# \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432\n\u00a0\u00a0- npm ci\n\nbefore_script:\n\u00a0\u00a0# \u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 \u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432\n\u00a0\u00a0- npm run dev &amp;\n\nscript:\n\u00a0\u00a0# \u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0442\u0435\u0441\u0442\u044b\n\u00a0\u00a0- npm run test<\/code><\/pre>\n<p>\nAqu\u00ed terminan las acciones que se llevan a cabo para todas las ramas del repositorio y para los pull requests.<\/p>\n<h3>\u258dDespliegue<\/h3>\n<p>\nPartiendo de la suposici\u00f3n de que todas las pruebas automatizadas se completaron con \u00e9xito, podemos, aunque no es necesario, desplegar el c\u00f3digo en el servidor de producci\u00f3n. Dado que queremos hacer esto solo para el c\u00f3digo de la rama <code>master<\/code>, le damos a la sistema las instrucciones adecuadas en la configuraci\u00f3n de implementaci\u00f3n. Antes de que intentes usar en tu proyecto el c\u00f3digo que revisaremos a continuaci\u00f3n, me gustar\u00eda advertirte que debes tener un script real que se llame para la implementaci\u00f3n.<\/p>\n<pre><code class=\"plaintext\">deploy:\n  # Compilar el contenedor Docker y enviarlo a Docker Hub\n  provider: script\n  script: bash deploy.sh\n  on:\n    branch: master<\/code><\/pre>\n<p>\nEl script de implementaci\u00f3n cumple dos funciones:<\/p>\n<ul>\n<li>Compilaci\u00f3n, etiquetado y env\u00edo de la imagen a Docker Hub utilizando herramientas de CI (en nuestro caso, se trata de Travis CI).<\/li>\n<li>Descarga de la imagen en el servidor, detenci\u00f3n del contenedor antiguo y lanzamiento del nuevo (en nuestro caso, el servidor funciona en la plataforma DigitalOcean).<\/li>\n<\/ul>\n<p>\nPrimero, es necesario configurar el proceso autom\u00e1tico de compilaci\u00f3n, etiquetado y env\u00edo de la imagen a Docker Hub. Todo esto es muy similar a lo que ya hac\u00edamos manualmente, excepto que aqu\u00ed necesitamos una estrategia para asignar etiquetas \u00fanicas a las im\u00e1genes y automatizar el inicio de sesi\u00f3n. Tuve dificultades con algunos detalles del script de implementaci\u00f3n, como la estrategia de etiquetado, el inicio de sesi\u00f3n, la codificaci\u00f3n de las claves SSH y el establecimiento de la conexi\u00f3n SSH. Pero, afortunadamente, mi novio maneja muy bien bash, al igual que muchas otras cosas. \u00c9l me ayud\u00f3 a escribir este script.<\/p>\n<p>Entonces, la primera parte del script es enviar la imagen a Docker Hub. Hacer esto es bastante simple. El esquema de etiquetado que utilic\u00e9 implica combinar el hash de git y el tag de git, si existe. Esto permite garantizar la creaci\u00f3n de una etiqueta \u00fanica y simplifica la identificaci\u00f3n de la compilaci\u00f3n de la que se basa. <code>DOCKER_USERNAME<\/code> y <code>DOCKER_PASSWORD<\/code> son variables de entorno personalizadas que se pueden establecer a trav\u00e9s de la interfaz de Travis CI. Travis CI manejar\u00e1 autom\u00e1ticamente los datos secretos para que no caigan en manos ajenas.<\/p>\n<p>Aqu\u00ed est\u00e1 la primera parte del script <code>deploy.sh<\/code>.<\/p>\n<pre><code class=\"plaintext\">#!\/bin\/sh\nset -e # \u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u043a\u0440\u0438\u043f\u0442 \u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 \u043e\u0448\u0438\u0431\u043e\u043a\n\nIMAGE=&quot;&lt;username&gt;\/&lt;repository&gt;&quot; \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # \u041e\u0431\u0440\u0430\u0437 Docker\nGIT_VERSION=$(git describe --always --abbrev --tags --long) # Git-\u0445\u044d\u0448 \u0438 \u0442\u0435\u0433\u0438\n\n# \u0421\u0431\u043e\u0440\u043a\u0430 \u0438 \u0442\u0435\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0431\u0440\u0430\u0437\u0430\ndocker build -t ${IMAGE}:${GIT_VERSION} .\ndocker tag ${IMAGE}:${GIT_VERSION} ${IMAGE}:latest\n\n# \u0412\u0445\u043e\u0434 \u0432 Docker Hub \u0438 \u0432\u044b\u0433\u0440\u0443\u0437\u043a\u0430 \u043e\u0431\u0440\u0430\u0437\u0430\necho &quot;${DOCKER_PASSWORD}&quot; | docker login -u &quot;${DOCKER_USERNAME}&quot; --password-stdin\ndocker push ${IMAGE}:${GIT_VERSION}<\/code><\/pre>\n<p>\nQu\u00e9 ser\u00e1 la segunda parte del script depende completamente de qu\u00e9 hosting est\u00e9s utilizando y c\u00f3mo est\u00e1 organizada la conexi\u00f3n. En mi caso, ya que uso Digital Ocean, se utilizan los comandos para conectarse al servidor <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/digitalocean\/doctl\">doctl<\/a><\/noindex>. Al trabajar con Aws, se utilizar\u00e1 la utiler\u00eda <code>aws<\/code>, y as\u00ed sucesivamente.<\/p>\n<p>No fue especialmente dif\u00edcil configurar el funcionamiento del servidor. As\u00ed, configur\u00e9 un droplet basado en una imagen b\u00e1sica. Cabe destacar que el sistema que eleg\u00ed requiere una instalaci\u00f3n manual de Docker una sola vez y un inicio manual de Docker una sola vez. Para instalar Docker, utilic\u00e9 Ubuntu 18.04, por lo que si tambi\u00e9n utilizas Ubuntu para hacer lo mismo, puedes simplemente seguir <noindex><a rel=\"nofollow\" href=\"https:\/\/phoenixnap.com\/kb\/how-to-install-docker-on-ubuntu-18-04\">este<\/a><\/noindex> una gu\u00eda sencilla.<\/p>\n<p>No estoy hablando aqu\u00ed de comandos espec\u00edficos para el servicio, ya que este aspecto puede variar mucho entre diferentes casos. Solo proporcionar\u00e9 un plan de acci\u00f3n general que se lleva a cabo despu\u00e9s de conectarse por SSH al servidor donde se desplegar\u00e1 el proyecto:<\/p>\n<ul>\n<li>Hay que encontrar el contenedor que se est\u00e1 ejecutando actualmente y detenerlo.<\/li>\n<li>Luego, se debe iniciar un nuevo contenedor en segundo plano.<\/li>\n<li>Necesitar\u00e1s configurar el puerto local del servidor en el valor <code>80<\/code> \u2014 lo que permitir\u00e1 acceder al sitio web en una direcci\u00f3n como <code>example.com<\/code>, sin necesidad de especificar el puerto, en lugar de usar una direcci\u00f3n como <code>example.com:5000<\/code>.<\/li>\n<li>Y, por \u00faltimo, hay que eliminar todos los contenedores y las im\u00e1genes antiguas.<\/li>\n<\/ul>\n<p>\nAqu\u00ed est\u00e1 la continuaci\u00f3n del script.<\/p>\n<pre><code class=\"plaintext\"># \u041d\u0430\u0439\u0442\u0438 ID \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0435\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430\nCONTAINER_ID=$(docker ps | grep takenote | cut -d&quot; &quot; -f1)\n\n# \u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0442\u0430\u0440\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440, \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0439, \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0443\ndocker stop ${CONTAINER_ID}\ndocker run --restart unless-stopped -d -p 80:5000 ${IMAGE}:${GIT_VERSION}\ndocker system prune -a -f<\/code><\/pre>\n<p><\/p>\n<h2>Algunas cosas a tener en cuenta<\/h2>\n<p>\nEs posible que, cuando te conectes al servidor por SSH desde Travis CI, veas una advertencia que impedir\u00e1 continuar con la instalaci\u00f3n, ya que el sistema esperar\u00e1 una respuesta del usuario.<\/p>\n<pre><code class=\"plaintext\">La autenticidad del host '&lt;hostname&gt; (&lt;IP address&gt;)' no puede ser establecida.\nLa huella digital de la clave RSA es &lt;key fingerprint&gt;.\n\u00bfEst\u00e1s seguro de que quieres continuar conect\u00e1ndote (s\u00ed\/no)?<\/code><\/pre>\n<p>\nDescubr\u00ed que la clave en texto plano se puede codificar en base64 para poder guardarla en un formato que se pueda manejar de manera conveniente y segura. En la etapa de instalaci\u00f3n, se puede decodificar la clave p\u00fablica y escribirla en el archivo <code>known_hosts<\/code> para poder deshacerse del error mencionado anteriormente.<\/p>\n<pre><code class=\"plaintext\">echo &lt;public key&gt; | base64 # genera &lt;clave p\u00fablica codificada en base64&gt;<\/code><\/pre>\n<p>\nEn la pr\u00e1ctica, este comando puede verse as\u00ed:<\/p>\n<pre><code class=\"plaintext\">echo \"123.45.67.89 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom\/BWDSU\nGPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3\nPbv7kOdJ\/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK\/7XA\nt3FaoJoAsncM1Q9x5+3V0Ww68\/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw\/Pb0rwert\/En\nmZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z\/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbx\nNrRFi9wrf+M7Q== you@example.com\" | base64<\/code><\/pre>\n<p>\nY as\u00ed es como se ve lo que genera: una cadena en codificaci\u00f3n base64:<\/p>\n<pre><code class=\"plaintext\">123.45.67.89 ssh-rsa AABAB3NzaC1yc2EAAAAABQiD0AAQ...0M1zWWMz0XNoM2FRQl93AHksNVU3Zjg2eUInTHZ0aVERcGg2SHNhcG5yHzdaJTEJPVdqL0lMQWVpMkFGQmU1OENjdTdmSC9mN2NDOUk1b2N5dlhZNDNNRGFnXkxvcmZUbnpjbHRZYXdSZXMtYV9lYXBjaVZKcExNT0FwaGV4aHE3ZGBlemJKVi82NCtVY1hFZ3Y5QmRjWDNOa2pjN0FOVTlNSUV0b2RWcUVzdzk9Y2Qw...9Cgo==<\/code><\/pre>\n<p>\nAqu\u00ed est\u00e1 el comando mencionado anteriormente<\/p>\n<pre><code class=\"plaintext\">install:\n\u00a0\u00a0- echo  | base64 -d &gt;&gt; $HOME\/.ssh\/known_hosts<\/code><\/pre>\n<p>\nEl mismo enfoque se puede usar con la clave privada al establecer una conexi\u00f3n, ya que es posible que necesites la clave privada para acceder al servidor. Al trabajar con la clave, solo necesitas asegurarte de que se almacene de forma segura como una variable de entorno en Travis CI y que no se imprima en ning\u00fan lugar.<\/p>\n<p>Otra cosa a tener en cuenta es que es posible que necesites ejecutar todo el script de despliegue presentado como una sola l\u00ednea, por ejemplo, utilizando <code>doctl<\/code>. Esto puede requerir algunos esfuerzos adicionales.<\/p>\n<pre><code class=\"plaintext\">doctl compute ssh  --ssh-command \"todas las instrucciones estar\u00e1n aqu\u00ed &amp;&amp; aqu\u00ed\"<\/code><\/pre>\n<p><\/p>\n<h2>TLS\/SSL y balanceo de carga<\/h2>\n<p>\nDespu\u00e9s de hacer todo lo mencionado anteriormente, el \u00faltimo problema que enfrent\u00e9 fue que el servidor no ten\u00eda SSL. Dado que estoy usando un servidor Node.js, para forzar <noindex><a rel=\"nofollow\" href=\"https:\/\/www.digitalocean.com\/community\/tutorials\/how-to-secure-a-containerized-node-js-application-with-nginx-let-s-encrypt-and-docker-compose\">funcionar<\/a><\/noindex> un proxy inverso Nginx y Let's Encrypt, es necesario hacer un buen trabajo.<\/p>\n<p>No quer\u00eda hacer toda esta configuraci\u00f3n de SSL manualmente, as\u00ed que simplemente cre\u00e9 un balanceador de carga y registr\u00e9 la informaci\u00f3n en DNS. En el caso de DigitalOcean, por ejemplo, crear un certificado autofirmado auto-renovable en el balanceador de carga es un procedimiento simple, gratuito y r\u00e1pido. Este enfoque tiene la ventaja adicional de que, si es necesario, permite configurar SSL muy f\u00e1cilmente en m\u00faltiples servidores que trabajan detr\u00e1s del balanceador de carga. Esto permite que los servidores no tengan que preocuparse por SSL, pero a\u00fan as\u00ed utilicen, como de costumbre, el puerto <code>80<\/code>. As\u00ed que configurar SSL en el balanceador de carga es mucho m\u00e1s f\u00e1cil y conveniente que los m\u00e9todos alternativos de configuraci\u00f3n de SSL.<\/p>\n<p>Ahora se pueden cerrar todos los puertos en el servidor que aceptan conexiones entrantes, excepto el puerto <code>80<\/code>, utilizado para comunicarse con el balanceador de carga, y el puerto <code>22<\/code> para SSH. Como resultado, intentar acceder directamente al servidor a trav\u00e9s de cualquier puerto, excepto estos dos, fallar\u00e1.<\/p>\n<h2>Resultados<\/h2>\n<p>\nDespu\u00e9s de haber hecho todo lo que mencion\u00e9 en este material, ya no ten\u00eda miedo ni de la plataforma Docker ni de las concepciones de las cadenas automatizadas de CI\/CD. Pude establecer una cadena de integraci\u00f3n continua, durante la cual se realiza la prueba del c\u00f3digo antes de que llegue a producci\u00f3n y el despliegue autom\u00e1tico del c\u00f3digo en el servidor. Todo esto es todav\u00eda relativamente nuevo para m\u00ed, y estoy segura de que hay formas de mejorar mi flujo de trabajo automatizado y hacerlo m\u00e1s eficiente. As\u00ed que si tienes ideas al respecto, h\u00e1zmelo saber. <noindex><a rel=\"nofollow\" href=\"mailto:hello@taniarascia.com\">yo<\/a><\/noindex> Espero que este art\u00edculo te haya ayudado en tus asuntos. Quiero creer que al leerlo, aprendiste tanto como yo mientras investigaba todo lo que aqu\u00ed se menciona.<\/p>\n<p><b>P.D.<\/b> En nuestro <noindex><a rel=\"nofollow\" href=\"https:\/\/ruvds.com\/ru-rub\/marketplace#order\">en el marketplace<\/a><\/noindex> hay una imagen <noindex><a rel=\"nofollow\" href=\"https:\/\/ruvds.com\/ru-rub\/marketplace\/docker#advantages\">Docker<\/a><\/noindex>, que se instala con un solo clic. Puedes comprobar el funcionamiento de los contenedores en <noindex><a rel=\"nofollow\" href=\"https:\/\/ruvds.com\/\">VPS<\/a><\/noindex>. Todos los nuevos clientes tienen 3 d\u00edas de prueba gratuita.<\/p>\n<p><b>\u00a1Estimados lectores!<\/b> \u00bfUtilizas tecnolog\u00edas de CI\/CD en tus proyectos?<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/ruvds.com\/ru-rub\/#order\"><img decoding=\"async\" alt=\"Creando una cadena CI\/CD y automatizando el trabajo con Docker\" src=\"\/wp-content\/uploads\/2020\/02\/3fbfa90ea56a3d9b6b89ba945eb94570.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/a><\/noindex><br \/>\n<br \/>Fuente: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/ruvds\/blog\/488668\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0430 \u043c\u043e\u0438 \u043f\u0435\u0440\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b \u0432 \u043a\u043e\u043d\u0446\u0435 90-\u0445. \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u0445 \u0432 \u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e. \u0411\u044b\u043b Apache-\u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0430 \u043a\u0430\u043a\u043e\u043c-\u043d\u0438\u0431\u0443\u0434\u044c \u043e\u0431\u0449\u0435\u043c \u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0435, \u043d\u0430 \u044d\u0442\u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0432\u043e\u0439\u0442\u0438 \u043f\u043e FTP, \u043d\u0430\u043f\u0438\u0441\u0430\u0432 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0435 \u043d\u0435\u0447\u0442\u043e \u0432\u0440\u043e\u0434\u0435 ftp:\/\/ftp.example.com. \u041f\u043e\u0442\u043e\u043c \u043d\u0430\u0434\u043e \u0431\u044b\u043b\u043e \u0432\u0432\u0435\u0441\u0442\u0438 \u0438\u043c\u044f \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0438 \u0432\u044b\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440. \u0414\u0440\u0443\u0433\u0438\u0435 \u0431\u044b\u043b\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0430, \u0432\u0441\u0451 \u0442\u043e\u0433\u0434\u0430 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":69352,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-69351","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0430 \u043c\u043e\u0438 \u043f\u0435\u0440\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b \u0432 \u043a\u043e\u043d\u0446\u0435 90-\u0445. \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u0445 \u0432 \u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"es_ES\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 CI\/CD-\u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Docker | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0430 \u043c\u043e\u0438 \u043f\u0435\u0440\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b \u0432 \u043a\u043e\u043d\u0446\u0435 90-\u0445. \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u0445 \u0432 \u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-02-18T11:44:06+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-03-03T13:15:00+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Creaci\u00f3n de una cadena de CI\/CD y automatizaci\u00f3n del trabajo con Docker | ProHoster","description":"Escrib\u00ed mis primeros sitios a finales de los 90. Entonces, ponerlos en funcionamiento era muy sencillo.","canonical_url":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"es_ES","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 CI\/CD-\u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Docker | ProHoster","og:description":"\u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0430 \u043c\u043e\u0438 \u043f\u0435\u0440\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b \u0432 \u043a\u043e\u043d\u0446\u0435 90-\u0445. \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u0445 \u0432 \u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e.","og:url":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-02-18T11:44:06+00:00","article:modified_time":"2020-03-03T13:15:00+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"69351","title":null,"description":null,"keywords":null,"keyphrases":{"focus":[],"additional":[]},"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 19:24:45","updated":"2026-08-11 12:50:24","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/69351","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/comments?post=69351"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/69351\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media\/69352"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media?parent=69351"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/categories?post=69351"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/tags?post=69351"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}