# Creando nuestro CI/CD con Github Actions y Okteto.

Muchas veces me consultan sobre pipelines, Docker o Kubernetes. Siempre he creído que el Hands On es la mejor forma de aprender, es por ello que vamos hacer una pequeña aplicación con Python & Flask, dockerizarla y luego hacer el deployment en un cluster Kubernetes. Vamos a utilizar servicios gratuitos como GitHub Actions, para la creacion de nuestro Pipeline & Okteto, para soportar nuestro deployment de Kubernetes.

[Aca](https://github.com/safernandez666/Okteto) encontraran el proyecto y todo lo necesario para la implementación.

Lo primero que haremos será crear la imagen, de Docker, que subiremos a Docker Hub para que sea pulleada por nuestro deployment de Kubernetes. Es necesario que ingresemos las credenciales para poder hacer el push a Docker Hub y el Config File del Cluster de Kubernetes. Cada vez que actualicemos nuestro proyecto, en GitHub, correra nuestro YAML en GitHub Actions, que realizara todos estos pasos en nuestro Pipeline.

## Creación de la Imagen Docker

Vamos a revisar los archivos que son necesarios, para la creación de nuestra imagen.

```bash
.
├── Dockerfile
├── app.py
├── static
│   └── style.css
└── template
    └── index.html

2 directories, 5 files
```

Vamos a crear la imagen, con **docker build -t flask –no-cache=true .**, para probarla localmente. Para ello es necesario el **Dockerfile**, que tenemos en nuestro proyecto.

```bash
 /tmp/Okteto  main !8  docker build -t flask --no-cache=true .   ok  4s
[+] Building 19.7s (12/12) FINISHED
 => [internal] load .dockerignore                                   0.1s
 => => transferring context: 2B                                     0.0s
 => [internal] load build definition from Dockerfile                0.1s
 => => transferring dockerfile: 496B                                0.0s
 => [internal] load metadata for docker.io/library/python:alpine    2.3s
 => [1/7] FROM docker.io/library/python:alpine@sha256:4228f7566ffd  0.0s
 => [internal] load build context                                   0.1s
 => => transferring context: 816B                                   0.0s
 => CACHED [2/7] WORKDIR /usr/src/app                               0.0s
 => [3/7] RUN pip install --upgrade pip                             8.1s
 => [4/7] RUN pip install --no-cache-dir flask flask-api ifaddr     8.6s
 => [5/7] COPY app.py ./                                            0.0s
 => [6/7] ADD static ./static                                       0.0s
 => [7/7] ADD template ./template                                   0.1s
 => exporting to image                                              0.4s
 => => exporting layers                                             0.4s
 => => writing image sha256:0af8866c65c1eff632e6ed2a3ad9e932344000  0.0s
 => => naming to docker.io/library/flask                            0.0s
 /tmp/Okteto  main !8
```

## Corremos el Docker

Revisamos si tenemos la imagen creada, con docker images, y luego corremos los comando para crear el contenedor en base dicha imagen con **docker run --publish 80:80 flask** .

```bash
 /tmp/Okteto  main !8  docker images                                                                        ok
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
flask               latest              0af8866c65c1        About a minute ago   56.8MB
 /tmp/Okteto  main !8  docker run  --publish 80:80 flask                                                                    ok
2a4609ac3706
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 294-411-388
```

Ahora podemos probar si esta corriendo nuestro contenedor en http://localhost

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707410008336/18f87184-22ef-40a7-a3e0-c9bb6e824c7b.png align="center")

## Creación del YAML para que corra nuestro Pipeline

Podemos crear la carpeta .github/workflows/NOMBRE.YAML o hacerlo desde el proyecto, en GitHub.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707410031504/1d64917d-78c2-4622-9deb-03d2af1c1535.jpeg align="center")

Vamos a ver la primer parte donde realizamos el build & push a Dockerhub. En mi caso lo llame **push.yaml**. Van a encontrarlo en la ruta que comente anteriormente.

```yaml
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - name: Publish to Registry
      uses: elgohr/Publish-Docker-Github-Action@master
      with:
        name: safernandez666/okteto
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
        dockerfile: ./Dockerfile
        tags: "latest,${{ github.sha }}"
```

