УРОК 24 / 30 0%

Helm + ArgoCD (GitOps)

🎯

Цель урока

Сделать полноценный CRUD для задач с правильными HTTP-статусами (201/204/404), обработкой ошибок, и подготовить Task к подключению JPA.

🧠

Теория · для собеса

1 Helm — зачем

Helm — это пакетный менеджер для K8s (apt для k8s):

  • Шаблоны + параметры (один чарт → разные окружения)
  • Версионирование (helm install taskflow-0.1.0)
  • Управление зависимостями (PostgreSQL как subchart)
2 Структура чарта

taskflow-chart/
├── Chart.yaml           # метаданные (name, version, appVersion)
├── values.yaml          # значения по умолчанию
├── charts/              # зависимости (subcharts)
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   ├── secret.yaml
│   ├── _helpers.tpl     # reusable snippets
│   └── NOTES.txt        # что выведется после helm install
└── values-dev.yaml      # override для dev
    values-prod.yaml     # override для prod
3 ArgoCD — GitOps

GitOps = Git — единственный источник истины. ArgoCD:

  1. Следит за Git-репозиторием
  2. Если манифесты в Git изменились → применяет в кластер
  3. Если в кластере что-то поменяли руками → показывает OutOfSync
  4. Можно auto-sync или manual

Преимущества:

  • История изменений = git log
  • Rollback = git revert
  • Code review для K8s-изменений

---

💻

