From 863a65e3c826afbaf989efe951f0de529105f050 Mon Sep 17 00:00:00 2001 From: Noah Lancien Date: Fri, 30 May 2025 11:39:14 +0200 Subject: [PATCH] ci: Added ci/cd --- .dockerignore | 22 +++-- .gitlab-ci.yml | 36 ++++---- Dockerfile | 38 ++++++++ k8s-queues.sh | 30 +++++++ manifest.yaml | 185 ++++++++++++++++++++++++++++++++++++++ production-resources.yaml | 11 +++ 6 files changed, 298 insertions(+), 24 deletions(-) create mode 100644 k8s-queues.sh create mode 100644 manifest.yaml create mode 100644 production-resources.yaml diff --git a/.dockerignore b/.dockerignore index c0e2501..2d1dc13 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,10 +1,16 @@ -.git/ -.gitlab/ -.gitlab-ci.yml +.git *.md -.vscode/ -auth.json -Dockerfile -manifest.yaml node_modules/ -production-resources.yaml +npm-debug.log +.npmrc +tests/ +Dockerfile* +.dockerignore +.git +.gitignore +README.md +LICENSE +.vscode +vendor/ +public/build/ +auth.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7f03fb0..1b009f4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,19 +1,23 @@ include: -- project: 'ldc-web/shared-ci' - ref: 'main' - file: 'lint/lint.yml' -- project: 'ldc-web/shared-ci' - ref: 'main' - file: 'security/security.yml' -- project: 'ldc-web/shared-ci' - ref: 'main' - file: 'build/build.yml' -- project: 'ldc-web/shared-ci' - ref: 'main' - file: 'test/test.yml' + - project: "ldc-web/shared-ci" + ref: "main" + file: "lint/lint.yml" + - project: "ldc-web/shared-ci" + ref: "main" + file: "security/security.yml" + - project: "ldc-web/shared-ci" + ref: "main" + file: "build/docker-multistage.yml" + - project: "ldc-web/shared-ci" + ref: "main" + file: "test/test.yml" + - project: "ldc-web/shared-ci" + ref: "main" + file: "deploy/deploy.yml" stages: -- lint -- security -- build -- test + - lint + - security + - build + - test + - deploy diff --git a/Dockerfile b/Dockerfile index 46c0914..e63c3e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,44 @@ +FROM composer:2.8 AS vendor + +ARG GITLAB_USER +ARG GITLAB_PASSWORD + +COPY database/ database/ + +COPY composer.json composer.json +COPY composer.lock composer.lock + +RUN composer config gitlab-token.lab.s-web.fr ${GITLAB_USER} ${GITLAB_PASSWORD} +RUN composer install \ + --ignore-platform-reqs \ + --no-interaction \ + --no-plugins \ + --no-scripts \ + --no-dev \ + --prefer-dist + +FROM node:20.18 AS frontend + +ARG GITLAB_PASSWORD + +RUN mkdir -p /app/public/build + +COPY package*.json tsconfig.json vite.config.ts .eslintrc.json .stylelintrc.json /app/ +COPY resources/ /app/resources/ +WORKDIR /app +RUN npm config set @ldc-web:registry https://lab.s-web.fr/api/v4/packages/npm/ +RUN npm config set -- '//lab.s-web.fr/api/v4/packages/npm/:_authToken' "${GITLAB_PASSWORD}" +RUN npm ci && npm run prod + FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/ldcweb-production-8.3:latest + ARG USER=www-data + COPY --chown=${USER}:${USER} . /app/srcs +COPY --chown=${USER}:${USER} ./resources/favicon /app/srcs/public +COPY --from=vendor --chown=${USER}:${USER} /app/vendor/ /app/srcs/vendor/ +COPY --from=frontend --chown=${USER}:${USER} /app/public/build/ /app/srcs/public/build/ + USER root RUN chmod -R 755 /app/srcs/storage USER ${USER} diff --git a/k8s-queues.sh b/k8s-queues.sh new file mode 100644 index 0000000..76dd2eb --- /dev/null +++ b/k8s-queues.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# From https://stackoverflow.com/a/76488746/17320077 + +work=true + +function stopQueues() { + echo "Stopping queues..." + work=false + php artisan queue:restart + sleep 45s + exit 0 +} + +# handle graceful shutdown +trap "" SIGPIPE +trap stopQueues EXIT + +echo "Starting PHP queue workers..." + +while $work; do + php artisan queue:work database --queue=default,database --sleep=3 --tries=3 --max-time=3600 --no-interaction + sleep 1 +done + +# Si besoin de plusieurs threads puis terminer par un done sans & +#(while $work; do +# php artisan queue:work --queue="$(grep -oP 'SQS_QUEUE=\K.*' .env)" --no-interaction +# sleep 1 +#done &) diff --git a/manifest.yaml b/manifest.yaml new file mode 100644 index 0000000..483fee8 --- /dev/null +++ b/manifest.yaml @@ -0,0 +1,185 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: ${CI_PROJECT_PATH_SLUG}-config-map +data: + APP_NAME: "${APP_APP_NAME}" + APP_ENV: "${APP_APP_ENV}" + APP_KEY: "${APP_APP_KEY}" + APP_DEBUG: "${APP_APP_DEBUG}" + APP_URL: "https://${REDIRECT}${APP_DOMAIN}" + LOG_CHANNEL: "k8s" + DB_HOST: "${APP_DB_HOST}" + DB_PORT: "${APP_DB_PORT}" + DB_DATABASE: "${APP_DB_DATABASE}" + DB_USERNAME: "${APP_DB_USERNAME}" + DB_PASSWORD: "${APP_DB_PASSWORD}" + MAIL_HOST: "${APP_MAIL_HOST}" + MAIL_PORT: "${APP_MAIL_PORT}" + MAIL_USERNAME: "${APP_MAIL_USERNAME}" + MAIL_PASSWORD: "${APP_MAIL_PASSWORD}" + MAIL_ENCRYPTION: "${APP_MAIL_ENCRYPTION}" + MAIL_FROM_ADDRESS: "${APP_MAIL_FROM_ADDRESS}" + MAIL_FROM_NAME: "${APP_APP_NAME}" + MAIL_TO_ADDRESS: "${APP_MAIL_TO_ADDRESS}" + MAIL_TO_NAME: "${APP_MAIL_TO_NAME}" + SENTRY_LARAVEL_DSN: "${APP_SENTRY_LARAVEL_DSN}" + LDC_WEB_API_KEY: "${APP_LDC_WEB_API_KEY}" + SAML2_SP_CERT_x509: "${APP_SAML2_SP_CERT_x509}" + SAML2_SP_CERT_PRIVATEKEY: "${APP_SAML2_SP_CERT_PRIVATEKEY}" + SAML2_CONTACT_TECHNICAL_EMAIL: "${APP_SAML2_CONTACT_TECHNICAL_EMAIL}" + SAML2_CONTACT_SUPPORT_EMAIL: "${APP_SAML2_CONTACT_SUPPORT_EMAIL}" + SAML2_DEFAULT_TENANT_UUID: "${APP_SAML2_DEFAULT_TENANT_UUID}" + CACHE_DRIVER: "${APP_CACHE_DRIVER}" +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: ${CI_PROJECT_PATH_SLUG}-pvc-storage +spec: + storageClassName: "csi-cinder-classic" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: $APP_PVC_SIZE +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ${CI_PROJECT_PATH_SLUG}-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: ${CI_PROJECT_PATH_SLUG}-app + template: + metadata: + labels: + app: ${CI_PROJECT_PATH_SLUG}-app + spec: + containers: + - name: ${CI_PROJECT_PATH_SLUG}-container + image: $IMAGE_TAG + envFrom: + - configMapRef: + name: ${CI_PROJECT_PATH_SLUG}-config-map + volumeMounts: + - name: storage + mountPath: /app/srcs/storage + - name: ${CI_PROJECT_PATH_SLUG}-queue-worker + image: $IMAGE_TAG + lifecycle: + preStop: + exec: + command: + [ + "/bin/sh", + "-c", + "kill -s TERM 1; while test $(ps ax | grep -v grep | grep bash | wc -l) -gt 0; do sleep 2s; done", + ] + resources: + limits: + memory: 512Mi + cpu: "1" + command: + - "bash" + args: + - "k8s-queues.sh" + envFrom: + - configMapRef: + name: ${CI_PROJECT_PATH_SLUG}-config-map + volumeMounts: + - name: storage + mountPath: /app/srcs/storage + imagePullSecrets: + - name: docker-registry-credentials + securityContext: + runAsUser: 33 + runAsGroup: 33 + fsGroup: 33 + fsGroupChangePolicy: "OnRootMismatch" + volumes: + - name: storage + persistentVolumeClaim: + claimName: ${CI_PROJECT_PATH_SLUG}-pvc-storage +--- +apiVersion: v1 +kind: Service +metadata: + name: ${CI_PROJECT_PATH_SLUG}-service +spec: + selector: + app: ${CI_PROJECT_PATH_SLUG}-app + ports: + - port: 80 + targetPort: 443 +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: ${CI_PROJECT_PATH_SLUG}-ingress-route +spec: + entryPoints: + - web + - websecure + routes: + - match: Host(`${APP_DOMAIN}`) + kind: Rule + services: + - name: ${CI_PROJECT_PATH_SLUG}-service + port: 80 + middlewares: + - name: redirect-no-www-to-www + tls: + secretName: ${CRT_TLS_NAME} +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: ${CI_PROJECT_PATH_SLUG}-cronjob +spec: + schedule: "* * * * *" + concurrencyPolicy: Forbid + jobTemplate: + spec: + template: + spec: + affinity: + podAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - ${CI_PROJECT_PATH_SLUG}-app + topologyKey: "kubernetes.io/hostname" + namespaces: [$KUBE_NAMESPACE] + containers: + - name: ${CI_PROJECT_PATH_SLUG}-scheduled + image: $IMAGE_TAG + imagePullPolicy: IfNotPresent + command: + - "php" + args: + - "artisan" + - "schedule:run" + envFrom: + - configMapRef: + name: ${CI_PROJECT_PATH_SLUG}-config-map + volumeMounts: + - name: storage + mountPath: /app/srcs/storage + imagePullSecrets: + - name: docker-registry-credentials + volumes: + - name: storage + persistentVolumeClaim: + claimName: ${CI_PROJECT_PATH_SLUG}-pvc-storage + securityContext: + runAsUser: 33 + runAsGroup: 33 + fsGroup: 33 + fsGroupChangePolicy: "OnRootMismatch" + restartPolicy: OnFailure diff --git a/production-resources.yaml b/production-resources.yaml new file mode 100644 index 0000000..721e5d2 --- /dev/null +++ b/production-resources.yaml @@ -0,0 +1,11 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: $APP_DOMAIN +spec: + dnsNames: + - $APP_DOMAIN + secretName: $APP_DOMAIN + issuerRef: + name: $APP_CERT_ISSUER + kind: ClusterIssuer