Ejemplo CD
Este es un ejemplo de cómo utilizar el módulo de CD de Harness.
Para esta tarea es necesario un cluster K8S local. Recomiendo crear un cluster usando Kind.
Comprueba si tenemos el cluster listo para uso.
k get nodes
NAME STATUS ROLES AGE VERSION
personal-cluster-control-plane Ready control-plane 35d v1.30.0
personal-cluster-worker Ready <none> 35d v1.30.0
personal-cluster-worker2 Ready <none> 35d v1.30.0
personal-cluster-worker3 Ready <none> 35d v1.30.0
Instalando el Delegate en el Cluster
Primeramente debemos instalar el delegate en tu cluster local. Él es quien hará el trabajo de interactuar en nombre de Harness en tu infraestructura.
En este ejemplo podemos elegir si vamos a instalar vía manifiesto o helm. Vamos con Helm para probar y vamos a instalar Pipeline en lugar de GitOps. Es necesario tener helm instalado también.

bash <(curl -fsSL https://raw.githubusercontent.com/harness-community/scripts/main/delegate-preflight-checks/cluster-preflight-checks.sh)
Some commands may require sudo. Enter your password if prompted.
Checking for cluster.
Cluster connection successful.
Required memory is 2048 MiB
Available memory is 127282 MiB
Required cpu is 1000 m
Available cpu is 96000 m
Cluster has enough resources available for Harness delegate.
Siguiendo los pasos vamos a añadir el repo en helm.
helm repo add harness-delegate https://app.harness.io/storage/harness-download/delegate-helm-chart/
helm repo update harness-delegate
# En este punto estamos ya instalando pasando las configuraciones correctas para el values.
# Es necesario que tu accountId y delegateToken sean modificados.
# Será creado el namespace harness-delegate-ng para deploy de los pods.
helm upgrade -i helm-delegate --namespace harness-delegate-ng --create-namespace \
harness-delegate/harness-delegate-ng \
--set delegateName=helm-delegate \
--set accountId=YYYYYYYYYYYYYYYYYYYYYYYYYY \
--set delegateToken=XXXXXXXXXXXXXXXXXXXXXXXXX \
--set managerEndpoint=https://app.harness.io \
--set delegateDocker/docs/pipeline/harness/pics/Image=harness/delegate:24.07.83605 \
--set replicas=1 --set upgrader.enabled=true
kubectl get all -n harness-delegate-ng
NAME READY STATUS RESTARTS AGE
pod/helm-delegate-6f7cf79b8c-sxfsz 1/1 Running 0 3m24s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/helm-delegate 1/1 1 0 3m24s
NAME DESIRED CURRENT READY AGE
replicaset.apps/helm-delegate-6f7cf79b8c 1 1 1 3m24s
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
cronjob.batch/helm-delegate-upgrader-job 0 */1 * * * <none> False 0 <none> 3m24s
kubectl get secrets -n harness-delegate-ng
NAME TYPE DATA AGE
helm-delegate Opaque 1 4m4s
helm-delegate-upgrader-token Opaque 1 4m4s
sh.helm.release.v1.helm-delegate.v1 helm.sh/release.v1 1 4m4s
kubectl get sa -n harness-delegate-ng
NAME SECRETS AGE
default 0 4m30s
helm-delegate 0 4m30s
helm-delegate-upgrader-cronjob-sa 0 4m30s
Harness CLI
Vamos a descargar el harness cli y dejarlo disponible en el sistema ya vinculado a la cuenta.
curl -LO https://github.com/harness/harness-cli/releases/download/v0.0.25-Preview/harness-v0.0.25-Preview-linux-amd64.tar.gz
tar -xvf harness-v0.0.25-Preview-linux-amd64.tar.gz
# Mueve a algún lugar en el path de tu sistema para encontrar el binario
sudo mv harness /usr/local/bin
# O sigue así
export PATH="$(pwd):$PATH"
echo 'export PATH="'$(pwd)':$PATH"' >> ~/.bash_profile
# Comprueba
harness --version
v0.0.25-Preview
En tu cuenta principal genera un token.

Vamos a configurar la cli para utilizar este token.
harness login --api-key TuApiKey --account-id TuAccountID
Welcome to Harness CLI!
Login successfully done. Yay!
Aplicación Ejemplo
Haz un fork del proyecto ejemplo de Harness para tu cuenta de github o gitlab y descárgalo a tu máquina.
# Usa tu cuenta
git clone https://github.com/davidpuziol/harnesscd-example-apps
cd harnesscd-example-apps
El comando abajo creará un secreto para el proyecto default pues no pasamos el nombre del proyecto.
# Antes crea un api token en github o gitlab
harness secret apply --token TuToken --secret-name "harness_gitpat"
Podríamos hacer esto vía interfaz gráfica, pero usamos la cli para hacer esta demostración.

Ahora vamos a crear un connector.
Solo para que entendamos el comando que será aplicado, el yaml está disponible en https://github.com/harness-community/harnesscd-example-apps/blob/master/guestbook/harnesscd-pipeline/github-connector.yml con el siguiente contenido.
connector:
name: harness_gitconnector
identifier: harnessgitconnector
description: ""
orgIdentifier: default
projectIdentifier: default_project
type: Github
spec:
url: https://github.com/GITHUB_USERNAME/harnesscd-example-apps # Estamos cambiando pasando el --git-user
authentication:
type: Http
spec:
type: UsernameToken
spec:
username: GITHUB_USERNAME # Estamos cambiando pasando el --git-user
tokenRef: harness_gitpat # Ya creamos con el secreto
apiAccess:
type: Token
spec:
tokenRef: harness_gitpat # Ya creamos con el secreto
executeOnDelegate: false
type: Repo
El connector está haciendo una integración con un repositorio git.
Estamos usando una plantilla y modificando el usuario, pero aprovechamos el secreto con el mismo nombre. Un detalle importante es que varios comandos abajo usarán apply para crear.
harness connector --file helm-guestbook/harnesscd-pipeline/github-connector.yml apply --git-user davidpuziol
Ahora vamos a hacer un connector con el cluster y para eso vamos a usar el delegate que desplegamos en el cluster. Podemos verlo usando la interfaz gráfica. Apareció después que nuestro delegate desplegado en el cluster envió las informaciones al proyecto. Si no observaste, pasamos el nombre utilizando --set delegateName=helm-delegate.

De la misma forma estamos usando una plantilla con el contenido abajo y modificando DELEGATE-NAME con la opción --delegate-name.
connector:
name: harness_k8sconnector
identifier: harnessk8sconnector
description: ""
orgIdentifier: default
projectIdentifier: default_project
type: K8sCluster
spec:
credential:
type: InheritFromDelegate
delegateSelectors:
- DELEGATE_NAME
harness connector --file helm-guestbook/harnesscd-pipeline/kubernetes-connector.yml apply --delegate-name helm-delegate
Ahora vamos a crear un service representando nuestra aplicación. ¡Este no es un service de kubernetes! En este caso el service es de hecho uno de los elementos del proyecto.
service:
name: harnessguestbookdep
identifier: harnessguestbookdep
orgIdentifier: default
projectIdentifier: default_project
serviceDefinition:
type: Kubernetes
spec:
manifests:
- manifest:
identifier: guestbook
type: HelmChart
spec:
store:
type: Github
spec:
connectorRef: harnessgitconnector # Esto me da la cuenta https://github.com/davidpuziol
folderPath: /helm-guestbook # Dentro del repo queremos esta carpeta
repoName: harnesscd-example-apps # Dentro de la cuenta queremos este repo
branch: master # En la branch master
subChartPath: ""
valuesPaths: # Ya que es un helm, tenemos el values que está en el repositorio
- helm-guestbook/values.yaml
skipResourceVersioning: false
enableDeclarativeRollback: false
helmVersion: V3
gitOpsEnabled: false
harness service --file helm-guestbook/harnesscd-pipeline/k8s-service.yml apply

Ahora vamos a crear un entorno para esta aplicación. En un entorno observa que es posible definir variables.
environment:
name: harnessdevenvdep
identifier: harnessdevenvdep
tags: {}
type: PreProduction
orgIdentifier: default
projectIdentifier: default_project
variables: []
harness environment --file helm-guestbook/harnesscd-pipeline/k8s-environment.yml apply

Ahora vamos a crear la infraestructura de la aplicación en el entorno harnessdevenvdep. El contenido del archivo aplicado sería este.
infrastructureDefinition:
name: harnessk8sinfradep
identifier: harnessk8sinfradep
description: ""
tags: {}
orgIdentifier: default # En esta org
projectIdentifier: default_project # En este Proyecto
environmentRef: harnessdevenvdep # Es el mismo identificador que dimos al entorno
deploymentType: Kubernetes
type: KubernetesDirect
spec:
# Estamos diciendo que la infra será en kubernetes con el connector que creamos anteriormente
connectorRef: harnessk8sconnector
namespace: default # Namespace que vamos a utilizar en kubernetes
releaseName: r<+INFRA_KEY>
allowSimultaneousDeployments: false
harness infrastructure --file helm-guestbook/harnesscd-pipeline/k8s-infrastructure-definition.yml apply
Observa que la infraestructura es una configuración del entorno.

Ahora que ya tenemos muchas configuraciones vamos a nuestro pipeline de hecho. El ejemplo es de un pipeline canary.
harness pipeline --file helm-guestbook/harnesscd-pipeline/k8s-canary-pipeline.yml apply
Lo que tenemos aquí es:
pipeline:
name: guestbook_canary_pipeline
identifier: guestbook_canary_pipeline
projectIdentifier: default_project
orgIdentifier: default
tags: {}
stages: # Esperando una lista
- stage: # Si tuviéramos un stage más otro bloque de este sería esperado
name: deploy-guestbook
identifier: deployguestbook
description: ""
type: Deployment
spec:
deploymentType: Kubernetes
service:
serviceRef: harnessguestbookdep
environment:
environmentRef: harnessdevenvdep
deployToAll: false
infrastructureDefinitions:
- identifier: harnessk8sinfradep
execution:
steps:
- stepGroup:
name: Canary Deployment
identifier: canaryDepoyment
steps:
- step:
name: Canary Deployment
identifier: canaryDeployment
type: K8sCanaryDeploy
timeout: 10m
spec:
instanceSelection:
type: Count
spec:
count: 1
skipDryRun: false
- step:
name: Canary Delete
identifier: canaryDelete
type: K8sCanaryDelete
timeout: 10m
spec: {}
- stepGroup: # Observa que incluso con solo un step pertenece a un grupo
name: Primary Deployment
identifier: primaryDepoyment
steps:
- step:
name: Rolling Deployment
identifier: rollingDeployment
type: K8sRollingDeploy
timeout: 10m
spec:
skipDryRun: false
rollbackSteps:
- step:
name: Canary Delete
identifier: rollbackCanaryDelete
type: K8sCanaryDelete
timeout: 10m
spec: {}
- step:
name: Rolling Rollback
identifier: rollingRollback
type: K8sRollingRollback
timeout: 10m
spec: {}
tags: {}
failureStrategies:
- onFailure:
errors:
- AllErrors
action:
type: StageRollback
Todo esto generó para nosotros este pipeline. Observa que solo tenemos un stage y dentro de él un step group que poseen dos steps.

Ahora podemos ejecutar el pipeline presionando run y acompañar visualmente lo que va a suceder.

Vamos a comprobar en nuestro cluster en el namespace default lo que tenemos.
❯ kubectl get all -n default
NAME READY STATUS RESTARTS AGE
pod/r4dd1fb76a0cc028e2d4a0a1335b6fd2ce2832291-helm-guestbook-5984x5 1/1 Running 0 4m57s
pod/r4dd1fb76a0cc028e2d4a0a1335b6fd2ce2832291-helm-guestbook-5bf7n4 1/1 Running 0 4m57s
pod/r4dd1fb76a0cc028e2d4a0a1335b6fd2ce2832291-helm-guestbook-5phw6r 1/1 Running 0 4m57s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 35d
service/r4dd1fb76a0cc028e2d4a0a1335b6fd2ce2832291-helm-guestbook ClusterIP 10.106.192.32 <none> 80/TCP 5m31s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/r4dd1fb76a0cc028e2d4a0a1335b6fd2ce2832291-helm-guestbook 3/3 3 3 4m57s
NAME DESIRED CURRENT READY AGE
replicaset.apps/r4dd1fb76a0cc028e2d4a0a1335b6fd2ce2832291-helm-guestbook-56665c4b4b 3 3 3 4m57s