Что-то сделала нейронка
Что-то сделала нейронка

Вступление и немного про себя

Всем привет! На связи снова General RJ45 с новым прекрасным решением, но на сей раз по теме ML и аналитики.

Я MLOps/DevOps, на моем счету уже два законченных ML проекта и за это время я достаточно много поработал с аналитиками и ML инженерами, да и вообще над созданием ML и аналитических решений и могу сказать что у меня сформировалось своё представление о данных решения и я вижу какие проблемы возникают в данных процессах и что нужно разработчикам для их более эффективной работы, как пример это прозрачность всего процесса чтобы они могли видеть весь процесс от начала до конца и контролировать его.

В рамках данной статьи хочу рассказать как можно максимально (ну или немного не просто) просто поднять ML стенд с нуля от разворачивания кластера kubernetes до полноценной готовой инфраструктуры, на которой можно будет вести полноценную разработку и ETL процессов, и различных обучений моделей и их переобучений.

Стек того что мы поднимем в рамках этой статьи, также будут и другие инструменты как Nginx, Postgresql но мы их учитываем как часть компонентов ниже:

  • K3s - это легковесный Kubernetes (K8s), которая предназначена для использования в ограниченных ресурсах или встроенных системах. Собственно на нём и будет всё крутится

  • Airflow - это открытая платформа управления рабочими процессами (workflow) с возможностью планирования и выполнения задач. Она позволяет создавать, планировать и мониторить рабочие процессы, включая задачи связанные с обработкой данных, машинным обучением и аналитикой. В нашем случае он будет работать через KubernetesPodOperator.

  • MLflow - это открытая платформа для управления жизненным циклом моделей машинного обучения. Она предоставляет возможности для тренировки, отслеживания, управления и развертывания моделей машинного обучения в различных средах.

  • Minio - это распределенная система хранения объектов (object storage), которая позволяет хранить и управлять большими объемами данных. В нашем случае это будет хранилище артефактов для MLflow.

  • Gitea - это легковесная система управления версиями и хостинга Git репозиториев. Для нас он будет выполнять роль по мимо хранения Git репозиториев еще как registry и CI.

  • Kaniko - это инструмент для сборки контейнеров без необходимости использования привилегированных режимов или доступа к демону Docker. Благодаря ему сможем собирать образы не прибегая к Docker, что в рамках kubernetes очень полезно, так как Docker требует привилегий для запуска. В рамках статьи будет использоваться мой образ kaniko основанный на образе другого образа kaniko.

Со списком ознакомились, тогда погнали:

K3S и подготовка к работе

Для данной задачи в рамках статьи я сделал VM с 4 CPU, 12 RAM, 100 Gb, ОС ubuntu 20.04.

В который раз мы начнем с K3S.

Немного про K3S

K3s (с офф сайт) — это сертифицированный дистрибутив Kubernetes с высокой доступностью, предназначенный для производственных рабочих нагрузок в автоматических, ограниченных по ресурсам, удаленных местах или внутри устройств IoT. (взято с офф сайт).

k3s (описание от меня) - это оркестратор контейнеров который может:

  • Запускать контейнеры (для контейнера мы можем также задать переменные, директории с нашего хоста или вообще с удаленного сервера, контейнер который будет запускаться до старта нашего основного контейнера, благодаря чему мы можем как пример подготовить какие либо файлы или выполнить скрипт для корректной работы нашего основного контейнера).

  • Ставить лимиты по ресурсам для контейнеров. (Как пример что приложение не сможет потреблять больше 2 Gb RAM или 2 CPU).

  • Задавать политики рестарта контейнеров.

  • Управлять доступностью к приложениям по сети.

  • Позволяет удобно читать логи.

  • Анализировать метрики.

  • Автомасштабированием контейнеров при повышении нагрузки ( к примеру если наше приложение в контейнере начинает грузится скажем под 80% по RAM или CPU оркестратор поднимет еще контейнеры и будет балансировать автоматическим сам нагрузку между ними).

  • И т.д.

  • И самое главное для нас мы можем описать это в манифесте один раз и потом переиспользовать и даже передавать кому угодно чтобы они могли запустить у себя или же наоборот мы можем запустить у себя чужой манифест.

Подготовка ВМ или физического сервера оставляю на вас.

  1. Открываем сайт https://k3s.io, на нем нам говорят что установка k3s не займет много времени.

    Ну изи же
    Ну изи же
  1. Копируем команду и выполняем. Обычно я для своих установок добавляю --disable servicelb, но вам не обязательно это делать.

curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --disable servicelb" sh -
  1. Далее всего менее 30 секунд и кластер готов.

Установка K3S
Установка K3S
  1. Прописываем sudo kubectl get nodes и видим что наш кластер готов!

Что еще нужно для счастья?
Что еще нужно для счастья?
Возможные проблемы при разворачивании

Возможно если вы выделили не очень много ресурсов, то сервер может чуть дольше подниматься, тут просто можно подождать минуту и сообщение как указано ниже уйдет.

Пример сообщения: couldn't get resource list for metrics.k8s.io/v1beta1: the server is currently unable to handle the request

  1. Далее, чтобы мы могли работать удобно из под своей УЗ нам требуется скопировать kubeconfig в свою домашнюю папку и выполнить пару команд:

mkdir ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $USER:$USER .kube/config
echo "export KUBECONFIG=~/.kube/config" >> ~/.bashrc
source ~/.bashrc
kubectl get node
Советы бывалого

Можно выгрузить на свою локальную машину конфигфайл /etc/rancher/k3s/k3s.yaml и с личного устройства удобно управлять kubernetes, только нужно предварительно установить kubectl, еще есть очень удобный инструмент https://k8slens.dev который сделает управление kubernetes весьма приятным.

Работает!
Работает!
  1. Всё почти готово для начала работы, но нужно еще на то устройств с которого планируете всё разворачивать поставить git и helm. На ubuntu это делается так:

#git
sudo apt-get update
sudo apt-get install -y git

#helm
wget https://get.helm.sh/helm-v3.12.1-linux-amd64.tar.gz
tar xvzf helm-v3.12.1-linux-amd64.tar.gz
sudo mv linux-amd64/helm /usr/local/bin && rm -rf 

Gitea

Первым делом мы должны развернуть наше хранилище кода, для этого потребуется:

  1. Скачать подготовленный git репозиторий со всеми необходимыми хелмами для нашего проекта и зайти в него.

git clone https://github.com/general-rj45/mini-ml-stand-GRJ45.git
cd mini-ml-stand-GRJ45
  1. В репозитории можно увидеть множество директорий, почти всё это helm-charts кроме values и best-repo-ci. Я сделал иерархию удобную для себя что хелм чарты лежат в своих папках, а их values я вынес в отдельную папку чтобы удобнее с ними работать, она так и называется values.

Зачем helm charts?

Если коротко он позволяет собрать в кучу все манифесты для kubernetes и шаблонизировать их с различными условиями, переменными которые описаны в едином файле и потом применяются ко всем шаблонам внутри, к примеру как название релиза или имя неймспейса.

  1. Чтобы развернуть Gitea в kubernetes нужно поправить под себя values.

vi values/values_gitea.yaml
  1. Нет необходимости брать из хелм чарта весь values, можно вынести только нужные нам значения, в данном случае я сделал так.

Про values

Еще раз, values это единый файл для helm charts в котором хранятся все переменные которые в нём используются и через него происходит управление каким будет развертывание. Если есть потребность посмотреть на все значения values то они находятся тут gitea-8.3.0/gitea/values.yaml или прописать команду helm show values airflow-14.2.5/airflow с указанием пути к хелму

Хочу сразу отметить в этой статье мы не будем использовать какие либо балансеры или еще сложные методы чтобы вывести сервисы наружу, они буду все выводится посредством kubernetes через стандартный NodePort.

Немного теории

Хочу рассказать про методы которыми можно вывести сервис из kubernetes:

