Словарь: что есть что
- Kubernetes (k8s)
- система, которая запускает и следит за контейнерами на нескольких компьютерах сразу.
- Кластер и нода
- кластер это группа компьютеров под управлением Kubernetes, нода это один такой компьютер. У нас три ноды: одна главная (control-plane) и две рабочие (worker), все в виртуальных машинах VirtualBox.
- Под (pod)
- наименьшая единица в Kubernetes, обычно один запущенный контейнер. У нас это контейнер с nginx.
- Deployment
- объект, который создаёт заданное число копий пода и пересоздаёт их при сбое.
- Namespace (пространство имён)
- отдельная «папка» для объектов внутри кластера. У нас три окружения (test, preprod, prod) живут в одном кластере, каждое в своём namespace.
- Service
- постояный адрес внутри кластера для группы подов. Поды приходят и уходят, а адрес Service остаётся.
- Ingress
- правила вида «запросы для сайта ruslans.site отправь в такой-то Service». Сами правила ничего не делают, их исполняет Ingress-контроллер (в нашем случае ingress-nginx).
- ConfigMap
- хранилище небольших файлов и настроек внутри Kubernetes.
- Helm
- менеджер пакетов для Kubernetes: ставит готовые программы одной командой.
- Cloudflare Tunneldsds
- программа cloudflared сама подключается к Cloudflare и пропускает через это соединение запросы к вашему сайту. Поэтому белый IP и открытые порты на роутере не нужны.
- Ansible
- программа, которая выполняет команды на нескольких серверах сразу по SSH, без установки агентов на них (раздел 4).
- CI/CD, пайплайн, раннер
- автоматический запуск действий после
git push: пайплайн это цепочка действий, раннер это программа, которая их выполняет (разделы 5 и 7). - kubectl
- консольная программа, через которую мы управляем кластером. Все команды ниже выполняются на главной ноде (node1), кроме тех, где указано иное (WSL, GitLab).
1что у меня установлено
- Работающий кластер Kubernetes из трёх нод (установлен через Kubespray).
- Helm на node1 (понадобится для установки Ingress-контроллера).
- Ansible на node1, установленный в виртуальное окружение Python (
/opt/ansible/.venv). - Домен ruslans.site, добавленный в Cloudflare (статус Active).
- Windows с WSL и установленным Docker внутри WSL там же gitlab runner.
- Проект на gitlab.com и его клон в WSL (у нас
zeynaloff-project). - Файлы
index.html(эта документация): мы положим их в папкуsiteрепозитория в разделе 9.
2Проверка кластера
kubectl get nodes -o wide
kubectl get nodesпоказывает список нод.-o wideдобавляет подробности, дажее IP-адреса.
В колонке STATUS у всех нod должно быть Ready (готова к работе). Если там NotReady, то сидим и исправляем
4Ansible: управление нoдами с node1
Ansible выполняет команды сразу на нескольких серверах по SSH. На самих серверах ничего ставить не нужно. Список серверов хранится в файле inventory (у нас он называется hosts), настройки в файле ansible.cfg. Всё делается на node1 в папке /opt/ansible, где Ansible установлен в виртуальное окружение Python (.venv).
Файл inventory: hosts
[servers] negga1 ansible_host=192.168.56.103 ansible_user=zeynaloff ansible_port=22 negga2 ansible_host=192.168.56.101 ansible_user=zeynaloff ansible_port=22
[servers]: имя группы. Группа нужна, чтобы обращаться ко всем серверам сразу.negga1: короткое имя сервера внутри Ansible, оно может быть любым.ansible_host: реальный IP-адрес сервера.ansible_user: под каким пользователем заходить по SSH.ansible_port: порт SSH
Файл настроек: ansible.cfg
[defaults] inventory = /opt/ansible/hosts remote_tmp = $HOME/.ansible/tmp forks = 150 sudo_user = zeynaloff [privilege_escalation] become = True become_method = sudo become_user = root
inventory: где лежит список серверов. Благодаря этому не нужно каждый раз писать-i hosts.remote_tmp: временная папка на сервере, куда Ansible кладёт свои файлы на время работы.forks = 150: на скольких серверах работать одновременно.become = True: выполнять команды с правами root черезsudo. Работает, если у пользователя настроен sudo без пароля .
Проверка связи
cd /opt/ansible source .venv/bin/activate ansible --version | grep "config file" ansible all -m ping
cd /opt/ansible: перейти в папку проекта, чтобы Ansible нашёлansible.cfg.source .venv/bin/activate: включить виртуальное окружение. В начале строки появится(.venv), и командаansibleстанет доступна.ansible --version | grep "config file": показать, какой конфиг реально используется. ЗначениеNoneозначает, что конфиг не найден.ansible all -m ping: формат всегда такой:ansible КОГО -m МОДУЛЬ.allзначит все серверы из inventory,-m pingзапускает модуль ping. Это не сетевой ping, а проверка, что Ansible смог зайти по SSH и запустить Python на сервере.
Успешный результат выглядит так: negga1 | SUCCESS => {"ping": "pong"} для каждого сервера..
Вход без пароля и sudo без пароля
ssh-copy-id [email protected] ssh-copy-id [email protected] echo "zeynaloff ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/zeynaloff
ssh-copy-id: добавляет ваш публичный SSH-ключ на сервер, после чего вход идёт без пароля. Выполняется один раз для каждого сервера, спросит пароль пользователя.- Вторая команда выполняется на каждом сервере. Файл в
/etc/sudoers.d/разрешает пользователюzeynaloffвыполнять любые команды черезsudoбез ввода пароля. Черезteeзаписываем потому, что обычное перенаправление>выполнилось бы без прав root и не сработало бы.
5CI/CD в GitLab: схема и ветки
CI/CD это автоматизация: вы делаете git push, GitLab сам запускает цепочку действий (пайплайн), а в конце сайт обновляется в кластере. Пайплайн описывается файлом .gitlab-ci.yml в корне репозитория.
- Пайплайн (pipeline)
- вся цепочка действий, которая запускается после push.
- Джоба (job)
- одно действие в пайплайне, например «выложить сайт в кластер».
- Раннер (runner)
- программа, которая реально выполняет джобы. GitLab.com сам до вашей сети 192.168.56.x не достаёт, поэтому раннер стоит у нас в WSL и сам подключается к GitLab.
- Окружение (environment)
- место, куда выкладывается сайт: test, preprod, prod. Для каждого окружения своя ветка git и своё пространство имён (namespace) в кластере.
Три ветки
git checkout -b test git push -u origin test git checkout -b preprod git push -u origin preprod git checkout -b prod git push -u origin prod
git checkout -b test: создать новую веткуtestи сразу перейти в неё.git push -u origin test: отправить ветку на GitLab (originэто адрес репозитория). Ключ-uзапоминает связь, и дальше достаточно писать простоgit push.
kubectl create namespace test kubectl create namespace preprod kubectl create namespace prod
Namespace это отдельная «папка» для объектов в кластере. Три окружения живут в одном кластере, но не мешают друг другу.
Защита веток
В GitLab: Settings → Repository → Protected branches → Add protected branch.
preprod: Allowed to merge: Maintainers, Allowed to push: Maintainers.prod: Allowed to merge: Maintainers, Allowed to push: No one. Изменения попадают в prod только через merge request.testне защищаем.
Путь изменений: test → preprod → prod. Каждая ветка хранит свой .gitlab-ci.yml, потому что GitLab читает файл из той ветки, для которой запущен пайплайн.
6SSH-ключ для CI: WSL и три ноды
Ключ состоит из двух файлов: приватный (секретный, остаётся у вас и уходит в переменную GitLab) и публичный (его можно раздавать, он копируется на все ноды). Создаём отдельный ключ именно для CI, чтобы не использовать личный.
ssh-keygen -t ed25519 -N "" -C "gitlab-ci" -f ~/.ssh/gitlab_ci
-t ed25519: современный тип ключа.-N "": пустая парольная фраза, иначе CI не сможет использовать ключ без человека.-C "gitlab-ci": комментарий внутри ключа, чтобы потом понять, чей он.-f ~/.ssh/gitlab_ci: имя файла. Появятсяgitlab_ci(приватный) иgitlab_ci.pub(публичный).
ssh-copy-id -i ~/.ssh/gitlab_ci.pub [email protected] ssh-copy-id -i ~/.ssh/gitlab_ci.pub [email protected] ssh-copy-id -i ~/.ssh/gitlab_ci.pub zeynaloff@IP_NODE1 ssh -i ~/.ssh/gitlab_ci [email protected] hostname
ssh-copy-id -i ...pub: дописывает указанный публичный ключ в файл~/.ssh/authorized_keysна ноде. Команда повторяется для каждой из трёх нод.ssh -i ... hostname: проверка. Подключаемся с этим ключом и просим ноду назвать своё имя. Если имя пришло без вопроса о пароле, всё настроено.
Если ssh-copy-id отвечает Permission denied, на ноде отключён вход по паролю. Временно включите PasswordAuthentication yes в /etc/ssh/sshd_config, выполните sudo systemctl restart ssh и после копирования ключа верните значение no.
7GitLab Runner в WSL
Раннер запускаем в Docker внутри WSL: там уже есть Docker, и из WSL видны ноды кластера.
docker volume create gitlab-runner-config docker run -d --name gitlab-runner --restart always \ -v gitlab-runner-config:/etc/gitlab-runner \ -v /var/run/docker.sock:/var/run/docker.sock \ gitlab/gitlab-runner:latest
docker volume create: создать постоянное хранилище для настроек раннера, чтобы они не пропали при пересоздании контейнера.-d: запустить в фоне.--name gitlab-runner: имя контейнера.--restart always: поднимать контейнер заново после сбоя и перезагрузки.-v gitlab-runner-config:/etc/gitlab-runner: подключить хранилище в папку, где раннер держит конфигурацию.-v /var/run/docker.sock:/var/run/docker.sock: дать раннеру доступ к Docker хоста. Без этого он не сможет запускать контейнеры для джоб.
Регистрация
В GitLab откройте Settings → CI/CD → Runners → New project runner, укажите тег wsl и скопируйте токен вида glrt-.... Затем в WSL:
docker exec -it gitlab-runner gitlab-runner register --non-interactive --url https://gitlab.com --token glrt-ВАШ_ТОКЕН --executor docker --docker-image alpine:latest
docker exec -it gitlab-runner: выполнить команду внутри запущенного контейнера.--url: адрес GitLab.--token: токен, который связывает раннер с вашим проектом.--executor docker: каждая джоба запускается в отдельном чистом контейнере.--docker-image alpine:latest: образ по умолчанию, если в джобе свой образ не указан.
Команду нужно писать в одну строку. Если строка разорвалась, программа переходит в диалоговый режим и задаёт вопросы по очереди: URL (Enter), токен, имя раннера, executor (docker), образ (alpine:latest). Тег wsl при токене glrt- задаётся только в интерфейсе GitLab.
docker exec -it gitlab-runner gitlab-runner verify
verify проверяет связь с GitLab. В Settings → CI/CD → Runners раннер должен стать зелёным (online).
Если контейнеры джоб не видят кластер
В файле /etc/gitlab-runner/config.toml (внутри контейнера раннера) в секции [runners.docker] добавьте строку network_mode = "host" и выполните docker restart gitlab-runner. Контейнеры джоб тогда используют сеть самого WSL, из которой доступны адреса 192.168.56.x.
8Переменные GitLab: kubeconfig и SSH-ключ
Секреты не хранят в репозитории. Их кладут в Settings → CI/CD → Variables, а джоба получает их при запуске.
Где взять kubeconfig
sudo cat /etc/kubernetes/admin.conf
Это файл с адресом кластера и ключами доступа. Скопируйте весь вывод. В строке server: должен быть адрес главной ноды, достижимый из WSL, например https://192.168.56.103:6443. Если там 127.0.0.1, замените на IP ноды, иначе контейнер раннера будет стучаться сам в себя.
nano /tmp/kc.yaml kubectl --kubeconfig /tmp/kc.yaml get nodes rm /tmp/kc.yaml
nano /tmp/kc.yaml: вставьте туда kubeconfig с исправленнымserver:.kubectl --kubeconfig ... get nodes: проверить, что из WSL кластер виден. Должны быть три ноды в статусе Ready.rm: удалить временный файл с секретами.
Какие переменные создать
Для каждого окружения (test, preprod, prod) нужно по две переменные, всего шесть. Поля при создании одинаковые, меняется только Environments и флаг Protect.
- Key
KUBECONFIG_FILE(значение: kubeconfig целиком) иSSH_PRIVATE_KEY(значение: содержимое~/.ssh/gitlab_ci, командаcat ~/.ssh/gitlab_ci).- Type
- File. В джобе переменная становится путём к временному файлу с содержимым. Именно так её ждут
kubectl --kubeconfigиssh. - Environments
- test, preprod или prod. Переменная будет видна только джобе этого окружения.
- Visibility
- Visible. Режим Masked принимает только однострочные значения, а kubeconfig и ключ многострочные. Содержимое File-переменной в лог не попадает, пока вы сами его не выведете.
- Protect variable
- нет для test, да для preprod и prod. Защищённая переменная доступна только защищённым веткам.
- Expand variable reference
- не отмечать: знак
$внутри ключей и конфигов не должен раскрываться как ссылка на другую переменную.
SSH_PRIVATE_KEY должна быть пустая строка после -----END OPENSSH PRIVATE KEY-----, иначе будет ошибка error in libcrypto. Kubeconfig даёт полный доступ к кластеру: никому его не показывайте и не публикуйте скриншоты со значением.9Файлы проекта и пайплайн деплоя
Объекты в кластере мы вручную не создаём. Мы описываем, что должно существовать, в файлах, кладём их в репозиторий, а пайплайн сам применяет их в нужном окружении. Файлы пишутся на языке YAML: отступы важны (вложенность задаётся двумя пробелами), строки с # это комментарии и ни на что не влияют, три дефиса --- разделяют несколько объектов в одном файле.
В корне клона репозитория должна быть такая структура:
.
├── .gitlab-ci.yml
├── site/
│ ├── index.html
│ └── project.html
└── k8s/
└── docs.yamlgit checkout test mkdir -p site k8s cp index.html project.html site/ nano k8s/docs.yaml
git checkout test: перейти в ветку test. Файлы создаём именно в ней.mkdir -p site k8s: создать две папки (-pне ругается, если папка уже есть).cp index.html project.html site/: положить обе страницы документации в папкуsite.nano k8s/docs.yaml: создать манифест, он описан ниже. Сохранение в nano: Ctrl+O, Enter, Ctrl+X.
k8s/docs.yaml
Это шаблон. Вместо реального домена в нём написано __HOST__, пайплайн подставит нужный домен в зависимости от ветки.
apiVersion: apps/v1 # версия API для объектов этого типа
kind: Deployment # создаёт и поддерживает поды
metadata:
name: docs # имя объекта
spec:
replicas: 3 # сколько копий держать запущенными
selector:
matchLabels:
app: docs # управлять подами с меткой app=docs
template: # шаблон, по которому создаются поды
metadata:
labels:
app: docs # метка на каждом поде
spec:
topologySpreadConstraints: # раскладывать копии по разным нодам
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: docs
containers:
- name: nginx
image: nginx:1.27-alpine # из какого образа запускать
ports:
- containerPort: 80 # порт, который слушает nginx в контейнере
volumeMounts: # подключить том в контейнер
- name: site
mountPath: /usr/share/nginx/html # папка, откуда nginx берёт сайт
volumes:
- name: site
configMap:
name: docs-site # том заполнен файлами из этого ConfigMap
---
apiVersion: v1
kind: Service # постоянный внутренний адрес для подов
metadata:
name: docs
spec:
selector:
app: docs # отправлять трафик на поды с этой меткой
ports:
- port: 80 # порт самого Service
targetPort: 80 # порт контейнера, куда передавать трафик
---
apiVersion: networking.k8s.io/v1
kind: Ingress # правила входа с внешней стороны
metadata:
name: docs
spec:
ingressClassName: nginx # правила исполняет контроллер ingress-nginx
rules:
- host: __HOST__ # сюда пайплайн подставит домен
http:
paths:
- path: / # для всех адресов сайта
pathType: Prefix # путь считается началом адреса
backend:
service:
name: docs # отправить в этот Service
port:
number: 80- Deployment запускает три копии nginx. Страницы он берёт из ConfigMap
docs-site, который создаёт пайплайн из папкиsite. - Service даёт этим копиям один постоянный адрес внутри кластера.
- Ingress говорит: запросы для домена
__HOST__отправлять в этот Service. Само правило ничего не делает, его исполняет Ingress-контроллер, который мы ставим в разделе 10. - На главной ноде по умолчанию стоит запрет (taint) на запуск обычных подов, поэтому три копии размещаются на рабочих нодах, а
topologySpreadConstraintsстарается распределить их поровну.
.gitlab-ci.yml для ветки test
stages: [deploy]
variables:
NAMESPACE: test
HOST: test.ruslans.site
deploy-docs:
stage: deploy
image:
name: bitnami/kubectl:latest
entrypoint: [""]
tags: [wsl]
environment:
name: test
url: https://test.ruslans.site
script:
- export KUBECONFIG="$KUBECONFIG_FILE"
- test -n "$HOST"
- kubectl create namespace $NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
- kubectl -n $NAMESPACE create configmap docs-site --from-file=site/ --dry-run=client -o yaml | kubectl apply -f -
- sed "s/__HOST__/$HOST/" k8s/docs.yaml | kubectl -n $NAMESPACE apply -f -
- kubectl -n $NAMESPACE rollout restart deployment/docs
- kubectl -n $NAMESPACE rollout status deployment/docs
rules:
- if: $CI_COMMIT_BRANCH == "test"stages: [deploy]: у пайплайна один этап.variables: свои переменные для этой ветки: в какой namespace выкладывать и какой домен подставить.deploy-docs: имя джобы.imageиentrypoint: [""]: джоба запускается в контейнере с программой kubectl. У этого образа программой по умолчанию является сам kubectl, а CI нужна оболочка для своих команд, поэтому entrypoint сбрасывается.tags: [wsl]: выполнить джобу на раннере с тегомwsl.environment: привязывает джобу к окружению. Благодаря этому подхватываются переменные с нужным Environments, а в GitLab появляется ссылка на сайт.export KUBECONFIG="$KUBECONFIG_FILE": сказать kubectl, какой файл конфигурации использовать (путь к File-переменной).test -n "$HOST": проверка, что домен не пустой. Если пустой, джоба остановится сразу.kubectl create namespace ... --dry-run=client -o yaml | kubectl apply -f -: создать namespace, если его нет.--dry-run=client -o yamlне создаёт объект, а только выводит его описание. Символ|передаёт этот вывод следующей команде, а-f -значит «читать описание из входа, а не из файла». Такая связка безопасна для повторного запуска.create configmap docs-site --from-file=site/: упаковать все файлы папкиsiteв ConfigMap, при повторном запуске он обновляется на месте.sed "s/__HOST__/$HOST/" k8s/docs.yaml | kubectl apply -f -:sedзаменяет в шаблоне__HOST__на домен, и результат сразу применяется. Файл в репозитории не меняется.rollout restart: перезапустить поды, чтобы они взяли свежие страницы.rollout status: ждать, пока обновление завершится. Если поды не поднялись, джоба упадёт и покажет ошибку.rules: запускать джобу только для ветки test.
Что меняется в других ветках
В ветках preprod и prod файл тот же, меняются четыре места, а для prod ещё добавляется ручной запуск:
- preprod
NAMESPACE: preprod,HOST: preprod.ruslans.site,environment.name: preprodиurl: https://preprod.ruslans.site, в rules$CI_COMMIT_BRANCH == "preprod".- prod
NAMESPACE: prod,HOST: ruslans.site,environment.name: prodиurl: https://ruslans.site, в rules$CI_COMMIT_BRANCH == "prod"и строкаwhen: manualпод ней. Тогда боевой деплой запускается только кнопкой Run в пайплайне.
kubectl -n test get pods -o wide.10Ingress-контроллер и MetalLB: вход в кластер
Правила Ingress из раздела 9 исполняет Ingress-контроллер: это программа (тот же nginx), которая принимает запросы снаружи и разбирает их по правилам. Чтобы у контроллера был собственный адрес в сети 192.168.56.x, не привязанный к конкретной ноде, используем MetalLB: у нас нет облачного балансировщика, и MetalLB выдаёт такие адреса сам. Сначала ставим MetalLB, затем контроллер. У нас контроллер получил адрес 192.168.56.50.
Шаг 1. Включить strict ARP
kubectl edit configmap -n kube-system kube-proxy
Откроется редактор. Найдите строку strictARP: false, замените на strictARP: true и сохраните. Это настройка сети, без которой MetalLB может работать неправильно.
Шаг 2. Установить MetalLB
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml kubectl wait -n metallb-system --for=condition=ready pod --selector=app=metallb --timeout=120s
Первая команда применяет готовый файл с адреса в интернете (можно открыть в браузере и посмотреть, что в нём), вторая ждёт готовности подов MetalLB.
Шаг 3. Задать пул адресов
apiVersion: metallb.io/v1beta1 kind: IPAddressPool # набор адресов, которые MetalLB может выдавать metadata: name: pool namespace: metallb-system spec: addresses: - 192.168.56.50-192.168.56.60 # диапазон: должен быть свободен --- apiVersion: metallb.io/v1beta1 kind: L2Advertisement # объявлять выданные адреса в локальной сети metadata: name: l2 namespace: metallb-system
Диапазон не должен пересекаться с адресами нод и с DHCP-диапазоном VirtualBox. Применяется командой kubectl apply -f metallb.yaml.
Шаг 4. Установить Ingress-контроллер
helm upgrade --install ingress-nginx ingress-nginx \ --repo https://kubernetes.github.io/ingress-nginx \ --namespace ingress-nginx --create-namespace kubectl wait --namespace ingress-nginx --for=condition=ready pod \ --selector=app.kubernetes.io/component=controller --timeout=180s kubectl get svc -n ingress-nginx ingress-nginx-controller
helm upgrade --install: установить программу, а если она уже есть, обновить.ingress-nginx ingress-nginx: первое слово это имя установки, второе это название пакета.--repo ...: адрес каталога, откуда Helm возьмёт пакет.--namespace ingress-nginx --create-namespace: поставить в отдельное пространство имён и создать его, если его нет.- Тип сервиса указывать не нужно: по умолчанию контроллер создаёт сервис типа
LoadBalancer, и адрес для него выдаёт MetalLB. kubectl wait ...: ждать до 180 секунд, пока под контроллера не станет готовым (--for=condition=ready).--selectorвыбирает под по его метке. Скачивание образа занимает около двух минут.kubectl get svc ...: в колонкеEXTERNAL-IPдолжен появиться адрес из пула (у нас192.168.56.50). Это и есть адрес входа в кластер.
Если пайплайн из раздела 9 уже запускался, поле ADDRESS у Ingress (kubectl -n test get ingress) тоже заполнится этим адресом.
11Cloudflare Tunnel: выход в интернет
Туннель работает так: программа cloudflared сама подключается к Cloudflare и пропускает через это соединение запросы к вашему сайту. Поэтому белый IP и открытые порты на роутере не нужны. Для туннеля должна быть настроена сеть WSL (раздел 3) и получен адрес Ingress (раздел 10).
- В панели Cloudflare откройте Zero Trust → Networks → Tunnels и создайте туннель типа Cloudflared. Скопируйте токен: это длинный секретный ключ, он связывает ваш контейнер с этим туннелем. Никому его не показывайте.
- В настройках туннеля на вкладке Public Hostname добавьте три маршрута, у каждого тип
HTTPи один и тот же адрес192.168.56.50:80(IP Ingress):test.ruslans.siteдля окружения test;preprod.ruslans.siteдля окружения preprod;ruslans.siteдля окружения prod.
Все три домена приходят на один Ingress, а он по имени сайта выбирает нужный namespace. Cloudflare сам создаёт DNS-записи и бесплатный сертификат для домена и поддоменов первого уровня.
Дальше, в WSL, запустите контейнер туннеля:
docker run -d --name cloudflared --restart unless-stopped \ cloudflare/cloudflared:latest tunnel --no-autoupdate run --token ВАШ_ТОКЕН
docker run -d: запустить контейнер в фоне.--name cloudflared: имя контейнера, чтобы потом им управлять.--restart unless-stopped: автоматически запускать заново после сбоя или перезагрузки Docker.cloudflare/cloudflared:latest: образ программы cloudflared.tunnel --no-autoupdate run --token ...: запустить туннель с вашим токеном и без автообновления программы.
Проверка: docker logs --tail 20 cloudflared (показать 20 последних строк лога). В нём должны быть слова Registered tunnel connection. Менять маршруты в Cloudflare можно в любой момент, контейнер перезапускать не нужно.
12Запуск, проверка и продвижение по веткам
Отправка в test
git branch --show-current git status git add site/ k8s/ .gitlab-ci.yml git commit -m "add docs site and k8s manifests" git push origin test
git branch --show-current: показать текущую ветку, должно бытьtest.git status: что изменилось и что ещё не добавлено.git add ...: выбрать файлы для коммита.git commit -m "...": сохранить изменения в истории с комментарием.git push origin test: отправить ветку на GitLab. Пайплайн стартует сам.
Откройте Build → Pipelines. Должна появиться джоба deploy-docs. После зелёного статуса проверьте результат:
kubectl -n test get pods -o wide kubectl -n test get ingress curl -H "Host: test.ruslans.site" http://192.168.56.50
- Первая команда показывает три пода и ноды, на которых они запущены.
- Вторая показывает правило Ingress с заполненным
ADDRESS. - Третья должна вернуть HTML этой страницы.
curlэто консольный браузер, а-H "Host: ..."подставляет имя сайта: Ingress выбирает правило по домену, а не по IP. Без этого заголовка вернётся404 Not Foundот nginx. - В браузере откройте
https://test.ruslans.siteиhttps://test.ruslans.site/project.html. - Устойчивость: удалите один под (
kubectl -n test delete pod ИМЯ_ПОДА). Сайт продолжает открываться, а Deployment создаёт замену.
Перенос в preprod
git checkout preprod git merge test nano .gitlab-ci.yml git add .gitlab-ci.yml git commit -m "ci for preprod" git push origin preprod
git checkout preprod: перейти в ветку preprod.git merge test: принести в неё все изменения из test: папкиsite,k8sи файл.gitlab-ci.yml.nano .gitlab-ci.yml: поправить четыре значения (namespace, домен, имя окружения, ветку в rules), потому что merge принёс файл из test.- Остальное как в прошлом блоке: сохранить коммит и отправить.
Чтобы файл .gitlab-ci.yml не перезаписывался при будущих merge, можно добавить в репозиторий файл .gitattributes со строкой .gitlab-ci.yml merge=ours и один раз выполнить git config merge.ours.driver true (настройка локальная, повторите её на каждой машине).
Перенос в prod
Те же шаги с веткой prod (merge лучше делать через merge request, так как ветка защищена). Пайплайн остановится на ручной джобе: откройте его в GitLab и нажмите Run. После этого сайт открывается по адресу https://ruslans.site.
13Если не работает
Ansible и SSH
- Ansible: No inventory was parsed, only implicit localhost is available
- конфиг не найден, потому что файл назывался
.ansible.cfg(с точкой) в/opt/ansible. Переименуйте вansible.cfgили передайте inventory явно:ansible all -i /opt/ansible/hosts -m ping. - Ansible: Could not match supplied host pattern, ignoring: ping
- перепутан порядок слов. Правильно:
ansible all -m ping. - Permission denied (publickey)
- публичный ключ не добавлен на какую-то ноду или в
SSH_PRIVATE_KEYвставлен не тот ключ. - error in libcrypto
- в конце значения
SSH_PRIVATE_KEYнет пустой строки.
Пайплайн GitLab
- Пайплайн упал за 0 секунд, джоба называется Workers Builds, стадия external
- это не ваш пайплайн: к репозиторию подключён проект Cloudflare Workers Builds, который пытается собрать каждую ветку. Откройте Workers & Pages → проект → Settings → Builds и отключите репозиторий (Disconnect) или сборку нужных веток.
- В пайплайне нет нужной джобы
- файл называется не
.gitlab-ci.yml, лежит не в корне или не закоммичен в эту ветку. Проверить синтаксис можно в Build → Pipeline editor, вкладка Validate. Нельзя вставлять в файл фрагменты с...: нужен полный файл. - Джоба висит в pending
- раннер выключен или у него нет тега
wsl. Проверьтеdocker psи статус раннера в Settings → CI/CD → Runners. - Cannot connect to the Docker daemon
- в контейнер раннера не подключён
/var/run/docker.sock. Пересоздайте контейнер командой из раздела 7. - kubectl: connection refused или timeout
- в kubeconfig в
server:указан не тот адрес (например 127.0.0.1) или контейнеры джоб не видят сеть 192.168.56.x. Исправьте адрес и добавьтеnetwork_mode = "host"(раздел 7). - Пустая переменная в джобе
- не совпал Environments или стоит флаг Protect, а ветка не защищена. Для test флаг не ставьте.
- Образ bitnami/kubectl не скачивается
- возьмите другой образ, в котором есть kubectl и оболочка, например
alpine/k8sнужной версии, и оставьтеentrypoint: [""].
Кластер и сайт
- The Ingress "docs" is invalid: spec.rules[0].host: Invalid value: "__HOST__"
- манифест применился без подстановки домена. Либо в
.gitlab-ci.ymlосталась старая джоба сkubectl apply -f k8s/, либо файл применили вручную. Применяйте только черезsed "s/__HOST__/ДОМЕН/" k8s/docs.yaml | kubectl -n NAMESPACE apply -f -. - failed calling webhook ... connection refused
- Ingress-контроллер ещё запускается. Подождите минуту (команда
kubectl waitиз раздела 10) и запустите джобу снова. - 404 Not Found от nginx
- запрос пришёл на IP без имени сайта. Используйте
curl -H "Host: test.ruslans.site" ...или заходите по домену. - Открывается не та страница или два Ingress на один домен
- на этот же домен есть другой Ingress (например, остался от ручных экспериментов), и контроллер выбирает его. Найдите его командой
kubectl get ingress -Aи удалите:kubectl delete ingress ИМЯ -n NAMESPACE. - DNS_PROBE_FINISHED_NXDOMAIN в браузере
- DNS не знает домен. Проверьте, что в Cloudflare домен в статусе Active (у регистратора указаны nameserver Cloudflare) и что в DNS есть запись CNAME для домена на
ID-ТУННЕЛЯ.cfargotunnel.com. Проверка:nslookup ruslans.site 1.1.1.1. - Ошибка 502 или 1033 на сайте
- туннель не достаёт до кластера или не запущен. Проверьте
docker logs cloudflaredи доступность192.168.56.50из WSL (раздел 3). - Поды возвращаются после удаления
- ими управляет Deployment. Чтобы убрать их насовсем, удалите сам объект (
kubectl -n NAMESPACE delete deployment docs), но следующий запуск пайплайна создаст его снова.