Skip to main content

4.12 Integración continua

Es muy común que usemos un sistema de control de versiones para mantener los cambios de nuestros proyectos y sus releases de una forma estable y predecible.

Las imágenes de Docker no son una excepción a esta regla, lo que a su vez nos brinda la oportunidad de hacer uso de los diversos sistemas de integración continua para automatizar muchas de nuestras tareas como por ejemplo la publicación de imágenes en registros públicos o privados, revisión automática de las dependencias, etc...

Las posibilidades son infinitas, a su vez nos permite una gestión más óptima de los tokens, api keys y secretos que manejamos a nivel de la organización, evitando así la necesidad de compartir tokens con personas individuales, facilitando así la rotación de los mismos.

Veamos unos ejemplos

En esta ocasión usaremos Github Actions, pero fácilmente podría portarse a otros sistemas de CI sin mucho esfuerzo.

Imaginemos que tenemos un simple "Hola mundo" en un fichero ./src/server.js y su correspondiente package.json con las dependencias y típicos scripts de Npm (start, test, lint...)

const express = require('express')

const app = express()

const PORT = 8080;
const HOST = '0.0.0.0';

app.get('/', (req, res) => {
res.send('Hola mundo!')
})

app.listen(PORT, HOST () => {
console.log('Listening on port ${PORT}}...')
})

Luego hacemos un Dockerfile siguiendo las buenas prácticas (multi-stage, gestión de privilegios, crear una imagen final liviana, metadatos...)

# Fase 1: dependencies

FROM node:14.15.5 as dependencies
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm i --only=production

# Fase 2: Imagen final
FROM node:14.15.5-alpine

# Distribution details.
ARG BUILD_DATE
ARG VCS_REF
ARG BUILD_VERSION

# Labels.
LABEL org.label-schema.schema-version="1.0"
LABEL org.label-schema.build-date=$BUILD_DATE
LABEL org.label-schema.name="ulisesgascon/hello-world-sample"
LABEL org.label-schema.description="Simple Hello World Sample in Nodejs with Express"
LABEL org.label-schema.url="https://github.com/ulisesgascon/hello-world-sample"
LABEL org.label-schema.vcs-url="https://github.com/ulisesgascon/hello-world-sample"
LABEL org.label-schema.vcs-ref=$VCS_REF
LABEL org.label-schema.vendor="Ulises Gascon"
LABEL org.label-schema.version=$BUILD_VERSION
LABEL org.label-schema.docker.cmd="docker run -p 8080:8080 -d ulisesgascon/hello-world-sample"

WORKDIR /app
RUN chown -R node:node /app
USER node
COPY --from=dependencies /usr/src/app/node_modules ./node_modules
COPY package*.json ./
COPY /src ./src
EXPOSE 8080
CMD [ "npm", "start" ]

Revisar los cambios

Un flujo bastante común se produciría se creará una nueva pull request. Disparando un workflow que valide específicamente que nuestro Dockerfile está pasando un linter como Hadolint y que es capaz de hacer una build sin romperse (incluyendo metadatos).

name: Check Dockerfile
on: [pull_request]

jobs:
check-dockerfile:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2

- name: Get current time
uses: josStorer/get-current-[email protected]
id: current-time

- name: Linting with hadolint
uses: reviewdog/action-hadolint@v1
with:
reporter: github-pr-review

- name: Build the Docker image
env:
RELEASE_VERSION: ${{ steps.vars.outputs.tag }}
BUILD_DATE: ${{ steps.current-time.outputs.time }}
RELEASE_VERSION: "PR${{github.event.number}}"
run: |
docker build .
--build-arg BUILD_DATE=${{ env.BUILD_DATE }}
--build-arg VCS_REF=${{ github.sha }}
--build-arg BUILD_VERSION=${{ env.RELEASE_VERSION }}
--tag ulisesgascon/hello-world-sample:$RELEASE_VERSION
--tag ulisesgascon/hello-world-sample:latest

Publicar en un registro público

Con unas pocas modificaciones sobre el código anterior podemos hacer la publicación de la imagen de manera pública en Docker Hub cuando creamos una release en el proyecto.

name: Publish Docker Image
on: [pull_request]

jobs:
check-dockerfile:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2

- name: Set RELEASE_VERSION
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV

- name: Get current time
uses: josStorer/get-current-[email protected]
id: current-time

- name: Linting with hadolint
uses: reviewdog/action-hadolint@v1
with:
reporter: github-pr-review

- name: Build the Docker image
env:
RELEASE_VERSION: ${{ steps.vars.outputs.tag }}
BUILD_DATE: "${{ steps.current-time.outputs.time }}"
run: |
docker build .
--build-arg BUILD_DATE=${{ env.BUILD_DATE }}
--build-arg VCS_REF=${{ github.sha }}
--build-arg BUILD_VERSION=${{ env.RELEASE_VERSION }}
--tag ulisesgascon/hello-world-sample:$RELEASE_VERSION
--tag ulisesgascon/hello-world-sample:latest

- name: Docker Hub Login
env:
DOCKER_USER: ${{secrets.DOCKER_USER}}
DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}}
run: |
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD

- name: Docker Hub Publish
run: docker push --all-tags ulisesgascon/hello-world-sample
Información

Debemos incluir los secretos que solicitamos en la configuración del repositorio de Github (DOCKER_USER y DOCKER_PASSWORD para Docker Hub)