diff --git a/.github/workflows/change-values-kube.yml b/.github/workflows/change-values-kube.yml new file mode 100644 index 00000000..c789ac04 --- /dev/null +++ b/.github/workflows/change-values-kube.yml @@ -0,0 +1,115 @@ +on: + workflow_call: + inputs: + secret_name: + required: false + type: string + description: "Secret name in kubernetes cluster" + default: "basegun-secret" + namespace: + required: true + type: string + description: "Namespace name in kubernetes cluster" + default: "basegun" + domain: + required: true + type: string + description: "Nom de domaine utilisé par l'application" + default: "basegun.fr" + branch: + required: true + type: string + description: "Branche de déploiement" + secrets: + KUBECONFIG: + description: 'Service account secret (run kubectl get serviceaccounts -o yaml and copy the service-account-secret-name)' + required: true + X_OVH_TOKEN: + required: true + API_OVH_TOKEN: + required: true + OS_PASSWORD: + required: true + OS_PROJECT_NAME: + required: true + OS_USERNAME: + required: true + JOB_GITHUB_TOKEN: + required: true + +jobs: + deployment: + name: Update deployment + runs-on: ubuntu-20.04 + steps: + - name: Checkout branch + uses: actions/checkout@v2 + with: + ref: ${{ inputs.branch }} + + - uses: azure/k8s-set-context@v3 + with: + method: kubeconfig + kubeconfig: ${{ secrets.KUBECONFIG }} + + - name: Create secret for Kubernetes + uses: azure/k8s-create-secret@v4 + with: + namespace: ${{ inputs.namespace }} + secret-type: 'generic' + secret-name: ${{ inputs.secret_name }} + string-data: | + { + "OS_PASSWORD": "${{ secrets.OS_PASSWORD }}", + "OS_PROJECT_NAME": "${{ secrets.OS_PROJECT_NAME }}", + "OS_USERNAME": "${{ secrets.OS_USERNAME }}", + "X_OVH_TOKEN": "${{ secrets.X_OVH_TOKEN }}", + "API_OVH_TOKEN": "${{ secrets.API_OVH_TOKEN }}" + } + + - name: Install yq + run: | + sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq && sudo chmod +x /usr/bin/yq + + - name: Update Infra Version + run: | + export TAG=$(make get-current-tag) + yq -i '.backend.image.tag = strenv(TAG)' ./infra/kube/helm/values.yaml + yq -i '.frontend.image.tag = strenv(TAG)' ./infra/kube/helm/values.yaml + + - name: Update ingress domaine + run: | + export DOMAIN="${{ inputs.domain }}" + yq -i '.ingress.hosts[0].host = strenv(DOMAIN)' ./infra/kube/helm/values.yaml + + - name: Commit and push changes + uses: devops-infra/action-commit-push@v0.3 + with: + github_token: ${{ secrets.JOB_GITHUB_TOKEN }} + commit_prefix: "[skip ci]" + commit_message: "Version updated" + +# To generate a kubeconfig, fill this file with informations available in theses commands: +# - ${token} and ${ca} +# kubectl get secret -n -o yaml +# - ${server} +# kubectl config view --minify -o 'jsonpath={.clusters[0].cluster.server}' + +# apiVersion: v1 +# kind: Config +# clusters: +# - name: default-cluster +# cluster: +# certificate-authority-data: ${ca} +# server: ${server} +# contexts: +# - name: default-context +# context: +# cluster: default-cluster +# namespace: default +# user: default-user +# current-context: default-context +# users: +# - name: default-user +# user: +# token: ${token} \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index f0775b81..8ac81ae9 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -18,26 +18,11 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} build-and-test: - runs-on: ubuntu-latest - env: - BUILD_TARGET: 'test' - TAG: '2.0' - steps: - - uses: actions/checkout@v2 - - name: Build for tests - run: | - echo "Building containers" - docker compose --profile e2e -f docker-compose-dev.yml config - docker compose --profile e2e -f docker-compose-dev.yml build - echo "Containers built" - - name: Run backend tests - run: make test-backend - env: - OS_USERNAME: ${{ secrets.OS_USERNAME }} - OS_PASSWORD: ${{ secrets.OS_PASSWORD }} - OS_PROJECT_NAME: ${{ secrets.OS_PROJECT_NAME }} - - name: Test frontend is alive - run: make test-frontend-alive - - name: Run frontend end-to-end tests - run: docker compose --profile e2e -f docker-compose-dev.yml up --abort-on-container-exit --exit-code-from cypress - + uses: ./.github/workflows/test-on-kube.yml + needs: tag-pr + secrets: + API_OVH_TOKEN: ${{ secrets.API_OVH_TOKEN }} + OS_PASSWORD: ${{ secrets.OS_PASSWORD }} + OS_PROJECT_NAME: ${{ secrets.OS_PROJECT_NAME }} + OS_USERNAME: ${{ secrets.OS_USERNAME }} + X_OVH_TOKEN: ${{ secrets.PREPROD_OVH_TOKEN }} diff --git a/.github/workflows/preprod.yml b/.github/workflows/preprod.yml index 2346ec8d..2fc00aa1 100644 --- a/.github/workflows/preprod.yml +++ b/.github/workflows/preprod.yml @@ -1,4 +1,8 @@ -on: workflow_dispatch +on: + push: + branches: + - develop + workflow_dispatch: name: CI for preprod jobs: build-push: @@ -42,22 +46,20 @@ jobs: prune-untagged: true deploy-preprod: - uses: ./.github/workflows/deploy.yml + uses: ./.github/workflows/change-values-kube.yml needs: build-push with: - image_version: "develop" branch: ${{ github.ref_name }} - volume_size: 10 - flavor: "s1-2" - workspace: "preprod" + namespace: basegun-preprod + domain: preprod.basegun.fr secrets: API_OVH_TOKEN: ${{ secrets.API_OVH_TOKEN }} - SERVER_IP: ${{ secrets.PREPROD_SERVER_IP }} OS_PASSWORD: ${{ secrets.OS_PASSWORD }} - OS_PROJECT_ID: ${{ secrets.OS_PROJECT_ID }} OS_PROJECT_NAME: ${{ secrets.OS_PROJECT_NAME }} OS_USERNAME: ${{ secrets.OS_USERNAME }} X_OVH_TOKEN: ${{ secrets.PREPROD_OVH_TOKEN }} + JOB_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + KUBECONFIG: ${{ secrets.PREPROD_K8_CONFIG }} test: runs-on: ubuntu-latest diff --git a/.github/workflows/test-on-kube.yml b/.github/workflows/test-on-kube.yml new file mode 100644 index 00000000..bf5815a3 --- /dev/null +++ b/.github/workflows/test-on-kube.yml @@ -0,0 +1,108 @@ +name: Test on kubernetes + +on: + workflow_call: + secrets: + API_OVH_TOKEN: + required: true + OS_PASSWORD: + required: true + OS_PROJECT_NAME: + required: true + OS_USERNAME: + required: true + X_OVH_TOKEN: + required: true + + +jobs: + test-app-k8s: + runs-on: ubuntu-latest + env: + LOCAL_DOMAIN: basegun.kubernetes.local + steps: + - name: Checkout to code + uses: actions/checkout@v3 + + - name: Create k8s Kind Cluster + uses: helm/kind-action@v1.4.0 + with: + cluster_name: basegun-testing + config: ./infra/kube/kind/kind-config.yml + wait: 60s + verbosity: 2 + + - name: Set up Helm + uses: azure/setup-helm@v3 + with: + version: v3.11.2 + + - name: Set up ingress controller + run: | + helm repo add traefik https://traefik.github.io/charts && helm repo update + helm install --namespace ingress-traefik --create-namespace traefik traefik/traefik --values ./infra/kube/kind/traefik-values.yml + + - name: Add hosts to /etc/hosts + run: | + sudo echo "127.0.0.1 $LOCAL_DOMAIN" | sudo tee -a /etc/hosts + # we are forced to attribute a DNS to kube cluster for it to work properly + + - name: Build and install basegun with helm, and test if deployment is successful + id: tests + run: | + TAG=$(make get-current-tag) BUILD_TARGET=test docker-compose -f docker-compose-prod.yml build backend + TAG=$(make get-current-tag) docker-compose -f docker-compose-prod.yml build frontend + kind load docker-image \ + basegun-backend:$(make get-current-tag)-prod \ + basegun-frontend:$(make get-current-tag)-prod \ + --name basegun-testing + helm upgrade --install basegun ./infra/kube/helm/ \ + --set ingress.hosts[0].host="$LOCAL_DOMAIN" \ + --set ingress.hosts[0].paths[0].path="/" \ + --set ingress.hosts[0].paths[0].pathType="Prefix" \ + --set backend.image.repository="basegun-backend" \ + --set backend.image.tag="$(make get-current-tag)-prod" \ + --set frontend.image.repository="basegun-frontend" \ + --set frontend.image.tag="$(make get-current-tag)-prod" \ + --set backend.secret.create="true" \ + --set-string backend.secret.values.OS_USERNAME="${{ secrets.OS_USERNAME }}" \ + --set-string backend.secret.values.OS_PASSWORD="${{ secrets.OS_PASSWORD }}" \ + --set-string backend.secret.values.OS_PROJECT_NAME="${{ secrets.OS_PROJECT_NAME }}" \ + --set-string backend.secret.values.X_OVH_TOKEN="${{ secrets.X_OVH_TOKEN }}" \ + --set-string backend.secret.values.API_OVH_TOKEN="${{ secrets.API_OVH_TOKEN }}" + for i in $(kubectl get deploy -o name); do kubectl rollout status $i -w --timeout=130s; done + + - name: Display pod logs on failure + if: failure() && steps.tests.outcome == 'failure' + run: | + kubectl describe pods + kubectl logs deploy/basegun-backend --all-containers --ignore-errors + kubectl logs deploy/basegun-frontend --all-containers --ignore-errors + + - name: Test unitests on backend + run : | + kubectl exec deploy/basegun-backend -c basegun-backend -- python -m unittest discover -v + + - name: Setup nodejs (for cypress) + uses: actions/setup-node@v3 + with: + node-version: 18 + check-latest: true + cache: "npm" + cache-dependency-path: "frontend/package-lock.json" + + - name: Install npm packages (for cypress) + run: npm ci + working-directory: ./frontend + + - name: Test end to end (cypress) + run: FRONTEND_HOST=$LOCAL_DOMAIN FRONTEND_PORT=80 npm run test:e2e-ci + working-directory: ./frontend + + - name: Send artifacts + uses: actions/upload-artifact@v3 + if: ${{ failure() }} + with: + name: cypress-screenshots + path: | + ./frontend/cypress/screenshots/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..e215d3a7 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,82 @@ +include: + - local: '/templates/docker.yml' + - local: '/templates/vault.yml' + +default: + image: python:3.9-slim-buster + + +variables: + http_proxy: $http_proxy + https_proxy: $http_proxy + no_proxy: $no_proxy + HTTP_PROXY: $http_proxy + HTTPS_PROXY: $http_proxy + NO_PROXY: $no_proxy + PROJECT_NAME: "basegun" + PROJECT_REPOSITORY: "basegun" + PROJECT_ORGANISATION: "ministere-interieur" + BUILD_CONFIG_FILE: $BUILD_CONFIG + REGISTRY_URL: "${QUAY_ROOT_URL}/${PROJECT_ORGANISATION}-${PROJECT_NAME}" + TAG: "1.5" + #TAG: "${CI_COMMIT_REF_SLUG}" + DOCKERFILE: 'Dockerfile' + +# GIT_CURL_VERBOSE: "1" +# GIT_DEBUG_LOOKUP: "1" +# GIT_TRANSLOOP_DEBUG: "1" +# GIT_TRANSPORT_HELPER_DEBUG: "1" + +stages: + - read-secret + - test-app + - build-docker + +read_secret: + stage: read-secret + extends: + - .vault:read_secret + +test: + image: python:3.9-slim-buster + stage: test-app + variables: + OS_USERNAME: data + OS_PASSWORD: data + OS_PROJECT_NAME: data + script: + - pip install --upgrade pip && pip install --no-cache-dir -f https://download.pytorch.org/whl/cpu/torch_stable.html -r backend/requirements.txt + - python -m unittest discover -v -s ./backend + allow_failure: true + +build_docker_front: + variables: + WORKING_DIR: 'frontend' + IMAGE_NAME: 'frontend' + DOCKERFILE: 'Dockerfile-dso' + stage: build-docker + extends: + - .kaniko:build + +build_docker_back: + variables: + WORKING_DIR: 'backend' + IMAGE_NAME: 'backend' + DOCKERFILE: 'Dockerfile-dso' + stage: build-docker + extends: + - .kaniko:build + +build_docker_logs: + variables: + WORKING_DIR: 'logs' + IMAGE_NAME: 'logs' + DOCKERFILE: 'Dockerfile-dso' + NO_PROXY: "*,gitlab-op.apps.ocp4-8.infocepo.com,dindservice,quay.apps.ocp4-8.infocepo.com" + no_proxy: "*,gitlab-op.apps.ocp4-8.infocepo.com,dindservice,quay.apps.ocp4-8.infocepo.com" + stage: build-docker + extends: + - .docker:build + tags: + - docker + - vms diff --git a/Makefile b/Makefile index 0304b1f9..af85ead4 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ SHELL := /bin/bash DOCKER := $(shell type -p docker) DC := $(shell type -p docker-compose) -TAG := 2.1 +TAG := 3.0 APP_NAME := basegun REG := ghcr.io ORG := datalab-mi - +UVICORN_LOG_LEVEL := # info, debug, trace export @@ -15,6 +15,9 @@ show-current-tag: done ; \ [ $$CONTINUE = "y" ] || [ $$CONTINUE = "Y" ] || (echo "Exiting."; exit 1;) +get-current-tag: + @echo ${TAG} + check-prerequisites: ifeq ("$(wildcard ${DOCKER})","") @echo "docker not found" ; exit 1 diff --git a/backend/Dockerfile b/backend/Dockerfile index 2057e94c..581bd293 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -37,7 +37,7 @@ CMD ["uvicorn", "src.main:app", "--reload", "--host", "0.0.0.0", "--port", "5000 FROM base as test RUN pip install requests && rm -r /root/.cache COPY tests/ tests/ -CMD ["uvicorn", "src.main:app", "--reload", "--host", "0.0.0.0", "--port", "5000"] +CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "5000"] FROM base as prod -CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "5000"] \ No newline at end of file +CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "5000"] diff --git a/backend/Dockerfile-dso b/backend/Dockerfile-dso new file mode 100644 index 00000000..b3dc618f --- /dev/null +++ b/backend/Dockerfile-dso @@ -0,0 +1,49 @@ +FROM python:3.9-slim-buster as base +WORKDIR /app + +# certificates config +ARG CACERT_LOCATION +COPY ./cert/. /etc/ssl/certs/ + +# librairies necessary for image processing +RUN apt update && apt install -y \ + libgl1-mesa-glx \ + libglib2.0-0 \ + curl \ + gcc \ + && rm -rf /var/lib/apt/lists/* + +# install python libraries +COPY requirements.txt ./ +ENV PIP_CERT=$CACERT_LOCATION +RUN pip install --upgrade pip \ + && pip --default-timeout=300 install --no-cache-dir -f \ + https://download.pytorch.org/whl/cpu/torch_stable.html -r requirements.txt \ + && rm -r /root/.cache + +# launch website +ARG VERSION +ARG MODEL="EffB4_2022-03-02_08" +ENV SSL_CERT_FILE=$CACERT_LOCATION +RUN curl -o model.pth https://storage.gra.cloud.ovh.net/v1/AUTH_df731a99a3264215b973b3dee70a57af/basegun-public/models/${MODEL}/${MODEL}.pth +COPY src/ src/ +RUN mkdir -p src/weights \ + && mv model.pth src/weights/model.pth \ + && echo '{"app": "'${VERSION}'", "model": "'${MODEL}'"}' > versions.json + +FROM base as dev +CMD ["uvicorn", "src.main:app", "--reload", "--host", "0.0.0.0", "--port", "5000"] + +FROM base as test +RUN pip install requests && rm -r /root/.cache +COPY tests/ tests/ +CMD ["uvicorn", "src.main:app", "--reload", "--host", "0.0.0.0", "--port", "5000"] + +FROM base as prod +USER root +RUN ls +RUN grep -rlw http /etc |while read i ;do sed -ri 's#\b80\b#8080#' ${i} ; sed -ri 's#\b443\b#6443#' ${i} ;done +RUN find / -xdev -exec chmod -f -c g=u {} \; -exec chown -f -c 1001:root {} \; >/dev/null 2>&1 +RUN grep -w 1001 /etc/passwd ||echo "rootless:x:1001:0:root:/root:/bin/bash" >>/etc/passwd +USER 1001 +CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "5000"] diff --git a/backend/src/main.py b/backend/src/main.py index 3ca0d4ad..dad4c7a1 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -1,3 +1,4 @@ + import os import logging from logging.handlers import TimedRotatingFileHandler @@ -16,6 +17,7 @@ CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) +WORKSPACE = os.environ.get("WORKSPACE") CLOUD_PATH = f'https://storage.gra.cloud.ovh.net/v1/' + \ 'AUTH_df731a99a3264215b973b3dee70a57af/basegun-public/' + \ @@ -155,11 +157,11 @@ def upload_image_ovh(content: bytes, img_name: str): "https://preprod.basegun.fr", "http://localhost", "http://localhost:8080", - "http://localhost:3000" + "http://localhost:3000", ] app.add_middleware( CORSMiddleware, - allow_origins=origins, + allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], @@ -192,19 +194,22 @@ def upload_image_ovh(content: bytes, img_name: str): conn = None -if "OS_USERNAME" in os.environ: - # Connection to OVH cloud - conn = swiftclient.Connection( - authurl="https://auth.cloud.ovh.net/v3", - user=os.environ["OS_USERNAME"], - key=os.environ["OS_PASSWORD"], - os_options={ - "project_name": os.environ["OS_PROJECT_NAME"], - "region_name": "GRA" - }, - auth_version='3' - ) - conn.get_account() +if all(var in os.environ for var in ["OS_USERNAME", "OS_PASSWORD", "OS_PROJECT_NAME"]) : + try: + # Connection to OVH cloud + conn = swiftclient.Connection( + authurl="https://auth.cloud.ovh.net/v3", + user=os.environ["OS_USERNAME"], + key=os.environ["OS_PASSWORD"], + os_options={ + "project_name": os.environ["OS_PROJECT_NAME"], + "region_name": "GRA" + }, + auth_version='3' + ) + conn.get_account() + except Exception as e: + logger.exception(e) else: logger.warn('Variables necessary for OVH connection not set !') diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 7c919cc5..10542cfb 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -1,7 +1,5 @@ version: '3.8' - services: - backend: profiles: - app @@ -24,6 +22,8 @@ services: - OS_PROJECT_NAME - http_proxy - https_proxy + - UVICORN_LOG_LEVEL=${UVICORN_LOG_LEVEL} + - LOG_LEVEL=${UVICORN_LOG_LEVEL} - no_proxy - WORKSPACE=dev - REQUESTS_CA_BUNDLE=$CACERT_LOCATION @@ -58,19 +58,3 @@ services: volumes: - $PWD/frontend/src:/app/src - /app/node_modules - - cypress: - depends_on: - - frontend - - backend - profiles: - - e2e - container_name: basegun-cypress - build: - context: ./frontend - dockerfile: Dockerfile.cypress - image: basegun-cypress:${TAG:-2.0}-dev - environment: - - FRONTEND_HOST=frontend - - FRONTEND_PORT=80 - working_dir: /e2e diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index f5a76cb3..53a46107 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -7,7 +7,7 @@ services: args: - VERSION=${TAG} context: ./backend - target: prod + target: ${BUILD_TARGET:-prod} container_name: basegun-backend environment: - PATH_LOGS=/app/logs @@ -28,7 +28,7 @@ services: container_name: basegun-frontend image: basegun-frontend:${TAG}-prod ports: - - ${PORT_PROD:-80}:80 + - ${PORT_PROD:-80}:8080 filebeat: image: elastic/filebeat:6.5.4 @@ -38,6 +38,6 @@ services: - X_OVH_TOKEN - API_OVH_TOKEN volumes: - - $PWD/infra/filebeat.elastic.yml:/usr/share/filebeat/filebeat.yml:ro + - $PWD/infra/kube/helm/configs/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro - /var/log/basegun:/var/log/basegun command: bash -c "sleep 30 && filebeat -e --strict.perms=false" \ No newline at end of file diff --git a/frontend/Dockerfile-dso b/frontend/Dockerfile-dso new file mode 100644 index 00000000..d4af73a5 --- /dev/null +++ b/frontend/Dockerfile-dso @@ -0,0 +1,39 @@ +FROM node:18-bullseye-slim as base + +WORKDIR /app + +# certificates config +ARG NODE_EXTRA_CA_CERTS +COPY ./cert/. /etc/ssl/certs/ + +# downgrade npm version to avoid chokidar error +RUN npm install -g npm@8 + +COPY ./package.json ./package-lock.json ./ +RUN npm ci + +COPY src ./src +COPY public ./public +COPY vite.config.js index.html ./ + +FROM base as dev + +CMD ["npm", "run", "dev"] + +FROM base as build + +# RUN npm ci --production +RUN npm run build + +FROM nginx:1.21.4-alpine as prod + +COPY nginx.conf /etc/nginx/nginx.conf + +COPY --from=build /app/dist /usr/share/nginx/html/ + +USER root +RUN ls +RUN grep -rlw http /etc |while read i ;do sed -ri 's#\b80\b#8080#' ${i} ; sed -ri 's#\b443\b#6443#' ${i} ;done +RUN find / -xdev -exec chmod -f -c g=u {} \; -exec chown -f -c 1001:root {} \; >/dev/null 2>&1 +RUN grep -w 1001 /etc/passwd ||echo "rootless:x:1001:0:root:/root:/bin/bash" >>/etc/passwd +USER 1001 diff --git a/frontend/Dockerfile.cypress b/frontend/Dockerfile.cypress deleted file mode 100644 index 129ebf91..00000000 --- a/frontend/Dockerfile.cypress +++ /dev/null @@ -1,7 +0,0 @@ -FROM cypress/included as base - -WORKDIR /e2e - -COPY . . - -RUN npm ci \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md index 34e38d23..9c7e6bff 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -32,6 +32,17 @@ Open localhost:3000 docker run --rm -p 3000:3000 -d basegun-front:dev ``` +## Test project +Run end-to-end tests with cypress. You need first to have installed dependancies with `npm ci`. + +First run website with Docker then +```bash +# run in headless mode (only in terminal) +npm --prefix frontend run test:e2e-run + +# run with graphical interface +npm --prefix frontend run test:e2e-open +``` ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/). diff --git a/frontend/nginx.conf b/frontend/nginx.conf index c92676f3..bbc780b3 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -4,7 +4,7 @@ events { worker_connections 1024; } http { server { - listen 80; + listen 8080; root /usr/share/nginx/html; include /etc/nginx/mime.types; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 22a7eb93..85f7e0fc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,19 +1,18 @@ { "name": "basegun", - "version": "2.0.0", + "version": "3.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "basegun", - "version": "2.0.0", + "version": "3.0.0", "dependencies": { "@gouvfr/dsfr": "^1.9.2", "@gouvminint/vue-dsfr": "3.6.0", "@vue/compat": "^3.3.2", "@vueuse/core": "^10.1.2", "axios": "^1.4.0", - "cypress": "^12.13.0", "pinia": "^2.1.0", "stylelint-config-recommended-vue": "^1.4.0", "swiper": "^9.3.2", diff --git a/frontend/package.json b/frontend/package.json index 3e7965ad..17734966 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "basegun", - "version": "2.0.0", + "version": "3.0.0", "private": true, "scripts": { "preview": "vite preview", @@ -11,8 +11,9 @@ "format": "eslint src --fix", "format:css": "stylelint --fix src/**/*.{css,vue}", "pretest:unit": "npm run lint", - "test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e --browser firefox'", - "test:e2e-dev": "API_HOST=localhost start-server-and-test 'vite --port 4173' http://localhost:4173 'cypress open --e2e'" + "test:e2e-ci": "cypress run --e2e --browser firefox", + "test:e2e-open": "FRONTEND_HOST=localhost FRONTEND_PORT=3000 cypress open --e2e --browser firefox", + "test:e2e-run": "FRONTEND_HOST=localhost FRONTEND_PORT=3000 cypress run --e2e --browser firefox" }, "dependencies": { "@gouvfr/dsfr": "^1.9.2", @@ -20,7 +21,6 @@ "@vue/compat": "^3.3.2", "@vueuse/core": "^10.1.2", "axios": "^1.4.0", - "cypress": "^12.13.0", "pinia": "^2.1.0", "stylelint-config-recommended-vue": "^1.4.0", "swiper": "^9.3.2", @@ -54,6 +54,6 @@ "not dead" ], "volta": { - "node": "18.14.0" + "node": "18.16.1" } } diff --git a/frontend/src/components/SnackbarAlert.vue b/frontend/src/components/SnackbarAlert.vue index 42109826..25109395 100644 --- a/frontend/src/components/SnackbarAlert.vue +++ b/frontend/src/components/SnackbarAlert.vue @@ -22,6 +22,7 @@ watch(route, closeSnackbar) v-show="show" class="mx-auto snackbar" > + - -### Classic conf files -- `backend.tf` : the remote state (in Swift container), where current state is stored. -- `provider.tf` : select provider (OVH) -- `output.tf` -- `version.tf` : version for terraform init - -### Variables -We add TF variables in 2 different ways. -- Variables related to **the instance** configuration are directly passed as env variables to Terraform using the prefix `TF_VAR`. -The variables related to the instance are the following: - - fixed_ip: fixed ip for VM (populated with github secret `PREPROD_SERVER_IP` or `PROD_SERVER_IP`) - - flavor: code for VM size on OVH - - volume_size: size in GB of instance - -- Variables related to **Basegun product** are passed via `terraform apply --varfile="deployenv.tfvars"`. This `deployenv.tfvars` file is created by replacing the env variables from the CD in the file `env.tfvars`. -The variables related to Basegun website are the folowing: - - API_OVH_TOKEN : credential token for logs (see Logging section) - - OS_USERNAME : username of cloud provider - - OS_PASSWORD : password for this user - - OS_PROJECT_NAME : value from `openrc.sh` (find on horizon platform) - - OS_PROJECT_ID : value from `openrc.sh` (find on horizon platform) - - WORKSPACE : preprod or prod - - X_OVH_TOKEN : token for logs (see Logging section) -For practical reasons, we encapsulate all these variables in one large dictionary variable called `deploy_env`. - -> Regardless of how they are passed to Terraform, all variables must be declared in `variables.tf` file. - -### Instance deployment - - -The file `instance.tf` contains all details related to instance deployment. -* VM size -* VM base image (Debian) -* IP (declared via network object) -* SSH connexion configuration (keypair and port) - -The file `template.tf` decribes the files which must be rendered during deployment. Here we render 2 scripts, `init.yaml` as a cloud-init file (lauched at VM start), which installs dependencies and add security settings, and `deploy.sh` as a regular script file (therefore launched when VM is ready), which downloads github repository archive and launches the app. - -## Logging -### Requirements -2 variables in environment -* X_OVH_TOKEN : token for OVH data steam -* API_OVH_TOKEN : credential token for OVH log data platform - -### Explanation -Basegun website backend writes logs in a log file hosted locally on the server where the website runs. -The log file rotates every day : the current log file is `log.json` while the previous logs are stored in the same folder with the date as suffix in the filename. The log files are stored locally for 7 days. - -We use OVH Log Data Platform as endpoint for these logs. We use 3 separate "Data streams" destinations which correspond to 3 different ElasticSearch aliases/indexes: one for dev, one for preprod, one for prod, so that we don't mix the logs issues from these 3 phases. We set the variable `X_OVH_TOKEN` accordingly to the data stream we want to use. - -The logs are sent from the server to the endpoint using Filebeat. A Filebeat Docker container using `filebeat.elastic.yml` conf file is launched at the same time as the website. It watches the log file written by the website and sends the log to OVH every time there is a change in the file. - -In OVH Log Data Platform, we use Kibana to visualize the logs and make queries to extract valuable information from them. - - +The containers of the application are deployed using files in folder `helm/templates`: +- `deployment-frontend.yml`: config for pod for frontend of basegun +- `deployment-backend.yml`: config for pod for backend (container for backend + container for handling logs) +Some of the values in these configs are configured in `helm/values.yaml`, and can be passed from CI/CD using keyword `helm upgrade --install basegun ./infra/kube/helm/ --set="value.arg1.arg2=my-value"` which overwrites content of `values.yml`. +To test a template / generate a manifest, you can use command `helm template /path/to/file`. \ No newline at end of file diff --git a/infra/img/full-infra.jpg b/infra/img/full-infra.jpg deleted file mode 100644 index 8e3cb6a9..00000000 Binary files a/infra/img/full-infra.jpg and /dev/null differ diff --git a/infra/img/openstack.png b/infra/img/openstack.png deleted file mode 100644 index aea26f5f..00000000 Binary files a/infra/img/openstack.png and /dev/null differ diff --git a/infra/img/terraform.jpg b/infra/img/terraform.jpg deleted file mode 100644 index 8c43fe3e..00000000 Binary files a/infra/img/terraform.jpg and /dev/null differ diff --git a/infra/kube/cluster-config/README.md b/infra/kube/cluster-config/README.md new file mode 100644 index 00000000..ff4af90b --- /dev/null +++ b/infra/kube/cluster-config/README.md @@ -0,0 +1,110 @@ +# Configuration d'un cluster Kubernetes + +Exemple de configuration d'un cluster kubernetes pour l'installation de l'application + + +## 1. Ingress controller + +L'ingress controller permet de gérer connexions entrantes au cluster sous la forme d'un reverse proxy. Pour notre cas nous utiliserons `traefik` et activerons +- letsencrypt par défaut +- la redirection en https automatique pour tous les ingress + + +Ajout du dépôt traefik +``` +helm repo add traefik traefik/traefik +helm repo update +``` + +Installation de l'ingress controller + +``` +helm upgrade traefik traefik/traefik -n traefik -f values-lb.yaml +``` + +## 2. Installation de ArgoCD + + +``` +kubectl create namespace argocd +kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml +``` + +Pour plus de configuration, comme l'authentification ou les RBAC, consulter ce [lien](https://argo-cd.readthedocs.io/en/stable/getting_started/). + +Ajouter un ingress pour argocd + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: argocd + namespace: argocd +spec: + ingressClassName: traefik-internal + rules: + - host: + http: + paths: + - backend: + service: + name: argocd-server + port: + number: 443 + path: / + pathType: Prefix + - backend: + service: + name: argocd-server + port: + number: 80 + path: / + pathType: Prefix +``` + +## 3. Création des comptes de service + +Cette partie permet de créer un compte de service pour l'ajout des secrets via Github (en Github Action) + +``` +kubectl apply -f sa.yaml -n +``` + +Cette commande permet de : +- Créer un rôle permettant au service account de gérer les secrets dans son namespace +- Créer un SA (compte de service) +- Créer un secret permanent permettant de se connecter au cluster (via un token) en tant que compte de service, crée précédemment +- Ajouter le rôle crée à la première étape au compte de service + +Par exemple, pour récupérer le kubeconfig pour l'environnement + +```bash +token=$(kubectl get secret -n basegun- token-github -o jsonpath="{.data.token}" | base64 -d) +ca=$(kubectl get secret -n basegun- token-github -o jsonpath="{.data.ca\.crt}") +server=$(kubectl config view --minify -o 'jsonpath={.clusters[0].cluster.server}') +``` + +Et remplir le yaml suivant : + +```yaml +apiVersion: v1 +kind: Config +clusters: +- name: default-cluster + cluster: + certificate-authority-data: ${ca} + server: ${server} +contexts: +- name: default-context + context: + cluster: default-cluster + namespace: default + user: default-user +current-context: default-context +users: +- name: default-user + user: + token: ${token} +``` + +Et ajouter le contenu du yaml dans le secret github _K8_CONFIG \ No newline at end of file diff --git a/infra/kube/cluster-config/sa.yaml b/infra/kube/cluster-config/sa.yaml new file mode 100644 index 00000000..83033144 --- /dev/null +++ b/infra/kube/cluster-config/sa.yaml @@ -0,0 +1,35 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: create-secret-role +rules: +- apiGroups: [""] + resources: ["secrets"] + verbs: ["*"] +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: github +--- +apiVersion: v1 +kind: Secret +type: kubernetes.io/service-account-token +metadata: + namespace: basegun-preprod + name: token-github + annotations: + kubernetes.io/service-account.name: github +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: github-secret-rolebinding +subjects: +- kind: ServiceAccount + name: github + apiGroup: "" +roleRef: + kind: Role + name: create-secret-role + apiGroup: "" diff --git a/infra/kube/cluster-config/values-lb.yaml b/infra/kube/cluster-config/values-lb.yaml new file mode 100644 index 00000000..792eb0b9 --- /dev/null +++ b/infra/kube/cluster-config/values-lb.yaml @@ -0,0 +1,59 @@ +certResolvers: + le: + email: datalab@interieur.gouv.fr + httpChallenge: + entryPoint: "web" + storage: /tmp/acme.json + #caServer: https://acme-staging-v02.api.letsencrypt.org/directory +persistence: + enabled: true + name: external-traefik + accessMode: ReadWriteOnce + storageClass: csi-cinder-classic + size: 1Gi + path: /data + + +service: + enabled: true + +providers: + kubernetesIngress: + ingressClass: traefik-external + publishedService: + enabled: true + +ingressClass: + enabled: true + isDefaultClass: true + +ports: + web: + redirectTo: websecure + websecure: + tls: + certResolver: le + +deployment: + # Can be either Deployment or DaemonSet + kind: Deployment + +updateStrategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + maxSurge: 0 + +logs: + general: + # Alternative logging levels are DEBUG, PANIC, FATAL, ERROR, WARN, and INFO. + level: INFO + access: + # To enable access logs + enabled: true + ## By default, logs are written using the Common Log Format (CLF) on stdout. + ## To write logs in JSON, use json in the format option. + ## If the given format is unsupported, the default (CLF) is used instead. + # format: json + filePath: "/data/logs/access.log" + diff --git a/infra/kube/helm/.helmignore b/infra/kube/helm/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/infra/kube/helm/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/infra/kube/helm/Chart.yaml b/infra/kube/helm/Chart.yaml new file mode 100644 index 00000000..de43662f --- /dev/null +++ b/infra/kube/helm/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: basegun +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/infra/filebeat.elastic.yml b/infra/kube/helm/configs/filebeat.yml similarity index 99% rename from infra/filebeat.elastic.yml rename to infra/kube/helm/configs/filebeat.yml index 76a49cb6..dd794283 100644 --- a/infra/filebeat.elastic.yml +++ b/infra/kube/helm/configs/filebeat.yml @@ -25,7 +25,7 @@ filebeat.inputs: # Paths that should be crawled and fetched. Glob based paths. paths: - - /var/log/basegun/log.json + - /tmp/log/basegun/log.json # Exclude lines. A list of regular expressions to match. It drops the lines that are # matching any regular expression from the list. diff --git a/infra/kube/helm/templates/NOTES.txt b/infra/kube/helm/templates/NOTES.txt new file mode 100644 index 00000000..25408dd7 --- /dev/null +++ b/infra/kube/helm/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.frontend.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "basegun.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.frontend.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "basegun.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "basegun.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.frontend.service.port }} +{{- else if contains "ClusterIP" .Values.frontend.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "basegun.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/infra/kube/helm/templates/_helpers.tpl b/infra/kube/helm/templates/_helpers.tpl new file mode 100644 index 00000000..f483aa35 --- /dev/null +++ b/infra/kube/helm/templates/_helpers.tpl @@ -0,0 +1,91 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "basegun.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "basegun.FrontFullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}-frontend +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }}-frontend +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}-frontend +{{- end }} +{{- end }} +{{- end }} + +{{- define "basegun.BackFullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}-backend +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }}-backend +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}-backend +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "basegun.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "basegun.BackLabels" -}} +helm.sh/chart: {{ include "basegun.chart" . }} +{{ include "basegun.BackSelectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + + +{{- define "basegun.FrontLabels" -}} +helm.sh/chart: {{ include "basegun.chart" . }} +{{ include "basegun.FrontSelectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "basegun.FrontSelectorLabels" -}} +app.kubernetes.io/name: {{ include "basegun.name" . }}-frontend +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + + +{{- define "basegun.BackSelectorLabels" -}} +app.kubernetes.io/name: {{ include "basegun.name" . }}-backend +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "basegun.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "basegun.name" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/infra/kube/helm/templates/configmap.yaml b/infra/kube/helm/templates/configmap.yaml new file mode 100644 index 00000000..8daa0ba6 --- /dev/null +++ b/infra/kube/helm/templates/configmap.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "basegun.name" . }}-config +data: + WORKSPACE: {{ .Values.backend.config.workspace }} + PATH_IMGS: {{ .Values.backend.config.path_imgs }} + PATH_LOGS: /tmp/log/basegun/ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "basegun.name" . }}-filebeat-config +data: + filebeat.yml: |- + {{ .Files.Get "configs/filebeat.yml" | nindent 4 }} \ No newline at end of file diff --git a/infra/kube/helm/templates/deployment-backend.yaml b/infra/kube/helm/templates/deployment-backend.yaml new file mode 100644 index 00000000..7b213736 --- /dev/null +++ b/infra/kube/helm/templates/deployment-backend.yaml @@ -0,0 +1,104 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "basegun.BackFullname" . }} + labels: + {{- include "basegun.BackLabels" . | nindent 4 }} +spec: + {{- if not .Values.backend.autoscaling.enabled }} + replicas: {{ .Values.backend.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "basegun.BackSelectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "basegun.BackSelectorLabels" . | nindent 8 }} + spec: + {{- with .Values.backend.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "basegun.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.backend.podSecurityContext | nindent 8 }} + containers: + - name: {{ include "basegun.BackFullname" . }} + securityContext: + {{- toYaml .Values.backend.securityContext | nindent 12 }} + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + envFrom: + - configMapRef: + name: {{ include "basegun.name" . }}-config + - secretRef: + name: {{ .Values.backend.secret.secretName }} + ports: + - name: api + containerPort: {{ .Values.backend.service.containerPort }} + protocol: TCP + livenessProbe: + httpGet: + path: / + port: api + initialDelaySeconds: 10 + periodSeconds: 60 + readinessProbe: + httpGet: + path: / + port: api + volumeMounts: + - name: logs + mountPath: /tmp/log/basegun/ + - name: filebeat + imagePullPolicy: {{ .Values.backend.logs.pullPolicy }} + image: "{{ .Values.backend.logs.repository }}:{{ .Values.backend.logs.tag }}" + command: ["/bin/bash"] + args: ["-c", "filebeat -e -c /tmp/filebeat/filebeat.yml --strict.perms=false"] + resources: {} + env: + - name: X_OVH_TOKEN + valueFrom: + secretKeyRef: + name: {{ include "basegun.name" . }}-secret + key: X_OVH_TOKEN + - name: API_OVH_TOKEN + valueFrom: + secretKeyRef: + name: {{ include "basegun.name" . }}-secret + key: API_OVH_TOKEN + volumeMounts: + - name: {{ include "basegun.name" . }}-filebeat-conf + mountPath: /tmp/filebeat/ + - name: logs + mountPath: /tmp/log/basegun + volumes: + - name: {{ include "basegun.name" . }}-filebeat-conf + configMap: + name: {{ include "basegun.name" . }}-filebeat-config + items: + - key: filebeat.yml + path: filebeat.yml + - name: {{ include "basegun.name" . }}-secret + secret: + secretName: {{ .Values.backend.secret.secretName }} + - name: logs + emptyDir: {} + + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.backend.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.backend.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/infra/kube/helm/templates/deployment-frontend.yaml b/infra/kube/helm/templates/deployment-frontend.yaml new file mode 100644 index 00000000..d6f0ce31 --- /dev/null +++ b/infra/kube/helm/templates/deployment-frontend.yaml @@ -0,0 +1,59 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "basegun.FrontFullname" . }} + labels: + {{- include "basegun.FrontLabels" . | nindent 4 }} +spec: + replicas: {{ .Values.frontend.replicaCount }} + selector: + matchLabels: + {{- include "basegun.FrontSelectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "basegun.FrontSelectorLabels" . | nindent 8 }} + spec: + {{- with .Values.frontend.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "basegun.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.frontend.podSecurityContext | nindent 8 }} + containers: + - name: {{ include "basegun.FrontFullname" . }} + securityContext: + {{- toYaml .Values.frontend.securityContext | nindent 12 }} + image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.frontend.service.containerPort }} + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 60 + readinessProbe: + httpGet: + path: / + port: http + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.frontend.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.frontend.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/infra/kube/helm/templates/hpa.yaml b/infra/kube/helm/templates/hpa.yaml new file mode 100644 index 00000000..9744fba2 --- /dev/null +++ b/infra/kube/helm/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.backend.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "basegun.BackFullname" . }} + labels: + {{- include "basegun.BackLabels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "basegun.BackFullname" . }} + minReplicas: {{ .Values.backend.autoscaling.minReplicas }} + maxReplicas: {{ .Values.backend.autoscaling.maxReplicas }} + metrics: + {{- if .Values.backend.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.backend.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.backend.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.backend.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/infra/kube/helm/templates/ingress.yaml b/infra/kube/helm/templates/ingress.yaml new file mode 100644 index 00000000..9e72b1fe --- /dev/null +++ b/infra/kube/helm/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "basegun.chart" . -}} +{{- $svcPort := .Values.frontend.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: "basegun-ingress" + labels: + {{- include "basegun.FrontLabels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: "basegun-frontend" + port: + number: {{ $svcPort }} + {{- else }} + serviceName: "basegun-frontend" + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/infra/kube/helm/templates/secret.yaml b/infra/kube/helm/templates/secret.yaml new file mode 100644 index 00000000..059faf63 --- /dev/null +++ b/infra/kube/helm/templates/secret.yaml @@ -0,0 +1,12 @@ +{{- if .Values.backend.secret.create -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.backend.secret.secretName }} +data: +{{- range $key, $value := .Values.backend.secret.values }} + {{ $key }}: {{ $value | b64enc }} +{{- end -}} +{{- end -}} + + diff --git a/infra/kube/helm/templates/service.yaml b/infra/kube/helm/templates/service.yaml new file mode 100644 index 00000000..7bb28f7f --- /dev/null +++ b/infra/kube/helm/templates/service.yaml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: Service +metadata: + name: basegun-frontend + labels: + {{- include "basegun.FrontLabels" . | nindent 4 }} +spec: + type: {{ .Values.frontend.service.type }} + ports: + - port: {{ .Values.frontend.service.port }} + targetPort: {{ .Values.frontend.service.containerPort }} + protocol: TCP + name: http + selector: + {{- include "basegun.FrontSelectorLabels" . | nindent 4 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: basegun-backend + labels: + {{- include "basegun.BackLabels" . | nindent 4 }} +spec: + type: {{ .Values.backend.service.type }} + ports: + - port: {{ .Values.backend.service.port }} + targetPort: {{ .Values.backend.service.containerPort }} + protocol: TCP + name: http + selector: + {{- include "basegun.BackSelectorLabels" . | nindent 4 }} diff --git a/infra/kube/helm/templates/serviceaccount.yaml b/infra/kube/helm/templates/serviceaccount.yaml new file mode 100644 index 00000000..2a39beb8 --- /dev/null +++ b/infra/kube/helm/templates/serviceaccount.yaml @@ -0,0 +1,10 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "basegun.serviceAccountName" . }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/infra/kube/helm/templates/tests/test-connection.yaml b/infra/kube/helm/templates/tests/test-connection.yaml new file mode 100644 index 00000000..2667e7a6 --- /dev/null +++ b/infra/kube/helm/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "basegun.FrontFullname" . }}-test-connection" + labels: + {{- include "basegun.FrontLabels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "basegun.FrontFullname" . }}:{{ .Values.frontend.service.port }}'] + restartPolicy: Never diff --git a/infra/kube/helm/values.yaml b/infra/kube/helm/values.yaml new file mode 100644 index 00000000..2a603f0f --- /dev/null +++ b/infra/kube/helm/values.yaml @@ -0,0 +1,130 @@ +# Default values for basegun. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +nameOverride: "" +fullnameOverride: "" +ingress: + enabled: true + className: "" + #annotations: + #traefik.ingress.kubernetes.io/router.tls: "true" + #traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt + hosts: + - host: preprod.basegun.fr + paths: + - path: / + pathType: Prefix + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" +# Config for backend +backend: + replicaCount: 1 + image: + repository: ghcr.io/datalab-mi/basegun/basegun-backend + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "3.0" + imagePullSecrets: [] + podAnnotations: {} + podSecurityContext: {} + # fsGroup: 2000 + securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + logs: + repository: elastic/filebeat + tag: 6.5.4 + pullPolicy: IfNotPresent + service: + type: ClusterIP + port: 5000 + containerPort: 5000 + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + config: + # Where images are stored locally + path_imgs: /app/images + # Used for showing current workspace in app + workspace: preprod + secret: + create: false + # If create is true, you can provide values else it use existing secret + #values: + # OS_PASSWORD: "" + # OS_PROJECT_NAME: "" + # OS_USERNAME: "" + # X_OVH_TOKEN: "test" + # API_OVH_TOKEN: "test" + secretName: "basegun-secret" + resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + nodeSelector: {} + tolerations: [] + affinity: {} +# Config for frontend +frontend: + replicaCount: 1 + image: + repository: ghcr.io/datalab-mi/basegun/basegun-frontend + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "3.0" + imagePullSecrets: [] + podAnnotations: {} + podSecurityContext: {} + # fsGroup: 2000 + + securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + + service: + type: ClusterIP + port: 8080 + containerPort: 8080 + resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + nodeSelector: {} + tolerations: [] + affinity: {} diff --git a/infra/kube/kind/README.md b/infra/kube/kind/README.md new file mode 100644 index 00000000..961b8d23 --- /dev/null +++ b/infra/kube/kind/README.md @@ -0,0 +1,12 @@ +# Run kubernetes locally with docker + +## Prerequisite + +Download & install on your local machine : +- [kind](https://github.com/kubernetes-sigs/kind) +- [kubectl](https://github.com/kubernetes/kubectl) +- [helm](https://github.com/helm/helm) + +## Cluster + +One single node is deployed but it can be customized in `./kind-config.yml`. The cluster comes with [Traefik](https://doc.traefik.io/traefik/providers/kubernetes-ingress/) ingress controller installed with port mapping on both ports `80` and `443`. diff --git a/infra/kube/kind/kind-config.yml b/infra/kube/kind/kind-config.yml new file mode 100644 index 00000000..66f509dc --- /dev/null +++ b/infra/kube/kind/kind-config.yml @@ -0,0 +1,18 @@ +--- +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: +- role: control-plane + kubeadmConfigPatches: + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: + node-labels: "ingress-ready=true" + extraPortMappings: + - containerPort: 30080 + hostPort: 80 + protocol: TCP + - containerPort: 30443 + hostPort: 443 + protocol: TCP diff --git a/infra/kube/kind/traefik-values.yml b/infra/kube/kind/traefik-values.yml new file mode 100644 index 00000000..3af7988d --- /dev/null +++ b/infra/kube/kind/traefik-values.yml @@ -0,0 +1,19 @@ +--- +providers: + kubernetesCRD: + namespaces: + - default + - traefik + kubernetesIngress: + namespaces: + - default + - traefik + +ports: + web: + nodePort: 30080 + websecure: + nodePort: 30443 + +service: + type: NodePort diff --git a/infra/legacy/README.md b/infra/legacy/README.md new file mode 100644 index 00000000..1cd4f230 --- /dev/null +++ b/infra/legacy/README.md @@ -0,0 +1,81 @@ +# Infra + +This folder stores various contents related to Basegun deployment. + +## Full infrastructure of the project +![Basegun-infra-legacy-1](https://github.com/datalab-mi/Basegun/assets/24997639/12692b3a-6108-47a9-ac16-44da2ebabd01) + +1. Using Github actions, we trigger either the [*preprod* deployment](../../.github/workflows/develop.yml) or the [*prod* deployment](../../.github/workflows/release.yml). +2. For both of them, the [workflow calls Terraform](../.github/workflows/deploy.yml) using specific env variables to differentiate preprod from prod, to start a VM on OVH public cloud. +3. On the VM, we always have [3 containers](../docker-compose-prod.yml) : +* 1 for Basegun frontend (Vue.js website) +* 1 for Basegun backend (Python API) +* 1 for the log collector (Filebeat ) +The frontend communicates with the backend using HTTP requests on the API. On prod, the OVH service SSL Gateway puts itself as intermediary to provide HTTPS connection to the user (+ some benefits like load balancing, protection to DDoS...). We could only use 1 DNS for this service so for preprod we set up an additional container for Traefik which replaces the SSL Gateway to provide HTTPS for the user. +4. The backend and Filebeat share a common volume on the VM, so that when the backend writes logs on the VM, Filebeat can collect them and send them to the OVH public cloud service Log Data Platform. +5. The images uploaded in the frontend of Basegun are sent as blob data to the backend, which then uploads them directly to OVH public cloud object storage service using Python Swift API. + + + +## Terraform explanation + +Terraform is an open-source infrastructure as code (IaC) on Openstack project. It reads all files with a `.tf` extension. We separated the Terraform code in several files for better understandability but they are read by TF as one whole block. + + +### Classic conf files +- `backend.tf` : the remote state (in Swift container), where current state is stored. +- `provider.tf` : select provider (OVH) +- `output.tf` +- `version.tf` : version for terraform init + +### Variables +We add TF variables in 2 different ways. +- Variables related to **the instance** configuration are directly passed as env variables to Terraform using the prefix `TF_VAR`. +The variables related to the instance are the following: + - fixed_ip: fixed ip for VM (populated with github secret `PREPROD_SERVER_IP` or `PROD_SERVER_IP`) + - flavor: code for VM size on OVH + - volume_size: size in GB of instance + +- Variables related to **Basegun product** are passed via `terraform apply --varfile="deployenv.tfvars"`. This `deployenv.tfvars` file is created by replacing the env variables from the CD in the file `env.tfvars`. +The variables related to Basegun website are the folowing: + - API_OVH_TOKEN : credential token for logs (see Logging section) + - OS_USERNAME : username of cloud provider + - OS_PASSWORD : password for this user + - OS_PROJECT_NAME : value from `openrc.sh` (find on horizon platform) + - OS_PROJECT_ID : value from `openrc.sh` (find on horizon platform) + - WORKSPACE : preprod or prod + - X_OVH_TOKEN : token for logs (see Logging section) +For practical reasons, we encapsulate all these variables in one large dictionary variable called `deploy_env`. + +> Regardless of how they are passed to Terraform, all variables must be declared in `variables.tf` file. + +### Instance deployment + + +The file `instance.tf` contains all details related to instance deployment. +* VM size +* VM base image (Debian) +* IP (declared via network object) +* SSH connexion configuration (keypair and port) + +The file `template.tf` decribes the files which must be rendered during deployment. Here we render 2 scripts, `init.yaml` as a cloud-init file (lauched at VM start), which installs dependencies and add security settings, and `deploy.sh` as a regular script file (therefore launched when VM is ready), which downloads github repository archive and launches the app. + +## Logging +### Requirements +2 variables in environment +* X_OVH_TOKEN : token for OVH data steam +* API_OVH_TOKEN : credential token for OVH log data platform + +### Explanation +Basegun website backend writes logs in a log file hosted locally on the server where the website runs. +The log file rotates every day : the current log file is `log.json` while the previous logs are stored in the same folder with the date as suffix in the filename. The log files are stored locally for 7 days. + +We use OVH Log Data Platform as endpoint for these logs. We use 3 separate "Data streams" destinations which correspond to 3 different ElasticSearch aliases/indexes: one for dev, one for preprod, one for prod, so that we don't mix the logs issues from these 3 phases. We set the variable `X_OVH_TOKEN` accordingly to the data stream we want to use. + +The logs are sent from the server to the endpoint using Filebeat. A Filebeat Docker container using `filebeat.yml` conf file is launched at the same time as the website. It watches the log file written by the website and sends the log to OVH every time there is a change in the file. + +In OVH Log Data Platform, we use Kibana to visualize the logs and make queries to extract valuable information from them. + +## Kubernetes deployment + +It is possible to deploy basegun in a kubernetes cluster with list of manifest or diff --git a/infra/scripts/deploy.sh b/infra/legacy/scripts/deploy.sh old mode 100755 new mode 100644 similarity index 100% rename from infra/scripts/deploy.sh rename to infra/legacy/scripts/deploy.sh diff --git a/infra/scripts/init.yaml b/infra/legacy/scripts/init.yaml similarity index 100% rename from infra/scripts/init.yaml rename to infra/legacy/scripts/init.yaml diff --git a/infra/legacy/scripts/mirror.sh b/infra/legacy/scripts/mirror.sh new file mode 100644 index 00000000..cd701d01 --- /dev/null +++ b/infra/legacy/scripts/mirror.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +set -e + +# Colorize terminal +red='\e[0;31m' +no_color='\033[0m' +# Console step increment +i=1 + +# Declare script helper +TEXT_HELPER="\nThis script aims to send a request through DSO api to trigger pipelines. + + +Following flags are available: + + -g GitLab trigger token + + -a Api Domain + + -i GitLab project id DSO + + -k Api manager consummer key. + + -s Api manager consummer secret. + + -h Print script help.\n\n" + +print_help() { + printf "$TEXT_HELPER" +} + +# Parse options +while getopts ":g:a:i:k:s:" flag +do + case "${flag}" in + g) + GITLAB_TRIGGER_TOKEN=${OPTARG};; + a) + API_DOMAIN=${OPTARG};; + i) + GITLAB_PROJECT_ID=${OPTARG};; + k) + CONSUMER_KEY=${OPTARG};; + s) + CONSUMER_SECRET=${OPTARG};; + h | *) + print_help + exit 0;; + esac +done + + +if [ -z ${GITLAB_TRIGGER_TOKEN} ] || [ -z ${CONSUMER_SECRET} ] || [ -z ${CONSUMER_KEY} ] || [ -z ${GITLAB_PROJECT_ID} ] ; then + echo "\nArgument(s) missing, you don't specify consumer key, consumer secret and gitlab trigger token." + print_help + exit 0 +fi + + +URL="https://${API_DOMAIN}/gitlab/preprod/v4/projects/${GITLAB_PROJECT_ID}/trigger/pipeline" + +printf "$\n${red}${i}.${no_color} Retrieve DSO api access token.\n\n" +i=$(($i + 1)) +CONSUMER_CREDENTIALS=$(echo "${CONSUMER_KEY}:${CONSUMER_SECRET}" | tr -d '\n' | base64) + +TOKEN=$(curl -s -k -X POST https://${API_DOMAIN}/oauth2/token \ + -d "grant_type=client_credentials" \ + -H "Authorization: Basic ${CONSUMER_CREDENTIALS}" \ + | sed -e 's,{"access_token":"\([^"]*\)".*,\1,') + +printf "\n${red}${i}.${no_color} Send request to DSO api.\n\n" +curl -v -X POST -H "Authorization: Bearer ${TOKEN}" -H "accept: application/json" -F token=${GITLAB_TRIGGER_TOKEN} -F ref=master --form "variables[GIT_BRANCH_DEPLOY]=${CI_COMMIT_REF:-main}" $URL diff --git a/infra/terraform/backend.tf b/infra/legacy/terraform/backend.tf similarity index 100% rename from infra/terraform/backend.tf rename to infra/legacy/terraform/backend.tf diff --git a/infra/terraform/env.tfvars b/infra/legacy/terraform/env.tfvars similarity index 100% rename from infra/terraform/env.tfvars rename to infra/legacy/terraform/env.tfvars diff --git a/infra/terraform/instance.tf b/infra/legacy/terraform/instance.tf similarity index 100% rename from infra/terraform/instance.tf rename to infra/legacy/terraform/instance.tf diff --git a/infra/terraform/output.tf b/infra/legacy/terraform/output.tf similarity index 100% rename from infra/terraform/output.tf rename to infra/legacy/terraform/output.tf diff --git a/infra/terraform/provider.tf b/infra/legacy/terraform/provider.tf similarity index 100% rename from infra/terraform/provider.tf rename to infra/legacy/terraform/provider.tf diff --git a/infra/terraform/template.tf b/infra/legacy/terraform/template.tf similarity index 100% rename from infra/terraform/template.tf rename to infra/legacy/terraform/template.tf diff --git a/infra/terraform/variable.tf b/infra/legacy/terraform/variable.tf similarity index 100% rename from infra/terraform/variable.tf rename to infra/legacy/terraform/variable.tf diff --git a/infra/terraform/version.tf b/infra/legacy/terraform/version.tf similarity index 100% rename from infra/terraform/version.tf rename to infra/legacy/terraform/version.tf diff --git a/infra/traefik/README.md b/infra/legacy/traefik/README.md similarity index 100% rename from infra/traefik/README.md rename to infra/legacy/traefik/README.md diff --git a/infra/traefik/conf/dynamic_conf.yml b/infra/legacy/traefik/conf/dynamic_conf.yml similarity index 95% rename from infra/traefik/conf/dynamic_conf.yml rename to infra/legacy/traefik/conf/dynamic_conf.yml index e0617816..eabaecf8 100644 --- a/infra/traefik/conf/dynamic_conf.yml +++ b/infra/legacy/traefik/conf/dynamic_conf.yml @@ -1,28 +1,28 @@ -# http routing section -http: - routers: - http-web: - middlewares: - - http-redirectscheme - rule: Host(`{{ env "HOST" }}`) - service: web - https-web: - rule: Host(`{{ env "HOST" }}`) - service: web - tls: - certResolver: resolver - - middlewares: - auth-user: - basicAuth: - usersFile: "/etc/traefik/users.conf" - http-redirectscheme: - redirectScheme: - scheme: "https" - permanent: true - services: - # Define how to reach an existing service on our infrastructure - web: - loadBalancer: - servers: - - url: http://localhost:8080 +# http routing section +http: + routers: + http-web: + middlewares: + - http-redirectscheme + rule: Host(`{{ env "HOST" }}`) + service: web + https-web: + rule: Host(`{{ env "HOST" }}`) + service: web + tls: + certResolver: resolver + + middlewares: + auth-user: + basicAuth: + usersFile: "/etc/traefik/users.conf" + http-redirectscheme: + redirectScheme: + scheme: "https" + permanent: true + services: + # Define how to reach an existing service on our infrastructure + web: + loadBalancer: + servers: + - url: http://localhost:8080 diff --git a/infra/traefik/docker-compose.yml b/infra/legacy/traefik/docker-compose.yml similarity index 97% rename from infra/traefik/docker-compose.yml rename to infra/legacy/traefik/docker-compose.yml index 69581db2..fd31c6a3 100644 --- a/infra/traefik/docker-compose.yml +++ b/infra/legacy/traefik/docker-compose.yml @@ -1,52 +1,52 @@ -version: '3' - -services: - reverse-proxy: - image: traefik:v2.6 - container_name: traefik - restart: unless-stopped - volumes: - - $PWD/infra/traefik/conf/:/etc/traefik/conf/ - - /var/log/traefik:/logs - - $PWD/infra/traefik/acme.json:/acme.json - # ports: - # - 80:80 - # - 443:443 - network_mode: host - environment: - # entrypoint web, websecure metrics and dashboard - TRAEFIK_ENTRYPOINTS_web_ADDRESS: :80 - TRAEFIK_ENTRYPOINTS_websecure_ADDRESS: :443 - #TRAEFIK_ENTRYPOINTS_metrics_ADDRESS: :8082 - # use file provider - TRAEFIK_PROVIDERS_FILE_DIRECTORY: ${TRAEFIK_PROVIDERS_FILE_DIRECTORY:-/etc/traefik/conf/} - TRAEFIK_PROVIDERS_FILE_WATCH: "true" - # redirection web to websecure globaly (not work with http challenge) - #TRAEFIK_ENTRYPOINTS_web_HTTP_REDIRECTIONS_ENTRYPOINT_PERMANENT: "true" - #TRAEFIK_ENTRYPOINTS_web_HTTP_REDIRECTIONS_ENTRYPOINT_TO: websecure - #TRAEFIK_ENTRYPOINTS_web_HTTP_REDIRECTIONS_ENTRYPOINT_SCHEME: https - # http challenge lets encrypt - TRAEFIK_CERTIFICATESRESOLVERS_resolver: "true" - TRAEFIK_CERTIFICATESRESOLVERS_resolver_ACME_EMAIL: basegun@interieur.gouv.fr - TRAEFIK_CERTIFICATESRESOLVERS_resolver_ACME_HTTPCHALLENGE: "true" - TRAEFIK_CERTIFICATESRESOLVERS_resolver_ACME_HTTPCHALLENGE_ENTRYPOINT: web - TRAEFIK_CERTIFICATESRESOLVERS_resolver_ACME_STORAGE: acme.json - # dashboard - TRAEFIK_API: "false" - TRAEFIK_API_DASHBOARD: "false" - # logs - TRAEFIK_LOG_LEVEL: ${TRAEFIK_LOG_LEVEL:-INFO} - TRAEFIK_ACCESSLOG: ${TRAEFIK_ACCESSLOG:-true} - TRAEFIK_ACCESSLOG_FORMAT: ${TRAEFIK_ACCESSLOG:-common} - TRAEFIK_ACCESSLOG_FILEPATH: ${TRAEFIK_ACCESSLOG_FILEPATH:-/logs/traefik.log} - TRAEFIK_LOG_FILEPATH: ${TRAEFIK_LOG_FILEPATH:-/logs/access.log} - TZ: ${TZ:-Europe/Paris} - # metrics with prometheus - TRAEFIK_METRICS_PROMETHEUS: "false" - TRAEFIK_METRICS_PROMETHEUS_ADDENTRYPOINTSLABELS: "false" - TRAEFIK_METRICS_PROMETHEUS_ADDROUTERSLABELS: "false" - TRAEFIK_METRICS_PROMETHEUS_ADDSERVICESLABELS: "false" - #TRAEFIK_METRICS_PROMETHEUS_ENTRYPOINT: metrics - # ping - TRAEFIK_PING: "false" - HOST: preprod.basegun.fr +version: '3' + +services: + reverse-proxy: + image: traefik:v2.6 + container_name: traefik + restart: unless-stopped + volumes: + - $PWD/infra/traefik/conf/:/etc/traefik/conf/ + - /var/log/traefik:/logs + - $PWD/infra/traefik/acme.json:/acme.json + # ports: + # - 80:80 + # - 443:443 + network_mode: host + environment: + # entrypoint web, websecure metrics and dashboard + TRAEFIK_ENTRYPOINTS_web_ADDRESS: :80 + TRAEFIK_ENTRYPOINTS_websecure_ADDRESS: :443 + #TRAEFIK_ENTRYPOINTS_metrics_ADDRESS: :8082 + # use file provider + TRAEFIK_PROVIDERS_FILE_DIRECTORY: ${TRAEFIK_PROVIDERS_FILE_DIRECTORY:-/etc/traefik/conf/} + TRAEFIK_PROVIDERS_FILE_WATCH: "true" + # redirection web to websecure globaly (not work with http challenge) + #TRAEFIK_ENTRYPOINTS_web_HTTP_REDIRECTIONS_ENTRYPOINT_PERMANENT: "true" + #TRAEFIK_ENTRYPOINTS_web_HTTP_REDIRECTIONS_ENTRYPOINT_TO: websecure + #TRAEFIK_ENTRYPOINTS_web_HTTP_REDIRECTIONS_ENTRYPOINT_SCHEME: https + # http challenge lets encrypt + TRAEFIK_CERTIFICATESRESOLVERS_resolver: "true" + TRAEFIK_CERTIFICATESRESOLVERS_resolver_ACME_EMAIL: basegun@interieur.gouv.fr + TRAEFIK_CERTIFICATESRESOLVERS_resolver_ACME_HTTPCHALLENGE: "true" + TRAEFIK_CERTIFICATESRESOLVERS_resolver_ACME_HTTPCHALLENGE_ENTRYPOINT: web + TRAEFIK_CERTIFICATESRESOLVERS_resolver_ACME_STORAGE: acme.json + # dashboard + TRAEFIK_API: "false" + TRAEFIK_API_DASHBOARD: "false" + # logs + TRAEFIK_LOG_LEVEL: ${TRAEFIK_LOG_LEVEL:-INFO} + TRAEFIK_ACCESSLOG: ${TRAEFIK_ACCESSLOG:-true} + TRAEFIK_ACCESSLOG_FORMAT: ${TRAEFIK_ACCESSLOG:-common} + TRAEFIK_ACCESSLOG_FILEPATH: ${TRAEFIK_ACCESSLOG_FILEPATH:-/logs/traefik.log} + TRAEFIK_LOG_FILEPATH: ${TRAEFIK_LOG_FILEPATH:-/logs/access.log} + TZ: ${TZ:-Europe/Paris} + # metrics with prometheus + TRAEFIK_METRICS_PROMETHEUS: "false" + TRAEFIK_METRICS_PROMETHEUS_ADDENTRYPOINTSLABELS: "false" + TRAEFIK_METRICS_PROMETHEUS_ADDROUTERSLABELS: "false" + TRAEFIK_METRICS_PROMETHEUS_ADDSERVICESLABELS: "false" + #TRAEFIK_METRICS_PROMETHEUS_ENTRYPOINT: metrics + # ping + TRAEFIK_PING: "false" + HOST: preprod.basegun.fr diff --git a/infra/scripts/test-alive.sh b/infra/scripts/test-alive.sh index 97fa65dc..bc151f61 100755 --- a/infra/scripts/test-alive.sh +++ b/infra/scripts/test-alive.sh @@ -3,10 +3,11 @@ echo "# Test website is up" sudo apt install -y ca-certificates set +e -timeout=1800; +timeout=900; elapse=0; range=10; test_result=1 +TAG=$(make get-current-version) until [ "$elapse" -ge "$timeout" -o "$test_result" -eq "0" ] ; do [[ "$(curl -L -s $1/api/)" == "Basegun backend" ]] @@ -19,6 +20,6 @@ until [ "$elapse" -ge "$timeout" -o "$test_result" -eq "0" ] ; do done if [ "$test_result" -gt "0" ] ; then ret=$test_result - echo "ERROR: url does not respond" + echo "ERROR: url does not respond or version not updated" exit 1 fi