为机器学习项目设置 Gitlab CI/CD

2023-08-22 14:09 459 阅读 ID:1353
磐创AI
磐创AI

本文演示了Gitlab上CI/CD的基本配置。持续集成(Continous Integration)和持续开发(Continous Development)是描述从开发到生产环境的几个变化的端到端过程的术语。CI/CD 自动执行代码集成的所有工作,例如集成测试、单元测试和回归测试,以及具有一组预定义标准的部署过程。因此,CI/CD 减少了人工工作量,以保持软件的质量。

本文重点介绍推理部分,你可能需要为模型开发部分添加更多层。但是,它几乎是相同的过程。

先决条件

请阅读这些资料以掌握 CI/CD 的概念

https://docs.gitlab.com/ee/ci/

https://cloud.google.com/architecture/mlops-continuous-delivery-and-automation-pipelines-in-machine-learning

  • 在 gitlab.com 创建 Gitlab 帐户
  • 创建用于Web api部署的 fly.io帐户(http://fly.io/)
  • 在 gitlab.com 上创建存储库
  • 克隆存储库

运行git clone git@gitlab.com:<your-username>/iris-api.git

  • 构建一个简单的 RESTFUL API

这是该项目的文件树

创建src/main.py

"""Iris Web API Service."""
from fastapi import FastAPI
from pydantic import BaseModel
import numpy as np
from src import distance, iris

dataset = iris.get_iris_data()

app = FastAPI()


class Item(BaseModel):
    """Input class for predict endpoint.

    Args:
        BaseModel (BaseModle): Inherited from pydantic
    """

    sepal_length: float
    sepal_width: float
    petal_length: float
    petal_width: float


@app.get("/")
def homepage():
    """Homepage for the web.

    Returns:
        str: Homepage
    """
    return "Homepage Iris Flower - tags 0.0.2"


@app.post("/predict/")
async def predict(item: Item):
    """Predict function for inference.

    Args:
        item (Item): dictionary of sepal dan petal data

    Returns:
        str: predict the target
    """
    sepal_length = item.sepal_length
    sepal_width = item.sepal_width
    petal_length = item.petal_length
    petal_width = item.petal_width

    data_input = np.array([[sepal_length, sepal_width, petal_length, petal_width]])

    result = distance.calculate_manhattan(dataset, data_input)
    return result

创建src/iris.py

"""Load iris dataset from scikit-learn."""
from sklearn import datasets


def get_iris_data():
    """Load iris dataset.

    Returns:
        set: consists of X, y, feature names, and target_names
    """
    iris = datasets.load_iris()
    x_data = iris.data
    y_label = iris.target
    features_names = ["sepal_length", "sepal_width", "petal_length", "petal_width"]
    target_names = iris.target_names

    return x_data, y_label, features_names, target_names


if __name__ == "__main__":
    x_data, y_label, features, target_names = get_iris_data()

    print("X", x_data)
    print("y", y_label)
    print("features", features)
    print("target_names", target_names)

创建src/distance.py

"""Distance module for calculating distance between data input and dataset."""
import numpy as np


def calculate_manhattan(iris_data: np.ndarray, input_data: np.ndarray):
    """Calculate the distance between 2 vectors using manhattan distance.

    Args:
        dataset (np.ndarray): Iris dataset
        input_data (np.ndarray): 1x4 matrix data input

    Returns:
        string: Return prediction
    """
    x_data, y_label, _, target_names = iris_data

    distance = np.sqrt(np.sum(np.abs(x_data - input_data), axis=1))
    distance_index = np.argsort(distance)
    y_pred = target_names[y_label[distance_index[0]]]

    return y_pred


if __name__ == "__main__":
    dataset = [
        np.array([[4.9, 3.0, 1.4, 0.2], [4.9, 3.0, 1.4, 0.9]]),
        [0, 0],
        ["sepal_length", "sepal_width", "petal_length", "petal_width"],
        ["setosa", "versicolor", "virginica"],
    ]
    sample_data = np.array([[4.9, 3.0, 1.4, 0.2]])
    print(calculate_manhattan(dataset, sample_data))

创建test/test_distance.py

import numpy as np
from src.iris import get_iris_data
from src.distance import calculate_manhattan 

def test_calculate_manhattan():
    dataset = get_iris_data()
    input_data = np.array([[4.9, 3.0, 1.4, 0.2]])
    result = calculate_manhattan(dataset, input_data)
    assert result == 'setosa'

创建 Dockerfile

FROM python:3.10

EXPOSE 8000

WORKDIR /app

COPY . .

RUN pip install -r requirements.txt

ENTRYPOINT ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port",  "8000"]

设置requirements.txt

# python
pydoclint>=0.0.10
pylint>=2.17.0
black>=22.6.0
pydocstyle>=6.1.1
pytest>=7.1.2

# web app
fastapi>=0.98.0
uvicorn>=0.22.0

# models
numpy>=1.21.6
scikit-learn>=1.2.2

创建fly.toml

app = "iris-api-demo-stg"
primary_region = "sin"

[build]
  dockerfile = "Dockerfile"

[http_service]
  internal_port = 8000
  force_https = true
  auto_stop_machines = true
  auto_start_machines = true
  min_machines_running = 0

在 Fly.io 上设置 Web 应用程序

首先,请按照此链接在你的计算机上安装flyctl :https://fly.io/docs/hands-on/install-flyctl/

然后,进行身份验证flyctl auth login

然后在此处为 Gitlab 创建个人访问令牌:https://fly.io/user/personal_access_tokens

将令牌保存到记事本中,稍后我们会将令牌添加到 gitlab 环境中。

现在,你需要创建 2 个应用程序:staging  和 production

Staging app

flyctl launch --auto-confirm --copy-config --dockerfile Dockerfile --name iris-api-demo-stg --now --org personal --region sin

Production app

flyctl launch --auto-confirm --copy-config --dockerfile Dockerfile --name iris-api-demo--now --org personal --region sin

最终,你将在 Fly.io 仪表板中看到类似这样的内容

不要忘记将访问令牌 Fly.io 添加到 gitlab 环境以进行部署。添加变量并将其命名为 FLY_TOKEN。

设置 CI/CD

drum-roll

现在,我们重点关注这里的主要内容,配置 CI/CD pipline。

  • 让我们创建一个名为gitlab-ci.ymlv1 的新文件
image: python:latest

docker-build:
  stage: build
  script:
  - echo "Build Docker"

code-test:
  stage: test
  script:
  - echo "Run Code Test"

production:
  stage: deploy
  environment: production
  script:
  - echo "Deploy to fly.io"

这是一个简单的 gitlab-ci,它运行你对远程存储库所做的每一次推送。它的作用是,当你推送一个更改时,将触发 3 个作业。docker 构建、代码测试和生产。

让我们深入了解一下运作原理。

image: python:latest:意味着所有这些作业都在 python 最新版本的 docker 镜像之上运行,你可以在 docker hub(https://hub.docker.com/_/python/tags)上找到该镜像。

docker-build:作业的名称。作业的名称可以是任何名称,你可以在单个 .yml 文件中创建多个作业。

stage表示该工作属于哪个阶段。CI/CD 管道中有 3 个常见阶段:构建、测试和部署。

environment用于指定该作业将运行的环境。你将获得具有特定环境的作业列表。这允许你部署要重新部署的提交。因此,如果在staging  或 production中出现问题,这将使问题更容易解决。

script允许你在容器中编写 shell 命令。想象一下一组脚本将在终端中运行。

完成后:

git add gitlab-ci.yml
git commit -m "add gitlab-ci.yml
git push

然后,你可以导航到管道选项卡

正如你所看到的,有 3 个绿色复选标记,表明作业已成功运行。如果失败,图标将为红叉。

管道详细页面

现在,你已经创建了一个简单的管道。

让我们创建一个通常用于 ML API 开发的管道。

image: python:latest

code-check:
  stage: build
  only:
    - merge_requests
  script:
  - echo "Build Docker"
  - pip install -r requirements.txt
  - pylint src --rcfile=.pylintrc
  - black src --check
  - pydocstyle src

code-test:
  stage: test
  only:
    - merge_requests
  script:
  - echo "Run Code Test"
  - pip install -r requirements.txt
  - pytest

staging:
  stage: deploy
  environment: staging
  only:
    - staging
  script:
  - echo "Deploy to fly.io in staging environment"
  - curl -L https://fly.io/install.sh | sh
  - bash
  - /root/.fly/bin/flyctl deploy --app iris-api-demo-stg --access-token $FLY_TOKEN

production:
  stage: deploy
  environment: production
  only:
    - tags
  script:
  - echo "Deploy to fly.io in production environment"
  - curl -L https://fly.io/install.sh | sh
  - /root/.fly/bin/flyctl deploy --app iris-api-demo --access-token $FLY_TOKEN

我们有4个作业:

  • code-check:此作业运行代码质量检查,例如使用pylint进行代码检查、使用black进行格式化和使用pydocstyle进行文档字符串检查。这旨在确保编写的代码符合指南。此作业仅在合并请求时运行。如果你只是推送到远程分支,它不会触发此作业。
  • code-test:接下来是代码测试,我们已经在test_main.py中创建了一个简单的单元测试,以确保我们创建的模块按预期运行。
  • staging:如果合并请求已被批准并合并到staging分支中,此作业将运行。它将自动部署到fly.io,使用staging应用程序。这允许你进行用户验收测试
  • production:最后,我们有一个production作业。其目的与staging类似。如果你在存储库中创建了一个标签,将触发此作业。

创建用于部署到生产 Web 应用程序的标签

创建合并请求并合并到暂存分支后。它将部署到临时应用程序。如果符合预期,你可以继续将请求合并到主分支,然后批准。完成后,你可以创建标签以部署到生产 Web 应用程序中。

结论

这就是如何在Gitlab上设置CI/CD的指南。这可能看起来很简单,我将创建越来越复杂的管道,涉及 MLOps,例如模型跟踪、数据版本控制、模型注册、模型监控等。

免责声明:作者保留权利,不代表本站立场。如想了解更多和作者有关的信息可以查看页面右侧作者信息卡片。
反馈
to-top--btn