Despliegue y configuración de infraestructura DevOps para tus proyectos

acoronadoc

Albert Coronado

Posted on April 18, 2024

Despliegue y configuración de infraestructura DevOps para tus proyectos

En este vídeo hemos aprendido a montarnos una pipeline de despliegue DevOps real usando cantidad de tecnologías, algunas de las cuales las hemos instalado: Gitlab, Gitlab CICD, Docker hub, Kubernetes(GKE) sobre Google Cloud Platform(GCP) o Kaniko.

Creación de un cluster sobre Google Cloud Platform(GCP)

Este paso lo hemos realizado desde la consola de GCP en https://console.cloud.google.com/ y es bastante visual, con lo que mejor os basáis en el vídeo.

Obviamente, podríamos haber contratado el cluster a través de cualquier otro proveeros cloud como Amazon o Azure o incluso instalar Kubernetes en nuestros propios servidores con K0s, K3s o similares(Tenemos vídeos y artículos de referencia sobre el tema).

Otra mejora hubiera sido contratar el servicio vía Gcloud o Terraform que nos permitía automatizar este paso. Pero esto ya es otra historia.

Configuración de credenciales de Docker Hub para el despliegue

Para poder desplegar sobre Kubernetes necesitamos un "Registry", que básicamente, se trata de un repositorio donde almacenar las imágenes que vamos a desplegar. En este caso, para no liar mas el vídeo, hemos optado por usar Docker Hub, que nos permite usarlo como servicio SaaS y con su capa gratuita nos vale.

Para ello hemos tenido que configurar unas credenciales y lo hemos hecho vía su interfaz web así que es mas fácil verlo en el vídeo que explicarlo en un artículo.

Me guardo para otro vídeo como instalar alguna alternativa como Nexus para tener nuestro propio registry.

Acceso a Google Cloud Platform(GCP) usando la imagen Docker con Cloud SDK

Para conectarnos al Kubernetes sobre GCP hemos optado por hacerlo usando una imagen de Docker que nos provee el mismo Google. Esta imagen incluye de serie cantidad de utilidades para interactuar con GCP y Kubernetes como son: gcloud(Herramienta CLI para interactuar con GCP), bq(Utilidad para interactuar con los servicios de ingeniería de datos de GCP), gsutils(para subir fichero a GCP), kubectl, Helm, etc.

Hemos arrancado el contenedor con el comando:

docker run --rm -it -v .:/poc google/cloud-sdk:latest 
Enter fullscreen mode Exit fullscreen mode

Este commando básicamente arranca el contenedor habilitando el modo interactivo(parámetros -it), montando la carpeta actual en la carpeta /poc(parámetro -v .:/poc) y haciendo que se autodestruya al salir del contenedor(parámetro --rm).

Una vez dentro nos hemos logueado en GCP y configurado el cliente de kubernetes(kubectl) para que use nuestro cluster:

# Login dentro de GCP
gcloud auth login

# Configuración de kubectl apuntando a nuestro cluster
gcloud container clusters get-credentials --zone "ZONA DEL CLUSTER" --project "PROYECTO DE GCP" "NOMBRE DEL CLUSTER"
Enter fullscreen mode Exit fullscreen mode

Instalación de Gitlab sobre Kubernetes(GKE) sobre GCP

Para instalar Gitlab hemos usado el siguiente manifiesto de kubernetes(gitlab.yaml):

kind: Namespace
apiVersion: v1
metadata:
  name: gitlab
  labels:
    name: gitlab-namespace
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gitlab-deployment
  namespace: gitlab
  labels:
    app: gitlab
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gitlab
  template:
    metadata:
      labels:
        app: gitlab
    spec:
      containers:
      - name: gitlab
        image: gitlab/gitlab-ce:16.7.5-ce.0
        env:
          - name: GITLAB_OMNIBUS_CONFIG
            value: "external_url 'http://gitlab-service'"
          - name: GITLAB_ROOT_PASSWORD
            value: "YoutubePlus!"
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  namespace: gitlab
  name: gitlab-service
spec:
  type: LoadBalancer
  selector:
    app: gitlab
  ports:
      - protocol: TCP
        port: 80
        targetPort: 80      
Enter fullscreen mode Exit fullscreen mode

Hemos usado el siguiente comando para aplicarlo:

Kubectl apply -f gitlab.yaml
Enter fullscreen mode Exit fullscreen mode

Instalación del Runner de Gitlab

Los Runners de Gitlab son los encargado de ejecutar las tareas de las pipelines de Gitlab CICD y los hay de muchos tipos(locales, amazon, Docker, etc). Nosotros hemos optado por instalar una de tipo kubernetes.

