# Template variables (rendered by Jenkins `envsubst "$K8S_RENDER_VARS"`): # KRA_APP application name, derives every resource name # KRA_NAMESPACE target namespace # KRA_ENV development | production # KRA_REPLICAS replicas per deployment # KRA_FRONTEND_NODEPORT node port for the frontend service # KRA_BACKEND_NODEPORT node port for the backend debug service # KRA_MYSQL_IMAGE mysql server image # KRA_REDIS_IMAGE redis server image # KRA_BACKEND_IMAGE backend image built by Jenkins # KRA_FRONTEND_IMAGE frontend image built by Jenkins # BIN_NAME backend binary name inside the image apiVersion: v1 kind: Namespace metadata: name: ${KRA_NAMESPACE} labels: app.kubernetes.io/part-of: ${KRA_APP} app.kubernetes.io/managed-by: jenkins --- apiVersion: apps/v1 kind: StatefulSet metadata: name: ${KRA_APP}-mysql namespace: ${KRA_NAMESPACE} labels: app.kubernetes.io/name: ${KRA_APP}-mysql app.kubernetes.io/part-of: ${KRA_APP} spec: serviceName: ${KRA_APP}-mysql replicas: 1 selector: matchLabels: app.kubernetes.io/name: ${KRA_APP}-mysql template: metadata: labels: app.kubernetes.io/name: ${KRA_APP}-mysql app.kubernetes.io/part-of: ${KRA_APP} spec: automountServiceAccountToken: false containers: - name: mysql image: ${KRA_MYSQL_IMAGE} imagePullPolicy: IfNotPresent ports: - name: mysql containerPort: 3306 env: - name: MYSQL_DATABASE value: ${KRA_APP} - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: ${KRA_APP}-runtime key: mysql-root-password volumeMounts: - name: mysql-data mountPath: /var/lib/mysql readinessProbe: exec: command: ["sh", "-c", "mysqladmin ping -h 127.0.0.1 -uroot -p\"$MYSQL_ROOT_PASSWORD\" --silent"] initialDelaySeconds: 20 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 12 livenessProbe: exec: command: ["sh", "-c", "mysqladmin ping -h 127.0.0.1 -uroot -p\"$MYSQL_ROOT_PASSWORD\" --silent"] initialDelaySeconds: 60 periodSeconds: 20 timeoutSeconds: 5 resources: requests: cpu: 250m memory: 512Mi limits: cpu: 1 memory: 2Gi volumes: - name: mysql-data hostPath: path: /var/lib/${KRA_APP}/mysql type: DirectoryOrCreate --- apiVersion: v1 kind: Service metadata: name: ${KRA_APP}-mysql namespace: ${KRA_NAMESPACE} spec: clusterIP: None selector: app.kubernetes.io/name: ${KRA_APP}-mysql ports: - name: mysql port: 3306 targetPort: mysql --- apiVersion: v1 kind: Service metadata: name: ${KRA_APP}-redis namespace: ${KRA_NAMESPACE} spec: selector: app.kubernetes.io/name: ${KRA_APP}-redis ports: - name: redis port: 6379 targetPort: redis --- apiVersion: apps/v1 kind: Deployment metadata: name: ${KRA_APP}-redis namespace: ${KRA_NAMESPACE} labels: app.kubernetes.io/name: ${KRA_APP}-redis app.kubernetes.io/part-of: ${KRA_APP} spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: ${KRA_APP}-redis template: metadata: labels: app.kubernetes.io/name: ${KRA_APP}-redis app.kubernetes.io/part-of: ${KRA_APP} spec: automountServiceAccountToken: false initContainers: - name: prepare-redis-storage image: ${KRA_REDIS_IMAGE} imagePullPolicy: IfNotPresent securityContext: runAsUser: 0 runAsGroup: 0 command: ["sh", "-c"] args: - chown -R redis:redis /data volumeMounts: - name: redis-data mountPath: /data affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - topologyKey: kubernetes.io/hostname labelSelector: matchLabels: app.kubernetes.io/name: ${KRA_APP}-mysql containers: - name: redis image: ${KRA_REDIS_IMAGE} imagePullPolicy: IfNotPresent ports: - name: redis containerPort: 6379 command: ["redis-server", "--appendonly", "yes"] volumeMounts: - name: redis-data mountPath: /data readinessProbe: exec: command: ["redis-cli", "ping"] initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: exec: command: ["redis-cli", "ping"] initialDelaySeconds: 15 periodSeconds: 20 resources: requests: cpu: 50m memory: 64Mi limits: cpu: 250m memory: 512Mi volumes: - name: redis-data hostPath: path: /var/lib/${KRA_APP}/redis type: DirectoryOrCreate --- apiVersion: apps/v1 kind: Deployment metadata: name: ${KRA_APP}-backend namespace: ${KRA_NAMESPACE} labels: app.kubernetes.io/name: ${KRA_APP}-backend app.kubernetes.io/part-of: ${KRA_APP} spec: replicas: ${KRA_REPLICAS} revisionHistoryLimit: 3 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 selector: matchLabels: app.kubernetes.io/name: ${KRA_APP}-backend template: metadata: labels: app.kubernetes.io/name: ${KRA_APP}-backend app.kubernetes.io/part-of: ${KRA_APP} spec: securityContext: fsGroup: 1000 fsGroupChangePolicy: OnRootMismatch automountServiceAccountToken: false initContainers: - name: initialize-shared-storage image: ${KRA_BACKEND_IMAGE} imagePullPolicy: Always securityContext: runAsUser: 0 runAsGroup: 0 command: ["sh", "-c"] args: - >- mkdir -p /data/conf /app/uploads; if [ ! -f /data/conf/config.yaml ]; then cp /bootstrap/config.yaml /data/conf/config.yaml; fi; chown 1000:1000 /data/conf /data/conf/config.yaml /app/uploads; chmod u+rwX,g+rwX /data/conf /data/conf/config.yaml /app/uploads volumeMounts: - name: config mountPath: /data/conf - name: uploads mountPath: /app/uploads - name: config-template mountPath: /bootstrap readOnly: true # /data/conf 由初始化容器从 Secret 初始化到节点共享目录, # 目录必须可写:初始化向导会把数据库连接写回该文件。 imagePullSecrets: - name: harbor-registry affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - topologyKey: kubernetes.io/hostname labelSelector: matchLabels: app.kubernetes.io/name: ${KRA_APP}-mysql podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: topologyKey: kubernetes.io/hostname labelSelector: matchLabels: app.kubernetes.io/name: ${KRA_APP}-backend containers: - name: backend image: ${KRA_BACKEND_IMAGE} imagePullPolicy: Always command: ["./${BIN_NAME}"] args: ["-conf", "/data/conf"] ports: - name: http containerPort: 8000 env: # 数据库连接不在这里注入:应用启动时未配置数据库会进入 bootstrap 模式, # 首页出现"前往初始化",由 /init/initdb 建表、写入种子数据并持久化连接配置。 # JWT 签名密钥由 /data/conf/config.yaml 唯一管理,避免环境变量覆盖管理页面的配置变更; # Redis 地址已在下方 config.yaml 中指定。 - name: KRA_ADMIN_SYSTEM_USE_REDIS value: "true" volumeMounts: - name: config mountPath: /data/conf - name: uploads mountPath: /app/uploads readinessProbe: httpGet: path: /health port: http initialDelaySeconds: 10 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 6 livenessProbe: httpGet: path: /health port: http initialDelaySeconds: 30 periodSeconds: 20 timeoutSeconds: 3 failureThreshold: 3 resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 1Gi volumes: # 当前是单节点集群;required podAffinity 保证所有后端副本落在 # MySQL 所在节点,从而共享同一组 hostPath 目录。它不是真正的 RWX。 # 扩展到多节点前,必须换成 PVC + 真正的共享存储(如 NFS/CephFS)。 - name: config hostPath: path: /var/lib/${KRA_APP}/conf type: DirectoryOrCreate - name: uploads hostPath: path: /var/lib/${KRA_APP}/uploads type: DirectoryOrCreate - name: config-template secret: name: ${KRA_APP}-config-template --- apiVersion: v1 kind: Service metadata: name: ${KRA_APP}-backend namespace: ${KRA_NAMESPACE} labels: app.kubernetes.io/name: ${KRA_APP}-backend spec: type: ClusterIP selector: app.kubernetes.io/name: ${KRA_APP}-backend ports: - name: http port: 8000 targetPort: http --- apiVersion: v1 kind: Service metadata: name: ${KRA_APP}-backend-nodeport namespace: ${KRA_NAMESPACE} spec: type: NodePort selector: app.kubernetes.io/name: ${KRA_APP}-backend ports: - name: http port: 8000 targetPort: http nodePort: ${KRA_BACKEND_NODEPORT} --- apiVersion: apps/v1 kind: Deployment metadata: name: ${KRA_APP}-frontend namespace: ${KRA_NAMESPACE} labels: app.kubernetes.io/name: ${KRA_APP}-frontend app.kubernetes.io/part-of: ${KRA_APP} spec: replicas: ${KRA_REPLICAS} revisionHistoryLimit: 3 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 selector: matchLabels: app.kubernetes.io/name: ${KRA_APP}-frontend template: metadata: labels: app.kubernetes.io/name: ${KRA_APP}-frontend app.kubernetes.io/part-of: ${KRA_APP} spec: automountServiceAccountToken: false imagePullSecrets: - name: harbor-registry affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: topologyKey: kubernetes.io/hostname labelSelector: matchLabels: app.kubernetes.io/name: ${KRA_APP}-frontend containers: - name: frontend image: ${KRA_FRONTEND_IMAGE} imagePullPolicy: Always ports: - name: http containerPort: 80 readinessProbe: httpGet: path: / port: http initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: httpGet: path: / port: http initialDelaySeconds: 15 periodSeconds: 20 resources: requests: cpu: 50m memory: 64Mi limits: cpu: 250m memory: 256Mi --- apiVersion: v1 kind: Service metadata: name: ${KRA_APP}-frontend namespace: ${KRA_NAMESPACE} spec: type: NodePort selector: app.kubernetes.io/name: ${KRA_APP}-frontend ports: - name: http port: 80 targetPort: http nodePort: ${KRA_FRONTEND_NODEPORT}