Skip to main content

Desplegar servicios

En muchas ocasiones vamos a necesitar desplegar nuestros propios proyectos, posiblemente a través de un proceso CI/CD. Esta sección es para esos casos.

Según las herramientas que utilicemos, la forma de desplegar será diferente. En este artículo se utilizan estos:

  • El ejemplo se basa en el despliegue de esta web, así que los ficheros reales se pueden encontrar en el repositorio. Existen ligeras diferencias entre los explicados aquí.
  • GitLab Runner es el sistema utilizado para el CI/CD, con lo que el fichero .gitlab-ci.yaml dispone los detalles sobre el proceso.
  • Helm es el sistema utilizado para el despliegue. Desde el CI/CD se generan los valores acorde a los requisitos por el clúster.
  • El proyecto de GitLab está enlazado a un GitLab Runner desplegado en la misma red que el clúster, esto le permite conectar remotamente a partir de un kubeconfig. No se utilizan agentes externos.
  • Las variables de CI/CD se configuran a nivel del proyecto en GitLab. Estos no son visibles en el proyecto, así que son explicados en esta documentación.
  • Dentro del proceso de CI/CD se encuentra la compilación, creación del contenedor, publicación de este en Nexus Repository y su despliegue. Esta documentación únicamente explica el proceso de despliegue en Kubernetes. Si se desea explorar el resto del proceso, es preferible mirar el fichero de gitlab-ci directamente.
  • El clúster está configurado acorde al K8s Project y utiliza el Nexus Repository como registro de contenedores por defecto, así que no requiere indicar el dominio donde encontrar el contenedor. El CI/CD en cambio sí lo necesita ya que GitLab Runner está desplegado sobre un Docker y este busca automáticamente en hub.docker.com.
compte

Para entender todo el procedimiento, se debe tener unos conocimientos mínimos de CI/CD y del sistema creado por GitLab.

Variables

tip

El sistema utilizado tiene dos ramas que se relacionan acorde al entorno: PROD para la rama master y TEST para la rama develop. Ya que los valores son diferentes según el entorno, estos se duplican y el proceso de CI/CD escoge la correcta según desde que rama se ejecuta el proceso.

El proceso necesita los datos de conexión con el clúster y los datos finales como la URL acorde al entorno.

VariableDescripciónEjemplo
KUBECONFIG_PRODKubeConfig con un usuario con acceso a desplegar el servicio en producción-
KUBECONFIG_TESTIgual que el anterior, pero con acceso a test-
PUBLISH_DOMAIN_PRODDominio utilizado en produccióndomain.cat
PUBLISH_DOMAIN_TESTDominio utilizado en testdomain.intranet
PUBLISH_NAMENombre del subdominio donde se desplegaráwww
INTRANETtrue si el servicio se despliega en intranet o false si va en extranetfalse
NAMESPACENamespace previamente creado y del que se tiene acceso para desplegar el serviciodocumoon

Proceso

El resultado final del proceso de despliegue es el siguiente:

.gitlab-ci.yml
k8s deploy:
image:
name: alpine/helm
entrypoint: [""]
stage: deploy
cache: []
before_script:
- apk add --update --no-cache jq
# Configure kubeconfig
- >
if [[ $CI_COMMIT_REF_NAME == "master" ]]; then
KUBECONFIG=$KUBECONFIG_PROD
else
KUBECONFIG=$KUBECONFIG_TEST
fi
script:
# Retrieve service name
- SERVICE_NAME=$(jq -r ".name" package.json)
# Prepare URL
- >
if [[ $CI_COMMIT_REF_NAME == "master" ]]; then
PUBLISH_DOMAIN=$PUBLISH_DOMAIN_PROD
else
if [ $INTRANET == "true" ]; then
PUBLISH_NAME="$PUBLISH_NAME.test"
else
PUBLISH_NAME="$PUBLISH_NAME-extranet.test"
fi
PUBLISH_DOMAIN=$PUBLISH_DOMAIN_TEST
fi
# Choose ingress classname
- >
if [ $INTRANET == "true" ]; then
INGRESS_CLASSNAME=nginx-intranet
else
INGRESS_CLASSNAME=nginx-extranet
fi
# Prepare values
- |
cat << EOF > helm-values.yaml
ingress:
enabled: true
className: $INGRESS_CLASSNAME
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
host: $PUBLISH_NAME.$PUBLISH_DOMAIN
EOF
# Change to non-stable image
- |
if [[ $CI_COMMIT_REF_NAME != "master" ]]; then
cat << EOF >> helm-values.yaml
image:
pullPolicy: Always
tag: develop
podAnnotations:
commit-sha: $CI_COMMIT_SHA
EOF
fi
# Deploy
- >
helm upgrade --install $SERVICE_NAME ./helm \
-f helm-values.yaml \
--namespace $NAMESPACE \
--kubeconfig $KUBECONFIG
only:
- master
- develop
except:
- schedules
info

