Skip to content

Commit e2d7f9e

Browse files
committed
feat(ml): add ml service
1 parent c792bdc commit e2d7f9e

9 files changed

Lines changed: 321 additions & 0 deletions

File tree

infra/terraform-dev/main.tf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ resource "azurerm_postgresql_flexible_server" "main" {
7171
}
7272
}
7373

74+
# PostgreSQL Extensions Configuration
75+
resource "azurerm_postgresql_flexible_server_configuration" "extensions" {
76+
name = "azure.extensions"
77+
server_id = azurerm_postgresql_flexible_server.main.id
78+
value = "pg_trgm,uuid-ossp,pgcrypto"
79+
}
80+
7481
# PostgreSQL Database
7582
resource "azurerm_postgresql_flexible_server_database" "main" {
7683
name = "simpledb"

ml-model-service/build.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/bin/bash
2+
set -e
3+
4+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5+
cd "$SCRIPT_DIR/../../app"
6+
7+
IMAGE_GHCR="ghcr.io/${OWNER_NAME,,}/${REPO_NAME,,}:${COMMIT_TAG}-${ENV_TAG}"
8+
IMAGE_DOCKERHUB="${DOCKER_USER}/${REPO_NAME,,}:${COMMIT_TAG}-${ENV_TAG}"
9+
10+
echo "Building ML Model Service Docker image..."
11+
docker build -t "$IMAGE_GHCR" .
12+
13+
docker tag "$IMAGE_GHCR" "ghcr.io/${OWNER_NAME,,}/${REPO_NAME,,}:${MOVING_TAG}"
14+
docker tag "$IMAGE_GHCR" "$IMAGE_DOCKERHUB"
15+
docker tag "$IMAGE_GHCR" "${DOCKER_USER}/${REPO_NAME,,}:${MOVING_TAG}"
16+
17+
TEST_CONTAINER="test_${COMMIT_TAG}_$$"
18+
echo "Testing container..."
19+
docker run -d --name "$TEST_CONTAINER" "$IMAGE_GHCR"
20+
sleep 15
21+
22+
# Test health endpoint
23+
echo "Testing health endpoint..."
24+
if docker exec "$TEST_CONTAINER" python -c "import requests; r = requests.get('http://localhost:8000/health'); assert r.status_code == 200" 2>/dev/null; then
25+
echo "✅ Health check passed"
26+
else
27+
echo "⚠️ Health check failed, but continuing..."
28+
fi
29+
30+
docker stop "$TEST_CONTAINER"
31+
docker rm "$TEST_CONTAINER"
32+
33+
echo "Pushing to registries..."
34+
echo "${GITHUB_PAT}" | docker login ghcr.io -u "${OWNER_NAME}" --password-stdin
35+
36+
docker push "$IMAGE_GHCR"
37+
docker push "ghcr.io/${OWNER_NAME,,}/${REPO_NAME,,}:${MOVING_TAG}"
38+
39+
echo "${DOCKER_PASS}" | docker login -u "${DOCKER_USER}" --password-stdin
40+
41+
docker push "$IMAGE_DOCKERHUB"
42+
docker push "${DOCKER_USER}/${REPO_NAME,,}:${MOVING_TAG}"

ml-model-service/deploy.sh

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/bin/bash
2+
set -e
3+
4+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5+
6+
if [ -z "$ENV_TAG" ]; then
7+
echo "ENV_TAG not set"
8+
exit 1
9+
fi
10+
11+
if [ "${ENV_TAG}" = "prod" ]; then
12+
RELEASE_NAME="ml-model-service-prod"
13+
exit 1
14+
elif [ "${ENV_TAG}" = "dev" ]; then
15+
RELEASE_NAME="ml-model-service-dev"
16+
else
17+
exit 1
18+
fi
19+
20+
21+
# Install Azure CLI (if not already installed)
22+
if ! command -v az &> /dev/null; then
23+
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash > /dev/null 2>&1
24+
fi
25+
26+
# Install kubectl (if not already installed)
27+
if ! command -v kubectl &> /dev/null; then
28+
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
29+
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
30+
rm kubectl
31+
fi
32+
33+
# Install Helm (if not already installed)
34+
if ! command -v helm &> /dev/null; then
35+
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash > /dev/null 2>&1
36+
fi
37+
38+
# Login to Azure
39+
az login --service-principal \
40+
-u ${AZURE_CLIENT_ID} \
41+
-p ${AZURE_CLIENT_SECRET} \
42+
--tenant ${AZURE_TENANT_ID} > /dev/null 2>&1
43+
44+
# Get AKS credentials
45+
az aks get-credentials \
46+
--resource-group ${AZURE_RESOURCE_GROUP} \
47+
--name ${AZURE_CLUSTER_NAME} \
48+
--overwrite-existing > /dev/null 2>&1
49+
50+
helm upgrade ml-model-service "$SCRIPT_DIR/helm-chart" \
51+
--install \
52+
--create-namespace \
53+
--namespace default \
54+
--set image.tag="$IMAGE_TAG" \
55+
--set image.repository="$IMAGE_REPO" \
56+
--history-max 3 \
57+
--wait \
58+
--timeout 5m \
59+
--atomic
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: v2
2+
name: ml-model-service
3+
description: ML Model Service for Content Quality Prediction
4+
type: application
5+
version: 1.0.0
6+
appVersion: "1.0.0"
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{{/*
2+
Expand the name of the chart.
3+
*/}}
4+
{{- define "ml-model-service.name" -}}
5+
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
6+
{{- end }}
7+
8+
{{/*
9+
Create a default fully qualified app name.
10+
*/}}
11+
{{- define "ml-model-service.fullname" -}}
12+
{{- if .Values.fullnameOverride }}
13+
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
14+
{{- else }}
15+
{{- $name := default .Chart.Name .Values.nameOverride }}
16+
{{- if contains $name .Release.Name }}
17+
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
18+
{{- else }}
19+
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
20+
{{- end }}
21+
{{- end }}
22+
{{- end }}
23+
24+
{{/*
25+
Create chart name and version as used by the chart label.
26+
*/}}
27+
{{- define "ml-model-service.chart" -}}
28+
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
29+
{{- end }}
30+
31+
{{/*
32+
Common labels
33+
*/}}
34+
{{- define "ml-model-service.labels" -}}
35+
helm.sh/chart: {{ include "ml-model-service.chart" . }}
36+
{{ include "ml-model-service.selectorLabels" . }}
37+
{{- if .Chart.AppVersion }}
38+
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
39+
{{- end }}
40+
app.kubernetes.io/managed-by: {{ .Release.Service }}
41+
{{- end }}
42+
43+
{{/*
44+
Selector labels
45+
*/}}
46+
{{- define "ml-model-service.selectorLabels" -}}
47+
app.kubernetes.io/name: {{ include "ml-model-service.name" . }}
48+
app.kubernetes.io/instance: {{ .Release.Name }}
49+
{{- end }}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: {{ include "ml-model-service.fullname" . }}
5+
namespace: {{ .Release.Namespace }}
6+
labels:
7+
{{- include "ml-model-service.labels" . | nindent 4 }}
8+
spec:
9+
replicas: {{ .Values.replicaCount }}
10+
selector:
11+
matchLabels:
12+
{{- include "ml-model-service.selectorLabels" . | nindent 6 }}
13+
template:
14+
metadata:
15+
labels:
16+
{{- include "ml-model-service.selectorLabels" . | nindent 8 }}
17+
annotations:
18+
# Force pod restart on upgrade by adding timestamp
19+
rollme: {{ randAlphaNum 5 | quote }}
20+
spec:
21+
containers:
22+
- name: ml-model-service
23+
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
24+
imagePullPolicy: {{ .Values.image.pullPolicy }}
25+
ports:
26+
- name: http
27+
containerPort: {{ .Values.service.targetPort }}
28+
protocol: TCP
29+
env:
30+
- name: PORT
31+
value: {{ .Values.env.PORT | quote }}
32+
- name: APP_VERSION
33+
value: {{ .Values.env.APP_VERSION | quote }}
34+
- name: LOG_LEVEL
35+
value: {{ .Values.env.LOG_LEVEL | quote }}
36+
resources:
37+
{{- toYaml .Values.resources | nindent 10 }}
38+
{{- with .Values.livenessProbe }}
39+
livenessProbe:
40+
{{- toYaml . | nindent 10 }}
41+
{{- end }}
42+
{{- with .Values.readinessProbe }}
43+
readinessProbe:
44+
{{- toYaml . | nindent 10 }}
45+
{{- end }}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{{- if .Values.ingress.enabled -}}
2+
apiVersion: networking.k8s.io/v1
3+
kind: Ingress
4+
metadata:
5+
name: {{ include "ml-model-service.fullname" . }}
6+
namespace: {{ .Release.Namespace }}
7+
labels:
8+
{{- include "ml-model-service.labels" . | nindent 4 }}
9+
{{- with .Values.ingress.annotations }}
10+
annotations:
11+
{{- toYaml . | nindent 4 }}
12+
{{- end }}
13+
spec:
14+
ingressClassName: {{ .Values.ingress.className }}
15+
{{- if .Values.ingress.tls }}
16+
tls:
17+
{{- range .Values.ingress.tls }}
18+
- hosts:
19+
{{- range .hosts }}
20+
- {{ . | quote }}
21+
{{- end }}
22+
secretName: {{ .secretName }}
23+
{{- end }}
24+
{{- end }}
25+
rules:
26+
{{- range .Values.ingress.hosts }}
27+
- host: {{ .host | quote }}
28+
http:
29+
paths:
30+
{{- range .paths }}
31+
- path: {{ .path }}
32+
pathType: {{ .pathType }}
33+
backend:
34+
service:
35+
name: {{ include "ml-model-service.fullname" $ }}
36+
port:
37+
number: {{ $.Values.service.port }}
38+
{{- end }}
39+
{{- end }}
40+
{{- end }}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: {{ include "ml-model-service.fullname" . }}
5+
namespace: {{ .Release.Namespace }}
6+
labels:
7+
{{- include "ml-model-service.labels" . | nindent 4 }}
8+
spec:
9+
type: {{ .Values.service.type }}
10+
ports:
11+
- port: {{ .Values.service.port }}
12+
targetPort: {{ .Values.service.targetPort }}
13+
protocol: TCP
14+
name: http
15+
selector:
16+
{{- include "ml-model-service.selectorLabels" . | nindent 4 }}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
replicaCount: 1
2+
3+
image:
4+
repository: karimzakzouk/ml-model-service
5+
pullPolicy: Always
6+
tag: "dev"
7+
8+
nameOverride: ""
9+
fullnameOverride: ""
10+
11+
service:
12+
type: ClusterIP
13+
port: 8000
14+
targetPort: 8000
15+
16+
ingress:
17+
enabled: true
18+
className: nginx
19+
annotations:
20+
cert-manager.io/cluster-issuer: "letsencrypt-prod"
21+
nginx.ingress.kubernetes.io/ssl-redirect: "true"
22+
hosts:
23+
- host: "ml.hankers.myaddr.tools"
24+
paths:
25+
- path: /
26+
pathType: Prefix
27+
tls:
28+
- secretName: ml-model-tls
29+
hosts:
30+
- ml.hankers.myaddr.tools
31+
32+
resources:
33+
limits:
34+
cpu: 500m
35+
memory: 512Mi
36+
requests:
37+
cpu: 250m
38+
memory: 256Mi
39+
40+
livenessProbe:
41+
httpGet:
42+
path: /health
43+
port: 8000
44+
initialDelaySeconds: 30
45+
periodSeconds: 10
46+
47+
readinessProbe:
48+
httpGet:
49+
path: /health
50+
port: 8000
51+
initialDelaySeconds: 10
52+
periodSeconds: 5
53+
54+
env:
55+
PORT: "8000"
56+
APP_VERSION: "v1.0"
57+
LOG_LEVEL: "INFO"

0 commit comments

Comments
 (0)