diff --git a/edu_master/k8s/namespace.yaml b/edu_master/k8s/namespace.yaml new file mode 100644 index 0000000..e241a25 --- /dev/null +++ b/edu_master/k8s/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: edu-master diff --git a/edu_master/k8s/playwright.yaml b/edu_master/k8s/playwright.yaml new file mode 100644 index 0000000..09dc476 --- /dev/null +++ b/edu_master/k8s/playwright.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: playwright-service + namespace: edu-master + labels: + app: edu-master-playwright +spec: + replicas: 1 + selector: + matchLabels: + app: edu-master-playwright + template: + metadata: + labels: + app: edu-master-playwright + spec: + containers: + - name: playwright + image: mcr.microsoft.com/playwright:v1.56.0-jammy + imagePullPolicy: IfNotPresent + command: + - npx + - -y + - playwright@1.56.0 + - run-server + - --port + - "3000" + - --path + - /ws + ports: + - containerPort: 3000 + readinessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 3 + livenessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 3 +--- +apiVersion: v1 +kind: Service +metadata: + name: playwright-service + namespace: edu-master +spec: + selector: + app: edu-master-playwright + ports: + - name: ws + port: 3000 + targetPort: 3000 diff --git a/edu_master/k8s/redis.yaml b/edu_master/k8s/redis.yaml new file mode 100644 index 0000000..b28a938 --- /dev/null +++ b/edu_master/k8s/redis.yaml @@ -0,0 +1,74 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: edu-master + labels: + app: edu-master-redis +spec: + replicas: 1 + selector: + matchLabels: + app: edu-master-redis + template: + metadata: + labels: + app: edu-master-redis + spec: + containers: + - name: redis + image: redis:alpine + imagePullPolicy: IfNotPresent + ports: + - containerPort: 6379 + volumeMounts: + - name: redis-data + mountPath: /data + resources: + requests: + cpu: 25m + memory: 64Mi + limits: + cpu: 250m + memory: 256Mi + readinessProbe: + exec: + command: ["redis-cli", "ping"] + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + livenessProbe: + exec: + command: ["redis-cli", "ping"] + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 3 + volumes: + - name: redis-data + persistentVolumeClaim: + claimName: redis-data-pvc +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: redis-data-pvc + namespace: edu-master +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: redis + namespace: edu-master +spec: + selector: + app: edu-master-redis + ports: + - name: redis + port: 6379 + targetPort: 6379 diff --git a/edu_master/k8s/restore-seed-job.yaml.example b/edu_master/k8s/restore-seed-job.yaml.example new file mode 100644 index 0000000..08de33d --- /dev/null +++ b/edu_master/k8s/restore-seed-job.yaml.example @@ -0,0 +1,50 @@ +# Разовый Job для переноса состояния redis из docker compose в k8s (maintenance window). +# Файл .example -> deploy-пайплайн его не применяет (маска *.example.yaml). +# +# Runbook: +# 1. docker compose -f /edu_master/compose.yaml stop # SIGTERM -> redis сбросит dump.rdb +# 2. docker run --rm -v edu_master_redis-data:/data \ +# -v /tmp/edu-master-backup:/backup \ +# redis:alpine sh -c "cp /data/dump.rdb /backup/ && ls -la /backup" +# 3. kubectl apply -f edu_master/k8s/namespace.yaml +# 4. kubectl apply -f <только PVC из redis.yaml> # seed строго ДО старта redis-пода +# 5. kubectl apply -f edu_master/k8s/restore-seed-job.yaml.example +# kubectl wait --for=condition=complete job/redis-restore-seed -n edu-master --timeout=120s +# 6. kubectl delete job redis-restore-seed -n edu-master +# 7. kubectl apply -f edu_master/k8s/ -R # остальные манифесты +apiVersion: batch/v1 +kind: Job +metadata: + name: redis-restore-seed + namespace: edu-master +spec: + backoffLimit: 2 + ttlSecondsAfterFinished: 3600 + template: + spec: + restartPolicy: Never + containers: + - name: seed + image: redis:alpine + command: + - /bin/sh + - -ec + - | + ls -la /backup + cp /backup/dump.rdb /data/dump.rdb + chmod 644 /data/dump.rdb + ls -la /data + volumeMounts: + - name: redis-data + mountPath: /data + - name: backup + mountPath: /backup + readOnly: true + volumes: + - name: redis-data + persistentVolumeClaim: + claimName: redis-data-pvc + - name: backup + hostPath: + path: /tmp/edu-master-backup + type: DirectoryOrCreate \ No newline at end of file diff --git a/edu_master/k8s/secrets.yaml.example b/edu_master/k8s/secrets.yaml.example new file mode 100644 index 0000000..5887bbe --- /dev/null +++ b/edu_master/k8s/secrets.yaml.example @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: Secret +metadata: + name: edu-master-secrets + namespace: edu-master +type: Opaque +stringData: + # PHPSESSID keeper + KEEPER_LOGIN: "" + KEEPER_PASSWORD: "" + KEEPER_INTERVAL: "10" + # EDU links + EDU_URL_BASE: "https://edu.edu.vn.ua" + EDU_URL_LOGIN: "/user/login" + EDU_URL_COURSES: "/course/userlist" + EDU_URL_WEBINAR: "/webinar/useractive" + # Playwright + USER_AGENT: "" + PLAYWRIGHT_WS: "ws://playwright-service:3000/ws" + # Webinar-checker + WEBINAR_TELEGRAM_TOKEN: "" + WEBINAR_ADMIN_ID: "" + WEBINAR_CHECK_INTERVAL: "60" + # Database + REDIS_HOST: "redis" + REDIS_PORT: "6379" \ No newline at end of file diff --git a/edu_master/k8s/session-keeper.yaml b/edu_master/k8s/session-keeper.yaml new file mode 100644 index 0000000..a78fee0 --- /dev/null +++ b/edu_master/k8s/session-keeper.yaml @@ -0,0 +1,52 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: session-keeper + namespace: edu-master + labels: + app: edu-master-session-keeper +spec: + replicas: 1 + selector: + matchLabels: + app: edu-master-session-keeper + template: + metadata: + labels: + app: edu-master-session-keeper + spec: + initContainers: + - name: wait-redis + image: redis:alpine + command: + - /bin/sh + - -ec + - | + i=0 + until redis-cli -h redis ping | grep -q PONG; do + i=$((i+1)) + [ "$i" -ge 300 ] && echo "TIMEOUT: redis not ready" && exit 1 + sleep 2 + done + echo "redis is ready" + containers: + - name: session-keeper + image: gcr.forust.xyz/forust/session-keeper:latest + imagePullPolicy: Always + envFrom: + - secretRef: + name: edu-master-secrets + resources: + requests: + cpu: 25m + memory: 96Mi + limits: + cpu: 250m + memory: 256Mi + readinessProbe: + exec: + command: ["/bin/sh", "-ec", "redis-cli -h redis EXISTS EDU_PHPSESSID | grep -q 1"] + initialDelaySeconds: 15 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 10 diff --git a/edu_master/k8s/webinar-checker.yaml b/edu_master/k8s/webinar-checker.yaml new file mode 100644 index 0000000..0813cc3 --- /dev/null +++ b/edu_master/k8s/webinar-checker.yaml @@ -0,0 +1,59 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: webinar-checker + namespace: edu-master + labels: + app: edu-master-webinar-checker +spec: + replicas: 1 + selector: + matchLabels: + app: edu-master-webinar-checker + template: + metadata: + labels: + app: edu-master-webinar-checker + spec: + initContainers: + # Держит порядок зависимостей как в compose depends_on: + # redis healthy -> session-keeper healthy (EXISTS EDU_PHPSESSID) -> playwright started + - name: wait-deps + image: redis:alpine + command: + - /bin/sh + - -ec + - | + i=0 + until redis-cli -h redis ping | grep -q PONG; do + i=$((i+1)) + [ "$i" -ge 300 ] && echo "TIMEOUT: redis not ready" && exit 1 + sleep 2 + done + echo "redis ok" + until [ "$(redis-cli -h redis EXISTS EDU_PHPSESSID)" = "1" ]; do + i=$((i+1)) + [ "$i" -ge 300 ] && echo "TIMEOUT: no PHPSESSID (session-keeper down?)" && exit 1 + sleep 2 + done + echo "PHPSESSID ok" + until nc -z playwright-service 3000; do + i=$((i+1)) + [ "$i" -ge 300 ] && echo "TIMEOUT: playwright-service not reachable" && exit 1 + sleep 2 + done + echo "playwright ok" + containers: + - name: webinar-checker + image: gcr.forust.xyz/forust/webinar-checker:latest + imagePullPolicy: Always + envFrom: + - secretRef: + name: edu-master-secrets + resources: + requests: + cpu: 25m + memory: 128Mi + limits: + cpu: 300m + memory: 384Mi