Sample deployment CD
Esse é um exemplo de como utilizar o módulo de CD do Harness
Para essa tarefa é necessário um cluster K8S local. Recomendo criar um cluster usando o Kind.
Confira se temos o cluster pronto pra 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 o Delegate no Cluster
Primeiramente devemos para instalar o delegate no seu cluster local. Ele é quem fará o trabalho de interagir em nome do Harness na sua infra estrutura.
Neste exemplo podemos escolher se vamos instalar via manifesto ou helm. Vamos pro Helm para testar e vamos instalar o Pipeline ao invés do GitOps. É necessário ter o helm instalado também.
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.
Seguindo os passos vamos adicionar o repo no helm.
helm repo add harness-delegate https://app.harness.io/storage/harness-download/delegate-helm-chart/
helm repo update harness-delegate
# Neste ponto estamos já instalando passando as configurações corretas para o values.
# É necessário que sua accountId e delegateToken sejam alteradas.
# Será criado o namespace harness-deleagte-ng para deploy dos 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./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 baixar o harness cli e deixar disponível no sistema já vinculado a conta.
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
# mova para algum lugar no path do seu sistema para encontrar o binário
sudo mv harness /usr/local/bin
# ou siga como a
export PATH="$(pwd):$PATH"
echo 'export PATH="'$(pwd)':$PATH"' >> ~/.bash_profile
# confere
harness --version
v0.0.25-Preview
Na sua conta principal gere um token
Vamos configurar a cli para utilizar este token
harness login --api-key SuaApiKey --account-id SuaAccountID
Welcome to Harness CLI!
Login successfully done. Yay!
Applicação Exemplo
Faça um fork do projeto exemplo do Harness para a sua conta do github ou gitlab e baixe para a sua máquina.
# Use sua conta
git clone https://github.com/davidpuziol/harnesscd-example-apps
cd harnesscd-example-apps
O comando abaixo irá criar uma secret para o projeto default pois não passamos o nome do projeto
# Antes crie um api token no github ou gitlab
harness secret apply --token SeuToken --secret-name "harness_gitpat"
Poderíamos fazer isso via Interface gráfica, mas usamos a cli para fazer esta demonstração.
Agora vamos criar um connector.
Só pra gente entender o comando que será aplicado, o yaml esta disponível disponível em https://github.com/harness-community/harnesscd-example-apps/blob/master/guestbook/harnesscd-pipeline/github-connector.yml com o seguinte conteúdo.
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 mudando passando o --git-user
authentication:
type: Http
spec:
type: UsernameToken
spec:
username: GITHUB_USERNAME # Estamos mudando no passando o --git-user
tokenRef: harness_gitpat # Já criamos com a secret
apiAccess:
type: Token
spec:
tokenRef: harness_gitpat # Já criamos com a secret
executeOnDelegate: false
type: Repo
O connector esta fazendo uma integração com um repositório git.
Estamos usando um template e alterando o usuário, mas aproveitamos a secret com o mesmo nome. Um detalhe importante é que vários comandos abaixo usarão o apply para criar.
harness connector --file helm-guestbook/harnesscd-pipeline/github-connector.yml apply --git-user davidpuziol
Agora vamos fazer um connector com o cluster e para isso vamos usar o delegate que deployamos no cluster. Podemos vê-lo usando a inteface gráfica. Ele apareceu depois que o nosso deletage deployado no cluster enviou as informações para o projeto. Se não observou passamos o nome utilizando --set delegateName=helm-delegate
Da mesma forma estamos usando um template com o conteúdo abaixo e alterando o DELEGATE-NAME com a opção --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
Agora vamos criar um service representando a nossa aplicação. Este não é um service do kubernetes heim! Neste caso o service é de fato um dos itens do projeto.
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 # Esta me dando a conta https://github.com/davidpuziol
folderPath: /helm-guestbook # Dentro do repo queremos esta pasta
repoName: harnesscd-example-apps # Dentro da conta temos queremos este repo
branch: master # Na branch master
subChartPath: ""
valuesPaths: # Já que é o um helm, temos o values que esta no repositório
- helm-guestbook/values.yaml
skipResourceVersioning: false
enableDeclarativeRollback: false
helmVersion: V3
gitOpsEnabled: false
harness service --file helm-guestbook/harnesscd-pipeline/k8s-service.yml apply
Agora vamos criar um ambiente para esta aplicação. Em um ambiente observe que é possível definir variáveis.
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
Agora vamos criar a infra estrutura da aplicação no ambiente harnessdevenvdep
. O conteúdo do arquivo aplicado seria este.
infrastructureDefinition:
name: harnessk8sinfradep
identifier: harnessk8sinfradep
description: ""
tags: {}
orgIdentifier: default # Nesta org
projectIdentifier: default_project # Neste Projeto
environmentRef: harnessdevenvdep # É o mesmo identificado que demos ao ambiente
deploymentType: Kubernetes
type: KubernetesDirect
spec:
# Estamos dizendo que a infra sera no kubernetes com o connector que criamos anteriormente
connectorRef: harnessk8sconnector
namespace: default # Namespace que vamos utilizar no kubernetes
releaseName: r<+INFRA_KEY>
allowSimultaneousDeployments: false
harness infrastructure --file helm-guestbook/harnesscd-pipeline/k8s-infrastructure-definition.yml apply
Observe que a infra estrutura é uma configuração do ambiente.
Agora que já temos muitas configurações vamos ao nosso pipeline de fato. O exemplo é de um pipeline canary.
harness pipeline --file helm-guestbook/harnesscd-pipeline/k8s-canary-pipeline.yml apply
O que temos aqui é.
pipeline:
name: guestbook_canary_pipeline
identifier: guestbook_canary_pipeline
projectIdentifier: default_project
orgIdentifier: default
tags: {}
stages: # Esperando uma lista
- stage: # Se tivessemos um stage a mais outro bloco desse seria 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: # Observe que mesmo com só um step ele pertence a um 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
Tudo isso gerou para nós este pipeline. Obseve que só temos um stage e dentro dele um step group que possuem dois steps.
Agora podemos rodar o pipeline apertando o run e acompanhar visualmente o que irá acontecer.
Vamos conferir no nosso cluster no namespace default o que temos
❯ 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