Debido al tamaño del proceso, he preferido dividirlo y explicar cada sección por separado.

Imagen

Cada proceso de GitLab Runner inicia un contenedor donde se copia el código fuente y se ejecutan las órdenes. Debido a que se despliega a través de Helm, hace falta utilizar un contenedor diseñado para ello.

La imagen de alpine/helm dispone de lo necesario para este caso, aunque los procesos de CI/CD deben anular el uso por defecto de entrypoint para poder ejecutar ordenes dentro del contenedor.

image: 
name: alpine/helm
entrypoint: [""]

Orden y caché

Cada proceso tiene que estar definido a un stage para que GitLab Runner pueda ejecutarlos en orden y en paralelo cuando sea posible.

Debido a que mi proceso tiene una cache por defecto y el despliegue no la requiere, la deshabilito para que el sistema no pierda tiempo.

stage: deploy
cache: []

Preparar el contenedor

El proceso requiere de dos puntos:

  • La capacidad de leer ficheros JSON para extraer el nombre del servicio (otros servicios pueden requerir XML).
  • Conocer qué KubeConfig utilizar acorde al entorno.

Este punto preinstala y configura las variables requeridas para que las órdenes del proceso funcionen según lo esperado.

before_script:
- apk add --update --no-cache jq
# Configure kubeconfig
- >
if [[ $CI_COMMIT_REF_NAME == "master" ]]; then
KUBECONFIG=$KUBECONFIG_PROD
else
KUBECONFIG=$KUBECONFIG_TEST
fi

Despliegue

Este punto tiene diferentes apartados que son ejecutados en orden:

  1. Obtener el nombre del servicio
  2. Construir la URL que será utilizada por el Ingress
    1. La rama no master añade un test. al dominio
    2. En caso de desplegarse en extranet, la rama no master además añade un -extranet en el dominio
  3. Se define la clase de Ingress acorde a la variable de intranet
  4. Los valores se definen en un fichero de values acorde a Helm para activar y configurar Ingress
  5. Los contenedores en la rama no master utilizan el nombre de la rama como versión del contenedor. Hay que ajustar Helm para apuntar a la versión correcta y obligar a que el pod se vuelva a generar acorde a la nueva imagen.
  6. Despliegue en Helm acorde a los valores creados, el namespace y el KubeConfig
script:
# Retrieve service name
- SERVICE_NAME=$(jq -r ".name" package.json)
# Prepare URL
- >
if [[ $CI_COMMIT_REF_NAME == "master" ]]; then
PUBLISH_DOMAIN=$PUBLISH_DOMAIN_PROD
else
if [ $INTRANET == "true" ]; then
PUBLISH_NAME="$PUBLISH_NAME.test"
else
PUBLISH_NAME="$PUBLISH_NAME-extranet.test"
fi
PUBLISH_DOMAIN=$PUBLISH_DOMAIN_TEST
fi
# Choose ingress classname
- >
if [ $INTRANET == "true" ]; then
INGRESS_CLASSNAME=nginx-intranet
else
INGRESS_CLASSNAME=nginx-extranet
fi
# Prepare values
- |
cat << EOF > helm-values.yaml
ingress:
enabled: true
className: $INGRESS_CLASSNAME
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
host: $PUBLISH_NAME.$PUBLISH_DOMAIN
EOF
# Change to non-stable image
- |
if [[ $CI_COMMIT_REF_NAME != "master" ]]; then
cat << EOF >> helm-values.yaml
image:
pullPolicy: Always
tag: develop
podAnnotations:
commit-sha: $CI_COMMIT_SHA
EOF
fi
# Deploy
- >
helm upgrade --install $SERVICE_NAME ./helm \
-f helm-values.yaml \
--namespace $NAMESPACE \
--kubeconfig $KUBECONFIG

Ramas para el proceso

Tras cualquier commit recibido en el proyecto, se iniciarán todos los procesos. Esto puede limitarse.

En este caso se limita únicamente a dos ramas acorde a su nombre y se anula su ejecución mediante temporizadores.

only:
- master
- develop
except:
- schedules