Предыдущая статья: Установка Kubernetes через MicroK8s и настройка деплоя NestJS и Angular приложений
В Kubernetes очень легко настраивается работа с SSL, это наверное одна из главных причин почему я и начал им пользоваться, в этой статье я опишу простой сценарий его подключения.
1. Установка менеджера сертификатов на выделенный сервер
Подключаемся к нашему серверу по SSH и включаем cert-manager плагин у MicroK8s.
Плагин cert-manager управляет процессом выпуска и продления SSL — сертификатов.
Команды
ssh root@194.226.49.162 microk8s enable cert-manager
Вывод консоли
root@vps1724252356:~# microk8s enable cert-manager Infer repository core for addon cert-manager Enable DNS addon Infer repository core for addon dns Addon core/dns is already enabled Enabling cert-manager namespace/cert-manager created customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created serviceaccount/cert-manager-cainjector created serviceaccount/cert-manager created serviceaccount/cert-manager-webhook created configmap/cert-manager-webhook created clusterrole.rbac.authorization.k8s.io/cert-manager-cainjector created clusterrole.rbac.authorization.k8s.io/cert-manager-controller-issuers created clusterrole.rbac.authorization.k8s.io/cert-manager-controller-clusterissuers created clusterrole.rbac.authorization.k8s.io/cert-manager-controller-certificates created clusterrole.rbac.authorization.k8s.io/cert-manager-controller-orders created clusterrole.rbac.authorization.k8s.io/cert-manager-controller-challenges created clusterrole.rbac.authorization.k8s.io/cert-manager-controller-ingress-shim created clusterrole.rbac.authorization.k8s.io/cert-manager-view created clusterrole.rbac.authorization.k8s.io/cert-manager-edit created clusterrole.rbac.authorization.k8s.io/cert-manager-controller-approve:cert-manager-io created clusterrole.rbac.authorization.k8s.io/cert-manager-controller-certificatesigningrequests created clusterrole.rbac.authorization.k8s.io/cert-manager-webhook:subjectaccessreviews created clusterrolebinding.rbac.authorization.k8s.io/cert-manager-cainjector created clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-issuers created clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-clusterissuers created clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-certificates created clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-orders created clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-challenges created clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-ingress-shim created clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-approve:cert-manager-io created clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-certificatesigningrequests created clusterrolebinding.rbac.authorization.k8s.io/cert-manager-webhook:subjectaccessreviews created role.rbac.authorization.k8s.io/cert-manager-cainjector:leaderelection created role.rbac.authorization.k8s.io/cert-manager:leaderelection created role.rbac.authorization.k8s.io/cert-manager-webhook:dynamic-serving created rolebinding.rbac.authorization.k8s.io/cert-manager-cainjector:leaderelection created rolebinding.rbac.authorization.k8s.io/cert-manager:leaderelection created rolebinding.rbac.authorization.k8s.io/cert-manager-webhook:dynamic-serving created service/cert-manager created service/cert-manager-webhook created deployment.apps/cert-manager-cainjector created deployment.apps/cert-manager created deployment.apps/cert-manager-webhook created mutatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created validatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created Waiting for cert-manager to be ready. ..ready Enabled cert-manager =========================== Cert-manager is installed. As a next step, try creating a ClusterIssuer for Let's Encrypt by creating the following resource: $ microk8s kubectl apply -f - <<EOF --- apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt spec: acme: # You must replace this email address with your own. # Let's Encrypt will use this to contact you about expiring # certificates, and issues related to your account. email: me@example.com server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: # Secret resource that will be used to store the account's private key. name: letsencrypt-account-key # Add a single challenge solver, HTTP01 using nginx solvers: - http01: ingress: class: public EOF Then, you can create an ingress to expose 'my-service:80' on 'https://my-service.example.com' with: $ microk8s enable ingress $ microk8s kubectl create ingress my-ingress \ --annotation cert-manager.io/cluster-issuer=letsencrypt \ --rule 'my-service.example.com/*=my-service:80,tls=my-service-tls'
2. Создаем в репозитории файл с параметрами ресурса для создания сертификатов
Существуют различные бесплатные и платные сайты которые выдают SSL — сертификаты, и мы можем указать в cert-manager каким образом и у кого брать эти сертификаты.
Лично я сам настраивал только получение сертификатов с https://letsencrypt.org и https://www.cloudflare.com, в данном проекте будут использоваться сертификаты от Let's Encrypt
.
Для того чтобы указать источник для получения сертификатов нам нужно создать ресурс для создания сертификатов.
Создаем файл .kubernetes/templates/node/8.issuer.yaml
apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod namespace: cert-manager spec: acme: email: nestjs-mod@site15.ru server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: letsencrypt-prod solvers: - http01: ingress: class: public
3. Создаем в репозитории файл с параметрами сущности Kubernetes которая отвечает за проксирование внешнего трафика в наши сервисы
По умолчанию эта сущность создается на основе Nginx, ну можно настроить и Traefik, в данном проекте будет использоваться Nginx, так как он подставляется по умолчанию.
Создаем файл .kubernetes/templates/client/5.ingress.yaml
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: namespace: '%NAMESPACE%' name: %NAMESPACE%-client-ingress annotations: cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/proxy-connect-timeout: '3600' nginx.ingress.kubernetes.io/proxy-read-timeout: '3600' nginx.ingress.kubernetes.io/proxy-send-timeout: '3600' spec: rules: - host: %SERVER_DOMAIN% http: paths: - path: / pathType: Prefix backend: service: name: %NAMESPACE%-client port: number: %NGINX_PORT% tls: - hosts: - %SERVER_DOMAIN% secretName: %NAMESPACE%-client-tls
4. Немного меняем CI/CD-конфигурацию для деплоя в Kubernetes, чтобы исключить лишний передеплой инфраструктуры
Пайплайн деплоя можно по разному разбивать, кому как удобно и как надо, но в текущем посте я просто запрещу перезапуск инфраструктуры если версия приложения не была изменена.
Обновленный вариант задачи по деплою приложения .github/workflows/kubernetes.yml
# ... jobs: # ... deploy: environment: kubernetes needs: [check-server-image, 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: fetch-depth: 2 - name: Deploy infrastructure if: ${{ needs.check-server-image.outputs.result != 'success' || contains(github.event.head_commit.message, '[skip cache]') || contains(github.event.head_commit.message, '[skip infrastructure]') }} env: 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_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 docker compose -f ./.kubernetes/generated/docker-compose-infra.yml --compatibility down || echo 'docker-compose-infra not started' docker compose -f ./.kubernetes/generated/docker-compose-infra.yml --compatibility up -d - name: Deploy applications 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 }} 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 .kubernetes/generated/install.sh > /dev/null 2>&1 & - name: Run E2E-tests env: SERVER_DOMAIN: ${{ secrets.SERVER_DOMAIN }} 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 docker compose -f ./.kubernetes/generated/docker-compose-e2e-tests.yml --compatibility up
5. Меняем адрес тестируемого сайта в «Docker Compose» — файле для E2E тестов
Так как теперь мы имеем домен с SSL — сертификатом, нам больше не нужно указывать порт с глобальным сервисом фронтенд Nginx при запуске E2E — тестов.
Обновленный файл .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' networks: - 'nestjs-mod-fullstack-network' environment: BASE_URL: 'https://%SERVER_DOMAIN%' working_dir: '/usr/src/app' volumes: - './../../apps:/usr/src/app/apps' - './../../libs:/usr/src/app/libs'
6. Коммитим изменения и ждем когда CI/CD отработает успешно и руками проверяем работу сайта
Текущий результат работы CI/CD: https://github.com/nestjs-mod/nestjs-mod-fullstack/actions/runs/10877250887
Текущий сайт: https://fullstack.nestjs-mod.com
7. Удаляем глобальный сервис который создавали в предыдущем посте
Теперь деплой и тестирование происходит сайта который работает через Ingress и значит сайт запущенный в виде глобального сервиса через NodePort больше нам не нужен и его можно удалить.
Удалить можно подключившись к выделенному серверу по ssh через команду sudo microk8s kubectl delete service master-client-global --namespace master
, но желательно всегда доносить изменения через git — репозиторий, так как у нас может быть несколько стендов на которых также нужно будет еще раз запускать эту команду.
В данном проекте изменения мы донесем через git, для этого удалим файл .kubernetes/templates/client/4.global-service.yaml
и добавим команду удаления в скрипт инсталяции приложений.
Обновляем файл .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 # depricated sudo microk8s kubectl delete service master-client-global --namespace master || echo 'not need delete master-client-global'
8. Коммитим изменения и ждем когда CI/CD отработает успешно и руками проверяем что сайт http://fullstack.nestjs-mod.com:30222 больше не работает
Текущий сайт: https://fullstack.nestjs-mod.com
Заключение
В данном проекте Ingress будет работать просто как прокси до нашего собственного Nginx с встроенным фронтенд приложением и все дальнейшие новые микросервисы и приложения которые будут разрабатываться и к которым нужен будет доступ снаружи, также будут описываться в нашем собственном Nginx.
Собственный Nginx со всеми нашими правилами маршрутизации нужен для того чтобы иметь возможность развернуть все приложения без Kubernetes и не пришлось иметь несколько разных файлов с конфигурацией Nginx.
Планы
В следующем посте я добавлю семантическое версионирование приложений которое будет запускаться в зависимости от изменений в зависимых файлах…
Ссылки
https://nestjs.com — официальный сайт фреймворка
https://nestjs-mod.com — официальный сайт дополнительных утилит
https://fullstack.nestjs-mod.com — сайт из поста
https://github.com/nestjs-mod/nestjs-mod-fullstack — проект из поста
https://github.com/nestjs-mod/nestjs-mod-fullstack/compare/28ebc77b38b2b1c9945e87806e5726451b8d22a2..33b51edf67471600e583f89f10b2d99a1b9b79da — изменения
ссылка на оригинал статьи https://habr.com/ru/articles/843572/
Добавить комментарий