Search
Duplicate

MLflow와 Google Cloud Platform 연동(2) - 코드

태그
MLflow
Google Cloud Platform
Docker
<Table of Contents> 개요
이번 포스팅은 MLflow-GCP 간 연동에 대한 두 번째 글입니다. 앞선 포스팅을 읽지 않으신 경우 해당 링크를 확인해주세요!
이번에는 MLflow-GCP 간 연동에 필요한 Dockerfile과 쉘 스크립트를 작성하고 세부 내용을 확인하는 시간을 갖도록 하겠습니다.
아무래도 모든 코드에 대한 설명을 포함하기 때문에, 분량이 조금 많은 편입니다. (절대로 칭찬해달라고 애원하는 글은 아닙니다 ㅎㅎ…)
틈 나실 때 조금씩 읽어보시는 것도 하나의 방법이 될 것 같아요

개요

연동을 위해 필요한 코드 트리 구조는 아래와 같습니다.
. ├── .dockerignore ├── .envs │ ├── .gcp │ └── .tracking-server ├── Makefile ├── docker │ ├── Dockerfile │ └── scripts │ └── run-server.sh ├── docker-compose.yaml ├── poetry.lock ├── pyproject.toml └── scripts ├── create-server.sh └── startup-script.sh
.envs 폴더 내에는 MLflow tracking server / Artifact Registry / Artifact Store / Backend store 접근 + VM 인스턴스 생성을 위해 필요한 환경 변수를 저장한 파일이 있습니다.
docker 폴더 내에는 VM 인스턴스에서 실행할 Dockerfile과 MLflow tracking server에 대한 스크립트(run-server.sh)가 있습니다.
scripts 폴더 내에는 VM 인스턴스 생성을 수행하는 스크립트와(create-server.sh) VM 인스턴스 실행 직후 실행되는 스크립트(startup-script.sh)가 있습니다.
각 파일의 역할 및 세부 코드는 아래에서 설명하겠습니다.

환경변수 설정

1.
./envs/.gcp 파일
# GCP Environment variable GCP_PROJECT_ID=e2eml-jiho PROJECT_NAME=e2eml-jiho DOCKER_IMAGE_NAME=${PROJECT_NAME}-mlflow GCP_DOCKER_REGISTRY_NAME=mlflow-jiho GCP_DOCKER_REGISTERY_URL=asia-northeast3-docker.pkg.dev/${GCP_PROJECT_ID}/${GCP_DOCKER_REGISTRY_NAME}/${DOCKER_IMAGE_NAME} # IMAGE_NAME=<gcp-machine-image-name> IMAGE_NAME=ubuntu-2204-jammy-v20240126 # IMAGE_PROJECT_ID=<gcp-machine-image-project-name> IMAGE_PROJECT_ID=ubuntu-os-cloud # Compute Instance (GCP VM Instance) VM_NAME=${PROJECT_NAME}-mlflow REGION=asia-northeast3 ZONE=asia-northeast3-c LABELS=env=production,project=e2eml-jiho,task=jiho-mlflow-tracking-server MACHINE_TYPE=n2-standard-4 NETWORK=default SUBNET=default
Plain Text
복사
GCP 리소스 접근을 위한 환경변수, VM Instance에서 사용할 운영체제 및 관련 환경변수를 정의한 파일입니다.
프로젝트 ID, 프로젝트 이름, 리전, Zone, Machine 타입 등 환경변수를 본인의 것에 알맞게 바꿔주세요.
2. .envs/.tracking-server 파일
# MLFlow MLFLOW_HOST=0.0.0.0 MLFLOW_PORT=6100 # Postgres (Backend store) # POSTGRES_CONNECTION_NAME=<SQL Instance-connection-name> (GCP -> SQL -> SQL Instance -> Connection Name) POSTGRES_CONNECTION_NAME=e2eml-jiho:asia-northeast3:jiho-mlflow-backend-store1 # POSTGRES_USER=<Username for SQL Instance> POSTGRES_USER=mlflow # POSTGRES_PASSWORD_SECRET_NAME=<postgres-secret-name> (GCP -> Security -> Secret manager -> Secret name) POSTGRES_PASSWORD_SECRET_NAME=postgresql-mlflow-password # Private IP Address for SQL instance (GCP -> SQL -> SQL Instance -> Private IP Address) POSTGRES_HOST=10.81.160.3 POSTGRES_PORT=5432 # POSTGRES_DATABASE_NAME=<Database name for SQL instance> POSTGRES_DATABASE_NAME=mlflow # ARTIFACT_STORE=gs://<bucket-name> (GCP -> Cloud Storage) ARTIFACT_STORE=gs://e2eml-jiho-mlflow-prac
Plain Text
복사
본 파일은 아래의 환경 변수들을 저장하고 있습니다.
MLflow 서버의 호스트 정보
Backend store 및 Artifact store 접근을 위한 환경 변수
아래 환경변수는 반드시 자신의 것으로 바꿔주세요!
POSTGRES_CONNECTION_NAME
POSTGRES_USER
POSTGRES_PASSWORD_SECRET_NAME
POSTGRES_HOST
POSTGRES_DATABASE_NAME
ARTIFACT_STORE

