Integracion Continua y Entrega Continua (CI/CD)#
CI es integrar contantemente el cambio en el codigo, usando scripts para builds y pruebas sobre los cambios.
CD es la practica de crear y desplegar codigo, generalmente en un ambiente de prueba, asi se puede pasar a produccion en cualquier momento.
Se puede hacer despliegue cada vez que se integra, o se puede tener CI haciendo tests en otras ramas y pull_requests, y al hacer el merge a master comienza CD.
GitHub Actions#
Permite tratar pipeline CI como codigo. Solo se necesita un archivo .yaml para la definicion del workflow en un directorio llamado “.github/workflows”.
No importa el nombre que se le de a los archivos porque cada archivo describe cuando se debe ejecutar. El «cuando» es el evento que debe ocurrir para ejecutar el job.
Una gran ventaja de GitHub Actions sobre otras herramientas CI como Circle CI, Travis CI y Jenkins, es que basta con crear el directorio de workflows y agregar los .yaml, no es necesario configurar el workflow en una pagina externa.
Conceptos#
Un Workflow es una series de procesos automaticos que se ejecutan por GitHub Actions. En un repositorio pueden haber multiples workflows (ej: 1 para CI, 1 para CD, 1 para Seguridad). Cada workflow se compone de:
![digraph conceptos {
"Event" [shape=box]
"Jobs" [shape=box]
"Runner" [shape=ellipse]
"Steps" [shape=box]
"Actions" [shape=box]
{ rank=same "Event" "Jobs" "Runner" "Steps" "Actions"}
"Event" -> "Jobs" -> "Runner" -> "Steps" -> "Actions";
}](_images/graphviz-351b4916daf38d1b5edb6968e66deee08fd93251.png)
- Event
Evento que dice cuando debe ejecutarse un workflow, por ejemplo push, pull_request, release, fork, delete. Ver GitHub Actions Events.
- Jobs
Es un conjunto de pasos en un workflow que se ejecutan en un mismo “Runner”. En un workflow hay 1 o mas “Jobs”. Cada “Job” puede contener 1 o mas “Steps”. Por defecto los “Jobs” se ejecutan en paralelo, menos cuando se especifica dependencia (con la keyword “needs”).
- Runner
Servidor donde se ejecutan los “Jobs”.
- Steps
Es un conjunto de shell commands o acciones. Aquellos que son parte de un mismo “Job” se ejecutan en el mimsmo “Runner”, por lo que pueden compartir informacion entre ellos. Los pasos se ejecutan en orden y se puede tener un paso para compilar, otro para aplicar formato, otro para los tests, otro que crea un contenedor de Docker.
- Actions
Es el nivel mas bajo en un workflow. Ejecutan una sola tarea.
Configurar#
name: Example workflow
on: # Para indicar el evento
push:
branches: ['master']
jobs:
build: # Nombre del Job
runs-on: ubuntu-latest # Indica runner
steps: # Especifica pasos que se ejecutan en orden
- name: checkout
uses: actions/checkout@v3
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Python Black Check
uses: rodrigogiraoserrao/python-black-check@v2.0
with:
line-length: '88'
exclude: '/(\.git|\.hg|\.mypy_cache|\.pytest_cache|\.tox|\.venv|_build|build|dist)/'
Referencia entre workflows#
name: Dependent workflow
on:
workflow_run:
workflows: ['Example workflow']
branches: ['master']
types:
- completed
Si se especifican multiples workflows, solo uno necesita lograr el estado indicado en types. Si se necesita que el workflow que llego al estado indicado, lo haya hecho con exito, es necesario controlar el estado en el job.
jobs:
on-success:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- run: echo 'The triggering workflow passed'
on-failure:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
steps:
- run: echo 'The triggering workflow failed'
GitHub Pages#
name: GitHub Pages
on:
push:
branches: [ "main"]
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Setup Graphviz
uses: ts-graphviz/setup-graphviz@v1
with:
ubuntu-skip-apt-update: true
- name: Sphinx build
run: |
sphinx-build -b html docs/source/ docs/build/html
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
if: ${{ github.ref == 'refs/heads/main' }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs/build/html
force_orphan: true
Runner propio#
Un self-hosted runner se puede agregar a un repositorio, una organizacion o una empresa. Como ejemplo, para agregar un runner a un repositorio se va a Settings/Actions/Runners. Alli estaran las instrucciones para instalar la aplicacion de GitHub Actions para configurar un servidor como Runner. Tambien se indica como agregarlo al workflow. Todos los Runners propios tienen labels que permiten seleccionarlo en el .yaml.
runs-on: [self-hosted, linux, ARM64]
Costos#
Product |
Storage |
Minutes (per month) |
|---|---|---|
GitHub Free |
500 MB |
2,000 |
GitHub Pro |
1 GB |
3,000 |
GitHub Free for organizations |
500 MB |
2,000 |
GitHub Team |
2 GB |
3,000 |
GitHub Enterprise Cloud |
50 GB |
50,000 |
Para mas detalles de costos y tiempos, ver About billing for GitHub Actions.
Diferencia con GitLab CI/CD#
GitLab CI se define en un archivo .gitlab-ci.yaml En gitlab se define un pipeline. Un pipeline se compone de Jobs independientes que ejecutan scripts. Cada Job se es parte de un Stage. La ejecucion de stages es secuencial, y la ejecucion de jobs en un stage es en paralelo.
![digraph conceptosgitlab {
"Pipeline" [shape=box]
"Stage" [shape=box]
"Jobs" [shape=box]
"Script" [shape=box]
{ rank=same "Pipeline" "Stage" "Jobs" "Script"}
"Pipeline" -> "Stage" -> "Jobs" -> "Script";
}](_images/graphviz-01b61242858f510ed4131842e5c7b1102ca2c9c9.png)
El orden de lo items en stages define el orden de ejecucion.
stages: # List of stages for jobs, and their order of execution
- build
- test
- deploy
build-job: # This job runs in the build stage, which runs first.
stage: build
script:
- echo "Compiling the code..."
- echo "Compile complete."
unit-test-job: # This job runs in the test stage.
stage: test # It only starts when the job in the build stage completes successfully.
script:
- echo "Running unit tests... This will take about 60 seconds."
- sleep 60
- echo "Code coverage is 90%"
lint-test-job: # This job also runs in the test stage.
stage: test # It can run at the same time as unit-test-job (in parallel).
script:
- echo "Linting code... This will take about 10 seconds."
- sleep 10
- echo "No lint issues found."
deploy-job: # This job runs in the deploy stage.
stage: deploy # It only runs when *both* jobs in the test stage complete successfully.
environment: production
script:
- echo "Deploying application..."
- echo "Application successfully deployed."
Tekton#
Tekton es un framework open source para crear pipelines CI/CD. Se tiene control total de la secuencia de ejecucion, y se pueden ejecutar trabajos en serie o en parelelo. Funciona en cualquier lugar donde se pueda ejecutar un cluster de Kubernetes.
Puede crear rápidamente sistemas de CI/CD que sean escalables, serverless y cloud native. Tekton se ejecuta de forma nativa en un cluster de Kubernetes, lo que elimina la necesidad de una solución de CI/CD independiente.
Conceptos#
![digraph conceptostekton {
"Event" [shape=box]
"Trigger" [shape=box]
"Pipeline" [shape=box]
"Task" [shape=box]
"Steps" [shape=box]
{ rank=same "Event" "Trigger" "Pipeline" "Task" "Steps"}
"Event" -> "Trigger" -> "Pipeline" -> "Task" -> "Steps";
}](_images/graphviz-eb793bbba5def93a964b220cde15a1a6739cd184.png)
- Event
Evento externo que dice cuando debe “dispararse un trigger”, por ejemplo push, pull_request.
- Trigger
El estimulo que comienza un “pipeline run”.
- Pipeline
Es un conjunto de tareas a ejecutar. No hay cantidad limite de tareas que se puede tener en un pipeline y pueden ejecutarse en paralelo o en serie. Por defecto se ejecutan en paralelo, para la ejecucion en serie se deben indicar dependencias.
- Task
Es la unidad que comprende uno o mas pasos. En una “Task” se pueden definir parametros que seran entregados a los trabajos, tambien se pueden especificar el workspace necesario para almacenar artefactos.
- Steps
Son los comandos ejecutados para llevar a cabo las “Tasks”. Generalmente son shell scripts que ejecutan comandos para build, test y deploy de aplicaciones. Se ejecutan en la secuencia indicada.
Conceptos en concreto#
Tekton funciona con Kubernetes CRD, en orden de ejecucion los CRD son los siguentes:
- EventListener:
Un CRD que escucha por eventos de un repositorio.
- TriggerBinding
Captura datos del evento y se lo asigna o vincula a las propiedades en el pipeline.
- TriggerTemplate
Toma los parametros de de TriggerBinding y los asocia con PipelineRun.
- PipelineRun
Cuando se gatilla por un evento, TiggerTemplate crea un PipelineRun, pasando los parametros del evento necesarios para la ejecucion. Un PipelineRun se puede crear manualmente, sin usar eventos. PipelineRun es lo que crea una pipeline. Es responsable de las tareas, para eso crea un TaskRun para cada tarea.
- TaskRun
Crea un pod Kubernetes para que en el se ejecute la tarea. Todos los pasos de una tarea se ejecutan en el mismo pod. Se crea un contenedor para cada paso.
- PersistentVolumeClaim
Almacenamiento para artefactos que pueden ser compartidos en la pipeline. Por esto se puede hacer check-out en una tarea, correr unit tesk en otra, linting en otra, crear una imagen en otra, etc.
Nota
Todo en Tekton es nativo de Kubernetes. Todo se ejecuta en un cluster de Kubernetes sin necesidad de servidores CI/CD externos.
Configurar#
Definir Tasks y Steps#
Un archivo de especificacion de una tarea se define de la siguiente manera:
Para cada step se debe especificar una imagen sobre la cual se ejecutara.
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: checkout
spec:
params:
- name: repo-url
description: Repo URL
type: string
steps:
- name: checkout
image: bitname/git:latest
command: [git]
args: ["clone", "$(params.repo-url)"]
Un ejemplo de checkout task.
Definir Pipeline#
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: pipeline
spec:
params:
- name: repo-url
tasks:
- name: clone
taskRef:
name: checkout
params:
- name: repo-url
value: "$(params.repo-url)"
Ejecutar Pipeline#
El segundo valor pipeline es el nombre de pipeline especificado en el archivo.
El parametro -p es para pasar los parametros que se especifican en el archivo de pipeline.
$ kubectl apply -f tasks.yaml
$ kubectl apply -f pipeline.yaml
$ tkn pipeline start pipeline --showlog -p repo-url="https://github.com/..."
Definir Triggers#
Usan EventListener, TriggerBinding y TriggerTemplate.
apiVersion: triggers.tekton.dev/
kind: EventListener
metadata:
name: cd-listener
spec:
serviceAccountName: pipeline
triggers:
- binding:
name: cd-binding
template:
name: cd-template
apiVersion: triggers.tekton.dev/
kind: TriggerBinding
metadata:
name: cd-binding # Igual al nombre en EventListener
spec:
params:
- name: repository
value: "$(body.repository.url)"
- name: branch
value: "$(body.ref)"
apiVersion: triggers.tekton.dev/
kind: TriggerTemplate
metadata:
name: cd-template # Igual al nombre en EventListener
spec:
params:
- name: repository
description: GIT repo URL
default: ""
- name: branch
description: Branch to process
default: "master"
resoursetemplates: # Contiene un recurso PipelineRun
- apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: cd-pipeline-run # con generateName se especifica un id unico
spec:
serviceAccountName: pipeline # service account que corre la pipeline
pipelineRef: # referencia a la pipeline que se quiere correr
name: cd-pipeline # asumiendo que hay una pipeline llamada asi
params: # parametros que cd-pipeline indico recibir en params
- name: repo-url # nombre que la cd-pipeline especifica
value: $(tt.params.repository) # este viene de la seccion params de TriggerTemplate
- name: branch
value: $(tt.params.branch)