ClusterIP — это тип службы по умолчанию в Kubernetes. Он создает службу внутри кластера Kubernetes, к которой могут обращаться другие приложения в кластере, не разрешая внешний доступ.

NodePort - открывает определенный порт на всех узлах в кластере, и любой трафик, отправляемый на этот порт, перенаправляется в службу. Доступ к службе невозможен с IP-адреса кластера. 

LoadBalancer — это стандартный способ предоставления службы Kubernetes извне, чтобы к ней можно было получить доступ через Интернет. Если вы используете Google Kubernetes Engine (GKE), это создает балансировщик сетевой нагрузки с одним IP-адресом, к которому могут получить доступ внешние пользователи, а затем они перенаправляются на соответствующий узел в вашем кластере Kubernetes. Доступ к LoadBalancer можно получить так же, как к ClusterIP или NodePort.

#Стоит отметить что GITEA__actions__ENABLED и отвечает за CI
#Важно указывать адрес по которому будет идти непосредственно обращение к Gitea

statefulset:
  env:
  - name: GITEA__actions__ENABLED
    value: "true"
  - name: GITEA__server__ROOT_URL
    value: "http://192.168.1.23:32339"
  - name: GITEA__server__SSH_DOMAIN
    value: "192.168.1.223"
  - name: GITEA__server__DOMAIN
    value: "192.168.1.223"

#Настройка Storage для Gitea
persistence:
  enabled: true
  existingClaim:
  size: 10Gi
  accessModes:
    - ReadWriteOnce
  labels: {}
  annotations: {}
  storageClass:
  subPath:

#Админский пароль, задается при первом разворачивании
gitea:
  admin:
    # existingSecret: gitea-admin-secret
    existingSecret:
    username: gitea_admin
    password: pomenya_parol
    email: "gitea@local.domain"

#Настройки базы
postgresql:
  enabled: true
  global:
    postgresql:
      auth:
        password: gitea
        database: gitea
        username: gitea
      service:
        ports:
          postgresql: 5432
  primary:
    persistence:
      size: 10Gi

#Порт http и ssh по которому можно будет подключиться к gitea
service:
  http:
    type: NodePort
    nodePort: 32339
  ssh:
    type: NodePort
    nodePort: 32338
Непрошенный совет.

Вообще Gitea можно полностью настроить через ENV, об этом можно подробнее почитать тут https://docs.gitea.com/installation/install-with-docker?_highlight=gitea__s#managing-deployments-with-environment-variables

  1. После того как заполнили values следует выполнить команду:

helm install gitea gitea-8.3.0/gitea/ -n gitea --create-namespace -f values/values_gitea.yaml
  1. Через некоторое время, минут 5, пока бд установиться и gitea развернется, можно уже проверять Gitea по заданному выше адресу http://192.168.1.227:32339 и пробовать авторизоваться.

Также что всё корректно запустилось можно проверить командой kubeclt get pods -n gitea

Проверка gitea через kubeclt
Проверка gitea через kubeclt
Gitea развернут, легко, не правда ли?
Gitea развернут, легко, не правда ли?

На этом Gitea развернут, чуть позже еще вернёмся к нему, пока перейдем к другим компонентам.

Minio

  1. Прежде чем разворачивать MLflow необходимо развернуть хранилище артефактов, для этого следует описать values:

vi minio-12.6.4/minio/values.yaml
  1. Заполнить по примеру ниже:

#Выводим Minio наружу чтобы могли к нему подключаться
service:
  type: NodePort
  ports:
    api: 9000
    console: 9001
  nodePorts:
    api: "32224"
    console: "32225"

#Указываем размер PVC под Minio
persistence:
  size: 8Gi

#Данные входа в Minio
auth:
  rootUser: mlflow
  rootPassword: "pomenya_parol"

#Бакет по умолчанию
defaultBuckets: "mlflow"
  1. Выполнить команду:

helm install minio minio-12.6.4/minio/ -n minio --create-namespace -f values/values_minio.yaml
  1. Проверить что всё pod работает и нет ошибок.

    Проверка через kubeclt
    Проверка через kubeclt

Можно также пройти по адресу хоста на котором стоит kubernetes по порту 32225 и проверить что есть возможность подключиться и авторизоваться.

та да

MLflow

Для управления процессом обучения и анализа его развернем MLflow.

  1. По традиции открываем values, но уже для Mlflow

vi values/values_mlflow.yaml
  1. Далее заполняем values по примеру ниже.

Сразу отметим что в MLflow по умолчанию нет авторизации, поэтому она будет настроена через Nginx. В values mlflow > password нужно будет ввести зашифрованный пароль, так как Nginx нужно передавать пароль зашифрованным. Это можно сделать на сайте к примеру https://hostingcanada.org.

mlflow:
  image: generalrj45/mlflow-psycopg2 
  tag: v2.4.1-r2 
  port: 8080
  NodePort: 32226
  serviceType: NodePort
  username: mlflow
  #password pomenya_parol
  password: "{SHA}Z/xepKPsTx+MJw81WnDgIoGtZo8="

s3:
  MLFLOW_S3_ENDPOINT_URL: "http://minio.minio:9000"
  s3ArtifactRoot: "s3://mlflow/"
  accessKey: "mlflow" 
  secretKey: "pomenya_parol"
  MLFLOW_S3_IGNORE_TLS: false

database:
  port: 5432
  url: "mlflow-postgresql"
  username: "mlflow"
  password: "pomenya_parol"
  db_name: "mlflow"

global:
  imageRegistry: ""
  imagePullSecrets: []
  storageClass: ""
  postgresql:
    auth:
      postgresPassword: ""
      username: "mlflow"
      password: "pomenya_parol"
      database: "mlflow"
  1. Далее выполняем:

helm install mlflow mlflow/ -n mlflow --create-namespace -f values/values_mlflow.yaml
  1. Проверяем что всё работает.

Попробуем зайти в MLflow и авторизоваться.

Ля красиво
Ля красиво

Готово! Идём дальше.

Airfow

Если коротко то данный компонент нужен будет для управления, автоматизацией процессов машинного обучения (ML) и аналитики данных (ETL), ну если оооооочень коротко.

Начнём:

  1. Прежде чем развертывать airflow нужно подготовить репозиторий в Gitea и создать в ней директорию в которой будут хранится Dags для работы. В целом это не сложно.

Создадим новый новый репозиторий.

Дадим имя репозиторию.

Чуть пролистаем внизу и поставим создать README, это позволит создать прям из Gitea файлы, как раз так и положим тестовый даг.

Добавим новый файл.

Пропишем путь до дага.

Заполним его.

from datetime import datetime
from airflow import DAG
from airflow.contrib.operators.kubernetes_pod_operator import KubernetesPodOperator

default_args = {
    'owner': 'airflow',
    'depends_on_past': False,
    'start_date': datetime(2023, 6, 24),
    'retries': 1,
}

dag = DAG('kubernetes_hello_world', default_args=default_args, schedule_interval=None)

task = KubernetesPodOperator(
    task_id='hello_world_task',
    name='hello-world-pod',
    namespace='airflow',
    image='busybox:latest',
    cmds=['echo', 'Hello, World!'],
    dag=dag
)

Промотаем еще внизу и сделаем коммит нажатием на Сохранить правки.

  1. Далее нужно настроить values для airflow. выполняем команду

vi values/values_airflow.yaml
  1. Заполнить по примеру ниже values.

В данном развертывании есть возможность синхронизации дагов с гит репозиторием откуда будут скачиваться даги, но чтобы он корректно работал нужно прописать такие вещи как:

repository - http://login:password@url.git

branch - имя ветки

name - имя папки с дагами внутри контейнера

path - путь до dags в репозитории

#стандартный логи и пароль для входа
auth:
  username: "generalrj45"
  password: "pomenya_parol"