Makefile

Makefile은 다음의 역할을 수행합니다.
도커 이미지를 빌드 (build)
도커 이미지를 Artifact Registry에 push (push)
GCP VM instance(Tracking server) 생성 (deploy)
코드 내용은 다음과 같습니다.
include .envs/.gcp include .envs/.tracking-server export SHELL := /usr/bin/env bash HOSTNAME := $(shell hostname) ifeq (, $(shell which docker-compose)) DOCKER_COMPOSE_COMMAND = docker compose else DOCKER_COMPOSE_COMMAND = docker-compose endif # Returns true if the stem is a non-empty environment variable, or else raises an error. guard-%: @#$(or ${$*}, $(error $* is not set)) # Deploy MLFlow using GCP Cloud Run deploy: push ./scripts/create-server.sh # Build docker containers with docker-compose build: $(DOCKER_COMPOSE_COMMAND) build # Push docker image to GCP Container Registery. Requires IMAGE_TAG to be specified. push: guard-IMAGE_TAG build @gcloud auth configure-docker asia-northeast3-docker.pkg.dev --quiet @docker tag "${DOCKER_IMAGE_NAME}:latest" "$${GCP_DOCKER_REGISTRY_URL}:$${IMAGE_TAG}" @docker push "$${GCP_DOCKER_REGISTRY_URL}:$${IMAGE_TAG}"
Makefile
복사
.envs 폴더에 저장되어 있는 환경변수 값을 읽어들입니다.
build target은 docker-compose.yaml 파일을 통해 도커 이미지를 build하는 역할을 수행합니다.
./docker/Dockerfile
gcloud cli, gsutils 설치
poetry 설치
poetry를 통한 의존성 패키지 설치
MLflow 서버 구동(./docker/scripts/run-server.sh)
push target은 prerequisites로서, guard-IMAGE_TAG target과 build target을 지정합니다.
guard-<keyword> target: make command 수행 시, <keyword>에 해당하는 인자를 넘겨주는지 확인합니다. 이때, IMAGE_TAG는 도커 이미지의 tag를 의미합니다.
push recipe의 첫 번째 커맨드는 Artifact Registry에 도커 이미지를 push할 수 있도록 권한을 부여받습니다.

Docker-compose

