ci: optimize deployment workflow for k3s
- Single job instead of 3 (no artifact overhead) - Build directly on k3s node (avoids image transfer) - Parallel Docker builds with BuildKit - Auto-create namespace if missing - Automatic rollback on failure - Health check via domain
This commit is contained in:
parent
3e66b63ac7
commit
c15702746a
|
|
@ -1,4 +1,4 @@
|
||||||
name: Build and Deploy to k3s
|
name: Deploy to k3s
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
@ -6,111 +6,101 @@ on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NAMESPACE: student-enrollment
|
K3S_HOST: "100.67.198.92"
|
||||||
API_IMAGE: student-api
|
K3S_USER: "andres"
|
||||||
FRONTEND_IMAGE: student-frontend
|
NAMESPACE: "student-enrollment"
|
||||||
|
DOMAIN: "academia.ingeniumcodex.com"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Build jobs run in parallel for speed
|
|
||||||
build-api:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Build API with cache
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: deploy/docker/Dockerfile.api
|
|
||||||
push: false
|
|
||||||
load: true
|
|
||||||
tags: ${{ env.API_IMAGE }}:${{ github.sha }}
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|
||||||
- name: Save API image
|
|
||||||
run: docker save ${{ env.API_IMAGE }}:${{ github.sha }} | gzip > /tmp/api-image.tar.gz
|
|
||||||
|
|
||||||
- name: Upload API artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: api-image
|
|
||||||
path: /tmp/api-image.tar.gz
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
build-frontend:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Build Frontend with cache
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: deploy/docker/Dockerfile.frontend
|
|
||||||
push: false
|
|
||||||
load: true
|
|
||||||
tags: ${{ env.FRONTEND_IMAGE }}:${{ github.sha }}
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|
||||||
- name: Save Frontend image
|
|
||||||
run: docker save ${{ env.FRONTEND_IMAGE }}:${{ github.sha }} | gzip > /tmp/frontend-image.tar.gz
|
|
||||||
|
|
||||||
- name: Upload Frontend artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: frontend-image
|
|
||||||
path: /tmp/frontend-image.tar.gz
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build-api, build-frontend]
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Download API image
|
- name: Setup SSH
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: api-image
|
|
||||||
path: /tmp
|
|
||||||
|
|
||||||
- name: Download Frontend image
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: frontend-image
|
|
||||||
path: /tmp
|
|
||||||
|
|
||||||
- name: Import images to k3s
|
|
||||||
run: |
|
run: |
|
||||||
gunzip -c /tmp/api-image.tar.gz | sudo k3s ctr images import -
|
mkdir -p ~/.ssh
|
||||||
gunzip -c /tmp/frontend-image.tar.gz | sudo k3s ctr images import -
|
echo "${{ secrets.K3S_SSH_KEY }}" > ~/.ssh/id_rsa
|
||||||
|
chmod 600 ~/.ssh/id_rsa
|
||||||
|
ssh-keyscan -H ${{ env.K3S_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
|
||||||
|
|
||||||
- name: Update deployments with new tag
|
- name: Sync code to k3s
|
||||||
run: |
|
run: |
|
||||||
kubectl -n ${{ env.NAMESPACE }} set image deployment/student-api \
|
rsync -az --delete \
|
||||||
api=${{ env.API_IMAGE }}:${{ github.sha }}
|
--exclude '.git' \
|
||||||
kubectl -n ${{ env.NAMESPACE }} set image deployment/student-frontend \
|
--exclude 'node_modules' \
|
||||||
frontend=${{ env.FRONTEND_IMAGE }}:${{ github.sha }}
|
--exclude 'dist' \
|
||||||
|
--exclude 'bin' \
|
||||||
|
--exclude 'obj' \
|
||||||
|
--exclude '.angular' \
|
||||||
|
--exclude 'tests' \
|
||||||
|
--exclude 'docs' \
|
||||||
|
./ ${{ env.K3S_USER }}@${{ env.K3S_HOST }}:~/student-enrollment/
|
||||||
|
|
||||||
- name: Wait for rollout
|
- name: Build images (parallel)
|
||||||
run: |
|
run: |
|
||||||
kubectl -n ${{ env.NAMESPACE }} rollout status deployment/student-api --timeout=120s
|
ssh ${{ env.K3S_USER }}@${{ env.K3S_HOST }} << 'ENDSSH'
|
||||||
kubectl -n ${{ env.NAMESPACE }} rollout status deployment/student-frontend --timeout=60s
|
cd ~/student-enrollment
|
||||||
|
export DOCKER_BUILDKIT=1
|
||||||
|
|
||||||
|
echo "${{ secrets.K3S_SUDO_PASS }}" | sudo -S docker build \
|
||||||
|
-f deploy/docker/Dockerfile.api -t student-api:latest . &
|
||||||
|
PID_API=$!
|
||||||
|
|
||||||
|
echo "${{ secrets.K3S_SUDO_PASS }}" | sudo -S docker build \
|
||||||
|
-f deploy/docker/Dockerfile.frontend -t student-frontend:latest . &
|
||||||
|
PID_FE=$!
|
||||||
|
|
||||||
|
wait $PID_API || exit 1
|
||||||
|
wait $PID_FE || exit 1
|
||||||
|
ENDSSH
|
||||||
|
|
||||||
|
- name: Import to k3s
|
||||||
|
run: |
|
||||||
|
ssh ${{ env.K3S_USER }}@${{ env.K3S_HOST }} << 'ENDSSH'
|
||||||
|
echo "${{ secrets.K3S_SUDO_PASS }}" | sudo -S sh -c \
|
||||||
|
'docker save student-api:latest | k3s ctr images import -'
|
||||||
|
echo "${{ secrets.K3S_SUDO_PASS }}" | sudo -S sh -c \
|
||||||
|
'docker save student-frontend:latest | k3s ctr images import -'
|
||||||
|
ENDSSH
|
||||||
|
|
||||||
|
- name: Setup namespace if needed
|
||||||
|
run: |
|
||||||
|
ssh ${{ env.K3S_USER }}@${{ env.K3S_HOST }} << 'ENDSSH'
|
||||||
|
cd ~/student-enrollment/deploy/k3s
|
||||||
|
if ! echo "${{ secrets.K3S_SUDO_PASS }}" | sudo -S kubectl get ns student-enrollment &>/dev/null; then
|
||||||
|
echo "Creating namespace and resources..."
|
||||||
|
echo "${{ secrets.K3S_SUDO_PASS }}" | sudo -S kubectl apply -k .
|
||||||
|
fi
|
||||||
|
ENDSSH
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
run: |
|
||||||
|
ssh ${{ env.K3S_USER }}@${{ env.K3S_HOST }} << 'ENDSSH'
|
||||||
|
echo "${{ secrets.K3S_SUDO_PASS }}" | sudo -S kubectl rollout restart \
|
||||||
|
deployment/student-api deployment/student-frontend -n student-enrollment
|
||||||
|
ENDSSH
|
||||||
|
|
||||||
|
- name: Wait rollout
|
||||||
|
run: |
|
||||||
|
ssh ${{ env.K3S_USER }}@${{ env.K3S_HOST }} << 'ENDSSH'
|
||||||
|
echo "${{ secrets.K3S_SUDO_PASS }}" | sudo -S kubectl rollout status \
|
||||||
|
deployment/student-api -n student-enrollment --timeout=120s
|
||||||
|
echo "${{ secrets.K3S_SUDO_PASS }}" | sudo -S kubectl rollout status \
|
||||||
|
deployment/student-frontend -n student-enrollment --timeout=60s
|
||||||
|
ENDSSH
|
||||||
|
|
||||||
- name: Health check
|
- name: Health check
|
||||||
run: |
|
run: |
|
||||||
sleep 10
|
sleep 5
|
||||||
API_POD=$(kubectl -n ${{ env.NAMESPACE }} get pods -l app=student-api -o jsonpath='{.items[0].metadata.name}')
|
curl -sf https://${{ env.DOMAIN }}/health || exit 1
|
||||||
kubectl -n ${{ env.NAMESPACE }} exec $API_POD -- wget -q --spider http://localhost:5000/health || exit 1
|
curl -sf https://${{ env.DOMAIN }}/ || exit 1
|
||||||
echo "Deployment healthy!"
|
|
||||||
|
- name: Rollback on failure
|
||||||
|
if: failure()
|
||||||
|
run: |
|
||||||
|
ssh ${{ env.K3S_USER }}@${{ env.K3S_HOST }} << 'ENDSSH'
|
||||||
|
echo "${{ secrets.K3S_SUDO_PASS }}" | sudo -S kubectl rollout undo \
|
||||||
|
deployment/student-api deployment/student-frontend -n student-enrollment 2>/dev/null || true
|
||||||
|
ENDSSH
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ server {
|
||||||
|
|
||||||
# API proxy
|
# API proxy
|
||||||
location /graphql {
|
location /graphql {
|
||||||
proxy_pass http://student-api:5000;
|
proxy_pass http://student-api:8080;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection 'upgrade';
|
proxy_set_header Connection 'upgrade';
|
||||||
|
|
@ -24,7 +24,7 @@ server {
|
||||||
}
|
}
|
||||||
|
|
||||||
location /health {
|
location /health {
|
||||||
proxy_pass http://student-api:5000;
|
proxy_pass http://student-api:8080;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cache static assets
|
# Cache static assets
|
||||||
|
|
|
||||||
|
|
@ -1,264 +0,0 @@
|
||||||
# Student Enrollment System - K3s All-in-One Deployment
|
|
||||||
# Deploy: kubectl apply -f all-in-one.yaml
|
|
||||||
# Delete: kubectl delete -f all-in-one.yaml
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: student-enrollment
|
|
||||||
---
|
|
||||||
# ===== SECRETS =====
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: app-secrets
|
|
||||||
namespace: student-enrollment
|
|
||||||
type: Opaque
|
|
||||||
stringData:
|
|
||||||
SA_PASSWORD: "Admin123!"
|
|
||||||
JWT_SECRET: "super-secret-key-for-jwt-tokens-minimum-32-chars-long!"
|
|
||||||
ADMIN_PASSWORD: "admin123"
|
|
||||||
---
|
|
||||||
# ===== SQL SERVER =====
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: mssql-data
|
|
||||||
namespace: student-enrollment
|
|
||||||
spec:
|
|
||||||
accessModes: [ReadWriteOnce]
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 2Gi
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: mssql
|
|
||||||
namespace: student-enrollment
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: mssql
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: mssql
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: mssql
|
|
||||||
image: mcr.microsoft.com/mssql/server:2022-latest
|
|
||||||
ports:
|
|
||||||
- containerPort: 1433
|
|
||||||
env:
|
|
||||||
- name: ACCEPT_EULA
|
|
||||||
value: "Y"
|
|
||||||
- name: MSSQL_SA_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: app-secrets
|
|
||||||
key: SA_PASSWORD
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: "2Gi"
|
|
||||||
cpu: "1000m"
|
|
||||||
requests:
|
|
||||||
memory: "1Gi"
|
|
||||||
cpu: "500m"
|
|
||||||
volumeMounts:
|
|
||||||
- name: mssql-data
|
|
||||||
mountPath: /var/opt/mssql
|
|
||||||
readinessProbe:
|
|
||||||
exec:
|
|
||||||
command: ["/opt/mssql-tools18/bin/sqlcmd", "-S", "localhost", "-U", "sa", "-P", "$(MSSQL_SA_PASSWORD)", "-C", "-Q", "SELECT 1"]
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 10
|
|
||||||
volumes:
|
|
||||||
- name: mssql-data
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: mssql-data
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: mssql
|
|
||||||
namespace: student-enrollment
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: mssql
|
|
||||||
ports:
|
|
||||||
- port: 1433
|
|
||||||
targetPort: 1433
|
|
||||||
---
|
|
||||||
# ===== BACKEND API =====
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: api
|
|
||||||
namespace: student-enrollment
|
|
||||||
spec:
|
|
||||||
replicas: 2
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: api
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: api
|
|
||||||
spec:
|
|
||||||
initContainers:
|
|
||||||
- name: wait-for-db
|
|
||||||
image: busybox:1.36
|
|
||||||
command: ['sh', '-c', 'until nc -z mssql 1433; do echo waiting for mssql; sleep 5; done']
|
|
||||||
containers:
|
|
||||||
- name: api
|
|
||||||
image: student-enrollment-api:latest
|
|
||||||
imagePullPolicy: IfNotPresent
|
|
||||||
ports:
|
|
||||||
- containerPort: 8080
|
|
||||||
env:
|
|
||||||
- name: ASPNETCORE_ENVIRONMENT
|
|
||||||
value: "Production"
|
|
||||||
- name: ASPNETCORE_URLS
|
|
||||||
value: "http://+:8080"
|
|
||||||
- name: ConnectionStrings__DefaultConnection
|
|
||||||
value: "Server=mssql;Database=StudentEnrollment;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
|
||||||
- name: SA_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: app-secrets
|
|
||||||
key: SA_PASSWORD
|
|
||||||
- name: JwtSettings__Secret
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: app-secrets
|
|
||||||
key: JWT_SECRET
|
|
||||||
- name: JwtSettings__Issuer
|
|
||||||
value: "student-enrollment-api"
|
|
||||||
- name: JwtSettings__Audience
|
|
||||||
value: "student-enrollment-app"
|
|
||||||
- name: JwtSettings__ExpirationMinutes
|
|
||||||
value: "60"
|
|
||||||
- name: ADMIN_USERNAME
|
|
||||||
value: "admin"
|
|
||||||
- name: ADMIN_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: app-secrets
|
|
||||||
key: ADMIN_PASSWORD
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: "512Mi"
|
|
||||||
cpu: "500m"
|
|
||||||
requests:
|
|
||||||
memory: "256Mi"
|
|
||||||
cpu: "250m"
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /health
|
|
||||||
port: 8080
|
|
||||||
initialDelaySeconds: 15
|
|
||||||
periodSeconds: 10
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /health
|
|
||||||
port: 8080
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 30
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: api
|
|
||||||
namespace: student-enrollment
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: api
|
|
||||||
ports:
|
|
||||||
- port: 5000
|
|
||||||
targetPort: 8080
|
|
||||||
---
|
|
||||||
# ===== FRONTEND =====
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: frontend
|
|
||||||
namespace: student-enrollment
|
|
||||||
spec:
|
|
||||||
replicas: 2
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: frontend
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: frontend
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: frontend
|
|
||||||
image: student-enrollment-frontend:latest
|
|
||||||
imagePullPolicy: IfNotPresent
|
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: "128Mi"
|
|
||||||
cpu: "100m"
|
|
||||||
requests:
|
|
||||||
memory: "64Mi"
|
|
||||||
cpu: "50m"
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: 80
|
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 10
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: frontend
|
|
||||||
namespace: student-enrollment
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: frontend
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
targetPort: 80
|
|
||||||
---
|
|
||||||
# ===== INGRESS =====
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: student-enrollment-ingress
|
|
||||||
namespace: student-enrollment
|
|
||||||
annotations:
|
|
||||||
nginx.ingress.kubernetes.io/rewrite-target: /
|
|
||||||
spec:
|
|
||||||
ingressClassName: traefik
|
|
||||||
rules:
|
|
||||||
- host: students.local
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /graphql
|
|
||||||
pathType: Prefix
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: api
|
|
||||||
port:
|
|
||||||
number: 5000
|
|
||||||
- path: /health
|
|
||||||
pathType: Prefix
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: api
|
|
||||||
port:
|
|
||||||
number: 5000
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: frontend
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
|
|
@ -28,13 +28,13 @@ spec:
|
||||||
image: academia-api:latest
|
image: academia-api:latest
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 5000
|
- containerPort: 8080
|
||||||
name: http
|
name: http
|
||||||
env:
|
env:
|
||||||
- name: ASPNETCORE_ENVIRONMENT
|
- name: ASPNETCORE_ENVIRONMENT
|
||||||
value: "Production"
|
value: "Production"
|
||||||
- name: ASPNETCORE_URLS
|
- name: ASPNETCORE_URLS
|
||||||
value: "http://+:5000"
|
value: "http://+:8080"
|
||||||
- name: ConnectionStrings__DefaultConnection
|
- name: ConnectionStrings__DefaultConnection
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
|
|
@ -61,7 +61,7 @@ spec:
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /health
|
path: /health
|
||||||
port: 5000
|
port: 8080
|
||||||
initialDelaySeconds: 30
|
initialDelaySeconds: 30
|
||||||
periodSeconds: 30
|
periodSeconds: 30
|
||||||
timeoutSeconds: 10
|
timeoutSeconds: 10
|
||||||
|
|
@ -69,7 +69,7 @@ spec:
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /health
|
path: /health
|
||||||
port: 5000
|
port: 8080
|
||||||
initialDelaySeconds: 15
|
initialDelaySeconds: 15
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
timeoutSeconds: 5
|
timeoutSeconds: 5
|
||||||
|
|
@ -87,7 +87,7 @@ spec:
|
||||||
selector:
|
selector:
|
||||||
app: student-api
|
app: student-api
|
||||||
ports:
|
ports:
|
||||||
- port: 5000
|
- port: 8080
|
||||||
targetPort: 5000
|
targetPort: 8080
|
||||||
name: http
|
name: http
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ metadata:
|
||||||
namespace: student-enrollment
|
namespace: student-enrollment
|
||||||
data:
|
data:
|
||||||
ASPNETCORE_ENVIRONMENT: "Production"
|
ASPNETCORE_ENVIRONMENT: "Production"
|
||||||
ASPNETCORE_URLS: "http://+:5000"
|
ASPNETCORE_URLS: "http://+:8080"
|
||||||
# Frontend config
|
# Frontend config
|
||||||
API_URL: "https://academia.ingeniumcodex.com/graphql"
|
API_URL: "https://academia.ingeniumcodex.com/graphql"
|
||||||
HEALTH_CHECK_URL: "https://academia.ingeniumcodex.com/health"
|
HEALTH_CHECK_URL: "https://academia.ingeniumcodex.com/health"
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,132 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Quick deploy to k3s
|
# K3s Deployment Script - Optimizado para tiempo mínimo
|
||||||
set -e
|
# Uso: ./deploy.sh [build|deploy|all|status|clean]
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
echo "=== Building Docker images ==="
|
# Colores
|
||||||
cd "$PROJECT_ROOT"
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
# Build API
|
# Cargar credenciales si existen
|
||||||
docker build -t student-enrollment-api:latest -f deploy/docker/Dockerfile.api .
|
CREDS_FILE="$HOME/.secrets/credentials.env"
|
||||||
|
[[ -f "$CREDS_FILE" ]] && source "$CREDS_FILE"
|
||||||
|
|
||||||
# Build Frontend
|
# Configuración
|
||||||
docker build -t student-enrollment-frontend:latest -f deploy/docker/Dockerfile.frontend .
|
K3S_HOST="${K8S_MASTER_HOST:-hp62a}"
|
||||||
|
NAMESPACE="student-enrollment"
|
||||||
|
API_IMAGE="academia-api:latest"
|
||||||
|
FRONTEND_IMAGE="academia-frontend:latest"
|
||||||
|
|
||||||
echo "=== Deploying to k3s ==="
|
log() { echo -e "${GREEN}[$(date +%T)]${NC} $1"; }
|
||||||
kubectl apply -f "$SCRIPT_DIR/all-in-one.yaml"
|
warn() { echo -e "${YELLOW}[$(date +%T)]${NC} $1"; }
|
||||||
|
err() { echo -e "${RED}[$(date +%T)]${NC} $1" >&2; }
|
||||||
|
|
||||||
echo "=== Waiting for deployments ==="
|
build_images() {
|
||||||
kubectl -n student-enrollment rollout status deployment/mssql --timeout=120s
|
log "=== Construyendo imágenes Docker (paralelo) ==="
|
||||||
kubectl -n student-enrollment rollout status deployment/api --timeout=120s
|
cd "$PROJECT_ROOT"
|
||||||
kubectl -n student-enrollment rollout status deployment/frontend --timeout=60s
|
|
||||||
|
|
||||||
echo "=== Deployment complete ==="
|
# Build en paralelo
|
||||||
echo "Add to /etc/hosts: <k3s-ip> students.local"
|
docker build -t "$API_IMAGE" -f deploy/docker/Dockerfile.api . &
|
||||||
kubectl -n student-enrollment get pods
|
PID_API=$!
|
||||||
|
docker build -t "$FRONTEND_IMAGE" -f deploy/docker/Dockerfile.frontend . &
|
||||||
|
PID_FRONTEND=$!
|
||||||
|
|
||||||
|
# Esperar ambos
|
||||||
|
wait $PID_API && log "✓ API imagen construida" || { err "✗ Error construyendo API"; exit 1; }
|
||||||
|
wait $PID_FRONTEND && log "✓ Frontend imagen construida" || { err "✗ Error construyendo Frontend"; exit 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
transfer_images() {
|
||||||
|
log "=== Transfiriendo imágenes a k3s ==="
|
||||||
|
|
||||||
|
# Exportar y transferir en paralelo
|
||||||
|
docker save "$API_IMAGE" | ssh "$K3S_HOST" "sudo k3s ctr images import -" &
|
||||||
|
PID_API=$!
|
||||||
|
docker save "$FRONTEND_IMAGE" | ssh "$K3S_HOST" "sudo k3s ctr images import -" &
|
||||||
|
PID_FRONTEND=$!
|
||||||
|
|
||||||
|
wait $PID_API && log "✓ API transferida" || { err "✗ Error transfiriendo API"; exit 1; }
|
||||||
|
wait $PID_FRONTEND && log "✓ Frontend transferida" || { err "✗ Error transfiriendo Frontend"; exit 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
deploy_k3s() {
|
||||||
|
log "=== Desplegando en k3s con Kustomize ==="
|
||||||
|
|
||||||
|
# Aplicar con kustomize
|
||||||
|
ssh "$K3S_HOST" "cd /tmp && sudo kubectl apply -k -" < <(kubectl kustomize "$SCRIPT_DIR")
|
||||||
|
|
||||||
|
log "=== Esperando despliegues ==="
|
||||||
|
# Esperar SQL Server primero (es dependencia)
|
||||||
|
ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE rollout status deployment/sqlserver --timeout=180s" || warn "SQL Server timeout"
|
||||||
|
|
||||||
|
# Esperar API y Frontend en paralelo
|
||||||
|
ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE rollout status deployment/student-api --timeout=120s" &
|
||||||
|
PID_API=$!
|
||||||
|
ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE rollout status deployment/student-frontend --timeout=60s" &
|
||||||
|
PID_FRONTEND=$!
|
||||||
|
|
||||||
|
wait $PID_API && log "✓ API desplegada" || warn "API timeout"
|
||||||
|
wait $PID_FRONTEND && log "✓ Frontend desplegado" || warn "Frontend timeout"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_status() {
|
||||||
|
log "=== Estado del despliegue ==="
|
||||||
|
ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE get pods -o wide"
|
||||||
|
echo ""
|
||||||
|
ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE get svc"
|
||||||
|
echo ""
|
||||||
|
ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE get ingress 2>/dev/null || true"
|
||||||
|
}
|
||||||
|
|
||||||
|
clean() {
|
||||||
|
warn "=== Limpiando namespace $NAMESPACE ==="
|
||||||
|
ssh "$K3S_HOST" "sudo kubectl delete namespace $NAMESPACE --ignore-not-found"
|
||||||
|
log "✓ Namespace eliminado"
|
||||||
|
}
|
||||||
|
|
||||||
|
restart_pods() {
|
||||||
|
log "=== Reiniciando pods (rolling restart) ==="
|
||||||
|
ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE rollout restart deployment/student-api"
|
||||||
|
ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE rollout restart deployment/student-frontend"
|
||||||
|
log "✓ Pods reiniciados"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main
|
||||||
|
case "${1:-all}" in
|
||||||
|
build)
|
||||||
|
build_images
|
||||||
|
;;
|
||||||
|
transfer)
|
||||||
|
transfer_images
|
||||||
|
;;
|
||||||
|
deploy)
|
||||||
|
deploy_k3s
|
||||||
|
show_status
|
||||||
|
;;
|
||||||
|
all)
|
||||||
|
build_images
|
||||||
|
transfer_images
|
||||||
|
deploy_k3s
|
||||||
|
show_status
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
show_status
|
||||||
|
;;
|
||||||
|
clean)
|
||||||
|
clean
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
restart_pods
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Uso: $0 [build|transfer|deploy|all|status|clean|restart]"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
log "=== Completado ==="
|
||||||
|
|
|
||||||
|
|
@ -62,13 +62,13 @@ spec:
|
||||||
namespace: student-enrollment
|
namespace: student-enrollment
|
||||||
services:
|
services:
|
||||||
- name: student-api
|
- name: student-api
|
||||||
port: 5000
|
port: 8080
|
||||||
# Health check
|
# Health check
|
||||||
- kind: Rule
|
- kind: Rule
|
||||||
match: Host(`academia.ingeniumcodex.com`) && PathPrefix(`/health`)
|
match: Host(`academia.ingeniumcodex.com`) && PathPrefix(`/health`)
|
||||||
services:
|
services:
|
||||||
- name: student-api
|
- name: student-api
|
||||||
port: 5000
|
port: 8080
|
||||||
# Frontend (catch-all)
|
# Frontend (catch-all)
|
||||||
- kind: Rule
|
- kind: Rule
|
||||||
match: Host(`academia.ingeniumcodex.com`)
|
match: Host(`academia.ingeniumcodex.com`)
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,12 @@ resources:
|
||||||
- frontend.yaml
|
- frontend.yaml
|
||||||
- ingress.yaml
|
- ingress.yaml
|
||||||
|
|
||||||
commonLabels:
|
labels:
|
||||||
app.kubernetes.io/name: student-enrollment
|
- pairs:
|
||||||
app.kubernetes.io/part-of: interrapidisimo
|
app.kubernetes.io/name: student-enrollment
|
||||||
app.kubernetes.io/managed-by: kustomize
|
app.kubernetes.io/part-of: interrapidisimo
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
includeSelectors: false
|
||||||
|
|
||||||
# Dominio: https://academia.ingeniumcodex.com
|
# Dominio: https://academia.ingeniumcodex.com
|
||||||
# TLS configurado con Traefik + Let's Encrypt
|
# TLS configurado con Traefik + Let's Encrypt
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ spec:
|
||||||
kubernetes.io/metadata.name: kube-system
|
kubernetes.io/metadata.name: kube-system
|
||||||
ports:
|
ports:
|
||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
port: 5000
|
port: 8080
|
||||||
# Desde frontend (para nginx proxy)
|
# Desde frontend (para nginx proxy)
|
||||||
- from:
|
- from:
|
||||||
- podSelector:
|
- podSelector:
|
||||||
|
|
@ -59,7 +59,7 @@ spec:
|
||||||
app: student-frontend
|
app: student-frontend
|
||||||
ports:
|
ports:
|
||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
port: 5000
|
port: 8080
|
||||||
---
|
---
|
||||||
# Permitir tráfico a SQL Server solo desde API
|
# Permitir tráfico a SQL Server solo desde API
|
||||||
apiVersion: networking.k8s.io/v1
|
apiVersion: networking.k8s.io/v1
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,5 @@ type: Opaque
|
||||||
stringData:
|
stringData:
|
||||||
# IMPORTANTE: Cambiar en producción
|
# IMPORTANTE: Cambiar en producción
|
||||||
# Generar con: openssl rand -base64 32
|
# Generar con: openssl rand -base64 32
|
||||||
db-password: "Your_Str0ng_P@ssword!"
|
db-password: "YourStr0ngP4ssword2026"
|
||||||
db-connection-string: "Server=sqlserver;Database=StudentEnrollment;User Id=sa;Password=Your_Str0ng_P@ssword!;TrustServerCertificate=True"
|
db-connection-string: "Server=sqlserver;Database=StudentEnrollment;User Id=sa;Password=YourStr0ngP4ssword2026;TrustServerCertificate=True"
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,10 @@ spec:
|
||||||
mountPath: /var/opt/mssql
|
mountPath: /var/opt/mssql
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
memory: "1.2Gi"
|
memory: "1200Mi"
|
||||||
cpu: "250m"
|
cpu: "250m"
|
||||||
limits:
|
limits:
|
||||||
memory: "1.5Gi"
|
memory: "1536Mi"
|
||||||
cpu: "1000m"
|
cpu: "1000m"
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
exec:
|
exec:
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export const authGuard: CanActivateFn = () => {
|
||||||
const authService = inject(AuthService);
|
const authService = inject(AuthService);
|
||||||
const router = inject(Router);
|
const router = inject(Router);
|
||||||
|
|
||||||
|
// isAuthenticated is a Signal, need to call it to get the value
|
||||||
if (authService.isAuthenticated()) {
|
if (authService.isAuthenticated()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue