From 5828b6c268a9c1340d7a7e5b76652bfa475cb38e Mon Sep 17 00:00:00 2001 From: plf Date: Fri, 1 May 2026 09:29:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BC=80=E5=8F=91=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 7 ++++ .gitignore | 3 ++ Dockerfile | 7 ++++ app/__init__.py | 0 app/main.py | 15 +++++++ app/test_main.py | 23 +++++++++++ argocd/application.yaml | 20 +++++++++ k8s/deployment.yaml | 53 ++++++++++++++++++++++++ k8s/kustomization.yaml | 5 +++ requirements.txt | 4 ++ tekton/pipeline.yaml | 90 +++++++++++++++++++++++++++++++++++++++++ tekton/pvc.yaml | 11 +++++ tekton/trigger.yaml | 65 +++++++++++++++++++++++++++++ 13 files changed, 303 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 app/__init__.py create mode 100644 app/main.py create mode 100644 app/test_main.py create mode 100644 argocd/application.yaml create mode 100644 k8s/deployment.yaml create mode 100644 k8s/kustomization.yaml create mode 100644 requirements.txt create mode 100644 tekton/pipeline.yaml create mode 100644 tekton/pvc.yaml create mode 100644 tekton/trigger.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..87b67fa --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +__pycache__ +*.pyc +.git +.gitignore +.omc +README.md +LICENSE diff --git a/.gitignore b/.gitignore index a7d0406..e27479d 100644 --- a/.gitignore +++ b/.gitignore @@ -187,3 +187,6 @@ cython_debug/ # PyPI configuration file .pypirc +# oh-my-claudecode +.omc/ + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2e358f7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.10-slim +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY app/ ./app/ +EXPOSE 8000 +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..676beca --- /dev/null +++ b/app/main.py @@ -0,0 +1,15 @@ +from fastapi import FastAPI + +app = FastAPI(title="CI/CD Demo", version="1.0.0") + +@app.get("/") +def root(): + return {"status": "ok", "version": "1.0.0"} + +@app.get("/health") +def health(): + return {"healthy": True} + +@app.get("/items/{item_id}") +def get_item(item_id: int): + return {"item_id": item_id, "name": f"Item {item_id}"} diff --git a/app/test_main.py b/app/test_main.py new file mode 100644 index 0000000..09cc9af --- /dev/null +++ b/app/test_main.py @@ -0,0 +1,23 @@ +from fastapi.testclient import TestClient +from app.main import app + +client = TestClient(app) + +def test_root(): + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"status": "ok", "version": "1.0.0"} + +def test_health(): + response = client.get("/health") + assert response.status_code == 200 + assert response.json() == {"healthy": True} + +def test_get_item(): + response = client.get("/items/1") + assert response.status_code == 200 + assert response.json() == {"item_id": 1, "name": "Item 1"} + +def test_get_item_invalid(): + response = client.get("/items/abc") + assert response.status_code == 422 diff --git a/argocd/application.yaml b/argocd/application.yaml new file mode 100644 index 0000000..5a63ddc --- /dev/null +++ b/argocd/application.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: fastapi-demo + namespace: argocd +spec: + project: default + source: + repoURL: https://git.plfai.cn/root/fastapi-demo.git + targetRevision: main + path: k8s + destination: + server: https://kubernetes.default.svc + namespace: fastapi-demo + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml new file mode 100644 index 0000000..c6582b6 --- /dev/null +++ b/k8s/deployment.yaml @@ -0,0 +1,53 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: fastapi-demo + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fastapi-app + namespace: fastapi-demo +spec: + replicas: 2 + selector: + matchLabels: + app: fastapi-app + template: + metadata: + labels: + app: fastapi-app + spec: + containers: + - name: app + image: registry.plfai.cn/fastapi-demo:latest + ports: + - containerPort: 8000 + readinessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 5 + periodSeconds: 5 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi + +--- +apiVersion: v1 +kind: Service +metadata: + name: fastapi-app + namespace: fastapi-demo +spec: + type: ClusterIP + selector: + app: fastapi-app + ports: + - port: 80 + targetPort: 8000 diff --git a/k8s/kustomization.yaml b/k8s/kustomization.yaml new file mode 100644 index 0000000..0c00d6f --- /dev/null +++ b/k8s/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- deployment.yaml diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2d21a6d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +fastapi==0.115.0 +uvicorn==0.30.0 +httpx==0.27.0 +pytest==8.3.0 diff --git a/tekton/pipeline.yaml b/tekton/pipeline.yaml new file mode 100644 index 0000000..52858a1 --- /dev/null +++ b/tekton/pipeline.yaml @@ -0,0 +1,90 @@ +apiVersion: tekton.dev/v1 +kind: Pipeline +metadata: + name: fastapi-ci + namespace: tekton-ci +spec: + params: + - name: git-url + type: string + - name: git-revision + type: string + - name: image-tag + type: string + workspaces: + - name: source + - name: dockerconfig + tasks: + + # 1. 拉取代码 + - name: git-clone + taskRef: + name: git-clone + params: + - name: url + value: $(params.git-url) + - name: revision + value: $(params.git-revision) + workspaces: + - name: output + workspace: source + + # 2. 单元测试 + - name: pytest + runAfter: [git-clone] + taskSpec: + steps: + - name: test + image: python:3.10-slim + script: | + cd $(workspaces.source.path) + pip install -r requirements.txt + pytest app/test_main.py -v + workspaces: + - name: source + workspace: source + + # 3. 构建并推送镜像 (Kaniko) + - name: build-and-push + runAfter: [pytest] + taskRef: + name: kaniko + params: + - name: IMAGE + value: "registry.plfai.cn/fastapi-demo:$(params.image-tag)" + workspaces: + - name: source + workspace: source + - name: dockerconfig + workspace: dockerconfig + + # 4. 镜像漏洞扫描 + - name: trivy-scan + runAfter: [build-and-push] + taskSpec: + steps: + - name: scan + image: aquasec/trivy:latest + args: + - image + - --severity=HIGH,CRITICAL + - --exit-code=1 + - "registry.plfai.cn/fastapi-demo:$(params.image-tag)" + + # 5. 更新部署清单 (GitOps) + - name: gitops-update + runAfter: [trivy-scan] + taskSpec: + steps: + - name: update-image + image: alpine/git + script: | + git clone $(params.git-url) /workspace/repo + cd /workspace/repo + sed -i "s|image: registry.plfai.cn/fastapi-demo:.*|image: registry.plfai.cn/fastapi-demo:$(params.image-tag)|" \ + k8s/deployment.yaml + git config user.email "tekton@plfai.cn" + git config user.name "Tekton CI" + git add k8s/deployment.yaml + git commit -m "ci: update image to $(params.image-tag) [skip ci]" + git push origin main diff --git a/tekton/pvc.yaml b/tekton/pvc.yaml new file mode 100644 index 0000000..f21ce67 --- /dev/null +++ b/tekton/pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: fastapi-ci-cache + namespace: tekton-ci +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi diff --git a/tekton/trigger.yaml b/tekton/trigger.yaml new file mode 100644 index 0000000..0ab7820 --- /dev/null +++ b/tekton/trigger.yaml @@ -0,0 +1,65 @@ +apiVersion: triggers.tekton.dev/v1beta1 +kind: TriggerTemplate +metadata: + name: fastapi-trigger-template + namespace: tekton-ci +spec: + params: + - name: git-url + - name: git-revision + resourcetemplates: + - apiVersion: tekton.dev/v1 + kind: PipelineRun + metadata: + generateName: fastapi-ci-run- + namespace: tekton-ci + spec: + pipelineRef: + name: fastapi-ci + params: + - name: git-url + value: $(tt.params.git-url) + - name: git-revision + value: $(tt.params.git-revision) + - name: image-tag + value: $(tt.params.git-revision) + workspaces: + - name: source + volumeClaimTemplate: + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + - name: dockerconfig + secret: + secretName: registry-credentials + +--- +apiVersion: triggers.tekton.dev/v1beta1 +kind: TriggerBinding +metadata: + name: fastapi-trigger-binding + namespace: tekton-ci +spec: + params: + - name: git-url + value: $(body.repository.clone_url) + - name: git-revision + value: $(body.after) + +--- +apiVersion: triggers.tekton.dev/v1beta1 +kind: EventListener +metadata: + name: fastapi-listener + namespace: tekton-ci +spec: + serviceAccountName: tekton-triggers-sa + triggers: + - name: fastapi-push + bindings: + - ref: fastapi-trigger-binding + template: + ref: fastapi-trigger-template