-
-
-
diff --git a/components/Footer.vue b/components/Footer.vue
deleted file mode 100644
index 746758f..0000000
--- a/components/Footer.vue
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
diff --git a/content/ru/docker-envs.md b/content/ru/docker-envs.md
deleted file mode 100644
index 3740a59..0000000
--- a/content/ru/docker-envs.md
+++ /dev/null
@@ -1,161 +0,0 @@
----
-title: Разделение докера на среды.
-description: Как работать с несколькими docker-compose.
-position: 2
-category: DevOps
----
-
-
-# Разделение докера на среды
-
-Должен ли ты разделять среды докера в несколько `docker-compose` файлов?
-Определенно! В некоторых случаях невозможно разобраться что разработчики хотели сделать или почему ничего не работает. Настройка раздельных сред может стать настояшей мешаниной. В этой статье я покажу как настроить `docker-compose` и избежать миллиона проблем.
-
-Как мы вообще можем разделить среды для локальной разработки и продовые?
-Отет прост: Требуется декомпозировать проект и создать отдельные файлы под каждую из сред или даже сервисов.
-
-Это нормально, если у тебя будет больше 2-х `docker-compose` файлов.
-
-Например:
-```
-deploy
-├── docker-compose.autotests.yml
-├── docker-compose.db.yml
-├── docker-compose.dev.yml
-├── docker-compose.yml
-├── dockerfiles
-│ ├── api.Dockerfile
-│ └── front.Dockerfile
-└── scripts
- ├── start-autotests.sh
- ├── start-backend.sh
- └── start-migrations.sh
-```
-
-### Как это работает?
-Докер умеет работать с множеством `docker-compose` файлов одновременно. И мы можем использовать это для разделения сред.
-
-Запуск таких конфигураций выглядит следующим образом:
-```bash
-$ docker-compose \
- -f "deploy/docker-compose.yml" \
- -f "deploy/docker-compose.db.yml" \
- -f "deploy/docker-compose.dev.yml" \
- up
-```
-
-В каждом из этих файлов определен какой-то кусок конфигурации, который не пересекается. Например в `docker-compose.yml` определено приложение и некоторые необходимые сервисы, а в остальных файлах добавляются сервисы или меняются значения предыдущих.
-
-Наверное, тут проще показать на примере.
-
-Допустим у нас есть проект, у которого поднимается бекенд с параметрами, которые отличаются на проде и локально.
-
-Для простоты примера создадим простецкий проект со следующей структурой:
-```
-proj
-├── deploy
-│ ├── docker-compose.yml
-│ └── dockerfiles
-│ └── script.Dockerfile
-└── script.py
-```
-
-Для начала напишем скрипт, который будет в центре всего.
-```python{}[script.pt]
-from sys import argv # это аргуметы переданные в скрипт
-
-def main():
- print("; ".join(argv[1:])) # выводит на экран все аргументы программы
-
-if __name__ == "__main__":
- main()
-```
-
-
-После того, как скрипт готов и отлажен давайте завернем его в докер,
-создав `Dockerfile`.
-
-```dockerfile{}[deploy/dockerfiles/script.Dockerfile]
-from python:3.8 # asd
-
-WORKDIR /app
-COPY script.py /app/script.py
-CMD python /app/script.py
-```
-
-Как вы видите, Dockerfile очень прост. Теперь добавим главный `docker-compose.yml`.
-
-```yaml{}[deploy/docker-compose.yml]
----
-version: '3.7'
-
-services:
- script:
- build: # Собираем приложение используя наш dockerfile.
- dockerfile: ./deploy/dockerfiles/script.Dockerfile
- context: .
- # Запускаем его с командой для продового запуска.
- command: python script.py this is prod
-```
-
-Теперь запустим всё это чудо.
-
-```bash
-$ docker-compose \
- -f "./deploy/docker-compose.yml" \
- --project-directory "." \
- run --rm script
-```
-
-Вот что будет выведено на экран.
-```log
-Creating d-test_script_run ... done
-this; is; prod
-```
-
-Как мы видим, на экран вывелось сообщение, которое мы указали в нашем `docker-compose.yml`
-
-А теперь для локальной разработки мы не будем ничего менять в нашем файле композиции, а создадим новый рядом.
-
-```yaml{}[deploy/docker-compose.dev.yml]
----
-version: '3.7'
-
-services:
- script:
- command: python script.py this is dev
-```
-
-Теперь добавим ещё один файл в нашу команду запуска.
-
-```bash
-$ docker-compose \
- -f "deploy/docker-compose.yml" \
- -f "deploy/docker-compose.dev.yml" \
- --project-directory "." \
- run --rm script
-```
-
-Вот что будет выведено на экран:
-```log
-Creating d-test\_script\_run ... done
-this; is; dev
-```
-
-Как можно заметить, конфигурация запуска перезаписалась в порядке вызова.
-
-То есть, каждый последующий файл композиции может добавлять сервисы и **частично** изменять конфигурацию предыдущих.
-
-Итоговая структура проекта:
-```
-proj
-├── deploy
-│ ├── docker-compose.dev.yml
-│ ├── docker-compose.yml
-│ └── dockerfiles
-│ └── script.Dockerfile
-└── script.py
-```
-
-### Где это применимо?
-Ну, в любом проекте, сложнее того, который мы рассмотрели. Потому что в реальной жизни не всё так радужно и локальная версия приложения может отличаться не только параметрами запуска, но и целыми сервисами, которые требуются для локальной копии приложения.
diff --git a/content/ru/makefiles.md b/content/ru/makefiles.md
deleted file mode 100644
index 0806700..0000000
--- a/content/ru/makefiles.md
+++ /dev/null
@@ -1,139 +0,0 @@
----
-title: Makefiles для чайников.
-description: Автоматизируем по старинке.
-position: 3
-category: 'DevOps'
----
-
-# Makefiles для чайников
-Что такое мейкфайлы? Вы наверняка их встречали,
-если собирали проекты из ихсодных файлов.
-Если описывать в двух словах, то это просто описание команд для упрощения работы
-с проектом. Изначально делались для того, чтобы удобно компилировать всякие проекты на любых языках.
-
-## Как они работают?
-Достаточно просто.
-
-Вот пример `Makefile`:
-
-```makefile{}[Makefile]
-# Сгенерируем простой файл
-test.gen:
- @echo 'echo "Hi!"' > "test.gen"
-
-# Команда для запуска файла test.gen
-.PHONY: run
-run: test.gen
- sh "test.gen"
-```
-
-До двоеточий обазначены команды (tagets). Например тут это: `test.gen` и `run`.
-Данные таргеты можно запускать через `make ${target}`.
-
-Например, если ввести `make run` в папке с `Makefile`, то мы получим следующее:
-```console
-$ sh "test.gen"
-Hi!
-```
-
-Как видно из выхлопа данной команды, у нас успешно запустился файл `test.gen`, хотя мы не запускали команду `make test.gen`. Что произошло? Давайте разбираться.
-
-## Зависимости таргетов
-
-На строчке объявления таргета `run` видно, что объявлен `test.gen`. Это зависимость данного таргета и она будет вызвана до того, как выполнится скрипт описываемого таргета. Таких зависимостей может быть много, перечисляются они через пробел.
-
-Например:
-```makefile{}[Makefile]
-.PHONY: target1
-target1:
- echo "1"
-
-.PHONY: target2
-target2: target1
- echo "2"
-
-.PHONY: target3
-target3: target1 target2
- echo "memes"
-```
-
-При вызове `make target3` будет выведено:
-```console
-$ make target3
-echo "1"
-1
-echo "2"
-2
-echo "memes"
-memes
-```
-
-Как можно видеть, он построил граф зависимостей и не выполнил `target1` дважды.
-
-## Сокрытие вывода команд
-
-В предыдущем примере можно заметить, что он написал все команды в терминал. Для того, чтобы этого избежать следует добавить "@" в начало команды и она не будет напечатана.
-
-```makefile{}[Makefile]
-.PHONY: target1
-target1:
- @echo "1"
-
-.PHONY: target2
-target2: target1
- @echo "2"
-
-.PHONY: target3
-target3: target1 target2
- @echo "memes"
-```
-
-Теперь при вызое `make target3` будет показано следующее:
-```cosole
-$ make target3
-1
-2
-memes
-```
-
-
-## Валидация сгенерированных файлов
-Зачастую `Makefile` используют для компиляции С и зачастую требуется
-собрать какую-либо часть проекта в файл и пропустить сборку этого файла, если он уже собран.
-Раскрою секрет, в Makefile это базовый функционал.
-
-Давайте немного поменяем первый Makefile и запустим дважды.
-```makefile{}[Makefile]
-# Сгенерируем простой файл
-test.gen:
- echo 'echo "Hi!"' > "test.gen"
-
-# Команда для запуска файла test.gen
-.PHONY: run
-run: test.gen
- sh "test.gen"
-```
-
-Теперь вызываемая команда таргета `test.gen` выводится на экран.
-
-Позапускаем.
-
-```console
-$ make run
-echo 'echo "Hi!"' > "test.gen" # Наша командабыла вызвана.
-sh "test.gen"
-Hi!
-$ make run
-sh "test.gen" # Наша команда не вызвана.
-Hi!
-```
-
-Дело в том, что названия таргетов - названия файлов, которые должны сгенерировать эти самые таргеты.
-То есть, в данном случае таргет `test.gen` должен сгенерировать файл `test.gen` по окончании выполнения. Если этот файл уже присутствует, то команда выполнена не будет. Именно поэтому у нас она не запустилась второй раз, так как в первый запуск был создан треубемый файл и его никто не удалял между запусками.
-
-А что если я вот не хочу чтобы команда создавала и проверяла файлы?
-Для этого пишут `.PHONY: ${target}`. Например у нас так объявлен таргет `run` и, даже если файл с названием `run` будет присутствовать в директории цель не будет выполняться.
-
-
-
-До новых встреч.
diff --git a/content/ru/project-start.md b/content/ru/project-start.md
deleted file mode 100644
index 43b3482..0000000
--- a/content/ru/project-start.md
+++ /dev/null
@@ -1,586 +0,0 @@
----
-title: Как стартовать проект
-description: Лучший вариант начала проекта на питоне.
-category: Python
-position: 1
----
-
-# Как стартовать проект
-
-В целом, вариантов несколько. Например, можно просто хранить несколько скриптов на github и быть довольным, можно дописать к ним `setup.py` и выложиться на pypi. Но лучший способ - структурированный проект с `poetry`.
-
-Почему?
-
-Особенностей у poetry много. Сейчас расскажу про то, как использовать и что делать.
-
-Для начала создадим проект.
-
-```console
-$ poetry new new_proj
-Created package new_proj in new_proj
-$ cd new_proj
-```
-
-Данная команда сгенерирует следующую структуру:
-```
-new_proj
-├── new_proj
-│ └── __init__.py
-├── pyproject.toml
-├── README.rst
-└── tests
- ├── __init__.py
- └── test_new_proj.py
-```
-
-Это наш новый проект с небольшими заготовками. В целом, тут нет ничего необычного. Весь код библиотеки/проекта будет в папке с названием проекта. `README.rst` - дефолтный README. `pyproject.toml` - мета-данные о проекте, такие как: зависимости, описание, дополнительные установочные опции и многое другое. Ты можешь прочитать побольше о `pyproject.toml` [тут](https://python-poetry.org/docs/pyproject/).
-
-## Как работать с poetry
-
-Вся информация есть в [официальной документации](https://python-poetry.org/docs) poetry. Тут я расскажу про основные моменты.
-
-### Управление зависимостями
-
-Чтобы добавить зависимость проекта достаточно выполнить
-```console
-$ poetry add ${dependency}
-```
-
-Данная команда найдет последнюю нужную версию и запишет её в `pyproject.toml` и в `poetry.lock`.
-
-Для того, чтобы установить зависимость для разработки (линтеры например), достаточно добавить флаг `--dev`.
-
-```console
-$ poetry add ${dependency} --dev
-```
-
-Чтобы удалить зависимость достаточно просто `add` заменить на `remove`.
-
-Примеры работы:
-```console
-$ poetry add loguru
-Using version ^0.5.3 for loguru
-
-Updating dependencies
-Resolving dependencies... (0.1s)
-
-Writing lock file
-
-Package operations: 1 install, 0 updates, 0 removals
- • Installing loguru (0.5.3)
-$ poetry remove loguru
-Updating dependencies
-Resolving dependencies... (0.1s)
-
-Writing lock file
-
-Package operations: 0 installs, 0 updates, 1 removal
- • Removing loguru (0.5.3)
-```
-### Выполнение команд
-
-При использовании virtualenv для входа в оболочку всегда надо было вводить
-```bash
-source venv/bin/activate
-```
-
-Однако с poetry это излишне. Он сам создает и менеджит виртуальные среды.
-Чтобы выполнить одиночную команду можно вызвать `run`.
-
-Например:
-```console
-$ poetry install black --dev
-Using version ^20.8b1 for black
- • Installing appdirs (1.4.4)
- • Installing click (7.1.2)
- • Installing mypy-extensions (0.4.3)
- • Installing pathspec (0.8.1)
- • Installing regex (2020.11.13)
- • Installing toml (0.10.2)
- • Installing typed-ast (1.4.2)
- • Installing typing-extensions (3.7.4.3)
- • Installing black (20.8b1)
-$ poetry run black .
-reformatted new_proj/new_proj/__init__.py
-reformatted new_proj/tests/test_new_proj.py
-All done! ✨ 🍰 ✨
-2 files reformatted, 1 file left unchanged.
-```
-
-Для выполнения нескольких комманд подряд, можно войти в shell. Это аналог `source venv/bin/activate`.
-
-```console
-$ poetry shell
-Spawning shell within /home/s3rius/.cache/pypoetry/virtualenvs/new-proj-eutP4v0O-py3.9
-$ black .
-reformatted new_proj/new_proj/__init__.py
-reformatted new_proj/tests/test_new_proj.py
-All done! ✨ 🍰 ✨
-2 files reformatted, 1 file left unchanged.
-```
-
-### Версионирование
-
-Менять версии пакета вручную больше не нужно.
-
-У poetry есть кое что для тебя.
-```console
-$ poetry version patch
-Bumping version from 0.1.0 to 0.1.1
-$ poetry version preminor
-Bumping version from 0.1.1 to 0.2.0-alpha.0
-$ poetry version minor
-Bumping version from 0.2.0-alpha.0 to 0.2.0
-$ poetry version premajor
-Bumping version from 0.2.0 to 1.0.0-alpha.0
-$ poetry version major
-Bumping version from 1.0.0-alpha.0 to 1.0.0
-```
-
-## pyproject.toml
-
-Как было сказано ранее, данный файл содержит в себе мета-информацию пакета.
-
-Пример `pyproject.toml`
-```toml{}[pyproject.toml]
-[tool.poetry]
-name = "new_proj"
-version = "0.1.0"
-description = "Test library for example"
-readme = "README.rst"
-homepage = "https://test_url.com/"
-repository = "https://github.meme/"
-authors = ["Pavel Kirilin "]
-
-[tool.poetry.dependencies]
-python = "^3.8"
-
-[tool.poetry.dev-dependencies]
-pytest = "^6.1"
-
-[build-system]
-requires = ["poetry-core>=1.0.0"]
-build-backend = "poetry.core.masonry.api"
-```
-
-Для того чтобы устновить все зависимости требуется просто ввести
-```console
-$ poetry install
-```
-
-Данная команда создаст виртуальную среду сама и установит **все** зависимости. Включая dev-зависимости. Чтобы установить зависимости только для приложения можно добавить `--no-dev` ключ.
-
-## Как паковать и публиковать на pypi
-Всё очень просто.
-Давайте добавим какую-нибудь функцию в проект.
-
-```python{}[new_proj/main.py]
-def ab_problem(a: int, b: int) -> int:
- return a + b
-```
-
-```python{}[new_proj/__init.py]
-from new_proj.main import ab_problem
-
-__all__ = [
- 'ab_problem'
-]
-```
-
-Теперь соберем проект.
-
-```console
-$ poetry build
-Building new_proj (0.1.0)
- - Building sdist
- - Built new_proj-0.1.0.tar.gz
- - Building wheel
- - Built new_proj-0.1.0-py3-none-any.whl
-```
-
-Та-да. Это готовый к публикации на pypi пакет. Лежит он в папке dist.
-
-Давайте проверим, что всё работает корректно.
-
-```console
-$ pip install "./dist/new_proj-0.1.0-py3-none-any.whl"
-Processing ./dist/new_proj-0.1.0-py3-none-any.whl
-Installing collected packages: new-proj
-Successfully installed new-proj-0.1.0
-
-$ python
-Python 3.9.1 (default, Feb 1 2021, 04:02:33)
-[GCC 10.2.0] on linux
-Type "help", "copyright", "credits" or "license" for more information.
-$ from new_proj import ab_problem
-$ ab_problem(1,33)
-34
-```
-
-Как можно видеть всё работает корректно и теперь мы можем использовать наш пакет.
-
-Для публикации следует использовать:
-```console
-$ poetry publish -u "user" -p "password"
-```
-
-Подробнее можно почитать [тут](https://python-poetry.org/docs/cli/#publish).
-
-# Конфигурация проекта
-Конечно, такого рода конфигурация проекта всё равно никуда не годиться.
-Давайте настроим автоматический линтинг.
-
-```console
-$ poetry add \
- flake8 \
- black \
- isort \
- mypy \
- pre-commit \
- yesqa \
- autoflake \
- wemake-python-styleguide --dev
-```
-
-Теперь добавим конфигурационных файлов в корень проекта.
-Это мои конфигурации, которые я настроил под себя, можешь менять их как хочешь.
-
-
-Для конфигурации сортировки импортов и проверки типов добавим следющее
-в наш основной файл проекта.
-
-Обычно я добавляю эти секции сразу после секции `[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
-
-...
-```
-
-`.flake8` - конфигурация линтинга. Тут довольно много. Это игнорирование ненужных кодов ошибок, которые не особо-то и ошибки.
-```ini{}[.flake8]
-[flake8]
-max-complexity = 6
-inline-quotes = double
-max-line-length = 88
-extend-ignore = E203
-docstring_style=sphinx
-
-ignore =
- ; Found `f` string
- WPS305,
- ; Missing docstring in public module
- D100,
- ; Missing docstring in magic method
- D105,
- ; Missing docstring in __init__
- D107,
- ; Found class without a base class
- WPS306,
- ; Missing docstring in public nested class
- D106,
- ; First line should be in imperative mood
- D401,
- ; Found `__init__.py` module with logic
- WPS412,
- ; Found implicit string concatenation
- WPS326,
- ; Found string constant over-use
- WPS226,
- ; Found upper-case constant in a class
- WPS115,
- ; Found nested function
- WPS430,
- ; Found using `@staticmethod`
- WPS602,
- ; Found method without arguments
- WPS605,
- ; Found overused expression
- WPS204,
- ; Found too many module members
- WPS202,
- ; Found too high module cognitive complexity
- WPS232,
- ; line break before binary operator
- W503,
- ; Found module with too many imports
- WPS201,
- ; Found vague import that may cause confusion: X
- WPS347,
- ; Inline strong start-string without end-string.
- RST210,
- ; subprocess call with shell=True seems safe, but may be changed in the future.
- S602,
- ; Starting a process with a partial executable path.
- S607,
- ; Consider possible security implications associated with subprocess module.
- S404,
- ; Found nested class
- WPS431,
- ; Found wrong module name
- WPS100,
- ; Found too many methods
- WPS214,
- ; Found too long ``try`` body
- WPS229,
- ; Found function with too much cognitive complexity
- WPS231,
-
- ; all init files
- __init__.py:
- ; ignore not used imports
- F401,
- ; ignore import with wildcard
- F403,
- ; Found wrong metadata variable
- WPS410,
-
-per-file-ignores =
- ; all tests
- test_*.py,tests.py,tests_*.py,*/tests/*:
- ; Use of assert detected
- S101,
-
-exclude =
- ./.git,
- ./venv,
- ./cached_venv,
- ./var,
-```
-
-`.pre-commit-config.yaml` - конфигураци хуков для запуска всех линтеров перед коммитом.
-```yaml{}[.pre-commit-config.yaml]
-# See https://pre-commit.com for more information
-# See https://pre-commit.com/hooks.html for more hooks
-repos:
- - repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v2.4.0
- hooks:
- - id: check-ast
- - id: trailing-whitespace
- - id: check-toml
- - id: end-of-file-fixer
-
- - repo: https://github.com/asottile/add-trailing-comma
- rev: v2.1.0
- hooks:
- - id: add-trailing-comma
-
- - repo: local
- hooks:
- - id: black
- name: Format with Black
- entry: black
- language: system
- types: [python]
-
- - id: autoflake
- name: autoflake
- entry: autoflake
- language: system
- types: [ python ]
- args: [ --in-place, --remove-all-unused-imports, --remove-duplicate-keys ]
-
- - id: isort
- name: isort
- entry: isort
- language: system
- types: [ python ]
-
- - id: flake8
- name: Check with Flake8
- entry: flake8
- language: system
- pass_filenames: false
- types: [ python ]
- args: [--count, .]
-
- - id: mypy
- name: Validate types with MyPy
- entry: mypy
- language: system
- types: [ python ]
-
- - id: yesqa
- name: Remove usless noqa
- entry: yesqa
- language: system
- types: [ python ]
-```
-
-И не забываем про `.gitignore`. Его можно найти [тут](https://github.com/github/gitignore/blob/master/Python.gitignore).
-
-ОН НЕОБХОДИМ ЧТОБЫ pre-commit РАБОТЛ МАКСИМАЛЬНО КОРРЕКТНО.
-
-Теперь установим хуки в репозиторий.
-```console
-$ git init
-Initialized empty Git repository in .git/
-$ poetry shell
-$ pre-commit install
-pre-commit installed at .git/hooks/pre-commit
-$ git commit
-... # Упадет с кучей ошибок
-```
-
-Теперь поправим все возникшие проблемы.
-
-Во первых исправим тесты.
-
-В `tests/test_new_proj.py` напишем следующее:
-```python{}[tests/test_new_proj.py]
-from new_proj import ab_problem
-
-
-def test_ab() -> None:
- """AB problecm success case."""
- assert ab_problem(1, 2) == 3
-```
-
-Добавим описания в `__init__` файлы.
-
-```python{}[tests/__init__.py]
-"""Tests for new_proj."""
-```
-
-```python{}[new_proj/__init__.py]
-"""Project for solving ab problem."""
-from new_proj.main import ab_problem
-
-__all__ = [
- "ab_problem",
-]
-```
-
-Пофиксим основной файл проекта.
-
-```python{}[new_proj/main.py]
-def ab_problem(first: int, second: int) -> int:
- """
- Solve AB problem.
-
- The function sums two integers.
-
- :param first: a argument.
- :param second: b argument.
- :returns: sum.
- """
- return first + second
-```
-
-Теперь вы можете сделать свой первый коммит.
-
-```console
-$ git commit
-Check python ast................Passed
-Trim Trailing Whitespace........Passed
-Check Toml......................Passed
-Fix End of Files................Passed
-Add trailing commas.............Passed
-Format with Black...............Passed
-autoflake.......................Passed
-isort...........................Passed
-Check with Flake8...............Passed
-Validate types with MyPy........Passed
-Remove usless noqa..............Passed
-pytest..........................Passed
-```
-
-Теперь ты знаешь как создавать шедевры.
-# Создание CLI-приложения
-А что если я хочу cli-приложение?
-Ты не представляешь насколько это просто.
-
-Пойдем модифицируем наш основной файл.
-
-```python{}[new_proj/main.py]
-import argparse
-
-
-def parse_args() -> argparse.Namespace:
- """
- Parse CLI arguments.
-
- :returns: parsed namespace.
- """
- parser = argparse.ArgumentParser()
-
- parser.add_argument("a", type=int)
- parser.add_argument("b", type=int)
-
- return parser.parse_args()
-
-
-def ab_problem(first: int, second: int) -> int:
- """
- Solve AB problem.
-
- The function sums two integers.
-
- :param first: a argument.
- :param second: b argument.
- :returns: sum.
- """
- return first + second
-
-
-def main() -> None:
- """Main function."""
- args = parse_args()
- print(ab_problem(args.a, args.b)) # noqa: WPS421
-
-```
-
-Теперь поправим pyproject.toml таким образом чтобы он создал cli для нашей функции.
-
-Добавим следующую секцию куда-нибудь в `pyproject.toml`:
-```toml
-[tool.poetry.scripts]
-ab_solver = "new_proj.main:main"
-```
-
-Теперь нам доступна программа `ab_solver` внутри shell.
-
-```console
-$ poetry install
-$ poetry shell
-$ ab_solver 1 2
-3
-```
-
-Хочешь установить? Пожалуйста.
-```console
-$ poetry build
-Building new_proj (0.1.0)
- - Building sdist
- - Built new_proj-0.1.0.tar.gz
- - Building wheel
- - Built new_proj-0.1.0-py3-none-any.whl
-$ pip install "./dist/new_proj-0.1.0-py3-none-any.whl"
-Processing ./dist/new_proj-0.1.0-py3-none-any.whl
-Installing collected packages: new-proj
-Successfully installed new-proj-0.1.0
-$ ab_solver 1 2
-3
-```
-
-Если запаблишить проект, то у пользователя тоже установится ваша cli-программа.
-
-А на этом всё.
-
-До новых встреч.
diff --git a/content/ru/python-speedup-with-rust.md b/content/ru/python-speedup-with-rust.md
deleted file mode 100644
index 7c33532..0000000
--- a/content/ru/python-speedup-with-rust.md
+++ /dev/null
@@ -1,479 +0,0 @@
----
-title: Ускоряем Python используя Rust.
-description: Как встроить Rust в проект на Python.
-position: 2
-category: 'Python'
----
-
-# Описание проблемы
-
-Каким бы Python удобным не был, скоростью он похвастаться никак не может.
-И для некоторых задач это достаточно критично.
-
-Например, не так давно у меня была задача достать много информации
-из файлов логов. Проблема в том, что один лог-файл занимает от 3-4 ГБ. А файлов
-таких много и информацию достать требуется быстро. Изначальный вариант на Python
-был написан за минут 15-20, но скорость его работы занимала 30-40 минут на один файл. После переписывания одной функции на Rust, скрипт отработал за 1 минуту.
-
-Давайте напишем свой проект со встроенной функцией на Rust.
-
-
-
-Задача проекта будет следующей:
-
-Дан файл лога запросов на некоторый сервер. Требуется
-найти количество запросов к определённому файлу и сумму переданных байт.
-
-Для демонстрации мы напишем 2 функции, одна будет использовать наивное решение задачи на питоне,
-вторая будет выполнять парсинг на расте.
-
-Сам файл лог имеет следующую структуру:
-```
-"$line_num" "-" "$method $url" "$current_time" "$bytes_sent"
-```
-
-где,
-* $line_num - номер строчки;
-* $method - HTTP метод;
-* $url - URL до файла, содержащий один из сгенерированных идентификаторов;
-* $current_time - текущее время (время выполнения запроса);
-* $bytes_sent - сколько байт было отправлено.
-
-Данный формат логов отчасти подражает формату [G-Core labs](https://gcorelabs.com/support/articles/115000511685/).
-
-# Проект на Python
-
-Сначала мы напишем весь функционал на Python.
-Для этого создадим проект `python-rust` по [гайду из блога](/project-start).
-
-Для создания CLI я буду использовать [typer](https://pypi.org/project/typer/).
-
-Весь код проекта доступен в [репозитории](https://github.com/s3rius/blog_examples/tree/master/python-rust).
-
-```bash
-$ poetry add typer
-```
-
-## Генератор логов
-
-Для тестов напишем генератор лог-файла.
-
-Данный генератор должен работать следующим образом.
-Я указываю сколько строк лог-файла я хочу увидеть,
-сколько уникальных id файлов использовать и название файла, куда писать.
-
-Функция же генерирует лог в заданном формате.
-
-```python{}[python_rust/main.py]
-import secrets
-import time
-import uuid
-from pathlib import Path
-from typing import Any
-
-import typer
-
-tpr = typer.Typer()
-
-
-def quote(somthing: Any) -> str:
- """
- Quote string.
-
- :param somthing: any string.
- :return: quoted string.
- """
- return f'"{somthing}"'
-
-
-@tpr.command()
-def generator( # noqa: WPS210
- output: Path,
- lines: int = 2_000_000, # noqa: WPS303
- ids: int = 1000,
-) -> None:
- """
- Test log generator.
-
- :param ids: how many file id's to generate.
- :param output: output file path.
- :param lines: how many lines to write, defaults to 2_000_000
- """
- ids_pool = [uuid.uuid4().hex for _ in range(ids)]
-
- with open(output, "w") as out_file:
- for line_num in range(lines):
- item_id = secrets.choice(ids_pool)
- prefix = secrets.token_hex(60)
- url = f"GET /{prefix}/{item_id}.jpg"
- current_time = int(time.time())
- bytes_sent = secrets.randbelow(800) # noqa: WPS432
- line = [
- quote(line_num),
- quote("-"),
- quote(url),
- quote(current_time),
- quote(bytes_sent),
- ]
- out_file.write(" ".join(line))
- out_file.write("\n")
- typer.secho("Log successfully generated.", fg=typer.colors.GREEN)
-
-
-@tpr.command()
-def parser(input_file: Path, rust: bool = False) -> None:
- """
- Parse given log file.
-
- :param input_file: path of input file.
- :param rust: use rust parser implementation.
- """
- typer.secho("Not implemented", err=True, fg=typer.colors.RED)
-
-
-def main() -> None:
- """Main program entrypoint."""
- tpr()
-
-```
-
-Для удобства работы добавим в pyproject.toml информацию о командах.
-
-```toml
-[tool.poetry.scripts]
-pyrust = "python_rust.main:main"
-```
-
-Теперь мы можем вызывать нашу программу.
-
-```
-$ pyrust --help
-Usage: pyrust [OPTIONS] COMMAND [ARGS]...
-
-Options:
- --install-completion [bash|zsh|fish|powershell|pwsh]
- Install completion for the specified shell.
- --show-completion [bash|zsh|fish|powershell|pwsh]
- Show completion for the specified shell, to
- copy it or customize the installation.
- --help Show this message and exit.
-
-Commands:
- generator Test log generator.
- parser Parse given log file.
-```
-
-Работает отлично.
-
-С помощью данного генератора сгенерируем файл на 8 милионов строк.
-
-```bash
-$ pyrust generator test.log --lines 8000000
-Log successfully generated.
-```
-
-У нас получился файл на 1.5G. Для начального теста этого будет достаточно.
-
-## Реализация парсера на Python
-
-Представленный формат разделяет ифнормацию кавычками, поэтому
-для сплита строк мы будем использовать встроенный в python модуль [shlex](https://docs.python.org/3/library/shlex.html).
-
-Функция парсинга логов будет выглядить следующим образом:
-
-
-```python{}[python_rust/py_parser.py]
-import shlex
-from pathlib import Path
-from typing import Dict, Tuple
-
-
-def parse_python( # noqa: WPS210
- filename: Path,
-) -> Dict[str, Tuple[int, int]]:
- """
- Parse log file with python.
-
- :param filename: log file.
- :return: parsed data.
- """
- parsed_data = {}
- with open(filename, "r") as input_file:
- for line in input_file:
- spl = shlex.split(line)
- # Splitting method and actual url.
- url = spl[2].split()[1]
- # Splitting url by /
- # This split will turn this "/test/aaa.png"
- # into "aaa".
- file_id = url.split("/")[-1].split(".")[0]
- file_info = parsed_data.get(file_id)
- # If information about file isn't found.
- if file_info is None:
- downloads, bytes_sent = 0, 0
- else:
- downloads, bytes_sent = file_info
- # Incrementing counters.
- downloads += 1
- bytes_sent += int(spl[4])
- # Saving back.
- parsed_data[file_id] = (downloads, bytes_sent)
- return parsed_data
-
-```
-
-
-Давайте импортируем её, модифицируем команду парсинга и замерим скорость работы.
-
-
-```python{}[python_rust/main.py]
-@tpr.command()
-def parser(input_file: Path, rust: bool = False) -> None:
- """
- Parse given log file.
-
- :param input_file: path of input file.
- :param rust: use rust parser implementation.
- """
- if rust:
- typer.secho("Not implemented", input_file, color=typer.colors.RED)
- return
- else:
- parsed_data = parse_python(input_file)
-
- typer.secho(
- f"Found {len(parsed_data)} files", # noqa: WPS237
- color=typer.colors.CYAN,
- )
-
-```
-
-Как можно видеть из кода, мы просто подсчитываем итоговое количество найденных файлов.
-
-Посмотрим сколько времени займёт парсинг нашего сгенерированного файла.
-
-```bash
-$ time pyrust parser "test.log"
-Found 1000 files
-pyrust parser test.log 2443.42s user 2.10s system 99% cpu 40:59.30 total
-```
-
-Обработка данного файла заняла 40 минут. Это довольно печальный результат.
-Самое время попробовать использовать Rust.
-
-# Интеграция Rust
-
-Для интеграции Rust в python код я буду использовать [PyO3](https://github.com/PyO3/pyo3) для биндингов и [maturin](https://github.com/PyO3/maturin) для сборки проекта.
-
-Начнём с инициализации проекта и установки сборщика. В корневой папке проекта
-создайте папку проекта с растом.
-
-
-
-В теории можно использовать maturin как основной инструмент сборки проекта,
-но это лишает вас всех прелестей poetry. Поэтому удобнее использовать Rust в
-подпроекте и указать его как зависимость в списке зависимостей своего проекта.
-
-
-
-```bash
-$ cargo new --lib rusty_log_parser
-```
-
-Теперь создадим `pyproject.toml` и напишем туда описание нашего python пакета.
-
-```toml{}[pyproject.toml]
-[tool.poetry]
-name = "rusty_log_parser"
-version = "0.1.0"
-description = "Log file parser with Rust core"
-authors = ["Pavel Kirilin "]
-
-[build-system]
-requires = ["maturin>=0.12,<0.13"]
-build-backend = "maturin"
-
-```
-
-Также поправим `Cargo.toml`.
-
-```toml{}[Cargo.toml]
-[lib]
-# Название модуля
-name = "rusty_log_parser"
-# Обязательный тип крейта, чтобы можно было использовать его
-# в питоне.
-crate-type = ["cdylib"]
-
-[dependencies]
-# Библиотека биндингов для питона.
-pyo3 = { version = "0.15.1", features = ["extension-module"] }
-# Библиотека для сплита. Повторяет функционал shlex.
-shell-words = "1.0.0"
-```
-После этих нехитрых изменений попробуем собрать проект.
-
-
-```shell
-$ cd rusty_log_parser
-$ cargo build
-```
-
-Теперь напишем сам парсер логов в Rust.
-
-```rust{}[rusty_log_parser/src/lib.rs]
-use std::collections::HashMap;
-use std::fs::File;
-use std::io;
-use std::io::BufRead;
-
-use pyo3::prelude::*;
-
-/// Parse log file in rust.
-/// This function parses log file and returns HashMap with Strings as keys and
-/// Tuple of two u128 as values.
-#[pyfunction]
-fn parse_rust(filename: &str) -> PyResult> {
- let mut result_map = HashMap::new();
- // Iterating over file.
- for log in io::BufReader::new(File::open(filename)?).lines().flatten() {
- // Splitting log string.
- if let Ok(mut spl) = shell_words::split(&log) {
- // Getting file id.
- let file_id_opt = spl.get_mut(2).and_then(|http_req| {
- // Splitting method and URL.
- http_req.split(' ').into_iter().nth(1).and_then(|url| {
- // Splitting by / and getting the last split.
- url.split('/')
- .into_iter()
- .last()
- // Split file id and extension.
- .and_then(|item_url| item_url.split('.').into_iter().next())
- // Turning &str into String.
- .map(String::from)
- })
- });
- // Getting number of bytes sent.
- let bytes_sent_opt =
- spl.get_mut(4)
- // Parsing string to u128
- .and_then(|bytes_str| match bytes_str.parse::() {
- Ok(bytes_sent) => Some(bytes_sent),
- Err(_) => None,
- });
- if file_id_opt.is_none() || bytes_sent_opt.is_none() {
- continue;
- }
- let file_id = file_id_opt.unwrap();
- let bytes_sent = bytes_sent_opt.unwrap();
-
- match result_map.get(&file_id) {
- Some(&(downloads, total_bytes_sent)) => {
- result_map.insert(file_id, (downloads + 1, total_bytes_sent + bytes_sent));
- }
- None => {
- result_map.insert(file_id, (1, bytes_sent));
- }
- }
- }
- }
- Ok(result_map)
-}
-
-/// A Python module implemented in Rust. The name of this function must match
-/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
-/// import the module.
-#[pymodule]
-fn rusty_log_parser(_py: Python, m: &PyModule) -> PyResult<()> {
- // Adding function to the module.
- m.add_function(wrap_pyfunction!(parse_rust, m)?)?;
- Ok(())
-}
-```
-
-Это и будет наша функция, которую мы будем вызывать.
-Теперь добавим `python` обёртку для нашего модуля.
-
-Для этого в проекте `rusty_log_parser` надо создать
-папку с тем же названием что и проект. В данном случае
-это будет `rusty_log_parser`.
-
-Внутри этой папки мы создадим `__init__.py`, в котором
-опишем и задокументируем доступные функции модуля.
-
-```python{}[rusty_log_parser/rusty_log_parser/__init__.py]
-from pathlib import Path
-from typing import Dict, Tuple
-
-from .rusty_log_parser import parse_rust as _parse_rust
-
-
-def parse_rust(input_file: Path) -> Dict[str, Tuple[int, int]]:
- """
- Parse log file using Rust as a backend.
-
- :param input_file: log file to parse.
- :return: Parsed
- """
- return _parse_rust(str(input_file.expanduser()))
-
-```
-
-Осталось только добавить свеженаписанный пакет как зависимости нашего проекта.
-
-Для этого в корневой `pyproject.toml` надо добавить следующую зависимость.
-
-
-```toml{}[pyproject.toml]
-[tool.poetry.dependencies]
-...
-rusty_log_parser = { path = "./rusty_log_parser" }
-```
-
-Теперь мы можем использовать нашу функцию.
-
-Перепишем нашу функцию парсера таким образом,
-чтобы она использовала rust, когда был передан
-соответствующий параметр.
-
-```python{}[python_rust/main.py]
-from rusty_log_parser import parse_rust
-
-...
-
-@tpr.command()
-def parser(input_file: Path, rust: bool = False) -> None:
- """
- Parse given log file.
-
- :param input_file: path of input file.
- :param rust: use rust parser implementation.
- """
- if rust:
- parsed_data = parse_rust(input_file)
- else:
- parsed_data = parse_python(input_file)
-
- typer.secho(
- f"Found {len(parsed_data)} files", # noqa: WPS237
- color=typer.colors.CYAN,
- )
-
-```
-
-Теперь проведём итоговый замер.
-
-```bash
-$ time pyrust parser test.log --rust
-Found 1000 files
-pyrust parser test.log --rust 20.44s user 0.35s system 99% cpu 20.867 total
-```
-
-Итого виден небольшой выйигрыш во времени в 2423 секунды, из чего следует,
-что реализация на Rust быстрее всего в 122 раза.
-
-Данной статьёй я не подталкиваю всех переписывать всё на Rust,
-а просто ознакамливаю с возможностями. Но если всё же хочется,
-то можно и попробовать.
-
-До новых встреч.
diff --git a/content/ru/start-with-k8s.md b/content/ru/start-with-k8s.md
deleted file mode 100644
index 95f9add..0000000
--- a/content/ru/start-with-k8s.md
+++ /dev/null
@@ -1,755 +0,0 @@
----
-title: Начало работы с kubernetes
-description: Как там это всё разворачивать в двух словах.
-category: DevOps
-position: 4
----
-
-# Проблема в изучении кубернетес
-
-Многие люди, кто задавались вопросом "Как начать работать с
-кубернетес?", сталкивались с тем, что документация крайне
-большая сложная и нет нормального описания как
-завернуть маленький проект из одного контейнра в свой кластер или
-как развернуть сам кластер без боли.
-
-А всё потому что вся документация нацелена на большие
-production-ready системы с большим rps и тому подобным.
-
-В данной статье я попробую исправить это вселенское
-недопонимание используя k3s, свой комплюктер и немного знаний по кодингу.
-
-
-
-# Что такое кубернетес и почему это лучше докера
-
-Многие ребята, кто хорошо знаком с докером и его
-возможностями, могут задаваться таким вопросом.
-Для тех кто в танке, напомню, что докер
-имеет вариант запуска в режиме кластера.
-Этот функционал называется [docker swarm](https://docs.docker.com/engine/swarm/).
-В целом, swarm отдалённо напоминает kubernetes,
-так как в этом режиме докер худо-бедно умеет
-автоскейлится и запускаться в кластере,
-но это всё равно немного не то.
-
-
- Также замечу, что кубер активно развивается и поддерживается.
- огромным количество компаний. А вот docker swarm уже по-немногу
- умирает и документация по нему не то чтобы супер хороша.
-
-
-По большей части кубернетес это система,
-которая будет управлять вашими приложениями,
-следить за их состоянием и помогать вам в их
-конфигурации. Кубер умеет очень много, поэтому
-все интересные способности я в этой статье не смогу осветить,
-но самую базу попробую рассказать.
-
-# Из чего состоит кубернетес
-
-Так как я в этой статье хотел затронуть
-совсем базовые и практические вещи, то рассматривать
-мы будем только самые часто используемые компоненты.
-
-- Container
-- Pod
-- Deployment
-- Service
-- Ingress
-- Namespace
-- Secret
-- ConfigMap
-
-А теперь рассмотрим каждый ресурс немного поподробнее.
-
-## Container
-
-Контейнеры не являются чем-то специфичным для кубернетес.
-С контейнерами вы можете быть знакомы из кучи других систем.
-В контексте кубера они не обладают никакими дополнительными
-свойствами. Это ровно то же, что и контейнеры `containerd`
-или те, с которыми вы возились с докером. Ничего нового.
-
-Собираются контейнеры для кубернетеса ровно тем же образом,
-что и для докера.
-
-
-
- Важная ремарка. Кубер, начиная с 2021 года, не поддерживает
- докер как бэкенд. Теперь он будет общаться с
- containerd напрямую. Это значит, что перед использованием
- контейнеров собранных на локальной машине надо будет импортировать их
- в `containerd` используя `ctr image import`.
-
-Как импортировать образы почитать можно в [этой статье](https://cwienczek.com/2020/06/import-images-to-k3s-without-docker-registry/).
-
-
-
-## Pod
-
-
-
-
-
-Поды - это логически связанные группы контейнеров.
-Это самая базовая еденица кубернетеса.
-
-В поде находится от одного до множества контейнеров.
-Интересная особенность пода в том, что все контейнеры
-делят один сетевой адрес. Другими словами,
-если у вас один из контейнеров открыл порт `3000`,
-то другие контейнеры из пода этот порт использовать не смогут.
-
-То есть, если вы хотите логически связанные приложения
-поместить в под, то они могут ходить друг к другу через лупбек
-адреса. Такие как `localhost` или `127.0.0.1`.
-
-## Deployment
-
-Поды это круто, но есть одно но. Данные объекты неизменяемые
-и сам под скейлить вручную занятие сомнительное.
-
-Конечно, никто не мешает создать вам вручную [ReplicaSet](https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/)
-и самому крутить там нужные значения. Но в среднем
-вам с этим возиться не очень то хочется.
-
-
-
-Deployment нужен именно для описания подов и создания
-некоторых ресурсов, нужных для скейлинга.
-Также с помощью деплойментов можно делать откаты приложения через
-механиз роллбеков. Я это рассматривать не буду. В этой статье только база.
-
-## Service
-
-Сервис - это ресурс, с помощью которого поды могут общаться
-между собой. По факту сервис описывает, какие порты пода
-открыты и перенаправляет трафик на них.
-
-Представьте себе под, который просто проксирует весь трафик с одного
-порта на какой-нибудь порт пода. Это и есть Service.
-
-
-
-Выглядит сервис примерно так как показано на кртинке выше.
-Он направляет весь входной трафик с указанного порта на
-порты подов.
-
-Также сервис выступает как балансировщик.
-
-
- Если вы хотите сделать запрос от одного пода до дргого, внутри кластера, то
- вам придётся использовать сервис.
-
- Если вы попробуете сделать запрос напрямую по IP пода, то у вас, конечно же,
- получится, но это довольно странная идея из-за того, что при пересоздании
- пода у него может обновится IP внутри кластера. Поэтому лучше использовать сервисы. Также они потребуются для ингресса.
-
-
-## Ingress
-
-Ингресс - это сервис описывающий куда пускать трафик,
-который поступает снаружи кластера.
-
-
-
-Принцип работы ингресса следующий:
-
-Вы указываете хост ингресса и различные пути.
-В зависимости от входящего запроса ингресс выбирает в какой сервис
-направить запрос и в какой порт.
-
-Настройка ингресса достаточно гибкая и я не думаю,
-что вы можете столкнуться с какими либо проблемами.
-
-## Namespace
-
-Неймспейсы это логические разделители уровня доступа.
-
-Я предпочитаю использовать разные неймспейсы под различные логические группы приложений.
-
-Например, в моём кластере есть отдельные неймспейсы для каждого приложения.
-
-Скажем, у меня есть проект, в котором есть база данных и веб сервер.
-Я держу их в одном неймспейсе, который называется также, как и приложени.
-
-А вот телеграм боты -- приложения достаточно лёгкие.
-Поэтому у меня есть неймспейс со всеми телеграм ботами.
-
-Эта штука позволяет просто лучше организовать свой кубернетес кластер.
-
-Также неймспейсы очень полезны для ограничивания возможностей
-конкретного пользователя.
-Например, вы можете создать правило, которое будет разрешать пользователю
-смотреть на поды в каком-то неймспейсе, но при этом ему нельзя будет
-что либо изменять.
-
-## Secret и ConfigMap
-
-Данные ресурсы нужны только чтобы хранить
-внутри кластера какую-нибудь информацию.
-
-Например вы можете сохранить в ConfigMap
-переменные среды и потом использовать их в подах в одном неймспейсе.
-
-В секреты обычно кидают сертификаты или какие-нибудь ключи.
-Но так как секреты не особо секретные принято использовать [Vault](https://www.vaultproject.io/docs/platform/k8s). Но так как эта статья затрагивает только
-основы рассматривать развертку и настройку Vault мы не будем, ну и также HashiCorp
-всё довольно подробно расписали сами.
-
-# Как развернуть k8s у себя
-
-Для локального кубера есть пара вариантов.
-
-- k3s (Недоступен под Windows)
-- minikube
-
-На первый взгляд minikube может показаться лучшим вариантом.
-И он действительно хорош тем, что его легко развернуть и почистить после
-своих экспериментов. Однако, там есть проблемы с ингрессами.
-По факту они не работают и там надо окольными путями получать
-адреса.
-
-
-
-k3s - это легковесная production-ready реализация k8s. Ingress у него работают
-отлично, поэтому я буду использовать его.
-
-
-
-Я не буду зацикливаться на установке `minikube`, так как
-он прост в установке и первоначальной настройке. Почитать подробнее можно в
-[официальном гайде от minikube](https://minikube.sigs.k8s.io/docs/start/).
-
-С `k3s` всё немного посложнее, но тоже достаточно просто, если немного разобраться. Первоначальная установка описана в [официальной доке k3s](https://rancher.com/docs/k3s/latest/en/installation/install-options/).
-
-## Подключение кластера
-
-После установки `kubectl` в домашней дериктории должен был быть
-сгенерирован файл `.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` надо посмотреть его конфиг
-и скопировать данные. Либо выставить переменную среды `KUBECONFIG`, которая будет
-указывать конфиг k3s. Конфиг подключения `k3s` лежит в файле `/etc/rancher/k3s/k3s.yaml`.
-
-Можете выполнить команду, которая будет просить kubectl использовать указанный конфиг:
-`export KUBECONFIG=/etc/rancher/k3s/k3s.yaml`
-
-
-
-Либо скопируйте нужные данные для подключения себе в `.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
-```
-
-Либо у вас неверные данные для входа.
-Перепроверьте свой `.kube/config`.
-
-### Мониторинг кластера
-
-Для просмотра ресурсов и управления кластером используется `kubectl`.
-Но для того, чтобы с ней работать нужно получше понять, что вообще в кластере есть.
-
-Для этого я советую использовать [Lens](https://k8slens.dev/). Это крайне
-удобный интерфейс для управления своим кластером.
-Также там есть очень клёвая настройка, которая сама включит мониторинг
-потребления памяти и процессора для всех подов и кластера в общем.
-
-На локальной машине это не имеет смысла,
-а вот в проде было бы очень полезно.
-
-Выглядит Lens примерно так:
-
-
-
-Для изучения **крайне настоятельно рекомендую** настроить Lens.
-
-# Ваше первое приложение в кластере.
-
-Вы можете следовать статье, а можете подсмотреть весь код в [репозитории](https://github.com/s3rius/blog_examples/tree/master/req_counter).
-
-### Сервер
-
-Давайте создадим своё первое приложение.
-
-Для этого я буду использовать [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}`);
-});
-```
-
-Это всё. Сервер готов.
-
-
-Протестируем запуск сервера, выполнив команду ниже и открыв в
-своём любимом браузере 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
-```
-
-После того, как неймспейс создан самое время сделать последний штрих.
-
-
-
-```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, выбрать свой кластер из списка
-и посмотреть как там поживает ваше приложение.
-
-Также не забудьте указать неймспейс, в который вы деплоили приложение.
-
-Выглядит это чудо примерно так:
-
-
-
-
-
-
-
-Также можно использовать команду и вывести всё себе в терминал.
-
-```
-$ 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 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,
-дописав в конец файла следующее:
-
-```[/etc/hosts]
-127.0.0.1 req-couter.local
-```
-
-И теперь вы можете открыть в браузере своё приложение и увидеть,
-как кубернетес заботливо направляет трафик туда, куда вы хотите.
-
-
-
-
-### Как мне очистить мой кластер?
-
-Всё очень просто.
-
-Так как у нас имеются описния ресурсов, то мы
-можем удалить всё сразу используя команду
-
-```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 удалит все описанные ресурсы из вашего кластера.
-
-До новых встреч.
diff --git a/content/ru/traefik.md b/content/ru/traefik.md
deleted file mode 100644
index 04c0d7d..0000000
--- a/content/ru/traefik.md
+++ /dev/null
@@ -1,471 +0,0 @@
----
-title: Traefik - роутинг просто.
-description: Изучите как использовать traefik.
-position: 2
-category: DevOps
----
-
-# Traefik - роутинг просто
-
-Сегодня я бы хотел рассказать о такой клёвой штуке, как [Traefik](https://traefik.io/traefik/). Не так давно я перевёл все сервисы своего сервера на traefik и это буквально сделало жизнь проще. Сегодня я расскажу вам зачем он нужен, как его настроить, и покажу на примере, как именно он помогает. По факту, это nginx на стероидах, в плане конфигурации, потому что в traefik за тебя сделано гораздо больше.
-
-# Что такое traefik
-
-Traefik - это система, которая позволяет настроить маппинг между доменными именами и конкретными приложениями. Допустим, у вас есть контейнер frontend-приложения, которое вы хотели бы разместить на домене `myapp.com`, и также у вас есть контейнер backend-приложения, которое вы бы хотели разместить на домене `api.mayapp.com`. [Traefik](https://traefik.io/traefik/) поможет вам это сделать без лишних файлов конфигурации.
-
-## Сравнение с другими инструментами
-
-Если бы вы решили сделать это через [Nginx](https://www.nginx.com/), то вы бы создали новые файлы конфигураций под каждый домен, в папочке с конфигурациями для всех доменов, положили куда-нибудь сертификат от домена `*myapp.com` и подключали бы его вручную в каждом файле доменов. А если бы вам надо было увеличить количество контейнеров отдельного приложения, вы бы указывали сервера в директиве `upstream` и перечисляли там адреса инстансов приложения вручную.
-
-Любители [Apache HTTP Server](https://httpd.apache.org/) на этом моменте обычно берут дробовик и разносят себе голову хорошим зарядом дроби.
-
-А господа, которые используют [Traefik](https://traefik.io/traefik/), просто дописывают лейблы контейнеру в `docker-compose.yml` и идут дальше дегустировать вино.
-
-
- Если же ваше приложение построено на вебсокетах, то тут уже фанаты Nginx тянутся за дробовиком. Ну а для ценителей traefik ничего не меняется, ведь в нём встроена поддержка HTTP/2.0.
-
-
-
-
-Как можно видеть по данному изображению, есть 5 основных составляющих traefik, а именно:
-
-- entrypoints
-- routers
-- rules (часть routers)
-- middlewares (чать routers)
-- services
-
-### Entrypoint
-
-Являются основными слушателями входящих соединений. Через них проходит весь трафик. Если попробовать объяснить в двух словах: "Порт, на который должно прийти входящее соединение", - вполне себе неплохое объяснение. В данном туториале я создам 2 `entrypoint`, которые будут слушать на http и https.
-
-### Routers, Rules и Middlewares
-
-Роутер - связующее звено между `entrypoint` и сервисом, куда нужно направить трафик. Роутер хранит в себе информацию, куда направить входящий трафик, и
-правила, по которым можно определить, пускать ли трафик дальше.
-
-Rules - это и есть те самые правила, которые определяют, через какой роутер пустить трафик.
-Допустим, у вас в конфиге есть следующие правила:
-
-```
-Host(`myapp.com`) && (PathPrefix(`/api`) || PathPrefix(`/memes`))
-```
-
-Это можно читать как "Этот роутер пустит в моё приложение, если запрос пришёл на хост `myapp.com` и путь запроса начинается с `/api` или `/memes`"
-
-Довольно просто, не так ли?
-
-Ну а middlewares - это некоторая логика, что нужно выполнить с запросом до того, как он попадёт в ваше приложение. В этой статье я не буду рассказывать про существующие `middlewares`, тут потребуется самостоятельное ознакомление с [документацией](https://doc.traefik.io/traefik/middlewares/overview/).
-
-# Конфигурация
-
-
- В данной статье я буду использовать Docker для конфигурации traefik.
- Вы, конечно же, можете использовать локально установленную версию, всё будет работать, как часы.
-
-
-Для нашего основного traefik на сервере мы создадим небольшой docker-compose.yml, файл конфига и папочку с сертификатами.
-Структура будет следующая:
-
-```
-.
-├── certs
-│ ├── local.key
-│ └── local.pem
-├── config.toml
-└── docker-compose.yml
-```
-
-Теперь разберем каждый файл по порядку.
-Вот основной docker-compose нашего traefik.
-
-```yaml{}[docker-compose.yml]
----
-version: '3.7'
-
-services:
- traefik-proxy:
- # The official v2.0 Traefik docker image
- image: traefik:v2.4.8
- container_name: traefik_main
- 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
- - "80:80"
- # The HTTPS port
- - "443:443"
- restart: unless-stopped
- networks:
- - traefik-shared
- environment:
- MAIN_HOST: 192.168.1.89
- volumes:
- # Обязательный вольюм. Так траефик может слушать
- # события происходящие в демоне докера.
- - /var/run/docker.sock:/var/run/docker.sock
- # Вольюм с файлом конфигурации.
- - ./config.toml:/etc/traefik/dynamic/traefik.toml
- # Папка сертификатов.
- - ./certs:/etc/certs/
-
-networks:
- # Сетка, внутри которой будут находится приложения.
- traefik-shared:
- name: traefik-shared
-
-```
-
-Вот какие параметры я передаю в `traefik-cli`.
-
-Конечно, вы всегда можете глянуть `traefik --help` и подобрать себе желаемые параметры.
-
-Также из docker-compose файла видно, что я создал докер сеть `traefik-shared`, которую в дальнейшем буду использовать на всех контейнерах, которым требуется свой домен.
-
-Далее следует папка с сертификатами. Для реальных доменов я использую cloudflare и скачиваю сертификаты и ключи с панели администратора. Также не забываю выставлять мод шифрования на strict.
-
-
-
-Для генерации локальных сертификатов я использую тулу [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]
- # Эта опция вообще для strict cloudflare tls encryption,
- # Но я включаю её и на локальных доменах.
- sniStrict = true
-
-[[tls.certificates]]
- # Я тут указываю /etc/certs, потому что в docker-compose
- # у нас volume на эту папку.
- certFile = "/etc/certs/local.pem"
- keyFile = "/etc/certs/local.key"
-```
-
-
- Для добавления новых сертификатов в данный файл достаточно добавить:
-
-
-```toml
-[[tls.certificates]]
- certFile = "/etc/certs/"
- keyFile = "/etc/certs/"
-```
-
-После этого вы можете запускать traefik и наслаждаться доменами для ваших контейнеров.
-
-# Запуск приложений
-
-Теперь сконфигурируем приложение таким образом, чтобы к нему можно было обращаться через доменное имя.
-
-Для примера возьмем мелкое приложение на nodejs со следующей структурой проекта:
-
-```
-.
-├── docker-compose.yml
-├── Dockerfile
-├── .dockerignore
-├── index.js
-├── package.json
-└── yarn.lock
-```
-
-```js{}[index.js]
-const express = require('express')
-
-let req_count = 0;
-const hostname = process.env.HOSTNAME;
-const PORT = process.env.PORT || 3000;
-
-app = express();
-
-app.get("/", (req, res) => {
- console.log(`GET / ${hostname}`)
- res.send({request_num: req_count++, host: hostname})
-})
-
-app.listen(PORT, () => {
- console.log(`Server is listening on port ${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"
- }
-}
-```
-
-```dockerfile{}[Dockerfile]
-FROM node:16-alpine3.11
-
-WORKDIR /app
-COPY package.json yarn.lock /app/
-
-RUN yarn install
-
-COPY . /app/
-
-ENTRYPOINT ["yarn", "run"]
-```
-
-```yaml{}[docker-compose.yml]
----
-version: '3.7'
-
-
-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:
- - traefik-shared
-
-
-networks:
- # Докер сеть, в которой находится traefik.
- traefik-shared:
- name: traefik-shared
- external: true
-```
-
-
- Файл yarn.lock генерируется командой `yarn install`. Если у вас не установлен `yarn`, то ничего страшного. Просто это будет занимать чуть больше времени на сборку образа.
-
-
-Как вы видите, приложение слушает на порт `3000` и отвечает свим hostname и количеством обработанных запросов. А в docker-compose.yml, в отличие от обычного проекта, появились labels.
-
-
-
-
-Роутеры, сервисы и хосты явно создавать нигде не нужно.
-Они сами создаются, когда вы их указываете.
-
-Название сервиса и роутера могут совпадать.
-
-Обратите внимание на косые кавычки при указании хоста! Это обязательно.
-
-В объявлении labels могут быть использованы переменные среды. Например:
-`` traefik.http.routers.test_node.rule=Host(`${APP_HOST}`) ``
-
-
-
-Также можно видеть, что я подключил контейнер к сети, которую мы указывали в контейнере traefik. Здесь она помечена как external.
-
-Теперь мы можем спокойно запустить наш сервис. Для этого воспользуемся следующей командой:
-
-```bash
-docker-compose up --build --scale server=3
-```
-
-В данной команде мы указали, что хотим поднять 3 инстанса нашего сервиса. Остальным пусть занимается traefik.
-
-Теперь попробуем некоторое количество раз выполнить запрос на наш сервис.
-
-```console
-$ curl -H "Host: test_node.local" "http://localhost"
-{"request_num":0,"host":"7417ac8fda92"}
-```
-
-Результат должен быть примерно таким:
-
-[](/images/traefik_imgs/curls.gif)
-
-Как вы видите, traefik балансирует между контейнерами за нас. И я считаю, что это - победа.
-
-## Подключение TLS и сертификатов
-
-Тут всё не намного сложнее. Давайте немного поменяем лейблы нашего контейнера.
-
-```yaml
-services:
- server:
- labels:
- - traefik.enable=true
- - traefik.http.routers.test_node.rule=Host(`test_node.local`)
- - traefik.http.routers.test_node.entrypoints=https
- - traefik.http.routers.test_node.tls=true
- - traefik.http.routers.test_node.service=node_test
- - traefik.http.services.node_test.loadbalancer.server.port=3000
-```
-
-Как вы видите, я поменял entrypoints на `https`. Как вы можете помнить, это entrypoint, который слушает на 443 порт. И также я включил поддержку tls лейблом `traefik.http.routers..tls=true`
-
-На данном этапе вам потребуется добавить свой хост в `/etc/hosts`, если вы используете нормальную систему. Но если вы всё же на windows, то вам потребуется добавить правило в `C:\Windows\System32\drivers\etc\hosts`.
-
-И добавляем в конец файла запись:
-
-```
-127.0.0.1 test_node.local
-```
-
-И также вам потребуется cертификат на этот домен.
-Для этого:
-
-1. Создадим сертификат через `mkcert`, как упоминалось ранее;
-2. Поместим ключ и сертификат в папку certs;
-3. Добавим ключ и сертификат для вашего домена в config.toml (Формат указан выше).
-
-Теперь вы можете обращаться к вашему приложению напрямую через локальное доменное имя.
-
-```console
-$ 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
- [http.routers.]
- rule = "Host(`my_app.local`)"
- entrypoints = "https"
- service = ""
- [http.routers..tls]
-
-[http.services]
- # Define how to reach an existing service on our infrastructure
- [http.services..loadBalancer]
- [[http.services..loadBalancer.servers]]
- url = "http://192.168.1.89:8100"
-```
-
-# Создание локального DNS
-
-В данном пункте я бы хотел рассказать, как настроить свой DNS-сервер, чтобы ваши домены были доступны всем устройствам в локальной сети. Для этого я буду использовать dnsmasq. Пользователям винды он недоступен, поэтому советую развернуть маленький домашний сервер на линуксе.
-
-Для этого установите `dnsmasq` и найдите и раскомментируйте, либо добавьте следующие строчки в файл `/etc/dnsmasq.conf`:
-
-```conf{}[/etc/dnsmasq.conf]
-# Never forward plain names (without a dot or domain part)
-domain-needed
-# Never forward addresses in the non-routed address spaces.
-bogus-priv
-address=/.local/192.168.1.89
-address=/./
-```
-
-У меня traefik развернут на хосте `192.168.1.89`. У вас ip может отличаться.
-Чтобы это узнать, посмотрите свой ip через роутер или выполните `ip addr`.
-
-Вообще, `dnsmasq` парсит файл `/etc/hosts` и вы можете туда добавлять записи, типа:
-
-```
-192.168.1.1 mydomain.local
-```
-
-Но так как я указал `address`, то это необязательно. `dnsmasq` и без явного указания поддоменов должен будет работать отлично.
-
-Запустите `dnsmasq` в режиме сревиса:
-
-```bash
-sudo systemctl enable dnsmasq.service
-sudo systemctl start dnsmasq.service
-```
-
-Теперь пойдите в настройки вашего роутера и найдите DNS-сервера. Добавьте туда ваш ip. Также не забудьте сделать локальный ip вашего устройства статичным.
-
-Для примера, в роутерах keenetic это можно сделать, зарегистрировав устройство в меню 'Список устройств' и нажав на галочку 'Постоянный IP-адрес'.
-
-И добавьте свой DNS-сервер в список DNS серверов вашего роутера.
-
-
-Готово. Вы можете попробовать зайти на свой домен с другого устройства в локальной сети, и это должно работать.
-
-# Мониторинг работы traefik
-
-Вообще, traefik имеет WEB интерфейс для отслеживания его работы.
-Для того чтобы подключить его, давайте поменяем `docker-compose.yml`.
-
-```yaml
-services:
- traefik-proxy:
- # The official v2.0 Traefik docker image
- image: traefik:v2.4.8
- container_name: traefik_main
- labels:
- - traefik.enable=true
- - traefik.http.routers.traefik_router.rule=Host(`traefik.local`)
- - traefik.http.routers.traefik_router.service=api@internal
- - traefik.http.routers.traefik_router.entrypoints=https
- - traefik.http.routers.traefik_router.tls=true
-
- command:
- - --api.dashboard=true
- ...
-```
-
-Я добавил ключ `--api.dashboard=true` и лейблы для роута до `traefik`.
-Замечу, service определён заранее - это `api@internal`. Укажите его в `traefik.http.routers..service`.
-
-Теперь вы можете зайти на `traefik.local` через свой браузер и увидеть конфигурацию traefik.
-
-[](/images/traefik_imgs/traefik_web.png)
-
-До новых встреч.
diff --git a/jsconfig.json b/jsconfig.json
deleted file mode 100644
index 29037a6..0000000
--- a/jsconfig.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "compilerOptions": {
- "baseUrl": ".",
- "paths": {
- "~/*": ["./*"],
- "@/*": ["./*"],
- "~~/*": ["./*"],
- "@@/*": ["./*"]
- }
- },
- "exclude": ["node_modules", ".nuxt", "dist"]
-}
diff --git a/layouts/default.vue b/layouts/default.vue
deleted file mode 100644
index 0577cdf..0000000
--- a/layouts/default.vue
+++ /dev/null
@@ -1,112 +0,0 @@
-
-