Практика: улучшаем CRUD

  1. 1
    Установка Helm
    
    # macOS
    brew install helm
    
    # Linux
    curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
    
  2. 2
    Создание чарта
    
    helm create taskflow-chart
    # Создаст скелет с примерами
    
  3. 3
    Chart.yaml
    
    apiVersion: v2
    name: taskflow
    description: TaskFlow — Spring Boot task management service
    type: application
    version: 0.1.0          # версия чарта
    appVersion: "0.1.0"     # версия приложения
    
  4. 4
    values.yaml (дефолтные)
    
    replicaCount: 2
    
    image:
      repository: taskflow
      tag: "0.1.0"
      pullPolicy: IfNotPresent
    
    imagePullSecrets: []
    
    nameOverride: ""
    fullnameOverride: ""
    
    service:
      type: ClusterIP
      port: 80
      targetPort: 8080
    
    ingress:
      enabled: true
      className: nginx
      annotations:
        nginx.ingress.kubernetes.io/rewrite-target: /
      hosts:
        - host: taskflow.local
          paths: ["/"]
    
    resources:
      limits:
        cpu: 500m
        memory: 512Mi
      requests:
        cpu: 250m
        memory: 256Mi
    
    autoscaling:
      enabled: true
      minReplicas: 2
      maxReplicas: 10
      targetCPUUtilizationPercentage: 70
    
    env:
      SPRING_PROFILES_ACTIVE: "prod"
      SPRING_DATASOURCE_URL: "jdbc:postgresql://postgres:5432/taskflow"
      SPRING_KAFKA_BOOTSTRAP_SERVERS: "kafka:9092"
    
    secrets:
      SPRING_DATASOURCE_PASSWORD: "REPLACE_ME"
      JWT_SECRET: "REPLACE_ME_with_32+_chars"
    
    probes:
      liveness:
        path: /actuator/health/liveness
      readiness:
        path: /actuator/health/readiness
    
  5. 5
    values-dev.yaml
    
    replicaCount: 1
    env:
      SPRING_PROFILES_ACTIVE: "dev"
    ingress:
      hosts:
        - host: taskflow-dev.local
    
  6. 6
    values-prod.yaml
    
    replicaCount: 5
    autoscaling:
      enabled: true
      maxReplicas: 20
    ingress:
      hosts:
        - host: taskflow.example.com
    secrets:
      SPRING_DATASOURCE_PASSWORD: "PROD_PASSWORD_HERE"
      JWT_SECRET: "PROD_JWT_SECRET_HERE"
    
  7. 7
    templates/_helpers.tpl
    
    {{- define "taskflow.name" -}}
    {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
    {{- end -}}
    
    {{- define "taskflow.fullname" -}}
    {{- if .Values.fullnameOverride -}}
    {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
    {{- else -}}
    {{- $name := default .Chart.Name .Values.nameOverride -}}
    {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
    {{- end -}}
    {{- end -}}
    
  8. 8
    templates/deployment.yaml
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: {{ include "taskflow.fullname" . }}
      labels:
        app: {{ include "taskflow.name" . }}
        chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
    spec:
      replicas: {{ .Values.replicaCount }}
      selector:
        matchLabels:
          app: {{ include "taskflow.name" . }}
      template:
        metadata:
          labels:
            app: {{ include "taskflow.name" . }}
        spec:
          containers:
          - name: {{ .Chart.Name }}
            image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
            imagePullPolicy: {{ .Values.image.pullPolicy }}
            ports:
            - containerPort: {{ .Values.service.targetPort }}
            env:
            {{- range $key, $value := .Values.env }}
            - name: {{ $key }}
              value: {{ $value | quote }}
            {{- end }}
            env:
            - name: SPRING_DATASOURCE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: {{ include "taskflow.fullname" . }}
                  key: SPRING_DATASOURCE_PASSWORD
            - name: JWT_SECRET
              valueFrom:
                secretKeyRef:
                  name: {{ include "taskflow.fullname" . }}
                  key: JWT_SECRET
            resources: {{ toYaml .Values.resources | nindent 8 }}
            readinessProbe:
              httpGet:
                path: {{ .Values.probes.readiness.path }}
                port: {{ .Values.service.targetPort }}
              initialDelaySeconds: 10
              periodSeconds: 5
            livenessProbe:
              httpGet:
                path: {{ .Values.probes.liveness.path }}
                port: {{ .Values.service.targetPort }}
              initialDelaySeconds: 30
              periodSeconds: 10
    
  9. 9
    templates/secret.yaml
    
    apiVersion: v1
    kind: Secret
    metadata:
      name: {{ include "taskflow.fullname" . }}
    type: Opaque
    stringData:
      SPRING_DATASOURCE_PASSWORD: {{ .Values.secrets.SPRING_DATASOURCE_PASSWORD }}
      JWT_SECRET: {{ .Values.secrets.JWT_SECRET }}
    
  10. 10
    templates/hpa.yaml (autoscaling)
    
    {{- if .Values.autoscaling.enabled }}
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: {{ include "taskflow.fullname" . }}
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: {{ include "taskflow.fullname" . }}
      minReplicas: {{ .Values.autoscaling.minReplicas }}
      maxReplicas: {{ .Values.autoscaling.maxReplicas }}
      metrics:
      - type: Resource
        resource:
          name: cpu
          target:
            type: Utilization
            averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
    {{- end }}
    
  11. 11
    Команды Helm
    
    # Установить с values-prod
    helm install taskflow ./taskflow-chart -f values-prod.yaml -n taskflow --create-namespace
    
    # Проверить что сгенерируется
    helm template taskflow ./taskflow-chart -f values-prod.yaml
    
    # Обновить
    helm upgrade taskflow ./taskflow-chart -f values-prod.yaml -n taskflow
    
    # Откатить
    helm history taskflow -n taskflow
    helm rollback taskflow 1 -n taskflow
    
    # Удалить
    helm uninstall taskflow -n taskflow
    
  12. 12
    Установка ArgoCD
    
    kubectl create namespace argocd
    kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
    
    # Получить пароль admin
    kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
    
    # Port-forward UI
    kubectl port-forward svc/argocd-server -n argocd 8080:443
    # Открыть: https://localhost:8080 (admin / )
    
  13. 13
    ArgoCD Application
    
    # argocd-app.yaml
    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: taskflow
      namespace: argocd
    spec:
      project: default
    
      source:
        repoURL: https://github.com/YOUR_USER/taskflow.git
        targetRevision: main
        path: k8s/
        helm:
          valueFiles:
            - values-prod.yaml
    
      destination:
        server: https://kubernetes.default.svc
        namespace: taskflow
    
      syncPolicy:
        automated:
          prune: true      # удалять ресурсы, которых нет в Git
          selfHeal: true   # синхронизировать при drift
        syncOptions:
          - CreateNamespace=true
    
    
    kubectl apply -f argocd-app.yaml
    
  14. 14
    GitOps workflow
    1. Developer пушит изменение в Git (k8s/deployment.yaml или values-prod.yaml)
    2. CI запускает тесты
    3. После merge в main — ArgoCD замечает изменение
    4. ArgoCD применяет в кластер (auto-sync)
    5. UI показывает статус Synced/Healthy

    ---

🎯

Зачем это на собесе

После урока ты должен уметь ответить на:

Single source of truth — Git. Не «у Васи на ноуте конфиг», а в репо, под code review.
Disaster recovery — потеряли кластер? Применили чарт = восстановили.
Multi-env — один чарт, разные values-{env}.yaml для dev/staging/prod.
Auto-healing — кто-то руками поправил в кластере? ArgoCD заметит и откатит.

5 вопросов на углубление

Раскрой вопрос и нажми «🤔 Хочу разобрать подробнее» — он попадёт в страницу ответов.

1
helm install vs kubectl apply — зачем Helm?

Разбор внутри: релизы, values, history и rollback.

2
Почему нельзя терять values-prod.yaml с секретами?

Разбор внутри: Secret генерируется из values и GitOps требует источник правды.

3
Что делает prune: true в ArgoCD?

Разбор внутри: удаление ресурсов, которых больше нет в Git.

4
Helm vs Kustomize — что выбрать?

Разбор внутри: шаблоны + values против overlay поверх base.

5
Что такое OCI registry для Helm charts?

Разбор внутри: хранение chart-ов как OCI-артефактов рядом с image.

Готов идти дальше?

Выбери вопросы, которые тебе интересны, и изучи их. Потом — к следующему уроку.

🚀 Перейти к Уроку 25

Сначала пройди все секции и выбери хотя бы 1 вопрос

⬅️ Назад к Уроку 23
🎉
Новый тир
Новый тир достигнут!