앞서 Makefile에서 build target이 recipe로서 docker-compose.yaml 파일을 참조한다고 하였습니다.
코드를 한 번 살펴보지요!
version: "3.8" services: app: &app user: root hostname: "${HOSTNAME}" image: "${DOCKER_IMAGE_NAME}" container_name: mlflow-tracking-server build: context: . dockerfile: ./docker/Dockerfile env_file: - .envs/.gcp - .envs/.tracking-server ipc: host network_mode: host init: true volumes: - ./:/app
Docker
복사
build 과정에서 ./docker/Dockerfile 을 참조하도록 설정하였습니다.
또한, env_file 키워드를 통해 .envs 폴더에 저장된 환경변수를 Dockerfile에서 참조할 수 있도록 하였습니다.
빌드되는 컨테이너 이름의 경우 mlflow-tracking-server로, 이미지 이름의 경우 ${DOCKER_IMAGE_NAME}으로 지정하였습니다. (여기서는 e2eml-jiho-mlflow)

Dockerfile

앞서 Docker-compose command를 통한 이미지 build 과정에서 Dockerfile을 참조한다고 말씀드렸지요
한 번 그 내용을 살펴봅시다.
FROM python:3.9-slim ENV HOME=/root ENV \ PYTHONUNBUFFERED=1 \ VIRTUAL_ENV="${HOME}/venv" \ PATH="$HOME/venv/bin:/usr/local/gcloud/google-cloud-sdk/bin/:${PATH}" \ PYTHONPATH="/app:${PYTHONPATH}" \ DEBIAN_FRONTEND="noninteractive" \ LC_ALL=C.UTF-8 \ LANG=C.UTF-8 \ BUILD_POETRY_LOCK="${HOME}/poetry.lock.build" RUN apt-get -qq update \ && apt-get -qq -y install git curl wget vim \ && rm -rf /var/lib/apt/lists/* \ && apt-get -qq -y clean # Install gcloud and gsutils RUN curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-462.0.1-linux-x86_64.tar.gz \ && mv /google-cloud-cli-462.0.1-linux-x86_64.tar.gz /tmp/google-cloud-cli-462.0.1-linux-x86_64.tar.gz RUN mkdir -p /usr/local/gcloud \ && tar -C /usr/local/gcloud -zxf /tmp/google-cloud-cli-462.0.1-linux-x86_64.tar.gz \ && /usr/local/gcloud/google-cloud-sdk/install.sh \ --usage-reporting false --command-completion true --bash-completion true --path-update true --quiet # Make sure gsutil will use the default service account RUN echo "[GoogleCompute]\nservice_account = default" > /etc/boto.cfg COPY ./docker/scripts/*.sh / RUN chmod +x /*.sh \ && HOME=/tmp \ && pip install --no-cache-dir poetry==1.7.1 \ && mkdir -p /app COPY ./pyproject.toml ./*.lock /app/ WORKDIR /app RUN python3.9 -m venv "${VIRTUAL_ENV}" \ && pip install --upgrade pip setuptools \ && poetry install --no-dev \ && cp poetry.lock "${BUILD_POETRY_LOCK}" \ && rm -rf $HOME/.cache/* ENTRYPOINT ["/bin/bash"] CMD ["/run-server.sh"] EXPOSE 6100
Docker
복사
python:3.9-slim 이미지를 사용합니다.
컨테이너에서 참조할 환경변수 값을 정의합니다.
PATH 환경변수에 gcloud CLI를 사용하기 위한 경로를 등록한다는 점에 주목해주세요!
gcloud CLI와 gstuil 도구를 다운로드하는 코드를 수행합니다.
gcloud CLI: Google Cloud 리소스를 만들고 관리하기 위한 도구 모음입니다.
gsutil: 명령줄에서 Cloud Storage에 액세스하는 데 사용할 수 있는 Python 애플리케이션입니다.
./docker/scripts/run-server.sh 스크립트를 컨테이너의 root 폴더에 복사합니다.
run-server.sh 스크립트를 컨테이너에서 실행할 수 있도록 권한을 변경합니다.
poetry 라이브러리를 설치한 후, pyproject.toml에 명시한 패키지를 설치합니다.
Entrypoint를 지정한 후, run-server.sh 스크립트를 구동합니다.
이때, 컨테이너는 6100번 포트에서 실행됩니다.

create-server.sh

Makefile에서 build와 push target을 수행한 다음, deploy target를 수행한다고 말씀드렸어요
이때, deploy target에서 ./scripts/create-server.sh 스크립트를 실행합니다.
중요한 옵션 위주로 설명을 드리도록 하겠습니다~
#!/usr/bin/env bash set -euo pipefail # Create VM gcloud compute instances create "${VM_NAME}" \ --image "${IMAGE_NAME}" \ --image-project "${IMAGE_PROJECT_ID}" \ --boot-disk-auto-delete \ --labels="${LABELS}" \ --machine-type="${MACHINE_TYPE}" \ --zone="${ZONE}" \ --no-address \ --network="${NETWORK}" \ --subnet="${SUBNET}" \ --scopes https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/cloud.useraccounts.readonly,https://www.googleapis.com/auth/cloudruntimeconfig \ --project="${GCP_PROJECT_ID}" \ --metadata-from-file=startup-script=./scripts/startup-script.sh \ --metadata \ gcp_docker_registery_url="${GCP_DOCKER_REGISTERY_URL}:${IMAGE_TAG}",\ mlflow_host="${MLFLOW_HOST}",\ mlflow_port="${MLFLOW_PORT}",\ artifact_store="${ARTIFACT_STORE}",\ postgres_user="${POSTGRES_USER}",\ postgres_host="${POSTGRES_HOST}",\ postgres_port="${POSTGRES_PORT}",\ postgres_database_name="${POSTGRES_DATABASE_NAME}",\ postgres_password_secret_name="${POSTGRES_PASSWORD_SECRET_NAME}"
Shell
복사
VM 인스턴스를 생성하는 명령어를 수행합니다.
Google Cloud Compute Engine → 여기서는 MLflow tracking server 역할
--no-address keyword
해당 키워드를 사용할 경우, External IP(외부 접근 IP)를 허용하지 않습니다.
보안 측면에서, 해당 인스턴스에 접근할 수 있는 클라이언트를 필터링하기 위함입니다.
따라서, 해당 VM 인스턴스에 접근하기 위해서는 트래픽을 HTTPS 커넥션에 연결할 수 있도록 하는 터널링 과정을 수행해야 합니다.
--metadata keyword
VM 인스턴스에서 사용할 메타데이터 정보를 저장할 수 있습니다.
Key=value의 형식으로 저장할 수 있습니다.
--metadata-from-file
startup-script를 key로 startup script의 파일 경로를 value로 지정합니다.
startup-script는 magic keyword로서, VM 인스턴스가 생성되고 실행될 때 자동으로 실행되는 스크립트입니다.
해당 스크립트를 실행하여 VM 인스턴스를 생성할 경우, 다음 그림과 같이 Key-value 형식의 메타데이터가 인스턴스에 등록된 것을 확인할 수 있습니다.

startup-script.sh

VM 인스턴스가 생성되고 실행될 때 startup-script 스크립트가 자동으로 수행된다고 말씀드렸습니다.
그럼, 해당 스크립트의 내용을 살펴보시죠!
#!/bin/bash MLFLOW_HOST=$(curl --silent http://metadata.google.internal/computeMetadata/v1/instance/attributes/mlflow_host -H "Metadata-Flavor: Google") MLFLOW_PORT=$(curl --silent http://metadata.google.internal/computeMetadata/v1/instance/attributes/mlflow_port -H "Metadata-Flavor: Google") ARTIFACT_STORE=$(curl --silent http://metadata.google.internal/computeMetadata/v1/instance/attributes/artifact_store -H "Metadata-Flavor: Google") POSTGRES_USER=$(curl --silent http://metadata.google.internal/computeMetadata/v1/instance/attributes/postgres_user -H "Metadata-Flavor: Google") POSTGRES_HOST=$(curl --silent http://metadata.google.internal/computeMetadata/v1/instance/attributes/postgres_host -H "Metadata-Flavor: Google") POSTGRES_PORT=$(curl --silent http://metadata.google.internal/computeMetadata/v1/instance/attributes/postgres_port -H "Metadata-Flavor: Google") POSTGRES_DATABASE_NAME=$(curl --silent http://metadata.google.internal/computeMetadata/v1/instance/attributes/postgres_database_name -H "Metadata-Flavor: Google") POSTGRES_PASSWORD_SECRET_NAME=$(curl --silent http://metadata.google.internal/computeMetadata/v1/instance/attributes/postgres_password_secret_name -H "Metadata-Flavor: Google") GCP_DOCKER_REGISTERY_URL=$(curl --silent http://metadata.google.internal/computeMetadata/v1/instance/attributes/gcp_docker_registery_url -H "Metadata-Flavor: Google") curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh echo '=========== Downloading Docker Image ============' gcloud auth configure-docker --quiet asia-northeast3-docker.pkg.dev echo "GCP_DOCKER_REGISTERY_URL = ${GCP_DOCKER_REGISTERY_URL}" time sudo docker pull "${GCP_DOCKER_REGISTERY_URL}" sudo docker run --init --network host --ipc host --user root --hostname "$(hostname)" --privileged \ --log-driver=gcplogs \ -e POSTGRES_USER="${POSTGRES_USER}" \ -e POSTGRES_PASSWORD=$(gcloud secrets versions access latest --secret="${POSTGRES_PASSWORD_SECRET_NAME}") \ -e POSTGRES_HOST="${POSTGRES_HOST}" \ -e POSTGRES_PORT="${POSTGRES_PORT}" \ -e POSTGRES_DATABASE_NAME="${POSTGRES_DATABASE_NAME}" \ -e ARTIFACT_STORE="${ARTIFACT_STORE}" \ -e MLFLOW_HOST="${MLFLOW_HOST}" \ -e MLFLOW_PORT="${MLFLOW_PORT}" \ ${GCP_DOCKER_REGISTERY_URL}
Shell
복사
VM 인스턴스에서는 저장된 환경변수 값을 읽기 위하여, 다음의 command를 수행할 수 있습니다.
curl http://metadata.google.internal/computeMetadata/v1/instance/attributes/<환경변수 이름>
이때, 헤더 값을 반드시 보내야 하며, 그 값은 아래와 같습니다.
"Metadata-Flavor: Google"
Makefile의 push target을 통해 Artifact Registry에 저장된 도커 이미지를 가져옵니다.
도커 이미지를 pull하기 위하여, gcloud auth configure-docker command를 수행합니다.
분명하게 말씀드리지만, 해당 도커 이미지는 Dockerfile 파트에서 설명드린 내용입니다.
Artifact Registry에서 가져온 도커 이미지를 컨테이너에서 구동합니다.
sudo docker run command
이때, Backend store / MLflow host & port / Artifact store에 대한 환경변수 값을 등록합니다.

run-server.sh

startup-script 스크립트의 궁극적인 목적은 VM 인스턴스 내부에서 MLflow 서버를 구동하는 것입니다.
#!/bin/bash set -euo pipefail BACKEND_STORE_URI="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DATABASE_NAME}" mlflow server \ --backend-store-uri "'${BACKEND_STORE_URI}'" \ --default-artifact-root "${ARTIFACT_STORE}" \ --host "${MLFLOW_HOST}" \ --port "${MLFLOW_PORT}"
Shell
복사
MLflow 서버 구동 시, Backend store와 Artifact store의 주소를 명시하는 것을 확인하실 수 있습니다.

정리

이번 포스팅에서는 MLflow-GCP 간 연동에 필요한 Dockerfile과 기타 스크립트에 대한 내용을 다루어보았습니다.
다음 포스팅에서는 실제로 MLflow-GCP 간 연동을 수행하는 시간을 가지도록 하겠습니다.
그럼 안녕히 가세요
위로 올라가기
뒤로 가기
Reference