Creando nuestro CI/CD con Github Actions y Okteto.
Un pipeline, completo
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 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.
.
├── 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.
/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 .
/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 localhost
Creación del YAML para que corra nuestro Pipeline
Podemos crear la carpeta .github/workflows/NOMBRE.YAML o hacerlo desde el proyecto, en GitHub.
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.
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.
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.
Obtenemos el variable para cargarla en GitHub.
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
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
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.
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.
git add .
git commit -m "Flask App"
git push origin main
Ya tenemos nuestro pipeline corriendo.
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.
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.
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.