init
This commit is contained in:
0
ado/azure-pipeline.docker.yaml.j2
Normal file
0
ado/azure-pipeline.docker.yaml.j2
Normal file
55
ado/azure-pipeline.k8s.yaml.j2
Normal file
55
ado/azure-pipeline.k8s.yaml.j2
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
trigger: none
|
||||||
|
|
||||||
|
## [SH] Import Self-hosting resources by DevOps Org. ==================================
|
||||||
|
|
||||||
|
pool: default
|
||||||
|
|
||||||
|
variables:
|
||||||
|
- group: SKI_ACR_CREDENTIAL_GRP
|
||||||
|
- name: GLOBAL_REGISTRY_URL # ACR 주소
|
||||||
|
value: {{ regi }}
|
||||||
|
- name: GLOBAL_REPOSITORY # 이미지 이름 ((프로젝트명)/(dev/prod 등)/(Repos or 이미지 명) --> skiwebacr.azurecr.io/devops/sample)
|
||||||
|
value: {% if regi_repo %}{{ regi_repo | trim('/') }}/{% endif %}node-{{ node_id }}-main
|
||||||
|
- name: DEPLOY_K8S # 배포할 K8s 이름 (pipelines -> environment 에서 확인 가능)
|
||||||
|
value: {{ k8s_name }}
|
||||||
|
- name: DEPLOY_NAMESPACE # 배포할 K8s namespace (pipelines -> environment -> Resources 에서 확인 가능)
|
||||||
|
value: {{ namespace }}
|
||||||
|
- name: DEPLOY_MANIFESTS_PATH # Manifest 파일 위치 (./manifests/dev/* or ./manifests/prd/*)
|
||||||
|
value: ./k8s/*
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
- name: IMAGE_TAG
|
||||||
|
displayName: "컨테이너 이미지 태그"
|
||||||
|
type: string
|
||||||
|
default: $(Build.SourceVersion)
|
||||||
|
|
||||||
|
resources:
|
||||||
|
repositories:
|
||||||
|
- repository: pipeline-template # In a different organization
|
||||||
|
endpoint: pipeline-template
|
||||||
|
type: git
|
||||||
|
name: DevOps_PF/pipeline-template
|
||||||
|
ref: main
|
||||||
|
|
||||||
|
##=================================================================================
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- template: pipeline/v2/ci/python/PipContainerBuildStage.yaml@pipeline-template
|
||||||
|
parameters:
|
||||||
|
DOCKERFILE_NAME: Dockerfile # Dockerfile 위치
|
||||||
|
CONTAINER_REGISTRY_URL: $(GLOBAL_REGISTRY_URL) # ACR URL
|
||||||
|
CONTAINER_REPOSITORY: $(GLOBAL_REPOSITORY) # ACR Repository
|
||||||
|
CONTAINER_TAG: {{ '${{ parameters.IMAGE_TAG }}' }} # 컨테이너 태그
|
||||||
|
|
||||||
|
COMMON_CONTAINER_REGISTRY_PASSWORD: $(COMMON_ACR_PW)
|
||||||
|
WEB_CONTAINER_REGISTRY_PASSWORD: $(WEB_ACR_PW)
|
||||||
|
|
||||||
|
- template: pipeline/v2/cd/k8s/ManifestDeployStage.yaml@pipeline-template
|
||||||
|
parameters:
|
||||||
|
CONTAINER_REGISTRY_URL: $(GLOBAL_REGISTRY_URL) # ACR URL
|
||||||
|
CONTAINER_REPOSITORY: $(GLOBAL_REPOSITORY) # ACR Repository
|
||||||
|
CONTAINER_TAG: {{ '${{ parameters.IMAGE_TAG }}' }} # 이미지 태그
|
||||||
|
|
||||||
|
ENVIRONMENT_NAME: $(DEPLOY_K8S) # 배포할 K8s 이름 (pipeline -> environment)
|
||||||
|
NAMESPACE: $(DEPLOY_NAMESPACE) # 배포할 k8s namespace
|
||||||
|
MANIFESTS_DIR: $(DEPLOY_MANIFESTS_PATH) # Manifest 파일 위치
|
||||||
14
agent/Dockerfile.j2
Normal file
14
agent/Dockerfile.j2
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
FROM {{ base_image | default("python:3.11-slim") }}
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
{% set pip_opts = "" %}
|
||||||
|
{% if pip_registry %}
|
||||||
|
{% set pip_opts = "--index-url " ~ pip_registry %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir -U pip
|
||||||
|
RUN pip install --no-cache-dir {{ pip_opts }} uvicorn fastapi
|
||||||
|
RUN pip install --no-cache-dir {{ pip_opts }} -r requirements.txt
|
||||||
|
|
||||||
|
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||||
10
agent/src_main.py.j2
Normal file
10
agent/src_main.py.j2
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
def root():
|
||||||
|
"""
|
||||||
|
This is an api that says, "Hello!"
|
||||||
|
"""
|
||||||
|
return {"response": "Hello ! {{ node_name }} !"}
|
||||||
4
common/.gitignore.j2
Normal file
4
common/.gitignore.j2
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.env
|
||||||
53
common/Jenkinsfile.docker.j2
Normal file
53
common/Jenkinsfile.docker.j2
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
parameters {
|
||||||
|
string(name: 'BRANCH_NAME', defaultValue: '', description: '빌드할 브랜치명(비우면 main)')
|
||||||
|
string(name: 'GIT_COMMIT_HASH', defaultValue: '', description: '빌드할 커밋 해시(비우면 최신 커밋 기준)')
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
sh 'git fetch --all'
|
||||||
|
def checkoutBranch = params.BRANCH_NAME?.trim() ? params.BRANCH_NAME.trim() : 'main'
|
||||||
|
def commitHash = params.GIT_COMMIT_HASH?.trim()
|
||||||
|
if (commitHash) {
|
||||||
|
echo "브랜치(${checkoutBranch})의 커밋 해시(${commitHash})로 체크아웃합니다."
|
||||||
|
sh "git fetch origin ${checkoutBranch}"
|
||||||
|
sh "git checkout ${checkoutBranch}"
|
||||||
|
sh "git checkout ${commitHash}"
|
||||||
|
} else {
|
||||||
|
echo "브랜치(${checkoutBranch})의 최신 커밋으로 체크아웃합니다."
|
||||||
|
sh "git fetch origin ${checkoutBranch}"
|
||||||
|
sh "git checkout ${checkoutBranch}"
|
||||||
|
commitHash = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
|
||||||
|
}
|
||||||
|
env.BRANCH_NAME = checkoutBranch
|
||||||
|
env.GIT_COMMIT_HASH = commitHash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Build & Deploy') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
def composeProject = "{{ node_id }}-" + (env.BRANCH_NAME ?: "") + "{{ node_type }}"
|
||||||
|
|
||||||
|
writeFile file: '.env', text: """
|
||||||
|
BRANCH_NAME=${env.BRANCH_NAME}
|
||||||
|
GIT_COMMIT_HASH=${env.GIT_COMMIT_HASH}
|
||||||
|
"""
|
||||||
|
sh "cat .env"
|
||||||
|
sh "cat docker-compose.yml"
|
||||||
|
sh "docker compose -p ${composeProject} --env-file .env down --rmi all"
|
||||||
|
sh "docker compose -p ${composeProject} build --no-cache"
|
||||||
|
sh "docker compose -p ${composeProject} --env-file .env up --force-recreate --remove-orphans -d"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
cleanWs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
168
common/Jenkinsfile.k8s.j2
Normal file
168
common/Jenkinsfile.k8s.j2
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
pipeline {
|
||||||
|
agent { label 'py-kaniko' } // JCasC 파드 템플릿: jnlp / ci-python / tooling / kaniko
|
||||||
|
|
||||||
|
options { timestamps() }
|
||||||
|
|
||||||
|
parameters {
|
||||||
|
string(name: 'BRANCH_NAME', defaultValue: '', description: '빌드할 브랜치명(비우면 main)')
|
||||||
|
string(name: 'GIT_COMMIT_HASH', defaultValue: '', description: '빌드할 커밋 해시(비우면 최신 커밋 기준)')
|
||||||
|
}
|
||||||
|
|
||||||
|
environment {
|
||||||
|
// 고정 값(노드 생성 시점에 Jinja로 박힘)
|
||||||
|
NODE_ID = '{{ node_id }}'
|
||||||
|
NODE_TYPE = '{{ node_type }}'
|
||||||
|
NAMESPACE = '{{ namespace }}'
|
||||||
|
REGISTRY = '{{ regi }}'
|
||||||
|
platform_namespace = '{{ platform_namespace }}'
|
||||||
|
|
||||||
|
// ✅ imagePullSecret 이름도 "메소드에서 전달"되도록 Jinja로 박힘
|
||||||
|
// (create_node_project_files(extra_context={"image_pull_secret":"..."}) 로 전달)
|
||||||
|
IMAGE_PULL_SECRET = '{{ image_pull_secret | default("acr-pull") }}'
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
container('jnlp') {
|
||||||
|
sh '''
|
||||||
|
set -e
|
||||||
|
git fetch --all || true
|
||||||
|
'''
|
||||||
|
script {
|
||||||
|
def checkoutBranch = params.BRANCH_NAME?.trim() ? params.BRANCH_NAME.trim() : 'main'
|
||||||
|
def commitHash = params.GIT_COMMIT_HASH?.trim()
|
||||||
|
|
||||||
|
if (commitHash) {
|
||||||
|
sh """
|
||||||
|
git fetch origin ${checkoutBranch}
|
||||||
|
git checkout ${checkoutBranch}
|
||||||
|
git checkout ${commitHash}
|
||||||
|
"""
|
||||||
|
} else {
|
||||||
|
sh """
|
||||||
|
git fetch origin ${checkoutBranch}
|
||||||
|
git checkout ${checkoutBranch}
|
||||||
|
"""
|
||||||
|
commitHash = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
env.BRANCH_NAME = checkoutBranch
|
||||||
|
env.GIT_COMMIT_HASH = commitHash
|
||||||
|
|
||||||
|
env.RELEASE_NAME = "node-${NODE_ID}-${env.BRANCH_NAME}"
|
||||||
|
env.IMAGE_REPO = "${REGISTRY}/node.${NODE_ID}.${env.BRANCH_NAME}"
|
||||||
|
env.IMAGE_TAG = env.GIT_COMMIT_HASH
|
||||||
|
env.FULL_IMAGE = "${env.IMAGE_REPO}:${env.IMAGE_TAG}"
|
||||||
|
|
||||||
|
echo "RELEASE_NAME=${env.RELEASE_NAME}"
|
||||||
|
echo "FULL_IMAGE=${env.FULL_IMAGE}"
|
||||||
|
echo "IMAGE_PULL_SECRET=${env.IMAGE_PULL_SECRET}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build Image (Kaniko)') {
|
||||||
|
steps {
|
||||||
|
container('kaniko') {
|
||||||
|
retry(3) {
|
||||||
|
sh '''
|
||||||
|
set -e
|
||||||
|
/kaniko/executor \
|
||||||
|
--context=$WORKSPACE \
|
||||||
|
--dockerfile=Dockerfile \
|
||||||
|
--destination=$FULL_IMAGE \
|
||||||
|
--cache=true \
|
||||||
|
--snapshot-mode=time
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Deploy (Manifests)') {
|
||||||
|
steps {
|
||||||
|
|
||||||
|
container('tooling') {
|
||||||
|
sh '''
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ✅ B) imagePullSecret 복제 (platform -> ${NAMESPACE})
|
||||||
|
# secret 이름도 IMAGE_PULL_SECRET로 통일
|
||||||
|
if [ -n "${IMAGE_PULL_SECRET}" ] && ! kubectl -n "${NAMESPACE}" get secret "${IMAGE_PULL_SECRET}" >/dev/null 2>&1; then
|
||||||
|
echo "[INFO] Copying imagePullSecret '${IMAGE_PULL_SECRET}' platform -> ${NAMESPACE} ..."
|
||||||
|
{% raw %}
|
||||||
|
kubectl -n "${platform_namespace}" get secret "${IMAGE_PULL_SECRET}" -o go-template='{{ index .data ".dockerconfigjson" }}' > .dockercfg.b64
|
||||||
|
{% endraw %}
|
||||||
|
base64 -d .dockercfg.b64 > .dockerconfigjson
|
||||||
|
kubectl -n "${NAMESPACE}" create secret generic "${IMAGE_PULL_SECRET}" \
|
||||||
|
--type=kubernetes.io/dockerconfigjson \
|
||||||
|
--from-file=.dockerconfigjson
|
||||||
|
rm -f .dockercfg.b64 .dockerconfigjson
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ✅ C) 배포 전 Deployment generation 저장
|
||||||
|
PREV_GEN=""
|
||||||
|
if kubectl -n "${NAMESPACE}" get deploy "${RELEASE_NAME}" >/dev/null 2>&1; then
|
||||||
|
PREV_GEN="$(kubectl -n "${NAMESPACE}" get deploy "${RELEASE_NAME}" -o jsonpath='{.metadata.generation}' 2>/dev/null || true)"
|
||||||
|
fi
|
||||||
|
echo "[INFO] PREV_GEN=${PREV_GEN:-<none>} deploy=${RELEASE_NAME}"
|
||||||
|
|
||||||
|
# ✅ D) repo의 manifests 템플릿을 렌더링 후 apply
|
||||||
|
# - manifests/*.yaml 은 Jinja로 이미 한번 렌더된 상태(노드 생성 시점)
|
||||||
|
# - 여기서는 배포마다 바뀌는 값(RELASE_NAME, FULL_IMAGE, BRANCH_NAME)만 envsubst로 주입
|
||||||
|
rm -rf .rendered && mkdir -p .rendered
|
||||||
|
|
||||||
|
# envsubst 치환 대상 화이트리스트
|
||||||
|
export RELEASE_NAME FULL_IMAGE BRANCH_NAME
|
||||||
|
|
||||||
|
for f in manifests/*.yaml; do
|
||||||
|
echo "[INFO] render $f"
|
||||||
|
envsubst '${RELEASE_NAME} ${FULL_IMAGE} ${BRANCH_NAME}' < "$f" > ".rendered/$(basename "$f")"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "---- Rendered files ----"
|
||||||
|
ls -al .rendered
|
||||||
|
|
||||||
|
kubectl -n "${NAMESPACE}" apply -f .rendered/
|
||||||
|
|
||||||
|
# ✅ E) 롤아웃 대기(Deployment가 이 이름으로 생성된다는 전제)
|
||||||
|
kubectl -n "${NAMESPACE}" rollout status deploy "${RELEASE_NAME}" --timeout=10m
|
||||||
|
|
||||||
|
# ✅ F) generation 비교 → 안 바뀌면 rollout restart
|
||||||
|
POST_GEN="$(kubectl -n "${NAMESPACE}" get deploy "${RELEASE_NAME}" -o jsonpath='{.metadata.generation}' 2>/dev/null || true)"
|
||||||
|
echo "[INFO] POST_GEN=${POST_GEN:-<none>} deploy=${RELEASE_NAME}"
|
||||||
|
|
||||||
|
if [ -n "${PREV_GEN}" ] && [ -n "${POST_GEN}" ] && [ "${PREV_GEN}" = "${POST_GEN}" ]; then
|
||||||
|
echo "[INFO] Deployment spec unchanged (generation=${POST_GEN}). Running rollout restart..."
|
||||||
|
kubectl -n "${NAMESPACE}" rollout restart deploy "${RELEASE_NAME}"
|
||||||
|
kubectl -n "${NAMESPACE}" rollout status deploy "${RELEASE_NAME}" --timeout=10m
|
||||||
|
else
|
||||||
|
echo "[INFO] Deployment spec changed (or first install). Skip rollout restart."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo '--- STATUS ---'
|
||||||
|
kubectl -n "${NAMESPACE}" get deploy,po,svc -l watcher.nodeId="${NODE_ID}",watcher.role="${NODE_TYPE}" || true
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
container('tooling') {
|
||||||
|
sh '''
|
||||||
|
set +e
|
||||||
|
echo '--- FINAL SUMMARY ---'
|
||||||
|
kubectl -n "${NAMESPACE}" get deploy "${RELEASE_NAME}" -o wide || true
|
||||||
|
kubectl -n "${NAMESPACE}" get po -l app.kubernetes.io/name="${RELEASE_NAME}" -o wide || true
|
||||||
|
kubectl -n "${NAMESPACE}" get svc "${RELEASE_NAME}" -o wide || true
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
cleanWs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
common/README.md.j2
Normal file
38
common/README.md.j2
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# {{ node_name }}
|
||||||
|
|
||||||
|
파이썬 프로젝트 템플릿
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## requirements.txt 작성 안내
|
||||||
|
|
||||||
|
배포 시 필요한 라이브러리 설치를 위해 **`requirements.txt`를 반드시 작성**해 주세요.
|
||||||
|
|
||||||
|
> ⚠️ 주의
|
||||||
|
> 이 프로젝트는 **import 이름과 pip 설치 이름이 다른 라이브러리**가 포함될 수 있어
|
||||||
|
> `pipreqs` 같은 자동 생성 도구 결과는 누락/오탐이 날 수 있습니다.
|
||||||
|
> (예: `import fitz` → `PyMuPDF`)
|
||||||
|
|
||||||
|
### 권장 작성 방법
|
||||||
|
1. 프로젝트가 정상 실행되는 가상환경(venv)에서 설치를 완료한 뒤
|
||||||
|
2. 아래 명령으로 생성하세요.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip freeze > requirements.txt
|
||||||
|
```
|
||||||
|
## MCP Tool 작성 안내
|
||||||
|
|
||||||
|
MCP(Custom Tool)로 노드를 구현하는 경우, 툴 함수에 설명(docstring)을 반드시 작성해야 합니다.
|
||||||
|
설명이 없으면 Dify/Tool Registry에서 노드가 정상적으로 노출되지 않거나 동작이 제한될 수 있습니다.
|
||||||
|
|
||||||
|
아래 형식을 지켜 주세요:
|
||||||
|
```
|
||||||
|
@mcp.tool()
|
||||||
|
def 함수명(...):
|
||||||
|
"""
|
||||||
|
설명 작성 필수
|
||||||
|
- 이 툴이 무엇을 하는지
|
||||||
|
- 입력 파라미터 의미
|
||||||
|
- 반환값 의미
|
||||||
|
"""
|
||||||
|
```
|
||||||
17
common/docker-compose.yml.j2
Normal file
17
common/docker-compose.yml.j2
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
version: "3.8"
|
||||||
|
services:
|
||||||
|
{{ node_id }}:
|
||||||
|
build: .
|
||||||
|
container_name: {{ node_id }}-${BRANCH_NAME}-{{ node_type }}
|
||||||
|
image: {{ node_id }}.${BRANCH_NAME}:${GIT_COMMIT_HASH}
|
||||||
|
environment:
|
||||||
|
- PYTHONUNBUFFERED=1
|
||||||
|
- BRANCH_NAME=${BRANCH_NAME}
|
||||||
|
- GIT_COMMIT_HASH=${GIT_COMMIT_HASH}
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- ai
|
||||||
|
|
||||||
|
networks:
|
||||||
|
ai:
|
||||||
|
external: true
|
||||||
0
common/requirements.txt.j2
Normal file
0
common/requirements.txt.j2
Normal file
1
common/src__init__.py.j2
Normal file
1
common/src__init__.py.j2
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# {{ node_name }} {{ node_type }} 패키지 초기화 파일
|
||||||
65
k8s/deployment.yaml copy.j2
Normal file
65
k8s/deployment.yaml copy.j2
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: ${RELEASE_NAME}
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ${RELEASE_NAME}
|
||||||
|
app.kubernetes.io/managed-by: "jenkins"
|
||||||
|
watcher.project: "{{ project_id }}"
|
||||||
|
watcher.nodeId: "{{ node_id }}"
|
||||||
|
watcher.role: "{{ node_type }}"
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: ${RELEASE_NAME}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ${RELEASE_NAME}
|
||||||
|
app.kubernetes.io/managed-by: "jenkins"
|
||||||
|
watcher.project: "{{ project_id }}"
|
||||||
|
watcher.nodeId: "{{ node_id }}"
|
||||||
|
watcher.role: "{{ node_type }}"
|
||||||
|
watcher.branch: ${BRANCH_NAME}
|
||||||
|
annotations:
|
||||||
|
watcher.enabled: "true"
|
||||||
|
spec:
|
||||||
|
{% if image_pull_secret %}
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: {{ image_pull_secret }}
|
||||||
|
{% endif %}
|
||||||
|
containers:
|
||||||
|
- name: app
|
||||||
|
image: ${FULL_IMAGE}
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: {{ app_port | default(80) }}
|
||||||
|
env:
|
||||||
|
- name: TZ
|
||||||
|
value: "Asia/Seoul"
|
||||||
|
# 기본 probe (필요 없으면 제거/수정)
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 3
|
||||||
|
periodSeconds: 5
|
||||||
|
timeoutSeconds: 2
|
||||||
|
failureThreshold: 6
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 2
|
||||||
|
failureThreshold: 6
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "50m"
|
||||||
|
memory: "128Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "500m"
|
||||||
|
memory: "512Mi"
|
||||||
94
k8s/deployment.yaml.j2
Normal file
94
k8s/deployment.yaml.j2
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: node-{{ node_id }}-main
|
||||||
|
namespace: {{ namespace }}
|
||||||
|
labels:
|
||||||
|
app: node-{{ node_id }}-main
|
||||||
|
watcher.project: "{{ project_id }}"
|
||||||
|
watcher.nodeId: "{{ node_id }}"
|
||||||
|
watcher.role: "{{ node_type }}"
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: node-{{ node_id }}-main
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: node-{{ node_id }}-main
|
||||||
|
watcher.project: "{{ project_id }}"
|
||||||
|
watcher.nodeId: "{{ node_id }}"
|
||||||
|
watcher.role: "{{ node_type }}"
|
||||||
|
annotations:
|
||||||
|
watcher.enabled: "true"
|
||||||
|
spec:
|
||||||
|
{% if image_pull_secret %}
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: {{ image_pull_secret }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if node_affinity_expressions %}
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
{% for expr in node_affinity_expressions %}
|
||||||
|
- key: {{ expr["key"] }}
|
||||||
|
operator: {{ expr["operator"] }}
|
||||||
|
values:
|
||||||
|
{% for v in expr["values"] %}
|
||||||
|
- {{ v }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if tolerations %}
|
||||||
|
tolerations:
|
||||||
|
{% for t in tolerations %}
|
||||||
|
- key: {{ t["key"] }}
|
||||||
|
operator: {{ t["operator"] }}
|
||||||
|
{% if t.get("value") %}
|
||||||
|
value: {{ t["value"] }}
|
||||||
|
{% endif %}
|
||||||
|
{% if t.get("effect") %}
|
||||||
|
effect: {{ t["effect"] }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
containers:
|
||||||
|
- name: app
|
||||||
|
image: {{ regi }}{% if regi_repo %}/{{ regi_repo | trim('/') }}{% endif %}/node-{{ node_id }}-main
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: {{ app_port | default(80) }}
|
||||||
|
env:
|
||||||
|
- name: TZ
|
||||||
|
value: "Asia/Seoul"
|
||||||
|
# 기본 probe (필요 없으면 제거/수정)
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 3
|
||||||
|
periodSeconds: 5
|
||||||
|
timeoutSeconds: 2
|
||||||
|
failureThreshold: 6
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 2
|
||||||
|
failureThreshold: 6
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "50m"
|
||||||
|
memory: "128Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "500m"
|
||||||
|
memory: "512Mi"
|
||||||
14
k8s/service.yaml copy.j2
Normal file
14
k8s/service.yaml copy.j2
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: ${RELEASE_NAME}
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ${RELEASE_NAME}
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: ${RELEASE_NAME}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: {{ service_port | default(80) }}
|
||||||
|
targetPort: http
|
||||||
15
k8s/service.yaml.j2
Normal file
15
k8s/service.yaml.j2
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: node-{{ node_id }}-main
|
||||||
|
namespace: {{ namespace }}
|
||||||
|
labels:
|
||||||
|
app: node-{{ node_id }}-main
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: node-{{ node_id }}-main
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: {{ service_port | default(80) }}
|
||||||
|
targetPort: http
|
||||||
14
mcp/Dockerfile.j2
Normal file
14
mcp/Dockerfile.j2
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
FROM {{ base_image | default("python:3.11-slim") }}
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
{% set pip_opts = "" %}
|
||||||
|
{% if pip_registry %}
|
||||||
|
{% set pip_opts = "--index-url " ~ pip_registry %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir -U pip
|
||||||
|
RUN pip install --no-cache-dir {{ pip_opts }} fastmcp
|
||||||
|
RUN pip install --no-cache-dir {{ pip_opts }} -r requirements.txt
|
||||||
|
|
||||||
|
CMD ["python", "src/main.py"]
|
||||||
13
mcp/src_main.py.j2
Normal file
13
mcp/src_main.py.j2
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from fastmcp import FastMCP
|
||||||
|
|
||||||
|
mcp = FastMCP(name="{{ node_name }}")
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def hello(name: str) -> str:
|
||||||
|
"""
|
||||||
|
This is a tool that says, "Hello!"
|
||||||
|
"""
|
||||||
|
return f"Hello ! {{ node_name }} ! {name} !"
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
mcp.run(transport="streamable-http", host="0.0.0.0", port=80)
|
||||||
Reference in New Issue
Block a user