Add uv example (#30)

Co-authored-by: Fedor Batonogov <f.batonogov@yandex.ru>
This commit is contained in:
github-actions[bot]
2024-12-23 17:33:29 +03:00
committed by GitHub
parent a998a1bfdb
commit ea0d72d879
17 changed files with 1950 additions and 130 deletions

View File

@@ -19,7 +19,7 @@ repos:
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/ansible-community/ansible-lint.git - repo: https://github.com/ansible-community/ansible-lint.git
rev: v24.10.0 rev: v24.12.2
hooks: hooks:
- id: ansible-lint - id: ansible-lint
exclude: ^talos/ exclude: ^talos/
@@ -31,8 +31,16 @@ repos:
- id: tofu_validate - id: tofu_validate
- repo: https://github.com/igorshubovych/markdownlint-cli - repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.42.0 rev: v0.43.0
hooks: hooks:
- id: markdownlint - id: markdownlint
args: ["--fix"] args: [--fix]
files: \.md$ files: \.md$
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.14.0
hooks:
- id: pretty-format-yaml
args: [--autofix, --indent, '2', --offset, '2']
- id: pretty-format-toml
args: [--autofix]

View File

@@ -1,133 +1,20 @@
# Docker # Docker
## Описание приложения ## Blue-green deployment
Простой веб сервис написанный на **Go**, возвращающий имя узла. Сине-зеленое развертывание — это стратегия развертывания, при которой вы создаете два отдельных, но идентичные среды.
Одна среда (синяя) использует текущую версию приложения и одна среда (зеленая) использует новую версию приложения.
Использование синего/зеленого развертывания стратегия повышает доступность приложений
и снижает риск развертывания за счет упрощения процесс отката в случае сбоя развертывания.
После завершения тестирования на зеленом среда, трафик активных приложений направляется в зеленую среду и синюю среда устарела.
Сервис можно запустить несколькими способами. [Blue-green deployment](./blue-green-deployment/) - это программная платформа для быстрой сборки, отладки и развертывания приложений с помощью **контейнеров**.
### Локальный запуск ## uv
Сборка приложения: Чрезвычайно быстрый менеджер пакетов и проектов Python, написанный на Rust.
```sh Один инструмент для замены **pip**, **pip-tools**, **pipx**, **poetry**, **pyenv**, **twine**, **virtualenv**, и многое другое.
go build main.go
```
Запуск [uv](./uv/)
[Официальный GitHub репозиторий](https://github.com/astral-sh/uv)
```sh
./main
```
Вывод
```output
Сервер запущен на порту 8080...
```
Проверка
```sh
curl localhost:8080
```
Вывод
```output
Имя узла: MacBook-Pro-Fedor.local
```
### Запуск в контейнере
```sh
docker build -t test . && docker run --hostname my_container --publish 8080:8080 test
```
Вывод
```output
Сервер запущен на порту 8080...
```
Проверка
```sh
curl localhost:8080
```
Вывод
```output
Имя узла: my_container
```
### Запуск при помощи docker compose
Запуск
```sh
docker compose --profile blue up --wait --remove-orphans --scale web-blue=3
```
Вывод
```output
✔ Network docker_default Created
✔ Container docker-web-blue-1 Healthy
✔ Container docker-nginx-proxy-1 Healthy
✔ Container docker-web-blue-3 Healthy
✔ Container docker-web-blue-2 Healthy
```
Проверка
Поскольку запущено несколько реплик сервиса, то при повторной проверке мы можем увидеть разное имена узлов
```sh
declare -A responses
count=0
while [ ${#responses[@]} -lt 3 ]; do
response=$(curl -s localhost:8080)
if [[ -z "${responses[$response]}" ]]; then
responses["$response"]=1
count=$((count + 1))
echo "Уникальный ответ $count: $response"
fi
done
```
Вывод
```output
Уникальный ответ 1: Имя узла: eeec60605b3c
Уникальный ответ 2: Имя узла: 1a0e8d8f0d64
Уникальный ответ 3: Имя узла: d5822762eab6
```
### Сине-зеленое развертывание
Запуск
```sh
bash ./deploy.sh
```
Вывод
```output
Список контейнеров
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
docker-nginx-proxy-1 nginxproxy/nginx-proxy:1.6.2-alpine "/app/docker-entrypo…" nginx-proxy 22 seconds ago Up 22 seconds (healthy) 0.0.0.0:80->80/tcp
docker-web-green-1 docker-web-green "./main" web-green 6 seconds ago Up 6 seconds (healthy) 8080/tcp
docker-web-green-2 docker-web-green "./main" web-green 6 seconds ago Up 6 seconds (healthy) 8080/tcp
docker-web-green-3 docker-web-green "./main" web-green 6 seconds ago Up 6 seconds (healthy) 8080/tcp
Журналы запуска web-green
web-green-3 | Сервер запущен на порту 8080...
web-green-2 | Сервер запущен на порту 8080...
web-green-1 | Сервер запущен на порту 8080...
```
[Проверка](README.md#L83)

View File

@@ -0,0 +1,133 @@
# Docker
## Описание приложения
Простой веб сервис написанный на **Go**, возвращающий имя узла.
Сервис можно запустить несколькими способами.
### Локальный запуск
Сборка приложения:
```sh
go build main.go
```
Запуск
```sh
./main
```
Вывод
```output
Сервер запущен на порту 8080...
```
Проверка
```sh
curl localhost:8080
```
Вывод
```output
Имя узла: MacBook-Pro-Fedor.local
```
### Запуск в контейнере
```sh
docker build -t test . && docker run --hostname my_container --publish 8080:8080 test
```
Вывод
```output
Сервер запущен на порту 8080...
```
Проверка
```sh
curl localhost:8080
```
Вывод
```output
Имя узла: my_container
```
### Запуск при помощи docker compose
Запуск
```sh
docker compose --profile blue up --wait --remove-orphans --scale web-blue=3
```
Вывод
```output
✔ Network docker_default Created
✔ Container docker-web-blue-1 Healthy
✔ Container docker-nginx-proxy-1 Healthy
✔ Container docker-web-blue-3 Healthy
✔ Container docker-web-blue-2 Healthy
```
Проверка
Поскольку запущено несколько реплик сервиса, то при повторной проверке мы можем увидеть разное имена узлов
```sh
declare -A responses
count=0
while [ ${#responses[@]} -lt 3 ]; do
response=$(curl -s localhost:8080)
if [[ -z "${responses[$response]}" ]]; then
responses["$response"]=1
count=$((count + 1))
echo "Уникальный ответ $count: $response"
fi
done
```
Вывод
```output
Уникальный ответ 1: Имя узла: eeec60605b3c
Уникальный ответ 2: Имя узла: 1a0e8d8f0d64
Уникальный ответ 3: Имя узла: d5822762eab6
```
### Сине-зеленое развертывание
Запуск
```sh
bash ./deploy.sh
```
Вывод
```output
Список контейнеров
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
docker-nginx-proxy-1 nginxproxy/nginx-proxy:1.6.2-alpine "/app/docker-entrypo…" nginx-proxy 22 seconds ago Up 22 seconds (healthy) 0.0.0.0:80->80/tcp
docker-web-green-1 docker-web-green "./main" web-green 6 seconds ago Up 6 seconds (healthy) 8080/tcp
docker-web-green-2 docker-web-green "./main" web-green 6 seconds ago Up 6 seconds (healthy) 8080/tcp
docker-web-green-3 docker-web-green "./main" web-green 6 seconds ago Up 6 seconds (healthy) 8080/tcp
Журналы запуска web-green
web-green-3 | Сервер запущен на порту 8080...
web-green-2 | Сервер запущен на порту 8080...
web-green-1 | Сервер запущен на порту 8080...
```
[Проверка](README.md#L83)

View File

@@ -11,7 +11,7 @@ services:
deploy: deploy:
resources: resources:
limits: limits:
cpus: "0.5" # Лимитированный доступ к ресурсам CPU cpus: '0.5' # Лимитированный доступ к ресурсам CPU
memory: 32M # Лимит памяти memory: 32M # Лимит памяти
restart: always # Перезапускать сервис при падении restart: always # Перезапускать сервис при падении
profiles: profiles:
@@ -19,7 +19,8 @@ services:
# Сервис для развертывания приложения с профилем "green" # Сервис для развертывания приложения с профилем "green"
web-green: web-green:
<<: *web # Используем настройки из сервиса web-blue <<: *web
# Используем настройки из сервиса web-blue
profiles: profiles:
- green - green

183
docker/uv/.dockerignore Normal file
View File

@@ -0,0 +1,183 @@
# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json
# End of https://www.toptal.com/developers/gitignore/api/python
# Other
.gitignore
Dockerfile*
Makefile
README.md
pyproject.toml

176
docker/uv/.gitignore vendored Normal file
View File

@@ -0,0 +1,176 @@
# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json
# End of https://www.toptal.com/developers/gitignore/api/python

43
docker/uv/Dockerfile Normal file
View File

@@ -0,0 +1,43 @@
# Базовый образ
FROM python:3.13.1-alpine
# Устанавливаем переменные окружения
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
WORKDIR_PATH="/app" \
VENV_PATH="/opt/venv"
# Определяем аргументы для пользователя и группы
ARG USERNAME=appuser
ARG GROUPNAME=appgroup
# Создаем пользователя и рабочую директорию
RUN addgroup --system ${GROUPNAME} \
&& adduser --system --ingroup ${GROUPNAME} ${USERNAME} \
&& mkdir -p ${WORKDIR_PATH} ${VENV_PATH} \
&& chown -R ${USERNAME}:${GROUPNAME} ${WORKDIR_PATH} ${VENV_PATH}
# Устанавливаем рабочую директорию
WORKDIR ${WORKDIR_PATH}
# Копируем uv чрезвычайно быстрый менеджер пакетов и проектов Python
COPY --from=ghcr.io/astral-sh/uv:0.5.11 /uv /uvx /bin/
# Создаем виртуальное окружение и настраиваем PATH
RUN uv venv ${VENV_PATH}
ENV PATH="${VENV_PATH}/bin:$PATH"
# Копируем requirements, устанавливаем зависимости в venv и устанавливаем права
COPY --chown=${USERNAME}:${GROUPNAME} ./requirements.txt ./requirements.txt
RUN uv pip install --no-cache --require-hashes --requirements ./requirements.txt \
&& chown -R ${USERNAME}:${GROUPNAME} ${VENV_PATH}
# Копируем остальные файлы приложения
COPY --chown=${USERNAME}:${GROUPNAME} ./ ./
# Запускаем контейнер от имени нового пользователя
USER ${USERNAME}:${GROUPNAME}
# Настраиваем команду запуска
ENTRYPOINT ["uvicorn", "main:app"]
CMD [ "--host", "0.0.0.0", "--port", "8000" ]

40
docker/uv/Dockerfile.pip Normal file
View File

@@ -0,0 +1,40 @@
# Базовый образ
FROM python:3.13.1-alpine
# Устанавливаем переменные окружения
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
WORKDIR_PATH="/app" \
VENV_PATH="/opt/venv"
# Определяем аргументы для пользователя и группы
ARG USERNAME=appuser
ARG GROUPNAME=appgroup
# Создаем пользователя и рабочую директорию
RUN addgroup --system ${GROUPNAME} \
&& adduser --system --ingroup ${GROUPNAME} ${USERNAME} \
&& mkdir -p ${WORKDIR_PATH} ${VENV_PATH} \
&& chown -R ${USERNAME}:${GROUPNAME} ${WORKDIR_PATH} ${VENV_PATH}
# Устанавливаем рабочую директорию
WORKDIR ${WORKDIR_PATH}
# Создаем виртуальное окружение и настраиваем PATH
RUN python -m venv ${VENV_PATH}
ENV PATH="${VENV_PATH}/bin:$PATH"
# Копируем requirements, устанавливаем зависимости в venv и устанавливаем права
COPY --chown=${USERNAME}:${GROUPNAME} ./requirements.txt ./requirements.txt
RUN pip install --no-cache --require-hashes -r ./requirements.txt \
&& chown -R ${USERNAME}:${GROUPNAME} ${VENV_PATH}
# Копируем остальные файлы приложения
COPY --chown=${USERNAME}:${GROUPNAME} ./ ./
# Запускаем контейнер от имени нового пользователя
USER ${USERNAME}:${GROUPNAME}
# Настраиваем команду запуска
ENTRYPOINT ["uvicorn", "main:app"]
CMD [ "--host", "0.0.0.0", "--port", "8000" ]

16
docker/uv/Makefile Normal file
View File

@@ -0,0 +1,16 @@
reqs:
rm ./requirements.txt || true
uv pip compile --generate-hashes pyproject.toml --output-file ./requirements.txt
venv:
uv venv --python 3.13
uv pip install --require-hashes --requirements ./requirements.txt
. .venv/bin/activate
build:
time docker build --no-cache --file Dockerfile -t uv .
docker images
build-pip:
time docker build --no-cache --file Dockerfile.pip -t pip .
docker images

37
docker/uv/README.md Normal file
View File

@@ -0,0 +1,37 @@
# uv
Чрезвычайно быстрый менеджер пакетов и проектов Python, написанный на Rust.
Один инструмент для замены **pip**, **pip-tools**, **pipx**, **poetry**, **pyenv**, **twine**, **virtualenv**, и многое другое.
[Официальный GitHub репозиторий](https://github.com/astral-sh/uv)
## Как работаь с **uv**
[Установка **UV**](https://github.com/astral-sh/uv#installation)
- Создание окружения
```sh
make venv
```
- Обновление библиотек
Обноваляем версии зависимостей в **pyproject.toml** и запускаем
```sh
make reqs
```
Сборка образа с **uv** без кэша
```sh
make build
```
Сборка образа с **pip** без кэша
```sh
make build-pip
```

16
docker/uv/main.py Normal file
View File

@@ -0,0 +1,16 @@
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.post("/items/")
async def create_item(item: Item):
return item

77
docker/uv/pyproject.toml Normal file
View File

@@ -0,0 +1,77 @@
[project]
authors = [{name = "Fedor Batonogov", email = "f.batonogov@yandex.ru"}]
dependencies = [
"aioboto3==13.3.0",
"aiobotocore==2.16.0",
"aiofiles==24.1.0",
"aiohappyeyeballs==2.4.4",
"aiohttp==3.11.11",
"aioitertools==0.12.0",
"aiosignal==1.3.2",
"annotated-types==0.7.0",
"anyio==4.7.0",
"asgi-lifespan==2.1.0",
"asyncio==3.4.3",
"attrs==24.3.0",
"autoflake==2.3.1",
"black==24.10.0",
"boto3==1.35.81",
"botocore==1.35.81",
"certifi==2024.12.14",
"cfgv==3.4.0",
"charset-normalizer==3.4.0",
"click==8.1.8",
"contextvars==2.4",
"distlib==0.3.9",
"fastapi==0.115.6",
"filelock==3.16.1",
"flake8==7.1.1",
"frozenlist==1.5.0",
"h11==0.14.0",
"httpcore==1.0.7",
"httpx==0.28.1",
"identify==2.6.3",
"idna==3.10",
"immutables==0.21",
"iniconfig==2.0.0",
"jmespath==1.0.1",
"logging==0.4.9.6",
"loguru==0.7.3",
"mccabe==0.7.0",
"multidict==6.1.0",
"multipart==1.2.1",
"mypy-extensions==1.0.0",
"nodeenv==1.9.1",
"packaging==24.2",
"pathspec==0.12.1",
"platformdirs==4.3.6",
"pluggy==1.5.0",
"pre_commit==4.0.1",
"propcache==0.2.1",
"pycodestyle==2.12.1",
"pydantic==2.10.4",
"pydantic-settings==2.7.0",
"pydantic-core==2.27.2",
"pyflakes==3.2.0",
"pytest==8.3.4",
"pytest-asyncio==0.25.0",
"pytest-mock==3.14.0",
"python-dateutil==2.9.0.post0",
"python-dotenv==1.0.1",
"python-multipart==0.0.20",
"PyYAML==6.0.2",
"requests==2.32.3",
"s3transfer==0.10.4",
"six==1.17.0",
"sniffio==1.3.1",
"starlette==0.41.3",
"typing_extensions==4.12.2",
"urllib3==2.3.0 ",
"uvicorn==0.34.0",
"virtualenv==20.28.0",
"wrapt==1.17.0",
"yarl==1.18.3"
]
dynamic = ["version"]
name = "uv"
requires-python = ">=3.13"

1203
docker/uv/requirements.txt Normal file

File diff suppressed because it is too large Load Diff