Los Runners se instalan a nivel de grupo, por lo que hemos necesitado primero crear un grupo de gitlab y una vez dentro, ir a la pestaña "CI / CD" y "Runners" para obtener el token necesario para instalarlo(Esto lo podríamos haber automatizado usando el API de Gitlab, por si ha alguien le interesa).

Ya con nuestro token(parámetro registration-token) hemos usado el siguiente manifiesto para instalar el runner(gitlab-runner.yaml):

apiVersion: v1
kind: ServiceAccount
metadata:
  name: gitlab-service-account-sa
  namespace: gitlab
  labels:
    app: native-app
imagePullSecrets:
  - name: registry-credentials    
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: gitlab-service-account-cr
  namespace: gitlab
  labels:
    app: native-app
rules:
  - apiGroups:
      - ''
      - apps
      - autoscaling
      - batch
      - build.openshift.io
      - image.openshift.io
      - route.openshift.io
    resources:
      - services
      - pods
      - pods/log
      - pods/exec
      - pods/attach
      - namespaces
      - replicationcontrollers
      - daemonsets.apps
      - deployments
      - replicasets
      - statefulsets
      - horizontalpodautoscalers
      - cronjobs
      - jobs
      - daemonsets
      - buildconfigs
      - builds
      - imagestreams
      - imagestreamtags
      - buildconfigs/instantiatebinary
      - routes
      - secrets
    verbs:
      - create
      - watch
      - get
      - list
      - update
      - patch
      - delete
      - deletecollection
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: gitlab-service-account-rbcr
  namespace: gitlab
roleRef:
  kind: ClusterRole
  name: gitlab-service-account-cr
  apiGroup: rbac.authorization.k8s.io
subjects:
  - kind: ServiceAccount
    name: gitlab-service-account-sa
    namespace: gitlab
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: runner-0
  namespace: gitlab
spec:
  selector:
    matchLabels:
      name: runner-pod-0
  template:
    metadata:
      namespace: gitlab
      labels:
        name: runner-pod-0
    spec:
      serviceAccount: gitlab-service-account-sa
      serviceAccountName: gitlab-service-account-sa
      containers:
      - name: pod
        image: alpinelinux/gitlab-runner:latest
        command: 
          - "/bin/sh"
          - "-c"
          - |           
            gitlab-runner register \
              --kubernetes-privileged true \
              --non-interactive \
              --run-untagged=false \
              --executor kubernetes \
              --kubernetes-pull-policy always \
              --request-concurrency 10 \
              --name 'My Runner' \
              --kubernetes-image alpine:latest \
              --kubernetes-image-pull-secrets registry-credentials \
              --kubernetes-namespace gitlab \
              --kubernetes-service-account gitlab-service-account-sa \
              --tag-list kubernetes \
              --url "http://gitlab-service/" \
              --registration-token "GR1348941zBB3FQSts6S282gMoNNC"

            cat /etc/gitlab-runner/config.toml

            gitlab-runner run
Enter fullscreen mode Exit fullscreen mode

Hemos usado el siguiente comando para aplicarlo:

Kubectl apply -f gitlab-runner.yaml
Enter fullscreen mode Exit fullscreen mode

Creación de nuestro primer repo y programación de nuestra api python

El siguiente paso ha sido crear nuestro repo de Gitlab y clonarlo(Con cuidado con la URL que te autogenera el Gitlab) usando el comando:

git clone http://URL DE NUESTRO REPO
Enter fullscreen mode Exit fullscreen mode

Hemos añadido dos fichero con nuestra aplicación:

requirements.txt(Contiene las dependencias de nuestra aplicación Python)

fastapi>=0.68.0,<0.69.0
uvicorn>=0.15.0,<0.16.0 
Enter fullscreen mode Exit fullscreen mode

main.py(Contiene nuestra aplicación Python)

from fastapi import FastAPI
import uvicorn

app = FastAPI()

@app.get("/")
def root():
    return {"msg": "Hello World"}

uvicorn.run(app, host="0.0.0.0", port=8080)
Enter fullscreen mode Exit fullscreen mode

Dockerfile para generar las imágenes Docker

En nuestro repo también hemos creado un fichero llamado "Dockerfile" que contiene las instrucciones para generar la imagen que vamos a desplegar en Kubernetes:

FROM python:3.9

RUN mkdir /code
WORKDIR /code

ADD requirements.txt /code/requirements.txt
ADD main.py /code/main.py

RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

CMD ["python", "main.py"]
Enter fullscreen mode Exit fullscreen mode

