diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7311b847..beafaf96 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,6 +20,11 @@ stages: - test - release - deploy + - deploy-production + +# +# Validation +# check-style: image: composer:latest @@ -45,6 +50,10 @@ validate-yarn: script: - pjv +# +# Build +# + .docker_template: &docker_definition image: docker:18 services: @@ -78,6 +87,10 @@ build-image: - docker build --pull --build-arg VERSION="${VERSION}" -t "${TEST_IMAGE}" -f docker/Dockerfile . - docker push "${TEST_IMAGE}" +# +# Test +# + audit-composer: image: ${TEST_IMAGE} stage: test @@ -125,6 +138,10 @@ test: after_script: - '"${DOCROOT}/bin/migrate" down' +# +# Release +# + release-image: <<: *docker_definition stage: release @@ -146,23 +163,16 @@ release-image-nginx: - master .deploy_template: &deploy_definition - stage: deploy + stage: release image: ${TEST_IMAGE} before_script: - apk add -q bash rsync openssh-client -.deploy_template_script: - # Configure SSH - - &deploy_template_script |- - eval $(ssh-agent -s) && echo "${SSH_PRIVATE_KEY}" | ssh-add - - rsync -vAax public/assets ${DOCROOT}/public/ - cd "${DOCROOT}" - build-release-file: <<: *deploy_definition - stage: deploy + stage: release artifacts: - name: "release_${CI_COMMIT_REF_SLUG}_${CI_JOB_ID}_${CI_COMMIT_SHA}" + name: release_${CI_COMMIT_REF_SLUG}_${CI_JOB_ID}_${CI_COMMIT_SHA} expire_in: 1 week paths: - ./release/ @@ -170,8 +180,20 @@ build-release-file: - rsync -vAax "${DOCROOT}" "${DOCROOT}/.babelrc" "${DOCROOT}/.browserslistrc" release/ - rsync -vAax public/assets release/public/ -deploy-staging: +# +# Deploy staging +# + +.deploy_template_script: + # Configure SSH + - &deploy_template_script |- + eval $(ssh-agent -s) && echo "${SSH_PRIVATE_KEY}" | ssh-add - + rsync -vAax public/assets ${DOCROOT}/public/ + cd "${DOCROOT}" + +deploy: <<: *deploy_definition + stage: deploy environment: name: staging only: @@ -187,8 +209,84 @@ deploy-staging: # Deploy to server - ./bin/deploy.sh -r "${STAGING_REMOTE}" -p "${STAGING_REMOTE_PATH}" -i "${CI_JOB_ID}-${CI_COMMIT_SHA}" +.kubectl_deployment: &kubectl_deployment + stage: deploy + image: + name: bitnami/kubectl:latest + entrypoint: [''] + before_script: + - &kubectl_deployment_script if [[ -z "${KUBE_INGRESS_BASE_DOMAIN}" ]]; then echo "Skipping deployment"; exit; fi + +.deploy_k8s: &deploy_k8s + <<: *kubectl_deployment + artifacts: + name: deployment.yaml + expire_in: 1 day + when: always + paths: + - deployment.yaml + script: + # CI_ENVIRONMENT_URL is the URL configured in the GitLab environment + - export CI_ENVIRONMENT_URL="${CI_ENVIRONMENT_URL:-https://${CI_PROJECT_PATH_SLUG}.${KUBE_INGRESS_BASE_DOMAIN}/}" + - export CI_IMAGE=$RELEASE_IMAGE + - export CI_IMAGE_NGINX=$RELEASE_IMAGE_NGINX + - export CI_INGRESS_DOMAIN=$(echo "$CI_ENVIRONMENT_URL" | grep -oP '(?:https?://)?\K([^/]+)' | head -n1) + - export CI_INGRESS_PATH=$(echo "$CI_ENVIRONMENT_URL" | grep -oP '(?:https?://)?(?:[^/])+\K(.*)') + - export CI_KUBE_NAMESPACE=$KUBE_NAMESPACE + # Any available storage class like longhorn + - export CI_PVC_SC=${CI_PVC_SC:-"${CI_PVC_SC_LOCAL:-local-path}"} + - export CI_REPLICAS=${CI_REPLICAS_REVIEW:-${CI_REPLICAS:-2}} + - export CI_APP_NAME=${CI_APP_NAME:-Engelsystem} + + - cp deployment.tpl.yaml deployment.yaml + - for env in ${!CI_*}; do sed -i "s#<${env}>#$(echo "${!env}"|head -n1)#g" deployment.yaml; done + + - echo "Deploying to ${CI_ENVIRONMENT_URL}" + - kubectl apply -f deployment.yaml + - >- + kubectl -n $CI_KUBE_NAMESPACE wait --for=condition=Ready pods --timeout=${CI_WAIT_TIMEOUT:-5}m + -l app=$CI_PROJECT_PATH_SLUG -l tier=database + - >- + kubectl -n $CI_KUBE_NAMESPACE wait --for=condition=Ready pods --timeout=${CI_WAIT_TIMEOUT:-5}m + -l app=$CI_PROJECT_PATH_SLUG -l tier=application -l commit=$CI_COMMIT_SHORT_SHA + +.deploy_k8s_stop: &deploy_k8s_stop + <<: *kubectl_deployment + variables: + GIT_STRATEGY: none + dependencies: [] + when: manual + script: + - kubectl delete all,ingress,pvc -l app=$CI_PROJECT_PATH_SLUG -l environment=$CI_ENVIRONMENT_SLUG + +deploy-k8s-review: + <<: *deploy_k8s + environment: + name: review/${CI_COMMIT_REF_NAME} + on_stop: stop-k8s-review + auto_stop_in: 1 week + url: https://${CI_PROJECT_PATH_SLUG}-review.${KUBE_INGRESS_BASE_DOMAIN}/${CI_COMMIT_REF_SLUG} + variables: + CI_REPLICAS_REVIEW: 1 + CI_APP_NAME: review/${CI_COMMIT_REF_NAME} + before_script: + - *kubectl_deployment_script + - RELEASE_IMAGE=$TEST_IMAGE + - RELEASE_IMAGE_NGINX=$TEST_IMAGE_NGINX + +stop-k8s-review: + <<: *deploy_k8s_stop + environment: + name: review/${CI_COMMIT_REF_NAME} + action: stop + +# +# Deploy production +# + deploy-production: <<: *deploy_definition + stage: deploy-production environment: name: production when: manual @@ -204,3 +302,22 @@ deploy-production: - *deploy_template_script # Deploy to server - ./bin/deploy.sh -r "${PRODUCTION_REMOTE}" -p "${PRODUCTION_REMOTE_PATH}" -i "${CI_JOB_ID}-${CI_COMMIT_SHA}" + +deploy-k8s-production: + <<: *deploy_k8s + stage: deploy-production + environment: + name: production + on_stop: stop-k8s-production + when: manual + only: + - master + +stop-k8s-production: + <<: *deploy_k8s_stop + stage: deploy-production + only: + - master + environment: + name: production + action: stop diff --git a/deployment.tpl.yaml b/deployment.tpl.yaml new file mode 100644 index 00000000..5b2377b7 --- /dev/null +++ b/deployment.tpl.yaml @@ -0,0 +1,199 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: engelsystem-db + labels: + app: + environment: +spec: + accessModes: + - ReadWriteOnce + storageClassName: + resources: + requests: + storage: 2Gi + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: database + labels: + app: + environment: +spec: + replicas: 1 + selector: + matchLabels: + app: + environment: + template: + metadata: + labels: + app: + environment: + tier: database + spec: + containers: + - image: mariadb:10.2 + name: database + imagePullPolicy: Always + env: + - name: MYSQL_DATABASE + value: engelsystem + - name: MYSQL_USER + value: engelsystem + - name: MYSQL_PASSWORD + value: engelsystem + - name: MYSQL_RANDOM_ROOT_PASSWORD + value: '1' + - name: MYSQL_INITDB_SKIP_TZINFO + value: 'yes' + volumeMounts: + - mountPath: /var/lib/mysql + name: data + volumes: + - name: data + persistentVolumeClaim: + claimName: engelsystem-db + +--- +apiVersion: v1 +kind: Service +metadata: + name: database + labels: + app: + environment: + commit: +spec: + type: ClusterIP + ports: + - port: 3306 + targetPort: 3306 + name: database + selector: + app: + environment: + tier: database + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: engelsystem + labels: + app: + environment: + commit: +spec: + replicas: + selector: + matchLabels: + app: + environment: + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + template: + metadata: + labels: + app: + environment: + tier: application + commit: + annotations: + app.gitlab.com/app: + app.gitlab.com/env: + commit: + spec: + initContainers: + - image: + name: engelsystem-migrate + imagePullPolicy: Always + command: + - sh + - -c + - while ! bin/migrate up; do sleep 1; done + env: + - name: MYSQL_HOST + value: database + - name: MYSQL_DATABASE + value: engelsystem + - name: MYSQL_USER + value: engelsystem + - name: MYSQL_PASSWORD + value: engelsystem + containers: + - image: + name: engelsystem-fpm + imagePullPolicy: Always + env: + - name: MYSQL_HOST + value: database + - name: MYSQL_DATABASE + value: engelsystem + - name: MYSQL_USER + value: engelsystem + - name: MYSQL_PASSWORD + value: engelsystem + - name: APP_URL + value: + - name: APP_NAME + value: '' + - image: + name: engelsystem-nginx + imagePullPolicy: Always + env: + - name: PHP_FPM_HOST + value: localhost + +--- +apiVersion: v1 +kind: Service +metadata: + name: engelsystem + labels: + app: + environment: + commit: +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 80 + name: engelsystem + selector: + app: + environment: + tier: application + +--- +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: engelsystem-ingress + annotations: + kubernetes.io/tls-acme: 'true' + kubernetes.io/ingress.class: 'nginx' + nginx.ingress.kubernetes.io/rewrite-target: /$1 + labels: + app: + environment: + commit: +spec: + tls: + - hosts: + - + secretName: + rules: + - host: + http: + paths: + - path: '/?(.*)' + backend: + serviceName: engelsystem + servicePort: 80 diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile index f4355af9..8bedaec9 100644 --- a/docker/nginx/Dockerfile +++ b/docker/nginx/Dockerfile @@ -1,4 +1,6 @@ FROM nginx:alpine as es_nginx +COPY docker/nginx/entrypoint.sh / +ENTRYPOINT /entrypoint.sh RUN mkdir -p /var/www/public/ && touch /var/www/public/index.php COPY docker/nginx/nginx.conf /etc/nginx/nginx.conf diff --git a/docker/nginx/entrypoint.sh b/docker/nginx/entrypoint.sh new file mode 100755 index 00000000..feec28b0 --- /dev/null +++ b/docker/nginx/entrypoint.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env sh +set -e + +sed -i "s/es_php_fpm:/${PHP_FPM_HOST:-es_php_fpm}:/g" /etc/nginx/nginx.conf + +# If first arg starts with a `-` or is empty +if [[ "${1#-}" != "${1}" ]] || [[ -z "${1}" ]]; then + set -- nginx -g 'daemon off;' "$@" +fi + +exec "$@"