@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Разделение докера на среды.
|
||||
description: Как работать с несколькими docker-compose.
|
||||
position: 3
|
||||
position: 2
|
||||
category: DevOps
|
||||
---
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Makefiles для чайников.
|
||||
description: Автоматизируем по старинке.
|
||||
position: 1
|
||||
position: 3
|
||||
category: 'DevOps'
|
||||
---
|
||||
|
||||
|
@ -246,25 +246,31 @@ $ wemake-python-styleguide --dev
|
||||
Теперь добавим конфигурационных файлов в корень проекта.
|
||||
Это мои конфигурации, которые я настроил под себя, можешь менять их как хочешь.
|
||||
|
||||
`.mypy.ini` для настройки валидации типов.
|
||||
```ini{}[.mypy.ini]
|
||||
[mypy]
|
||||
strict = True
|
||||
ignore_missing_imports=True
|
||||
allow_subclassing_any=True
|
||||
allow_untyped_calls=True
|
||||
pretty=True
|
||||
show_error_codes=True
|
||||
implicit_reexport=True
|
||||
allow_untyped_decorators=True
|
||||
```
|
||||
|
||||
`.isort.cfg` для конфигурации сортировки импортов.
|
||||
```ini{}[.isort.cfg]
|
||||
[isort]
|
||||
Для конфигурации сортировки импортов и проверки типов добавим следющее
|
||||
в наш основной файл проекта.
|
||||
|
||||
Обычно я добавляю эти секции сразу после секции `[tool.poetry.dev-dependencies]`.
|
||||
|
||||
```toml{}[pyproject.toml]
|
||||
...
|
||||
|
||||
[tool.mypy]
|
||||
strict = true
|
||||
ignore_missing_imports = true
|
||||
allow_subclassing_any = true
|
||||
allow_untyped_calls = true
|
||||
pretty = true
|
||||
show_error_codes = true
|
||||
implicit_reexport = true
|
||||
allow_untyped_decorators = true
|
||||
warn_return_any = false
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
multi_line_output = 3
|
||||
include_trailing_comma = true
|
||||
use_parentheses = true
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
`.flake8` - конфигурация линтинга. Тут довольно много. Это игнорирование ненужных кодов ошибок, которые не особо-то и ошибки.
|
||||
@ -416,13 +422,6 @@ repos:
|
||||
entry: yesqa
|
||||
language: system
|
||||
types: [ python ]
|
||||
|
||||
- id: pytest
|
||||
name: pytest
|
||||
entry: pytest
|
||||
language: system
|
||||
pass_filenames: false
|
||||
types: [ python ]
|
||||
```
|
||||
|
||||
И не забываем про `.gitignore`. Его можно найти [тут](https://github.com/github/gitignore/blob/master/Python.gitignore).
|
||||
|
753
content/ru/start-with-k8s.md
Normal file
753
content/ru/start-with-k8s.md
Normal file
@ -0,0 +1,753 @@
|
||||
---
|
||||
title: Начало работы с kubernetes
|
||||
description: Как там это всё разворачивать в двух словах.
|
||||
category: DevOps
|
||||
position: 4
|
||||
---
|
||||
|
||||
# Проблема в изучении кубернетес
|
||||
|
||||
Многие люди, кто задавались вопросом как начать работать с
|
||||
кубернетесом сталкивались с тем, что документация крайне
|
||||
большая сложная и нет нормального описания как
|
||||
завернуть маленький проект из одного проекта в свой кластер или
|
||||
как развернуть свой кластер без боли.
|
||||
|
||||
А всё потому что вся документация нацелена на большие
|
||||
production-ready системы с большим rps и тому подобным.
|
||||
|
||||
В данной статье я попробую исправить это вселенское
|
||||
недопонимание используя k3s, свой комплюктер и немного знаний по кодингу.
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
# Что такое кубернетес и почему это лучше докера
|
||||
|
||||
Многие ребята, кто хорошо знаком с докером и его
|
||||
возможностями могут задаваться таким вопросом.
|
||||
Для тех кто в танке, напомню, что докер
|
||||
имеет вариант запуска в режиме кластера.
|
||||
Этот функционал называется docker swarm.
|
||||
В целом, swarm отдалённо напоминает kubernetes,
|
||||
так как в этом режиме докер, худо-бедно но умеет
|
||||
автоскейлится и запускаться в кластере,
|
||||
но это всё равно немного не то.
|
||||
|
||||
<b-message type="is-info" has-icon>
|
||||
Также замечу, что кубер активно развивается и поддерживается.
|
||||
огромным количество компаний. А вот docker swarm уже по-немногу
|
||||
умирает и документация по нему не то чтобы супер хороша.
|
||||
</b-message>
|
||||
|
||||
По большей части кубернетес это система,
|
||||
которая будет управлять вашими приложениями,
|
||||
следить за их состоянием и помогать вам в их
|
||||
конфигурации. Кубер умеет очень много, поэтому
|
||||
все интересные способности я в этой статье не смогу осветить,
|
||||
но самую базу попробую рассказать.
|
||||
|
||||
# Из чего состоит кубернетес
|
||||
|
||||
Так как я в этой статье хотел затронуть
|
||||
совсем базовые и практические вещи, то рассматривать
|
||||
мы будем только крайне полезные компоненты.
|
||||
|
||||
- Container
|
||||
- Pod
|
||||
- Deployment
|
||||
- Service
|
||||
- Ingress
|
||||
- Namespace
|
||||
- Secret
|
||||
- ConfigMap
|
||||
|
||||
А теперь рассмотрим немного поподробнее.
|
||||
|
||||
## Container
|
||||
|
||||
Контейнеры не то чтобы часть специфичная для кубернетес.
|
||||
С контейнерами вы можете быть знакомы из кучи систем.
|
||||
В контексте кубера они не обладают никакими дополнительными
|
||||
свойствами. Это ровно то же, что и контейнеры `containerd`
|
||||
или те, с которыми вы возились с докером. Ничего нового.
|
||||
|
||||
Собираются контейнеры для кубернетеса ровно тем же образом,
|
||||
что и для докера.
|
||||
|
||||
<b-message type="is-warning" has-icon>
|
||||
|
||||
Важная ремарка. Кубер начиная с 2021 кубернетес не поддерживает
|
||||
докер как бэкенд. Теперь кубер будет общаться с
|
||||
containerd напрямую. Это значит, что теперь перед использованием
|
||||
контейнеров собранных на локальной машине надо будет импортировать их
|
||||
в `containerd` используя `ctr image import`.
|
||||
|
||||
Как импортировать образы почитать можно в [этой статье](https://cwienczek.com/2020/06/import-images-to-k3s-without-docker-registry/).
|
||||
|
||||
</b-message>
|
||||
|
||||
## Pod
|
||||
|
||||
<div align="center">
|
||||
<img alt="Pods" style="width: 100%" src="/images/k3s_start/pods.svg">
|
||||
</div>
|
||||
|
||||
Поды - это логически связанные группы контейнеров.
|
||||
Это самая базовая еденица кубернетеса.
|
||||
|
||||
В поде находится от одного до множества контейнеров.
|
||||
Интересная особенность пода в том, что все контейнеры
|
||||
делят один сетевой адресс. Другими словами,
|
||||
если у вас один из контейнеров открыл порт `3000`,
|
||||
то другие контейнеры из пода эти порты использовать не смогут.
|
||||
|
||||
То есть, если вы хотите логически связанные приложения
|
||||
поместить в под, то они могут ходить друг к другу через лупбек
|
||||
адреса. Такие как `localhost` или `127.0.0.1`.
|
||||
|
||||
## Deployment
|
||||
|
||||
Поды это круто, но есть одно но. Данные объекты неизменяемые
|
||||
и сам под скейлить вручную занятие сомнительное.
|
||||
|
||||
Конечно, никто не мешает создать вам вручную [ReplicaSet](https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/)
|
||||
и самому крутить там нужные значения. Но в среднем
|
||||
вам с этим возиться не очень то хочется.
|
||||
|
||||
<br>
|
||||
|
||||
Deployment нужна именно для описания подов и создания
|
||||
некоторых ресурсов, нужных для скейлинга.
|
||||
Также с помощью деплойментов можно делать откаты приложения через
|
||||
механиз роллбеков. Я это рассматривать не буду. Тут только база.
|
||||
|
||||
## Service
|
||||
|
||||
Сервис - это ресурс, с помощью которого поды могут общаться
|
||||
между собой. По факту сервис описывает, какие порты пода
|
||||
открыты и перенаправляет трафик на них.
|
||||
|
||||
Представьте себе под, который просто проксирует весь трафик с одного
|
||||
порта на какой-нибудь порт пода. Это и есть Service.
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
Выглядит сервис примерно так как показано на кртинке выше.
|
||||
Он направляет весь входной трафик с указанного порта на
|
||||
порты подов.
|
||||
|
||||
Также сервис выступает как балансировщик.
|
||||
|
||||
<b-message type="is-info" has-icon>
|
||||
Если вы хотите сделать запрос от одного пода до дргого внутри кластера, то
|
||||
вам придётся использовать сервис.
|
||||
<br>
|
||||
Если вы попробуете сделать запрос напрямую по IP пода, то у вас, конечно же,
|
||||
получится, но это довольно странная идея из-за того, что при пересоздании
|
||||
пода у него может обновится IP внутри кластера. Поэтому лучше использовать сервисы. Также они потребуются для ингресса.
|
||||
</b-message>
|
||||
|
||||
## Ingress
|
||||
|
||||
Ингрессы это сервис описывающий куда пускать трафик,
|
||||
который поступает снаружи кластера.
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
Принцип работы ингресса следующий:
|
||||
|
||||
Вы указываете хост ингресса и различные регексы пути.
|
||||
В зависимости от входящего запроса ингресс выбирает в какой сервис
|
||||
направить его и в какой порт.
|
||||
|
||||
Настройка ингресса достаточно гибкая и я не думаю,
|
||||
что вы можете столкнуться с какими либо проблемами.
|
||||
|
||||
## Namespace
|
||||
|
||||
Неймспейсы это логические разделители уровня доступа.
|
||||
|
||||
Я предпочитаю использовать разные неймспейсы под различные логические группы приложений.
|
||||
|
||||
Например, в моём кластере есть отдельные неймспейсы для каждого приложения.
|
||||
|
||||
Скажем, у меня есть проект, в котором есть база данных и веб сервер.
|
||||
Я держу их в одном неймспейсе, который называется также, как и приложени.
|
||||
|
||||
А вот телеграм боты -- приложения достаточно лёгкие.
|
||||
Поэтому у меня есть неймспейс со всеми телеграм ботами.
|
||||
|
||||
Эта штука позволяет просто лучше организовать свой кубернетес кластер.
|
||||
|
||||
Также неймспейсы очень полезны для ограничивания возможностей
|
||||
конкретного пользователя.
|
||||
Например, вы можете создать правило, которое будет разрешать пользователю
|
||||
смотреть на поды в каком-то неймспейсе, но при этом ему нельзя будет
|
||||
что либо изменять.
|
||||
|
||||
## Secret и ConfigMap
|
||||
|
||||
Данные ресурсы нужны только чтобы хранить
|
||||
внутри кластера какую-нибудь информацию.
|
||||
|
||||
Например вы можете сохранить в ConfigMap
|
||||
переменные среды и потом использовать их в подах в одном неймспейсе.
|
||||
|
||||
В секреты обычно кидают сертификаты или какие-нибудь ключи.
|
||||
Но так как секреты не особо секретные принято использовать [Vault](https://www.vaultproject.io/docs/platform/k8s). Но так как эта статья затрагивает только
|
||||
основы рассматривать развертку и настройку Vault мы не будем, ну и также HashiCorp
|
||||
всё довольно подробно расписали сами.
|
||||
|
||||
# Как развернуть k8s у себя
|
||||
|
||||
Для локального кубера есть пара вариантов.
|
||||
|
||||
- k3s (Недоступен под Windows)
|
||||
- minikube
|
||||
|
||||
На первый взгляд minikube может показаться лучшим вариантом.
|
||||
И он действительно хорош тем, что его легко почистить после
|
||||
своих экспериментов. Однако, там есть проблемы с ингрессами.
|
||||
По факту они не работают и там надо окольными путями получать
|
||||
адреса приложения.
|
||||
|
||||
<br>
|
||||
|
||||
k3s - это легковесная production-ready реализация k8s. Ingress у него работают
|
||||
отлично, поэтому я буду использовать его.
|
||||
|
||||
<br>
|
||||
|
||||
Я не буду зацикливаться на установке `minikube`, так как
|
||||
он прост в установке и первоначальной настройке. Почитать подробнее можно в
|
||||
[официальном гайде от minikube](https://minikube.sigs.k8s.io/docs/start/).
|
||||
|
||||
С `k3s` всё немного посложнее, но тоже достаточно просто, если немного разобраться. Первоначальная установка описана в [официальной доке k3s](https://rancher.com/docs/k3s/latest/en/installation/install-options/).
|
||||
|
||||
## Подключение кластера
|
||||
|
||||
После установки в домашней дериктории должен был быть
|
||||
сгенерирован файл `.kube/config`. Этот файл содержит данные для
|
||||
подключения к различным кластерам. `minikube` Сам добавляет
|
||||
ключи для подключения к .kube/config. `K3S` не изменяет
|
||||
никаким образом `.kube/config`, поэтому надо будет это сделать вручную.
|
||||
|
||||
Для того, чтобы это сделать сначала разберёмся как выглядит конфиг.
|
||||
|
||||
```yaml{}[.kube/config]
|
||||
apiVersion: v1
|
||||
kind: Config
|
||||
preferences: {}
|
||||
# Массив кластеров.
|
||||
# Каждый элемент -- данные для подключения
|
||||
# Тут также есть названия для каждого кластера.
|
||||
clusters:
|
||||
- name: hyper
|
||||
cluster:
|
||||
certificate-authority-data: DATA+OMITTED
|
||||
server: https://192.168.1.55:6443
|
||||
- name: k3s-local
|
||||
cluster:
|
||||
certificate-authority-data: DATA+OMITTED
|
||||
server: https://127.0.0.1:6443
|
||||
# Массив данных пользователя.
|
||||
# Тут указаны пользователи с
|
||||
# различными сертификатами.
|
||||
# Обычно для разных серверов у вас будут
|
||||
# Различные данные для входа.
|
||||
users:
|
||||
- name: hyper-s3rius
|
||||
user:
|
||||
client-certificate-data: REDACTED
|
||||
client-key-data: REDACTED
|
||||
- name: k3s-user
|
||||
user:
|
||||
client-certificate-data: REDACTED
|
||||
client-key-data: REDACTED
|
||||
# Массив контекстов.
|
||||
# Контекст - это связующее звено
|
||||
# между кластероами и пользователями.
|
||||
contexts:
|
||||
- context:
|
||||
cluster: hyper
|
||||
user: hyper-s3rius
|
||||
name: hyper
|
||||
- context:
|
||||
cluster: k3s-local
|
||||
user: k3s-user
|
||||
name: k3s
|
||||
# Текущий контекст указывает какой
|
||||
# контекст использовать по умолчанию.
|
||||
current-context: "k3s"
|
||||
```
|
||||
|
||||
Для работы с кубером из командной строки
|
||||
можно использовать `kubectl`. Чтобы сменить контекст в любой команде вы
|
||||
можете передать параметр `--context $CONTEXT_NAME`, где `$CONTEXT_NAME` это название контекста.
|
||||
|
||||
Чтобы достать данные для подключения к `k3s` надо посмотреть его конфиг
|
||||
и скопировать данные. Либо выставить переменную среды, которая будет
|
||||
указывать конфиг k3s. Конфиг подключения `k3s` лежит в файле `/etc/rancher/k3s/k3s.yaml`.
|
||||
|
||||
Можете выполнить команду, которая будет просить kubectl использовать указанный конфиг:
|
||||
`export KUBE_CONFIG=/etc/rancher/k3s/k3s.yaml`
|
||||
|
||||
<br>
|
||||
|
||||
Либо скопируйте нужные данные для подключения себе в `.kube/config`.
|
||||
|
||||
После настройки выполните команду и проверьте что вам вернулось
|
||||
что-то подобное. Это значит, что никаких ресурсов пока в
|
||||
кластере нет. Это мы исправим позже. Пока что можно сказать,
|
||||
что подключение прошло успешно.
|
||||
|
||||
```bash
|
||||
$ kubectl --context "my-context" get pods
|
||||
No resources found in default namespace.
|
||||
```
|
||||
|
||||
Если же у вас выпадает ошибка, например такая.
|
||||
|
||||
```
|
||||
The connection to the server localhost:8080 was refused - did you specify the right host or port?
|
||||
```
|
||||
|
||||
То либо запустите кластер
|
||||
|
||||
```
|
||||
sudo systemctl start k3s.service
|
||||
```
|
||||
|
||||
Либо у вас неверные данные для входа.
|
||||
|
||||
### Мониторинг кластера
|
||||
|
||||
Для просматривания управления кластером используется cli утилита `kubectl`.
|
||||
Но для того, чтобы с ней работать нужно получше понять что вообще в кластере есть.
|
||||
|
||||
Для этого я советую использовать [Lens](https://k8slens.dev/). Это крайне
|
||||
удобный интерфейс для управления своим кластером.
|
||||
Также там есть очень клёвая настрока, которая сама включит мониторинг
|
||||
потребления памяти и процессора для всех подов и кластера в общем.
|
||||
|
||||
На локальной машине это не очень много смысла имеет,
|
||||
а вот в проде было бы очень полезно.
|
||||
|
||||
Выглядит Lens примерно так:
|
||||
|
||||
<div align="center" style="margin-top:15px;">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
Для изучения **крайне настоятельно рекомендую** настроить Lens.
|
||||
|
||||
# Ваше первое приложение в кластере.
|
||||
|
||||
### Сервер
|
||||
|
||||
Давайте создадим своё первое приложение.
|
||||
|
||||
Для этого я буду использовать [express.js](https://expressjs.com/), так как он крайне популярен и прост.
|
||||
|
||||
Для этого сначала напишем сам сервер. Я буду использовать yarn, но можете и npm,
|
||||
сути не поменяет.
|
||||
|
||||
Создайте какую-нибудь папку, где вы будете эксперементировать, откройте в ней ваш любимый тектовый редактор и просто копируйте файлы ниже.
|
||||
|
||||
```json{}[package.json]
|
||||
{
|
||||
"name": "req_counter",
|
||||
"version": "1.0.0",
|
||||
// Указан модуль,
|
||||
// чтобы использовать нормальны импорты,
|
||||
// а не require.
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
// Скрипт старта сервера.
|
||||
"server": "node index.js"
|
||||
},
|
||||
// Зависимости проекта.
|
||||
"dependencies": {
|
||||
"express": "^4.17.1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
И само приложение
|
||||
|
||||
```js{}[index.js]
|
||||
import express from "express";
|
||||
import { hostname } from "os";
|
||||
import { argv, exit } from "process";
|
||||
|
||||
// Серверное приложение.
|
||||
const app = express();
|
||||
|
||||
// Глобальный счётчик входящих запросов.
|
||||
let requests = 0;
|
||||
|
||||
// Обработка входящего запроса.
|
||||
app.get("*", (req, res) => {
|
||||
// Увеличиваем глобальный счётчик запросов.
|
||||
requests += 1;
|
||||
// Логгируем входящий запрос.
|
||||
console.log(`${req.method} ${req.url}`);
|
||||
// Возвращаем информацию о текущем хосте и количестве запросов.
|
||||
res.json({
|
||||
requests: requests,
|
||||
hostname: hostname(),
|
||||
});
|
||||
});
|
||||
|
||||
// Аргументы командной строки.
|
||||
// Напрмиер yarn run server 127.0.0.1 8000
|
||||
const args = argv.slice(2);
|
||||
|
||||
// Если передано неверное количество аргументов.
|
||||
if (args.length != 2) {
|
||||
console.error("Usage: yarn run server {host} {port}");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Простейший "парсинг" аргументов командной строки.
|
||||
const host = args[0];
|
||||
const port = args[1];
|
||||
|
||||
// Старт сервера.
|
||||
app.listen(port, host, () => {
|
||||
console.log(`Server listening at http://${host}:${port}`);
|
||||
});
|
||||
```
|
||||
|
||||
Это всё. Сервер готов.
|
||||
|
||||
<hr>
|
||||
Протестируем запуск сервера выполнив команду ниже и открыв в
|
||||
своём любимом браузере http://localhost:8080.
|
||||
|
||||
```
|
||||
yarn run server 0.0.0.0 8080
|
||||
```
|
||||
|
||||
У меня всё работает и успешно отдаётся нужная информация.
|
||||
|
||||
```json
|
||||
{
|
||||
"requests": 1,
|
||||
"hostname": "s3rius-pc"
|
||||
}
|
||||
```
|
||||
|
||||
### Docker образ
|
||||
|
||||
Теперь создадим докер образ приложения.
|
||||
|
||||
Добавим `.dockerignore`, чтобы игнорировать ненужные файлы во время сборки образа.
|
||||
|
||||
```gitignore{}[.dockerignore]
|
||||
node_modules/
|
||||
```
|
||||
|
||||
И добавим в проект `Dockerfile` для описания самого процесса сборки.
|
||||
|
||||
```dockerfile{}[Dockerfile]
|
||||
FROM node:17-alpine
|
||||
|
||||
WORKDIR /app
|
||||
COPY . /app/
|
||||
|
||||
RUN yarn install
|
||||
|
||||
CMD [ "yarn", "run", "server", "0.0.0.0", "8000"]
|
||||
```
|
||||
|
||||
Давайте соберём и запустим проект в контейнере.
|
||||
|
||||
```bash
|
||||
docker build --tag="s3rius/req-counter-express:latest" .
|
||||
|
||||
docker run --rm -it -p 3400:8000 "s3rius/req-counter-express:latest"
|
||||
```
|
||||
|
||||
Можете проверить, что приложение работает успешно, открыв в браузере http://localhost:3400.
|
||||
|
||||
У меня в ответ пришло то же сообщение. Только, как можно заметить,
|
||||
`hostname` поменялся. На самом деле контейнеры используют свои hostname
|
||||
отличные от `hostname` локальной машины.
|
||||
|
||||
```json
|
||||
{
|
||||
"requests": 10,
|
||||
"hostname": "8f23adadc640"
|
||||
}
|
||||
```
|
||||
|
||||
Вариантов как положить это приложение в K8S несколько.
|
||||
|
||||
- Вы можете запушить собранное приложение в [Docker HUB](https://hub.docker.com/) и использовать его.
|
||||
- Можете использовать мой образ `s3rius/req-counter-express:latest`
|
||||
- Импортировать собранный образ как tar файл и импортировать его в containerd напрямую.
|
||||
Как это сделать почитать можно в [этой статье](https://cwienczek.com/2020/06/import-images-to-k3s-without-docker-registry/).
|
||||
|
||||
### Деплой в k8s
|
||||
|
||||
Создайте папку `kube` в папке проекта и теперь мы будем работать в ней.
|
||||
Все ресурсы будут описаны yaml-файлами.
|
||||
|
||||
```yaml{}[kube/deployment.yml]
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
# метаданные самого деплоймента.
|
||||
metadata:
|
||||
name: req-counter-deployment
|
||||
spec:
|
||||
# Количество реплик пода.
|
||||
replicas: 1
|
||||
# Селектор, который выбирает
|
||||
# какие поды принадлежат этому деплойменту.
|
||||
selector:
|
||||
matchLabels:
|
||||
app: req-counter
|
||||
# Шаблон пода,
|
||||
# который будет использоваться при
|
||||
# маштабировании.
|
||||
template:
|
||||
# Метаданные пода.
|
||||
# тут обычно помещаются лейблы,
|
||||
# с помощью которых деплоймент идентифицирует
|
||||
# свои поды.
|
||||
metadata:
|
||||
labels:
|
||||
app: req-counter
|
||||
spec:
|
||||
# Масссив контейнеров
|
||||
containers:
|
||||
# Название контейнера внутри пода.
|
||||
- name: req-counter-app
|
||||
# Образ приложения.
|
||||
image: s3rius/req-counter-express:latest
|
||||
# Ресурсы требуемые для работы приложения.
|
||||
resources:
|
||||
# Минимальное количество ресурсов,
|
||||
# которое кластер гарантированно предоставит приложению.
|
||||
# Также данные значения используются для того,
|
||||
# чтобы выяснить на какой ноде запускать приложение.
|
||||
requests:
|
||||
memory: "50Mi"
|
||||
cpu: "30m"
|
||||
# Максимально возможные значения приложения.
|
||||
# Если приложение выйдет за лимиты,
|
||||
# то кубер убьёт приложение.
|
||||
limits:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
# Порты на которых открыты приложения.
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
protocol: TCP
|
||||
```
|
||||
|
||||
Теперь опишем сервис для управления трафиком.
|
||||
|
||||
```yaml{}[kube/service.yml]
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
# Метадата сервиса.
|
||||
metadata:
|
||||
name: req-counter-service
|
||||
spec:
|
||||
# Селектор подов,
|
||||
# которым будет пускаться трафик.
|
||||
# Трафик может идти в любой под,
|
||||
# который матчит данному селектору.
|
||||
selector:
|
||||
app: req-counter
|
||||
# Порты для проксирования соединений.
|
||||
ports:
|
||||
# Порт сервиса
|
||||
- port: 80
|
||||
# Порт пода, куда будет идти трафик дальше.
|
||||
targetPort: 8000
|
||||
```
|
||||
|
||||
И в последнюю очередь опишем наш ингрес.
|
||||
|
||||
```yaml{}[kube/ingress.yml]
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
# Метаданные ингресса.
|
||||
metadata:
|
||||
name: req-counter-ingress
|
||||
labels:
|
||||
name: req-counter-ingress
|
||||
spec:
|
||||
# Правила роутинга.
|
||||
rules:
|
||||
# Требуемый хост.
|
||||
- host: req-counter.local
|
||||
http:
|
||||
paths:
|
||||
# Тип пути Prefix значит,
|
||||
# что все запросы, которые начинаются c
|
||||
# ${path} будут матчится тут.
|
||||
- pathType: Prefix
|
||||
# Сам путь.
|
||||
path: "/"
|
||||
backend:
|
||||
service:
|
||||
# Название нашего сервиса.
|
||||
name: req-counter-service
|
||||
# Порт сервиса, куда перенаправлять входящий трафик.
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
Перед тем, как создать все описанные ресурсы создадим неймспейс.
|
||||
|
||||
```bash
|
||||
$ kubectl --context k3s create namespace req-couter-ns
|
||||
namespace/req-couter-ns created
|
||||
```
|
||||
|
||||
После того, как неймспейс создан самое время сделать последний штрих.
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
```bash
|
||||
$ kubectl --context k3s --namespace req-couter-ns apply -f ./kube/
|
||||
deployment.apps/req-counter-deployment created
|
||||
ingress.networking.k8s.io/req-counter-ingress created
|
||||
service/req-counter-service created
|
||||
```
|
||||
|
||||
Готово. Теперь вы можете зайти в lens, выбрать свой кластер из списка
|
||||
и посмотреть как там поживает ваше приложение.
|
||||
|
||||
Также не забудьте указать неймспей, в который вы беплоиди приложение.
|
||||
|
||||
Выглядит это чудо примерно так:
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
Также можно использовать команду и вывести всё себе в терминал.
|
||||
|
||||
```
|
||||
$ kubectl --context k3s --namespace req-couter-ns get all
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
pod/req-counter-deployment-764476db97-dt2tc 1/1 Running 0 8m11s
|
||||
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
service/req-counter-service ClusterIP 10.43.50.23 <none> 80/TCP 8m11s
|
||||
|
||||
NAME READY UP-TO-DATE AVAILABLE AGE
|
||||
deployment.apps/req-counter-deployment 1/1 1 1 8m11s
|
||||
|
||||
NAME DESIRED CURRENT READY AGE
|
||||
replicaset.apps/req-counter-deployment-764476db97 1 1 1 8m11s
|
||||
```
|
||||
|
||||
### Маштабирование
|
||||
|
||||
В рамках демонстрации давате поменяем значение
|
||||
`replicas` в нашем файле деплоймента до `3`.
|
||||
|
||||
```yaml{}[kube/deployment.yml]
|
||||
...
|
||||
spec:
|
||||
# Количество реплик пода.
|
||||
replicas: 3
|
||||
...
|
||||
```
|
||||
|
||||
После изменения просто ещё раз вызовите команду apply.
|
||||
|
||||
```bash
|
||||
$ kubectl --context k3s --namespace req-couter-ns apply -f ./kube/
|
||||
deployment.apps/req-counter-deployment configured
|
||||
ingress.networking.k8s.io/req-counter-ingress unchanged
|
||||
service/req-counter-service unchanged
|
||||
```
|
||||
|
||||
Как можно видеть изменился только наш `Deployment`.
|
||||
Остальные ресурсы остались нетронутыми.
|
||||
|
||||
Давайте посмотрим на поды в нашем неймспейсе.
|
||||
|
||||
```
|
||||
$ kubectl --context k3s --namespace req-couter-ns get pods
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
req-counter-deployment-764476db97-dt2tc 1/1 Running 0 13m
|
||||
req-counter-deployment-764476db97-tdjrb 1/1 Running 0 69s
|
||||
req-counter-deployment-764476db97-x28fr 1/1 Running 0 69s
|
||||
```
|
||||
|
||||
Как видно, всё правильно. Теперь у нас 3 пода
|
||||
нашего приложения.
|
||||
|
||||
|
||||
Теперь я пойду и выполню кучу запросов по адресу http://req-counter.local/
|
||||
и получу балансировку между подами из коробки, без дополнительных
|
||||
конфигураций.
|
||||
|
||||
Если у вас не получается найти адрес. Добавьте данный хост себе в
|
||||
`/etc/hosts` на линуксе или в `C:\Windows\System32\drivers\etc\hosts` на windows,
|
||||
дописав в конец файла следующее:
|
||||
|
||||
```
|
||||
...
|
||||
127.0.0.1 req-couter.local
|
||||
```
|
||||
|
||||
И теперь вы можете открыть в браузере своё приложение и увидеть,
|
||||
как кубернетес заботливо направляет трафик туда, куда вы хотите.
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
|
||||
### Как мне очистить мой кластер?
|
||||
|
||||
Всё очень просто.
|
||||
|
||||
Так как у нас имеются описния ресурсов, то мы
|
||||
можем удалить всё сразу используя команду
|
||||
|
||||
```bash
|
||||
$ kubectl --context k3s --namespace req-couter-ns delete -f ./kube/
|
||||
deployment.apps "req-counter-deployment" deleted
|
||||
ingress.networking.k8s.io "req-counter-ingress" deleted
|
||||
service "req-counter-service" deleted
|
||||
```
|
||||
|
||||
Таким образом k8s удалит все описанные ресурсы из вашего кластера.
|
||||
|
||||
До новых встреч.
|
@ -7,7 +7,6 @@ category: DevOps
|
||||
|
||||
# Traefik - роутинг просто
|
||||
|
||||
|
||||
Сегодня я бы хотел рассказать о такой клёвой штуке, как [Traefik](https://traefik.io/traefik/). Не так давно я перевёл все сервисы своего сервера на traefik и это буквально сделало жизнь проще. Сегодня я расскажу вам зачем он нужен, как его настроить, и покажу на примере, как именно он помогает. По факту, это nginx на стероидах, в плане конфигурации, потому что в traefik за тебя сделано гораздо больше.
|
||||
|
||||
# Что такое traefik
|
||||
@ -33,6 +32,7 @@ Traefik - это система, которая позволяет настро
|
||||
</div>
|
||||
|
||||
## Архитектура
|
||||
|
||||
В официальной документации зарисована следующая схема работы traefik:
|
||||
|
||||
<div align="center">
|
||||
@ -43,21 +43,24 @@ Traefik - это система, которая позволяет настро
|
||||
|
||||
Как можно видеть по данному изображению, есть 5 основных составляющих traefik, а именно:
|
||||
|
||||
* entrypoints
|
||||
* routers
|
||||
* rules (часть routers)
|
||||
* middlewares (чать routers)
|
||||
* services
|
||||
- entrypoints
|
||||
- routers
|
||||
- rules (часть routers)
|
||||
- middlewares (чать routers)
|
||||
- services
|
||||
|
||||
### Entrypoint
|
||||
|
||||
Являются основными слушателями входящих соединений. Через них проходит весь трафик. Если попробовать объяснить в двух словах: "Порт, на который должно прийти входящее соединение", - вполне себе неплохое объяснение. В данном туториале я создам 2 `entrypoint`, которые будут слушать на http и https.
|
||||
|
||||
### Routers, Rules и Middlewares
|
||||
Роутер - связующее звено между `entrypoint` и сервисом, куда нужно направить трафик. Роутер хранит в себе информацию, куда направить входящий трафик, и
|
||||
### Routers, Rules и Middlewares
|
||||
|
||||
Роутер - связующее звено между `entrypoint` и сервисом, куда нужно направить трафик. Роутер хранит в себе информацию, куда направить входящий трафик, и
|
||||
правила, по которым можно определить, пускать ли трафик дальше.
|
||||
|
||||
Rules - это и есть те самые правила, которые определяют, через какой роутер пустить трафик.
|
||||
Допустим, у вас в конфиге есть следующие правила:
|
||||
|
||||
```
|
||||
Host(`myapp.com`) && (PathPrefix(`/api`) || PathPrefix(`/memes`))
|
||||
```
|
||||
@ -75,7 +78,6 @@ Host(`myapp.com`) && (PathPrefix(`/api`) || PathPrefix(`/memes`))
|
||||
Вы, конечно же, можете использовать локально установленную версию, всё будет работать, как часы.
|
||||
</b-message>
|
||||
|
||||
|
||||
Для нашего основного traefik на сервере мы создадим небольшой docker-compose.yml, файл конфига и папочку с сертификатами.
|
||||
Структура будет следующая:
|
||||
|
||||
@ -100,13 +102,20 @@ services:
|
||||
# The official v2.0 Traefik docker image
|
||||
image: traefik:v2.4.8
|
||||
container_name: traefik_main
|
||||
command:
|
||||
command:
|
||||
# Включает прослушивание докера на новые события и следит за лейблами контейнеров.
|
||||
- --providers.docker=true
|
||||
# Отключает автоматическое создание роутеров ко всем контейнерам на сервере.
|
||||
- --providers.docker.exposedbydefault=false
|
||||
# Сеть докера, по которой будет выполнятся подключение к контейнерам.
|
||||
- --providers.docker.network=traefik-shared
|
||||
# Папка с конфигурационным файлом.
|
||||
- --providers.file.directory=/etc/traefik/dynamic
|
||||
# Включает отслеживание изменений файла конфигурации.
|
||||
- --providers.file.watch=true
|
||||
# Создаёт entrypoint с названием http и слушает 80 порт.
|
||||
- --entrypoints.http.address=:80
|
||||
# Создаёт entrypoint с названием https и слушает 443 порт.
|
||||
- --entrypoints.https.address=:443
|
||||
ports:
|
||||
# The HTTP port
|
||||
@ -119,12 +128,16 @@ services:
|
||||
environment:
|
||||
MAIN_HOST: 192.168.1.89
|
||||
volumes:
|
||||
# So that Traefik can listen to the Docker events
|
||||
# Обязательный вольюм. Так траефик может слушать
|
||||
# события происходящие в демоне докера.
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# Вольюм с файлом конфигурации.
|
||||
- ./config.toml:/etc/traefik/dynamic/traefik.toml
|
||||
# Папка сертификатов.
|
||||
- ./certs:/etc/certs/
|
||||
|
||||
networks:
|
||||
# Сетка, внутри которой будут находится приложения.
|
||||
traefik-shared:
|
||||
name: traefik-shared
|
||||
|
||||
@ -132,16 +145,6 @@ networks:
|
||||
|
||||
Вот какие параметры я передаю в `traefik-cli`.
|
||||
|
||||
| Параметр | Что делает |
|
||||
|-------------------------------------------------|----------------------------------------------------------------------------------|
|
||||
| `providers.docker=true` | Включает прослушивание докера на новые события и следит за лейблами контейнеров. |
|
||||
| `providers.docker.exposedbydefault=false` | Отключает автоматическое создание роутеров ко всем контейнерам на сервере. |
|
||||
| `providers.docker.network=traefik-shared` | Сеть докера, по которой будет выполнятся подключение к контейнерам. |
|
||||
| `providers.file.directory=/etc/traefik/dynamic` | Папка с конфигурационным файлом. |
|
||||
| `providers.file.watch=true` | Включает отслеживание изменений файла конфигурации. |
|
||||
| `entrypoints.http.address=:80` | Создаёт entrypoint с названием http и слушает 80 порт. |
|
||||
| `entrypoints.https.address=:443` | Создаёт entrypoint с названием https и слушает 443 порт. |
|
||||
|
||||
Конечно, вы всегда можете глянуть `traefik --help` и подобрать себе желаемые параметры.
|
||||
|
||||
Также из docker-compose файла видно, что я создал докер сеть `traefik-shared`, которую в дальнейшем буду использовать на всех контейнерах, которым требуется свой домен.
|
||||
@ -153,16 +156,19 @@ networks:
|
||||
Для генерации локальных сертификатов я использую тулу [mkcert](https://github.com/FiloSottile/mkcert).
|
||||
|
||||
Для любого локального домена я делаю что-то типа:
|
||||
|
||||
```bash
|
||||
mkcert "*.local"
|
||||
mv _wildcard.local-key.pem local.key
|
||||
mv _wildcard.local.pem local.pem
|
||||
```
|
||||
|
||||
И помещаю это в папочку `certs` рядом с `docker-compose.yml`.
|
||||
|
||||
После того как я создал все нужные сертефикаты для всех доменов, их надо указать в файле `config.toml` в папочке traefik.
|
||||
|
||||
Вот пример:
|
||||
|
||||
```toml{}[config.toml]
|
||||
[tls.options]
|
||||
[tls.options.default]
|
||||
@ -173,7 +179,7 @@ mv _wildcard.local.pem local.pem
|
||||
[[tls.certificates]]
|
||||
# Я тут указываю /etc/certs, потому что в docker-compose
|
||||
# у нас volume на эту папку.
|
||||
certFile = "/etc/certs/local.pem"
|
||||
certFile = "/etc/certs/local.pem"
|
||||
keyFile = "/etc/certs/local.key"
|
||||
```
|
||||
|
||||
@ -183,16 +189,18 @@ mv _wildcard.local.pem local.pem
|
||||
|
||||
```toml
|
||||
[[tls.certificates]]
|
||||
certFile = "/etc/certs/<certFile>"
|
||||
certFile = "/etc/certs/<certFile>"
|
||||
keyFile = "/etc/certs/<keyFile>"
|
||||
```
|
||||
|
||||
После этого вы можете запускать traefik и наслаждаться доменами для ваших контейнеров.
|
||||
|
||||
# Запуск приложений
|
||||
|
||||
Теперь сконфигурируем приложение таким образом, чтобы к нему можно было обращаться через доменное имя.
|
||||
|
||||
Для примера возьмем мелкое приложение на nodejs со следующей структурой проекта:
|
||||
|
||||
```
|
||||
.
|
||||
├── docker-compose.yml
|
||||
@ -223,18 +231,18 @@ app.listen(PORT, () => {
|
||||
```
|
||||
|
||||
```json{}[package.json]
|
||||
{
|
||||
"name": "express-test",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"author": "s3rius",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"runserver": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.17.1"
|
||||
}
|
||||
{
|
||||
"name": "express-test",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"author": "s3rius",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"runserver": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.17.1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -260,10 +268,15 @@ services:
|
||||
server:
|
||||
build: .
|
||||
labels:
|
||||
# Включить поддержку роутинга через traefik.
|
||||
- traefik.enable=true
|
||||
# Поставить правило роутинга, если Host запроса равен test_node.local.
|
||||
- traefik.http.routers.test_node.rule=Host(`test_node.local`)
|
||||
# Слушать на entrypoint http (80 порт, это было объявлено в параметрах запуска traefik).
|
||||
- traefik.http.routers.test_node.entrypoints=http
|
||||
# Сервис, связанный с роутером test_node.
|
||||
- traefik.http.routers.test_node.service=node_test
|
||||
# Порт, куда направлять запросы в сервис node_test.
|
||||
- traefik.http.services.node_test.loadbalancer.server.port=3000
|
||||
command: runserver
|
||||
networks:
|
||||
@ -271,6 +284,7 @@ services:
|
||||
|
||||
|
||||
networks:
|
||||
# Докер сеть, в которой находится traefik.
|
||||
traefik-shared:
|
||||
name: traefik-shared
|
||||
external: true
|
||||
@ -282,14 +296,6 @@ networks:
|
||||
|
||||
Как вы видите, приложение слушает на порт `3000` и отвечает свим hostname и количеством обработанных запросов. А в docker-compose.yml, в отличие от обычного проекта, появились labels.
|
||||
|
||||
| Лейбл | Что делает |
|
||||
|---------------------------------------------------------------|---------------------------------------------------------------------------------------|
|
||||
| ``traefik.enable=true`` | Включить поддержку роутинга через traefik |
|
||||
| ``traefik.http.routers.<router>.rule=Host(`test_node.local`)`` | Поставить правило роутинга, если Host запроса равен test_node.local |
|
||||
| ``traefik.http.routers.<router>.entrypoints=http`` | Слушать на entrypoint http (80 порт, это было объявлено в параметрах запуска traefik) |
|
||||
| ``traefik.http.routers.<router>.service=<service>`` | Сервис, связанный с роутером test_node |
|
||||
| ``traefik.http.services.<service>.loadbalancer.server.port=3000`` | Порт, куда направлять запросы в сервис node_test |
|
||||
|
||||
<hr/>
|
||||
<b-message type="is-danger" has-icon>
|
||||
|
||||
@ -300,14 +306,15 @@ networks:
|
||||
|
||||
Обратите внимание на косые кавычки при указании хоста! Это обязательно.
|
||||
|
||||
В объявлении labels могут быть использованы переменные среды. Например:
|
||||
``traefik.http.routers.test_node.rule=Host(`${APP_HOST}`)``
|
||||
В объявлении labels могут быть использованы переменные среды. Например:
|
||||
`` traefik.http.routers.test_node.rule=Host(`${APP_HOST}`) ``
|
||||
|
||||
</b-message>
|
||||
|
||||
Также можно видеть, что я подключил контейнер к сети, которую мы указывали в контейнере traefik. Здесь она помечена как external.
|
||||
|
||||
Теперь мы можем спокойно запустить наш сервис. Для этого воспользуемся следующей командой:
|
||||
|
||||
```bash
|
||||
docker-compose up --build --scale server=3
|
||||
```
|
||||
@ -328,6 +335,7 @@ $ curl -H "Host: test_node.local" "http://localhost"
|
||||
Как вы видите, traefik балансирует между контейнерами за нас. И я считаю, что это - победа.
|
||||
|
||||
## Подключение TLS и сертификатов
|
||||
|
||||
Тут всё не намного сложнее. Давайте немного поменяем лейблы нашего контейнера.
|
||||
|
||||
```yaml
|
||||
@ -347,11 +355,12 @@ services:
|
||||
На данном этапе вам потребуется добавить свой хост в `/etc/hosts`, если вы используете нормальную систему. Но если вы всё же на windows, то вам потребуется добавить правило в `C:\Windows\System32\drivers\etc\hosts`.
|
||||
|
||||
И добавляем в конец файла запись:
|
||||
|
||||
```
|
||||
127.0.0.1 test_node.local
|
||||
```
|
||||
|
||||
И также вам потребуется cертификат на этот домен.
|
||||
И также вам потребуется cертификат на этот домен.
|
||||
Для этого:
|
||||
|
||||
1. Создадим сертификат через `mkcert`, как упоминалось ранее;
|
||||
@ -365,12 +374,12 @@ $ curl --insecure https://test_node.local
|
||||
{"request_num":0,"host":"7417ac8fda92"}
|
||||
```
|
||||
|
||||
|
||||
## Добавление локальных сервисов не из докера
|
||||
|
||||
Все те флаги, которые мы указываем в labels, вы также можете указать в файле конфигурации рядом с docker-compose.yml, указав конкретный ip адрес.
|
||||
|
||||
Например:
|
||||
|
||||
```toml
|
||||
[http.routers]
|
||||
# Define a connection between requests and services
|
||||
@ -387,9 +396,9 @@ $ curl --insecure https://test_node.local
|
||||
url = "http://192.168.1.89:8100"
|
||||
```
|
||||
|
||||
|
||||
# Создание локального DNS
|
||||
В данном пункте я бы хотел рассказать, как настроить свой DNS-сервер, чтобы ваши домены были доступны всем устройствам в локальной сети. Для этого я буду использовать dnsmasq. Пользователям винды он недоступен, поэтому советую развернуть маленький домашний сервер на линуксе.
|
||||
|
||||
В данном пункте я бы хотел рассказать, как настроить свой DNS-сервер, чтобы ваши домены были доступны всем устройствам в локальной сети. Для этого я буду использовать dnsmasq. Пользователям винды он недоступен, поэтому советую развернуть маленький домашний сервер на линуксе.
|
||||
|
||||
Для этого установите `dnsmasq` и найдите и раскомментируйте, либо добавьте следующие строчки в файл `/etc/dnsmasq.conf`:
|
||||
|
||||
@ -406,6 +415,7 @@ address=/.<other_domain>/<your_local_ip>
|
||||
Чтобы это узнать, посмотрите свой ip через роутер или выполните `ip addr`.
|
||||
|
||||
Вообще, `dnsmasq` парсит файл `/etc/hosts` и вы можете туда добавлять записи, типа:
|
||||
|
||||
```
|
||||
192.168.1.1 mydomain.local
|
||||
```
|
||||
@ -413,6 +423,7 @@ address=/.<other_domain>/<your_local_ip>
|
||||
Но так как я указал `address`, то это необязательно. `dnsmasq` и без явного указания поддоменов должен будет работать отлично.
|
||||
|
||||
Запустите `dnsmasq` в режиме сревиса:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable dnsmasq.service
|
||||
sudo systemctl start dnsmasq.service
|
||||
@ -445,7 +456,7 @@ services:
|
||||
- traefik.http.routers.traefik_router.entrypoints=https
|
||||
- traefik.http.routers.traefik_router.tls=true
|
||||
|
||||
command:
|
||||
command:
|
||||
- --api.dashboard=true
|
||||
...
|
||||
```
|
||||
@ -457,5 +468,4 @@ services:
|
||||
|
||||
[](/images/traefik_imgs/traefik_web.png)
|
||||
|
||||
|
||||
Разве это не круто?
|
||||
|
Reference in New Issue
Block a user