Sera necesario agregar los secretos, para conectarnos a Dockerhub y poder realizar el Push de nuestra imagen creada. Como pueden ver le agrego un tag que representa este build en particular y latest.

## Agregamos los Secretos

Para agregar las variables de enterno lo haremos en Settings | Secrets.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707410052791/bf5e4cc0-1677-4572-af2e-93ccb94919e7.jpeg align="center")

Como ven no solo agregue **DOCKER\_USERNAME** & **DOCKER\_PASSWORD** para conectarme a Dockerhub, si no que tambien agregue el archivo de configuracion **KUBE\_CONFIG\_DATA** de Okteto, para obtener el valor de esa variable tenemos que descargar el archivo de configuración.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707410069482/bfee5bc7-78dc-4b5e-8d80-29e7d595bb26.png align="center")

Obtenemos el variable para cargarla en GitHub.

```bash
cat okteto-kube.config | base64
```

Ahora si, tenemos casi todo listo. Vamos a revisar el push.yaml, completo, que realiza todos los pasos del pipeline.

## YAML GitHub Actions

```yaml
name: Publish To Docker & Okteto
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - name: Publish to Registry
      uses: elgohr/Publish-Docker-Github-Action@master
      with:
        name: safernandez666/okteto
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
        dockerfile: ./Dockerfile
        tags: "latest,${{ github.sha }}"

    - name: Deploy to Cluster
      uses: steebchen/kubectl@master
      env:
        KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA }}
      with:
        args: set image --record deployment/nginx-deployment nginx=safernandez666/okteto:${{ github.sha }}
    
    - name: Verify Deployment
      uses: steebchen/kubectl@master
      env:
        KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA }}
        KUBECTL_VERSION: "1.15"
      with:
        args: '"rollout status deployment/nginx-deployment"'
```

En este YAML vemos como hacemos el build & push y como se hace el deployment de kubernetes. El argumento mas importante es **set image --record deployment/nginx-deployment nginx=safernandez666/okteto:${{ github.sha }}** donde en cada push se refenciara la imagen creada. El primer deployment es necesario hacerlo a mano, para que se cree en Okteto. Podriamos crear la logica para consultar si esta creado y en el caso de no estarlo crearlo, pero para los fines practicos no suma. ¡Vamos a crearlo!

## Configuración del Contexto

```bash
export KUBECONFIG=$HOME/Downloads/okteto-kube.config:${KUBECONFIG:-$HOME/.kube/config}

kubectl apply -f ./deployment/deployment.yaml
```

Vamos a revisar el archivo **deployment.yaml** donde tenemos del deploy y el servicio. Es muy sencillo, no les va costar comprenderlo, donde creamos 3 replicas de nuestra imagen y sera balanceada.

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: safernandez666/okteto:latest 
        ports:
        - containerPort: 80
        imagePullPolicy: "Always"
---

apiVersion: v1
kind: Service
metadata:
  name: nginx-deployment
spec:
  type: LoadBalancer 
  ports:
    - name: http
      port: 80
  selector:
    app: nginx
```

## Push a GitHub

Vamos a modificar algo del index.html, para probar, y hacer el push necesario para que corra nuestro pipeline.

```bash
git add .
git commit -m "Flask App"
git push origin main
```

Ya tenemos nuestro pipeline corriendo.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707410108710/9db8fd3e-356a-4f97-a58e-1d024b2dd58b.png align="center")

De esta manera ya tenemos nuestra imagen en Docker Hub, que luego sera pulleada por el deployment. Vamos a revisar en Okteto el nombre de nuestro Endpoint.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707410121604/e2280885-1700-4d3f-a8bc-f9912fd380b1.jpeg align="center")

Podemos visitar el Endpoint y probar el balanceo a nuestros Pods. Aca les muestro como responden, via curl. Como resultado vamos a obtener en el HTML los Id de los Dockers.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707410179616/aa487948-0487-4a6b-bebe-aa85966ff40e.jpeg align="center")

Espero que les haya gustado y les sirva para crear su CI / CD, completo, gratis. ¡A disposición por cualquier consulta! En la proxima vamos a agregarle seguridad a nuestro Pipeline. Por ejemplo agregar una revision de la imager docker que hemos generado.
