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

@@ -0,0 +1,43 @@
# Используем официальный образ Golang
FROM golang:1.23.4-alpine AS builder
# Устанавливаем рабочую директорию
WORKDIR /build
# Копируем исходный код приложения в контейнер
COPY ./main.go ./
# Сборка приложения
RUN CGO_ENABLED=0 go build main.go
# # Отдельный этап сборки для уменьшения размера образа
# FROM alpine:3.20.3 AS runner
# # Определяем аргумент для имени пользователя
# ARG USERNAME=appuser
# ARG GROUPNAME=appgroup
# # Создаем пользователя и группу
# RUN addgroup -S ${GROUPNAME} && adduser -S ${USERNAME} -G ${GROUPNAME}
# # Устанавливаем рабочую директорию
# WORKDIR /app
# # Копируем скомпилированное приложение из предыдущего этапа
# COPY --from=builder /build/main ./
# # Меняем владельца файлов на нового пользователя
# RUN chown -R ${USERNAME}:${GROUPNAME} /app
# # Запускаем контейнер от имени нового пользователя
# USER ${USERNAME}
# # Запускаем healthcheck, проверяющий доступность веб-сервера на порту 8080
# HEALTHCHECK --interval=5s --timeout=5s --start-period=3s --retries=3 \
# CMD wget --quiet --tries=1 --spider http://localhost:8080/ || exit 1
# # Запускаем приложение при старте контейнера
# CMD ["./main"]
# Отдельный этап сборки для уменьшения размера образа
FROM scratch
# Создаем пользователя
USER 100:100
# Копируем скомпилированное приложение из предыдущего этапа
COPY --from=builder --chown=100:100 /build/main /
# Копируем curl для работы HEALTHCHECK
COPY --from=ghcr.io/tarampampam/curl:8.10.1 /bin/curl /bin/curl
# Запускаем healthcheck, проверяющий доступность веб-сервера на порту 8080
HEALTHCHECK --interval=5s --timeout=5s --start-period=3s --retries=3 \
CMD [ "curl", "--fail", "http://127.0.0.1:8080/" ]
# Запускаем приложение при старте контейнера
ENTRYPOINT ["/main"]

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

@@ -0,0 +1,52 @@
#!/bin/bash
# Эта строка настраивает оболочку так, чтобы она выходила из скрипта при возникновении ошибки в любой команде.
# Это помогает обнаружить и обрабатывать ошибки в скрипте.
set -e
# Проверяем состояние контейнера с именем "web-blue", чтобы определить, является ли он "здоровым" (healthy).
# Если это так, переменным NEW и OLD присваиваются значения "green" и "blue" соответственно, иначе наоборот.
if [ "$(docker compose ps web-blue | grep healthy)" ]
then
export NEW="green"
export OLD="blue"
else
export NEW="blue"
export OLD="green"
fi
# Выводим сообщение о том, какой профиль поднимается в данный момент (значение переменной NEW).
echo Поднимаю проект с профилем ${NEW}
# Эта команда использует docker-compose для запуска проекта с указанным профилем (значение переменной NEW),
# разворачивая контейнеры в фоновом режиме, пересоздавая их, удаляя сиротские контейнеры,
# масштабируя сервис "web" на три экземпляра и дожидаясь их запуска.
docker compose \
--profile ${NEW} \
up \
--detach \
--build \
--remove-orphans \
--scale web-${NEW}=3 \
--wait \
--quiet-pull
# Эта строка выводит сообщение о том, какие сервисы останавливаются в данный момент (значение переменной OLD).
echo Останавливаю сервисы ${OLD}
# Эта команда использует docker-compose для остановки сервиса "web" с именем, соответствующим значению переменной OLD.
docker compose stop \
web-${OLD}
# Эта строка выводит сообщение о том, какие сервисы удаляются в данный момент (значение переменной OLD).
echo Удаляю сервисы ${OLD}
# Эта команда использует docker-compose для принудительного удаления сервиса "web" с именем, соответствующим значению переменной OLD.
docker compose rm -f \
web-${OLD}
# Эта строка выводит сообщение о выводе списка всех контейнеров.
echo Список контейнеров
docker compose ps -a
# Эта команда выводит сообщение о том, что будут выведены журналы запуска для сервиса "web" с именем,
# соответствующим значению переменной NEW, и затем выводит эти журналы.
echo Журналы запуска web-${NEW}
docker compose logs web-${NEW}

View File

@@ -0,0 +1,56 @@
services:
# Сервис для развертывания приложения с профилем "blue"
web-blue: &web
build:
context: .
environment:
- VIRTUAL_HOST=web # Виртуальный хост для NGINX
- VIRTUAL_PORT=8080 # Виртуальный порт для NGINX
expose:
- 8080
deploy:
resources:
limits:
cpus: '0.5' # Лимитированный доступ к ресурсам CPU
memory: 32M # Лимит памяти
restart: always # Перезапускать сервис при падении
profiles:
- blue
# Сервис для развертывания приложения с профилем "green"
web-green:
<<: *web
# Используем настройки из сервиса web-blue
profiles:
- green
# NGINX-прокси
nginx-proxy:
image: nginxproxy/nginx-proxy:1.6.4-alpine
ports:
- 8080:8080 # Проксируем порт 8080 на хосте
healthcheck:
# Периодичность проверки состояния (5 секунд)
interval: 5s
# Максимальное время ожидания ответа (5 секунд)
timeout: 5s
# Количество попыток в случае неудачной проверки (5 попыток)
retries: 5
# Время ожидания перед началом проверок (3 секунды)
start_period: 5s
# Команда для выполнения теста
test: curl -f http://localhost:8080/ || exit 1
volumes:
# Монтируем сокет Docker
- /var/run/docker.sock:/tmp/docker.sock:ro
# Монтируем шаблон NGINX
- ./nginx.tmpl:/app/nginx.tmpl:ro
deploy:
resources:
limits:
cpus: '0.1' # Лимитированный доступ к ресурсам CPU
memory: 128M # Лимит памяти
restart: always
profiles:
- blue
- green

View File

@@ -0,0 +1,28 @@
package main
import (
"fmt"
"net/http"
"os"
)
func main() {
// Обработчик запросов
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Получаем имя хоста
hostname, err := os.Hostname()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Отправляем имя хоста в ответ
fmt.Fprintf(w, "Имя узла: %s\n", hostname)
})
// Запуск веб-сервера на порту 8080
fmt.Println("Сервер запущен на порту 8080...")
if err := http.ListenAndServe(":8080", nil); err != nil {
fmt.Printf("Ошибка запуска сервера: %s\n", err)
}
}

View File

@@ -0,0 +1,22 @@
{{ range $host, $containers := groupBy $ "Env.VIRTUAL_HOST" }}
upstream {{ $host }} {
{{ range $index, $value := $containers }}
{{ with $address := index $value.Addresses 0 }}
server {{ $value.Hostname }}:{{ $address.Port }};
{{ end }}
{{ end }}
}
# конфигурация веб-сервера
server {
listen 8080;
server_tokens off;
location / {
proxy_pass http://{{ $host }};
}
}
{{ end }}