Chart Helm para el despliegue

También nos falta una carpeta llamada helm que va a contener todos los ficheros con nuestro chart para desplegar el repo:

/Helm/Chart.yaml (Fichero que describe nuestro chart)

name: my-super-app
version: 0.0.1
appVersion: "1.0"
description: Mi Super App 
Enter fullscreen mode Exit fullscreen mode

/helm/values.yaml(Valores o variables que configuran nuestro chart)

appname: test1
environment: dev
namespace: test1

image: test1
Enter fullscreen mode Exit fullscreen mode

/helm/templates/deployment.yaml (Template Helm para configurar el despliegue)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ $.Values.appname }}-{{ $.Values.environment }}-deployment
  namespace: {{ $.Values.namespace }}
spec:
  selector:
    matchLabels:
      name: {{ $.Values.appname }}-{{ $.Values.environment }}-pod
  template:
    metadata:
      namespace: {{ $.Values.namespace }}
      labels:
        name: {{ $.Values.appname }}-{{ $.Values.environment }}-pod
    spec:
      containers:
      - name: pod
        image: {{ $.Values.image }}
      imagePullSecrets:
      - name: regcred
Enter fullscreen mode Exit fullscreen mode

/helm/templates/service.yaml (Template Helm para configurar el servicio)

apiVersion: v1
kind: Service
metadata:
  name: {{ $.Values.appname }}-service
  namespace: {{ $.Values.namespace }}
spec:
  type: LoadBalancer
  selector:
    name: {{ $.Values.appname }}-{{ $.Values.environment }}-pod
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080
Enter fullscreen mode Exit fullscreen mode

Configurar el Pipeline de despliegue

Esto es quizás lo mas importante del vídeo, en Gitlab CICD, el pipeline de despliegue viene configurado en un fichero en el propio repo llamado ".gitlab-ci.yml". En nuestro caso nos hemos configurado un pipeline con cuatro stages "version", "tests", "build image" y "deploy":

variables:
  KUBERNETES_NAMESPACE: test1
  DOCKER_REGISTRY_HOST: https://index.docker.io/v1/
  DOCKER_REGISTRY_USER: acoronadoc
  DOCKER_REGISTRY_PASSWORD: dckr_pat_GegpqyANy7NViEmIDjNQATERUmM

default:
  tags:
    - kubernetes
  image: alpine:latest

stages:
  - version
  - test
  - build-image
  - deploy

version:
  stage: version
  script:
    - echo "VERSION=$(date +%Y%m%d%H%M%S%N)-$CI_COMMIT_BRANCH" >> build.env
  artifacts:
    reports:
      dotenv: build.env

test:
  stage: test
  script:
    - echo "Testing..."

buid-image:
  stage: build-image
  image: 
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  script:
    - pwd
    - ls
    - mkdir -p /kaniko/.docker
    - |
      echo "{ \"auths\": { \"$DOCKER_REGISTRY_HOST\": { \"auth\": \"$(echo -n $DOCKER_REGISTRY_USER:$DOCKER_REGISTRY_PASSWORD | base64 -w0)\" } } }" > /kaniko/.docker/config.json
    - cat /kaniko/.docker/config.json
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination=$DOCKER_REGISTRY_USER/$CI_PROJECT_NAME:$VERSION 

deploy:
  stage: deploy
  script:    
    - apk add kubectl helm
    - cd helm
    - kubectl create namespace $KUBERNETES_NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
    - kubectl create secret docker-registry regcred -n $KUBERNETES_NAMESPACE --docker-server=$DOCKER_REGISTRY_HOST --docker-username=$DOCKER_REGISTRY_USER --docker-password=$DOCKER_REGISTRY_PASSWORD --dry-run=client -o yaml | kubectl apply -f -
    - helm upgrade --install $CI_PROJECT_NAME . --set image=$DOCKER_REGISTRY_USER/$CI_PROJECT_NAME:$VERSION
Enter fullscreen mode Exit fullscreen mode

Si lo analizáis bien veréis que tenemos un script por stage, en el vídeo los he comentado.

Subir código y ver como se despliega

Con todo ahora ya solo nos queda añadir todos los ficheros al repo(Cuidado porque el .gitlab-ci.yml se tiene que especificar al ser oculto), hacer el commit y hacer el push para ver los resultados:

git add *
git add .gitlab-ci.yml

git commit -m "Primer commit"

git push
Enter fullscreen mode Exit fullscreen mode

Y el resto ya es historia :D

💖 💪 🙅 🚩
acoronadoc
Albert Coronado

Posted on April 18, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related