BuildShipRun (BSR) to w tym wypadku i w tym kontekście, to :
Przedstawiam 5 podejść do próby realizacji za pośrednictwem argocd, którre zostało zintegrowane z helm-charts:
Oto proste repozytorium helma typu all-in-one – zawiera helm-chart + values:
sam helm-chart to:
├── Chart.yaml
├── templates
│ ├── configmap.yaml
│ ├── deployment.yaml
│ ├── NOTES.txt
│ ├── service.yaml
│ └── tests
│ └── test-connection.yaml
└── values.yaml
Zadanie polega na podziale frameworku opartego o tandem argocd+helm na środowiska DEV, TEST i PROD i zrobienie emulacji BuildShipRun na tymże tandemie (czyli jeden helm-chart, ale wdrażany wielokrotnie, za każdym razem inaczej i bazujący za każdym razem na innych wartościach w helmowym values.yaml)
Trzeba zatem ten sam helm-chart wdrożyć oddzielnie dla DEV, dla TEST i dla PROD
Na potrzeby tego LAB-u będziemy używać jednego GKE i zrobimy podział na Namespaces dev, test i prod – z grubsza odpowiada to normalnemu podziałowi na 3 osobne GKE.
Tzw „różnice między środowiskami DEV, TEST i PROD” będą zaemulowane innymi portami k8s-svc i inną ilością replik (odpowiednio dla DEV : k8s-svc-port=2222 i 2repliki , dla TEST 3333+3repliki i dla PROD 4444+4r)
W rzeczywistych środowiskach różnice są inne i jest ich znacznie więcej – tzn. diff między helmowym values-dev.yaml a values-prod.yaml może liczyć kilkadziesiąt parametrów – tu dla nas nie ma to znaczenia, bo nas interesuje mechanizm a nie zawartość.
Jednym słowem, dążymy do modelu gdzie helm-chart jest jeden ale ma 3 różne metody wdrożenia – czyli upraszczając 3 różne pliki values (odpowiednio dla DEV, dla TEST i dla PROD).
Pierwsze intuicyjne (ale bardzo słabe) rozwiązanie jakie przychodzi do głowy, to oczywiście dodać do repo 3 x plik Values i zdefiniować 3 aplikacje argo.
Rozbijamy zatem w naszym repo values.yaml na 3 różne pliki.
├── Chart.yaml
├── templates
│ ├── configmap.yaml
│ ├── deployment.yaml
│ ├── NOTES.txt
│ ├── service.yaml
│ └── tests
│ └── test-connection.yaml
├── values-dev.yaml
├── values-prod.yaml
└── values-test.yaml
te values-X.yaml różnią się ilością replik i portami dla k8s-svc:
$ cat values-dev.yaml | grep -E "replicaCount|servicePort|namespace"
replicaCount: 2
namespace: dev
servicePort : 2222
$ cat values-test.yaml | grep -E "replicaCount|servicePort|namespace"
replicaCount: 3
namespace: test
servicePort : 3333
$ cat values-prod.yaml | grep -E "replicaCount|servicePort|namespace"
replicaCount: 4
namespace: prod
servicePort : 4444
startujemy z czystym argo i dodajemy repo + 3 x argo-app:
argocd@argocd-server-5fff657769-fhml5:~$ argocd repo list
TYPE NAME REPO INSECURE OCI LFS CREDS STATUS MESSAGE PROJECT
argocd@argocd-server-5fff657769-fhml5:~$ argocd app list
NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS REPO PATH TARGET
argocd@argocd-server-5fff657769-fhml5:~$ argocd repo add https://github.com/slawekgh/argo-helm
Repository 'https://github.com/slawekgh/argo-helm' added
argocd@argocd-server-5fff657769-fhml5:~$ argocd app create argo-helm-dev --repo https://github.com/slawekgh/argo-helm --path test-chart --dest-namespace dev --dest-server https://kubernetes.default.svc --auto-prune --sync-policy automated --release-name test-release --values values-dev.yaml
application 'argo-helm-dev' created
argocd@argocd-server-5fff657769-fhml5:~$ argocd app create argo-helm-test --repo https://github.com/slawekgh/argo-helm --path test-chart --dest-namespace test --dest-server https://kubernetes.default.svc --auto-prune --sync-policy automated --release-name test-release --values values-test.yaml
application 'argo-helm-test' created
argocd@argocd-server-5fff657769-fhml5:~$ argocd app create argo-helm-prod --repo https://github.com/slawekgh/argo-helm --path test-chart --dest-namespace prod --dest-server https://kubernetes.default.svc --auto-prune --sync-policy automated --release-name test-release --values values-prod.yaml
application 'argo-helm-prod' created
po pewnym czasie wszystko wygląda na zgodne z założeniami, argo-apps się zsynchronizowały, zaś w każdym NS jest tyle podów ile miało być i k8s-svc są na tych portach na których miały być:
argocd@argocd-server-5fff657769-fhml5:~$ argocd app list
NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS REPO PATH TARGET
argocd/argo-helm-dev https://kubernetes.default.svc dev default Synced Healthy Auto-Prune <none> https://github.com/slawekgh/argo-helm test-chart
argocd/argo-helm-prod https://kubernetes.default.svc prod default Synced Healthy Auto-Prune <none> https://github.com/slawekgh/argo-helm test-chart
argocd/argo-helm-test https://kubernetes.default.svc test default Synced Healthy Auto-Prune <none> https://github.com/slawekgh/argo-helm test-chart
$ kk get po,svc -n dev
NAME READY STATUS RESTARTS AGE
pod/test-release-deploy-7c9c7669c-dvjfc 1/1 Running 0 107s
pod/test-release-deploy-7c9c7669c-q9g9h 1/1 Running 0 107s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/test-release ClusterIP 10.108.8.247 <none> 2222/TCP 2m30s
$ kk get po,svc -n test
NAME READY STATUS RESTARTS AGE
pod/test-release-deploy-7c9c7669c-hf5nj 1/1 Running 0 2m28s
pod/test-release-deploy-7c9c7669c-rbs49 1/1 Running 0 2m28s
pod/test-release-deploy-7c9c7669c-zk948 1/1 Running 0 2m28s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/test-release ClusterIP 10.108.8.138 <none> 3333/TCP 2m28s
$ kk get po,svc -n prod
NAME READY STATUS RESTARTS AGE
pod/test-release-deploy-7c9c7669c-2vdzr 1/1 Running 0 2m26s
pod/test-release-deploy-7c9c7669c-n2kkd 1/1 Running 0 2m26s
pod/test-release-deploy-7c9c7669c-qt7bp 1/1 Running 0 2m26s
pod/test-release-deploy-7c9c7669c-zfhlb 1/1 Running 0 2m26s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/test-release ClusterIP 10.108.2.95 <none> 4444/TCP 2m27s
rozwiązanie to mimo że proste i działa od ręki to jednak jest skrajnie niedoskonałe:
Drugie rozwiązanie (również pozornie proste ale również pozbawione większego sensu) to skopiowanie chartu do 3 różnych repozytoriów. Od tej pory trzeba będzie utrzymywać zawartość chartu w jakiejś synchronizacji, co spowoduje, że po chwili i tak wszystko się rozjedzie i zamieni się to w 3 osobne produkty.
Separacja między prod a nie-prod, co prawda będzie, ale BSR jest tu zupełnie nie zachowany i ryzyko różnic między PROD a środowiskami niższymi tak duże, że rozwiązanie to raczej nie nadaje się do wdrożeń – a zatem nawet go nie będziemy tu modelować.
dodajemy kolejne repo (to repo będzie akurat shipem konfiguracyjnym dla środowiska DEV) które ma dependencies do centralnego repo z chartem. W repo tym jest tylko dependency (w Chart.yaml) i values.yaml dla DEV.
ship-repo-dev$ tree
├── Chart.yaml
├── README.md
└── values.yaml
W Chart.yaml umieszczamy dependency i w nim trzeba się odwołać do centralnego repo chartów helma. Głównym wyzwaniem tutaj będzie zrobienie na bazie naszego głównego repo nowego i prawidłowego repozytorium helmowego.
Zakładamy nowe repo, idąc za tym że:
chart repository is really just an HTTP server that hosts an index.yaml file together with a bunch of packaged charts in form of .tgz files.
trzeba zrobić TGZ oraz index.yaml
TGZ robimy via
helm package test-chartINDEX.YAML via
helm repo index.powstaje:
chart-repository$ tree
.
├── index.yaml
└── test-chart-0.8.tgz
Trzeba jeszcze zmusić GitHuba żeby serwował pliki jak zwykły serwer HTTP – i tu z pomocą przychodzą ścieżki RAW jakie daje GitHuib – przykładowo:
https://raw.githubusercontent.com/slawekgh/argo-helm-chart-repository/main/index.yaml
a zatem komuś kto będzie chiał używać tego helm-repository taką ścieżkę trzeba będzie podawać. Zaś w helm-chartach, które będą się odwoływać do tego helm-repository, trzeba będzie na końcu ich Chart.yaml dodefiniować:
dependencies:
- name: test-chart
version: 0.8
repository: https://raw.githubusercontent.com/slawekgh/argo-helm-chart-repository/main/
pozostaje zatem w repo-ship-dev dodać takie dependency do Chart.yaml i sprawdzić via helm dependency update czy to się poprawnie przemieli.
ship-repo-dev$ tree
.
├── Chart.yaml
├── README.md
└── values.yaml
ship-repo-dev$ cat Chart.yaml
apiVersion: v2
name: dev-chart
type: application
version: 1.12
appVersion: "3.6"
dependencies:
- name: test-chart
version: 0.8
repository: https://raw.githubusercontent.com/slawekgh/argo-helm-chart-repository/main/
Pamiętając też o values – tutaj są nieco inaczej zdefiniowane gdyż sięgają (nadpisują) values z child-chartu – stąd musi być to tym razem w sekcji test-chart (chodzi o to wcięcie dla jasność):
ship-repo-dev$ cat values.yaml
# Default values for test-chart
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
test-chart:
replicaCount: 2
testerPodName: tester
namespace: dev
label: testlabel
ala: alamakota
servicePort : 2222
PROTO : TCP
targetPort : 8080
obraz:
image: gimboo/nginx_nonroot
imagePolicy: Always
Sprawdźmy czy to działa:
ship-repo-dev$ helm dependency update
Getting updates for unmanaged Helm repositories...
...Successfully got an update from the "https://raw.githubusercontent.com/slawekgh/argo-helm-chart-repository/main/" chart repository
Saving 1 charts
Downloading test-chart from repo https://raw.githubusercontent.com/slawekgh/argo-helm-chart-repository/main/
Deleting outdated charts
ship-repo-dev$ tree
.
├── Chart.lock
├── charts
│ └── test-chart-0.8.tgz
├── Chart.yaml
├── README.md
└── values.yaml
jak widać lokalnie (bez argo) się zrobiło – coś ściągnął więc jak widać działa to poprawnie. Teraz dodajemy to do argo:
argocd@argocd-server-5fff657769-fhml5:~$ argocd app list
NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS REPO PATH TARGET
(czysto i pusto)
argocd@argocd-server-5fff657769-fhml5:~$ argocd app create argo-helm-dev --repo https://github.com/slawekgh/helm-argo-ship-dev --path . --dest-namespace dev --dest-server https://kubernetes.default.svc --auto-prune --sync-policy automated --release-name test-release --values values.yaml
application 'argo-helm-dev' created
argocd@argocd-server-5fff657769-fhml5:~$ argocd app list
NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS REPO PATH TARGET
argocd/argo-helm-dev https://kubernetes.default.svc dev default Synced Healthy Auto-Prune <none> https://github.com/slawekgh/helm-argo-ship-dev .
na klastrze argo wykonało założenia
$ kk get po -n dev
NAME READY STATUS RESTARTS AGE
test-release-deploy-7c9c7669c-6cbqc 1/1 Running 0 69s
test-release-deploy-7c9c7669c-vnxh9 1/1 Running 0 69s
Analogicznie obok repo-ship-dev. Należy założyć identyczne osobne repo dla test i osobne dla prod. Ich struktura będzie taka sama – mają tu być tylko dependency (w Chart.yaml) do helm-repository i values.yaml odpowiednie dla TEST i dla PROD.
ship-repo-dev$ tree
├── Chart.yaml
├── README.md
└── values.yaml
Czy rozwiązanie z umbrella-chart ma sens? Ogólnie tak, ale:
Do repo spod linku, dodajemy ponownie generyczny values (values-generic.yaml), który co prawda ma wpisane defaultowe wartości dla replicaCount, servicePort i namespace, ale wg założeń nigdy nie powinny one być użyte .
helm-chart-repo$ cat test-chart/values-generic.yaml
replicaCount: 1
testerPodName: tester
namespace: dev
label: testlabel
ala: alamakota
servicePort : 1111
PROTO : TCP
targetPort : 8080
obraz:
image: gimboo/nginx_nonroot
imagePolicy: Always
Nie będą użyte ponieważ w tej metodzie zmienne replicaCount, servicePort oraz namespace będziemy. nadpisywać dostępne opcje dla argo app create:
--helm-set stringArray Helm set values on the command line (can be repeated to set several values: --helm-set key1=val1 --helm-set key2=val2)
--helm-set-file stringArray Helm set values from respective files specified via the command line (can be repeated to set several values: --helm-set-file key1=path1 --helm-set-file key2=path2)
--helm-set-string stringArray Helm set STRING values on the command line (can be repeated to set several values: --helm-set-string key1=val1 --helm-set-string key2=val2)
tutaj użyjemy –helm-set:
argocd@argocd-server-85f85d648b-tf2bx:~$ argocd repo list
TYPE NAME REPO INSECURE OCI LFS CREDS STATUS MESSAGE PROJECT
argocd@argocd-server-85f85d648b-tf2bx:~$ argocd repo add https://github.com/slawekgh/argo-helm
Repository 'https://github.com/slawekgh/argo-helm' added
argocd@argocd-server-85f85d648b-tf2bx:~$ argocd app list
NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS REPO PATH TARGET
argocd@argocd-server-85f85d648b-tf2bx:~$ argocd app create argo-helm-dev --repo https://github.com/slawekgh/argo-helm --path test-chart --dest-namespace dev --dest-server https://kubernetes.default.svc --auto-prune --sync-policy automated --release-name test-release --values values-generic.yaml --helm-set replicaCount=2 --helm-set servicePort=2222 --helm-set namespace=dev
application 'argo-helm-dev' created
argocd@argocd-server-85f85d648b-tf2bx:~$ argocd app create argo-helm-test --repo https://github.com/slawekgh/argo-helm --path test-chart --dest-namespace test --dest-server https://kubernetes.default.svc --auto-prune --sync-policy automated --release-name test-release --values values-generic.yaml --helm-set replicaCount=3 --helm-set servicePort=3333 --helm-set namespace=test
application 'argo-helm-test' created
argocd@argocd-server-85f85d648b-tf2bx:~$ argocd app create argo-helm-prod --repo https://github.com/slawekgh/argo-helm --path test-chart --dest-namespace prod --dest-server https://kubernetes.default.svc --auto-prune --sync-policy automated --release-name test-release --values values-generic.yaml --helm-set replicaCount=4 --helm-set servicePort=4444 --helm-set namespace=prod
application 'argo-helm-prod' created
argocd@argocd-server-85f85d648b-tf2bx:~$ argocd app listNAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS REPO PATH TARGET
argocd/argo-helm-dev https://kubernetes.default.svc dev default Synced Healthy Auto-Prune <none> https://github.com/slawekgh/argo-helm test-chart
argocd/argo-helm-prod https://kubernetes.default.svc prod default Synced Healthy Auto-Prune <none> https://github.com/slawekgh/argo-helm test-chart
argocd/argo-helm-test https://kubernetes.default.svc test default Synced Healthy Auto-Prune <none> https://github.com/slawekgh/argo-helm test-chart
Jak widać 3 argo-app się poprawnie zsynchronizowały, zaś na GKE jest również zgodnie z założeniami:
-----dev-------------
NAME READY STATUS RESTARTS AGE
test-release-deploy-7c9c7669c-8h498 1/1 Running 0 40s
test-release-deploy-7c9c7669c-b2nxt 1/1 Running 0 40s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
test-release ClusterIP 10.108.5.59 <none> 2222/TCP 41s
-----test-------------
NAME READY STATUS RESTARTS AGE
test-release-deploy-7c9c7669c-jghr8 1/1 Running 0 18s
test-release-deploy-7c9c7669c-jlmxw 1/1 Running 0 18s
test-release-deploy-7c9c7669c-z46qx 1/1 Running 0 18s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
test-release ClusterIP 10.108.11.61 <none> 3333/TCP 19s
-----prod-------------
NAME READY STATUS RESTARTS AGE
test-release-deploy-7c9c7669c-55cg2 1/1 Running 0 5s
test-release-deploy-7c9c7669c-t8dcm 1/1 Running 0 4s
test-release-deploy-7c9c7669c-t8x5q 1/1 Running 0 4s
test-release-deploy-7c9c7669c-z8mp5 1/1 Running 0 4s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
test-release ClusterIP 10.108.10.248 <none> 4444/TCP 6s
SPEC argo-aplikacji wygląda następująco (tu na przykładzie argo-helm-dev) – zwracamy uwagę na sekcję parameters:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
creationTimestamp: "2023-08-23T08:43:38Z"
generation: 14
name: argo-helm-dev
namespace: argocd
resourceVersion: "79461"
uid: aa96eae1-ff2d-4699-8aaa-6f382309413b
spec:
destination:
namespace: dev
server: https://kubernetes.default.svc
project: default
source:
helm:
parameters:
- name: replicaCount
value: "2"
- name: servicePort
value: "2222"
- name: namespace
value: dev
releaseName: test-release
valueFiles:
- values-generic.yaml
path: test-chart
repoURL: https://github.com/slawekgh/argo-helm
syncPolicy:
automated:
prune: true
Oczywiście podobnie jak argo-app-dev również argo-app dla test i dla prod zawierają takie parametry.
Zalety:
Wady: