Вступление и немного про себя
Всем привет! На связи снова 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 оркестратор поднимет еще контейнеры и будет балансировать автоматическим сам нагрузку между ними).
И т.д.
И самое главное для нас мы можем описать это в манифесте один раз и потом переиспользовать и даже передавать кому угодно чтобы они могли запустить у себя или же наоборот мы можем запустить у себя чужой манифест.
Подготовка ВМ или физического сервера оставляю на вас.
-
Открываем сайт https://k3s.io, на нем нам говорят что установка k3s не займет много времени.
Копируем команду и выполняем. Обычно я для своих установок добавляю --disable servicelb, но вам не обязательно это делать.
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --disable servicelb" sh -
Далее всего менее 30 секунд и кластер готов.
Прописываем sudo kubectl get nodes и видим что наш кластер готов!
Возможные проблемы при разворачивании
Возможно если вы выделили не очень много ресурсов, то сервер может чуть дольше подниматься, тут просто можно подождать минуту и сообщение как указано ниже уйдет.
Пример сообщения: couldn't get resource list for metrics.k8s.io/v1beta1: the server is currently unable to handle the request
Далее, чтобы мы могли работать удобно из под своей УЗ нам требуется скопировать 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 весьма приятным.
Всё почти готово для начала работы, но нужно еще на то устройств с которого планируете всё разворачивать поставить 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
Первым делом мы должны развернуть наше хранилище кода, для этого потребуется:
Скачать подготовленный git репозиторий со всеми необходимыми хелмами для нашего проекта и зайти в него.
git clone https://github.com/general-rj45/mini-ml-stand-GRJ45.git
cd mini-ml-stand-GRJ45
В репозитории можно увидеть множество директорий, почти всё это helm-charts кроме values и best-repo-ci. Я сделал иерархию удобную для себя что хелм чарты лежат в своих папках, а их values я вынес в отдельную папку чтобы удобнее с ними работать, она так и называется values.
Зачем helm charts?
Если коротко он позволяет собрать в кучу все манифесты для kubernetes и шаблонизировать их с различными условиями, переменными которые описаны в едином файле и потом применяются ко всем шаблонам внутри, к примеру как название релиза или имя неймспейса.
Чтобы развернуть Gitea в kubernetes нужно поправить под себя values.
vi values/values_gitea.yaml
Нет необходимости брать из хелм чарта весь 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
После того как заполнили values следует выполнить команду:
helm install gitea gitea-8.3.0/gitea/ -n gitea --create-namespace -f values/values_gitea.yaml
Через некоторое время, минут 5, пока бд установиться и gitea развернется, можно уже проверять Gitea по заданному выше адресу http://192.168.1.227:32339 и пробовать авторизоваться.
Также что всё корректно запустилось можно проверить командой kubeclt get pods -n gitea
На этом Gitea развернут, чуть позже еще вернёмся к нему, пока перейдем к другим компонентам.
Minio
Прежде чем разворачивать MLflow необходимо развернуть хранилище артефактов, для этого следует описать values:
vi minio-12.6.4/minio/values.yaml
Заполнить по примеру ниже:
#Выводим 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"
Выполнить команду:
helm install minio minio-12.6.4/minio/ -n minio --create-namespace -f values/values_minio.yaml
-
Проверить что всё pod работает и нет ошибок.
Можно также пройти по адресу хоста на котором стоит kubernetes по порту 32225 и проверить что есть возможность подключиться и авторизоваться.
та да
MLflow
Для управления процессом обучения и анализа его развернем MLflow.
По традиции открываем values, но уже для Mlflow
vi values/values_mlflow.yaml
Далее заполняем 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"
Далее выполняем:
helm install mlflow mlflow/ -n mlflow --create-namespace -f values/values_mlflow.yaml
Проверяем что всё работает.
Попробуем зайти в MLflow и авторизоваться.
Готово! Идём дальше.
Airfow
Если коротко то данный компонент нужен будет для управления, автоматизацией процессов машинного обучения (ML) и аналитики данных (ETL), ну если оооооочень коротко.
Начнём:
Прежде чем развертывать 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
)
Промотаем еще внизу и сделаем коммит нажатием на Сохранить правки.
Далее нужно настроить values для airflow. выполняем команду
vi values/values_airflow.yaml
Заполнить по примеру ниже 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
Далее выполняем:
helm install airflow airflow-14.2.5/airflow/ -n airflow --create-namespace -f values/values_airflow.yaml
Разворачиваться airflow может до 5-10 минут. Поэтому нужно подождать. Чтобы не обновлять команду постоянно вручную можно прописать:
kubectl get pods -n airflow -w
Всё поды в статусе Running, значит можем проверить airflow и попробовать авторизоваться
Собственно мы и зашли в airflow и сразу наш Dag подгрузился, может его запустить и проверить что он работает, для этого нужно нажать на имя Dag и перейти на его страницу.
Далее:
Нажимаем на пеключатель чтобы стал как на скришоте.
Запускаем Dag
По квадратику определяем его статус, темно зеленый значит что он успешно отработал.
И так с airflow пока всё, он полностью готов к работе!
Gitea act runner
И так, осталось развернуть только act runner через который мы будем строить наш CI/CD процессы. По сути act runner это повторения GitHub Actions в какой-то степени, поэтому многие моменты и настройки применимые к второму так же применимы и к первому, по крайнем мере я так понял, но нужно еще изучать.
Давайте же его развернем:
-
Перед созданием раннера нам нужно сделать токен, для этого нужно навестись на свой профиль и выбрать Панель управления.
Далее нужно перейти во вкладку Runners, нажать на Create new Runner и скопировать токен.
Далее открывает 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
Далее выполняем команду:
helm install gitea-runner gitea-runner/ -n gitea-runner --create-namespace -f values/values_gitea_runner.yaml
Проверяем что всё запустилось.
Также зайдем еще в Gitea во вкладку runners и проверим что он добавился.
Отлично! Работает, теперь можем перейти к CI.
Преднастройка для CI/CD
Наш 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
Перезапустить k3s чтобы настройки применились.
sudo systemctl restart k3s
Для того чтобы мы могли собирать образы через kaniko нужно добавить secret. Предварительно нужно перевести логин и пароль в base64, как в примере.
-
А также чтобы работал наш CI нужно перейти в настройки проекта
Промотать вниз и найти Actions (1) и поставить галочку и снова промотать вниз и обновить настойки (2).
Готово, теперь можно НАКОНЕЦ начать делать наш первый пайплайн.
Делаем первый Pipeline
Сейчас будет простой пример Pipeline в рамках которого запуститься под, выгрузит наш гит репозиторий, соберет образ из докерфайла и простого питоновского скрипта, а далее загрузит его в наш registry. И так приступим:
Так как мы при выполнении пайплайна будем использовать секреты, нужно их задать в нашем репозитории, для этого нужно:
Снова перейти в настройки репозитория:
Далее нам нужно нажать на Secrets и добавить секреты.
Хочу отметить что не нужно называть секреты заглавными буквами или чтобы они начинались с GITEA или GITHUB, вам GItea просто это не даст сделать.
Откроем наш старый репозиторий 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.
После того как сохранили изменения файла best-repo-ci/.gitea/workflows/pipeline.yaml переходим в actions, можно найти вверху экрана.
5. Далее в зависимости от того выполнился он или выполняется переходим в Open или Closed.
-
Здесь мы видим что пайплайн успешно завершился. Чтобы посмотреть подробности можем зайти в него.
Можем убедиться что всё завершилось корректно и ошибок нет.
Кстати удобный момент что Gitea прячет в логах всё что совпадает с секретом.
Теперь чтобы убедиться что образ действительно загрузился переходим на главную страницу нашего пользователя. И далее жмём пакеты.
Далее выбираем наш репозиторий
И можем увидеть что образ успешно загружен и тег у него как у номера запуска пайплайна.
Теперь сделаем простенький даг и попробуем запустить наш образ. Для это в репозиторий 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
)
Переходим в airflow, нажимаем на им нашего dag, далее активируем его (1) и нажимаем запустить. (2) и ждём кога отработает.
Здесь жмем на квадрат (1), далее на logs чтобы посмотреть логи (2). В целом проблем я не вижу, всё отработало.
Переходим в mlflow и можем увидеть что появились новые записи в нём.
-
Чтобы посмотреть его артефакты и проверить что он вообще сохраняет их, нажмем к примеру на последний 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 за то что помогал с идеями и порой накидывал простенькие шаблоны. (но бывала и путал он)
Всем спасибо! До новых встреч!