git:
  image:
    registry: docker.io
    repository: bitnami/git
    tag: 2.36.0-debian-10-r3
  dags:
    enabled: true
    repositories:
      - repository: http://gitea_admin:pomenya_parol@192.168.1.227:32339/gitea_admin/best-repo-ci.git
        branch: main
        name: example-dags-sync
        path: /dags/
  clone:
    extraEnvVars:
    - name: GIT_SSL_NO_VERIFY
      value: "1"
    resources: {}
  sync:
    interval: 60
    command: []
    args: []
    extraVolumeMounts: []
    extraEnvVars:
    - name: GIT_SSL_NO_VERIFY
      value: "1"

serviceAccount:
  ## @param serviceAccount.create Enable creation of ServiceAccount for Airflow pods
  ##
  create: true

#Где будут выполняться даги, в жанном случае на каждый даг будет
# подниматься под
executor: KubernetesExecutor

#сервисная уз для создания подов
rbac:
  create: true

#Порт для подключения
service:
  type: NodePort
  nodePorts:
    http: 32222

#Настройка базы постгрес
postgresql:
  enabled: true
  auth:
    enablePostgresUser: false
    username: bn_airflow
    password: "lolkekcheburek"
    database: bitnami_airflow
    existingSecret: ""
  architecture: standalone

#настройка редиса
redis:
  enabled: true
  auth:
    enabled: true
    ## Redis® password (both master and slave). Defaults to a random 10-character alphanumeric string if not set and auth.enabled is true.
    ## It should always be set using the password value or in the existingSecret to avoid issues
    ## with Airflow.
    ## The password value is ignored if existingSecret is set
    password: "lolkekcheburek2"
    existingSecret: ""
  architecture: standalone

#По умолчания в чарте хелма не реализовано сохранение логов
#Поэтому я вложил манифест для pvc который ниже мы монтируем для логов
web:
  extraVolumeMounts:
    - name: airflow-logs
      mountPath: /opt/bitnami/airflow/logs

  extraVolumes:
    - name: airflow-logs
      persistentVolumeClaim: 
        claimName: airflow-logs

scheduler:
  extraVolumeMounts:
    - name: airflow-logs
      mountPath: /opt/bitnami/airflow/logs

  extraVolumes:
    - name: airflow-logs
      persistentVolumeClaim: 
        claimName: airflow-logs
        
worker:
  extraVolumeMounts:
    - name: airflow-logs
      mountPath: /opt/bitnami/airflow/logs

  extraVolumes:
    - name: airflow-logs
      persistentVolumeClaim: 
        claimName: airflow-logs
  1. Далее выполняем:

helm install airflow airflow-14.2.5/airflow/ -n airflow --create-namespace -f values/values_airflow.yaml
  1. Разворачиваться airflow может до 5-10 минут. Поэтому нужно подождать. Чтобы не обновлять команду постоянно вручную можно прописать:

kubectl get pods -n airflow -w

Всё поды в статусе Running, значит можем проверить airflow и попробовать авторизоваться

К сожалению с темной темой кривовато работает airfow.
К сожалению с темной темой кривовато работает airfow.

Собственно мы и зашли в airflow и сразу наш Dag подгрузился, может его запустить и проверить что он работает, для этого нужно нажать на имя Dag и перейти на его страницу.

Далее:

  1. Нажимаем на пеключатель чтобы стал как на скришоте.

  2. Запускаем Dag

  3. По квадратику определяем его статус, темно зеленый значит что он успешно отработал.

И так с airflow пока всё, он полностью готов к работе!

Gitea act runner

И так, осталось развернуть только act runner через который мы будем строить наш CI/CD процессы. По сути act runner это повторения GitHub Actions в какой-то степени, поэтому многие моменты и настройки применимые к второму так же применимы и к первому, по крайнем мере я так понял, но нужно еще изучать.

Давайте же его развернем:

  1. Перед созданием раннера нам нужно сделать токен, для этого нужно навестись на свой профиль и выбрать Панель управления.

  2. Далее нужно перейти во вкладку Runners, нажать на Create new Runner и скопировать токен.

  1. Далее открывает values раннера.

vi values/values_gitea_runner.yaml

Вообще по классике act runer очень хочет запускать всё из под docker, но K3S по умолчанию на containerD, да и вообще мне лично не нравится запускать где-то докер и уже в нём запускать контейнеры в которых запускаются задачи, так как это тяжелее поддерживать, поэтому в рамках статьи будет использовать грубо говоря шелл режим, то есть там где запущен act-runner там и будут запускаться задачи.

Но если всё же вам нужен act-runner с docker то просто поменяйте privileged в true, закомментируйте образ который стоит в values и расскоментируйте образ выше.

deployment:
  #need to enable privileges for Docker in Docker
  #image: ocker.io/generalrj45/gitea-kubernetes-runner:v0.11
  image: docker.io/generalrj45/gitea-act-runner-shell-k8s-kubectl:v0.1
  resources:
    requests:
      memory: "500Mi"
      cpu: "250m"
    limits:
      memory: "10000Mi"
      cpu: "4000m"
    privileged: false
    
config:
  #хост и токен для подключения к gitea
  GITEA_HOST: http://192.168.1.227:32339
  GITEA_TOKEN: yjaFdN5YxzEwKWpkusl4jsLZ1Dks7o1Em6HFvSCa

#storage для хранения данных раннера и подключения
pvc:
  name: gitea-data-runner
  size: 5G
  storageClassName: local-path

ServiceAccount: gitea-runner-service-account
  1. Далее выполняем команду:

helm install gitea-runner gitea-runner/ -n gitea-runner --create-namespace -f values/values_gitea_runner.yaml
  1. Проверяем что всё запустилось.

Также зайдем еще в Gitea во вкладку runners и проверим что он добавился.

Можно начинать делать контент
Можно начинать делать контент

Отлично! Работает, теперь можем перейти к CI.

Преднастройка для CI/CD

  1. Наш K3S по умолчанию не может доверять нашему registry , а также ему еще нужно предать логин и пароль для нашего registry чтобы была возможность как загружать, так и выгружать образы. Для этого нам нужно будет:

Выполнить команду и наполнить по примеру ниже:

vi /etc/rancher/k3s/registries.yaml
mirrors:
  "192.168.1.227:32339":
    endpoint:
      - "http://192.168.1.227:32339"
 
configs:
  "http://192.168.1.227:32339":
    auth:
      username: gitea_admin
      password: pomenya_parol
    tls:
      insecure_skip_verify: true
  1. Перезапустить k3s чтобы настройки применились.

sudo systemctl restart k3s
  1. Для того чтобы мы могли собирать образы через kaniko нужно добавить secret. Предварительно нужно перевести логин и пароль в base64, как в примере.

  2. А также чтобы работал наш CI нужно перейти в настройки проекта

  3. Промотать вниз и найти Actions (1) и поставить галочку и снова промотать вниз и обновить настойки (2).

Готово, теперь можно НАКОНЕЦ начать делать наш первый пайплайн.

Делаем первый Pipeline

Сейчас будет простой пример Pipeline в рамках которого запуститься под, выгрузит наш гит репозиторий, соберет образ из докерфайла и простого питоновского скрипта, а далее загрузит его в наш registry. И так приступим:

  1. Так как мы при выполнении пайплайна будем использовать секреты, нужно их задать в нашем репозитории, для этого нужно:

Снова перейти в настройки репозитория:

Далее нам нужно нажать на Secrets и добавить секреты.

Хочу отметить что не нужно называть секреты заглавными буквами или чтобы они начинались с GITEA или GITHUB, вам GItea просто это не даст сделать.

  1. Откроем наш старый репозиторий best-repo-ci и добавим следующие файлы.

Простой докерфайл по пути best-repo-ci/image/Dockerfile

FROM ghcr.io/mlflow/mlflow:v2.4.1

COPY test-mlflow.py .

CMD ["python", "test-mlflow.py"]

Питоновский скрипт по пути best-repo-ci/image/test-mlflow.py. В нём следуют поменять на ваш адрес mlflow.

P.s. Да, там пароль и логин в открытом виде, я знаю. В рамках демонстрации я думаю это не критично, в целом его можно и в кубер спрятать или в airflow в будущем.

P.s.s Данный код можете найти в скачанном ранее репозитории в директории best-repo-ci

import os
from random import random, randint
import mlflow

if __name__ == "__main__":
    print("Running the test script ...")

    # Входные данные для аутентификации
    username = "mlflow"
    password = "pomenya_parol"

    # Аутентификация в MLflow
    mlflow.set_tracking_uri(f"http://{username}:{password}@192.168.1.227:32226")

    #Create directory for artifacts
    if not os.path.exists("artifact_folder"):
        os.makedirs("artifact_folder")

    #Test parametes
    mlflow.log_param("param1", randint(0, 100))

    #Test metrics
    mlflow.log_metric("metric1", random())
    mlflow.log_metric("metric1", random())
    mlflow.log_metric("metric1", random())
    mlflow.log_metric("metric1", random())

    #Test artifact
    with open("artifact_folder/test.txt", "w") as f:
        f.write("hello world!")
    mlflow.log_artifacts("artifact_folder")

Опишем наш пайплайн по пути best-repo-ci/.gitea/workflows/pipeline.yaml. Тут нужно поменять адрес 192.168.1.227:32339 на свой и указать https при необходимости.

Вообще в .gitea/workflow хранятся все пайплайны, их может быть много и под разные условия, ветки.

name: myapp
on:
  push:
    branches:
      - main
jobs:
  docker:
    runs-on: self-hosted
    steps:
      - name: Run kubectl apply
        env:
          SECRET_GIT_USER: ${{ secrets.SECRET_GIT_USER }}
          SECRET_GIT_PASSWORD: ${{ secrets.SECRET_GIT_PASSWORD }}
        run: |
          GIT_REPO_GITEA=$(echo "$GITHUB_REPOSITORY" | sed -e 's/\//-/g' -e 's/_/-/g')
          kubectl run -it build-$GIT_REPO_GITEA-$GITHUB_RUN_NUMBER \
          --image=generalrj45/kaniko-git-bash:v0.4 \
          --env="NAME_IMAGE=192.168.1.227:32339/$GITHUB_REPOSITORY" \
          --env="NAME_TAG=$GITHUB_RUN_NUMBER" \
          --env="GIT_URL=http://$SECRET_GIT_USER:$SECRET_GIT_PASSWORD@192.168.1.227:32339/$GITHUB_REPOSITORY.git" \
          --env="GIT_USERNAME=$SECRET_GIT_USER" \
          --env="GIT_PASSWORD=$SECRET_GIT_PASSWORD" \
          --env="REGISTRY_USERNAME=$SECRET_GIT_USER" \
          --env="REGISTRY_PASSWORD=$SECRET_GIT_PASSWORD" \
          --env="REGISTRY_URL=http://192.168.1.227:32339" \
          --restart=OnFailure
          kubectl delete pod build-$GIT_REPO_GITEA-$GITHUB_RUN_NUMBER   
      - name: Delete pod
        run: |
          GIT_REPO_GITEA=$(echo "$GITHUB_REPOSITORY" | sed -e 's/\//-/g' -e 's/_/-/g')
          kubectl delete pod build-$GIT_REPO_GITEA-$GITHUB_RUN_NUMBER
  
Если интересно что происходит в коде выше

Если коротко мы говорим что при пуше в ветку main запустить пайплайн, который запускается на раннере с меткой self-hosted.

Далее на первом шаге "Run kubectl apply" через env мы передаем ему два секрета.

Вторым шагом, мы просто удаляем ранее созданный под.

Кстати пока вы тут хочу отметить для чего перед kubectl мы задаем переменную GIT_REPO_GITEA, так как gitea даёт имя репозитория со знаками " _ " и " / ", которые kubernetes ой как не любит, поэтому мы эти знаки меняем на " - ".

Ну еще можно заметить используемые GITHUB_REPOSITORY и GITHUB_RUN_NUMBER переменные, эти переменные передаются в раннер для каждого запуска свои, так что от них можно отталкиваться при построении CI.

  1. После того как сохранили изменения файла best-repo-ci/.gitea/workflows/pipeline.yaml переходим в actions, можно найти вверху экрана.

5. Далее в зависимости от того выполнился он или выполняется переходим в Open или Closed.

  1. Здесь мы видим что пайплайн успешно завершился. Чтобы посмотреть подробности можем зайти в него.

    Пусть время вас не смущает, я одновременно писал и разрабатывал, так что время может быть большим между шагами.
    Пусть время вас не смущает, я одновременно писал и разрабатывал, так что время может быть большим между шагами.
    1. Можем убедиться что всё завершилось корректно и ошибок нет.

Кстати удобный момент что Gitea прячет в логах всё что совпадает с секретом.

  1. Теперь чтобы убедиться что образ действительно загрузился переходим на главную страницу нашего пользователя. И далее жмём пакеты.

  1. Далее выбираем наш репозиторий

  1. И можем увидеть что образ успешно загружен и тег у него как у номера запуска пайплайна.

  1. Теперь сделаем простенький даг и попробуем запустить наш образ. Для это в репозиторий best-repo-ci в директорию dags добавим еще один dag.

Я дам ему такое имя best-repo-ci/dags/test-mlflow-ci.py.

Здесь уже нужно будет вам подправить под свой образ там где image.

from datetime import datetime
from airflow import DAG
from airflow.contrib.operators.kubernetes_pod_operator import KubernetesPodOperator

default_args = {
    'owner': 'airflow',
    'depends_on_past': False,
    'start_date': datetime(2023, 6, 24),
    'retries': 1,
}

dag = DAG('test-mlflow-ci', default_args=default_args, schedule_interval=None)

task = KubernetesPodOperator(
    task_id='test_mlflow',
    name='test_mlflow',
    namespace='airflow',
    image='192.168.1.227:32339/gitea_admin/best-repo-ci:47',
    dag=dag
)
  1. Переходим в airflow, нажимаем на им нашего dag, далее активируем его (1) и нажимаем запустить. (2) и ждём кога отработает.

  1. Здесь жмем на квадрат (1), далее на logs чтобы посмотреть логи (2). В целом проблем я не вижу, всё отработало.

  1. Переходим в mlflow и можем увидеть что появились новые записи в нём.

  1. Чтобы посмотреть его артефакты и проверить что он вообще сохраняет их, нажмем к примеру на последний run valuable-lark-488, далее Artifacts (1), далее на test.txt (2) и наконец можем увидеть что файл есть и мы можем посмотреть его содержимое (3).

    Кстати также можно зайти в minio и посмотреть файлы там.

Итог

Я устал
Я устал

Вы всё таки дошли до сюда, ура. Давайте подведем итог.

И так, теперь у нас имеется собственный mini-ml-stand для разработки машинного обучения (ML) и процессов обработки данных (ETL). Кроме того, мы приобрели собственный сервер для управления репозиториями Git с функциональностью непрерывной интеграции и развертывания (CI/CD), а также собственный реестр и кластер Kubernetes. Классно да?

В целом можно обойтись и меньшим количеством ресурсов для развертывания всех компонентов приложений, как видно на моем текущем htop, правда это без значительной нагрузки.

Могу сказать от себя что это я затеял просто ради интереса и может помочь кому-то, как по мне получилось весьма неплохо, к тому же я смог поработать более плотно с gitea/helm/kaniko, написал несколько хелм чартов для mlflow и gitear runner, надел образов и загрузил в докер хаб, и я надеюсь мои наработки смогут действительно кому-то помочь.

Отмечу дополнительно, что данную инсталляцию не стоит применять в проде, так как многие моменты требует доработок.

Не знаю что еще сказать, ну разве что если эта статья наберет 150 плюсов, напишу как это всё добро поднять в закрытом контуре.

Хочу сказать спасибо ребятам своим Андрею и Артёму что помогли мне при возникших проблемах и не послали и спасибо ChatGPT за то что помогал с идеями и порой накидывал простенькие шаблоны. (но бывала и путал он)

Всем спасибо! До новых встреч!

Комментарии (0)