ArgoCD GitOps
В данном практическом занятии рассмотрим возможности argocd в качестве GitOps инструмента.
Vagrant
Для работы будем использовать следующий Vagrantfile
:
Vagrant.configure("2") do |config|
config.vm.define "argocd" do |c|
c.vm.provider "virtualbox" do |v|
v.cpus = 2
v.memory = 4096
end
c.vm.box = "ubuntu/lunar64"
c.vm.hostname = "argocd"
c.vm.network "forwarded_port", guest: 8888, host: 8888
c.vm.provision "shell", inline: <<-SHELL
apt-get update -q
apt-get install -yq docker.io docker-compose-v2
usermod -a -G docker vagrant
echo '{"registry-mirrors":["https:\\/\\/mirror.gcr.io"]}' > /etc/docker/daemon.json
systemctl restart docker
curl -LO https://dl.k8s.io/release/v1.30.0/bin/linux/amd64/kubectl
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.22.0/kind-linux-amd64
curl -Lo ./argocd https://github.com/argoproj/argo-cd/releases/download/v2.11.2/argocd-linux-amd64
curl -L https://get.helm.sh/helm-v3.15.1-linux-amd64.tar.gz | tar xvzf - --strip-components 1 linux-amd64/helm
install -m 755 kubectl kind argocd helm /usr/local/bin/
rm ./*
SHELL
end
end
Данная конфигурация установит на виртуальную машину docker, kubectl, kind, argocd и helm.
Install
Для развертывания кластера с помощью kind с возможностью использования локального registry воспользуемся скриптом:
#!/bin/sh
set -o errexit
# 1. Create registry container unless it already exists
reg_name='kind-registry'
reg_port='5000'
if [ "$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" != 'true' ]; then
docker run \
-d --restart=always -p "127.0.0.1:${reg_port}:5000" --network bridge --name "${reg_name}" \
registry:2
fi
# 2. Create kind cluster with containerd registry config dir enabled
# TODO: kind will eventually enable this by default and this patch will
# be unnecessary.
#
# See:
# https://github.com/kubernetes-sigs/kind/issues/2875
# https://github.com/containerd/containerd/blob/main/docs/cri/config.md#registry-configuration
# See: https://github.com/containerd/containerd/blob/main/docs/hosts.md
cat <<EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 8888
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/containerd/certs.d"
EOF
# 3. Add the registry config to the nodes
#
# This is necessary because localhost resolves to loopback addresses that are
# network-namespace local.
# In other words: localhost in the container is not localhost on the host.
#
# We want a consistent name that works from both ends, so we tell containerd to
# alias localhost:${reg_port} to the registry container when pulling images
REGISTRY_DIR="/etc/containerd/certs.d/registry.traefik.me:${reg_port}"
for node in $(kind get nodes); do
docker exec "${node}" mkdir -p "${REGISTRY_DIR}"
cat <<EOF | docker exec -i "${node}" cp /dev/stdin "${REGISTRY_DIR}/hosts.toml"
[host."http://${reg_name}:5000"]
EOF
done
# 4. Connect the registry to the cluster network if not already connected
# This allows kind to bootstrap the network but ensures they're on the same network
if [ "$(docker inspect -f='{{json .NetworkSettings.Networks.kind}}' "${reg_name}")" = 'null' ]; then
docker network connect "kind" "${reg_name}"
fi
# 5. Document the local registry
# https://github.com/kubernetes/enhancements/tree/master/keps/sig-cluster-lifecycle/generic/1755-communicating-a-local-registry
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: local-registry-hosting
namespace: kube-public
data:
localRegistryHosting.v1: |
host: "registry.traefik.me:${reg_port}"
help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
EOF
Сохраним в файл и запустим:
$ ./kind.sh
Unable to find image 'registry:2' locally
2: Pulling from library/registry
619be1103602: Pull complete
862815ae87dc: Pull complete
74e12953df95: Pull complete
6f0ce73649a0: Pull complete
ef4f267ce8ed: Pull complete
Digest: sha256:4fac7a8257b1d7a86599043fcc181dfbdf9c8f57e337db763ac94b0e67c6cfb5
Status: Downloaded newer image for registry:2
9f5d46586f8b64579e5acd891fd6eef51cd2dcd67ef4ee3a36ec3e3bae7aac45
Creating cluster "kind" ...
✓ Ensuring node image (kindest/node:v1.29.2) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-kind"
You can now use your cluster with:
kubectl cluster-info --context kind-kind
Thanks for using kind! 😊
configmap/local-registry-hosting created
Установим ingress-nginx
, а также добавим helm
репозитории gitea и argocd:
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
serviceaccount/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
configmap/ingress-nginx-controller created
service/ingress-nginx-controller created
service/ingress-nginx-controller-admission created
deployment.apps/ingress-nginx-controller created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
$ helm repo add gitea-charts https://dl.gitea.com/charts/
"gitea-charts" has been added to your repositories
$ helm repo add argo https://argoproj.github.io/argo-helm
"argo" has been added to your repositories
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "gitea-charts" chart repository
...Successfully got an update from the "argo" chart repository
Update Complete. ⎈Happy Helming!⎈
После чего установим gitea с помощью утилиты helm и убедимся, что контейнеры запущены:
$ helm upgrade -i gitea gitea-charts/gitea --namespace git --create-namespace \
--set global.imageRegistry=mirror.gcr.io --set persistence.enabled=false \
--set redis-cluster.enabled=false --set postgresql-ha.enabled=false \
--set postgresql.enabled=true --set ingress.enabled=true \
--set-json 'ingress.hosts[0]={"host":"git.traefik.me","paths":[{"path":"/","pathType":"Prefix"}]}'
Release "gitea" does not exist. Installing it now.
NAME: gitea
LAST DEPLOYED: Mon Jun 3 20:37:25 2024
NAMESPACE: git
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
http://git.traefik.me/
2. Review these warnings:
- Gitea uses 'memory' for caching which is not recommended for production use. See https://docs.gitea.com/next/administration/config-cheat-sheet#cache-cache for available options.
- Gitea uses 'leveldb' for queue actions which is not recommended for production use. See https://docs.gitea.com/next/administration/config-cheat-sheet#queue-queue-and-queue for available options.
- Gitea uses 'memory' for sessions which is not recommended for production use. See https://docs.gitea.com/next/administration/config-cheat-sheet#session-session for available options.
$ kubectl get po -n git
NAME READY STATUS RESTARTS AGE
gitea-7f66d84fc7-7sfqn 1/1 Running 0 76s
gitea-postgresql-0 1/1 Running 0 76s
После чего интерфейс будет доступен по адресу git.traefik.me:8888:
Установим также argocd с помощью helm и дождемся пока поды запустятся:
$ helm upgrade -i argo argo/argo-cd --namespace argo --create-namespace \
--set dex.enabled=false --set 'server.extraArgs={--insecure}' \
--set server.ingress.enabled=true --set server.ingress.hostname=argo.traefik.me
Release "argo" does not exist. Installing it now.
NAME: argo
LAST DEPLOYED: Mon Jun 3 20:50:10 2024
NAMESPACE: argo
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
In order to access the server UI you have the following options:
1. kubectl port-forward service/argo-argocd-server -n argo 8080:443
and then open the browser on http://localhost:8080 and accept the certificate
2. enable ingress in the values file `server.ingress.enabled` and either
- Add the annotation for ssl passthrough: https://argo-cd.readthedocs.io/en/stable/operator-manual/ingress/#option-1-ssl-passthrough
- Set the `configs.params."server.insecure"` in the values file and terminate SSL at your ingress: https://argo-cd.readthedocs.io/en/stable/operator-manual/ingress/#option-2-multiple-ingress-objects-and-hosts
After reaching the UI the first time you can login with username: admin and the random password generated during the installation. You can find the password by running:
kubectl -n argo get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
(You should delete the initial secret afterwards as suggested by the Getting Started Guide: https://argo-cd.readthedocs.io/en/stable/getting_started/#4-login-using-the-cli)
$ kubectl get po -n argo
NAME READY STATUS RESTARTS AGE
argo-argocd-application-controller-0 1/1 Running 0 114s
argo-argocd-applicationset-controller-79945d9cc8-6r865 1/1 Running 0 114s
argo-argocd-notifications-controller-64dcf79f6c-s8lrj 1/1 Running 0 114s
argo-argocd-redis-59789c768d-tpn7s 1/1 Running 0 114s
argo-argocd-redis-secret-init-zpxtb 0/1 Completed 0 2m17s
argo-argocd-repo-server-57b8c49d95-b9wt8 1/1 Running 0 114s
argo-argocd-server-786c44855d-w9gn6 1/1 Running 0 114s
После чего интерфейс будет доступен по адресу argo.traefik.me:8888:
Create App
Создадим репозиторий app
в gitea:
Склонируем репозиторий и создадим простое приложение и Dockerfile
к нему:
$ git clone http://git.traefik.me:8888/alex/app.git
Cloning into 'app'...
warning: You appear to have cloned an empty repository.
$ cd app
$ cat <<EOF>main.go
package main
import (
"net/http"
)
func main() {
http.ListenAndServe(":8080", http.HandlerFunc(
func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("hello\n"))
}))
}
EOF
$ cat <<EOF>Dockerfile
FROM mirror.gcr.io/golang:1.21 as build
WORKDIR /src
COPY main.go /src/
RUN CGO_ENABLED=0 go build -o /bin/app ./main.go
FROM scratch
COPY --from=build /bin/app /app
CMD ["/app"]
EOF
После чего соберем и запушим в локальный реджестри registry.traefik.me:5000
:
$ docker build -t registry.traefik.me:5000/app:v1 .
Sending build context to Docker daemon 45.57kB
Step 1/7 : FROM mirror.gcr.io/golang:1.21 as build
---> 0a5922bb6e20
Step 2/7 : WORKDIR /src
---> Running in e76b21970668
Removing intermediate container e76b21970668
---> 137ecccbe70b
Step 3/7 : COPY main.go /src/
---> e9da6a17b049
Step 4/7 : RUN CGO_ENABLED=0 go build -o /bin/app ./main.go
---> Running in 73edb1a2166d
Removing intermediate container 73edb1a2166d
---> 67701db7cb58
Step 5/7 : FROM scratch
--->
Step 6/7 : COPY --from=build /bin/app /app
---> 69ba82a41903
Step 7/7 : CMD ["/app"]
---> Running in b1ff3579ccbe
Removing intermediate container b1ff3579ccbe
---> c8c5d0269885
Successfully built c8c5d0269885
Successfully tagged registry.traefik.me:5000/app:v1
$ docker push registry.traefik.me:5000/app:v1
The push refers to repository [registry.traefik.me:5000/app]
fda33218fe64: Pushed
v1: digest: sha256:b6ec73fecc842f529e590bae6c4b6b176d0f4add5997c8e9aa46bd2e5f398466 size: 528
Create Chart
Создадим также helm
чарт в репозитории:
$ helm create chart
Creating chart
$ rm -r chart/templates/tests
И отредактируем chart/values.yaml
:
replicaCount: 1
image:
repository: registry.traefik.me:5000/app
pullPolicy: IfNotPresent
tag: "v1"
serviceAccount:
create: false
autoscaling:
enabled: false
service:
type: ClusterIP
port: 8080
ingress:
enabled: true
hosts:
- host: app.traefik.me
paths:
- path: /
pathType: Prefix
После чего отправим в удаленный репозиторий:
$ git add .
$ git commit -m init
[main (root-commit) 469db78] init
13 files changed, 363 insertions(+)
create mode 100644 Dockerfile
create mode 100644 chart/.helmignore
create mode 100644 chart/.values.yaml.swp
create mode 100644 chart/Chart.yaml
create mode 100644 chart/templates/NOTES.txt
create mode 100644 chart/templates/_helpers.tpl
create mode 100644 chart/templates/deployment.yaml
create mode 100644 chart/templates/hpa.yaml
create mode 100644 chart/templates/ingress.yaml
create mode 100644 chart/templates/service.yaml
create mode 100644 chart/templates/serviceaccount.yaml
create mode 100644 chart/values.yaml
create mode 100644 main.go
$ git push
Enumerating objects: 17, done.
Counting objects: 100% (17/17), done.
Delta compression using up to 2 threads
Compressing objects: 100% (17/17), done.
Writing objects: 100% (17/17), 5.76 KiB | 1.44 MiB/s, done.
Total 17 (delta 1), reused 0 (delta 0), pack-reused 0
remote: . Processing 1 references
remote: Processed 1 references in total
To http://git.traefik.me:8888/alex/app.git
* [new branch] main -> main
Create Argo App
Создадим приложение в argocd, указав внутренний git адрес:
$ kubectl create -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: app
namespace: argo
spec:
project: default
source:
repoURL: http://gitea-http.git.svc:3000/alex/app.git
targetRevision: main
path: chart
destination:
server: https://kubernetes.default.svc
namespace: default
EOF
После чего получим пароль и авторизуемся в
веб интерфейсе под пользователем admin
:
$ kubectl -n argo get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
UStarYkPA3nh25w0
Как видно argocd отображает ресурсы, описанные в helm
чарте. Нажмем кнопку
Sync
для создания ресурсов в кластере, после чего приложение перейдет в статус
Healthy
:
А по адресу app.traefik.me:8888 будет доступно само приложение:
Auto-Sync
Также в интерфейсе argocd в нашем приложении по кнопке Details
можно включить
автоматическую синхронизацию ресурсов кластера с git репозиторием
Включим ее для того, чтобы при любых изменениях в репозитории ресурсы сразу попадали в кластер.
Изменим строку в коде приложения, чтобы по выводу была понятна версия:
package main
import (
"net/http"
)
func main() {
http.ListenAndServe(":8080", http.HandlerFunc(
func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("hello v2\n"))
}))
}
Соберем и запушим новую версию:
$ docker build -t registry.traefik.me:5000/app:v2 .
Sending build context to Docker daemon 107kB
Step 1/7 : FROM mirror.gcr.io/golang:1.21 as build
---> 0a5922bb6e20
Step 2/7 : WORKDIR /src
---> Using cache
---> 137ecccbe70b
Step 3/7 : COPY main.go /src/
---> e8d44019610a
Step 4/7 : RUN CGO_ENABLED=0 go build -o /bin/app ./main.go
---> Running in 4e680dcb9a7c
Removing intermediate container 4e680dcb9a7c
---> 6a8312d3000d
Step 5/7 : FROM scratch
--->
Step 6/7 : COPY --from=build /bin/app /app
---> de7646146ed1
Step 7/7 : CMD ["/app"]
---> Running in b9f6a2586cba
Removing intermediate container b9f6a2586cba
---> fbd62922d125
Successfully built fbd62922d125
Successfully tagged registry.traefik.me:5000/app:v2
$ docker push registry.traefik.me:5000/app:v2
The push refers to repository [registry.traefik.me:5000/app]
f9ea5c2db5d8: Pushed
v2: digest: sha256:d33a410af15be7f43416ff61ee3cb2aa947297aeaadfef3a043973ba6caaa0fc size: 528
А в файле chart/values.yaml
укажем новую версию:
replicaCount: 1
image:
repository: registry.traefik.me:5000/app
pullPolicy: IfNotPresent
tag: "v2"
serviceAccount:
create: false
autoscaling:
enabled: false
service:
type: ClusterIP
port: 8080
ingress:
enabled: true
hosts:
- host: app.traefik.me
paths:
- path: /
pathType: Prefix
После чего отправим изменения в удаленный репозиторий:
$ git add .
$ git commit -m 'update version'
[main 7fca3bf] update version
3 files changed, 2 insertions(+), 2 deletions(-)
delete mode 100644 chart/.values.yaml.swp
$ git push
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 2 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 446 bytes | 446.00 KiB/s, done.
Total 5 (delta 3), reused 0 (delta 0), pack-reused 0
remote: . Processing 1 references
remote: Processed 1 references in total
To http://git.traefik.me:8888/alex/app.git
469db78..7fca3bf main -> main
Спустя некоторое время argocd получит информацию об изменениях в репозитории и
создаст новую ревизию с версией v2
:
А по адресу app.traefik.me:8888 начнет отвечать новая версия:
Параметры чарта можно определять не только через git репозиторий, но и
переназначать во вкладке Parameters
в Details
приложения argocd:
Может отредактировать, нажав кнопку Edit
и вернуть параметр image.tag
в
значение v1
:
Таким образом развернется приложение со старой версией: