Установка Kubernetes через MicroK8s и настройка деплоя NestJS и Angular приложений

dce05835f1451c7d6f8c17accc8b9fc2

Предыдущая статья: Ускорение деплоя NestJS и Angular с помощью общественных Github-раннеров и создания промежуточных Docker-образов

Когда в команде нет DevOps — инженеров, но очень хочется задеплоить приложение в Kubernetes, можно легко это сделать с помощью https://microk8s.io, в данном посте я опишу как это сделать и открыть доступ к приложению на определенном порте.

1. Установка MicroK8s на выделенный сервер

Про MicroK8s уже много писали на хабре, в общем этот установщик Kubernetes с дополнительными плагинами для типовых задач и он позволяет без изучения и глубокого погружения в мир DevOps быстро развернуть Kubernetes.

Подключаемся к нашему серверу по SSH и устанавливаем Kubernetes.

Команды

ssh root@194.226.49.162
sudo snap install microk8s --classic
sudo usermod -a -G microk8s $USER
mkdir ~/.kube
sudo chown -R $USER ~/.kube
newgrp microk8s
# close and open terminal with ssh
microk8s status --wait-ready
microk8s enable dashboard dns registry ingress hostpath-storage

Вывод консоли

root@vps1724252356:~# sudo snap install microk8s --classic
Start snap "microk8s" (7180) services
microk8s (1.30/stable) v1.30.4 from Canonical✓ installed
root@vps1724252356:~# sudo usermod -a -G microk8s $USER
root@vps1724252356:~# mkdir ~/.kube
root@vps1724252356:~# sudo chown -R $USER ~/.kube
root@vps1724252356:~# newgrp microk8s
# close and open terminal with ssh
root@vps1724252356:~# microk8s status --wait-ready
microk8s is running
high-availability: no
  datastore master nodes: 127.0.0.1:19001
  datastore standby nodes: none
addons:
  enabled:
    dns                  # (core) CoreDNS
    ha-cluster           # (core) Configure high availability on the current node
    helm                 # (core) Helm - the package manager for Kubernetes
    helm3                # (core) Helm 3 - the package manager for Kubernetes
  disabled:
    cert-manager         # (core) Cloud native certificate management
    cis-hardening        # (core) Apply CIS K8s hardening
    community            # (core) The community addons repository
    dashboard            # (core) The Kubernetes dashboard
    gpu                  # (core) Alias to nvidia add-on
    host-access          # (core) Allow Pods connecting to Host services smoothly
    hostpath-storage     # (core) Storage class; allocates storage from host directory
    ingress              # (core) Ingress controller for external access
    kube-ovn             # (core) An advanced network fabric for Kubernetes
    mayastor             # (core) OpenEBS MayaStor
    metallb              # (core) Loadbalancer for your Kubernetes cluster
    metrics-server       # (core) K8s Metrics Server for API access to service metrics
    minio                # (core) MinIO object storage
    nvidia               # (core) NVIDIA hardware (GPU and network) support
    observability        # (core) A lightweight observability stack for logs, traces and metrics
    prometheus           # (core) Prometheus operator for monitoring and logging
    rbac                 # (core) Role-Based Access Control for authorisation
    registry             # (core) Private image registry exposed on localhost:32000
    rook-ceph            # (core) Distributed Ceph storage using Rook
    storage              # (core) Alias to hostpath-storage add-on, deprecated
root@vps1724252356:~# microk8s enable dashboard dns registry ingress hostpath-storage
Infer repository core for addon dashboard
Infer repository core for addon dns
Infer repository core for addon registry
Infer repository core for addon ingress
Infer repository core for addon hostpath-storage
WARNING: Do not enable or disable multiple addons in one command.
         This form of chained operations on addons will be DEPRECATED in the future.
         Please, enable one addon at a time: 'microk8s enable '
Enabling Kubernetes Dashboard
Infer repository core for addon metrics-server
Addon core/metrics-server is already enabled
Applying manifest
serviceaccount/kubernetes-dashboard created
service/kubernetes-dashboard created
secret/kubernetes-dashboard-certs created
secret/kubernetes-dashboard-csrf created
secret/kubernetes-dashboard-key-holder created
configmap/kubernetes-dashboard-settings created
role.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard created
rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
deployment.apps/kubernetes-dashboard created
service/dashboard-metrics-scraper created
deployment.apps/dashboard-metrics-scraper created
secret/microk8s-dashboard-token unchanged

If RBAC is not enabled access the dashboard using the token retrieved with:

microk8s kubectl describe secret -n kube-system microk8s-dashboard-token

Use this token in the https login UI of the kubernetes-dashboard service.

In an RBAC enabled setup (microk8s enable RBAC) you need to create a user with restricted
permissions as shown in:
https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md

Enabling DNS
Using host configuration from /run/systemd/resolve/resolv.conf
Applying manifest
serviceaccount/coredns created
configmap/coredns created
deployment.apps/coredns created
service/kube-dns created
clusterrole.rbac.authorization.k8s.io/coredns created
clusterrolebinding.rbac.authorization.k8s.io/coredns created
CoreDNS service deployed with IP address 10.152.183.10
Restarting kubelet
DNS is enabled
Infer repository core for addon hostpath-storage
Addon core/hostpath-storage is already enabled
The registry will be created with the size of 20Gi.
Default storage class will be used.
namespace/container-registry created
persistentvolumeclaim/registry-claim created
deployment.apps/registry created
service/registry created
configmap/local-registry-hosting configured
Enabling Ingress
ingressclass.networking.k8s.io/public created
ingressclass.networking.k8s.io/nginx created
namespace/ingress created
serviceaccount/nginx-ingress-microk8s-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-microk8s-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-microk8s-role created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-microk8s created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-microk8s created
configmap/nginx-load-balancer-microk8s-conf created
configmap/nginx-ingress-tcp-microk8s-conf created
configmap/nginx-ingress-udp-microk8s-conf created
daemonset.apps/nginx-ingress-microk8s-controller created
Ingress is enabled
Addon core/hostpath-storage is already enabled

2. Разрешаем доступ к хост машине из подов Kubernetes

Так как база данных будет находиться на хосте и в кубе будут только приложения, то для того чтобы приложение Kubernetes могло достучаться до хоста необходимо разрешить доступ к хост машине.

Команды

ssh root@194.226.49.162
microk8s enable host-access

Вывод консоли

root@vps1724252356:~# microk8s enable host-access
Infer repository core for addon host-access
Setting 10.0.1.1 as host-access
Host-access is enabled

3. Временно расшариваем дашборд Kubernetes и смотрим работает ли он вообще

После запуска команды расшаривания, необходимо перебросить порт удаленного сервера на локальный компьютер, так как дашборд это веб приложение.

После запуска мы также увидем токен для подключения, его необходимо будет ввести для авторизации в дашборд.

Команды

microk8s dashboard-proxy

Вывод консоли

root@vps1724252356:~# microk8s dashboard-proxy
Checking if Dashboard is running.
Infer repository core for addon dashboard
Waiting for Dashboard to come up.
Trying to get token from microk8s-dashboard-token
Waiting for secret token (attempt 0)
Dashboard will be available at https://127.0.0.1:10443
Use the following token to login:
SOME_RANDOM_SYMBOLS

Если у вас возникли ошибки при подключении к дашборд, то попробуйте запустить команду:

sudo microk8s.refresh-certs --cert ca.crt

4. Создаем скрипт для создания дополнительных переменных окружения

Часть переменных будет такая же как и для режима «Docker Compose», поэтому можно просто скопировать и модифицировать существующий файл .docker/set-env.sh в .kubernetes/set-env.sh.

В этом скрипте много проверок на наличие необходимых переменных и в случаи их отсутствия, вместо них используется значения по умолчанию.

Создаем файл .kubernetes/set-env.sh

#!/bin/bash
set -e

export REPOSITORY=nestjs-mod/nestjs-mod-fullstack
export REGISTRY=ghcr.io
export BASE_SERVER_IMAGE_NAME="${REPOSITORY}-base-server"
export BUILDER_IMAGE_NAME="${REPOSITORY}-builder"
export MIGRATIONS_IMAGE_NAME="${REPOSITORY}-migrations"
export SERVER_IMAGE_NAME="${REPOSITORY}-server"
export NGINX_IMAGE_NAME="${REPOSITORY}-nginx"
export E2E_TESTS_IMAGE_NAME="${REPOSITORY}-e2e-tests"
export COMPOSE_INTERACTIVE_NO_CLI=1
export NX_DAEMON=false
export NX_PARALLEL=1
export NX_SKIP_NX_CACHE=true
export DISABLE_SERVE_STATIC=true

export ROOT_VERSION=$(npm pkg get version --workspaces=false | tr -d \")
export SERVER_VERSION=$(cd ./apps/server && npm pkg get version --workspaces=false | tr -d \")

# node
if [ -z "${NAMESPACE}" ]; then
    export NAMESPACE=master
fi

# common
if [ -z "${SERVER_DOMAIN}" ]; then
    export SERVER_DOMAIN=example.com
fi

# server
if [ -z "${SERVER_PORT}" ]; then
    export SERVER_PORT=9191
fi
if [ -z "${SERVER_APP_DATABASE_PASSWORD}" ]; then
    export SERVER_APP_DATABASE_PASSWORD=app_password
fi
if [ -z "${SERVER_APP_DATABASE_USERNAME}" ]; then
    export SERVER_APP_DATABASE_USERNAME=${NAMESPACE}_app
fi
if [ -z "${SERVER_APP_DATABASE_NAME}" ]; then
    export SERVER_APP_DATABASE_NAME=${NAMESPACE}_app
fi

# client
if [ -z "${NGINX_PORT}" ]; then
    export NGINX_PORT=8181
fi

# database
if [ -z "${SERVER_POSTGRE_SQL_POSTGRESQL_USERNAME}" ]; then
    export SERVER_POSTGRE_SQL_POSTGRESQL_USERNAME=postgres
fi
if [ -z "${SERVER_POSTGRE_SQL_POSTGRESQL_PASSWORD}" ]; then
    export SERVER_POSTGRE_SQL_POSTGRESQL_PASSWORD=postgres_password
fi
if [ -z "${SERVER_POSTGRE_SQL_POSTGRESQL_DATABASE}" ]; then
    export SERVER_POSTGRE_SQL_POSTGRESQL_DATABASE=postgres
fi

5. Шаблоны конфигураций

Обычно для разворачивания инфраструктуры и приложений используют Ansible и Helm, в данном проекте они не используются, для того чтобы не перегружать лишней информацией.

В данном проекте для копирования и подстановки переменных окружения используется утилита https://www.npmjs.com/package/rucken, а конкретнее ее команда copy-paste.

Мини пример использования утилиты:

mkdir cat-dog
echo "%START_ENV_VARIABLE%
catDog
cat-dogs
cat_dog" > cat-dog/cat_dog.txt
export START_ENV_VARIABLE="examples:"
npx -y rucken@latest copy-paste --find=cat-dog --replace=human-ufo --path=./cat-dog --replace-envs=true
cat ./human-ufo/human_ufo.txt

Вывод:

$ cat ./human-ufo/human_ufo.txt
examples:
humanUfo
human-ufos
human_ufo

Основные этапы запуска проекта в Kubernetes:

  1. Собираем докер образы;

  2. Запускаем скрипт создания дополнительных переменных окружения;

  3. Копируем файлы с шаблонами конфигураций запуска приложений и инфраструктуры, при этом заменяем во всех скопированных файлах все найденные переменные окружения;

  4. Запускаем инфраструктуру через «Docker Compose» (база данных + миграции);

  5. Создаем и запускаем приложения в Kubernetes;

  6. Запускам E2E-тесты через «Docker Compose».

6. Создаем «Docker Compose» — файлы для запуска базы данных, миграций и тестов

Инфраструктурные вещи наподобие базы данных или брокеров необходимо запускать на отдельных серверах, а в рамках Kubernetes запускать только наши приложения.

За каждый сервер и инфраструктурную программу могут отвечать специализированные команды DevOps — инженеров, а также меньше шансов потерять все данные, если сервер вдруг физически сломается.

База данных и миграции в этом проекте запускается через отдельный «Docker Compose» — файл, так что шаг запуска баз данных можно перенастроить на запуск в отдельном акшен ранере который будет установлен на отдельном сервере, в котором нет Kubernetes.

Создаем файл .kubernetes/templates/docker-compose-infra.yml

version: '3'
networks:
  nestjs-mod-fullstack-network:
    driver: 'bridge'
services:
  nestjs-mod-fullstack-postgre-sql:
    image: 'bitnami/postgresql:15.5.0'
    container_name: 'nestjs-mod-fullstack-postgre-sql'
    networks:
      - 'nestjs-mod-fullstack-network'
    ports:
      - '5432:5432'
    healthcheck:
      test:
        - 'CMD-SHELL'
        - 'pg_isready -U postgres'
      interval: '5s'
      timeout: '5s'
      retries: 5
    tty: true
    restart: 'always'
    environment:
      POSTGRESQL_USERNAME: '%SERVER_POSTGRE_SQL_POSTGRESQL_USERNAME%'
      POSTGRESQL_PASSWORD: '%SERVER_POSTGRE_SQL_POSTGRESQL_PASSWORD%'
      POSTGRESQL_DATABASE: '%SERVER_POSTGRE_SQL_POSTGRESQL_DATABASE%'
    volumes:
      - 'nestjs-mod-fullstack-postgre-sql-volume:/bitnami/postgresql'
  nestjs-mod-fullstack-postgre-sql-migrations:
    image: 'ghcr.io/nestjs-mod/nestjs-mod-fullstack-migrations:%ROOT_VERSION%'
    container_name: 'nestjs-mod-fullstack-postgre-sql-migrations'
    networks:
      - 'nestjs-mod-fullstack-network'
    tty: true
    environment:
      NX_SKIP_NX_CACHE: 'true'
      SERVER_ROOT_DATABASE_URL: 'postgres://%SERVER_POSTGRE_SQL_POSTGRESQL_USERNAME%:%SERVER_POSTGRE_SQL_POSTGRESQL_PASSWORD%@nestjs-mod-fullstack-postgre-sql:5432/%SERVER_POSTGRE_SQL_POSTGRESQL_DATABASE%?schema=public'
      SERVER_APP_DATABASE_URL: 'postgres://%SERVER_APP_DATABASE_USERNAME%:%SERVER_APP_DATABASE_PASSWORD%@nestjs-mod-fullstack-postgre-sql:5432/%SERVER_APP_DATABASE_NAME%?schema=public'
    depends_on:
      nestjs-mod-fullstack-postgre-sql:
        condition: 'service_healthy'
    working_dir: '/usr/src/app'
    volumes:
      - './../../apps:/usr/src/app/apps'
      - './../../libs:/usr/src/app/libs'
volumes:
  nestjs-mod-fullstack-postgre-sql-volume:
    external: true
    name: 'nestjs-mod-fullstack-postgre-sql-volume'

Запуск E2E — тестов также происходит через специальный «Docker Compose», это сделано для того чтобы можно было запустить множество параллельных Docker — контейнеров с тестами, и получим нечто похожее на нагрузочные тесты стенда, а также мы имеем возможность запускать тесты с разных регионов.

Создаем файл .kubernetes/templates/docker-compose-e2e-tests.yml

version: '3'
networks:
  nestjs-mod-fullstack-network:
    driver: 'bridge'
services:
  nestjs-mod-fullstack-e2e-tests:
    image: 'ghcr.io/nestjs-mod/nestjs-mod-fullstack-e2e-tests:%ROOT_VERSION%'
    container_name: 'nestjs-mod-fullstack-e2e-tests'
    extra_hosts:
      - 'host.docker.internal:host-gateway'
    networks:
      - 'nestjs-mod-fullstack-network'
    environment:
      BASE_URL: 'http://host.docker.internal:30222'
    working_dir: '/usr/src/app'
    volumes:
      - './../../apps:/usr/src/app/apps'
      - './../../libs:/usr/src/app/libs'

7. Создаем Kubernetes — файлы для настройки стенда

Создаем неймспейс стенда или текущего деплоя, все элементы приложения будут связанны с этим неймспес, и гораздо проще мониторить состояния элементов стенда.

Файл с неймспейс: .kubernetes/templates/node/0.namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: '%NAMESPACE%'

Если у нас имеются некие общие переменные окружения для разных приложений задеплоенных в рамках одного Kubernetes и одного и того же нейспейса, то мы можем поместить их в глобальный конфигурационный файл стенда.

Файл конфигурации: .kubernetes/templates/node/1.configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: '%NAMESPACE%'
  name: %NAMESPACE%-config
data:
  DEBUG: 'true'
  BITNAMI_DEBUG: 'true'

8. Создаем Kubernetes — файлы для запуска сервера на NestJS

Помимо общих переменных окружения, каждое приложение может иметь свои переменные окружения, для таких переменных создаем конфигурационный файл приложения.

Файл конфигурации: .kubernetes/templates/server/1.configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: '%NAMESPACE%'
  name: %NAMESPACE%-server-config
data:
  NODE_TLS_REJECT_UNAUTHORIZED: '0'
  SERVER_APP_DATABASE_URL: 'postgres://%SERVER_APP_DATABASE_USERNAME%:%SERVER_APP_DATABASE_PASSWORD%@10.0.1.1:5432/%SERVER_APP_DATABASE_NAME%?schema=public'
  SERVER_PORT: '%SERVER_PORT%'

Контейнер с приложением будет создаваться используя Docker — образ, который мы ранее собирали.

Установим лимиты контейнеру: общий лимит на процессор 30% и память 512 мегабайт, лимит процессора на запрос 10% и 128 мегабайт памяти.

Деплоймент файл: .kubernetes/templates/server/3.deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: '%NAMESPACE%'
  name: %NAMESPACE%-server
spec:
  replicas: 1
  selector:
    matchLabels:
      pod: %NAMESPACE%-server-container
  template:
    metadata:
      namespace: '%NAMESPACE%'
      labels:
        app: %NAMESPACE%-server
        pod: %NAMESPACE%-server-container
    spec:
      containers:
        - name: %NAMESPACE%-server
          image: ghcr.io/nestjs-mod/nestjs-mod-fullstack-server:%SERVER_VERSION%
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: %SERVER_PORT%
          envFrom:
            - configMapRef:
                name: %NAMESPACE%-config
            - configMapRef:
                name: %NAMESPACE%-server-config
          resources:
            requests:
              memory: 128Mi
              cpu: 100m
            limits:
              memory: 512Mi
              cpu: 300m
      imagePullSecrets:
        - name: docker-regcred

Для того чтобы другие приложения (в данном случаи Nginx с фронтендом) могли обращаться к контейнеру сервера, необходимо создать сервис.

Файл сервиса: .kubernetes/templates/server/4.service.yaml

apiVersion: v1
kind: Service
metadata:
  namespace: '%NAMESPACE%'
  name: %NAMESPACE%-server
  labels:
    app: %NAMESPACE%-server
spec:
  selector:
    app: %NAMESPACE%-server
  ports:
    - name: '%SERVER_PORT%'
      protocol: TCP
      port: %SERVER_PORT%
      targetPort: %SERVER_PORT%
  type: ClusterIP

9. Создаем Kubernetes — файлы для запуска Nginx c встроенным фронтенд на Angular

Файл конфигурации: .kubernetes/templates/client/1.configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: '%NAMESPACE%'
  name: %NAMESPACE%-client-config
data:
  SERVER_PORT: '%SERVER_PORT%'
  NGINX_PORT: '%NGINX_PORT%'
  SERVER_NAME: %NAMESPACE%-server.%NAMESPACE%

Деплоймент файл: .kubernetes/templates/client/3.deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: '%NAMESPACE%'
  name: %NAMESPACE%-client
spec:
  replicas: 1
  selector:
    matchLabels:
      pod: %NAMESPACE%-client-container
  template:
    metadata:
      namespace: '%NAMESPACE%'
      labels:
        app: %NAMESPACE%-client
        pod: %NAMESPACE%-client-container
    spec:
      containers:
        - name: %NAMESPACE%-client
          image: ghcr.io/nestjs-mod/nestjs-mod-fullstack-nginx:%SERVER_VERSION%
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: %NGINX_PORT%
          envFrom:
            - configMapRef:
                name: %NAMESPACE%-config
            - configMapRef:
                name: %NAMESPACE%-client-config
          resources:
            requests:
              memory: 128Mi
              cpu: 100m
            limits:
              memory: 512Mi
              cpu: 300m
      imagePullSecrets:
        - name: docker-regcred

Файл сервиса: .kubernetes/templates/client/4.service.yaml

apiVersion: v1
kind: Service
metadata:
  namespace: '%NAMESPACE%'
  name: %NAMESPACE%-client
  labels:
    app: %NAMESPACE%-client
spec:
  selector:
    app: %NAMESPACE%-client
  ports:
    - name: '%NGINX_PORT%'
      protocol: TCP
      port: %NGINX_PORT%
      targetPort: %NGINX_PORT%
  type: ClusterIP

Так как в начале мы пробуем задеплоить приложение на порту без домена, то нам необходимо создать еще один сервис который расшарит порт контейнера наружу.

Файл глобального сервиса: .kubernetes/templates/client/4.global-service.yaml

apiVersion: v1
kind: Service
metadata:
  namespace: '%NAMESPACE%'
  name: %NAMESPACE%-client-global
  labels:
    app: %NAMESPACE%-client-global
spec:
  selector:
    app: %NAMESPACE%-client
  ports:
    - port: 30222
      nodePort: 30222
      targetPort: %NGINX_PORT%
  type: NodePort

10. Создаем баш скрипт для применения конфигураций Kubernetes

Создаем файл .kubernetes/templates/install.sh

#!/bin/bash
set -e

# docker regcred for pull docker images
sudo microk8s kubectl delete secret docker-regcred || echo 'not need delete secret docker-regcred'
sudo microk8s kubectl create secret docker-registry docker-regcred --docker-server=%DOCKER_SERVER% --docker-username=%DOCKER_USERNAME% --docker-password=%DOCKER_PASSWORD% --docker-email=docker-regcred

# namespace and common config
sudo microk8s kubectl apply -f .kubernetes/generated/node
sudo microk8s kubectl get secret docker-regcred -n default -o yaml || sed s/"namespace: default"/"namespace: %NAMESPACE%"/ || microk8s kubectl apply -n %NAMESPACE% -f - || echo 'not need update docker-regcred'

# server
sudo microk8s kubectl apply -f .kubernetes/generated/server

# client
sudo microk8s kubectl apply -f .kubernetes/generated/client

11. Создаем CI/CD-конфигурацию для деплоя в Kubernetes

Часть задач будет такая же как и для режима «Docker Compose», поэтому можно просто скопировать и модифицировать существующий файл .github/workflows/docker-compose.workflows.yml в .github/workflows/kubernetes.yml и заменить задачу по деплою.

Генерация конфигураций и применение их в Kubernetes

# ...
jobs:
  # ...
  deploy:
    environment: kubernetes
    needs: [build-and-push-migrations-image, build-and-push-server-image, build-and-push-nginx-image, build-and-push-e2e-tests-image]
    runs-on: [self-hosted]

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          # We must fetch at least the immediate parents so that if this is
          # a pull request then we can checkout the head.
          fetch-depth: 2

      - name: Deploy
        env:
          DOCKER_SERVER: ${{ env.REGISTRY }}
          DOCKER_USERNAME: ${{ github.actor }}
          DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
          SERVER_APP_DATABASE_NAME: ${{ secrets.SERVER_APP_DATABASE_NAME }}
          SERVER_APP_DATABASE_PASSWORD: ${{ secrets.SERVER_APP_DATABASE_PASSWORD }}
          SERVER_APP_DATABASE_USERNAME: ${{ secrets.SERVER_APP_DATABASE_USERNAME }}
          SERVER_DOMAIN: ${{ secrets.SERVER_DOMAIN }}
          SERVER_POSTGRE_SQL_POSTGRESQL_DATABASE: ${{ secrets.SERVER_POSTGRE_SQL_POSTGRESQL_DATABASE }}
          SERVER_POSTGRE_SQL_POSTGRESQL_PASSWORD: ${{ secrets.SERVER_POSTGRE_SQL_POSTGRESQL_PASSWORD }}
          SERVER_POSTGRE_SQL_POSTGRESQL_USERNAME: ${{ secrets.SERVER_POSTGRE_SQL_POSTGRESQL_USERNAME }}
        run: |
          rm -rf ./.kubernetes/generated
          . .kubernetes/set-env.sh && npx -y rucken copy-paste --find=templates --replace=generated --replace-plural=generated --path=./.kubernetes/templates --replace-envs=true
          chmod +x .kubernetes/generated/install.sh
          docker compose -f ./.kubernetes/generated/docker-compose-infra.yml --compatibility down || echo 'docker-compose-infra not started'
          docker compose -f ./.kubernetes/generated/docker-compose-e2e-tests.yml --compatibility down || echo 'docker-compose-e2e-tests not started'
          docker compose -f ./.kubernetes/generated/docker-compose-infra.yml --compatibility up -d
          .kubernetes/generated/install.sh > /dev/null 2>&1 &
          docker compose -f ./.kubernetes/generated/docker-compose-e2e-tests.yml --compatibility up

12. Добавляем новое окружение

Переходим по адресу https://github.com/nestjs-mod/nestjs-mod-fullstack/settings/environments/new и добавляем окружение kubernetes.

13. Добавляем новые переменные окружения

Переходим в параметры созданного ранее окружении и поочередно добавляем все переменные в секцию Environment secrets, на данном этапе можно уже формировать защищенные значения для некоторых переменных.

SERVER_APP_DATABASE_NAME=app
SERVER_APP_DATABASE_PASSWORD=9UwcpRh12srXoPlTSN53ZOUc9ev9qNYg
SERVER_APP_DATABASE_USERNAME=app
SERVER_DOMAIN=fullstack.nestjs-mod.com
SERVER_POSTGRE_SQL_POSTGRESQL_DATABASE=postgres
SERVER_POSTGRE_SQL_POSTGRESQL_PASSWORD=DN7DHoMWd2D13YNH116cFWeJgfVAFO9e
SERVER_POSTGRE_SQL_POSTGRESQL_USERNAME=postgres

14. Подключаемся к выделенному серверу и удаляем все Docker — контейнеры

docker stop $(docker ps -a -q) || echo 'docker containers not started'
docker rm $(docker ps --filter status=exited -q) || echo 'docker containers not exists'

15. Коммитим изменения и ждем когда CI/CD отработает успешно и руками проверяем работу сайта

Текущий результат работы CI/CD: https://github.com/nestjs-mod/nestjs-mod-fullstack/actions/runs/10861775039
Текущий сайт: http://194.226.49.162:30222/

Заключение

Когда в команде есть DevOps — инженер он обычно и занимается деплоем приложения и данный пост больше для fullstack разработчиков которым приходится не только писать код, но и деплоить его на продакшен.

По хорошему при первой возможности нужно нанимать специалиста по DevOps или брать команду DevOps — инженеров на аутсорс и чтобы они реализовали все без использования bash — скриптов, а также инфраструктурные вещи нужно запускать не через «Docker Compose», а прямо на самих машинах.

DevOps — инженеры также должны поставить нормальную версию Kubernetes и настроить выпуск Helm — чартов, так как https://microk8s.io/ это больше для разработчиков.

Ну в целом описанная выше схема деплоя может работать в продакшен среде, как в режиме одной ноды так и в режиме кластера.

Планы

В следующем посте я добавлю Ingress для организации доступа к сайту по доменному имени и генерацию SSL — сертификата…

Ссылки

https://nestjs.com — официальный сайт фреймворка
https://nestjs-mod.com — официальный сайт дополнительных утилит
http://fullstack.nestjs-mod.com:30222 — сайт из поста
https://github.com/nestjs-mod/nestjs-mod-fullstack — проект из поста
https://github.com/nestjs-mod/nestjs-mod-fullstack/commit/4b1c3c7d6bcb0b3bac479d5f414bbefd49aa5e87 — изменения

© Habrahabr.ru